슈퍼마리오 게임 만들기 (1) - 이동 및 점프

2025. 6. 2. 23:39·개발/Unity

3주 간 멋사 강의에서 배운 내용들을 토대로 슈퍼마리오같은 플랫포머 게임을 만들어보려고 한다.

혼자서 실습하는 것은 처음이기 때문에 기대된다.

 

기본 맵 생성

캐릭터와 지형은 유니티 에셋 스토어에 있는 무료 에셋 '심플 2D 플랫포머 에셋 팩'을 사용하였다.

https://assetstore.unity.com/packages/2d/characters/simple-2d-platformer-assets-pack-188518

 

Simple 2D Platformer Assets Pack | 2D 캐릭터 | Unity Asset Store

Elevate your workflow with the Simple 2D Platformer Assets Pack asset from Goldmetal. Find this & more 캐릭터 on the Unity Asset Store.

assetstore.unity.com

 

캐릭터와 지형 및 장애물과 목표지점을 만들어주었다.

Collider, Rigid Body 적용

캐릭터에게는 Capsule Collider와 Rigid Body 컴포넌트를 추가하여 물리 작용을 적용해주었고, 지형은 아직 타일맵 콜라이더를 생성하는 법을 배우지 않았기 때문에 Edge Collider를 이용해 걸을 수 있게 하였다.

 

Managers 생성 및 이동 Script 작성

싱글톤 이용 Managers 생성

아직 수업에서는 싱글톤 패턴을 배우지 않았지만, 추후 관리를 위해 싱글톤을 적용하였다.

using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance;
    static Managers Instance { get { Init(); return s_instance; } }
    
    void Start()
    {
        Init();
    }

    static void Init()
    {
        if (s_instance == null)
        {
            GameObject go =GameObject.Find("@Managers");
            if (go == null)
            {
                go = new GameObject("@Managers"); 
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();
        }
    }
}

 

Character Controller 생성 및 Rigid Body 이용 이동

using System;
using UnityEngine;

public class CharacterController : MonoBehaviour
{
    Rigidbody2D characterRb;
    
    private float h;
    
    [SerializeField] 
    float _speed = 5.0f;
    
    void Start()
    {
        characterRb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        h = Input.GetAxis("Horizontal");
    }

    void FixedUpdate()
    {
        characterRb.linearVelocityX = h * _speed;
    }
}

Rigid Body를 이용한 이동을 구현하였다.

키 입력은 Input Manager를 이용해 Update문에서 받지만, 물리적 이동은 FixedUpdate에서 이루어진다.

 

Input Manager 생성

using System;
using UnityEngine;

public class InputManager
{
    public Action KeyAction = null;

    public void OnUpdate()
    {
        if (Input.anyKey == false) 
            return;
        
        if (KeyAction!= null) 
            KeyAction.Invoke();
    }
}

이 실습에서는 키 입력이 캐릭터 이동과 점프 뿐이기 때문에 Input Manager까지 만들어서 관리해야할 필요는 못 느꼈지만, 개인적으로 인강을 들으면서 헷갈렸던 부분이라 적용해보았다. 

Input Manager는 Monobehavior Script가 아닌 그냥 class로, 다른 곳에서 부르지 않으면 사용할 수 없다.

// Managers
InputManager _input = new InputManager();
public static InputManager Input { get { return Instance._input; } }

void Update()
{
    _input.OnUpdate();
}

따라서 위처럼 Managers 스크립트에 InputManager를 부르는 코드를 작성하였고, OnUpdate()를 실행시켜주었다.

OnUpdate()는 Update()와 같이 실행되지만, Input이 들어오지 않은 경우에는 return될 것이다.

// CharacterController
void Start()
{
    Managers.Input.KeyAction -= OnKeyboard;
    Managers.Input.KeyAction += OnKeyboard;

    characterRb = GetComponent<Rigidbody2D>();
}

void OnKeyboard()
{
    h = Input.GetAxis("Horizontal");
}

InputManager를 사용하기 위해 Player Controller에도 코드를 추가해주었다.

Update문에서 실행되었던 input을 받는 구문은 OnKeyboard라는 이름으로 바꿔서 Action으로서 Input.KeyAction에 추가했다. 중복으로 구독하는 일이 없도록 OnKeyboard를 뺀 다음 더해주었다.

이제 키보드 입력이 없을 경우에는 실행하지 않고, 키보드가 있을 경우에만 InputManager에서 Invoke되어 실행될 것이다.

 

Jump 스크립트

// CharacterController
[SerializeField] 
float jumpPower = 3.0f;

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

void OnCollisionExit2D(Collision2D other)
{
    if (other.gameObject.CompareTag("Ground")) isGround = false;
}
    
 void OnKeyboard()
{
    h = Input.GetAxis("Horizontal");
    if (Input.GetButtonDown("Jump") && isGround)
    {
        _characterRb.AddForceY(jumpPower, ForceMode2D.Impulse);
    }
}

OnCollision 이벤트를 이용해 바닥에 닿아있는지 체크하고, 점프 버튼(Space)를 눌렀다면 AddForceY()를 이용해 점프하도록 구현하였다. AddForceY() 또한 물리적 작용이기 때문에 FixedUpdate()에서 호출하는 것이 좋을까 고민했지만, 그렇게 되면 Update()에서 FixedUpdate()가 실행되기까지의 시간이 밀리기 때문에, 사용자 입장에서 점프 버튼을 눌렀을 때 딜레이가 느껴질 것 같아 그대로 점프하도록 하였다.

 

스프라이트 애니메이션 적용

