내일배움캠프 Node.js 사전캠프 15일차
1. ZEP에서 이루어진 팀단위 게임 개발 스터디
(1) 스터디 계획
이전 시간에 우리가 배웠던 것들을 다시 살펴보자. 우리는 유니티에서 다음 세 가지 기능의 사용법을 배웠다.
(1) 캐릭터의 이동
(2) 캐릭터와 다른 오브젝트와의 상호작용(충돌 등…)
(3) 캐릭터의 점프
그러나 우리가 구현한 코드에는 문제가 하나 있었는데, 그건 바로 캐릭터가 점프를 할 때 발이 아닌 곳이 닿아도 점프가 가능하다는 것이었다.
극단적으로 이런 구조의 씬이 있다고 가정해보자. 플레이어를 제외한 오브젝트들은 모두 "Ground"태그를 달고 있는 상태다. 앞을 막고 있는 벽, 머리 위를 가로막은 벽에 부딪혀도 점프를 할 수 있다. 슈퍼 마리오처럼 벽을 밟으면서 끊임없이 위로 올라갈 수 있다는 뜻이다.
물론 그런 기능을 의도했다면 괜찮지만, 만약 의도하지 않았다면 이러한 문제점을 시급히 해결할 필요가 있을 터.
각자 해결 방법을 찾아내는 것을 과제로 약속했다.
(2) 스터디 진행 과정
팀장님께서 힌트를 주셨는데, 그건 바로 플레이어 오브젝트에 하위 오브젝트를 생성하고 별도의 콜라이더로 활용하는 것이었다. 즉, 몸통을 맡는 콜라이더와 발바닥을 맡는 콜라이더를 따로 생성하는 것이다.
그리고 플레이어의 하위 오브젝트에서 점프를 제어하는 스크립트를 생성하는데…, 이곳에선 OnCollisionEnter2D 함수 대신에 OnTriggerEnter2D 함수를 사용한다.(Collision는 물리적 상호작용을 다루므로, 점프를 감지하는 데에는 Trigger를 쓰는 게 적절하기 때문일까?)
팀장님은 딱 여기까지 힌트를 주시고 무림의 고수처럼 떠나셨다.
public class TJumpSensor : MonoBehaviour
{
public TPlayer tplayer;
private void OnTriggerEnter2D(Collider2D other)
{
}
}
자, 이제 어떻게 풀면 좋을까.
C#은 잘 모르지만 C를 살짝 공부했던 내 입장에서는 자식 클래스가 먼저 떠올랐다. 왜냐하면 TJumpSensor 스크립트에서는 캐릭터가 점프를 뛰어도 되는지 확인만 하고 싶을 뿐이고, 실제 움직이는 건 TPlayer의 스크립트에게 맡기고 싶기 때문이다.(게다가 이미 구현도 했으니까 여러모로 그게 맞을 것 같다.)
따라서 일단 상속을 먼저 해주었다.
public class TJumpSensor : TPlayer
{
public TPlayer tplayer;
private void OnTriggerEnter2D(Collider2D other)
{
}
}
점프를 해도 된다고 판단해주는 코드는 어떻게 짜야 할까? 부모 클래스(TPlayer)의 코드를 작성했을 때 우린 이미 IsGround의 부울값을 통해 점프를 제어하는 중이었다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TPlayer : MonoBehaviour
{
public float moveSpeed = 8f;
public float radiusSize = 10f;
public Rigidbody2D rb;
public Vector2 direction;
public Transform camTrnsform;
public Vector3 camOffset;
public bool IsJump;
public bool IsGround;
public float JumpInterval = 0.5f;
public float currentJumpTime = 0f;
public float JumpPower = 7f;
// Update is called once per frame
void Update()
{
inputAndMove();
if (IsJump && IsGround)
{
if (currentJumpTime <= Time.time) // Time.time 게임 엔진상의 시간
{
currentJumpTime = Time.time + JumpInterval;
rb.velocity = new Vector2(rb.velocity.x, JumpPower);
}
}
}
private void inputAndMove()
{
// WASD, 방향키 등의 입력 시간을 방향에 따라 입력해주는 함수
float x = Input.GetAxis("Horizontal");
direction = new Vector2(x, 0); // X축 방향의 벡터를 생성
// 스페이스바를 누르면 IsJump = true
IsJump = Input.GetKeyDown(KeyCode.Space);
}
private void FixedUpdate()
{
rb.velocity = new Vector2(direction.x * moveSpeed, rb.velocity.y);
// Rigidbody의 속도 벡터=(direction 벡터 * 속력(크기), 현재 위치의 y)
var myPos = transform.position;
// 카메라의 포지션
camTrnsform.position = new Vector3(myPos.x, myPos.y, camTrnsform.position.z) + camOffset;
}
// 몸통 콜라이더와 충돌한 객체가 "Ground"면 IsGround에 부울 값을을 대입해주는 함수
private void SetGround(Collision2D other, bool _isGround)
{
if (!other.gameObject.CompareTag("Ground")) return;
IsGround = _isGround;
}
}
자식클래스(TJumpSensor)에서 SetGround() 함수를 상속받은 후, 발 콜라이더에 닿을 때에만 IsGround의 값을 true로 반환해주면 되지 않을까? 아래와 같이 작성해보았다.
public class TJumpSensor : TPlayer
{
public TPlayer tplayer;
private void SetGround(Collider2D other, bool _isGround)
{
if (!other.gameObject.CompareTag("Ground")) return;
tplayer.IsGround = _isGround;
}
private void OnTriggerEnter2D(Collider2D other)
{
SetGround(other, true); // 발이 닿으면 tplayer.IsGround = true
}
private void OnTriggerExit2D(Collider2D other) // exit되면 진행되는 함수
{
SetGround(other, false); // 발이 안 닿으면 tplayer.IsGround = false
}
}
부모 클래스에선 다음과 같이 코드를 작성했다.
// ... 생략 ...
public class TPlayer : MonoBehaviour
{
// ... 생략 ...
private void SetGround(Collision2D other, bool _isGround)
{
if (!other.gameObject.CompareTag("Ground")) return;
IsGround = _isGround;
// IsGround의 값은 자식 클래스의 발 콜라이더로 인해 결정된다.
// 발이 닿으면 true, 닿지 않으면 false
}
// stay 상태(벽에 부딪히는 중)일 때 진행되는 함수
private void OnCollisionStay2D(Collision2D other)
{
// 몸통 콜라이더와 "Ground"가 충돌 중인데,
// IsGround의 값이 true일 때(발이 닿았을 때)
if (other.gameObject.CompareTag("Ground") && IsGround)
{
SetGround(other, true); // IsGround의 값을 true로 반환해준다.
}
// 몸통 콜라이더와 "Ground"가 충돌 중인데,
// IsGround의 값이 false일 때(발이 닿지 않았을 때)
else if (other.gameObject.CompareTag("Ground") && !IsGround)
{
SetGround(other, false); // 발이 안 닿았으니까 false
rb.velocity = new Vector2(transform.position.x, -9.8f);
// 중력가속도만큼 떨어지는 걸로 구현해주었다.
}
}
}
(3) 인사이트
알고 보니 유니티에선 클래스를 상속하지 않아도 오브젝트의 연결을 통해 속성과 메서드를 사용할 수 있다고 한다. 따라서 SetGround(Collider2D other, bool _isGround)
함수를 TPlayer 클래스에만 둔 채로 사용해도 상관 없는 듯하다.
또 아래의 코드 조각에서 보이듯, 내가 작성한 스크립트는 플레이어의 발이 Ground에 닿지 않은 채로 충돌되고 있을 시 -9.8의 속력으로 낙하하게끔 하드코딩한 상황이다.
// stay 상태(벽에 부딪히는 중)일 때 진행되는 함수
private void OnCollisionStay2D(Collision2D other)
{
// 몸통 콜라이더와 "Ground"가 충돌 중인데,
// IsGround의 값이 true일 때(발이 닿았을 때)
if (other.gameObject.CompareTag("Ground") && IsGround)
{
SetGround(other, true); // IsGround의 값을 true로 반환해준다.
}
// 몸통 콜라이더와 "Ground"가 충돌 중인데,
// IsGround의 값이 false일 때(발이 닿지 않았을 때)
else if (other.gameObject.CompareTag("Ground") && !IsGround)
{
SetGround(other, false); // 발이 안 닿았으니까 false
rb.velocity = new Vector2(transform.position.x, -9.8f);
// 중력가속도만큼 떨어지는 걸로 구현해주었다.
}
}
그러나 이런 번거로운 방법 말고 유니티 내부에서 Material을 적용하고 그 속성을 조절할 수 있다.
2D 오브젝트의 경우 Physics Material 2D을 생성하자.
위에서 보는 것과 같이 Friction(마찰력)과 Bounciness(탄성)의 값을 설정해줄 수 있다. 위에서처럼 마찰을 0으로 줄이면 벽에 부딪혀도 속도가 느려지지 않고 바로 떨어져 내리는 게 가능해진다.(물론 공기저항(Linear Drag)은 별개로 작용하고 있음을 인지하고 있어야 한다. 참고로 Angular Drag는 오브젝트가 토크로 회전할 때 작용하는 공기저항이다.)
댓글