Dead Reckoning이란?

Dead Reckoning은 과거와 현재의 위치 데이터를 기반으로 객체의 미래 위치를 예측하는 방법입니다. 네트워크 지연이나 패킷 손실로 인해 최신 위치 정보가 즉각적으로 사용할 수 없을 때 유용합니다.

구현 개요

Unity 프로젝트에서 Dead Reckoning을 구현하기 위해 다음과 같은 주요 구성 요소와 로직을 사용합니다:

  • 클라이언트와 서버 마커: 게임 오브젝트를 사용하여 클라이언트와 서버의 위치를 시각적으로 나타냅니다.
  • 네트워크 지연 시뮬레이션: NetworkDelay 변수를 사용하여 네트워크 지연을 모방합니다.
  • 위치 업데이트: 클라이언트의 위치는 사용자 입력 또는 무작위 목적지로 이동하여 업데이트됩니다.
  • 서버 위치 예측 및 업데이트: 서버는 NetworkDelay를 고려하여 클라이언트의 미래 위치를 예측하고, 이를 향해 점진적으로 이동합니다.

구현 단계

  1. 마커 초기화: 클라이언트와 서버를 대표하는 마커를 생성하고, 각각 다른 색상으로 구분합니다.
  2. 라인 렌더러 초기화: 클라이언트와 서버 마커 사이의 거리를 시각적으로 표시하기 위해 라인 렌더러를 설정합니다.
  3. 사용자 입력 처리: 사용자 입력에 따라 클라이언트의 위치를 실시간으로 업데이트합니다.
  4. 네트워크 지연 시뮬레이션: NetworkDelay 값을 사용하여 네트워크 지연을 시뮬레이션하고, 이 기간이 지난 후에만 서버가 클라이언트의 위치를 업데이트합니다.
  5. 서버 위치 예측 및 업데이트: 서버는 클라이언트의 예측된 미래 위치로 이동합니다. 이동은 Vector3.MoveTowards 함수를 사용하여 부드럽게 수행됩니다.
  6. 위치 보정: 서버와 클라이언트 사이의 거리가 일정 임계값보다 작아지면, 서버의 위치를 클라이언트의 현재 위치로 보정하여 불필요한 "와리가리" 움직임을 방지합니다.

결론

Dead Reckoning과 네트워크 지연 시뮬레이션을 Unity에서 구현함으로써, 실시간 멀티플레이어 게임에서 플레이어의 위치 동기화와 예측을 향상시킬 수 있습니다. 이 방법은 플레이어에게 더 원활하고 정확한 게임 경험을 제공하는 데 중요합니다.

이 게시물에서 제공된 코드와 개념을 사용하여, 자신만의 네트워크 게임 프로젝트에 Dead Reckoning 기법을 적용해 볼 수 있습니다.

순서대로 0.5초, 0.4초, 0.3초, 0.2초, 0.1초 네트워크 지연 녹색(클라), 서버(파랑)

샘플코드

using UnityEngine;

public class DeadReckoningSimulation : MonoBehaviour {
    public Vector3 Min = new Vector3(-5, 0, -5);
    public Vector3 Max = new Vector3(5, 0, 5);
    public float MoveSpeed = 3f;
    public float NetworkDelay = 1f; // 네트워크 지연 시간


    public float CorrectionSpeed = 10f;
    public float SomeThreshold = 0.1f;

    public float ElapsedTime;
    public bool EnableKeyboardControl;

    private Vector3 Destination;
    private Vector3 PredictedPosition;
    private Vector3 ClientPosition;
    private Vector3 ServerPosition;
    private Vector3 MoveDirection;

    private GameObject ClientMarker;
    private GameObject ServerMarker;
    private LineRenderer LineRenderer;

    private void Awake()
    {
        InitializeMarkers();
        InitializeLineRenderer();
        SetRandomDestination();
        ClientPosition = ServerPosition = transform.position;
    }

    private void InitializeMarkers()
    {
        ClientMarker = CreateMarker(PrimitiveType.Sphere, Color.green, 0.5f);
        ServerMarker = CreateMarker(PrimitiveType.Cube, Color.blue, 0.55f);
    }

    private GameObject CreateMarker(PrimitiveType type, Color color, float scale)
    {
        GameObject marker = GameObject.CreatePrimitive(type);
        marker.transform.localScale = Vector3.one * scale;
        marker.GetComponent<Renderer>().material.color = color;
        return marker;
    }