수업 시간에는 모든 애니메이션을 자식 요소로 넣어놓고 상태에 따라 활성화시켜 애니메이션이 바뀌는 것처럼 연출하였다. 하지만 나는 실제로 이용하는 애니메이션 기법을 먼저 써보고 싶어서 State를 이용하여 애니메이션을 구현하였다.

 

Animator 설정

기본 동작은 Idle이며, moving이 0보다 클 시 Run을 실행하고, isJumping이 true라면 Jump 애니메이션을 실행하도록 설정했다.

 

PlayerState 설정

// CharacterController
public enum PlayerState
{
    Idle,
    Moving,
    Jumping,
    Dead,
}

void Update()
{
    switch (state)
    {
        case PlayerState.Idle:
            UpdateIdle();
            break;
        case PlayerState.Moving:
            UpdateMoving();
            break;
        case PlayerState.Jumping:
            UpdateJumping();
            break;
        case PlayerState.Dead:
            UpdateDead();
            break;
    }
}

enum 타입의 PlayerState를 선언하고, state에 따라 다른 Update문이 실행되게 분기 처리하였다. 현재는 Update문에서 별다른 이벤트가 실행되지 않지만, 추후 state에 따른 동작이 있을 경우 해당 함수 내에서 실행 가능하다.

Script에 Animator State 추가

void UpdateMoving()
{
    h = Input.GetAxis("Horizontal");
    if (Math.Abs(h) < 0.001f)
    {
        h = 0f;
        state = PlayerState.Idle;
        Animator anim = GetComponent<Animator>();
        anim.SetFloat("moving", 0);
    }

}

void OnCollisionEnter2D(Collision2D other)
{
    if (other.gameObject.CompareTag("Ground")) isJumping = false;
    Animator anim = GetComponent<Animator>();
    anim.SetBool("isJumping", false);
}

void OnKeyboard()
{
    Animator anim = GetComponent<Animator>();
    // 이동
    h = Input.GetAxis("Horizontal");
    if (Math.Abs(h) > 0)
    {
        anim.SetFloat("moving", Math.Abs(h));
        state = PlayerState.Moving;
    }
    // 점프
    if (Input.GetButtonDown("Jump") && isJumping == false)
    {
        anim.SetBool("isJumping", true);
        state = PlayerState.Jumping;
        characterRb.AddForceY(jumpPower, ForceMode2D.Impulse);
    }
}

void FixedUpdate()
{
    characterRb.linearVelocityX = h * speed;
}

모션이 바뀌는 순간 animator의 state도 바뀌도록 코드를 추가하였다. 

이동의 경우, 키보드 입력 시 OnKeyboard()에서 h 값을 확인해 Moving State로 바꾼다. State가 Moving인 경우 UpdateMoving에서 h를 계속 체크하다가 h가 0에 가까워진다면 State를 Idle로 바꾼다.

점프의 경우, 키보드 입력 시 Jumping State로 바뀌고 바닥과 충돌 시 Jumping이 해제된다.

그 외에도 가독성을 위해 isGround 값을 isJumping으로 바꾸었고, 키보드를 떼고도 h가 0이 되지 않는 부분을 수정하였다.

본 수업에서는 State를 사용한 애니메이션을 진행하지 않았는데, 인강에서 배운 State를 Rigid Body 이동에 적용하려니 적절히 작성한건지 잘 모르겠다. 또한 State 관리를 이렇게 중구난방으로 해도 되는지 잘 모르겠어서 추후 학습을 통해 수정해나가야겠다.

 

방향에 따른 좌우 반전

// CharacterController

private SpriteRenderer spriteRenderer;

void Start()
{
    spriteRenderer = GetComponent<SpriteRenderer>();
}

void FixedUpdate()
{
    if (h > 0.001)
    {
        spriteRenderer.flipX = false;
    }
    else if (h < -0.001)
    {
        spriteRenderer.flipX = true;
    }
    characterRb.linearVelocityX = h * speed;
}

좌우 반전은 Sprite Renderer의 FlipX를 이용했다. SpriteRenderer를 불러오고, FixedUpdate에서 h값에 따라 방향을  설정한다.

 

결과물

'개발/Unity' 카테고리의 다른 글
  • 슈퍼마리오 게임 만들기 (4) - 홈 화면 (UI, Scene 이동)
  • 슈퍼마리오 게임 만들기 (4) - 구현 기능 정리
  • 슈퍼마리오 게임 만들기 (3) - 타일맵, 카메라 이동, 몬스터
  • 슈퍼마리오 게임 만들기 (2) - 코인, 장애물, 클리어
로또
로또
게임 개발자 연습생의 발전 일지
  • 로또
    게임 개발 발전소
    로또
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 개발
        • 코딩테스트
        • JAVA
        • DB
        • Unity
      • 강의
        • 패스트캠퍼스 0원 챌린지
        • 멋쟁이 사자처럼 유니티 부트캠프
      • 게임
        • 공부
        • 리뷰
  • 블로그 메뉴

    • 홈
    • 방명록
    • 글쓰기
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    트리
    Java
    그리디
    패캠챌린지
    환급챌린지
    C#
    오공완
    dfs
    수강료0원챌린지
    완전탐색
    패캠인강후기
    게임개발
    한번에끝내는프론트엔드개발초격차패키지Online
    C4D
    직장인인강
    BFS
    직장인자기계발
    분리집합
    멋쟁이사자처럼후기
    백준
    자료구조
    그리디알고리즘
    그래프
    패스트캠퍼스후기
    3D웹인터랙티브
    백트래킹
    2839
    코딩테스트
    패스트캠퍼스
    Unity
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
로또
슈퍼마리오 게임 만들기 (1) - 이동 및 점프
상단으로

티스토리툴바