[ 메서드 정리 포스팅 ]
3D 게임에서 일반적으로 사용되는 메서드와 함수는 아래의 포스팅에서 한번에 다루도록 한다.
https://udangtangtang-cording-oldcast1e.tistory.com/209
캐릭터 입력 제어
캐릭터의 이동 구현 파트에서는 이전과는 다르게 입력을 다루는 소스와 입력에 따른 캐릭터의 움직임을 만드는 소스를 나누어 작성한다.
PlayerInput | 플레이어의 입력을 감지하고 이를 다른 컴포넌트에 전송 |
PlayerMovement | 플레이어의 입력에 따라 캐릭터를 앞뒤로 움직이고 좌우로 회전 |
이렇게 입력과 출력을 나누면서 코드의 수정 범위를 줄일 수 있고 불필요한 코드 사용을 막을 수 있다.
PlayerInput 스크립트 (1) : 입력축과 입력버튼
가장 먼저 입력축과 버튼을 string 변수로 선언한다.
public string moveAxisName = "Vertical"; // 앞뒤 움직임을 위한 입력축 이름 public string rotateAxisName = "Horizontal"; // 좌우 회전을 위한 입력축 이름 public string fireButtonName = "Fire1"; // 발사를 위한 입력 버튼 이름 public string reloadButtonName = "Reload"; // 재장전을 위한 입력 버튼 이름 |
위 코드에서 Vertical과 Horizontal은 입력 축인 반면 Fire1과 Reload는 입력버튼이다.
[ 입력축 ]: Vertical / Horizontal
아래 링크 참조
[ 입력 버튼 ]: Fire1 / Reload
축은 감지된 입력값으로 숫자가 반환되는 반면 입력 버튼은 눌렸을 때 true를 버튼을 누르지 않았을 때 false를 반환한다.
Fire1: 입력 매니저에 기본 추가되어 있는 입력버튼으로 좌 Ctrl과 왼쪽 마우스가 할당됨.
Reload는 사용자 설정 입력버튼이다.
PlayerInput 스크립트 (2) : 프로퍼티
감지할 입력축과 입력 버튼에 대한 변수 다음에는 감지된 입력값을 나태날 프로퍼티가 있다.
[ 프로퍼티 ]
프로퍼티는 변숫값을 읽거나 쓰는 과정에서 유연한 처리를 삽입할 수 있는 클래스 멤버이다.
이때 프로퍼티는 변수처럼 보이나, 변수가 아닌 특수한 형태의 메서드임을 주의하자!
아래 [더보기]의 예시를 확인하자.
아래의 예시는 디스크의 용량을 기록하는 volumeInfo 클래스로, 바이트 단위로 디스크의 용량을 계산하며 다른 바이트의 단위로의 변환을 제공한다.
public class volumeInfo{
public float megaBytes{
get{ return m_bytes * 0.000001f; }
set{
if(valsue <= 0){
m_bytes = 0;
}else{
m_bytes = value * 100000f;
}
}
}
public float killoBytes{
get{ return m_bytes * 0.001f; }
set{
if(valsue <= 0){
m_bytes = 0;
}else{
m_bytes = value * 1000f;
}
}
}
public float bytes{
get{ return m_bytes; }
set{
if(valsue <= 0){
m_bytes = 0;
}else{
m_bytes = value;
}
}
}
private float m_bytes = 0;
}
더보기란의 코드를 이용해 새로운 디스크 용량을 기록하고 출력하는 코드를 생생해보자.
volumeInfo info = new volumeInfo();//새로운 디스크 용량 정보 생성: 참조 변수
volumeInfo.bytes = 100000;//bytes의 set 실행(bytes 단위로 생성)
Debug.Log(info.killoBytes);//킬로바이트로 변환 출력
Debug.Log(info.megaBytes);//메가 바이트로 변환 출력
info.megaBytes = 4;//megaBytes의 set실행
Debug.Log(info.bytes);//바이트 반위로 변환 출력
위 코드를 보면 새로운 참조 변수를 생성하고 특정 바이트에 값을 선언(=기호 이용)하는 것은 set이 실행되어 값을 초기화하고 값을 선언하는 것이 아닌 값을 가져오려고 하는 경우 get이 실행되어 m_bytes에 변환하기 위한 수를 곱한 값이 return 된다.
이를 표로 정리하면 아래와 같다.
상황 | 사용되는 접근자 | 예시 | |
특정 바이트에 값을 선언(=기호 이용) | set | volumeInfo.bytes = 100000; | |
값을 참조(가져옴) | get | Debug.Log(info.killoBytes) |
이때 set 함수에서는 trash 변수를 처리하기 위한 조건문이 존재하는데, 0보다 작은 값인 경우 쓰레기 값이자 예상치 못한 연산이 발생할 수 있으므로 1차적으로 값을 걸러준다.
이처럼 프로퍼티를 이용하면 여러 단위의 값으로 즉시 출력할 수 있으며 데이터를 안전하게 다루는데 도움이 된다.
또한 원하는 접근자만 private 클래스로 범위를 제한하여 개발자가 원하는 경우 입력 혹은 출력만 접근하게 설정할 수 있다.
[ 스크립트 내의 프로퍼티 ]
사용할 프로퍼티는 아래와 같다.
public float move { get; private set; } // 감지된 움직임 입력값 public float rotate { get; private set; } // 감지된 회전 입력값 public bool fire { get; private set; } // 감지된 발사 입력값 public bool reload { get; private set; } // 감지된 재장전 입력값 |
PlayerInput 클래스에서 구현된 위와 같은 프로퍼티는 자동 구현 프로퍼티로, get과 set의 접근 권한을 분리하는 것 이외의 처리가 필요하지 않을 때 사용한다.
자동 구현 프로퍼티: get과 set의 접근 권한을 분리하는 것 이외의 처리가 필요하지 않을 때 사용
PlayerInput 클래스에 구현된 move 프로퍼티를 펼치면 아래와 같다.
public float move{
get { return m_move; }
private set { m_move = value; }
}
private float m_move;
쉽게 말해, 위에서 사용된 표현은 외부에서는 값을 출력(접근)할 수 있지만 값의 설정(초기화)은 PlayerInput 내부에서만 설정할 수 있도록 권한 범위를 설정했다는 뜻이다.
PlayerInput 스크립트 (3) : 입력 확인
update() 메서드 상단에서는 게임매니저가 씬에 존재하며 게임오버 상태가 아닌 경우 입력감지 변수를 초기화한다.
다음으로는 입력을 감지하고 감지된 입력값을 프로퍼티에 할당한다.
move = Input.GetAxis(moveAxisName); // move에 관한 입력 감지 rotate = Input.GetAxis(rotateAxisName); // rotate에 관한 입력 감지 fire = Input.GetButton(fireButtonName); // fire에 관한 입력 감지 reload = Input.GetButtonDown(reloadButtonName); // reload에 관한 입력 감지 |
입력에 대한 메서드는 오른쪽 링크를 참조하자. [링크]
전체 코드는 아래 [더보기]를 확인한다.
using UnityEngine;
// 플레이어 캐릭터를 조작하기 위한 사용자 입력을 감지
// 감지된 입력값을 다른 컴포넌트들이 사용할 수 있도록 제공
public class PlayerInput : MonoBehaviour {
public string moveAxisName = "Vertical"; // 앞뒤 움직임을 위한 입력축 이름
public string rotateAxisName = "Horizontal"; // 좌우 회전을 위한 입력축 이름
public string fireButtonName = "Fire1"; // 발사를 위한 입력 버튼 이름
public string reloadButtonName = "Reload"; // 재장전을 위한 입력 버튼 이름
// 값 할당은 내부에서만 가능
public float move { get; private set; } // 감지된 움직임 입력값
public float rotate { get; private set; } // 감지된 회전 입력값
public bool fire { get; private set; } // 감지된 발사 입력값
public bool reload { get; private set; } // 감지된 재장전 입력값
// 매프레임 사용자 입력을 감지
private void Update() {
// 게임오버 상태에서는 사용자 입력을 감지하지 않는다
if (GameManager.instance != null && GameManager.instance.isGameover)
{
move = 0;
rotate = 0;
fire = false;
reload = false;
return;
}
// move에 관한 입력 감지
move = Input.GetAxis(moveAxisName);
// rotate에 관한 입력 감지
rotate = Input.GetAxis(rotateAxisName);
// fire에 관한 입력 감지
fire = Input.GetButton(fireButtonName);
// reload에 관한 입력 감지
reload = Input.GetButtonDown(reloadButtonName);
}
}
캐릭터 이동 구현
PlayerMovement 스크립트는 플레이어 입력에 맞춰 플레이어 캐릭터를 이동하고 적절한 애니메이션을 재생한다.
먼저 해당 스크립트 내에서 사용할 변수와 컴포넌트/리지드바디/애니메이터 변수를 선언하고 start 매서드에서 이를 연결한다.
PlayerMovement 스크립트
[ 변수 선언과 초기화 ]
변수 선언 코드는 아래 [더보기]를 확인한다.
moveSpeed | 앞뒤 움직임의 속도 |
rotateSpeed | 좌우 회전 속도 |
[ 움직임, 회전, 애니메이션 처리 실행 ]
// FixedUpdate는 물리 갱신 주기에 맞춰 실행됨
private void FixedUpdate() {
// 물리 갱신 주기마다 움직임, 회전, 애니메이션 처리 실행
// 회전 실행
Rotate();
//움직임 실행
Move();
playerAnimator.SetFloat("Move",playerInput.move);
}
move 파라미터는 애니메이터의 Movement 상태의 블렌드 트리에서 사용된다.
이때 'playerAnimator.SetFloat'문장에서 볼 수 있듯 사용자의 입력에 따라 캐릭터의 걷고 뛰는 애니메이션이 자연스럽게 변경된다.
위에서 언급했듯이 입력이 변화함에 따라 인계값이 변화하고, 이는 애니메이션의 변화로 나타남을 알 수 있다.
FixedUpdate는 물리 갱신 주기에 맞춰 실행되는 내장 함수로, Time.fixedDeltaTime이 물리 정보의 갱신 주기를 사용하는 것과 같다.
유니티 내부에서는 FixedUpdate() 내부에서 Time.DeltaTime에 접근할 경우 자동으로 Time.fixedDeltaTime의 값으로 출력한다.
FixedUpdate: 물리 갱신 주기에 맞춰 실행되는 내장 함수(Time.fixedDeltaTime)
FixedUpdate에 대한 자세한 내용은 Time 라이브러리의 fixedDeltaTime를 다룬 오른쪽 링크를 참고한다. [링크]
[ Move 메서드 ]
private void Move() {
Vector3 moveDistance =
playerInput.move * transform.forward * moveSpeed *Time.deltaTime;
playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
}
거리(moveDistance) =
(사용자의 입력값/축) * { 방향(transform.forward) * 속력(moveSpeed) * 시간(Time.deltaTime) }
playerInput.move는 사용자의 입력값으로 이를 통해 거리계산의 조건 판단을 실행한다.
전진: 1 | 정지: 0 (거리 -> 0) | 후진: -1 |
MovePosition: 이동할 Vector3의 위치를 입력받고 해당 위치로 이동
리지드 바디의 MovePosition 메서드는 이동할 Vector3의 위치를 입력받으며 이때의 위치는 전역 위치임에 주의한다.
따라서 변경할 위치는 "현재 플레이어의 위치(playerRigidbody.position) + 이동할 거리(moveDistance)"가 된다.
이때 tansform 컴포넌트를 이용해도 되나, tansform컴포넌트는 물리처리를 무시하고 실행하기 때문에 예상치 못한 버그가 발생할 수 있다. 따라서 상대 위치를 변경하고자 하는 경우는 리지드바디의 MovePosition메서드를 사용하여 물리처리를 실행하여 사고를 방지한다.
[ Rotate 메서드 ]
Rotate 메서드는 플레이어 회전에 관한 입력값 playerInput.rotate를 사용하여 캐릭터를 회전시킨다.
// 입력값에 따라 캐릭터를 좌우로 회전
private void Rotate() {
float turn = playerInput.rotate * rotateSpeed * Time.deltaTime;
playerRigidbody.rotation =
playerRigidbody.rotation * Quaternion.Euler(0,turn,0f);
}
회전각(turn) =
(사용자의 입력값/축) * {회전 속도(rotateSpeed) * 회전 시간(Time.deltaTime) }
회전 각을 구했다면 회전각만큼 Y방향으로 회전을 진행한다. 이때 쿼터니언 곱 오일러 회전을 실행한다.
계산된 쿼터니언 회전값을 리지드바디에 할당하여 플레이어의 회전값을 변경할 수 있다. 이때 transform 컴포넌트를 사용할 수 있으나 Move 메서드에서 언급했듯이 물리 처리를 무시하기 때문에 위와 같은 방법을 사용하도록 하자.
전체 코드는 아래 [더보기]를 확인한다.
using UnityEngine;
// 플레이어 캐릭터를 사용자 입력에 따라 움직이는 스크립트
public class PlayerMovement : MonoBehaviour {
public float moveSpeed = 5f; // 앞뒤 움직임의 속도
public float rotateSpeed = 180f; // 좌우 회전 속도
private PlayerInput playerInput; // 플레이어 입력을 알려주는 컴포넌트
private Rigidbody playerRigidbody; // 플레이어 캐릭터의 리지드바디
private Animator playerAnimator; // 플레이어 캐릭터의 애니메이터
private void Start() {
// 사용할 컴포넌트들의 참조를 가져오기
playerInput = GetComponent<PlayerInput>();
playerRigidbody = GetComponent<Rigidbody>();
playerAnimator = GetComponent<Animator>();
}
// FixedUpdate는 물리 갱신 주기에 맞춰 실행됨
private void FixedUpdate() {
// 물리 갱신 주기마다 움직임, 회전, 애니메이션 처리 실행
// 회전 실행
Rotate();
//움직임 실행
Move();
playerAnimator.SetFloat("Move",playerInput.move);
}
// 입력값에 따라 캐릭터를 앞뒤로 움직임
private void Move() {
Vector3 moveDistance =
playerInput.move * transform.forward * moveSpeed *Time.deltaTime;
playerRigidbody.MovePosition(playerRigidbody.position + moveDistance);
}
// 입력값에 따라 캐릭터를 좌우로 회전
private void Rotate() {
float turn = playerInput.rotate * rotateSpeed * Time.deltaTime;
playerRigidbody.rotation =
playerRigidbody.rotation * Quaternion.Euler(0,turn,0f);
}
}
'Unity > ▶ Game Development: Zombie Game' 카테고리의 다른 글
[Zombie Game] #3. 슈터: PlayerShooter 스크립트 (1) | 2024.01.14 |
---|---|
[Zombie Game] #2. Gun 오브젝트와 스크립트 (1) | 2024.01.13 |