    private void InitializeLineRenderer()
    {
        LineRenderer = gameObject.AddComponent<LineRenderer>();
        LineRenderer.widthMultiplier = 0.2f;
        LineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        LineRenderer.startColor = Color.blue;
        LineRenderer.endColor = Color.yellow;
        LineRenderer.positionCount = 2;
    }

    private void Update()
    {
        HandleKeyboardInput();
        UpdateClientPosition();
        UpdateServerPosition();
        CorrectPositionIfNeeded();
    }

    private void UpdateClientPosition()
    {
        if (EnableKeyboardControl) {
            // 키보드 입력에 따라 클라이언트 위치 직접 업데이트
            Vector3 newPosition = ClientMarker.transform.position + MoveDirection * MoveSpeed * Time.deltaTime;
            ClientMarker.transform.position = newPosition;
            ClientPosition = newPosition;
        } else {
            // 무작위 목적지로 이동
            if (Vector3.Distance(ClientMarker.transform.position, Destination) < 0.1f) {
                SetRandomDestination();
            } else {
                ClientMarker.transform.position = Vector3.MoveTowards(ClientMarker.transform.position, Destination, MoveSpeed * Time.deltaTime);
                ClientPosition = ClientMarker.transform.position;
            }
        }
    }

    private void SetRandomDestination()
    {
        Destination = new Vector3(
            Random.Range(Min.x, Max.x),
            Random.Range(Min.y, Max.y),
            Random.Range(Min.z, Max.z)
        );
    }

    private void UpdateServerPosition()
    {
        ElapsedTime += Time.deltaTime;
        if (NetworkDelay < ElapsedTime) {
            // 클라이언트가 멈춰있는 경우
            if (MoveDirection == Vector3.zero) {
                // 예측 위치를 클라이언트의 현재 위치로 설정
                PredictedPosition = ClientPosition;
            } else {
                // 클라이언트가 이동 중인 경우, 예측 위치를 계산
                Vector3 directionToPredict = (ClientPosition - ServerPosition).normalized;
                PredictedPosition = ClientPosition + directionToPredict * MoveSpeed * NetworkDelay;
            }
            ElapsedTime -= NetworkDelay; // 시간을 리셋하지 않고, networkDelay만큼 감소
        }

        ServerPosition = Vector3.MoveTowards(ServerPosition, PredictedPosition, MoveSpeed * Time.deltaTime);
        ServerMarker.transform.position = ServerPosition;
    }

    private void CorrectPositionIfNeeded()
    {
        float distance = Vector3.Distance(ServerPosition, ClientMarker.transform.position);
        if (distance > SomeThreshold) {
            // Lerp 함수를 사용하여 서버 위치를 부드럽게 클라이언트 위치로 보정
            ServerPosition = Vector3.Lerp(ServerPosition, ClientMarker.transform.position, CorrectionSpeed * Time.deltaTime / distance);
            ServerMarker.transform.position = ServerPosition;
        }

        // 서버와 클라이언트 위치 사이를 시각적으로 나타내기 위해 라인 렌더러 업데이트
        LineRenderer.SetPosition(0, ServerPosition);
        LineRenderer.SetPosition(1, ClientMarker.transform.position);
    }


    private void HandleKeyboardInput()
    {
        if (EnableKeyboardControl == false) {
            return;
        }

        MoveDirection = Vector3.zero; // 매 프레임마다 방향 초기화
        if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) {
            MoveDirection += Vector3.forward;
        }
        if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) {
            MoveDirection += Vector3.back;
        }
        if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) {
            MoveDirection += Vector3.left;
        }
        if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) {
            MoveDirection += Vector3.right;
        }
        MoveDirection = MoveDirection.normalized;
    }
}

 

반응형

'Programming > Blah Blah' 카테고리의 다른 글

소프트웨어 테스트 방식  (0) 2019.11.05
Voxelize Shadow Map  (0) 2019.10.27
리팩토링  (0) 2019.10.03
Gitea 사용해보기  (0) 2019.08.14
SharpNav  (0) 2018.09.01
123  (0) 2018.06.21
비주얼 스튜디오 확장(vsix)  (0) 2018.05.05
.tt 확장자 예제  (0) 2018.04.19