🎮 Game Dev (게임개발)/PC (데스크탑, 노트북, 터치패널)
[3D 액션게임] 17. 게임 완성하기
- -
🔔 유튜브 크리에이터 골든메탈님의 유니티강의 3D 쿼터뷰 액션게임 [BE5] 를 보고 공부하여 작성한 게시글입니다! 🔔
드디어 마지막 시간이 다가왔네요, 이렇게 좋은 강의를 만들어 주신, 유튜브 골든메탈님께 감사드립니다.
Player 스크립트 전체보기
더보기
/*Player Script*/
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 GameManager gamemanager;
public SoundControl soundControl;
public int ammo;
public int health;
public int coin;
public int score;
public int maxAmmo;
public int maxHealth;
public int maxCoin;
public int maxHasGrenades;
float hAxis;
float vAxis;
bool rDown;
bool jDown;
bool fDown;
bool gDown;
bool reDown;
bool iDown;
bool sDown1;
bool sDown2;
bool sDown3;
bool isJump;
bool isDodge;
bool isSwap;
bool isReload = false;
bool isFireReady = true;
bool isBorder;
bool isDamage;
bool isShop;
bool isDead;
Vector3 moveVec;
Vector3 dodgeVec;
Rigidbody rigid;
Animator anim;
MeshRenderer[] meshs;
GameObject nearObject;
public Weapon equipWeapon;
int equipWeaponIndex = -1;
float fireDelay;
void Awake()
{
rigid = GetComponent<Rigidbody>();
anim = GetComponentInChildren<Animator>();
meshs = GetComponentsInChildren<MeshRenderer>();
}
// Update is called once per frame
void Update()
{
GetInput();
Move();
Turn();
Jump();
Grenade();
Attack();
Reload();
Dodge();
Swap();
Interation();
}
void GetInput()
{
// 키보드입력에 따라 0 ~ 1로 변환 left right up down
hAxis = Input.GetAxisRaw("Horizontal");
vAxis = Input.GetAxisRaw("Vertical");
rDown = Input.GetButton("Run");
jDown = Input.GetButtonDown("Jump");
reDown = Input.GetButtonDown("Reload");
fDown = Input.GetButton("Fire1");
gDown = Input.GetButtonDown("Fire2");
iDown = Input.GetButtonDown("Interation");
sDown1 = Input.GetButtonDown("Swap1");
sDown2 = Input.GetButtonDown("Swap2");
sDown3 = Input.GetButtonDown("Swap3");
}
void Move()
{
// x y z
moveVec = new Vector3(hAxis, 0, vAxis).normalized;
// 회피 시 업데이트 안되게
if (isDodge)
moveVec = dodgeVec;
// 스왑 및 공격 시 못움직이게
if (isSwap || !isFireReady || isDead)
moveVec = Vector3.zero;
// transform + 벽뚫방지
if (!isBorder)
transform.position += moveVec * (rDown ? 1.3f : 1f) * speed * Time.deltaTime;
// animator
anim.SetBool("isWalk", moveVec != Vector3.zero);
anim.SetBool("isRun", rDown);
}
void Turn()
{
// #1. 키보드에 의한 회전
transform.LookAt(transform.position + moveVec);
// #2. 마우스에 의한 회전
// Ray란?, ScreenPointToRay() : 스크린에서 월드로 Ray를 쏘는 함수
if (fDown && !isDead)
{
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
// out 키워드는, 반환값을 주어진 변수에 저장하는 키워드
if (Physics.Raycast(ray, out rayHit, 100))
{
Vector3 nextVec = rayHit.point;
nextVec.y = 0;
transform.LookAt(nextVec);
}
}
}
void Jump()
{
if (jDown && moveVec == Vector3.zero && !isJump && !isDodge && !isDead) {
// 물리엔진에 힘을 준다. 여기선 즉발적인 Impulse
rigid.AddForce(Vector3.up * 15, ForceMode.Impulse);
anim.SetBool("isJump", true);
anim.SetTrigger("doJump");
isJump = true;
soundControl.jumpSound.Play();
}
}
void Grenade()
{
if (hasGrenades == 0)
return;
if (gDown && !isReload && !isSwap && !isDead) {
Ray ray = followCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit rayHit;
// out 키워드는, 반환값을 주어진 변수에 저장하는 키워드
if (Physics.Raycast(ray, out rayHit, 100))
{
Vector3 nextVec = rayHit.point;
nextVec.y = 15;
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);
}
soundControl.ThrowSound.Play();
}
}
void Attack()
{
// 공격할 조건만 플레이어에 두고, 공격로직은 무기에 위임한다.
if (equipWeapon == null)
return;
fireDelay += Time.deltaTime;
isFireReady = equipWeapon.rate < fireDelay;
if (fDown && isFireReady && !isDodge && !isSwap && !isShop && !isDead) {
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 || equipWeapon.curAmmo == equipWeapon.maxAmmo)
return;
if (reDown && !isJump && !isDodge && !isSwap && isFireReady && !isShop && !isDead)
{
anim.SetTrigger("doReload");
isReload = true;
// Invoke("ReloadOut", 2f); -> 애니메이션 기능에 ReloadOut() 기능 구현했습니다.
}
}
public void ReloadOut()
{
int reAmmo = equipWeapon.maxAmmo - equipWeapon.curAmmo;
reAmmo = ammo < reAmmo ? ammo : reAmmo;
equipWeapon.curAmmo += reAmmo;
ammo -= reAmmo;
isReload = false;
}
void Dodge()
{
// 이동하면서 점프할때,
if (jDown && moveVec != Vector3.zero && !isJump && !isDodge && !isShop && !isDead)
{
dodgeVec = moveVec;
speed *= 2;
anim.SetTrigger("doDodge");
isDodge = true;
Invoke("DodgeOut", 0.5f); // 시간지연 라이브러리 기능
soundControl.dodgeSound.Play();
}
}
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;
// 1, 2 ,3 버튼 누를때
if ((sDown1 || sDown2 || sDown3) && !isJump && !isDodge && !isShop && !isDead) {
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 && !isDead) {
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;
}
soundControl.InteractSound.Play();
}
}
void FreezeRotation()
{
rigid.angularVelocity = Vector3.zero;
}
void StopToWall()
{
// DrawRay() : Scene내에 Ray를 보여주는 함수
//Debug.DrawRay(transform.position, transform.forward * 5, Color.green);
isBorder = Physics.Raycast(transform.position, transform.forward, 3, LayerMask.GetMask("Wall"));
}
void FixedUpdate()
{
FreezeRotation();
StopToWall();
}
// 충돌 시 이벤트 함수로 착지 구현
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Floor") {
isJump = false;
anim.SetBool("isJump", false);
}
}
// 아이템 획득
private 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;
break;
}
Destroy(other.gameObject);
soundControl.InteractSound.Play();
}
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)
Destroy(other.gameObject);
soundControl.playerOnDamageSound.Play();
}
}
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);
if (health <= 0 && !isDead)
OnDie();
yield return new WaitForSeconds(1f);
isDamage = false;
foreach (MeshRenderer mesh in meshs)
{
mesh.material.color = Color.white;
}
if (isBossAtk)
rigid.velocity = Vector3.zero;
}
void OnDie()
{
anim.SetTrigger("doDie");
isDead = true;
gamemanager.GameOver();
}
private void OnTriggerStay(Collider other)
{
if (other.tag == "Weapon" || other.tag == "Shop")
nearObject = other.gameObject;
}
private 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;
}
}
}
GameManager 스크립트 전체보기
더보기
/*GameObject Script*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public GameObject menuCam;
public GameObject gameCam;
public Player player;
public Boss boss;
public GameObject itemShop;
public GameObject weaponShop;
public GameObject startZone;
public int stage;
public float playTime;
public bool isBattle;
public int enemyCntA;
public int enemyCntB;
public int enemyCntC;
public int enemyCntD;
public Transform[] enemyZones;
public GameObject[] enemies;
public List<int> enemyList;
public GameObject menuPanel;
public GameObject gamePanel;
public GameObject overPanel;
public Text maxScoreTxt;
public Text scoreTxt;
public Text stageTxt;
public Text playerTimeTxt;
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;
public Text curScoreText;
public Text bestText;
void Awake()
{
enemyList = new List<int>();
maxScoreTxt.text = string.Format("{0:n0}", PlayerPrefs.GetInt("MaxScore"));
if (PlayerPrefs.HasKey("MaxScore"))
PlayerPrefs.SetInt("MaxScore", 0);
}
public void GameStart()
{
menuCam.SetActive(false);
gameCam.SetActive(true);
menuPanel.SetActive(false);
gamePanel.SetActive(true);
player.gameObject.SetActive(true);
}
public void GameOver()
{
gamePanel.SetActive(false);
overPanel.SetActive(true);
curScoreText.text = scoreTxt.text;
int maxScore = PlayerPrefs.GetInt("MaxScore");
if(player.score > maxScore) {
bestText.gameObject.SetActive(true);
PlayerPrefs.SetInt("MaxScore", player.score);
}
}
public void Restart()
{
SceneManager.LoadScene(0);
}
public void StageStart()
{
isBattle = true;
itemShop.SetActive(false);
weaponShop.SetActive(false);
startZone.SetActive(false);
player.transform.position = Vector3.up * 0.8f;
foreach (Transform zone in enemyZones)
zone.gameObject.SetActive(true);
StartCoroutine(InBattle());
}
public void StageEnd()
{
isBattle = false;
player.transform.position = Vector3.up * 0.8f;
itemShop.SetActive(true);
weaponShop.SetActive(true);
startZone.SetActive(true);
foreach (Transform zone in enemyZones)
zone.gameObject.SetActive(false);
stage++;
}
IEnumerator InBattle()
{
if(stage % 5 == 0) {
enemyCntD++;
GameObject instantEnemy = Instantiate(enemies[3], enemyZones[3].position, enemyZones[3].rotation);
Enemy enemy = instantEnemy.GetComponent<Enemy>();
enemy.Target = player.transform;
enemy.manager = this;
boss = instantEnemy.GetComponent<Boss>();
}
else{
for (int index = 0; index < stage; index++)
{
int ran = Random.Range(0, 3);
enemyList.Add(ran);
switch (ran)
{
case 0:
enemyCntA++;
break;
case 1:
enemyCntB++;
break;
case 2:
enemyCntC++;
break;
}
}
while (enemyList.Count > 0)
{
int ranZone = Random.Range(0, 4);
GameObject instantEnemy = Instantiate(enemies[enemyList[0]], enemyZones[ranZone].position, enemyZones[ranZone].rotation);
Enemy enemy = instantEnemy.GetComponent<Enemy>();
enemy.Target = player.transform;
enemy.manager = this;
enemyList.RemoveAt(0);
yield return new WaitForSeconds(4f);
}
}
while (enemyCntA + enemyCntB + enemyCntC + enemyCntD > 0) {
yield return null;
}
yield return new WaitForSeconds(4f);
boss = null;
StageEnd();
}
void Update()
{
if (isBattle)
playTime += Time.deltaTime;
}
void LateUpdate() //Update 끝난 후 호출되는 생명주기
{
// 상단 UI
scoreTxt.text = string.Format("{0:n0}", player.score);
stageTxt.text = "STAGE " + stage;
int hour = (int)(playTime / 3600);
int min = (int)((playTime - hour * 3600) / 60);
int sec = (int)(playTime % 60);
playerTimeTxt.text = string.Format("{0:00}", hour) + ":" + string.Format("{0:00}", min) + ":"+ string.Format("{0:00}", sec);
// 플레이어 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.3f);
weapon2Img.color = new Color(1, 1, 1, player.hasWeapons[1] ? 1 : 0.3f);
weapon3Img.color = new Color(1, 1, 1, player.hasWeapons[2] ? 1 : 0.3f);
weaponRImg.color = new Color(1, 1, 1, player.hasGrenades > 0 ? 1 : 0.3f);
// 몬스터 숫자 UI
enemyATxt.text = "x " + enemyCntA.ToString();
enemyBTxt.text = "x " + enemyCntB.ToString();
enemyCTxt.text = "x " + enemyCntC.ToString();
// 보스 체력 UI
if (boss != null)
{
bossHealthGroup.anchoredPosition = Vector3.down * 30;
bossHealthBar.localScale = new Vector3((float)boss.curHealth / boss.maxHealth, 1, 1);
}
else {
bossHealthGroup.anchoredPosition = Vector3.up * 300;
}
}
}
Enemy 스크립트 전체보기
더보기
/*Enemy Script*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : MonoBehaviour
{
public enum Type { A, B, C, D };
public Type enemyType;
public int maxHealth;
public int curHealth;
public int score;
public GameManager manager;
public Transform Target;
public BoxCollider melleArea;
public GameObject bullet;
public GameObject[] coins;
public bool isChase;
public bool isAttack;
public bool isDead;
public Rigidbody rigid;
public BoxCollider boxCollider;
public MeshRenderer[] meshs;
public NavMeshAgent nav;
public Animator anim;
void Awake()
{
rigid = GetComponent<Rigidbody>();
boxCollider = GetComponent<BoxCollider>();
// Material은 Mesh Renderer 컴포넌트에서 접근가능합니다.
meshs = GetComponentsInChildren<MeshRenderer>();
// 네비게이션
nav = GetComponent<NavMeshAgent>();
//애니메이션
anim = GetComponentInChildren<Animator>();
if(enemyType != Type.D)
Invoke("ChaseStart", 2);
}
void ChaseStart()
{
isChase = true;
anim.SetBool("isWalk", true);
}
void Update()
{
if (nav.enabled&& enemyType != Type.D) {
nav.SetDestination(Target.position);
nav.isStopped = !isChase;
}
}
void FreezeVelocity()
{
if (isChase) {
rigid.velocity = Vector3.zero;
rigid.angularVelocity = Vector3.zero;
}
}
void Targeting()
{
if (!isDead && enemyType != Type.D)
{
float targetRadius = 0;
float targetRange = 0;
switch (enemyType)
{
case Type.A:
targetRadius = 1.5f;
targetRange = 3f;
break;
case Type.B:
targetRadius = 1f;
targetRange = 12f;
break;
case Type.C:
targetRadius = 0.5f;
targetRange = 25f;
break;
}
RaycastHit[] rayHits =
Physics.SphereCastAll(transform.position,
targetRadius,
transform.forward,
targetRange,
LayerMask.GetMask("Player"));
if (rayHits.Length > 0 && !isAttack)
{
StartCoroutine(Attack());
}
}
}
IEnumerator Attack()
{
isChase = false;
isAttack = true;
anim.SetBool("isAttack", true);
switch (enemyType) {
case Type.A:
yield return new WaitForSeconds(0.2f);
melleArea.enabled = true;
yield return new WaitForSeconds(1f);
melleArea.enabled = false;
break;
case Type.B:
yield return new WaitForSeconds(0.1f);
rigid.AddForce(transform.forward * 20, ForceMode.Impulse);
melleArea.enabled = true;
yield return new WaitForSeconds(0.5f);
rigid.velocity = Vector3.zero;
melleArea.enabled = false;
yield return new WaitForSeconds(2f);
break;
case Type.C:
yield return new WaitForSeconds(0.5f);
GameObject instantBullet = Instantiate(bullet, transform.position, transform.rotation);
Rigidbody rigidBullet = instantBullet.GetComponent<Rigidbody>();
rigidBullet.velocity = transform.forward * 20;
yield return new WaitForSeconds(2f);
break;
}
isChase = true;
isAttack = false;
anim.SetBool("isAttack", false);
}
void FixedUpdate()
{
Targeting();
FreezeVelocity();
}
void OnTriggerEnter(Collider other)
{
if(other.tag == "Melee"){
Weapon weapon = other.GetComponent<Weapon>();
curHealth -= weapon.damage;
Vector3 reactVec = transform.position - other.transform.position;
StartCoroutine(OnDamage(reactVec, false));
}
else if(other.tag == "Bullet"){
Bullet bullet = other.GetComponent<Bullet>();
curHealth -= bullet.damage;
Vector3 reactVec = transform.position - other.transform.position;
Destroy(other.gameObject);
StartCoroutine(OnDamage(reactVec, false));
}
}
public void HitByGrenade(Vector3 explosionPos)
{
curHealth -= 100;
Vector3 reactVec = transform.position - explosionPos;
StartCoroutine(OnDamage(reactVec, true));
}
IEnumerator OnDamage(Vector3 reactVec, bool isGrenade)
{
foreach (MeshRenderer mesh in meshs)
mesh.material.color = Color.red;
yield return new WaitForSeconds(0.1f);
if(curHealth > 0) {
foreach (MeshRenderer mesh in meshs)
mesh.material.color = Color.white;
}
else {
if (!isDead)
{
foreach (MeshRenderer mesh in meshs)
mesh.material.color = Color.gray;
gameObject.layer = 12;
isDead = true;
isChase = false;
nav.enabled = false;
anim.SetTrigger("doDie");
Player player = Target.GetComponent<Player>();
player.score += score;
int ranCoin = Random.Range(0, 3);
Instantiate(coins[ranCoin], transform.position, Quaternion.identity);
yield return new WaitForSeconds(0.1f);
switch (enemyType)
{
case Type.A:
manager.enemyCntA--;
break;
case Type.B:
manager.enemyCntB--;
break;
case Type.C:
manager.enemyCntC--;
break;
case Type.D:
manager.enemyCntD--;
break;
}
}
// 죽었을 시, 넉백
if (isGrenade)
{
reactVec = reactVec.normalized;
reactVec += Vector3.up * 3;
rigid.freezeRotation = false; // freezeRotation
rigid.AddForce(reactVec * 5, ForceMode.Impulse);
rigid.AddTorque(reactVec * 15, ForceMode.Impulse);
}
else
{
reactVec = reactVec.normalized;
reactVec += Vector3.up;
rigid.AddForce(reactVec * 5, ForceMode.Impulse);
}
Destroy(gameObject, 4);
}
}
}
🧷 1. 스테이지 시작 및 종료
- Start Zone 오브젝트
- 전투를 하고, 스테이지를 플레이 하고 싶을 때, 이 안으로 들어가면 시작되는 zone입니다.
- 상점의 아무 zone을 복사 붙혀넣기 한 후, 속에 3D TEXT를 넣어줍니다.
- Monster Respawn Zone 오브젝트
- 파티클만 들어간 몬스터 리스폰 오브젝트를 만듭니다.
- Game Manager 스테이지 시작, 종료기능
public void StageStart()
{
isBattle = true;
itemShop.SetActive(false);
weaponShop.SetActive(false);
startZone.SetActive(false);
player.transform.position = Vector3.up * 0.8f;
foreach (Transform zone in enemyZones)
zone.gameObject.SetActive(true);
StartCoroutine(InBattle());
}
public void StageEnd()
{
isBattle = false;
player.transform.position = Vector3.up * 0.8f;
itemShop.SetActive(true);
weaponShop.SetActive(true);
startZone.SetActive(true);
foreach (Transform zone in enemyZones)
zone.gameObject.SetActive(false);
stage++;
}
- 게임이 시작되면, 상점과 start zone 은 비활성화 됩니다. enemyZone은 활성화 됩니다.
- 플레이어의 위치는 중앙으로 바뀌게 됩니다. 이후 InBattle() 코루틴이 시작됩니다.
- 게임이 종료되면 플레이어 위치는 중앙에 옵니다.
- 게임이 종료되면 상점과 start zone은 다시 활성화가 됩니다. enemyZone은 비활성화 됩니다.
- Start zone script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StratZone : MonoBehaviour
{
public GameManager manager;
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
manager.StageStart();
}
}
- start zone에 ontriggerEnter 메서드를 두어서, 플레이어가 닿을시에 StageStart() 함수가 일어나게 합니다.
🧷 2. 스테이지 시작 후 배틀 시작
- InBattle() 함수
IEnumerator InBattle()
{
if(stage % 5 == 0) {
enemyCntD++;
GameObject instantEnemy = Instantiate(enemies[3], enemyZones[3].position, enemyZones[3].rotation);
Enemy enemy = instantEnemy.GetComponent<Enemy>();
enemy.Target = player.transform;
enemy.manager = this;
boss = instantEnemy.GetComponent<Boss>();
}
else{
for (int index = 0; index < stage; index++)
{
int ran = Random.Range(0, 3);
enemyList.Add(ran);
switch (ran)
{
case 0:
enemyCntA++;
break;
case 1:
enemyCntB++;
break;
case 2:
enemyCntC++;
break;
}
}
while (enemyList.Count > 0)
{
int ranZone = Random.Range(0, 4);
GameObject instantEnemy = Instantiate(enemies[enemyList[0]], enemyZones[ranZone].position, enemyZones[ranZone].rotation);
Enemy enemy = instantEnemy.GetComponent<Enemy>();
enemy.Target = player.transform;
enemy.manager = this;
enemyList.RemoveAt(0);
yield return new WaitForSeconds(4f);
}
}
while (enemyCntA + enemyCntB + enemyCntC + enemyCntD > 0) {
yield return null;
}
yield return new WaitForSeconds(4f);
boss = null;
StageEnd();
}
- 코루틴으로 이루어지고, List에 몬스터 index를 저장합니다.
- 새로 태어난 기본 인스턴트 몬스터들은, target을 붙혀주고 manager도 붙혀줍니다.
- 기본 몬스터들은 리스트의 0번째 인덱스부터 게임 속 에 나오며, 리스트를 제거해가며 출현시켜줍니다.
- 그리고 적의 수를 세기위해서 enemy 스크립트에도 game manager을 붙혀서 죽었을시, 카운터를 줄여주는 코드도 넣어야합니다.
- 코루틴으로 이후에 적이 다 없을 때 까지, null로 막아주다가, 적의 수가 0이되고 4초후에 StageEnd()를 호출합니다.
- 5스테이지마다 보스는 따로 넣어줍니다. + BOSS 스크립트, target, manager 등등
🧷 3. 게임 오버 후, 게임 재시작
- 게임오버 UI
- 게임 오버 UI를 만들어줍니다
- Cur Score Image
- Cur Score Text
- ReStart Button
- Best Scroe Text
- Game Over Text
- 순으로 꾸며줄 예정입니다.
- 플레이어 죽을 시 게임오버 UI 연결
Enumerator OnDamage(bool isBossAtk)
{
if (health <= 0 && !isDead)
OnDie();
// yield return null;
}
void OnDie()
{
anim.SetTrigger("doDie");
isDead = true;
gamemanager.GameOver();
}
- 플레이어 스크립트에, 죽을 시 OnDie() 함수를 호출하여 Gameover() 함수도 호출해줍니다.
- 게임 재시작 연결
Restart Button
Restart()
public void Restart()
{
SceneManager.LoadScene(0);
}
- 게임 점수
void Awake()
{
if (PlayerPrefs.HasKey("MaxScore"))
PlayerPrefs.SetInt("MaxScore", 0);
}
public void GameOver()
{
gamePanel.SetActive(false);
overPanel.SetActive(true);
curScoreText.text = scoreTxt.text;
int maxScore = PlayerPrefs.GetInt("MaxScore");
if(player.score > maxScore) {
bestText.gameObject.SetActive(true);
PlayerPrefs.SetInt("MaxScore", player.score);
}
}
- 게임 시작 시에 MaxScroe을 세팅해줍니다. (Key가 없다면 새로운 최대 점수를 만들어 주는 것입니다.)
- 플레이어 점수는 저장되어 최댓값만 넣어주는 코드입니다.
- Game Manager 오브젝트 연결
오브젝트 연결을 잘해줘야합니다.
이후 여러 버그들을 고치고, 입맛대로 게임을 꾸민 후 빌드해주면 끝입니다.!!
저는 망치로 때릴 때, 몬스터의 수가 -가 되버리더라구요 그래서, enemy스크립트에 isDead 플래그를 세워, 죽는게 1번만 돌아가도록 하였습니다.
++ 이제 추가적으로, 난이도 조정과 음향을 맞게 넣으면 됩니다.
그리고 배경과 지형도 재밌게 꾸며보고 싶습니다.
이후에 3D 카메라기능을 설정한 후 생동감있는 fps 느낌도 내어볼 예정입니다.
추가적으로 세가지의 무기를 기본으로 업그레이드를 해가고 싶습니다.++
이를 이용하여 네트워크상에서 친구의 플레이어도 만나보고 싶네요.
출처: 골든메탈님 유튜브
https://www.youtube.com/watch?v=9g4prUqF2oA&list=PLO-mt5Iu5TeYkrBzWKuTCl6IUm_bA6BKy&index=17
'🎮 Game Dev (게임개발) > PC (데스크탑, 노트북, 터치패널)' 카테고리의 다른 글
[3D 액션게임] 19. 사운드 추가 (0) | 2022.04.25 |
---|---|
[3D 액션게임] 18. 버그 고치기 (0) | 2022.04.24 |
[3D 액션게임] 16. UI 로직 연결하기 (0) | 2022.04.13 |
[3D 액션게임] 15. 상점 만들기 (0) | 2022.04.11 |
[3D 액션게임] 14. UI 배치하기 (0) | 2022.04.09 |
Contents
소중한 공감 감사합니다