Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- 입문
- 6월
- 4월
- 골드메탈
- 수학
- 2025년
- 다이나믹 프로그래밍
- 2월
- 기초
- 10월
- 2024년
- 1월
- 5월
- c++
- 게임 엔진 공부
- 개인 프로젝트
- 코딩 테스트
- 코딩 기초 트레이닝
- 프로그래머스
- 자료 구조
- 3월
- 유니티
- 백준
- 개인 프로젝트 - 런앤건
- C/C++
- 유니티 심화과정
- 2022년
- todolist
- 2023년
- 단계별로 풀어보기
Archives
- Today
- Total
기록 보관소
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 간단한 상점 만들기[B52] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2
유니티 강좌 기초 채널 Basic
유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤
www.youtube.com
3D 쿼터뷰 액션 게임: 간단한 상점 만들기[B52]
1. 상점 꾸미기








- 에셋 Models 폴더 안의 Luna 애니메이션을 추가하고, Transition을 위와같이 설정해준다. Trigger형 매개변수 doHello도 만들어준다. Transition 변경 내용은 아래와 같다.
- Any State -> Hello : doHello, Transition Duration을 0.1로 변경, Has Exit Time 체크 해제
- Hello -> Exit : Transition Duration을 0.1로 변경. 나머지는 건드리지 않음











2. UI 구축하기











