게임 소개
게임 이름: 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;
}
}