일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 5월
- 7월
- 게임 엔진 공부
- 개인 프로젝트 - 런앤건
- 기초
- 2022년
- 백준
- 유니티
- 10월
- C/C++
- 4월
- 다이나믹 프로그래밍
- 3월
- 골드메탈
- 2024년
- todolist
- 유니티 심화과정
- 2025년
- 프로그래머스
- 단계별로 풀어보기
- 1월
- 코딩 테스트
- 입문
- 2월
- 자료 구조
- 2023년
- c++
- 개인 프로젝트
- 코딩 기초 트레이닝
- 수학
- Today
- Total
기록 보관소
[Unity/유니티] 기초-2D 종스크롤 슈팅: 따라다니는 보조 무기 만들기[B36] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
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 종스크롤 슈팅: 따라다니는 보조 무기 만들기[B36]
1. 준비하기
2. 기본 작동 구현
//Follower 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Follower : MonoBehaviour {
public float maxShotDelay;
public float curShotDelay;
public ObjectManager objectManager;
void Update() {
Follow();
Fire();
Reload();
}
void Follow() {
}
void Fire() {
if (!Input.GetButton("Fire1"))
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;
}
}
//ObjectManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour {
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;
GameObject[] enemyL;
GameObject[] enemyM;
GameObject[] enemyS;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBoom;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
GameObject[] bulletFollower;
GameObject[] targetPool;
void Awake() {
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];
Generate();
}
void Generate() {
//적 기체
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);
}
}
public GameObject MakeObj(string type) {
switch (type) {
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;
}
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 "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;
}
return targetPool;
}
}
3. 팔로우 로직
//Follower 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Follower : MonoBehaviour {
public float maxShotDelay;
public float curShotDelay;
public ObjectManager objectManager;
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 (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;
}
}
- 큐를 이용해서 Player의 위치를 계속 갱신해서 follow delay만큼 뒤에 그쪽으로 이동한다. 만약 Player가 움직이지 않아서 큐 값이 변하지 않았다면 Follower도 이전 위치 값을 계속 사용해 움직이지 않는다.
4. 파워 적용
//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;
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;
void Awake() {
anim = GetComponent<Animator>();
}
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 (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
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;
}
}
}
}
- 이제 플레이어의 Max Power를 6까지 늘려 파워가 4, 5, 6일때 Follower가 하나씩 활성화 되도록한다. Follower 0는 앞의 과정과 동일하게 Player를 따라다니지만, Follower 1은 Follower 0를, Follower 2는 Follower 1을 따라다녀 각 Follower들이 줄줄이 따라다니도록 만들었다.
'유니티 프로젝트 > 2D 종스크롤 슈팅' 카테고리의 다른 글
[Unity/유니티] 기초-2D 종스크롤 슈팅: 모바일 슈팅게임 만들기[BE4] (0) | 2022.03.12 |
---|---|
[Unity/유니티] 기초-2D 종스크롤 슈팅: 탄막을 뿜어대는 보스 만들기[B37] (0) | 2022.03.11 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 텍스트 파일을 이용한 커스텀 배치 구현[B35] (0) | 2022.03.08 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 최적화의 기본, 오브젝트 풀링[B34] (0) | 2022.03.06 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 원근감있는 무한 배경만들기[B33] (0) | 2022.03.05 |