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
- 6월
- 게임 엔진 공부
- 4월
- 3월
- 10월
- 2025년
- 백준
- c++
- 코딩 테스트
- 수학
- 2024년
- 코딩 기초 트레이닝
- 2023년
- 유니티
- 기초
- 골드메탈
- 자료 구조
- 단계별로 풀어보기
- C/C++
- 입문
- 5월
- 개인 프로젝트
- todolist
- 프로그래머스
- 다이나믹 프로그래밍
- 2022년
- 유니티 심화과정
- 개인 프로젝트 - 런앤건
- 1월
- 2월
Archives
- Today
- Total
기록 보관소
[Unity/유니티] 기초-2D 종스크롤 슈팅: 모바일 슈팅게임 만들기[BE4] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
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
2D 종스크롤 슈팅: 모바일 슈팅게임 만들기[BE4]
1. 플레이어 무적 시간
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
public GameObject[] followers;
Animator anim;
SpriteRenderer spriteRenderer;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
public bool isRespawnTime;
void Awake() {
anim = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
Unbeatable();
Invoke("Unbeatable", 3);
}
void Unbeatable() {
isRespawnTime = !isRespawnTime; //값을 반대로
if (isRespawnTime) { //무적 시간 (반투명화)
isRespawnTime = true;
spriteRenderer.color = new Color(1, 1, 1, 0.5f); //플레이어 스프라이트
for (int index = 0; index < followers.Length; index++) { //팔로워 스프라이트
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
}
else { //무적 시간 종료 (원래대로)
isRespawnTime = false;
spriteRenderer.color = new Color(1, 1, 1, 1);
for (int index = 0; index < followers.Length; index++) {
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 1);
}
}
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
void Fire() { //플레이어 총알 발사 함수
if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
default:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
for (int index = 0; index < enemiesL.Length; index++)
if (enemiesL[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesM.Length; index++)
if (enemiesM[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesS.Length; index++)
if (enemiesS[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int index = 0; index < bulletsA.Length; index++)
if (bulletsA[index].activeSelf) //활성화된 총알만 제거
bulletsA[index].SetActive(false);
for (int index = 0; index < bulletsB.Length; index++)
if (bulletsB[index].activeSelf) //활성화된 총알만 제거
bulletsB[index].SetActive(false);
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isRespawnTime) //무적 시간일때
return;
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
if (collision.gameObject.tag == "Enemy") {
Enemy enemyObject = collision.gameObject.GetComponent<Enemy>();
if (enemyObject.enemyName == "B")
return;
else
collision.gameObject.SetActive(false);
}
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else {
power++;
AddFollower();
}
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void AddFollower() {
if (power == 4)
followers[0].SetActive(true);
else if (power == 5)
followers[1].SetActive(true);
else if (power == 6)
followers[2].SetActive(true);
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}




- 보조무기 Follower들을 플레이어 자식 오브젝트로 넣어서 플레이어가 죽으면 같이 비활성화되도록 만들었다.
2. 폭발 효과





- Explosion -> Idle은 Transition Duration만 0으로 수정해준다.

//Explosion 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explosion : MonoBehaviour {
Animator anim;
void Awake() {
anim = GetComponent<Animator>();
}
void OnEnable() {
Invoke("Disable", 2f);
}
void Disable() {
gameObject.SetActive(false);
}
public void startExplosion(string target) {
anim.SetTrigger("OnExplosion");
switch (target) { //타겟에 따른 폭발 크기 변경
case "S":
transform.localScale = Vector3.one * 0.7f;
break;
case "P":
case "M":
transform.localScale = Vector3.one * 1f;
break;
case "L":
transform.localScale = Vector3.one * 2f;
break;
case "B":
transform.localScale = Vector3.one * 3f;
break;
}
}
}



//ObjectManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour {
public GameObject enemyBPrefab;
public GameObject enemyLPrefab;
public GameObject enemyMPrefab;
public GameObject enemySPrefab;
public GameObject itemCoinPrefab;
public GameObject itemPowerPrefab;
public GameObject itemBoomPrefab;
public GameObject bulletPlayerAPrefab;
public GameObject bulletPlayerBPrefab;
public GameObject bulletEnemyAPrefab;
public GameObject bulletEnemyBPrefab;
public GameObject bulletFollowerPrefab;
public GameObject bulletBossAPrefab;
public GameObject bulletBossBPrefab;
public GameObject explosionPrefab;
GameObject[] enemyB;
GameObject[] enemyL;
GameObject[] enemyM;
GameObject[] enemyS;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBoom;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
GameObject[] bulletFollower;
GameObject[] bulletBossA;
GameObject[] bulletBossB;
GameObject[] explosion;
GameObject[] targetPool;
void Awake() {
enemyB = new GameObject[1];
enemyL = new GameObject[10];
enemyM = new GameObject[10];
enemyS = new GameObject[20];
itemCoin = new GameObject[20];
itemPower = new GameObject[10];
itemBoom = new GameObject[10];
bulletPlayerA = new GameObject[100];
bulletPlayerB = new GameObject[100];
bulletEnemyA = new GameObject[100];
bulletEnemyB = new GameObject[100];
bulletFollower = new GameObject[100];
bulletBossA = new GameObject[50];
bulletBossB = new GameObject[1000];
explosion = new GameObject[20];
Generate();
}
void Generate() {
//적 기체
for (int index = 0; index < enemyB.Length; index++) {
enemyB[index] = Instantiate(enemyBPrefab);
enemyB[index].SetActive(false);
}
for (int index = 0; index < enemyL.Length; index++) {
enemyL[index] = Instantiate(enemyLPrefab);
enemyL[index].SetActive(false);
}
for (int index = 0; index < enemyM.Length; index++) {
enemyM[index] = Instantiate(enemyMPrefab);
enemyM[index].SetActive(false);
}
for (int index = 0; index < enemyS.Length; index++) {
enemyS[index] = Instantiate(enemySPrefab);
enemyS[index].SetActive(false);
}
//아이템
for (int index = 0; index < itemCoin.Length; index++) {
itemCoin[index] = Instantiate(itemCoinPrefab);
itemCoin[index].SetActive(false);
}
for (int index = 0; index < itemPower.Length; index++) {
itemPower[index] = Instantiate(itemPowerPrefab);
itemPower[index].SetActive(false);
}
for (int index = 0; index < itemBoom.Length; index++) {
itemBoom[index] = Instantiate(itemBoomPrefab);
itemBoom[index].SetActive(false);
}
//총알
for (int index = 0; index < bulletPlayerA.Length; index++) {
bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
bulletPlayerA[index].SetActive(false);
}
for (int index = 0; index < bulletPlayerB.Length; index++) {
bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
bulletPlayerB[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyA.Length; index++) {
bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
bulletEnemyA[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyB.Length; index++) {
bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
bulletEnemyB[index].SetActive(false);
}
for (int index = 0; index < bulletFollower.Length; index++) {
bulletFollower[index] = Instantiate(bulletFollowerPrefab);
bulletFollower[index].SetActive(false);
}
for (int index = 0; index < bulletBossA.Length; index++) {
bulletBossA[index] = Instantiate(bulletBossAPrefab);
bulletBossA[index].SetActive(false);
}
for (int index = 0; index < bulletBossB.Length; index++) {
bulletBossB[index] = Instantiate(bulletBossBPrefab);
bulletBossB[index].SetActive(false);
}
//폭발
for (int index = 0; index < explosion.Length; index++) {
explosion[index] = Instantiate(explosionPrefab);
explosion[index].SetActive(false);
}
}
public GameObject MakeObj(string type) {
switch (type) {
case "EnemyB":
targetPool = enemyB;
break;
case "EnemyL":
targetPool = enemyL;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyS":
targetPool = enemyS;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBoom":
targetPool = itemBoom;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
case "BulletFollower":
targetPool = bulletFollower;
break;
case "BulletBossA":
targetPool = bulletBossA;
break;
case "BulletBossB":
targetPool = bulletBossB;
break;
case "Explosion":
targetPool = explosion;
break;
}
for (int index = 0; index < targetPool.Length; index++) {
if (!targetPool[index].activeSelf) { //비활성화된 오브젝트에 접근
targetPool[index].SetActive(true); //해당 오브젝트를 활성화 후
return targetPool[index]; //오브젝트 반환
}
}
return null;
}
public GameObject[] GetPool(string type) {
switch (type) {
case "EnemyB":
targetPool = enemyB;
break;
case "EnemyL":
targetPool = enemyL;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyS":
targetPool = enemyS;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBoom":
targetPool = itemBoom;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
case "BulletFollower":
targetPool = bulletFollower;
break;
case "BulletBossA":
targetPool = bulletBossA;
break;
case "BulletBossB":
targetPool = bulletBossB;
break;
case "Explosion":
targetPool = explosion;
break;
}
return targetPool;
}
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.IO; //파일 읽기용 라이브러리. C#에서 지원
public class GameManager : MonoBehaviour {
string[] enemyObjs;
public Transform[] spawnPoints;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public Image[] boomImage;
public GameObject gameOverSet;
public ObjectManager objectManager;
public float nextSpawnDelay;
public float curSpawnDelay;
public List<Spawn> spawnList;
public int spawnIndex;
public bool spawnEnd;
void Awake() {
enemyObjs = new string[] { "EnemyS", "EnemyM", "EnemyL", "EnemyB" };
spawnList = new List<Spawn>();
ReadSpawnFile();
}
void ReadSpawnFile() {
//변수 초기화
spawnList.Clear();
spawnIndex = 0;
spawnEnd = false;
//텍스트 파일 읽기
TextAsset textFile = Resources.Load("Stage 0") as TextAsset; //텍스트 파일 Stage 0 불러오기
StringReader stringReader = new StringReader(textFile.text);
while (stringReader != null) {
string line = stringReader.ReadLine();
if (line == null)
break;
//스폰 데이터 생성
Spawn spawnData = new Spawn();
spawnData.delay = float.Parse(line.Split(',')[0]);
spawnData.type = line.Split(',')[1];
spawnData.point = int.Parse(line.Split(',')[2]);
spawnList.Add(spawnData);
}
//텍스트 파일 닫기
stringReader.Close();
//첫번째 스폰 딜레이 적용
nextSpawnDelay = spawnList[0].delay;
}
void Update() {
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > nextSpawnDelay && !spawnEnd) {
SpawnEnemy();
curSpawnDelay = 0;
}
//UI 점수 업데이트
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy() {
int enemyIndex = 0;
switch(spawnList[spawnIndex].type) {
case "S":
enemyIndex = 0;
break;
case "M":
enemyIndex = 1;
break;
case "L":
enemyIndex = 2;
break;
case "B":
enemyIndex = 3;
break;
}
int enemyPoint = spawnList[spawnIndex].point;
GameObject enemy = objectManager.MakeObj(enemyObjs[enemyIndex]);
enemy.transform.position = spawnPoints[enemyPoint].position;
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player; //플레이어 클래스 넘겨주기
enemyLogic.gameManager = this; //클래스 자신을 넘겨주기
enemyLogic.objectManager = objectManager; //오브젝트 매니저 클래스 넘겨주기
if (enemyPoint == 5 || enemyPoint == 6) { //오른쪽 포인트
enemy.transform.Rotate(Vector3.back * 90); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else if (enemyPoint == 7 || enemyPoint == 8) { //왼쪽 포인트
enemy.transform.Rotate(Vector3.back * (-90)); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else { //중앙 앞쪽 포인트
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
//스폰 인덱스 증가
spawnIndex++;
if (spawnIndex == spawnList.Count) {
spawnEnd = true;
return;
}
//다음 리스폰 딜레이 갱신
nextSpawnDelay = spawnList[spawnIndex].delay;
}
public void UpdateLifeIcon(int life) {
//UI 라이프 모두 투명화
for (int index = 0; index < 3; index++) {
lifeImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 라이프 수만큼 활성화
for (int index = 0; index < life; index++) {
lifeImage[index].color = new Color(1, 1, 1, 1);
}
}
public void UpdateBoomIcon(int boom) {
//UI 붐 모두 투명화
for (int index = 0; index < 3; index++) {
boomImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 붐 수만큼 활성화
for (int index = 0; index < boom; index++) {
boomImage[index].color = new Color(1, 1, 1, 1);
}
}
public void RespawnPlayer() {
Invoke("RespawnPlayerExe", 2f);
}
void RespawnPlayerExe() {
player.transform.position = Vector3.down * 3.5f; //플레이어 위치 초기화
player.SetActive(true);
Player playerLogic = player.GetComponent<Player>();
playerLogic.isHit = false;
}
public void CallExplosion(Vector3 pos, string type) {
GameObject explosion = objectManager.MakeObj("Explosion");
Explosion explosionLogic = explosion.GetComponent<Explosion>();
explosion.transform.position = pos; //위치 설정
explosionLogic.startExplosion(type); //크기 설정
}
public void GameOver() {
gameOverSet.SetActive(true);
}
public void GameRetry() {
SceneManager.LoadScene(0);
}
}
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
public GameObject[] followers;
Animator anim;
SpriteRenderer spriteRenderer;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
public bool isRespawnTime;
void Awake() {
anim = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
Unbeatable();
Invoke("Unbeatable", 3);
}
void Unbeatable() {
isRespawnTime = !isRespawnTime; //값을 반대로
if (isRespawnTime) { //무적 시간 (반투명화)
isRespawnTime = true;
spriteRenderer.color = new Color(1, 1, 1, 0.5f); //플레이어 스프라이트
for (int index = 0; index < followers.Length; index++) { //팔로워 스프라이트
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
}
else { //무적 시간 종료 (원래대로)
isRespawnTime = false;
spriteRenderer.color = new Color(1, 1, 1, 1);
for (int index = 0; index < followers.Length; index++) {
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 1);
}
}
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
void Fire() { //플레이어 총알 발사 함수
if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
default:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
for (int index = 0; index < enemiesL.Length; index++)
if (enemiesL[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesM.Length; index++)
if (enemiesM[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesS.Length; index++)
if (enemiesS[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int index = 0; index < bulletsA.Length; index++)
if (bulletsA[index].activeSelf) //활성화된 총알만 제거
bulletsA[index].SetActive(false);
for (int index = 0; index < bulletsB.Length; index++)
if (bulletsB[index].activeSelf) //활성화된 총알만 제거
bulletsB[index].SetActive(false);
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isRespawnTime) //무적 시간일때
return;
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
gameManager.CallExplosion(transform.position, "P");
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
if (collision.gameObject.tag == "Enemy") {
Enemy enemyObject = collision.gameObject.GetComponent<Enemy>();
if (enemyObject.enemyName == "B")
return;
else
collision.gameObject.SetActive(false);
}
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else {
power++;
AddFollower();
}
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void AddFollower() {
if (power == 4)
followers[0].SetActive(true);
else if (power == 5)
followers[1].SetActive(true);
else if (power == 6)
followers[2].SetActive(true);
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}
//Enemy 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public string enemyName;
public int enemyScore;
public float speed;
public int health;
public float maxShotDelay;
public float curShotDelay;
public int patternIndex;
public int curPatternCount;
public int[] maxPatternCount;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject itemCoin;
public GameObject itemPower;
public GameObject itemBoom;
public GameObject player;
public GameManager gameManager;
public ObjectManager objectManager;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
Animator anim;
void Awake() {
spriteRenderer = GetComponent<SpriteRenderer>();
if (enemyName == "B")
anim = GetComponent<Animator>();
}
void OnEnable() {
switch(enemyName) {
case "B":
health = 3000;
Invoke("Stop", 2);
break;
case "L":
health = 40;
break;
case "M":
health = 10;
break;
case "S":
health = 3;
break;
}
}
void Stop() {
if (!gameObject.activeSelf)
return;
Rigidbody2D rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.zero;
Invoke("Think", 2);
}
void Think() {
patternIndex = (patternIndex == 3) ? 0 : patternIndex + 1;
curPatternCount = 0;
switch (patternIndex) {
case 0:
FireFoward();
break;
case 1:
FireShot();
break;
case 2:
FireArc();
break;
case 3:
FireAround();
break;
}
}
void FireFoward() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//총알 앞으로 4발 발사
GameObject bulletR = objectManager.MakeObj("BulletBossA");
bulletR.transform.position = transform.position + Vector3.right * 0.65f + Vector3.down * 1;
GameObject bulletRR = objectManager.MakeObj("BulletBossA");
bulletRR.transform.position = transform.position + Vector3.right * 0.8f + Vector3.down * 1;
GameObject bulletL = objectManager.MakeObj("BulletBossA");
bulletL.transform.position = transform.position + Vector3.left * 0.65f + Vector3.down * 1;
GameObject bulletLL = objectManager.MakeObj("BulletBossA");
bulletLL.transform.position = transform.position + Vector3.left * 0.8f + Vector3.down * 1;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidRR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireFoward", 2);
else
Invoke("Think", 3);
}
void FireShot() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//플레이어를 향해 방사형 발사
for (int index = 0; index < 5; index++) {
GameObject bullet = objectManager.MakeObj("BulletEnemyB");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = player.transform.position - transform.position;
Vector2 ranVec = new Vector2(Random.Range(-0.5f,0.5f), Random.Range(0f, 2f));
dirVec += ranVec;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireShot", 3.5f);
else
Invoke("Think", 3);
}
void FireArc() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//부채 형태로 연속 발사
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
bullet.transform.rotation = Quaternion.identity;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 10 * curPatternCount/maxPatternCount[patternIndex]),-1);
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireArc", 0.15f);
else
Invoke("Think", 3);
}
void FireAround() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//원 형태로 광역 공격
int roundNumA = 50;
int roundNumB = 40;
int roundNum = (curPatternCount % 2 == 0) ? roundNumA : roundNumB;
for (int index = 0; index < roundNum; index++) {
GameObject bullet = objectManager.MakeObj("BulletBossB");
bullet.transform.position = transform.position;
bullet.transform.rotation = Quaternion.identity;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 2 * index / roundNum), Mathf.Sin(Mathf.PI * 2 * index / roundNum));
rigid.AddForce(dirVec.normalized * 2, ForceMode2D.Impulse);
Vector3 rotVec = Vector3.forward * 360 * index / roundNum + Vector3.forward * 90;
bullet.transform.Rotate(rotVec);
}
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireAround", 0.7f);
else
Invoke("Think", 3);
}
void Update() {
if (enemyName == "B")
return;
Fire();
Reload();
}
void Fire() {
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
if (enemyName == "S") {
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position; //목표물로 방향 = 목표물 위치 - 자신의 위치
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
else if (enemyName == "L") {
GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
bulletR.transform.position = transform.position + Vector3.right * 0.3f;
GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
bulletL.transform.position = transform.position + Vector3.left * 0.3f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
public void onHit(int dmg) {
if (health <= 0) //이미 죽은 상태라면
return;
health -= dmg;
if (enemyName == "B") {
anim.SetTrigger("OnHit");
}
else {
spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
Invoke("ReturnSprite", 0.1f);
}
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
//랜덤 아이템 드랍. 보스는 드랍하지 않음
int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
if (ran < 3) { //드랍 없음 30%
//아이템 없음
}
else if (ran < 6) { //동전 드랍 30%
GameObject itemCoin = objectManager.MakeObj("ItemCoin");
itemCoin.transform.position = transform.position;
}
else if (ran < 8) { //파워 드랍 20%
GameObject itemPower = objectManager.MakeObj("ItemPower");
itemPower.transform.position = transform.position;
}
else if (ran < 10) { //붐 드랍 20%
GameObject itemBoom = objectManager.MakeObj("ItemBoom");
itemBoom.transform.position = transform.position;
}
if (enemyName == "B")
CancelInvoke();
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
gameManager.CallExplosion(transform.position, enemyName);
}
}
void ReturnSprite() {
spriteRenderer.sprite = sprites[0];
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") { //보스가 아닌 적이 맵 경계로 가면
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
}
else if (collision.gameObject.tag == "PlayerBullet")
{ //플레이어 총알에 닿으면
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
onHit(bullet.dmg);
collision.gameObject.SetActive(false); //플레이어 총알 삭제
}
}
}



- Explosion이 프리펩이 되었으니 ObjectManager을 통해 오브젝트 풀로 등록해준다.
- 그리고 GameManager에 폭발 이펙트를 불러오는 함수를 만들고, 이를 Player와 Enemy가 파괴될 때 사용한다. Enemy는 프리펩이므로 이 함수를 사용할 수 있도록 GameManager에서 자기 자신(this)을 넘겨준다.

- 참고로 폭발 애니메이션이 중간에 잘려서 위 캡처처럼 Animator의 Exit Time을 1로 해서 애니메이션이 완전하게 출력되도록 임의로 변경해주었다.
3. 모바일 컨트롤 UI

- Render Mode에서 Overlay는 기존 방식처럼 UI가 따로 보이는 것이고, Camera는 Game 화면처럼 같이 보이는 것이다. Camera로 바뀌면 Render Camera에 MainCamera를 끌어넣어줘야 한다.






- 버튼을 이렇게 만든 이유는 플레이어가 패드를 누를 때 대각선 이동도 가능하게 하기 위함이다.

//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
public GameObject[] followers;
Animator anim;
SpriteRenderer spriteRenderer;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
public bool isRespawnTime;
public bool[] joyControl;
public bool isControl;
void Awake() {
anim = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
Unbeatable();
Invoke("Unbeatable", 3);
}
void Unbeatable() {
isRespawnTime = !isRespawnTime; //값을 반대로
if (isRespawnTime) { //무적 시간 (반투명화)
isRespawnTime = true;
spriteRenderer.color = new Color(1, 1, 1, 0.5f); //플레이어 스프라이트
for (int index = 0; index < followers.Length; index++) { //팔로워 스프라이트
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
}
else { //무적 시간 종료 (원래대로)
isRespawnTime = false;
spriteRenderer.color = new Color(1, 1, 1, 1);
for (int index = 0; index < followers.Length; index++) {
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 1);
}
}
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
public void JoyPad(int type) {
for (int index = 0; index < 9; index++) {
joyControl[index] = (index == type);
}
}
public void JoyDown() {
isControl = true;
}
public void JoyUp() {
isControl = false;
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
if (joyControl[0]) { h = -1; v = 1; }
if (joyControl[1]) { h = 0; v = 1; }
if (joyControl[2]) { h = 1; v = 1; }
if (joyControl[3]) { h = -1; v = 0; }
if (joyControl[4]) { h = 0; v = 0; }
if (joyControl[5]) { h = 1; v = 0; }
if (joyControl[6]) { h = -1; v = -1; }
if (joyControl[7]) { h = 0; v = -1; }
if (joyControl[8]) { h = 1; v = -1; }
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1) || !isControl)
h = 0;
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1) || !isControl)
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
void Fire() { //플레이어 총알 발사 함수
if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
default:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
for (int index = 0; index < enemiesL.Length; index++)
if (enemiesL[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesM.Length; index++)
if (enemiesM[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesS.Length; index++)
if (enemiesS[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int index = 0; index < bulletsA.Length; index++)
if (bulletsA[index].activeSelf) //활성화된 총알만 제거
bulletsA[index].SetActive(false);
for (int index = 0; index < bulletsB.Length; index++)
if (bulletsB[index].activeSelf) //활성화된 총알만 제거
bulletsB[index].SetActive(false);
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isRespawnTime) //무적 시간일때
return;
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
gameManager.CallExplosion(transform.position, "P");
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
if (collision.gameObject.tag == "Enemy") {
Enemy enemyObject = collision.gameObject.GetComponent<Enemy>();
if (enemyObject.enemyName == "B")
return;
else
collision.gameObject.SetActive(false);
}
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else {
power++;
AddFollower();
}
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void AddFollower() {
if (power == 4)
followers[0].SetActive(true);
else if (power == 5)
followers[1].SetActive(true);
else if (power == 6)
followers[2].SetActive(true);
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}


- Button LT 외 다른 버튼 8개도 컴포넌트 값(value) 복사를 통해서 Pointer Enter의 변수 값(1~8)만 다르게 설정한다.


//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
public GameObject[] followers;
Animator anim;
SpriteRenderer spriteRenderer;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
public bool isRespawnTime;
public bool[] joyControl;
public bool isControl;
public bool isButtonA;
public bool isButtonB;
void Awake() {
anim = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
Unbeatable();
Invoke("Unbeatable", 3);
}
void Unbeatable() {
isRespawnTime = !isRespawnTime; //값을 반대로
if (isRespawnTime) { //무적 시간 (반투명화)
isRespawnTime = true;
spriteRenderer.color = new Color(1, 1, 1, 0.5f); //플레이어 스프라이트
for (int index = 0; index < followers.Length; index++) { //팔로워 스프라이트
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
}
else { //무적 시간 종료 (원래대로)
isRespawnTime = false;
spriteRenderer.color = new Color(1, 1, 1, 1);
for (int index = 0; index < followers.Length; index++) {
followers[index].GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 1);
}
}
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
public void JoyPad(int type) {
for (int index = 0; index < 9; index++) {
joyControl[index] = (index == type);
}
}
public void JoyDown() {
isControl = true;
}
public void JoyUp() {
isControl = false;
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
if (joyControl[0]) { h = -1; v = 1; }
if (joyControl[1]) { h = 0; v = 1; }
if (joyControl[2]) { h = 1; v = 1; }
if (joyControl[3]) { h = -1; v = 0; }
if (joyControl[4]) { h = 0; v = 0; }
if (joyControl[5]) { h = 1; v = 0; }
if (joyControl[6]) { h = -1; v = -1; }
if (joyControl[7]) { h = 0; v = -1; }
if (joyControl[8]) { h = 1; v = -1; }
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1) || !isControl)
h = 0;
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1) || !isControl)
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
public void ButtonADown() {
isButtonA = true;
}
public void ButtonAUp() {
isButtonA = false;
}
public void ButtonBDown() {
isButtonB = true;
}
void Fire() { //플레이어 총알 발사 함수
//if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
// return;
if (!isButtonA) //발사 버튼을 누르지 않았다면
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
default:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
//if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
// return;
if (!isButtonB) //필살기 버튼을 누르지 않았다면
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
isButtonB = false;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
for (int index = 0; index < enemiesL.Length; index++)
if (enemiesL[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesM.Length; index++)
if (enemiesM[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesS.Length; index++)
if (enemiesS[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int index = 0; index < bulletsA.Length; index++)
if (bulletsA[index].activeSelf) //활성화된 총알만 제거
bulletsA[index].SetActive(false);
for (int index = 0; index < bulletsB.Length; index++)
if (bulletsB[index].activeSelf) //활성화된 총알만 제거
bulletsB[index].SetActive(false);
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isRespawnTime) //무적 시간일때
return;
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
gameManager.CallExplosion(transform.position, "P");
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
if (collision.gameObject.tag == "Enemy") {
Enemy enemyObject = collision.gameObject.GetComponent<Enemy>();
if (enemyObject.enemyName == "B")
return;
else
collision.gameObject.SetActive(false);
}
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else {
power++;
AddFollower();
}
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void AddFollower() {
if (power == 4)
followers[0].SetActive(true);
else if (power == 5)
followers[1].SetActive(true);
else if (power == 6)
followers[2].SetActive(true);
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}




- 앞의 패드와 비슷하게 bool형 변수들을 사용해서 클릭/클릭하지 않음으로 구분하여 총알과 필살기를 사용하도록 만들었다.
+) 추가 수정
//Follower 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Follower : MonoBehaviour {
public float maxShotDelay;
public float curShotDelay;
public ObjectManager objectManager;
public Player player;
public Vector3 followPos;
public int followDelay;
public Transform parent;
public Queue<Vector3> parentPos;
void Awake() {
parentPos = new Queue<Vector3>();
}
void Update() {
Watch();
Follow();
Fire();
Reload();
}
void Watch() {
//위치 입력
if (!parentPos.Contains(parent.position)) //같은 위치값이면 큐에 저장하지 않음
parentPos.Enqueue(parent.position);
//위치 출력
if (parentPos.Count > followDelay)
followPos = parentPos.Dequeue();
else if (parentPos.Count < followDelay)
followPos = parent.position;
}
void Follow() {
transform.position = followPos;
}
void Fire() {
//if (!Input.GetButton("Fire1"))
// return;
if (!player.isButtonA) //플레이어가 버튼 A를 누르지 않았다면
return;
if (curShotDelay < maxShotDelay)
return;
GameObject bullet = objectManager.MakeObj("BulletFollower");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
}


- follower가 버튼 A가 아닌 마우스 클릭을 인식하고 발사되어서 Player를 불러와 isButtonA에 따라서 발사하도록 수정했다.
4. 스테이지 관리







- On은 트리거형 매개변수다. 이전 애니메이션들과 마찬가지로 Idle은 Empty이고, Text -> Idle Transition은 Transition Duration만 0으로 설정해둔다.

- Text Animation은 텍스트의 Scale, 즉 크기만을 이용하여 구현할 것이다.
- 각 키 프레임이 0초에는 크기가 0/0/0으로 시작해 0.3초에 1.2/1.2/1.2, 이후 0.4초에 다시 1/1/1, 이후 5초까지 유지하다가 5.1초에 1.2/1.2/1.2, 마지막 5.3초에 0/0/0이 되어 사라진다. 이렇게 구현해서 실행해보면 텍스트가 약간 통통 튀듯이 나타났다가 사라지게 된다.

- 앞의 애니메이션이 Scale을 다루는 것이므로, 자연스럽게 Text도 활성화/비활성화 하는 것이 아닌 Scale을 0/0/0으로 만들어준다.


- 앞에서 폭발 이펙트에서 다뤘듯이 Transition이 겹치면 애니메이션이 정상적으로 출력되지 않고 일부 잘리는 일이 발생하므로 Exit Time을 1로 만들어준다. AnyState -> Text의 경우 Exit Time이 비활성화되어있으므로 아래 프레임을 이용하거나 잠시 Has Exit Time을 체크해서 바꾸는 걸로 수정할 수 있다.







- 어차피 Fade In을 통해서 검은 화면이 사라지고, Fade Out을 통해서 검은 화면이 생길테니 따로 Idle에 Transition을 연결할 필요가 없다.

- 각 스테이지가 끝나고 다시 시작될 때, 플레이어 위치를 초기화 해주어야하므로 이를 설정할 Empty를 하나 만든다.
- 이제 모든 UI 요소들이 완성되었으니 스크립트 작업을 하면 된다.
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.IO; //파일 읽기용 라이브러리. C#에서 지원
public class GameManager : MonoBehaviour {
public int stage;
public Animator stageAnim;
public Animator clearAnim;
public Animator fadeAnim;
public Transform playerPos;
string[] enemyObjs;
public Transform[] spawnPoints;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public Image[] boomImage;
public GameObject gameOverSet;
public ObjectManager objectManager;
public float nextSpawnDelay;
public float curSpawnDelay;
public List<Spawn> spawnList;
public int spawnIndex;
public bool spawnEnd;
void Awake() {
enemyObjs = new string[] { "EnemyS", "EnemyM", "EnemyL", "EnemyB" };
spawnList = new List<Spawn>();
StageStart();
}
public void StageStart() {
//스테이지 UI 불러오기
stageAnim.SetTrigger("On");
stageAnim.GetComponent<Text>().text = "Stage " + stage + "\nStart";
clearAnim.GetComponent<Text>().text = "Stage " + stage + "\nClear!!!";
//적 기체 소환 파일 읽기
ReadSpawnFile();
//페이드 인
fadeAnim.SetTrigger("In");
}
public void StageEnd() {
//클리어 UI 불러오기
clearAnim.SetTrigger("On");
//페이드 아웃
fadeAnim.SetTrigger("Out");
//플레이어 위치 잡아주기
player.transform.position = playerPos.position;
//다음 스테이지로
stage++;
if (stage > 2)
Invoke("GameOver", 6);
else
Invoke("StageStart", 5);
}
void ReadSpawnFile() {
//변수 초기화
spawnList.Clear();
spawnIndex = 0;
spawnEnd = false;
//텍스트 파일 읽기
TextAsset textFile = Resources.Load("Stage " + stage) as TextAsset; //텍스트 파일 Stage N 불러오기
StringReader stringReader = new StringReader(textFile.text);
while (stringReader != null) {
string line = stringReader.ReadLine();
if (line == null)
break;
//스폰 데이터 생성
Spawn spawnData = new Spawn();
spawnData.delay = float.Parse(line.Split(',')[0]);
spawnData.type = line.Split(',')[1];
spawnData.point = int.Parse(line.Split(',')[2]);
spawnList.Add(spawnData);
}
//텍스트 파일 닫기
stringReader.Close();
//첫번째 스폰 딜레이 적용
nextSpawnDelay = spawnList[0].delay;
}
void Update() {
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > nextSpawnDelay && !spawnEnd) {
SpawnEnemy();
curSpawnDelay = 0;
}
//UI 점수 업데이트
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy() {
int enemyIndex = 0;
switch(spawnList[spawnIndex].type) {
case "S":
enemyIndex = 0;
break;
case "M":
enemyIndex = 1;
break;
case "L":
enemyIndex = 2;
break;
case "B":
enemyIndex = 3;
break;
}
int enemyPoint = spawnList[spawnIndex].point;
GameObject enemy = objectManager.MakeObj(enemyObjs[enemyIndex]);
enemy.transform.position = spawnPoints[enemyPoint].position;
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player; //플레이어 클래스 넘겨주기
enemyLogic.gameManager = this; //클래스 자신을 넘겨주기
enemyLogic.objectManager = objectManager; //오브젝트 매니저 클래스 넘겨주기
if (enemyPoint == 5 || enemyPoint == 6) { //오른쪽 포인트
enemy.transform.Rotate(Vector3.back * 90); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else if (enemyPoint == 7 || enemyPoint == 8) { //왼쪽 포인트
enemy.transform.Rotate(Vector3.back * (-90)); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else { //중앙 앞쪽 포인트
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
//스폰 인덱스 증가
spawnIndex++;
if (spawnIndex == spawnList.Count) {
spawnEnd = true;
return;
}
//다음 리스폰 딜레이 갱신
nextSpawnDelay = spawnList[spawnIndex].delay;
}
public void UpdateLifeIcon(int life) {
//UI 라이프 모두 투명화
for (int index = 0; index < 3; index++) {
lifeImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 라이프 수만큼 활성화
for (int index = 0; index < life; index++) {
lifeImage[index].color = new Color(1, 1, 1, 1);
}
}
public void UpdateBoomIcon(int boom) {
//UI 붐 모두 투명화
for (int index = 0; index < 3; index++) {
boomImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 붐 수만큼 활성화
for (int index = 0; index < boom; index++) {
boomImage[index].color = new Color(1, 1, 1, 1);
}
}
public void RespawnPlayer() {
Invoke("RespawnPlayerExe", 2f);
}
void RespawnPlayerExe() {
player.transform.position = Vector3.down * 3.5f; //플레이어 위치 초기화
player.SetActive(true);
Player playerLogic = player.GetComponent<Player>();
playerLogic.isHit = false;
}
public void CallExplosion(Vector3 pos, string type) {
GameObject explosion = objectManager.MakeObj("Explosion");
Explosion explosionLogic = explosion.GetComponent<Explosion>();
explosion.transform.position = pos; //위치 설정
explosionLogic.startExplosion(type); //크기 설정
}
public void GameOver() {
gameOverSet.SetActive(true);
}
public void GameRetry() {
SceneManager.LoadScene(0);
}
}
//Enemy 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public string enemyName;
public int enemyScore;
public float speed;
public int health;
public float maxShotDelay;
public float curShotDelay;
public int patternIndex;
public int curPatternCount;
public int[] maxPatternCount;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject itemCoin;
public GameObject itemPower;
public GameObject itemBoom;
public GameObject player;
public GameManager gameManager;
public ObjectManager objectManager;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
Animator anim;
void Awake() {
spriteRenderer = GetComponent<SpriteRenderer>();
if (enemyName == "B")
anim = GetComponent<Animator>();
}
void OnEnable() {
switch(enemyName) {
case "B":
health = 3000;
Invoke("Stop", 2);
break;
case "L":
health = 40;
break;
case "M":
health = 10;
break;
case "S":
health = 3;
break;
}
}
void Stop() {
if (!gameObject.activeSelf)
return;
Rigidbody2D rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.zero;
Invoke("Think", 2);
}
void Think() {
patternIndex = (patternIndex == 3) ? 0 : patternIndex + 1;
curPatternCount = 0;
switch (patternIndex) {
case 0:
FireFoward();
break;
case 1:
FireShot();
break;
case 2:
FireArc();
break;
case 3:
FireAround();
break;
}
}
void FireFoward() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//총알 앞으로 4발 발사
GameObject bulletR = objectManager.MakeObj("BulletBossA");
bulletR.transform.position = transform.position + Vector3.right * 0.65f + Vector3.down * 1;
GameObject bulletRR = objectManager.MakeObj("BulletBossA");
bulletRR.transform.position = transform.position + Vector3.right * 0.8f + Vector3.down * 1;
GameObject bulletL = objectManager.MakeObj("BulletBossA");
bulletL.transform.position = transform.position + Vector3.left * 0.65f + Vector3.down * 1;
GameObject bulletLL = objectManager.MakeObj("BulletBossA");
bulletLL.transform.position = transform.position + Vector3.left * 0.8f + Vector3.down * 1;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidRR.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.down * 8, ForceMode2D.Impulse);
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireFoward", 2);
else
Invoke("Think", 3);
}
void FireShot() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//플레이어를 향해 방사형 발사
for (int index = 0; index < 5; index++) {
GameObject bullet = objectManager.MakeObj("BulletEnemyB");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = player.transform.position - transform.position;
Vector2 ranVec = new Vector2(Random.Range(-0.5f,0.5f), Random.Range(0f, 2f));
dirVec += ranVec;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireShot", 3.5f);
else
Invoke("Think", 3);
}
void FireArc() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//부채 형태로 연속 발사
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
bullet.transform.rotation = Quaternion.identity;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 10 * curPatternCount/maxPatternCount[patternIndex]),-1);
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireArc", 0.15f);
else
Invoke("Think", 3);
}
void FireAround() {
if (health <= 0) //이미 죽은 상태라면 멈추기
return;
//원 형태로 광역 공격
int roundNumA = 50;
int roundNumB = 40;
int roundNum = (curPatternCount % 2 == 0) ? roundNumA : roundNumB;
for (int index = 0; index < roundNum; index++) {
GameObject bullet = objectManager.MakeObj("BulletBossB");
bullet.transform.position = transform.position;
bullet.transform.rotation = Quaternion.identity;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector2 dirVec = new Vector2(Mathf.Cos(Mathf.PI * 2 * index / roundNum), Mathf.Sin(Mathf.PI * 2 * index / roundNum));
rigid.AddForce(dirVec.normalized * 2, ForceMode2D.Impulse);
Vector3 rotVec = Vector3.forward * 360 * index / roundNum + Vector3.forward * 90;
bullet.transform.Rotate(rotVec);
}
//패턴 횟수 갱신
curPatternCount++;
if (curPatternCount < maxPatternCount[patternIndex])
Invoke("FireAround", 0.7f);
else
Invoke("Think", 3);
}
void Update() {
if (enemyName == "B")
return;
Fire();
Reload();
}
void Fire() {
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
if (enemyName == "S") {
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position; //목표물로 방향 = 목표물 위치 - 자신의 위치
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
else if (enemyName == "L") {
GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
bulletR.transform.position = transform.position + Vector3.right * 0.3f;
GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
bulletL.transform.position = transform.position + Vector3.left * 0.3f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
public void onHit(int dmg) {
if (health <= 0) //이미 죽은 상태라면
return;
health -= dmg;
if (enemyName == "B") {
anim.SetTrigger("OnHit");
}
else {
spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
Invoke("ReturnSprite", 0.1f);
}
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
//랜덤 아이템 드랍. 보스는 드랍하지 않음
int ran = (enemyName == "B") ? 0 : Random.Range(0, 10);
if (ran < 3) { //드랍 없음 30%
//아이템 없음
}
else if (ran < 6) { //동전 드랍 30%
GameObject itemCoin = objectManager.MakeObj("ItemCoin");
itemCoin.transform.position = transform.position;
}
else if (ran < 8) { //파워 드랍 20%
GameObject itemPower = objectManager.MakeObj("ItemPower");
itemPower.transform.position = transform.position;
}
else if (ran < 10) { //붐 드랍 20%
GameObject itemBoom = objectManager.MakeObj("ItemBoom");
itemBoom.transform.position = transform.position;
}
if (enemyName == "B") {
CancelInvoke();
gameManager.StageEnd();
}
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
gameManager.CallExplosion(transform.position, enemyName);
}
}
void ReturnSprite() {
spriteRenderer.sprite = sprites[0];
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet" && enemyName != "B") { //보스가 아닌 적이 맵 경계로 가면
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
}
else if (collision.gameObject.tag == "PlayerBullet")
{ //플레이어 총알에 닿으면
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
onHit(bullet.dmg);
collision.gameObject.SetActive(false); //플레이어 총알 삭제
}
}
}







- 이제 마지막 게임 오버 텍스트 색상만 변경하면 스테이지 텍스트 작업을 제외한 모든 작업은 끝난다. 이제 모바일 빌드만 하면 완성이다.
5. 모바일 빌드






Unity BE4 Game.apk
14.89MB




- 패드가 조금 작아서 조작이 어려웠지만, 그래도 문제 없이 잘 작동한다.
'유니티 프로젝트 > 2D 종스크롤 슈팅' 카테고리의 다른 글
[Unity/유니티] 기초-2D 종스크롤 슈팅: 탄막을 뿜어대는 보스 만들기[B37] (0) | 2022.03.11 |
---|---|
[Unity/유니티] 기초-2D 종스크롤 슈팅: 따라다니는 보조 무기 만들기[B36] (0) | 2022.03.09 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 텍스트 파일을 이용한 커스텀 배치 구현[B35] (0) | 2022.03.08 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 최적화의 기본, 오브젝트 풀링[B34] (0) | 2022.03.06 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 원근감있는 무한 배경만들기[B33] (0) | 2022.03.05 |