기록 보관소

[Unity/유니티] 기초-2D 플랫포머: 플레이어 점프 구현하기[B16] 본문

유니티 프로젝트/2D 플랫포머

[Unity/유니티] 기초-2D 플랫포머: 플레이어 점프 구현하기[B16]

JongHoon 2022. 2. 11. 16:46

개요

유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.

현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.

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 플랫포머: 플레이어 점프 구현하기[B16]

1. 물리력에 의한 점프

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

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump"))    //스페이스바
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);
    }

}

jumpPower를 12로 설정하고 스페이스바를 눌러보니 점프가 잘 된다
Project settings의 Physics 2D에서 중력 값(Gravity Y)을 설정

  • 실행해보니 점프는 잘 되지만, 내려올때 공기 저항 값(Linear Drag)때문에 속도가 느리다. 이는 Project Settings의 Physics 2D 설정에서 Gravity Y값을 바꿔서 해결할 수 있다.

Player의 Inspector창 일부

  • 다만 위 방법의 경우 전반적인 프로젝트 설정을 변경하는 것이 문제다. 원하는 스프라이트의 중력 비율만 조정하고 싶다면, 해당 스프라이트의 중력 값(Rigidbody 2D의 Gravity Scale 값)을 바꾸면 된다. 참고로 Gravity Scale은 비율 값이라서 기본 값 1은 100%가 된다.

2. 점프-착지 애니메이션

새로운 애니메이션 Jump 생성
&nbsp;점프는 단발성이므로 Loop Time 끄기
Player의 Animator창에서 Jump를 Idle, Walk 애니메이션과 연결
&nbsp;bool형 매개변수 isJumping 추가

  • Animator에서 isJumping 매개변수와 이전에 만들어두었던 isWalking을 사용하면된다.
  • Walk -> Jump : isWalking = true | isJumping = true
  • Jump -> Walk : isWalking = true | isJumping = false
  • Idle -> Jump : isWalking = false | isJumping = true
  • Jump -> Idle : isWalking = false | isJumping = false
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump")) {    //스페이스바
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);
    }

}

스페이스 바를 누르니 애니메이션 전환은 잘 되었다
하지만 애니메이션이 바뀌진 않는다
Animator 창에서도 Jump에서 멈춰있다


3. 레이캐스트

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

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump")) {    //스페이스바
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //레이캐스트
        Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
        RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1); //레이에 닿는 물체
        if (rayHit.collider != null) {   //레이에 닿는 물체가 있다면
            Debug.Log(rayHit.collider.name);    //물체 이름 콘솔창에 출력
        }
    }

}

Ray의 모습. 에디터상에서만 보인다.
Ray에 닿는 물체가 Player만 출력된다.

  • RayCast : 오브젝트 검색을 위해 Ray를 쏘는 방식
  • DrawRay(위치, 방향, 색상) : 에디터 상에서만 Ray를 그려주는 함수
  • RayCastHit : Ray에 닿은 오브젝트. 해당 변수의 콜라이더로 검색 확인 가능.
  • 콘솔창에 Floor는 나오지 않고 Player만 출력되는 이유는 RayHit가 관통이 되지 않기 때문이다.

LayerMask
새로운 Layer 추가, 이후 모든 Floor에 적용

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

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump")) {    //스페이스바
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //레이캐스트
        Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
        RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
        if (rayHit.collider != null) {   //레이에 닿는 물체가 있다면
            Debug.Log(rayHit.collider.name);    //물체 이름 콘솔창에 출력
        }
    }

}

정상적으로 Floor가 출력된다

  • LayerMask : 물리 효과를 구분하는 정수값
  • LayerMask.GetMask() : 레이어 이름에 해당하는 정수값을 리턴하는 함수
  • .distance : Ray에 닿았을 때의 거리
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump")) {    //스페이스바
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

}

점프시 Ray가 나타나고, 점프 애니메이션도 실행된다
착지하니 다시 Idle 애니메이션으로 바뀌고, Ray도 사라졌다


4. 무한 점프 막기

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

public class PlayerMove : MonoBehaviour {
    public float maxSpeed;
    public float jumpPower;
    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

    void Awake() {
        rigid = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();

        rigid.freezeRotation = true;    //이동시 굴러가기 방지
    }

    void Update() {
        //점프
        if (Input.GetButtonDown("Jump") && !anim.GetBool("isJumping")) {    //스페이스바, 점프 애니메이션이 작동중이지 않다면
            rigid.AddForce(Vector2.up * jumpPower, ForceMode2D.Impulse);
            anim.SetBool("isJumping", true);    //Jump 애니메이션 추가
        }

        //멈출 때 속도
        if (Input.GetButtonUp("Horizontal")) {
            //normalized : 벡터 크기를 1로 만든 상태
            rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
        }

        //방향 전환
        if (Input.GetButton("Horizontal"))
            spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;    //-1은 왼쪽 방향

        //애니메이션 전환
        if (Mathf.Abs(rigid.velocity.x) < 0.3)   //정지 상태, Mathf : 수학관련 함수를 제공하는 클래스
            anim.SetBool("isWalking", false);
        else
            anim.SetBool("isWalking", true);
    }

    void FixedUpdate() {
        float h = Input.GetAxisRaw("Horizontal");       //좌,우 A, D

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        //오른쪽 속도 조절
        if (rigid.velocity.x > maxSpeed)    //velocity : 리지드바디의 현재 속도
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);   //y축 값을 0으로 하면 멈춤
        //왼쪽 속도 조절
        else if (rigid.velocity.x < maxSpeed*(-1))
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);

        //점프 후 착지시 애니메이션 전환(레이캐스트)
        if (rigid.velocity.y < 0) { //y축 속도 값이 0보다 클때만. 땅에 있으면 레이 표시 X
            Debug.DrawRay(rigid.position, Vector3.down, new Color(0, 1, 0)); //레이 표시
            RaycastHit2D rayHit = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Platform")); //레이에 닿는 물체
            if (rayHit.collider != null) //레이에 닿는 물체가 있다면
                if (rayHit.distance < 0.5f)
                    anim.SetBool("isJumping", false);
        }
    }

}
  • 간단하게 점프 애니메이션에서 사용하는 isJumping 매개변수를 사용하여 false일때만 점프 키가 작동하도록 설정하면 무한 점프를 해결할 수 있다.