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
- C/C++
- 10월
- 코딩 기초 트레이닝
- 백준
- c++
- 수학
- 프로그래머스
- 4월
- 입문
- 개인 프로젝트 - 런앤건
- 게임 엔진 공부
- 코딩 테스트
- 2024년
- 3월
- 2023년
- 유니티
- 골드메탈
- 2025년
- 5월
- 2월
- 2022년
- 6월
- 다이나믹 프로그래밍
- 개인 프로젝트
- 유니티 심화과정
- todolist
- 자료 구조
- 기초
- 단계별로 풀어보기
- 1월
Archives
- Today
- Total
기록 보관소
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: UI 로직 연결하기[B53] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
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 쿼터뷰 액션 게임: UI 로직 연결하기[B53]
1. 타이틀 카메라

- 기존의 Main Camera를 Game Camera로 이름 변경 후 복사해서 메뉴를 보여주는 Menu Camera로 이름을 바꿔준다.
- Menu Camera는 이전 시간에 만들었던 타이틀 UI를 보여주면서 기존의 게임 플레이 화면보다 더 높은 곳에서, 플레이어도 비활성화 시킨 상태로 화면을 보여줄 것이다. 또한 천천히 회전하면서 화면을 돌리는 것도 추가해서 대기 화면의 느낌을 살려줄 것이다.






- 애니메이션은 Menu Camera의 Rotation Y값을 초깃값 -20, 1초대에 20, 2초대에 다시 -20을 줘서 화면이 좌 우로 움직이도록 만들어준다. 실제 실행 속도는 위 애니메이터에서 속도를 0.1로 줄여뒀으니 애니메이션 편집 창에서 실행하는 것보다 느릴 것이다.



2. 최고 점수 기록
//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>();
PlayerPrefs.SetInt("MaxScore", 112500);
}
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;
}
}
}
- 대기 화면 타이틀 아래 점수를 수정하기 위해서 임의의 최고 기록 값을PlayerPrefs를 통해서 저장하도록 추가해주었다. 위 플레이어 스크립트에서 Awake() 항목에 한줄 추가했다. 이후 플레이어가 활성화된채로 잠시 실행하고 다시 종료하면 값이 저장된다. 저장된 값을 불러오는 것은 마찬가지로 플레이어 스크립트의 Awake()에서 한줄 추가해 Debug로 불러와주었다.
//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>();
Debug.Log(PlayerPrefs.GetInt("MaxScore"));
//PlayerPrefs.SetInt("MaxScore", 112500);
}
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;
}
}
}

- PlayerPrefs : 유니티에서 제공하는 간단한 저장 기능. 아래 함수들처럼 3가지 타입만 저장/불러오기 가능하다.
- .SetFloat / .SetInt / .SetString(string key, float/int/string value) : 해당 타입의 값을 key를 통해 저장하는 함수
- .GetFloat / .GetInt / .GetString(string key) : 해당 타입의 값을 key를 통해 불러오는 함수
3. 변수 세팅



//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public GameObject menuCam;
public GameObject gameCam;
public Player player;
public Boss boss;
public int stage;
public float playTime;
public bool isBattle;
public int enemyCntA;
public int enemyCntB;
public int enemyCntC;
public GameObject menuPanel;
public GameObject gamePanel;
public Text maxScoreTxt;
public Text scoreTxt;
public Text stageTxt;
public Text playTimeTxt;
public Text playerHealthTxt;
public Text playerAmmoTxt;
public Text playerCoinTxt;
public Image weapon1Img;
public Image weapon2Img;
public Image weapon3Img;
public Image weaponRImg;
public Text enemyATxt;
public Text enemyBTxt;
public Text enemyCTxt;
public RectTransform bossHealthGroup;
public RectTransform bossHealthBar;
}

4. 게임 시작

//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public GameObject menuCam;
public GameObject gameCam;
public Player player;
public Boss boss;
public int stage;
public float playTime;
public bool isBattle;
public int enemyCntA;
public int enemyCntB;
public int enemyCntC;
public GameObject menuPanel;
public GameObject gamePanel;
public Text maxScoreTxt;
public Text scoreTxt;
public Text stageTxt;
public Text playTimeTxt;
public Text playerHealthTxt;
public Text playerAmmoTxt;
public Text playerCoinTxt;
public Image weapon1Img;
public Image weapon2Img;
public Image weapon3Img;
public Image weaponRImg;
public Text enemyATxt;
public Text enemyBTxt;
public Text enemyCTxt;
public RectTransform bossHealthGroup;
public RectTransform bossHealthBar;
void Awake() {
maxScoreTxt.text = string.Format("{0:n0}", PlayerPrefs.GetInt("MaxScore")); //세자리 수마다 , 추가
}
public void GameStart() {
menuCam.SetActive(false);
gameCam.SetActive(true);
menuPanel.SetActive(false);
gamePanel.SetActive(true);
player.gameObject.SetActive(true);
}
}



