3 - 이동 & 카메라
개요
파트 3에서는 플레이어 입력에 따른 플레이어 움직임과 올바른 플레이어 객체를 따라가는 1인칭 카메라를 추가하여 기존 씬을 확장합니다.
플레이어 이동
Fusion에서는 움직임과 같은 모든 틱을 업데이트하는 게임 플레이 코드가 Update / FixedUpdate
에서 실행되어서는 안 됩니다. 대신 FixedUpdateNetwork
를 사용해야 합니다. 이를 통해 모든 클라이언트에서 움직임이 원활하고 올바르게 보간 됩니다.
새 스크립트를 만들고 이름을 PlayerMovement
로 지정한 다음 스크립트에 다음 코드를 추가합니다:
C#
using Fusion;
using UnityEngine;
public class PlayerMovement : NetworkBehaviour
{
private CharacterController _controller;
public float PlayerSpeed = 2f;
private void Awake()
{
_controller = GetComponent<CharacterController>();
}
public override void FixedUpdateNetwork()
{
// Only move own player and not every other player. Each player controls its own player object.
if (HasStateAuthority == false)
{
return;
}
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
_controller.Move(move);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
}
}
이는 FixedUpdateNetwork
를 제공하고 나중에 Networked Properties
을 사용할 수 있는 MonoBehaviour
대신 NetworkBehaviour
를 상속하는 것에 주목하세요.
HasStateAuthority
를 사용하면 클라이언트가 객체를 제어하는지 확인할 수 있습니다. NetworkTransform
은 StateAuthority
의 변경 내용만 동기화합니다. 다른 클라이언트가 객체의 위치를 변경하면 로컬 변경에 불과하고 향후 네트워크의 데이터를 무시합니다.
버튼 눌림 처리
Update
시 유니티에서는 GetButtonDown
과 up과 같은 입력이 한 번씩 발생합니다. FixedUpdateNetwork
에서는 움직임과 같은 게임 플레이 코드가 실행되어야 합니다. 버튼 상태를 정확하게 추적하기 위해서는 특별한 취급이 필요합니다.
FixedUpdate
에서 버튼 누름을 확인할 때도 동일한 문제가 발생합니다.FixedUpdateNetwork
에서 다음과 같은 입력을 캡처하는 여러 가지 접근 방식이 있습니다:
- 업데이트에서 버튼 다운을 요청합니다. 결과를 부울 변수에 저장한 후 게임 로직에 사용합니다.
FixedUpdateNetwork
의 마지막 부분에서 삭제합니다. - Fusion의 NetworkInput을
NetworkButtons
와 함께 사용합니다. - 새로운 유니티 입력 시스템을 사용하여
Update Mode
를Manual Update
로 설정하고FixedUpdateNetwork
에서InputSystem.Update
를 호출합니다.
점프
플레이어를 위한 점프 액션을 구현하여 버튼 누르기를 처리하는 예를 구현해 보겠습니다. 이 경우 이전 섹션에 나열된 옵션 중에서 옵션 1이 사용됩니다.
플레이어 이동 스크립트의 코드를 다음으로 바꿉니다:
C#
using Fusion;
using UnityEngine;
public class PlayerMovement : NetworkBehaviour
{
private Vector3 _velocity;
private bool _jumpPressed;
private CharacterController _controller;
public float PlayerSpeed = 2f;
public float JumpForce = 5f;
public float GravityValue = -9.81f;
private void Awake()
{
_controller = GetComponent<CharacterController>();
}
void Update()
{
if (Input.GetButtonDown("Jump"))
{
_jumpPressed = true;
}
}
public override void FixedUpdateNetwork()
{
// Only move own player and not every other player. Each player controls its own player object.
if (HasStateAuthority == false)
{
return;
}
if (_controller.isGrounded)
{
_velocity = new Vector3(0, -1, 0);
}
Vector3 move = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
_velocity.y += GravityValue * Runner.DeltaTime;
if (_jumpPressed && _controller.isGrounded)
{
_velocity.y += JumpForce;
}
_controller.Move(move + _velocity * Runner.DeltaTime);
if (move != Vector3.zero)
{
gameObject.transform.forward = move;
}
_jumpPressed = false;
}
}
이 코드는 KCC에 점프 기능과 속도/중력 처리 기능을 추가합니다.
버튼 상태를 추적하기 위해 _jumpPressed
를 사용합니다. 이 버튼은 매 업데이트마다 폴링 되어 FixedUpdateNetwork의 끝부분에 놓치는 키가 없는지 확인하고 재설정됩니다.
C#
if (_controller.isGrounded)
{
_velocity = new Vector3(0, -1, 0);
}
KCC가 작은 경사면을 걸을 때도 그라운드에 고정된 상태를 유지하고 점프만 하면 플레이어가 그라운드를 떠날 수 있도록 하는 데 사용됩니다.
플레이 테스트
PlayerMovement
스크립트가 준비된 상태에서 유니티로 돌아가 PlayerCharacter
프리팹에 PlayerMovement
컴포넌트를 추가합니다.
이제 플레이어의 움직임을 테스트할 시간입니다. 두 명의 클라이언트를 사용할 수 있도록 하는 것은 유니티 빌드를 만드는 것부터 시작합니다.
파일 > 빌드 설정
으로 이동하여 현재 씬을 목록에 추가한 다음 창의 왼쪽 하단에 있는 플레이어 설정 버튼을 누릅니다.
Resolution and Presentation
의 플레이어 설정
에서 Fullscreen Mode
를 Windowed
로 설정하고 Run In Background
가 선택되어 있는지 확인합니다.
빌드 설정
창으로 돌아가서 Build and Run
을 선택하여 빌드를 만듭니다. 빌드가 완료되면 유니티에서 플레이 모드로 전환하여 2개의 클라이언트를 사용할 수 있습니다.
두 클라이언트 모두 공유 클라이언트 시작 버튼을 누릅니다. 두 명의 플레이어가 등장하고, 각각 한 명의 클라이언트가 제어합니다. 걸어 다니기와 점프가 효과가 있는지 테스트합니다.
카메라
플레이어 설정을 완료하려면 카메라를 추가합니다.
카메라를 설정하는 많은 방법(제3자, 제1자 등)과 이를 구현하는 다른 방법이 있습니다. 멀티플레이어 게임에서는 종종 로컬 플레이어 객체를 따르는 단일 카메라가 필요합니다. 이를 달성하기 위한 두 가지 일반적인 방법이 있습니다.
- 로컬 플레이어 객체가 생성되면 카메라를 인스턴스화합니다.
- 카메라를 씬의 일부로 합니다. 로컬 플레이어 객체가 생성되면 카메라를 자신에게 부착합니다.
이 튜토리얼에서는 두 번째 접근 방식을 사용합니다.
새 스크립트를 만들고 이름을 FirstPersonCamera
로 지정합니다. 여기에 다음 코드를 추가합니다:
C#
using UnityEngine;
public class FirstPersonCamera : MonoBehaviour
{
public Transform Target;
public float MouseSensitivity = 10f;
private float verticalRotation;
private float horizontalRotation;
void LateUpdate()
{
if (Target == null)
{
return;
}
transform.position = Target.position;
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
verticalRotation -= mouseY * MouseSensitivity;
verticalRotation = Mathf.Clamp(verticalRotation, -70f, 70f);
horizontalRotation += mouseX * MouseSensitivity;
transform.rotation = Quaternion.Euler(verticalRotation, horizontalRotation, 0);
}
}
이것은 매우 간단한 1인칭 카메라 구현입니다. 코드에 멀티플레이어 요소가 포함되어 있지 않습니다. 이 카메라를 시네마 머신을 포함한 대상 객체와 함께 작동하는 다른 카메라 설정으로 대체할 수 있습니다.
FirstPersonCamera
동작을 `MainCamera 게임 객체에 올려놓습니다. 타깃을 드래그할 필요가 없습니다. 코드별로 런타임에 타깃을 설정합니다.
타깃을 지정하기 위해서, PlayerMovement
스크립트를 엽니다. 카메라를 저장할 변수를 추가합니다.
C#
public Camera Camera;
Then when the player object gets spawned find and set up the camera if it is the local player.
C#
public override void Spawned()
{
if (HasStateAuthority)
{
Camera = Camera.main;
Camera.GetComponent<FirstPersonCamera>().Target = transform;
}
}
NetworkObjects
를 초기화할 때는 반드시 Awake/Start
대신 Spawned
를 사용해야 합니다. Awake/Start
에서는 NetworkObject
를 사용할 준비가 되지 않았을 수 있습니다.
HasStateAuthority
는 플레이어가 제어하는 객체에만 해당하므로 로컬 플레이어 객체만 해당되고 다른 플레이어 객체는 해당되지 않습니다.마지막으로 1인칭 게임에서는 플레이어 캐릭터가 룩 방향으로 이동하기 때문에 움직임을 조금 조절합니다. Vector3 move
가 계산되는 선을 다음과 같이 바꿉니다:
C#
Quaternion cameraRotationY = Quaternion.Euler(0, Camera.transform.rotation.eulerAngles.y, 0);
Vector3 move = cameraRotationY * new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * Runner.DeltaTime * PlayerSpeed;
Back to top