기록 보관소

[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 간단한 상점 만들기[B52] 본문

유니티 프로젝트/3D 쿼터뷰 액션게임

[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 간단한 상점 만들기[B52]

JongHoon 2022. 4. 9. 23:29

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

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. 상점 꾸미기

Empty 생성
Cube 추가 및 설정
Cube에 적용시킬 Material 생성 및 설정
Table 설정
Table 위에 아이템들을 장식하고 Empty를 만들어 Group으로 묶어준다
상점 NPC Luna 생성 및 설정
애니메이터 Luna 생성
Luna Animator 설정

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

완성된 애니메이터는 Mesh Object에 넣어준다
플레이어가 들어가면 상호작용할 범위 생성 및 설정(Particle System)
트리거 발동을 위해 Sphere Collider 추가 및 Tag 설정
Shop 스크립트 파일 생성
Zone에 추가해준다
아이템 구입시 아이템을 생성할 위치 Spawn Pos A, B, C 생성 및 설정
아이템 상점을 복사해서 무기 상점으로 변경
Luna 대신 Ludo를 넣어준다
Ludo 애니메이터를 생성하고 애니메이션을 Ludo의 것으로 변경
상점 Table의 장식 아이템들도 Weapon Shop에 맞게 변경
다른 상점이므로 Zone 색깔만 변경


2. UI 구축하기

상점 UI를 관리할 Empty 생성 및 설정
Group 배경으로 사용할 Image 컴포넌트 추가 및 설정
버튼 생성 및 설정
버튼 아래에 가격을 표시할 Text를 하나 더 추가해준다
판매할 아이템과 가격 표시를 위한 Image를 2개 더 생성하고 설정해준다
완성된 아이템 버튼 그룹의 앵커를 위쪽으로 잡아주고 여백을 설정해준다
Item Button A를 2개 더 복사해서 각각 Item Button B와 C로 수정하고 Image와 Text를 변경한다
상점을 나갈 수 있는 버튼을 만들어서 설정한다. 아래 Text는 삭제하고, 버튼 클릭시 변경될 색상들을 모두 지정해준다.
NPC 초상화로 사용할 Image를 추가하고 설정해준다. 앵커는 왼쪽 하단에 잡아준다.
NPC의 대화창으로 사용할 Text를 추가하고 설정해준다. 앵커는 아래쪽 가득 채우기로 해주었다.
완성된 Item Shop Group을 복사해서 Weapon Shop Group으로 수정해준다


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키를 통해서 상점을 들어가거나 나와 이들을 발동시키도록 수정한다.

Item Shop의 Zone의 Shop 스크립트에 변수 채워주기
Weapon Shop도 마찬가지로 변수들을 채워준다
아이템 상점 UI의 Exit 버튼의 On Click()에 Shop 스크립트의 Exit() 함수를 걸어둔다
무기 상점 UI의 Exit Button도 똑같이 만들어준다
들어가서 E키를 누르면
이렇게 상점 화면이 뜬다
Exit 버튼을 눌러 나오니 손을 흔드는 애니메이션이 발동된다
트리거 밖으로 나와도 창이 닫기면서 상인 애니메이션이 발동한다
무기 상점도 마찬가지로 UI가 잘 뜬다
잘 안보이지만 나오니 고개를 흔드는 애니메이션이 나온다


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];
	}
}
  • 아이템을 사면 플레이어의 보유 코인에 따라서 아이템을 살 수 있다면 아이템 가격만큼 깎고 지정했던 위치 근처 주변 랜덤한 위치에 아이템을 생성해 구입처리하고, 아이템을 살 수 없다면 지정한 대사가 출력되어 아이템 구입을 못하도록 추가했다.

Item Shop의 Zone 설정
Weapon Shop의 Zone 설정
상점 UI의 각 버튼들의 On Click()에 Shop.Buy 함수를 넣고 index값을 0~2까지 각 버튼에 맞게 입력해준다.
실행 후 아이템을 구입하니 아이템들이 생성된 모습
아이템을 구입할 돈이 모자라자 변경된 대사
2초 뒤 다시 원래 대사로 돌아왔다


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형 변수를 추가해서 이를 방지하였다.