5. 인게임 UI

//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 score;
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;
public 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 스크립트에서는 점수 관리를 위한 public int형 score 변수를 추가했고, 현재 장착 무기를 GameManager에서 알려주도록 equipWeapon 변수를 public으로 전환했다.
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour {
public GameObject menuCam;
public GameObject gameCam;
public Player player;
public Boss boss;
public int stage;
public float playTime;
public bool isBattle;
public int enemyCntA;
public int enemyCntB;
public int enemyCntC;
public GameObject menuPanel;
public GameObject gamePanel;
public Text maxScoreTxt;
public Text scoreTxt;
public Text stageTxt;
public Text playTimeTxt;
public Text playerHealthTxt;
public Text playerAmmoTxt;
public Text playerCoinTxt;
public Image weapon1Img;
public Image weapon2Img;
public Image weapon3Img;
public Image weaponRImg;
public Text enemyATxt;
public Text enemyBTxt;
public Text enemyCTxt;
public RectTransform bossHealthGroup;
public RectTransform bossHealthBar;
void Awake() {
maxScoreTxt.text = string.Format("{0:n0}", PlayerPrefs.GetInt("MaxScore")); //세자리 수마다 , 추가
}
public void GameStart() {
menuCam.SetActive(false);
gameCam.SetActive(true);
menuPanel.SetActive(false);
gamePanel.SetActive(true);
player.gameObject.SetActive(true);
}
void Update() {
if (isBattle)
playTime += Time.deltaTime;
}
void LateUpdate() {
//좌측 상단 UI
scoreTxt.text = string.Format("{0:n0}", player.score);
//우측 상단 UI
stageTxt.text = "STAGE " + stage;
int hour = (int)(playTime / 3600); //시
int min = (int)((playTime - hour * 3600) / 60); //분
int second = (int)(playTime % 60); //초
playTimeTxt.text = string.Format("{0:00}", hour) + ":" + string.Format("{0:00}", min) + ":" + string.Format("{0:00}", second);
//플레이어(좌측 하단) UI
playerHealthTxt.text = player.health + " / " + player.maxHealth;
playerCoinTxt.text = string.Format("{0:n0}", player.coin);
if (player.equipWeapon == null)
playerAmmoTxt.text = "- / " + player.ammo;
else if (player.equipWeapon.type == Weapon.Type.Melee)
playerAmmoTxt.text = "- / " + player.ammo;
else
playerAmmoTxt.text = player.equipWeapon.curAmmo + " / " + player.ammo;
//무기(중앙 하단) UI
weapon1Img.color = new Color(1, 1, 1, (player.hasWeapons[0] ? 1 : 0));
weapon2Img.color = new Color(1, 1, 1, (player.hasWeapons[1] ? 1 : 0));
weapon3Img.color = new Color(1, 1, 1, (player.hasWeapons[2] ? 1 : 0));
weaponRImg.color = new Color(1, 1, 1, (player.hasGrenades > 0 ? 1 : 0));
//몬스터 숫자(우측 하단) UI
enemyATxt.text = enemyCntA.ToString();
enemyBTxt.text = enemyCntB.ToString();
enemyCTxt.text = enemyCntC.ToString();
//보스 체력(중앙 상단) UI
bossHealthBar.localScale = new Vector3((float)boss.curHealth / boss.maxHealth, 1, 1);
}
}
- LateUpdate() : Update()가 끝난 후 호출되는 생명주기 함수. 이미 처리된 정보를 단순히 보여주기만 할 것이므로 인게임 UI의 정보들은 여기서 적용해준다.
6. 테스트













'유니티 프로젝트 > 3D 쿼터뷰 액션게임' 카테고리의 다른 글
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 게임 완성하기[BE5] (0) | 2022.04.13 |
---|---|
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 간단한 상점 만들기[B52] (0) | 2022.04.09 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: UI 배치하기 [B51] (0) | 2022.04.08 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 다양한 패턴을 구사하는 보스 만들기[B50] (0) | 2022.04.07 |
[Unity/유니티] 기초-3D 쿼터뷰 액션 게임: 다양한 몬스터 만들기[B49] (0) | 2022.04.03 |