김산나
[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 2월 5일 회고록 - 플랫포머 2 - 맵 세팅,점프, 벽점프, 각종 이펙트 본문
[멋쟁이사자처럼부트캠프 유니티 게임 개발 7기] 2026년 2월 5일 회고록 - 플랫포머 2 - 맵 세팅,점프, 벽점프, 각종 이펙트
김산나 2026. 2. 6. 00:282026_02_05 강의 요약본
1. 맵 세팅
이번에 구현할 게임은 카타나 제로.
의외로 타일 방식이 아닌, 통맵이었다.
그냥 맵을 크게 그리고, 그 위에 콜라이더를 적용하는 방식으로 만드는 것.

이런 식이다. 박스 콜라이더는 알고 있기에 간단히 저 대각선 길을 구현한 Egde Collider2D만 간단히 알아보자.

처음 생성 시 직선 하나 던져준다. 마우스를 가져다 대면 점이 보이는데, 이 점을 조절하여 형태를 잡을 수 있다.
ctrl을 누르면 5도..? 정도 스냅이 되어 움직인다.
추가로, 플레이어가 벽점프를 할 수 있는 구간도 콜라이더로 잡아준다. 벽 콜라이더보다 바깥으로 나와있어야 인식하니 주의.

2. 기본적인 움직임 구현
캐릭터 애니메이션은 여기까지 구현이 되었다.

run은 idle에서 달리기 시작하는 애니메이션이다.

파라미터는 이렇게 세 가지이다.점프는 점프고, 그랩은 벽을 잡고 있는 상태 애니메이션이다.
- 키 조작
void KeyInput()
{
dir.x = Input.GetAxisRaw("Horizontal");
if(dir.x <0)
{
sp.flipX = true;
panimator.SetBool("Run", !isWall); // 벽에 붙어있지 않을 때만 Run 재생
isRight = -1;
}
else if(dir.x >0)
{
sp.flipX = false;
panimator.SetBool("Run", !isWall); // 벽에 붙어있지 않을 때만 Run 재생
isRight = 1;
}
else if(dir.x == 0)
{
panimator.SetBool("Run", false);
}
}
키 입력을 받아 이미지를 좌우반적 시키고, 애니메이션을 재생시키는 함수이다.
void Move()
{
// transform.position += dir * spd * Time.deltaTime;
prig2D.linearVelocity = new Vector2(dir.x * spd, prig2D.linearVelocity.y);
}
Move메서드는 실제로 캐릭터가 좌, 우로 움직이는 함수이다. 두 가지 종류가 있는데, 어느 쪽으로 하든 상관이 없다.
드래곤 플라이트 구현 때 말한 콜라이더 충돌 떨림 현상을 방지하기 위해, 물리 연산 방식만 잘 맞춰주면 된다.
void Jump()
{
prig2D.linearVelocity = Vector2.zero;
// 점프
prig2D.AddForce(new Vector2(0, jump), ForceMode2D.Impulse);
}
Jump메서드. 점프 시점의 가속도를 제한하여 좀 더 자연스러운 움직임을 만든다.
AddForce라는 코드는 리지드바디 연산에 힘을 가해주는 코드이다.
ForceMode2D.Impulse는 순간적인 힘을 가해주는 코드, ForceMode2D.Force는 지속적인 힘을 가해주는 코드이다.
void Update()
{
KeyInput();
Move();
}
방금 만든 두 메서드를 Update메서드에 넣는다. 점프 애니메이션은 벽 점프 파트에서 함께 보자.
3. 점프, 월킥
플레이어가 벽을 향해 점프하고, 부딪힌 면이 "Wall"레이어의 콜라이더라면, 벽에 붙어있는 애니메이션을 재상한다.
그리고 그 상태에서 점프를 하면 붙은 방향의 반대 대각선 방향으로 점프한다.
특정 면에 붙은 상태여야 1회 작동한다는 점에서 점프, 월킥은 비슷하지만, 어느 면에 붙었는지에 따라 분류를 해야 한다.
void Update()
{
isWall = Physics2D.Raycast(wallChk.position, Vector2.right * isRight, wallChkDistance, wLayer);
panimator.SetBool("Grab", isWall);
KeyInput();
Move();
if(Input.GetKeyDown(KeyCode.W))
{
// 애니메이션이 착지 상태일 때만
if(panimator.GetBool("Jump") == false)
{
Jump();
}
}
if(isWall)
{
isWallJump = false; // 벽점 상태 초기화
if (!isWallJump)
{
// 미끄럼 속도 적용
prig2D.linearVelocity = new Vector2(prig2D.linearVelocityX, prig2D.linearVelocityY * slideingSpd); // 감속 적용
if(Input.GetKeyDown(KeyCode.W))
{
isWallJump = true;
GameObject go = Instantiate(Gdust, transform.position + new Vector3(-0.4f * isRight, 0, 0), Quaternion.identity);
go.GetComponent<SpriteRenderer>().flipX = !sp.flipX;
Destroy(go, 0.5f);
// 일정 시간 후 해체
Invoke("FreezeX", 0.3f);
prig2D.linearVelocity = new Vector2(-isRight * wallJumpPower, 1.2f * wallJumpPower);
// 방향 전환
sp.flipX = sp.flipX == false ? true : false;
isRight = -isRight;
}
}
}
}
업데이트 코드를 수정하였다.
isWall = Physics2D.Raycast(wallChk.position, Vector2.right * isRight, wallChkDistance, wLayer);
이게 좀 길고 생소한데, Physics2D.Raycast는 bool값을 가진다. 그러니까 레이캐스트에 걸리면 true, 걸리지 않으면 false.
벽체크를 시도한 포지션에서, 오른쪽 벡터 * 이게 오른쪽이니? (아니면 왼쪽으로), 체크 거리만큼, wLayer가 있으면
true, 아니면 false인 것이다. (이거 외워서 쓸 수 있나?)
panimator.SetBool("Grab", isWall);
Grab파라미터를 isWall로 세팅한다. 벽에 닿아 레이캐스트가 true를 뿜는 순간 벽을 붙잡는 애니메이션이 작동한다.
if(isWall)
{
isWallJump = false; // 벽점 상태 초기화
if (!isWallJump)
{
// 미끄럼 속도 적용
prig2D.linearVelocity = new Vector2(prig2D.linearVelocityX, prig2D.linearVelocityY * slideingSpd); // 감속 적용
if(Input.GetKeyDown(KeyCode.W))
{
isWallJump = true;
GameObject go = Instantiate(Gdust, transform.position + new Vector3(-0.4f * isRight, 0, 0), Quaternion.identity);
go.GetComponent<SpriteRenderer>().flipX = !sp.flipX;
Destroy(go, 0.5f);
// 일정 시간 후 해체
Invoke("FreezeX", 0.3f);
prig2D.linearVelocity = new Vector2(-isRight * wallJumpPower, 1.2f * wallJumpPower);
// 방향 전환
sp.flipX = sp.flipX == false ? true : false;
isRight = -isRight;
}
}
}
아래는 월킥에 대한 내용이다.
일단 값을 초기화 한다.
그리고 벽에 붙은 동안에 물리값을 다시 세팅해 주는데, x축은 동일하나, y값은 벽과 마찰하는 설정으로 느리게 바뀌도록 세팅한다.
이 상태에서 "w"키를 누르면 월킥이 적용된다.
월킥을 했냐? true로 바꾸고, 월킥 이펙트 작동 on
그리고 0.3초 후에 FreezeX 메서드를 작동 시킨다.
void FreezeX()
{
isWallJump = false;
}
0.3초 후에는 다시 벽에 붙어 월킥 할 수 있는 상태로 전환된다.
// 벽 체크 레이 시각화
void OnDrawGizmos()
{
Gizmos.color = Color.blue;
Gizmos.DrawRay(wallChk.position, Vector2.right * isRight * wallChkDistance); // 씬 뷰에서 벽 체크 레이 시각화
}
이건 필수는 아닌데, 디버깅용으로 좋아서 넣은 코드라고 한다.
기즈모 색상을 블루로 하고, 레이를 그리게 시킨다. 벽 체크하는 포지션에, 플레이어가 현재 보는 방향으로 측정 거리까지 선을 그어준다.
OnDrawGizmos는 update 메서드 이런 거에 안 넣어도 알아서 작동하는 메서드이다.
이번에는 점프를 위한 바닥 체크.
void FixedUpdate()
{
Debug.DrawRay(prig2D.position, Vector3.down, new Color(1f, 0, 0));
// 바닥 레이어로 레이캐스트 (땅바닥인지 체크)
RaycastHit2D rayHit = Physics2D.Raycast(prig2D.position, Vector3.down, GROUND_CHECK_DISTANCE, LayerMask.GetMask("Ground"));
// 레이캐스트에 맞았나? = 레이케스트(리지드바디 포지션의, 아래로, 0.7f거리안에, Ground레이어)에 맞았냐?
CheckGroundedState(rayHit);
}
FixedUpdate는 물리 계산에 특화된 업데이트 메서드이다.
Debug.DrawRay는 기즈모 그리기랑 비슷한듯? 일단 보이는 게 같다.
차이점이라면 디버그용은 게임 시작 시에만 보였고, 기즈모는 게임 시작 전부터 표시된다는 차이 정도?

RaycastHit2D rayHit = Physics2D.Raycast(prig2D.position, Vector3.down, GROUND_CHECK_DISTANCE, LayerMask.GetMask("Ground"));
그래서 이 부분도 용도가 비슷한데, 변수로 선언해두어 지속적으로 체크하게 하는 것이다.
다만 Physics2D.Raycast는 bool값이었는데, RaycastHit2D는 좀 더 종합적인 정보를 담고 있는것 같다.
가령 Raycast된 콜라이더 정보도 받는 것 같다.
설명도 장면에서 2D 피직스 쿼리로 감지된 2D콜라이터에 대한 정보를 반환합니다. 라고 써있음.
void CheckGroundedState(RaycastHit2D rayHit)
{
bool isGround = rayHit.collider != null && rayHit.distance < GROUND_CHECK_DISTANCE;
if(isGround)
{
panimator.SetBool("Jump", false);
}
else
{
if(!isWall)
{
panimator.SetBool("Jump", true);
}
else
{
panimator.SetBool("Grab", true);
}
}
}
레이캐스트 정보값을 받아먹는 메서드이다.
bool isGround = rayHit.collider != null && rayHit.distance < GROUND_CHECK_DISTANCE;
isGround값은 rayHit된 콜라이더가 null이 아니면서 rayHit의 거리가 미리 지정해둔 "GROUND_CHECK_DISTANCE"값보다 작지 않다면 true라는 뜻이다.
암튼 이 값이 true면 땅이라는 뜻이기 때문에 애니메이션 파라미터를 Jump를 false로 한다.
만약 false면 캐릭터가 공중에 있다는 뜻인데, 거기다 벽이 아닌 상태까지 겹친다면 무조건 점프 (혹은 낙하중)이기에
점프 애니메이션을 작동시킨다.
그게 아니라면 벽에 붙어 있는 애니메이션을 작동 시킨다.
public void JumpDust(GameObject dust)
{
{
GameObject Jdus = Instantiate(dust, transform.position + new Vector3(0, 0, 0), Quaternion.identity);
Destroy(Jdus, 0.5f);
}
}
이펙트는 이런 구조이다.
꼭 코드 내에서 작동시킬 필요는 없고, 애니메이션에 메소드를 작동시키는 트리거를 심는 방법도 있다
.

저 파란 작대기가 트리거 위치이다.
그럼 인스펙터 창에 메서드를 선택하는 창이 뜬다.


플레이어 스크립트에서 메서드를 긁어오는 것을 볼 수 있다.
+ 숙제
랜딩 더스트가 착지 이펙트 아니냐는 지적과 함께 숙제를 받아버렸다.
ㅠㅠ
지금까지 작성한 코드를 기반으로 어떻게 작동시킬지 생각한다.
착지 이펙트는, 플레이어가 공중에 있는 상태일 때, 바닥에 떨어짐과 동시에 1회 작동해야 한다.
이펙트는 1회 생성되어야 하는데, 기존 위치 체크(벽인지 바닥인지) 메서드들은 프레임단위로 생성하기에 냅다 박기엔 위험하다.
바닥에 닿는 순간 1회 발생 후 사라져야 한다.
public void LandDust()
{
GameObject Ldus = Instantiate(Ldust, transform.position + new Vector3(-0.114f, -0.5f, 0), Quaternion.identity);
Destroy(Ldus, 0.5f);
}
일단 랜드 더스트를 만든다.
그리고 wasGrounded라는 변수를 하나 추가하고, "CheckGroundedState"에 내용을 넣는다.
void CheckGroundedState(RaycastHit2D rayHit)
{
wasGrounded = isGround; // 현재 isGround 상태를 이전 상태(wasGrounded)로 저장
isGround = rayHit.collider != null && rayHit.distance < GROUND_CHECK_DISTANCE;
if (!wasGrounded && isGround)
{
// 방금 바닥이 아니었고, 지금은 바닥인 경우에만 실행
LandDust();
}
if(isGround)
{
panimator.SetBool("Jump", false);
}
else
{
if(!isWall)
{
panimator.SetBool("Jump", true);
}
else
{
panimator.SetBool("Grab", true);
}
}
}
함수 호출 직전에 이전 bool값을 받고, 그 값과 함께 비교를 때리는 거다.
그럼 방금 전에 공중이었고, 지금은 아닌 경우에만 실행된다.

잘 작동한다.
===========================================================
통맵의 콜라이더 설정 방법을 알게 되었다 !
플랫포머의 기초를 알게 되었다 !
