2D PlatFormer를 구현하다보면 빠지지 않는 것이 있다. 바로 사다리다.
사다리 타기 로직이 생각보다 구현할게 많아, 막상 구현해보면 골치가 아프다. 여기저기 정보라도 많으면 모르겠지만 한국어로 된 사다리타기 로직에 대한 설명이 별로 없는 거 같아. 내가 직접 만들어 보았다.

먼저 사다리가 다음과 같이 위는 걸쳐있고 밑은 걸쳐있지 않은 상태에서만 이 로직이 작동한다.
밑은 플레이어 캐릭터에 component로 추가되어있는 script 중 일부이다.
void StartLadding() {
Collider2D[] colliders = Physics2D.OverlapBoxAll(ladderCheck.bounds.center, ladderCheck.bounds.extents, 0);
foreach (Collider2D collider in colliders) {
if (collider.gameObject.layer == LayerMask.NameToLayer("Ladder")) {
isLadder = true;
BoxCollider2D colBc = collider.gameObject.GetComponent<BoxCollider2D>();
float x_pos = colBc.bounds.center.x;
float y_pos = Mathf.Clamp(transform.position.y, colBc.bounds.center.y - colBc.bounds.extents.y + 0.1f, colBc.bounds.center.y + colBc.bounds.extents.y - 0.1f);
transform.position = new Vector2(x_pos, y_pos);
rb.velocity = Vector2.zero;
break;
}
}
}
먼저 방향키 예를 들어, 위,아래 키가 입력되었을 때 실행되는 함수이다. 여기서 ladderCheck는 BoxCollider2D이며 내 캐릭터의 boxCollider2D를 참조하였다. foreach문을 돌면서 LayerMask가 Ladder인 collider를 찾고 존재하면 밑의 로직을 실행한다. isLadder = true로 바꿈으로써 현재 사다리에 매달려 있는 상황이라고 알려준다.(isLadder가 true이면 좌우 방향키 입력 불가, 사다리에 매달려있는 animation 실행 등 부가적인 코드들을 실행시키면 된다.)

본인은 위와 같이 활용하였다.
그 다음 사다리의 BoxCollider2D를 참조한다. Mathf.Clamp를 통해 플레이어의 position의 y값을 안전하게 설정해준다.

