게임 소개
게임 이름: PO!SON
장르: 타워 디펜스, 생존
게임 목표: 마을이 몬스터의 공격에 당하고 독가스에 오염되는 가운데, 주인공이 몬스터를 막고 보스 몬스터를 잡아 약초를 모아 정화 가스를 제작하여 마을을 정화해서 마을을 구하는것이 목표다.
게임 특징: 기본적으로 타워 디펜스 게임이지만 정해진 칸에 타워를 설치해서 몬스터가 정해진 길을 따라오는 걸 막는 타워 디펜스 게임이 아니다. 타워가 아무데나 설치, 철거, 이동이 가능하며 몹도 랜덤으로 뱀파이어 서바이벌 같은 느낌으로 맵 가장자리에 스폰되서 타워와 플레이어한테 다가오게 된다.
제작 기간: 2022년 10월 28일 ~ 2023년 3월 14일
플레이
플레이는 아래 링크에서 할 수 있다.
https://wny0320.itch.io/poison
코드
아래는 동아리 프로젝트 내에서 직접 코드를 짠 부분만 정리해보았다.
이해가 안되거나 고치면 좋을점을 지적하는 것은 언제나 환영
Player.cs
플레이어에 관한 모든 움직임과 특징이 들어있다.
후에는 좀 더 모듈화를 시키면 좋을 것 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
//IMove 관련 필드
[SerializeField] [Tooltip("이동속도")] float moveSpeed;
[SerializeField] EnemySpawner enemySpawner;
Vector3 inputDir;
Vector3 moveDir;
CharacterController controller;
//Health 관련 필드
Health health;
int healthBuffer;
[SerializeField] Slider hpBar;
public bool playerRegenerating;
[Header("체력 자연 재생 타이머")]
[SerializeField] [Tooltip("맞지 않은 시간")] float healthTimer;
[SerializeField] [Tooltip("자연 재생까지 맞지 않아야 하는 시간")] float healthTime;
Coroutine healthUp;
//Pick 관련 필드
GameObject Hand = null;
float standardDistance = 1.2f;
//피격 관련 필드
[Header("")]
[Tooltip("플레이어가 피격 가능한지 유무")]
public bool canDamaged = true;
float unBeatTime = 1.0f;
Material material;
//Menu 관련 필드
GameObject menuUI;
GameObject selectUI;
GameObject buildUI;
GameObject manageUI;
GameObject manageSelectUI;
bool isEnabledMenuUI;
/*bool isEnabledSelectUI;
bool isEnabledBuildUI;
bool isEnabledRepairUI;*/
public int modeSelect;
public int manageSelect;
int manageSelectBuffer;
public bool buildMode;
public bool manageMode;
enum towerList
{
BasicTower = 0,
PunchTower = 1,
RangeTower = 2,
SlowTower = 3,
}
enum modeSelection
{
NonSelect = -1,
BuildMode = 0,
ManageMode = 1,
}
enum manageSelection
{
NonSelect = -1,
RepairMode = 0,
RemoveMode = 1,
UpgradeMode = 2,
}
enum towerAttackType
{
기본 = 1,
밀기 = 2,
광역 = 3,
슬로우 = 4,
}
[SerializeField]
GameObject[] towerObjList;
public int choice;
public int[] buildCostAdder;
//repair 관련 필드
GameObject manageTarget;
public bool manageOn;
Text lv;
Text attackType;
Text maxHP;
Text curHP;
Text power;
Text cost;
int layerMask = (1 << 6);
[SerializeField] Sprite[] towerSpriteList;
[SerializeField] Image towerImage;
[SerializeField] AudioSource source;
void Start()
{
//Move 필드 초기화
controller = GetComponent<CharacterController>();
//Health 필드 초기화
health = GetComponent<Health>();
healthBuffer = health.healthCheck;
//UnBeat에 쓰일 필드 초기화
material = GetComponent<Renderer>().material;
//repair 필드 초기화
manageTarget = null;
manageMode = false;
manageOn = false;
manageSelectBuffer = (int)manageSelection.NonSelect;
lv = GameObject.Find("Lv").GetComponent<Text>();
attackType = GameObject.Find("AttackType").GetComponent<Text>();
maxHP = GameObject.Find("Max").GetComponent<Text>();
curHP = GameObject.Find("Cur").GetComponent<Text>();
power = GameObject.Find("Power").GetComponent<Text>();
cost = GameObject.Find("Cost").GetComponent<Text>();
//Menu에 쓰일 필드 초기화
menuUI = GameObject.Find("MenuUI");
selectUI = GameObject.Find("SelectUI");
buildUI = GameObject.Find("BuildUI");
manageUI = GameObject.Find("ManageUI");
manageSelectUI = GameObject.Find("ManageSelectUI");
buildUI.SetActive(false);
manageUI.SetActive(false);
selectUI.SetActive(false);
manageSelectUI.SetActive(false);
menuUI.SetActive(false);
isEnabledMenuUI = false;
/*isEnabledSelectUI = true;
isEnabledBuildUI = false;
isEnabledRepairUI = false;*/
modeSelect = -1;
manageSelect = -1;
buildMode = false;
buildCostAdder = new int[4];
}
void Update()
{
Move();
OnPick();
Menu();
BuildMode();
ManageMode();
HealthRegenerate();
Clipping();
}
void LateUpdate()
{
PlayerHpCheck();
PlayerDead();
}
void Pick()
{
//플레이어 안에 게임 오브젝트를 하나 둠
//주울 수 있는 오브젝트 List인 PickList 안에 있는 물체에서 제일 가까운 물체의 거리와 Target 오브젝트를 찾음
float closeDistance = float.PositiveInfinity;
GameObject Target = null;
//손에 들고 있지 않은 경우
if (Hand == null)
{
/*foreach (GameObject i in GameManager.PickList)
//distance에 값을 두기
{
//PickList에 있는 물체들로 거리 측정 반복문
Vector3 targetPos = i.transform.position;
Vector3 playerPos = this.transform.position;
float nowDistacne = Vector3.Magnitude(playerPos - targetPos);
if (closeDistance > nowDistacne)
{
closeDistance = nowDistacne;
Target = i;
}
}*/
//PickList 사용 안한 충돌 물체 구하기
//기준 거리 내에 있는 Collider 추출해서 목록에 넣기
Collider[] collider = Physics.OverlapSphere(this.transform.position, standardDistance);
foreach (Collider i in collider)
{
Vector3 targetPos = i.gameObject.transform.position;
Vector3 playerPos = this.transform.position;
float nowDistacne = Vector3.Magnitude(playerPos - targetPos);
if (closeDistance > nowDistacne && (i.gameObject.tag == "Tower" || i.gameObject.tag == "Resource" || i.gameObject.tag == "Herb"))
{
closeDistance = nowDistacne;
Target = i.gameObject;
}
}
//collider가 IsTrigger든 아니든 줍고 내려놓을 수 있음, 단 플레이어의 위치에 내려놓는다면
//Trigger가 아닐시 플레이어가 움직일시 밀릴 수 있음
//기준 거리 안에 있다면(주울 수 있는 거리라면)
if (closeDistance <= standardDistance)
{
//Target의 이름별로 실행
if (Target.tag == "Tower")
{
Hand = Target;
Hand.transform.SetParent(this.transform);
Hand.SetActive(false);
//renderer가 자식한테도 있어서 타워가 포신이 안지워짐(그거까지 넣기)
/*TowerAttackType HandScript = Hand.GetComponentInChildren<TowerAttackType>();
TowerManager HandScript2 = Hand.GetComponentInChildren<TowerManager>();
HandScript.enabled = false;
HandScript2.enabled = false;
Renderer Handrenderer = Hand.GetComponent<Renderer>();
Handrenderer.enabled = false;*/
//오브젝트 콜라이더 제외 비활성화
//상속
}
if (Target.tag == "Resource")
{
GameManager.resource++;
//GameManager.PickList.Remove(Target);
Destroy(Target);
//자원 추가
}
if (Target.tag == "Herb")
{
GameManager.herb++;
Destroy(Target);
}
}
}
else
{
//물체를 들고 있는 경우(들 수 있는 물체가 타워만 우선 구현)
//타워 내려놓기, 설치방법에 따라 함수호출
//임시로 플레이어 위치에 내려놓기로 설정
Hand.transform.position = this.transform.position;
Hand.SetActive(true);
/*TowerAttackType HandScript = Hand.GetComponentInChildren<TowerAttackType>();
TowerManager HandScript2 = Hand.GetComponentInChildren<TowerManager>();
HandScript.enabled = true;
HandScript2.enabled = true;
Renderer Handrenderer = Hand.GetComponent<Renderer>();
Handrenderer.enabled = true;*/
Hand.transform.SetParent(null);
Hand = null;
}
}
void OnPick()
{
//space가 눌리면 Pick 함수 실행
if (Input.GetKeyDown(KeyCode.Space))
{
Pick();
}
}
public void ManageHP(int amount)
{
health.healthCheck += amount;
// amount는 음수, 양수로 받아 계산
}
//무적 코루틴
IEnumerator UnBeat()
{
canDamaged = false;
int blinkTime = (int)(unBeatTime / 0.25f);
for (int i = 0; i < blinkTime; i++)
{
if (i % 2 == 0)
{
material.color = new Color32(255, 100, 0, 255);
}
else
{
material.color = new Color32(255, 255, 255, 255);
}
yield return new WaitForSeconds(0.25f);
}
material.color = new Color32(255, 255, 255, 255);
canDamaged = true;
yield return null;
}
void Menu()
{
if (Input.GetKeyDown(KeyCode.B))
{
if (isEnabledMenuUI)
{
isEnabledMenuUI = false;
buildUI.SetActive(false);
manageUI.SetActive(false);
selectUI.SetActive(false);
manageSelectUI.SetActive(false);
menuUI.SetActive(false);
manageTarget = null;
manageMode = false;
modeSelect = (int)modeSelection.NonSelect;
manageSelectBuffer = (int)manageSelection.NonSelect;
TutorialUIOff();
}
else
{
isEnabledMenuUI = true;
menuUI.SetActive(true);
selectUI.SetActive(true);
}
}
if (modeSelect == (int)modeSelection.BuildMode)
{
buildUI.SetActive(true);
selectUI.SetActive(false);
modeSelect = (int)modeSelection.NonSelect;
TutorialUIOff();
}
if (modeSelect == (int)modeSelection.ManageMode)
{
selectUI.SetActive(false);
manageSelectUI.SetActive(true);
if (manageSelect != (int)manageSelection.NonSelect)
{
manageSelectUI.SetActive(false);
manageUI.SetActive(true);
manageMode = true;
modeSelect = (int)modeSelection.NonSelect;
manageSelectBuffer = manageSelect;
manageSelect = (int)manageSelection.NonSelect;
TutorialUIOff();
}
TutorialUIManageOff();
}
}
void TutorialUIOff()
{
if (SceneManager.GetActiveScene().name == "Tutorial")
{
MouseEvent ME = FindObjectOfType<MouseEvent>().GetComponent<MouseEvent>();
foreach (GameObject i in ME.eventList)
{
i.SetActive(false);
}
}
}
void TutorialUIManageOff()
{
if (SceneManager.GetActiveScene().name == "Tutorial")
{
MouseEvent ME = FindObjectOfType<MouseEvent>().GetComponent<MouseEvent>();
ME.eventList[6].SetActive(false);
}
}
void BuildMode()
{
if (buildMode)
{
switch (choice)
{
case (int)towerList.BasicTower:
buildMode = false;
if (GameManager.resource >= buildCostAdder[choice] + 6)
{
Instantiate(towerObjList[(int)towerList.BasicTower], transform.position, new Quaternion(0, 0, 0, 0));
GameManager.resource -= buildCostAdder[choice] + 6;
buildCostAdder[choice] += 3;
}
choice = -1;
break;
case (int)towerList.PunchTower:
buildMode = false;
if (GameManager.resource >= buildCostAdder[choice] + 15)
{
Instantiate(towerObjList[(int)towerList.PunchTower], transform.position, new Quaternion(0, 0, 0, 0));
GameManager.resource -= buildCostAdder[choice] + 15;
buildCostAdder[choice] += 5;
}
choice = -1;
break;
case (int)towerList.RangeTower:
buildMode = false;
if (GameManager.resource >= buildCostAdder[choice] + 9)
{
Instantiate(towerObjList[(int)towerList.RangeTower], transform.position, new Quaternion(0, 0, 0, 0));
GameManager.resource -= buildCostAdder[choice] + 9;
buildCostAdder[choice] += 4;
}
choice = -1;
break;
case (int)towerList.SlowTower:
buildMode = false;
if (GameManager.resource >= buildCostAdder[choice] + 18)
{
Instantiate(towerObjList[(int)towerList.SlowTower], transform.position, new Quaternion(0, 0, 0, 0));
GameManager.resource -= buildCostAdder[choice] + 18;
buildCostAdder[choice] += 6;
}
choice = -1;
break;
}
source.Play();
}
}
void VisualizeStatus(int select)
{
TowerManager targetTowerManager = manageTarget.GetComponent<TowerManager>();
int targetCurHP = targetTowerManager.hp.health;
int targetMaxHP = targetTowerManager.hp.healthCheck;
int targetPower = targetTowerManager.damage;
int targetAttackType = targetTowerManager.damageType;
int targetLv = targetTowerManager.towerLevel;
int targetCost = 0;
lv.text = targetLv.ToString();
curHP.text = targetCurHP.ToString();
maxHP.text = targetMaxHP.ToString();
power.text = targetPower.ToString();
switch (targetAttackType)
{
case (int)towerAttackType.기본:
attackType.text = "기본";
towerImage.sprite = towerSpriteList[0];
if (select == (int)manageSelection.RemoveMode)
targetCost = (int)((buildCostAdder[targetAttackType - 1] + 6) * 0.7);
if (select == (int)manageSelection.RepairMode)
targetCost = 10;
if (select == (int)manageSelection.UpgradeMode)
targetCost = targetTowerManager.upgradeCost[Mathf.Clamp(targetLv - 1, 0, 3)];
break;
case (int)towerAttackType.밀기:
attackType.text = "밀기";
towerImage.sprite = towerSpriteList[1];
if (select == (int)manageSelection.RemoveMode)
targetCost = (int)((buildCostAdder[targetAttackType - 1] + 9) * 0.7);
if (select == (int)manageSelection.RepairMode)
targetCost = 12;
if (select == (int)manageSelection.UpgradeMode)
targetCost = targetTowerManager.upgradeCost[Mathf.Clamp(targetLv - 1, 0, 3)];
break;
case (int)towerAttackType.광역:
attackType.text = "광역";
towerImage.sprite = towerSpriteList[2];
if (select == (int)manageSelection.RemoveMode)
targetCost = (int)((buildCostAdder[targetAttackType - 1] + 18) * 0.7);
if (select == (int)manageSelection.RepairMode)
targetCost = 14;
if (select == (int)manageSelection.UpgradeMode)
targetCost = targetTowerManager.upgradeCost[Mathf.Clamp(targetLv - 1, 0, 3)];
break;
case (int)towerAttackType.슬로우:
attackType.text = "슬로우";
towerImage.sprite = towerSpriteList[3];
if (select == (int)manageSelection.RemoveMode)
targetCost = (int)((buildCostAdder[targetAttackType - 1] + 15) * 0.7);
if (select == (int)manageSelection.RepairMode)
targetCost = 16;
if (select == (int)manageSelection.UpgradeMode)
targetCost = targetTowerManager.upgradeCost[Mathf.Clamp(targetLv - 1, 0, 3)];
break;
}
cost.text = targetCost.ToString();
}
void ManageMode()
{
if (manageMode)
{
if (manageTarget != null && manageTarget.tag == "Tower")
{
StartCoroutine("LoopViusalizeStatus");
}
if (Input.GetMouseButton(0))
{
Vector3 mousePos = Input.mousePosition;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100f, layerMask))
{
manageTarget = hit.transform.gameObject;
}
if (manageTarget != null && manageTarget.tag == "Tower")
{
VisualizeStatus(manageSelectBuffer);
manageOn = false;
}
}
if (manageOn && manageTarget.tag == "Tower")
{
if (manageSelectBuffer == (int)manageSelection.RepairMode)
{
TowerManager targetTowerManager = manageTarget.GetComponent<TowerManager>();
int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
switch (targetAttackType)
{
case (int)towerAttackType.기본:
if (GameManager.resource >= 10)
{
targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
GameManager.resource -= 10;
}
break;
case (int)towerAttackType.밀기:
if (GameManager.resource >= 12)
{
targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
GameManager.resource -= 12;
}
break;
case (int)towerAttackType.광역:
if (GameManager.resource >= 14)
{
targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
GameManager.resource -= 14;
}
break;
case (int)towerAttackType.슬로우:
if (GameManager.resource >= 16)
{
targetTowerManager.hp.healthCheck = targetTowerManager.hp.health;
GameManager.resource -= 16;
}
break;
}
//자원 감소 코드
//타워 매니저 데미지 타입으로 설정하면 될듯
}
if (manageSelectBuffer == (int)manageSelection.RemoveMode)
{
int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
switch (targetAttackType)
{
case (int)towerAttackType.기본:
GameManager.resource += (int)((buildCostAdder[targetAttackType - 1] + 6) * 0.7);
break;
case (int)towerAttackType.밀기:
GameManager.resource += (int)((buildCostAdder[targetAttackType - 1] + 9) * 0.7);
break;
case (int)towerAttackType.광역:
GameManager.resource += (int)((buildCostAdder[targetAttackType - 1] + 18) * 0.7);
break;
case (int)towerAttackType.슬로우:
GameManager.resource += (int)((buildCostAdder[targetAttackType - 1] + 15) * 0.7);
break;
}
GameObject.Destroy(manageTarget);
}
if (manageSelectBuffer == (int)manageSelection.UpgradeMode)
{
manageTarget.GetComponent<TowerManager>().UpgradeTower();
//int targetAttackType = manageTarget.GetComponent<TowerManager>().damageType;
//int targetLv = manageTarget.GetComponent<TowerManager>().towerLevel;
//int cost = 0;
//switch (targetAttackType)
//{
// case (int)towerAttackType.기본:
// cost = 4 + targetLv * 4;
// if (GameManager.resource >= cost && targetLv < 5)
// {
// manageTarget.GetComponent<TowerManager>().towerLevel += 1;
// GameManager.resource -= cost;
// }
// break;
// case (int)towerAttackType.밀기:
// cost = 7 + targetLv * 4;
// if (GameManager.resource >= cost && targetLv < 5)
// {
// manageTarget.GetComponent<TowerManager>().towerLevel += 1;
// GameManager.resource -= cost;
// }
// break;
// case (int)towerAttackType.광역:
// cost = 16 + targetLv * 4;
// if (GameManager.resource >= cost && targetLv < 5)
// {
// manageTarget.GetComponent<TowerManager>().towerLevel += 1;
// GameManager.resource -= cost;
// }
// break;
// case (int)towerAttackType.슬로우:
// cost = 13 + targetLv * 4;
// if (GameManager.resource >= cost && targetLv < 5)
// {
// manageTarget.GetComponent<TowerManager>().towerLevel += 1;
// GameManager.resource -= cost;
// }
// break;
//}
}
manageOn = false;
}
}
}
IEnumerator LoopViusalizeStatus()
{
VisualizeStatus(manageSelectBuffer);
yield return 0;
}
void Move()
{
inputDir = Vector3.zero;
if (Input.GetKey(KeyCode.A)) inputDir += Vector3.left;
if (Input.GetKey(KeyCode.D)) inputDir += Vector3.right;
if (Input.GetKey(KeyCode.S)) inputDir += Vector3.back;
if (Input.GetKey(KeyCode.W)) inputDir += Vector3.forward;
inputDir.Normalize();
moveDir = Vector3.MoveTowards(moveDir, inputDir, Time.deltaTime * 5);
controller.Move(moveDir * moveSpeed);
if (inputDir != Vector3.zero)
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation(inputDir), Time.deltaTime * 5);
}
void PlayerDead()
{
if (health.healthCheck <= 0)
{
gameObject.SetActive(false);
}
}
void PlayerHpCheck()
{
//체력이 변했는지 체크
if (healthBuffer != health.healthCheck)
{
if (healthBuffer > health.healthCheck)
{
//체력이 줄음
StartCoroutine(UnBeat());
}
//체력 변환 체크값을 현재 체력으로 바꿔줌
healthBuffer = health.healthCheck;
}
}
void HealthRegenerate()
{
healthTimer += Time.deltaTime;
if (!canDamaged)
{
healthTimer = 0f;
}
if (healthTimer > healthTime)
{
if (health.healthCheck < health.health && healthUp == null)
{
healthUp = StartCoroutine(HealthUp());
hpBar.gameObject.SetActive(true);
playerRegenerating = true;
}
if (health.healthCheck == health.health)
{
hpBar.gameObject.SetActive(false);
playerRegenerating = false;
}
}
}
IEnumerator HealthUp()
{
health.healthCheck += 1;
yield return new WaitForSeconds(1f);
healthUp = null;
}
void Clipping()
{
transform.position = new Vector3(Mathf.Clamp(transform.position.x, -enemySpawner.spawnPos[0] / 2, enemySpawner.spawnPos[0] / 2), transform.position.y,
Mathf.Clamp(transform.position.z, -enemySpawner.spawnPos[1] / 2, enemySpawner.spawnPos[1] / 2));
}
}
WaveManager.cs
웨이브의 시간이 될 때마다 웨이브를 소환해주는 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class WaveManager : MonoBehaviour
{
//웨이브 시간 관련 필드
[Header("웨이브 대기 시간")]
[SerializeField][Tooltip("현재 웨이브 남은 대기 시간")] float waveTime;
[SerializeField][Tooltip("웨이브 기본 대기 시간")] float waveTimer;
[SerializeField][Tooltip("웨이브 대기 시간 추가 가중치")] int timeAdder;
GameObject enemyAlive;
//현재 웨이브
public int wave;
public bool bossWave;
//코드 참조
WaveSystem waveSystem;
//UI
GameObject timer;
//정화 웨이브
[SerializeField] LabUI labUI;
void Start()
{
bossWave = false;
waveTime = 10f;
waveTimer = 10f;
timeAdder = 5;
timeAdder = 0;
wave = 0;
waveSystem = GameObject.Find("waveSystem").GetComponent<WaveSystem>();
timer = GameObject.Find("Timer");
}
void TimeAdd()
{
enemyAlive = GameObject.FindWithTag("Enemy");
//몹이 전부 죽은 경우 wave 카운트 다운
if (enemyAlive == null)
{
timer.SetActive(true);
waveTime -= Time.deltaTime;
}
else
{
timer.SetActive(false);
}
}
void waveStarter()
{
if (labUI.purifyBt)
{
wave = 100;
Debug.Log("정화 웨이브 시작");
waveTime += 30f;
}
if (waveTime < 0f)
{
if ((wave == 3 || wave == 5 || wave == 7 || wave == 9 || wave == 10) && bossWave)
{
bossWave = false;
waveSystem.bossStage = true;
}
else if (wave < 10)
{
wave += 1;
if (wave == 3 || wave == 5 || wave == 7 || wave == 9 || wave == 10) bossWave = true;
waveSystem.bossStage = false;
}
waveSystem.waveStage = wave;
Debug.Log(wave + " 웨이브 시작");
waveTime = waveTimer + (timeAdder * waveSystem.waveStage);
if (bossWave) waveTime -= timeAdder;
}
}
void VisualizeTimer()
{
if (!bossWave) timer.GetComponent<Text>().text = "다음 웨이브까지 " + (int)waveTime + "초";
else timer.GetComponent<Text>().text = "보스 등장까지 " + (int)waveTime + "초";
}
void Update()
{
TimeAdd();
waveStarter();
VisualizeTimer();
}
}
EnemySpawner.cs
몬스터를 사각형 맵 범위 밖에 랜덤한 위치에 스폰시켜주는 스포너 스크립트
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class EnemySpawner : MonoBehaviour
{
//적 프리팹 리스트
public GameObject[] enemyPrefab;
//적 스폰 타임
//public float maxSpawnTime;
//public float curSpawnTime;
//맵 크기 좌표 x,z
public int[] spawnPos = new int[2];
public float spawnRange;
public int selection;
private void Start()
{
}
// spawnPos = 1024 x 1080
// 1024 / 2 = -512 ~ 512
// 1080 / 2 = -540 ~ 540
// Random 0 1
// 0 : -pos
// float posX = Random.Range(-700f, -512f);
// 1 : +pos
// float posX = Random.Range(+512f, 700f);
public void EnemySpawn()
{
//정해진 적 선택 후 생성
GameObject selectPrefab = enemyPrefab[selection];
//소환할 좌표
float posX;
float posZ;
int pos = Random.Range(0, 4);
if (pos == 0)
{
posX = Random.Range((-spawnPos[0] / 2) - spawnRange, 0);
if(posX < -spawnPos[0] / 2)
{
posZ = Random.Range(0, spawnPos[1] / 2 + spawnRange);
}
else
{
posZ = Random.Range(spawnPos[1] / 2, spawnPos[1] / 2 + spawnRange);
}
}
else if (pos == 1)
{
posX = Random.Range(0, spawnPos[0] / 2 + spawnRange);
if (posX > spawnPos[0] / 2)
{
posZ = Random.Range(0, spawnPos[1] / 2 + spawnRange);
}
else
{
posZ = Random.Range(spawnPos[1] / 2, spawnPos[1] / 2 + spawnRange);
}
}
else if (pos == 2)
{
posX = Random.Range(0, spawnPos[0] / 2 + spawnRange);
if (posX > spawnPos[0] / 2)
{
posZ = Random.Range(-(spawnPos[1] / 2 + spawnRange), 0);
}
else
{
posZ = Random.Range(-(spawnPos[1] / 2 + spawnRange), -(spawnPos[1] / 2));
}
}
else
{
posX = Random.Range((-spawnPos[0] / 2) - spawnRange, 0);
if (posX < -spawnPos[0] / 2)
{
posZ = Random.Range(-(spawnPos[1] / 2 + spawnRange), 0);
}
else
{
posZ = Random.Range(-(spawnPos[1] / 2 + spawnRange), -(spawnPos[1] / 2));
}
}
Instantiate(selectPrefab, new Vector3(posX, 0.5f, posZ), Quaternion.identity);
}
public void Update()
{
}
}
WaveSystem.cs
웨이브의 몬스터 구성이 어떻게 되는지와 웨이브를 관리해주는 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WaveSystem : MonoBehaviour
{
EnemySpawner spawner;
GameObject poison;
public int waveStage;
public bool bossStage = false;
int[] SpawnNum = new int[5];
enum enemySelection
{
basic = 0,
rush = 1,
interrupt = 2,
tanker = 3,
healer = 4,
}
private void Start()
{
spawner = GameObject.Find("spawner").GetComponent<EnemySpawner>();
poison = GameObject.Find("Poison");
poison.SetActive(false);
waveStage = 0;
}
void ModifyWaveInfo()
{
if (waveStage == 1) SpawnNum = new int[] { 8, 3, 2, 0, 0 };
else if(waveStage == 2) SpawnNum = new int[] { 10, 3, 2, 1, 0 };
else if (waveStage == 3 && bossStage) SpawnNum = new int[] { 1, 0, 0, 0, 0 };
else if (waveStage == 3) SpawnNum = new int[] { 10, 4, 4, 2, 1 };
else if (waveStage == 4) SpawnNum = new int[] { 12, 5, 4, 2, 2 };
else if (waveStage == 5 && bossStage) SpawnNum = new int[] { 0, 1, 0, 0, 0 };
else if (waveStage == 5) SpawnNum = new int[] { 15, 5, 5, 3, 2 };
else if (waveStage == 6) SpawnNum = new int[] { 15, 6, 6, 3, 3 };
else if (waveStage == 7 && bossStage) SpawnNum = new int[] { 0, 0, 1, 0, 0 };
else if (waveStage == 7) SpawnNum = new int[] { 17, 7, 6, 4, 3 };
else if (waveStage == 8) SpawnNum = new int[] { 18, 7, 7, 4, 4 };
else if (waveStage == 9 && bossStage) SpawnNum = new int[] { 0, 0, 0, 1, 0 };
else if (waveStage == 9) SpawnNum = new int[] { 21, 8, 7, 5, 4 };
else if (waveStage == 10 && bossStage) SpawnNum = new int[] { 0, 0, 0, 0, 1 };
else if (waveStage == 10) SpawnNum = new int[] { 23, 8, 8, 5, 5 };
else if (waveStage == 100) SpawnNum = new int[] { 25, 10, 8, 7, 5 };
//개발 시간때문에 하드 코딩을 했지만 나중엔 하드코딩을 고쳐보아야한다.
SpawnWave(SpawnNum);
}
void SpawnWave(int[] roopTime)
{
for(int i = 0; i < 5; i++)
{
spawner.selection = i;
if (bossStage) spawner.selection += 5;
for(int j = 0; j < roopTime[i]; j++)
{
spawner.EnemySpawn();
}
}
waveStage = 0;
bossStage = false;
poison.SetActive(true);
}
private void Update()
{
if(waveStage != 0) ModifyWaveInfo();
}
}
Posion.cs
독가스가 커지는것과 플레이어를 따라가는 것을 작성한 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Poison : MonoBehaviour
{
GameObject objPlayer;
Player player;
[SerializeField]
float moveSpeed;
[SerializeField]
float growSpeed;
[SerializeField]
EnemySpawner spawner;
int posionDamage = -5;
// Start is called before the first frame update
void Start()
{
objPlayer = GameObject.Find("Player");
player = objPlayer.GetComponent<Player>();
transform.position = Vector3.zero;
PoisonSpawn();
}
void PoisonSpawn()
{
Vector3 direction = Vector3.zero;
direction.x = Random.Range(-spawner.spawnPos[0] / 2, spawner.spawnPos[0] / 2);
direction.z = Random.Range(-spawner.spawnPos[1] / 2, spawner.spawnPos[1] / 2);
transform.position = direction;
}
void PoisonMove()
{
Vector3 playerPos = new Vector3(objPlayer.transform.position.x, 1, objPlayer.transform.position.z);
transform.position = Vector3.MoveTowards(transform.position, playerPos, Time.deltaTime * moveSpeed);
}
void PoisonGrow()
{
if(transform.localScale.x < spawner.spawnPos[0] / 2)
transform.localScale = new Vector3(transform.localScale.x + growSpeed, 1, transform.localScale.z + growSpeed);
}
private void OnTriggerStay(Collider other)
{
if(other.tag == "Player" && player.canDamaged)
{
player.canDamaged = false;
player.ManageHP(posionDamage);
}
}
// Update is called once per frame
void Update()
{
PoisonMove();
PoisonGrow();
}
}
GameManager.cs
자원과 같은 부분을 담당하는 스크립트
GameManager라기보다 ResourceManager에 가깝다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
[Header("자원")]
[Tooltip("타워에 관련된 자원")]
[SerializeField] int reveal_resource;
public static int resource;
[Tooltip("클리어 정화가스")]
[SerializeField] int reveal_purifyRs;
[Tooltip("클리어 제작재료")]
[SerializeField] int reveal_herb;
public static int herb;
public static int purifyRs;
//public static List<GameObject> PickList = new List<GameObject>();
void Start()
{
ResourceSetting();
}
private void Awake()
{
Application.targetFrameRate = 50;
}
void Update()
{
SynchronizeRs();
}
void ResourceSetting()
{
if (SceneManager.GetActiveScene().name == "MainScene")
{
resource = 30;
purifyRs = 0;
herb = 0;
}
else if(SceneManager.GetActiveScene().name == "Tutorial" || SceneManager.GetActiveScene().name == "ScenePSW")
{
resource = int.MaxValue;
}
}
void SynchronizeRs()
{
reveal_purifyRs = purifyRs;
reveal_resource = resource;
reveal_herb = herb;
}
}
PurifyLab.cs
정화가스를 제작하는 제작대에 관한 스크립트
게임 시작시 랜덤 위치 스폰에 관한 내용이 들어있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PurifyLab : MonoBehaviour
{
[SerializeField] EnemySpawner enemySpawner;
public bool IsNearLab = false;
Vector3 spawnloca = Vector3.zero;
private void Start()
{
LabSpawn();
}
void LabSpawn()
{
spawnloca.x = Random.Range(-enemySpawner.spawnPos[0] / 2, enemySpawner.spawnPos[0] / 2);
spawnloca.z = Random.Range(-enemySpawner.spawnPos[1] / 2, enemySpawner.spawnPos[1] / 2);
spawnloca.y = 0.5f;
transform.position = spawnloca;
}
private void OnTriggerStay(Collider other)
{
if(other.CompareTag("Player"))
{
IsNearLab = true;
}
}
private void OnTriggerExit(Collider other)
{
if(other.CompareTag("Player"))
{
IsNearLab = false;
}
}
}
LabUI.cs
근처로 가면 UI를 작동시키고 아니라면 UI끄는 기능과 가스 제작에 관한 게이지바에 대한 기능이 들어있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LabUI : MonoBehaviour
{
Camera cam;
[SerializeField] Slider gauge;
[SerializeField] float timer;
[SerializeField] float purifyTime;
public bool purifyBt = false;
[SerializeField] PurifyLab purifyLab;
[SerializeField] GameObject UI;
bool manufactComple = false;
// Start is called before the first frame update
void Start()
{
cam = Camera.main;
gauge.value = 0;
gauge.enabled = false;
UI.SetActive(false);
}
// Update is called once per frame
void Update()
{
Purify();
VisualizeUI();
//Debug.Log(Time.deltaTime);
}
IEnumerator GaugeIncrease()
{
gauge.value = 0;
while(timer < purifyTime)
{
transform.rotation = Camera.main.transform.rotation;
gauge.value = timer;
yield return new WaitForSeconds(1f);
timer += 1f;
}
manufactComple = true;
gauge.value = 0;
yield return null;
}
void Purify()
{
if(purifyBt)
{
if (GameManager.herb >= 5)
{
StartCoroutine(GaugeIncrease());
GameManager.herb -= 5;
purifyBt = false;
}
else
purifyBt = false;
}
}
public void PurifyButton()
{
if (GameManager.herb >= 5) purifyBt = true;
//Debug.Log("Pressed");
}
void VisualizeUI()
{
if (purifyLab.IsNearLab)
{
UI.SetActive(true);
if(manufactComple)
{
GameManager.purifyRs += 1;
manufactComple = false;
AlarmMessage.ins.Message("정화 가스를 획득했습니다.");
}
}
else
{
UI.SetActive(false);
}
}
}
AlarmMessage.cs
메세지를 화면에 띄우는 UI
코루틴을 이용하여 중복해서 뜨는 것을 막고 싱글톤에 함수 처리를 활용하여 간편하게 메세지를 띄울 수 있도록 설계함
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AlarmMessage : MonoBehaviour
{
static AlarmMessage instance;
public static Coroutine MessageCo;
Image image;
Text text;
private void Awake()
{
image = GetComponentInChildren<Image>();
text = GetComponentInChildren<Text>();
image.gameObject.SetActive(false);
}
public static AlarmMessage ins
{
get
{
if(instance == null)
{
instance = FindObjectOfType<AlarmMessage>();
if(ins == null)
{
GameObject go = new GameObject();
instance = go.AddComponent<AlarmMessage>();
go.name = typeof(AlarmMessage).Name;
}
}
return instance;
}
}
public void Message(string contents)
{
if(MessageCo == null)
MessageCo = StartCoroutine(ShowMessage(contents));
}
IEnumerator ShowMessage(string contents)
{
Debug.Log("show");
text.text = contents;
StartCoroutine(BlinkMessage());
yield return new WaitForSeconds(1f);
}
IEnumerator BlinkMessage()
{
Debug.Log("blink");
float timer = 0f;
float blinkTime = 2f;
float imageA = 0.6f;
float textA = 1f;
image.gameObject.SetActive(true);
image.color = new Color(image.color.r, image.color.g, image.color.b, imageA);
text.color = new Color(text.color.r, text.color.g, text.color.b, textA);
while (timer < blinkTime)
{
timer += Time.deltaTime;
imageA -= Time.deltaTime / 4;
textA -= Time.deltaTime / 4;
image.color = new Color(image.color.r, image.color.g, image.color.b, imageA);
text.color = new Color(text.color.r, text.color.g, text.color.b, textA);
yield return new WaitForSeconds(0.1f);
}
image.gameObject.SetActive(false);
MessageCo = null;
yield return null;
}
}
'Unity' 카테고리의 다른 글
[던전크롤링] 기획안 (1) | 2024.03.14 |
---|---|
[턴제 생존 게임] 유닛 정보 UI (0) | 2023.05.19 |
[턴제 생존 게임] We Mustn't Die Here (0) | 2023.04.14 |
[타워디펜스] 타워 철거기능 구현 (0) | 2023.02.03 |
[타워디펜스] 몹 스포너(Spawner)와 웨이브(Wave) (0) | 2023.01.16 |