3. 상점 출입
//Shop 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shop : MonoBehaviour {
public RectTransform uiGroup;
public Animator anim;
Player enterPlayer;
public void Enter(Player player) {
enterPlayer = player;
uiGroup.anchoredPosition = Vector3.zero;
}
public void Exit() {
anim.SetTrigger("doHello");
uiGroup.anchoredPosition = Vector3.down * 1000;
}
}
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public float speed;
public GameObject[] weapons;
public bool[] hasWeapons;
public GameObject[] grenades;
public int hasGrenades;
public GameObject grenadeObj;
public Camera followCamera;
public int ammo;
public int coin;
public int health;
public int maxAmmo;
public int maxCoin;
public int maxHealth;
public int maxHasGrenades;
float hAxis;
float vAxis;
bool wDown;
bool jDown;
bool fDown;
bool gDown;
bool rDown;
bool iDown;
bool sDown1; //망치
bool sDown2; //권총
bool sDown3; //기관총
bool isJump;
bool isDodge;
bool isSwap;
bool isReload;
bool isFireReady = true;
bool isBorder;
bool isDamage;
Vector3 moveVec;
Vector3 dodgeVec;
Animator anim;
Rigidbody rigid;
MeshRenderer[] meshs;
GameObject nearObject;
Weapon equipWeapon;
int equipWeaponIndex = -1;
float fireDelay;
void Awake() {
rigid = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>(); //Player 자식 오브젝트에 있으므로
meshs = GetComponentsInChildren<MeshRenderer>();
}
void Update() {
GetInput();
Move();
Turn();
Jump();
Grenade();
Attack();
Reload();
Dodge();
Swap();
Interation();
}
void GetInput() {
hAxis = Input.GetAxisRaw("Horizontal"); //좌우 방향키
vAxis = Input.GetAxisRaw("Vertical"); //상하 방향키
wDown = Input.GetButton("Walk"); //shift 키
jDown = Input.GetButtonDown("Jump"); //스페이스바
fDown = Input.GetButton("Fire1"); //마우스 왼쪽 클릭
gDown = Input.GetButtonDown("Fire2"); //마우스 오른쪽 클릭
rDown = Input.GetButtonDown("Reload"); //R키
iDown = Input.GetButtonDown("Interation"); //E키
sDown1 = Input.GetButton("Swap1"); //번호 1번 키
sDown2 = Input.GetButton("Swap2"); //번호 2번 키
sDown3 = Input.GetButton("Swap3"); //번호 3번 키
}
void Move() {
moveVec = new Vector3(hAxis, 0, vAxis).normalized; //normalized : 방향 값이 1로 보정된 벡터
if (isDodge) //회피 중일때는
moveVec = dodgeVec; //회피하는 중인 방향으로 유지
if (isSwap || isReload || !isFireReady) //무기 교체, 재장전, 공격 중일때는
moveVec = Vector3.zero; //멈추기
if (!isBorder)
transform.position += moveVec * speed * (wDown ? 0.3f : 1f) * Time.deltaTime;
anim.SetBool("isRun", (moveVec != Vector3.zero)); //이동을 멈추면
anim.SetBool("isWalk", wDown);
}
void Turn() {
//키보드로 회전
transform.LookAt(transform.position + moveVec); //나아갈 방향 보기
//마우스로 회전
if (fDown) {
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100)) {
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 0;
transform.LookAt(transform.position + nextVec);
}
}
}
void Jump() {
if (jDown && (moveVec == Vector3.zero) && !isJump && !isDodge && !isSwap) { //움직이지 않고 점프
rigid.AddForce(Vector3.up * 15, ForceMode.Impulse);
anim.SetBool("isJump", true);
anim.SetTrigger("doJump");
isJump = true;
}
}
void Grenade() {
if (hasGrenades == 0)
return;
if (gDown && !isReload && !isSwap) {
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100)) {
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 10;
GameObject instantGrenade = Instantiate(grenadeObj, transform.position, transform.rotation);
Rigidbody rigidGrenade = instantGrenade.GetComponent<Rigidbody>();
rigidGrenade.AddForce(nextVec, ForceMode.Impulse);
rigidGrenade.AddTorque(Vector3.back * 10, ForceMode.Impulse);
hasGrenades--;
grenades[hasGrenades].SetActive(false);
}
}
}
void Attack() {
if (equipWeapon == null)
return;
fireDelay += Time.deltaTime;
isFireReady = (equipWeapon.rate < fireDelay);
if (fDown && isFireReady && !isDodge && !isSwap && !isReload) {
equipWeapon.Use();
anim.SetTrigger(equipWeapon.type == Weapon.Type.Melee ? "doSwing" : "doShot");
fireDelay = 0;
}
}
void Reload() {
if (equipWeapon == null)
return;
if (equipWeapon.type == Weapon.Type.Melee)
return;
if (ammo == 0)
return;
if (rDown && !isJump && !isDodge && !isSwap && isFireReady) {
anim.SetTrigger("doReload");
isReload = true;
Invoke("ReloadOut", 3);
}
}
void ReloadOut() {
int reAmmo = (ammo < equipWeapon.maxAmmo) ? ammo : equipWeapon.maxAmmo;
equipWeapon.curAmmo = reAmmo;
ammo -= reAmmo;
isReload = false;
}
void Dodge() {
if (jDown && (moveVec != Vector3.zero) && !isJump && !isDodge && !isSwap) { //이동하면서 점프
dodgeVec = moveVec;
speed *= 2;
anim.SetTrigger("doDodge");
isDodge = true;
Invoke("DodgeOut", 0.4f);
}
}
void DodgeOut() {
speed *= 0.5f;
isDodge = false;
}
void Swap() {
if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0)) return;
if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1)) return;
if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2)) return;
int weaponIndex = -1;
if (sDown1) weaponIndex = 0;
if (sDown2) weaponIndex = 1;
if (sDown3) weaponIndex = 2;
if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge) {
if (equipWeapon != null)
equipWeapon.gameObject.SetActive(false);
equipWeaponIndex = weaponIndex;
equipWeapon = weapons[weaponIndex].GetComponent<Weapon>();
equipWeapon.gameObject.SetActive(true);
anim.SetTrigger("doSwap");
isSwap = true;
Invoke("SwapOut", 0.4f);
}
}
void SwapOut() {
isSwap = false;
}
void Interation() {
if (iDown && nearObject != null && !isJump && !isDodge) {
if (nearObject.tag == "Weapon") {
Item item = nearObject.GetComponent<Item>();
int weaponIndex = item.value;
hasWeapons[weaponIndex] = true;
Destroy(nearObject);
}
else if(nearObject.tag == "Shop") {
Shop shop = nearObject.GetComponent<Shop>();
shop.Enter(this);
}
}
}
void FreezeRotation() {
rigid.angularVelocity = Vector3.zero;
}
void StopToWall() {
Debug.DrawRay(transform.position, transform.forward * 5, Color.green);
isBorder = Physics.Raycast(transform.position, moveVec, 5, LayerMask.GetMask("Wall"));
}
void FixedUpdate() {
FreezeRotation();
StopToWall();
}
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.tag == "Floor") {
anim.SetBool("isJump", false);
isJump = false;
}
}
void OnTriggerEnter(Collider other) {
if (other.tag == "Item") {
Item item = other.GetComponent<Item>();
switch(item.type) {
case Item.Type.Ammo:
ammo += item.value;
if (ammo > maxAmmo)
ammo = maxAmmo;
break;
case Item.Type.Coin:
coin += item.value;
if (coin > maxCoin)
coin = maxCoin;
break;
case Item.Type.Heart:
health += item.value;
if (health > maxHealth)
health = maxHealth;
break;
case Item.Type.Grenade:
if (hasGrenades == maxHasGrenades)
return;
grenades[hasGrenades].SetActive(true);
hasGrenades += item.value;
if (hasGrenades > maxHasGrenades)
hasGrenades = maxHasGrenades;
break;
}
Destroy(other.gameObject);
}
else if (other.tag == "EnemyBullet") {
if (!isDamage) {
Bullet enemyBullet = other.GetComponent<Bullet>();
health -= enemyBullet.damage;
bool isBossAtk = other.name == "Boss Melee Area";
StartCoroutine(OnDamage(isBossAtk));
}
if (other.GetComponent<Rigidbody>() != null) //Rigidbody 유무를 판단(미사일)
Destroy(other.gameObject);
}
}
IEnumerator OnDamage(bool isBossAtk) {
isDamage = true;
foreach(MeshRenderer mesh in meshs) {
mesh.material.color = Color.yellow;
}
if (isBossAtk)
rigid.AddForce(transform.forward * -25, ForceMode.Impulse);
yield return new WaitForSeconds(1f);
isDamage = false;
foreach (MeshRenderer mesh in meshs) {
mesh.material.color = Color.white;
}
if (isBossAtk)
rigid.velocity = Vector3.zero;
}
void OnTriggerStay(Collider other) {
if (other.tag == "Weapon" || other.tag == "Shop")
nearObject = other.gameObject;
}
void OnTriggerExit(Collider other) {
if (other.tag == "Weapon")
nearObject = null;
else if (other.tag == "Shop") {
Shop shop = nearObject.GetComponent<Shop>();
shop.Exit();
nearObject = null;
}
}
}
- Shop 스크립트 파일의 함수들은 플레이어가 들어오고 나갈 때 UI를 위로 올리고 내리는 역할을 하며, Player 스크립트는 트리거와 Shop 태그, E키를 통해서 상점을 들어가거나 나와 이들을 발동시키도록 수정한다.










