일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 골드메탈
- 2월
- 2022년
- 다이나믹 프로그래밍
- 프로그래머스
- 1월
- 백준
- 코딩 테스트
- 단계별로 풀어보기
- 7월
- c++
- 입문
- 5월
- 유니티 심화과정
- 게임 엔진 공부
- 기초
- 개인 프로젝트
- 2023년
- 수학
- 2024년
- 코딩 기초 트레이닝
- 자료 구조
- 2025년
- 4월
- 개인 프로젝트 - 런앤건
- 10월
- todolist
- 3월
- C/C++
- 유니티
- Today
- Total
기록 보관소
[Unity/유니티] 기초-2D 종스크롤 슈팅: UI 간단하게 완성하기[B31] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
+) 이번 포스트는 안타깝게도 작성 중에 한번 날아가서 조금 간략하게 작성했다. 앞으로는 글 작성 중에 미리미리 저장해두는 버릇을 들어놔야겠다...
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 종스크롤 슈팅: UI 간단하게 완성하기[B31]
1. 목숨과 점수 UI 배치
- Canvas Sclaer에서 UI Scale Mode를 Scale With Screen Size로 변경.
- Scale With Screen Size : 기준 해상도의 UI 크기 유지
2. UI 로직
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public GameObject gameOverSet;
public float maxSpawnDelay;
public float curSpawnDelay;
void Update() {
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > maxSpawnDelay) {
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
//UI 점수 업데이트
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy() {
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 9);
GameObject enemy = Instantiate(enemyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player;
if (ranPoint == 5 || ranPoint == 6) { //오른쪽 포인트
enemy.transform.Rotate(Vector3.back * 90); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else if (ranPoint == 7 || ranPoint == 8) { //왼쪽 포인트
enemy.transform.Rotate(Vector3.back * (-90)); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else { //중앙 앞쪽 포인트
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
}
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 RespawnPlayer() {
Invoke("RespawnPlayerExe", 2f);
}
void RespawnPlayerExe() {
player.transform.position = Vector3.down * 3.5f; //플레이어 위치 초기화
player.SetActive(true);
}
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 manager;
public GameObject bulletObjA;
public GameObject bulletObjB;
Animator anim;
public int life;
public int score;
public float speed;
public float power;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
void Awake() {
anim = GetComponent<Animator>();
}
void Update() {
Move();
Fire();
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 = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
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;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.35f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.35f, transform.rotation);
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 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") {
life--;
manager.UpdateLifeIcon(life);
if (life == 0) {
manager.GameOver();
}
else {
manager.RespawnPlayer();
}
gameObject.SetActive(false);
Destroy(collision.gameObject);
}
}
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 GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject player;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
void Awake() {
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update(){
Fire();
Reload();
}
void Fire() {
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
if (enemyName == "S") {
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
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 = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
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;
}
void onHit(int dmg) {
health -= dmg;
spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
Invoke("ReturnSprite", 0.1f);
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
Destroy(gameObject);
}
}
void ReturnSprite() {
spriteRenderer.sprite = sprites[0];
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet") //맵의 경계로 가게되면
Destroy(gameObject);
else if (collision.gameObject.tag == "PlayerBullet") { //플레이어 총알에 닿으면
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
onHit(bullet.dmg);
Destroy(collision.gameObject); //플레이어 총알 삭제
}
}
}
- string.format() : 지정된 양식으로 문자열을 변환해주는 함수
- {0:n0} : 세자리마다 쉼표로 나눠주는 숫자 양식. 위 캡처에서 4,350점으로 쉼표가 찍힌것을 볼 수 있다.
3. 예외 처리
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public GameObject gameOverSet;
public float maxSpawnDelay;
public float curSpawnDelay;
void Update() {
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > maxSpawnDelay) {
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
//UI 점수 업데이트
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy() {
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 9);
GameObject enemy = Instantiate(enemyObjs[ranEnemy], spawnPoints[ranPoint].position, spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player;
if (ranPoint == 5 || ranPoint == 6) { //오른쪽 포인트
enemy.transform.Rotate(Vector3.back * 90); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else if (ranPoint == 7 || ranPoint == 8) { //왼쪽 포인트
enemy.transform.Rotate(Vector3.back * (-90)); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else { //중앙 앞쪽 포인트
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
}
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 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 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 manager;
public GameObject bulletObjA;
public GameObject bulletObjB;
Animator anim;
public int life;
public int score;
public float speed;
public float power;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
void Awake() {
anim = GetComponent<Animator>();
}
void Update() {
Move();
Fire();
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 = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
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;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.35f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.35f, transform.rotation);
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 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--;
manager.UpdateLifeIcon(life);
if (life == 0) {
manager.GameOver();
}
else {
manager.RespawnPlayer();
}
gameObject.SetActive(false);
Destroy(collision.gameObject);
}
}
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형 변수를 추가하여 이제 동시에 두대를 맞아 라이프가 2개 이상 깎일 일이 없어지게 되었다.
'유니티 프로젝트 > 2D 종스크롤 슈팅' 카테고리의 다른 글
[Unity/유니티] 기초-2D 종스크롤 슈팅: 원근감있는 무한 배경만들기[B33] (0) | 2022.03.05 |
---|---|
[Unity/유니티] 기초-2D 종스크롤 슈팅: 아이템과 필살기 구현하기[B32] (0) | 2022.03.04 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 적 전투와 피격 이벤트 만들기[B30] (0) | 2022.03.02 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 적 비행기 만들기[B29] (0) | 2022.02.28 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 총알발사 구현하기[B28] (0) | 2022.02.27 |