ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [유니티 2D 스터디] 캐릭터 이동, 벽 충돌처리
    쾌락없는 책임 (공부)/Unity 2021. 3. 11. 21:29
    반응형

    <전체 소스코드>

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PlayerMove : MonoBehaviour{
        Rigidbody2D rigid;
        SpriteRenderer spriteRenderer;
        Animator anim;
        public float maxSpeed;
        public float jumpPower;
        public int jumpCount = 0;
        public int maxJumpCount = 2;
        // 방향을 가져오기 위한 변수
        Vector3 dirVec;
        // 레이로 스캔하는 물체 받아오는 변수
        GameObject scanObject;
    
        void Awake() {
            rigid = GetComponent<Rigidbody2D>();
            spriteRenderer = GetComponent<SpriteRenderer>();
            anim = GetComponent<Animator>();
        }
    
        void Update(){
            // 버튼에서 손을 떼면 움직임 멈춤
            if(Input.GetButtonUp("Horizontal")){
                rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.2f, rigid.velocity.y);
            }
    
            // Flip
            if (Input.GetButton("Horizontal"))
                spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == 1;
            
            // 걷는 애니메이션
            if (Mathf.Abs(rigid.velocity.x) < 0.2f || jumpCount > 0) //속도가 0이라면 = 단위벡터가 0
                anim.SetBool("isWalk", false);
            else
                anim.SetBool("isWalk", true);
    
            //바라보는 방향
            float h = Input.GetAxisRaw("Horizontal");
    
            if(h == -1)
                dirVec = Vector3.left;
            else if(h == 1)
                dirVec = Vector3.right;
            
            // 점프
            if(Input.GetKeyDown(KeyCode.Space) && jumpCount < maxJumpCount){
                rigid.velocity = Vector2.up * jumpPower;
                jumpCount++;
            }
        }
        void FixedUpdate(){
            // 좌우 이동
            float h = Input.GetAxisRaw("Horizontal");
            rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);
    
                // 최대 스피드 maxSpeed 를 넘지 못하게 함
            if(rigid.velocity.x > maxSpeed)
                rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);
            else if(rigid.velocity.x < -maxSpeed)
                rigid.velocity = new Vector2(-maxSpeed, rigid.velocity.y);
    
            // 플레이어 아래로 쏘는 ray, 내려올때만
            if(rigid.velocity.y < 0){
                Debug.DrawRay(rigid.position, Vector3.down, new Color(1, 0, 0));
                RaycastHit2D downRay = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Ground"));
    
                if(downRay.collider != null && downRay.distance < 0.5f){
                    Debug.Log("땅");
                    jumpCount = 0;
                }
            }
    
            // 조사를 하기 위한 Ray
            // 씬 상에서 빨간색 선을 쏴서 레이에 닿으면 상호작용 가능하게 개조하면 됨
            Debug.DrawRay(rigid.position, dirVec * 1.5f, new Color(1, 0, 0));
            RaycastHit2D rayHit2 = Physics2D.Raycast(rigid.position, dirVec, 1.5f, LayerMask.GetMask("Object"));
    
            if(rayHit2.collider != null)
            {
                scanObject = rayHit2.collider.gameObject;
            }
            else
            {
                scanObject = null;
            }
            
        }
    }
    

    <FixedUpdate와 Update 차이>

       - Update : 프레임 단위로 호출되는 함수, 단발적인 키 입력을 받기 좋음

       - FixedUpdate : Update와 달리 일정한 시간으로 호출되는 함수, 지속적인 키 입력을 받기 좋음

     업데이트 함수는 프레임 단위로 호출되기 때문에 물리 엔진과 다른 시간을 가진다고 합니다. 그래서 물리 충돌과 관련한 기능들은 FixedUpdate에서 구현해야 한다고 합니다.

     

     

     

    <변수 설명>

    Rigidbody2D rigid;
    SpriteRenderer spriteRenderer;
    Animator anim;

     

     위 3개는 각각 플레이어의 리지드바디, 스프라이트랜더러, 애니메이션 컨트롤러를 가져오기 위한 변수로 Awake 함수에서 초기화 되었습니다.

     

    public float maxSpeed;
    public float jumpPower;
    public int jumpCount = 0;
    public int maxJumpCount = 2;

     maxSpeed의 경우 플레이어의 최대 이동 속도를 의미하며 jumpPower은 AddForce에서 Vector2.up에 곱해지게 됩니다.

     jumpCount와 maxJumpCount는 각각 현재 플레이어의 점프 횟수와 최대 횟수를 의미합니다. maxJumpCount 변수 조작으로 점프 횟수를 조정할 수 있으며 이후 아이템 등을 통해서 늘어날 여지가 있게 public으로 선언했습니다.

    // 방향을 가져오기 위한 변수
    Vector3 dirVec;
    // 레이로 스캔하는 물체 받아오는 변수
    GameObject scanObject;

     위 두 변수는 플레이어 앞에 달려 있는 Ray의 방향을 정해주기 위해 있으며 dirVec에 방향이 달리게 되며 scanObject에는 Ray에 닿은 물체를 가져오게 됩니다.

     

     

     

    <플레이어 이동>

     

    1. 좌 우 이동

        void Update(){
            // 버튼에서 손을 떼면 움직임 멈춤
            if(Input.GetButtonUp("Horizontal")){
                rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.2f, rigid.velocity.y);
            }
        }
        
        void FixedUpdate(){
            // 좌우 이동
            float h = Input.GetAxisRaw("Horizontal");
            rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);
    
                // 최대 스피드 maxSpeed 를 넘지 못하게 함
            if(rigid.velocity.x > maxSpeed)
                rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);
            else if(rigid.velocity.x < -maxSpeed)
                rigid.velocity = new Vector2(-maxSpeed, rigid.velocity.y);
    	}
    
    

     기본적으로 FixedUpdate에서 키보드 입력을 받습니다. 유니티 프로젝트 설정에 있는 Horizontal을 이용해 해당하는 키 (방향키, AD)를 입력받으면 rigid.AddForce를 통해 오른쪽으로 힘을 가해줍니다. 왼쪽의 경우 변수 h가 음수가 되어서 왼쪽으로 이동하게 됩니다.

     일단 저 AddForce의 경우 물체에 계속해서 힘을 가하는것이라 속도가 점점 빨라지게 됩니다. 때문에 아래 if문을 통해 maxSpeed란 변수보다 빠를 수 없게 제어를 해 줍니다.

     

     Update에 있는 코드는 키보드에서 손을 떼는 순간 속도를 급격하게 줄여 마찰과 공기저항 등을 통해 플레이어가 빠르게 정지할 수 있도록 해주는 코드입니다.

     

     

    2. 점프

        void Update(){
            // 점프
            if(Input.GetKeyDown(KeyCode.Space) && jumpCount < maxJumpCount){
                rigid.velocity = Vector2.up * jumpPower;
                jumpCount++;
            }
        }
        void FixedUpdate(){
            // 플레이어 아래로 쏘는 ray, 내려올때만
            if(rigid.velocity.y < 0){
                Debug.DrawRay(rigid.position, Vector3.down, new Color(1, 0, 0));
                RaycastHit2D downRay = Physics2D.Raycast(rigid.position, Vector3.down, 1, LayerMask.GetMask("Ground"));
    
                if(downRay.collider != null && downRay.distance < 0.5f){
                    Debug.Log("땅");
                    jumpCount = 0;
                }
            }
    }
    

     좌우 이동의 경우 키보드를 지속적으로 누르는 반면 범프의 경우 단발적으로 누르는 이벤트라 Update에서 키 입력을 받는다고 합니다. (from. 유튜브) 점프를 했을 시 플레이어를 위로 띄우게 되고 이 때 jumpPower만큼 힘을 가하게 됩니다.

     

     점프 횟수의 경우 maxJumpCount로 컨트롤하고 있는데 키 입력 점프시 jumpCount 변수를 1씩 증가시키고 키 입력에서 jumpCount < maxJumpCount인지 조건을 넣어줬습니다. 프로젝트에서는 maxJumpCount를 2로 두어서 2단 점프까지 할 수 있습니다.

     

    jumpCount는 플레이어가 땅에 닿으면 0으로 초기화가 되며 플레이어가 땅에 닿는건 RaycastHit2D를 통해서, 이 Ray는 플레이어가 공중에서 떨어질때만 나오게 됩니다.

     

    + 점프 관련 개선점

        - 지금의 Ray는 단일 ray로 플레이어 중심에서 내려오게 됩니다. 때문에 플랫폼 끝에 걸치는 경우 점프 초기화가 안되는 경우가 예상됩니다.

     

     

     

    <애니메이션 전환>

     

     애니메이션의 경우 스프라이트의 애니메이션이 다양하지 않아 좌우 전환, 달리기 모션만 처리했습니다.

     

    1. Flip

        void Update(){
            // Flip
            if (Input.GetButton("Horizontal"))
                spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == 1;
    
            //바라보는 방향
            float h = Input.GetAxisRaw("Horizontal");
    
            if(h == -1)
                dirVec = Vector3.left;
            else if(h == 1)
                dirVec = Vector3.right;
        }
      

     SpriteRenderer를 통해서 좌우 회전을 하게 되는데 인스펙터창의 Flip X가 눌려진게 true (==1) 눌리지 않은게 false (==0) 현재 스크립트가 담긴 스프라이트들에 따라 1로 만들지 -1로 만들지 살펴봐야 합니다.

     

     아래 바라보는 방향의 경우 좌, 우 조사를 하기 위한 Ray가 플레이어 방향을 바라볼 수 있게끔 dirVec 변수에 담아주는 과정이며 dirVec는 전역변수입니다.

     

    2. 애니메이션 전환

        void Update(){
            // 걷는 애니메이션
            if (Mathf.Abs(rigid.velocity.x) < 0.2f || jumpCount > 0) //속도가 0이라면 = 단위벡터가 0
                anim.SetBool("isWalk", false);
            else
                anim.SetBool("isWalk", true);
        }
      

     애니메이션의 경우 idle, walk 2가지를 준비했으며 isWalk 변수가 true가 되면 걷는 애니메이션으로 변경되게 했습니다.

     

     속도가 0이거나 jumpCount가 양수 (점프를 한 상태) 일 경우 isWalk는 false가 되어 idle 모션이 되고 외의 경우에는 걷는 모션이 됩니다.

    <벽과의 충돌처리>

     벽의 경우 material을 추가해 마찰을 0으로 만들어 플레이어가 매달릴 수 없게 만들었습니다.

    반응형

    댓글

Designed by Tistory.