4. 아이템 구입
//Shop 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Shop : MonoBehaviour {
public RectTransform uiGroup;
public Animator anim;
public GameObject[] itemObj;
public int[] itemPrice;
public Transform[] itemPos;
public string[] talkData;
public Text talkText;
Player enterPlayer;
public void Enter(Player player) {
enterPlayer = player;
uiGroup.anchoredPosition = Vector3.zero;
}
public void Exit() {
anim.SetTrigger("doHello");
uiGroup.anchoredPosition = Vector3.down * 1000;
}
public void Buy(int index) {
int price = itemPrice[index];
if (price > enterPlayer.coin) {
StopCoroutine(Talk()); //플레이어가 같은 버튼을 계속 누를 수 있으므로
StartCoroutine(Talk());
return;
}
enterPlayer.coin -= price;
Vector3 ranVec = Vector3.right * Random.Range(-3, 3) + Vector3.forward * Random.Range(-3, 3);
Instantiate(itemObj[index], itemPos[index].position + ranVec, itemPos[index].rotation);
}
IEnumerator Talk() {
talkText.text = talkData[1];
yield return new WaitForSeconds(2f);
talkText.text = talkData[0];
}
}
- 아이템을 사면 플레이어의 보유 코인에 따라서 아이템을 살 수 있다면 아이템 가격만큼 깎고 지정했던 위치 근처 주변 랜덤한 위치에 아이템을 생성해 구입처리하고, 아이템을 살 수 없다면 지정한 대사가 출력되어 아이템 구입을 못하도록 추가했다.






