※ 주의
이 글은 아마추어가 개인적으로 생각하여 작성하는 것으로, 이곳에 나오는 내용을 맹신하지 않는것을 당부드립니다.
메뉴
1. 움직임 적용.
- Horizontal을 이용한 움직임.
2. 중력 적용.
- 점프에 의한 중력.
- 낙하에 의한 중력.
3. 충돌 적용. (레이 캐스트로 충돌체크.)
- 점프로 인한 위쪽블록과의 충돌.
- 낙하로 인한 블록과의 충돌.
- 오른쪽, 혹은 왼쪽으로 움직일때에 벽과 충돌.
4. 점프
- 점프로 인한 충돌체크 및 중력적용.
5. 좌,우 충돌체크.
3. 레이 캐스트 충돌체크
이번엔 충돌을 적용시켜 보도록 하자.
Ray를 플레이어의 아래쪽으로 쏴서 땅에 맞는다면 플레이어는 땅에 존재하는 것이므로,
중력을 적용하지 않으면 된다.
반대로 Ray를 쐈을 때 아무것도 맞지 않는다면 플레이어의 발 밑에 발판이 존재하지 않는다는 말이므로,
중력을 적용하면 된다.
Ray를 이용하여 충돌여부를 미리 체크하므로써 효율적인 연산을 수행할 수 있다.
Ray를 맞은 객체가 땅이라고 판별하기 위해서는 한 가지 작업이 필요하다.
Layer에 Ground라는 요소를 추가하여 설정하면 된다.
설정이 끝난 뒤 Ray에 맞으면 그 객체가 땅인지 아닌지 판별할 수 있게 된다.
아무 객체를 선택한 후, 인스펙터창 우측 상단에 Layer를 클릭하여 Ground를 추가 한 후
발판 객체들을 선택하여 Layer를 Ground로 설정 하도록 하자.
1.
2.
3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System.Collections; using System.Collections.Generic; using UnityEngine;
public class Player : MonoBehaviour { // 공개 public float Speed; // 좌, 우로 움직이는 스피드. public float Gravity; // 작용하는 중력. // 비공개 private Vector2 Pos; // 플레이어가 실직적으로 움직일 좌, 우 벡터. private Vector2 Dir; // 점프 및 낙하 여부의 벡터. private Vector2 Radius; // 플레이어의 x,y의 반지름. private float H; // Horizontal. private bool isGround; // 현재 땅이냐? true 땅이 아니냐? false // 각종 값 초기화. void Init() { Pos = Vector2.zero; Dir = Vector2.down; Radius = new Vector2(transform.localScale.x * 0.5f, transform.localScale.y * 0.5f); H = 0; isGround = false; } void Start() { Init(); } void Update() { H = Input.GetAxis("Horizontal"); CollisionCheck(); Move(); } // 좌, 우 움직임. void Move() { Pos.Set(H * Speed, Pos.y); transform.Translate(Pos * Time.deltaTime); } // 충돌 체크를 위한 레이 캐스트. void CollisionCheck() { RayCastFire(transform.position, Dir, 0.6f); } // 레이캐스트를 발사한다. void RayCastFire(Vector2 _Axis, Vector2 _Dir, float _length) { RaycastHit Hit; Debug.DrawRay(_Axis, _Dir * _length, Color.blue, 1); if (Physics.Raycast(_Axis, _Dir, out Hit, _length)) { // Ray를 발사 했는데 땅에 맞았음. if (Hit.transform.gameObject.layer == 8) { // 플레이어가 땅에 있으면 함수 종료. if (isGround) return; Transform Target = Hit.transform; // 충돌한 블럭의 위에 정착. transform.position = new Vector2(transform.position.x, Target.position.y + (Target.localScale.y * 0.5f) + Radius.y + 0.1f); Pos.y = 0; // 중력이 작용했던 값에 대해 0으로 초기화. isGround = true; // 이제 땅에 있다고 표시. return; } } // Ray를 발사 했는데 땅에 맞지 않았음. isGround = false; Pos.y -= Gravity * Time.deltaTime; } } | cs |
※ Radius변수 설명에 대해서는 밑을 참고.
※
isGround == true : 현재 플레이어가 땅에 존재함.
isGround == false : 현재 플레이어가 땅에 존재하지 않음.
RaycastHit Hit;
Hit 변수에는 Ray에 맞은 객체에 대한 정보가 들어있다.
현재 사용한 레이캐스트의 인자들을 살펴보면,
Physics.Raycast(Ray가 발사되는 위치,
Ray가 발사되는 방향,
Ray를 맞을 객체에 대한 정보를 저장할 변수,
Ray의 길이 );
이렇게 되어있다.
Ray에 맞은 객체에 대한 정보가 Hit에 존재하기 때문에 Hit.transform.gameObject.laye의 경로로
그 객체의 레이어를 체크한다.
체크된 레이어가 8번 레이어(Ground)라면
플레이어의 위치를 발판 위에 올려놔야 되는데,
플레이어의 x값에 대해서는 변환할 필요가 없기 때문에,
y값만 바꿔주면 된다.
보통 어떤 객체의 기준점은 객체의 정 가운데 있다.
그림으로 살펴보면 이렇다.
그래서 Ray가 발판에 닿았을 때 플레이어의 위치를 발판의 위치로 대입해 버리면
밑에의 그림과 같은 상황이 일어난다.
transform.position = 객체.position;
그렇기 때문에 밑에 그림과 같이 계산해야 한다.
플레이어의 위치를 발판 위에 올려다 놓고.
플레이어에게 작용하는 y의 벡터 값을 0으로 줘버린다.
그리고 isGround의 값을 true로 줌으로써 현재 자신은 땅에 존재한다고 명시해준다.
다음 Ray를 쐈을 때 Ray가 발판에 닿았고 또 위의 구문을 수행하기전에
if (isGround)
return;
의 구문에 의해서 밑의 구문을 수행하지 않고 함수를 종료해 버린다.
결과.
※파란색 선이 Ray이다.
Ray를 쏴서 충돌을 체크하는데, Ray가 나오지 않는 부분은 충돌이 될까?
※ Ray가 닿지 않는 부분은 충돌이 되지 않는것을 볼 수 있다.
당연히 충돌이 되지 않는다.
그러면 어떻게 해야할까? 플레이어의 크기 만큼 Ray를 쏴주면 된다.
Ray를 플레이어의 크기만큼 쏴주기 위해 새로운 변수를 선언한다.
Vector2 Radius;
자료형을 벡터로 선언한 이유는 이렇다.
벡터는 2가지 정보를 가지고 있다.
바로 x값과 y값인데, 플레이어의 x만큼의 반지름과 y만큼의 반지름 크기가 필요하다.
그럼 float형으로 x값과 y값 따로 선언해 주면 되지 않냐고 묻는다면, 그렇게 해도 아무 문제도 없다.
아무튼 원래 Ray를 쏘았던 위치에서 플레이어의 반지름을 더해주거나 빼주면 플레이어의 크기만큼 Ray를
발사할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { // 공개 public float Speed; // 좌, 우로 움직이는 스피드. public float Gravity; // 작용하는 중력. // 비공개 private Vector2 Pos; // 플레이어가 실직적으로 움직일 좌, 우 벡터. private Vector2 Dir; // 점프 및 낙하 여부의 벡터. private Vector2 Radius; // 플레이어의 x,y의 반지름. private float H; // Horizontal. private bool isGround; // 현재 땅이냐? true 땅이 아니냐? false // 각종 값 초기화. void Init() { Pos = Vector2.zero; Dir = Vector2.down; Radius = new Vector2(transform.localScale.x * 0.5f, transform.localScale.y * 0.5f); H = 0; isGround = false; } void Start() { Init(); } void Update() { H = Input.GetAxis("Horizontal"); CollisionCheck(); Move(); } // 좌, 우 움직임. void Move() { Pos.Set(H * Speed, Pos.y); transform.Translate(Pos * Time.deltaTime); } // 충돌 체크를 위한 레이 캐스트. void CollisionCheck() { for (int i = -1; i < 2; i++) { Vector2 MyPos = transform.position; MyPos = new Vector2(MyPos.x + (Radius.x * i), MyPos.y); RayCastFire(MyPos, Dir, Radius.y + 0.1f); } } // 레이캐스트를 발사한다. void RayCastFire(Vector2 _Axis, Vector2 _Dir, float _length) { RaycastHit Hit; Debug.DrawRay(_Axis, _Dir * _length, Color.blue, 1); if (Physics.Raycast(_Axis, _Dir, out Hit, _length)) { // Ray를 발사 했는데 땅에 맞았음. if (Hit.collider.gameObject.layer == 8) { // 플레이어가 땅에 있으면 함수 종료. if (isGround) return; Transform Target = Hit.transform; // 충돌한 블럭의 위에 정착. transform.position = new Vector2(transform.position.x, Target.position.y + (Target.localScale.y * 0.5f) + _length); Pos.y = 0; // 중력이 작용했던 값에 대해 0으로 초기화. isGround = true; // 이제 땅에 있다고 표시. return; } } // Ray를 발사 했는데 땅에 맞지 않았음. isGround = false; Pos.y -= Gravity * Time.deltaTime; } } | cs |
다음은 점프를 구현해 보도록 하자.