그 다음 Update 문에서 UpdateLadding 함수를 호출하였다. UpdateLadding함수는 다음과 같다.
void UpdateLadding() {
if (!isLadder) {
EndLadding();
return;
}
LadderCheck();
if (!isLadder) {
EndLadding();
return;
}
float direction = inputVec.y;
if (direction < 0 && CheckGround()) return;
if(direction == 0) {
StopLadding();
}
else {
GoLadding();
}
}
isLadder가 false인지 안전하게 두번 확인하였다. 그리고 false가 되었다면 EndLadding을 하였다. LadderCheck함수는 isLadder를 계속 업데이트해주는 함수이다. 다음과 같다.
void LadderCheck() {
Collider2D[] colliders = Physics2D.OverlapBoxAll(ladderCheck.bounds.center, ladderCheck.bounds.extents, 0);
foreach (Collider2D collider in colliders) {
isLadder = collider.gameObject.layer == LayerMask.NameToLayer("Ladder");
if (isLadder) {
break;
}
}
}
현재 플레이어가 ladder와 겹치는지 계속 확인한다. 안겹치면 false이다.
float direction = inputVec.y;
if (direction < 0 && CheckGround()) return;
if(direction == 0) {
StopLadding();
}
else {
GoLadding();
}
그 다음 현재 눌리고 있는 방향키를 확인한다. 밑의 키가 눌렸다면(direction < 0) 밑에 혹시 Ground가 있는지 확인한다.
bool CheckGround() {
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.up * -1, 0.58f, LayerMask.GetMask("Ground"));
if (hit && hit.collider.enabled) {
EndLadding();
rb.velocity = Vector2.zero;
return true;
}
return false;
}
밑으로 ray를 발사해서 Ground가 존재하며 그 Ground의 collider가 enabled인지 확인한다. 왜 확인하는지는 곧 알려주겠다. 땅에 도달했으므로 사다리 타기를 멈춘다. 따라서 EndLadding을 호출한다. EndLadding은 다음과 같다.
void EndLadding() {
if(groundColl) groundColl.enabled = true;
isLadder = false;
gravity = 1;
anim.speed = 1f;
}
groundColl이 무엇인지 궁금할텐데 이건 사다리랑 겹쳐있는 ground의 collider이다. (곧 자세히 알려주겠다.) 어쨋든 groundColl에 해당하는 객체가 존재한다면 true로 바꿔주고 isLadder= false로 바꾼다. gravity를 1로 다시 바꿔주고 anim.speed를 1로 바꿔준다. 사다리에서 멈춰있을 때 gravity, anim.speed를 0 으로 설정해주고 다시 움직일때 1로 설정해줌으로써 멈춰있는 상태를 자연스럽게 표현할 수 있다. 여기서는 사다리 타기가 끝났으므로 안전하게 anim.speed를 1로 바꿔주었다.
void GoLadding() {
rb.velocity = new Vector2(0, inputVec.y * climbSpeed);
gravity = 1;
anim.speed = 1f;
}
void StopLadding() {
rb.velocity = Vector2.zero;
gravity = 0;
anim.speed = 0f;
}
다음과 같이 사다리에서 위아래로 움직일때 anim.speed와 gravity를 1로 바꿔주고 멈췄을 때는 0으로 바꿔줌으로써 자연스러운 연출이 가능하다.
여기까지 했으면 문제가 하나 발생한다.
"사다리를 밑에서 위로 타는 것은 가능한데, 사다리 위(사다리 맨 꼭대기)에서 밑으로 타는 것은 안된다." 그 이유가 ground와 ladder가 겹쳐있어서이다. 즉, 사다리 꼭대기와 ground가 겹쳐있기 때문이다. 이걸 해결하는 방법은 플레이어가 밑의 방향키를 눌러 사다리를 타려고 할 때 ground의 collider를 꺼버리면 된다. 코드는 다음과 같다.
void DownLadding() {
Vector3 newPos = new Vector3(transform.position.x, transform.position.y - 2*ladderCheck.bounds.extents.y, 0);
RaycastHit2D hit = Physics2D.Raycast(newPos, Vector2.up * inputVec.y, 0.5f, LayerMask.GetMask("Ladder"));
RaycastHit2D hit2 = Physics2D.Raycast(newPos, Vector2.up * inputVec.y, 0.5f, LayerMask.GetMask("Ground"));
if (hit) {
isLadder = true;
hit2.collider.enabled = false;
groundColl = hit2.collider;
transform.position = new Vector2(hit.collider.transform.position.x, transform.position.y - ladderCheck.bounds.extents.y);
}
}
밑으로 ray를 두개 발사한다. hit은 ladder를 감지하고 hit2는 ground를 감지한다. 이때 ladder가 존재한다면, isLadder를 true로 바꿔 사다리타기에 돌입하고 hit2.collider.enabled = false를 통해 ground의 collider를 꺼버린다. 그러면 자연스럽게 땅을 무시하고 사다리에 탈 수 있다. 그리고 그렇게 꺼버린 collider를 groundColl에 담아서 사다리에서 탈출할 때 다시 true로 바꿔준다. 그래서 아까 endLadding()에서 사용된 로직이 바로 이 로직이다. CheckGround에서 왜 coll.enabled가 true인지 확인하는 이유도 이해가 될 것이다. 만약 coll.enabled를 확인하지 않으면 사다리를 타자마자 (ground와 ladder가 겹쳐있는 부분에 도달하기 때문에 checkGround함수의 hit에 타고 내려온 ground가 참조된다)checkGround가 true를 뱉어내버려 사다리 타기가 끝난다. 따라서 enabled가 true인지 확인하여. 즉, 우리가 타고 내려온 땅이 아닌 사다리 밑에 있는 땅인지를 확인하여 땅에 도달했으면 true를 뱉는 것이다.
사다리타기 로직을 설명한 내용이 별로 없어서 내가 직접 작성해보았다. 도움이 되었으면 좋겠다.