5. 액션 제한
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public float speed;
public GameObject[] weapons;
public bool[] hasWeapons;
public GameObject[] grenades;
public int hasGrenades;
public GameObject grenadeObj;
public Camera followCamera;
public int ammo;
public int coin;
public int health;
public int maxAmmo;
public int maxCoin;
public int maxHealth;
public int maxHasGrenades;
float hAxis;
float vAxis;
bool wDown;
bool jDown;
bool fDown;
bool gDown;
bool rDown;
bool iDown;
bool sDown1; //망치
bool sDown2; //권총
bool sDown3; //기관총
bool isJump;
bool isDodge;
bool isSwap;
bool isReload;
bool isFireReady = true;
bool isBorder;
bool isDamage;
bool isShop;
Vector3 moveVec;
Vector3 dodgeVec;
Animator anim;
Rigidbody rigid;
MeshRenderer[] meshs;
GameObject nearObject;
Weapon equipWeapon;
int equipWeaponIndex = -1;
float fireDelay;
void Awake() {
rigid = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>(); //Player 자식 오브젝트에 있으므로
meshs = GetComponentsInChildren<MeshRenderer>();
}
void Update() {
GetInput();
Move();
Turn();
Jump();
Grenade();
Attack();
Reload();
Dodge();
Swap();
Interation();
}
void GetInput() {
hAxis = Input.GetAxisRaw("Horizontal"); //좌우 방향키
vAxis = Input.GetAxisRaw("Vertical"); //상하 방향키
wDown = Input.GetButton("Walk"); //shift 키
jDown = Input.GetButtonDown("Jump"); //스페이스바
fDown = Input.GetButton("Fire1"); //마우스 왼쪽 클릭
gDown = Input.GetButtonDown("Fire2"); //마우스 오른쪽 클릭
rDown = Input.GetButtonDown("Reload"); //R키
iDown = Input.GetButtonDown("Interation"); //E키
sDown1 = Input.GetButton("Swap1"); //번호 1번 키
sDown2 = Input.GetButton("Swap2"); //번호 2번 키
sDown3 = Input.GetButton("Swap3"); //번호 3번 키
}
void Move() {
moveVec = new Vector3(hAxis, 0, vAxis).normalized; //normalized : 방향 값이 1로 보정된 벡터
if (isDodge) //회피 중일때는
moveVec = dodgeVec; //회피하는 중인 방향으로 유지
if (isSwap || isReload || !isFireReady) //무기 교체, 재장전, 공격 중일때는
moveVec = Vector3.zero; //멈추기
if (!isBorder)
transform.position += moveVec * speed * (wDown ? 0.3f : 1f) * Time.deltaTime;
anim.SetBool("isRun", (moveVec != Vector3.zero)); //이동을 멈추면
anim.SetBool("isWalk", wDown);
}
void Turn() {
//키보드로 회전
transform.LookAt(transform.position + moveVec); //나아갈 방향 보기
//마우스로 회전
if (fDown) {
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100)) {
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 0;
transform.LookAt(transform.position + nextVec);
}
}
}
void Jump() {
if (jDown && (moveVec == Vector3.zero) && !isJump && !isDodge && !isSwap) { //움직이지 않고 점프
rigid.AddForce(Vector3.up * 15, ForceMode.Impulse);
anim.SetBool("isJump", true);
anim.SetTrigger("doJump");
isJump = true;
}
}
void Grenade() {
if (hasGrenades == 0)
return;
if (gDown && !isReload && !isSwap) {
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
if (Physics.Raycast(ray, out rayHit, 100)) {
Vector3 nextVec = rayHit.point - transform.position;
nextVec.y = 10;
GameObject instantGrenade = Instantiate(grenadeObj, transform.position, transform.rotation);
Rigidbody rigidGrenade = instantGrenade.GetComponent<Rigidbody>();
rigidGrenade.AddForce(nextVec, ForceMode.Impulse);
rigidGrenade.AddTorque(Vector3.back * 10, ForceMode.Impulse);
hasGrenades--;
grenades[hasGrenades].SetActive(false);
}
}
}
void Attack() {
if (equipWeapon == null)
return;
fireDelay += Time.deltaTime;
isFireReady = (equipWeapon.rate < fireDelay);
if (fDown && isFireReady && !isDodge && !isSwap && !isReload && !isShop) {
equipWeapon.Use();
anim.SetTrigger(equipWeapon.type == Weapon.Type.Melee ? "doSwing" : "doShot");
fireDelay = 0;
}
}
void Reload() {
if (equipWeapon == null)
return;
if (equipWeapon.type == Weapon.Type.Melee)
return;
if (ammo == 0)
return;
if (rDown && !isJump && !isDodge && !isSwap && isFireReady && !isShop) {
anim.SetTrigger("doReload");
isReload = true;
Invoke("ReloadOut", 3);
}
}
void ReloadOut() {
int reAmmo = (ammo < equipWeapon.maxAmmo) ? ammo : equipWeapon.maxAmmo;
equipWeapon.curAmmo = reAmmo;
ammo -= reAmmo;
isReload = false;
}
void Dodge() {
if (jDown && (moveVec != Vector3.zero) && !isJump && !isDodge && !isSwap) { //이동하면서 점프
dodgeVec = moveVec;
speed *= 2;
anim.SetTrigger("doDodge");
isDodge = true;
Invoke("DodgeOut", 0.4f);
}
}
void DodgeOut() {
speed *= 0.5f;
isDodge = false;
}
void Swap() {
if (sDown1 && (!hasWeapons[0] || equipWeaponIndex == 0)) return;
if (sDown2 && (!hasWeapons[1] || equipWeaponIndex == 1)) return;
if (sDown3 && (!hasWeapons[2] || equipWeaponIndex == 2)) return;
int weaponIndex = -1;
if (sDown1) weaponIndex = 0;
if (sDown2) weaponIndex = 1;
if (sDown3) weaponIndex = 2;
if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge) {
if (equipWeapon != null)
equipWeapon.gameObject.SetActive(false);
equipWeaponIndex = weaponIndex;
equipWeapon = weapons[weaponIndex].GetComponent<Weapon>();
equipWeapon.gameObject.SetActive(true);
anim.SetTrigger("doSwap");
isSwap = true;
Invoke("SwapOut", 0.4f);
}
}
void SwapOut() {
isSwap = false;
}
void Interation() {
if (iDown && nearObject != null && !isJump && !isDodge) {
if (nearObject.tag == "Weapon") {
Item item = nearObject.GetComponent<Item>();
int weaponIndex = item.value;
hasWeapons[weaponIndex] = true;
Destroy(nearObject);
}
else if(nearObject.tag == "Shop") {
Shop shop = nearObject.GetComponent<Shop>();
shop.Enter(this);
isShop = true;
}
}
}
void FreezeRotation() {
rigid.angularVelocity = Vector3.zero;
}
void StopToWall() {
Debug.DrawRay(transform.position, transform.forward * 5, Color.green);
isBorder = Physics.Raycast(transform.position, moveVec, 5, LayerMask.GetMask("Wall"));
}
void FixedUpdate() {
FreezeRotation();
StopToWall();
}
void OnCollisionEnter(Collision collision) {
if (collision.gameObject.tag == "Floor") {
anim.SetBool("isJump", false);
isJump = false;
}
}
void OnTriggerEnter(Collider other) {
if (other.tag == "Item") {
Item item = other.GetComponent<Item>();
switch(item.type) {
case Item.Type.Ammo:
ammo += item.value;
if (ammo > maxAmmo)
ammo = maxAmmo;
break;
case Item.Type.Coin:
coin += item.value;
if (coin > maxCoin)
coin = maxCoin;
break;
case Item.Type.Heart:
health += item.value;
if (health > maxHealth)
health = maxHealth;
break;
case Item.Type.Grenade:
if (hasGrenades == maxHasGrenades)
return;
grenades[hasGrenades].SetActive(true);
hasGrenades += item.value;
if (hasGrenades > maxHasGrenades)
hasGrenades = maxHasGrenades;
break;
}
Destroy(other.gameObject);
}
else if (other.tag == "EnemyBullet") {
if (!isDamage) {
Bullet enemyBullet = other.GetComponent<Bullet>();
health -= enemyBullet.damage;
bool isBossAtk = other.name == "Boss Melee Area";
StartCoroutine(OnDamage(isBossAtk));
}
if (other.GetComponent<Rigidbody>() != null) //Rigidbody 유무를 판단(미사일)
Destroy(other.gameObject);
}
}
IEnumerator OnDamage(bool isBossAtk) {
isDamage = true;
foreach(MeshRenderer mesh in meshs) {
mesh.material.color = Color.yellow;
}
if (isBossAtk)
rigid.AddForce(transform.forward * -25, ForceMode.Impulse);
yield return new WaitForSeconds(1f);
isDamage = false;
foreach (MeshRenderer mesh in meshs) {
mesh.material.color = Color.white;
}
if (isBossAtk)
rigid.velocity = Vector3.zero;
}
void OnTriggerStay(Collider other) {
if (other.tag == "Weapon" || other.tag == "Shop")
nearObject = other.gameObject;
}
void OnTriggerExit(Collider other) {
if (other.tag == "Weapon")
nearObject = null;
else if (other.tag == "Shop") {
Shop shop = nearObject.GetComponent<Shop>();
shop.Exit();
isShop = false;
nearObject = null;
}
}
}
- 상점 구입을 위해서 버튼을 클릭할때, 플레이어가 공격하거나 공격 모션을 취하는 경우가 있어 Player 스크립트에 bool형 변수를 추가해서 이를 방지하였다.
'유니티 프로젝트 > 3D 쿼터뷰 액션게임' 카테고리의 다른 글
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 게임 완성하기[BE5] (0) | 2022.04.13 |
---|---|
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: UI 로직 연결하기[B53] (0) | 2022.04.10 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: UI 배치하기 [B51] (0) | 2022.04.08 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 다양한 패턴을 구사하는 보스 만들기[B50] (0) | 2022.04.07 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 다양한 몬스터 만들기[B49] (0) | 2022.04.03 |