[20260220] 간단한 2D 게임

SmartBear·2026년 2월 20일
post-thumbnail

금일 배운 것을 TIL 로 올리려 했으나, 노트용 자료가 덜 올라와 자료 정리가 좀 애매해서,
실습시간에 진행한 간단한 2D 게임에 대해 소개 한다.

게임 장르 및 내용

  • 떨어지는 돈 줏어먹기

시연

파일 및 Object 구성

  • Map
  • Player
    • GamePlayerController.cs; 플레이어 제어 스크립트
    • GamePlayerAnimation.cs; 플레이어 Animation 스크립트
  • GameManager
    • GameManager.cs; Game Score 저장을 위한 싱글톤 오브젝트
  • CoinSpawner
    • CoinSpawner.cs; Coin object 관리를 위한 싱글톤 오브젝트
  • Canvas
    • UIControl.cs; 화면에 보여줄 UI 에 대한 스크립트
  • Coin(prefab)
    • Coin.cs; Coin 이 가져야할 행동에 대한 스크립트

개발시 이슈

Input System 사용 숙지 미흡

  • Behavior; Send Message vs Invoke Unity Events
    • 기본으로 설정시 Send Message.
    • OnMove 등의 함수 사용시 받는 파라메터가 InputValue
    • 따라서 파라메터를 InpuAction.CallbackContext로 사용할 수 없음.
    • InputAction.CallbackContextInvoke Unity Events로만 사용 가능.
  • Invoke Unity Events 사용시 추가 주의 사항
    • InspectorEvents 에 관련 함수를 수동으로 Bind 해주어야함.

2D 에서의 OnCollisionEnter 및 Collision Class 사용

현상

  • OnCollisionEnter 와 파라메터를 Collision으로 받음. -> 동작 안함.

원인

  • 알고보니 2D 전용으로 OnCollisionEnter2DCollision2D가 별도로 존재.

조치

  • 교체 후 정상 동작 확인.

시작시 한번에 코인이 발생되는 이슈

현상

  • 본 코드에서는 Object Pool Pattern을 활용함.
  • 시작시 Pool 에 5개의 Coin 을 넣는 부분이 있음.
  • 게임 시작 후 바로 5개의 Coin 이 생성되어버리는 이슈 발생.

원인

  • Object Pool 에 들어가기 전 Coin Object 가 활성상태.

조치

  • Object Pool 에 들어가기 전 Coin 을 비활성상태로 변경 후 삽입.
  • 정상 동작.

코드

개인적인 Backup 느낌으로 적는 것 입니다.

Coin.cs

using System;
using System.Collections;
using UnityEngine;

public class Coin : MonoBehaviour
{
    public int cost = 100;
    public void OnEnable()
    {
        StartCoroutine(Discard());
    }
    
    public void OnCollisionEnter2D(Collision2D other)
    {
        if (other.gameObject.CompareTag("Ground"))
        {
            DownCost();
        } 
        else if (other.gameObject.CompareTag("Player"))
        {
            TagPlayer();
        }
    }

    private void DownCost()
    {
        cost -= 10;
    }

    private void TagPlayer()
    {
        GameManager.Instance.Score += cost;
        ReturnCoin();
    }

    private IEnumerator Discard()
    {
        yield return new WaitForSeconds(10f);
        ReturnCoin();
    }
    
    private void ReturnCoin() 
    {
        gameObject.SetActive(false);
        CoinSpwaner.Instance.PushCoin(gameObject);
    }
}

CoinSpwaner.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class CoinSpwaner : MonoBehaviour
{
    private static CoinSpwaner _instance;

    public static CoinSpwaner Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<CoinSpwaner>();
            }
            return _instance;
        }
    }
    
    [SerializeField] private GameObject coinPrefab; 
    Stack<GameObject> coinStack = new();
    
    private void Awake()
    {
        Init();
    }
    
    private void Init()
    {
        for (int i = 0; i < 5; i++)        
            PushCoin();
        
        StartCoroutine(SpawnCoin());
    }
    
    private IEnumerator SpawnCoin()
    {
        while (!GameManager.Instance.IsGameOver)
        {
            yield return new WaitForSeconds(Random.Range(1f, 2.5f));
            if (coinStack.Count =< 0)            
                PushCoin();
            
            GameObject coin = coinStack.Pop();
            coin.transform.position = new Vector2(Random.Range(-12, 12), 9);
            coin.SetActive(true);
        }
    }
    
    private void PushCoin() 
    {
        GameObject coin = Instantiate(coinPrefab);
        coin.SetActive(false);
        coinStack.Push(coin);
    }

    public void PushCoin(GameObject coin)
    {
        coinStack.Push(coin);
    }
}

GameManager.cs

using UnityEngine;
using UnityEngine.Events;

public class GameManager : MonoBehaviour
{
    private static GameManager _instance;

    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<GameManager>();
            }
            return _instance;
        }
    }

    public UnityEvent<int> OnScoreChanged;
    private int _score = 0;
    public int Score
    {
        get
        {
            return _score;
        }
        set
        {
            _score = value;
            OnScoreChanged?.Invoke(value);
        }
    }

    public bool IsGameOver = false;
}

GamePlayerAnimation.cs

using UnityEngine;
using UnityEngine.InputSystem;

public class GamePlayerAnimation : MonoBehaviour
{
    private float _direction;
    private Animator _animator;
    private SpriteRenderer _spriteRenderer;

    private void Awake()
    {
        _animator = GetComponent<Animator>();
        _spriteRenderer = GetComponent<SpriteRenderer>();
    }
    
    public void OnMove(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            _direction = context.ReadValue<Vector2>().x;
            if (_direction < 0) _spriteRenderer.flipX = true;
            else if (_direction > 0) _spriteRenderer.flipX = false;
            _animator.SetBool("IsRun", true);
        }    
        else if (context.canceled)
        {
            _animator.SetBool("IsRun", false);
        }
    }
}

GamePlayerController.cs

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;

public class GamePlayerController : MonoBehaviour
{
    private Vector2 _moveInput;
    [SerializeField][Range(0.5f, 10f)] private float _moveSpeed;    
    
    private void Update()
    {
        transform.Translate(_moveInput * (_moveSpeed * Time.deltaTime));
    }
    
    public void OnMove(InputAction.CallbackContext context)
    {
        Vector2 direction = context.ReadValue<Vector2>();
        _moveInput = new Vector2(direction.x, 0);
    }
}

UIControl.cs

using TMPro;
using UnityEngine;

public class UIControl : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI _ScoreText;

    private void Start()
    {
        OnChangeScore(0);
    }
    
    private void OnEnable()
    {
        GameManager.Instance.OnScoreChanged.AddListener(OnChangeScore);
    }

    private void OnDisable()
    {
        GameManager.Instance.OnScoreChanged.RemoveListener(OnChangeScore);
    }
    
    private void OnChangeScore(int score)
    {
        _ScoreText.text = score.ToString();
    }
}
profile
Python Dev with Infra -> Game Programmer

0개의 댓글