본문 바로가기
게임 프로그래밍/게임개발 중급

게임개발 중급(9) - 2D 플랫포머 게임 만들기(4)

by jyppro 2023. 4. 28.

2D 플랫포머 게임 만들기(4)

 

이전 시간에 플레이어 움직임과 코인을 먹으면 점수가 올라가는 단계까지 진행하였습니다. 이번에는 스크립트 작성할 때, 최적화 혹은 더 간편하게 코드를 짜는 방법에 대해서 이야기 해보겠습니다.

 

사실 최적화 같은 기술은 한번 배운다고 하루아침에 잘하게 되는것은 아닙니다. 해당 코드와 게임의 구조를 생각하고, 어울리도록 만들 줄 아는 능력이 요구됩니다. 저도 게임 프로그래밍을 배우면서 작성하는 사람인지라 완벽하게 최적화하거나 간결한 코드를 만들 순 없습니다. 사람마다 코드를 짜는 방식도 다르고, 스타일도 다르기 때문에 항상 더 좋은 코드를 찾아서 나아가는 것이 최적화의 길입니다.

 

추후에 프로젝트가 커지는 것까지 고려한다면, 구조를 상당히 잘 짜야하지만, 일단 저는 간단한 예제로 시작했으니, 제 방식대로 한번 코드를 바꿔보겠습니다. 아마 구글링을 하시다 보면 훨씬 더 좋은 방식을 많이 찾아보실 수 있을 겁니다.

 

using UnityEngine;
using UnityEngine.UI;

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float jumpForce = 5f;
    public int scoreValue = 10;  // 코인 하나의 점수
    public int score = 0;  // 현재 점수
    private Rigidbody2D rb;
    private bool isGrounded = false;
    GameObject ST;

    void Start()
    {
        this.ST = GameObject.Find("ScoreText"); 
        rb = GetComponent<Rigidbody2D>();
    }

    void FixedUpdate()
    {
        float moveDirection = Input.GetAxis("Horizontal");
        rb.velocity = new Vector2(moveDirection * moveSpeed, rb.velocity.y);

        if (moveDirection > 0)
        {
            transform.localScale = new Vector3(1f, 1f, 1f);
        }
        else if (moveDirection < 0)
        {
            transform.localScale = new Vector3(-1f, 1f, 1f);
        }
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
        {
            rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
            isGrounded = false;
        }
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.gameObject.CompareTag("Ground"))
        {
            isGrounded = true;
        }

        if (other.gameObject.CompareTag("Item"))
        {
            score += scoreValue;  // 점수 증가
            if(score >= 0){
                this.ST.GetComponent<Text>().text = "Score: " + score.ToString();
            }  // UI Text에 점수 반영
            Destroy(other.gameObject);  // 코인 아이템 제거
        }
    }
}

 

저는 크게 바꾸지 않고, 기존에 PlayerController 와 ScoreManager로 나눠져있던 스크립트 두개를 하나로 합쳤습니다.

ScoreManager에 있던 변수와 동작하는 코드를 그대로 가져와 플레이어가 처리할 수 있도록 조금 바꿔주면 하나로 합칠 수 있습니다.

 

제가 이렇게 한 이유는 어차피 간단한 게임이라면 나눠서 처리할 필요가 없고, 플레이어의 동작으로 이루어지는 것이기 때문입니다. 이렇게 작성하면 이전과 똑같이 동작합니다.

 

하지만 약간의 문제가 발생합니다. 동작은 똑같이 하지만, 코인과 부딪힐 때, 자연스럽게 코인을 먹지 않고, 플레이어가 어딘가에 부딪히는 것처럼 움직입니다. 그 이유는 역시 부딪히는 동작을 처리해주는 Collider에 있습니다. 저희가 해당 코드를 합칠 때, 코인에 있던 동작코드를 OnCollisonEnter2D에 넣었습니다. 하지만 해당 이벤트는 반드시 부딪히는 동작이 있어야만 작동합니다. 이것이 무슨 뜻이냐, 이전에 저희가 사용했던 isTrigger() 를 사용하여 물리적으로 부딪히는 것이 아닌 부딪혔다는 처리만 해주도록 해야합니다.

 

다음 코드를 보시겠습니다.

 

using UnityEngine;
using UnityEngine.UI;

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float jumpForce = 5f;
    public int scoreValue = 10;  // 코인 하나의 점수
    public int score = 0;  // 현재 점수
    private Rigidbody2D rb;
    private bool isGrounded = false;
    GameObject ST;

    void Start()
    {
        this.ST = GameObject.Find("ScoreText"); 
        rb = GetComponent<Rigidbody2D>();
    }

    void FixedUpdate()
    {
        float moveDirection = Input.GetAxis("Horizontal");
        rb.velocity = new Vector2(moveDirection * moveSpeed, rb.velocity.y);

        if (moveDirection > 0)
        {
            transform.localScale = new Vector3(1f, 1f, 1f);
        }
        else if (moveDirection < 0)
        {
            transform.localScale = new Vector3(-1f, 1f, 1f);
        }
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
        {
            rb.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
            isGrounded = false;
        }
    }

    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.gameObject.CompareTag("Ground"))
        {
            isGrounded = true;
        }  
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        score += scoreValue;  // 점수 증가
        if(score >= 0){
        this.ST.GetComponent<Text>().text = "Score: " + score.ToString();
        }  // UI Text에 점수 반영
        Destroy(other.gameObject);  // 코인 아이템 제거
    }
}

 

이전 코드와 달라진 점은 OnCollisonEnter2D에 있던 코드를 OnTriggerEnter2D를 새로 만들어서 그대로 넣어줬습니다. 넣어주는 과정에서 코인이라고 체크해주는 CompareTag 코드도 삭제해주었습니다. 더 큰 프로젝트를 진행하다보면 태그로 관리 하는 방식이 필요할 수 있겠지만 이 예제에서는 불필요하다고 느꼈습니다.

이렇게 작성한 뒤, 코인 오브젝트에 isTrigger를 체크해주어야 합니다. 그렇게 하면 코인과 부딪혔을 때, 물리적 충돌은 일어나지 않지만, 부딪혔다는 처리를 해줌으로써, 저희가 작성한 동작을 해주게 됩니다.

 

최적화는 이미 이야기 했듯이, 모든 개발자의 끝없는 발전의 길이고, 성장할 수 있는 지름길입니다. 만약 이 코드를 보고 맘에 안든다고 생각했다면, 당신은 더욱 발전할 수 있을 것입니다. 항상 더 좋은 코드를 찾아서 공부하는 것을 추천드립니다.

 

여기까지 진행하면서 아마도 여러분은 코인 오브젝트를 생성하고 사용할 때, 오브젝트를 하나하나 복사하고, isTrigger 처리를 해줄때도, 하나하나 눌러줘야 한다는 것이 불편하다고 느꼈을 것입니다. 이것을 해결해 주기 위한 프리팹에 대해 다음에는 설명하겠습니다. 감사합니다.