
이전 포스트에서 작성했던 코드를 더 효율적으로 Refactoring 하는 과정이 있었다.
계속 작성한 것 갈아엎는 행동이 반복돼서 좀 그렇긴 하지만 또 한번 어떤 점에 변화가 있었는지 알아보자.
설명하기에 앞서, 원래 Player Prefab에는 RigidBody2D 컴포넌트가 붙어있지 않았다.
갑자기 추가한 이유는, 어차피 충돌 이벤트를 작성하려면 RigidBody2D 컴포넌트가 적용되어있어야 하기 때문이다.
추가로, 이동 관련 코드를 RigidBody에 적용하는 것으로 변경했다.
종합적으로 변경된 코드는 아래와 같다.
private NetworkVariable<PlayerNetworkState> _playerState;
private Vector3 _vel;
private float _rotVel;
[SerializeField] private bool _usingServerAuth;
[SerializeField] private float _cheapInterpolationTime = 0.1f;
private GameObject _jet;
private Rigidbody2D _rb;
먼저 필드변수가 늘어났다. 기존의 netState가 playerState로 명칭 변경되었고, RigidBody와 GameObject (Child) 변수가 추가되었다. 중복된 컴퓨팅을 방지하기 위함이다.
private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
_jet = transform.GetChild(0).gameObject;
var permission = _usingServerAuth ? NetworkVariableWritePermission.Server : NetworkVariableWritePermission.Owner;
_playerState = new NetworkVariable<PlayerNetworkState>(writePerm: permission);
}
이렇게 필드변수들을 초기화 하는 모습을 볼 수 있다.
PlayerController 클래스는 이전 포스팅에서 NetworkBehavior를 상속하면서, IsOwner 조건을 체크해서 이동함수를 호출했었다.
그런데 이제 Network Object가 소환될 때 바로 Owner인지 아닌지 체크하여 아닐경우 PlayerController 스크립트를 해당 오브젝트에서 제거하는 코드를 만들었다.
public override void OnNetworkSpawn()
{
if (!IsOwner) Destroy(transform.GetComponent<PlayerController>());
}
이렇게 되면, 더이상 PlayerController 클래스에서 NetworkBehavior를 상속할 이유가 사라진다. 그래서 PlayerController 클래스는 아래와 같이 수정되었다.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 0;
private float angle = 0;
void FixedUpdate()
{
Vector2 worldMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 direction = ((Vector3)worldMousePosition - transform.position).normalized;
angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
Vector3 currentPosition = transform.position;
MoveTowardMouse(currentPosition, direction, speed * 0.1f, angle);
}
public void MoveTowardMouse(Vector3 pos, Vector3 dir, float speed, float angle)
{
transform.position = new Vector3(pos.x + dir.x * speed, pos.y + dir.y * speed, 0);
transform.Find("Jet").transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle - 90));
}
}
먼저 PlayerNetwork는 Player의 이동 관련 데이터를 동기화하는 클래스이기 때문에, 두 가지 역할이 혼재한다.
서버로 데이터를 보내는 역할과, 서버에서 받은 데이터를 Player 오브젝트에 반영하는 역할.
이렇게 두가지 역할에 따라서 메서드를 구성했다.
private void FixedUpdate()
{
if (IsOwner) TransmitState();
else ConsumeState();
}
이렇게 FixedUpdate에서는 경우에 따라 두 가지 메서드를 호출하는 방식으로 수정되었고, 각각의 메서드는 아래와 같이 구현되었다.
private void TransmitState()
{
var state = new PlayerNetworkState
{
Position = _rb.position,
Rotation = _jet.transform.rotation.eulerAngles
};
if (IsServer || !_usingServerAuth) _playerState.Value = state;
else TransmitStateServerRpc(state);
}
[ServerRpc]
private void TransmitStateServerRpc(PlayerNetworkState state)
{
_playerState.Value = state;
}
TransmitState의 경우에는, 현재 Position과 Rotation 값을 받아와서 TransmitStateServerRpc 메서드에 넘겨준다.
조건분기는 아직 명확히 뭐에 트리거되는건지 잘 모르겠다. 더 자세히 알아볼 예정이다.
서버로부터 받은 데이터를 반영하는 코드는 아래와 같다.
private void ConsumeState()
{
// TODO: Need to change the interpolation method here
_rb.MovePosition(Vector3.SmoothDamp(transform.position, _playerState.Value.Position, ref _vel, _cheapInterpolationTime));
_jet.transform.rotation = Quaternion.Euler(0, 0, Mathf.SmoothDampAngle(_jet.transform.rotation.eulerAngles.z, _playerState.Value.Rotation.z, ref _rotVel, _cheapInterpolationTime));
}
매우 간단한 보간법을 활용하여 위치와 각도를 보정하는데, 배포레벨에서는 더 좋은 방법을 써야 한다고 해서 우선 TODO로 남겨두었다.
이 부분도 더 자세히 공부한 뒤에 수정할 예정이다.