수정중인 페이지 입니다.

TickTimer

소개

Fusion은 다양한 사전 구축된 NetworkBehaviour들을 제공하여 빠르게 수행할 수 있도록 지원합니다.

    [Networked]
    public TickTimer Timer { get; set; }

    // move object
    public override void FixedUpdateNetwork() {

        // set timer with seconds
        Timer = TickTimer.CreateFromSeconds(Runner, 1);

        // set timer with ticks
        Timer = TickTimer.CreateFromTicks(Runner, 5);


        // check if timer is running 
        if (Timer.IsRunning) {

        }

        // returns nullable int? if this has a value its the tick the timer will expire on
        var targetTick     = Timer.TargetTick;

        // if any remaining ticks returns how many ticks that are remaining, if not returns null 
        var remainingTicks = Timer.RemainingTicks(Runner);

        // if the timer has been started and has then expired, is not true if timer has not been started
        if (Timer.Expired(Runner)) {

        }

        // if the timer expired (same as Expired) OR if its not running at all
        if (Timer.ExpiredOrNotRunning(Runner)) {

        }

        // to clear/stop a timer:
        Timer = TickTimer.None;
    }

메인 화면으로

NetworkRunner

NetworkRunner는 Fusion의 핵심입니다. NetworkRunner는 네트워크에 대한 연결을 관리하고 입력 수집에서 스냅샷 병합, 콜백 및 SimulationBehaviour의 라이프 사이클 메소드 호출에 이르기까지 시뮬레이션을 제어합니다. 각 클라이언트와 서버의 씬에 있는 NetworkRunner는 하나뿐입니다.

이러한 작업을 수행하기 위해서 NetworkRunner는 모든 NetworkObject, NetworkBehaviourSimulationBehaviour를 추적해야 합니다.

메인 화면으로

속성

모든 NetworkBehaviourRunner 속성을 통해 현재의 NetworkRunner에 접근할 수 있으며, Runner 자체가 일부 중요한 시스템 속성을 나타냅니다.

  • IsServer: 이 러너가 권한 있는 서버를 나타내는 경우 true
  • Stage: 현재 시뮬레이션 단계입니다. 시뮬레이션이 현재 다음 상태를 예측하고 있는 경우 Forward가 되고, 서버 업데이트를 조정하기 위해 이전 상태를 다시 시뮬레이션하는 경우 Resimulate가 될 수 있습니다. 서버나 시뮬레이션이 항상 Forward인 클라이언트-권한 모드에서는 Resimulate가 발생하지 않습니다.

메인 화면으로

콜백

NetworkRunnerINetworkRunnerCallbacks 인터페이스를 구현하고 NetworkRunner.AddCallbacks()를 호출하여 러너에 등록함으로써 애플리케이션이 중요한 네트워크 관련 이벤트에 후크 할 수 있도록 합니다.

노트: 또한 Fusion은 INetworkRunnerCallbacks 인터페이스를 콜백 대상으로 구현하는 모든 SimulationBehaviour를 자동으로 등록합니다.

가장 중요한 콜백은 OnServerConnectedOnPlayerJoined로, 이 게임에서 초기 플레이어 객체를 생성할 수 있습니다.

public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
    _players[player] = runner.Spawn(_playerPrefab, Vector3.zero, Quaternion.identity, player);
}

사용 가능한 모든 콜백에 대한 상세 설명은 API 레퍼런스를 참고하세요.

메인 화면으로

GameMode

GameMode는 로컬 피어의 동작 방식을 정의합니다. GameModeNetworkRunner.StartGame() 메소드에 파라미터로 전달됩니다.

  • client: 클라이언트로 실행, 로컬 플레이어 생성, 클라이언트 호스트(클라우드 공유) 또는 전용(서버 기반)에 연결합니다.
  • host: 서버로서 실행합니다. 로컬 플레이어를 생성합니다 (서버 + 클라이언트)
  • server: 전용 서버로서 실행합니다. 플레이어는 없습니다
  • shared: 클라이언트로 실행합니다. Fusion Plugin 서버로 연결하기 위해 로컬 플레이어를 생성합니다.
  • single: "서버"로서 실행하고, 로컬 플레이어를 생성합니다. 연결은 허용되지 않습니다 (Singleplayer)

메인 화면으로

NetworkTransform

NetworkTransform은 실제 로컬 Transform 객체와 시뮬레이션 상태 사이를 보간하여 단순한 운동학적 객체를 관리합니다.

메인 화면으로

NetworkRigidbody

강체의 경우, NetworkRigidbody는 단순히 이전 상태 사이를 보간하는 것보다 더 나은 기능을 하며 더 정확한 로컬 위치를 추정하기 위해 물리 물체의 예측 가능한 특성을 사용합니다.

메인 화면으로

NetworkCharacterController

가장 불규칙한 객체는 플레이어가 직접 제어하는 객체(일반적으로 캐릭터 컨트롤러라고 함)이며, Fusion에는 특정 사용 사례에 대한 NetworkCharacterController도 있습니다.

사전 구축된 NetworkCharacterController는 일반적으로 원하는 동작을 포함하므로 빠른 프로토 타이핑을 가능하게 합니다. 그러나 캐릭터 컨트롤러 구현이 게임마다 매우 다르기 때문에 모든 경우에 적합한 캐릭터 컨트롤러는 없습니다.

따라서 NetworkCharacterController가 사용하는 두 가지 핵심 방식인 Move()ComputeRawMovement()의 소스 코드를 읽어 게임 제작에 필요한 대체 맞춤형 캐릭터 컨트롤러를 만드는 영감을 이끌어내는 것이 좋습니다.

메인 화면으로

ComputeRawSteer()

참조용 구현입니다.

ComputeRawSteer()는 캐릭터가 현재 수행하고 있는 동작 유형에 따라 동작 계산을 대부분 수행하는 내부 메소드입니다. 사전 구축된 NetworkCharacterController에서 Move()는 작성할 구조체에 대한 참조를 전달하여 ComputeRawMovement()에서 movementPack 값을 요청합니다.

void ComputeRawSteer(ref Movement movementPack, float dt) {
  Grounded = movementPack.Grounded;

  float minYSpeed = -100;
  float maxYSpeed = 100;

  var current = Velocity;

  switch (movementPack.Type) {
    case MovementType.FreeFall:

      current.y -= Config._gravityStrength * dt;
      if (!Config.AirControl || movementPack.Tangent == default(Vector3)) {
        current.x = Mathf.Lerp(current.x, 0, dt * Config.Braking);
        current.z = Mathf.Lerp(current.z, 0, dt * Config.Braking);
      } else {
        current += movementPack.Tangent * Config.Acceleration * dt;
      }

      break;

    case MovementType.Horizontal:
      // apply tangent velocity
      current += movementPack.Tangent * Config.Acceleration * dt;

      var tangentSpeed = Vector3.Dot(current, movementPack.Tangent);

      // lerp current velocity to tangent
      var tangentVel = tangentSpeed * movementPack.Tangent;
      var lerp       = Config.Braking * dt;

      current.x = Mathf.Lerp(current.x, tangentVel.x, lerp);
      current.z = Mathf.Lerp(current.z, tangentVel.z, lerp);

      // we only lerp the vertical velocity if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (Jumped == false) {
        current.y = Mathf.Lerp(current.y, tangentVel.y, lerp);
      }

      // clamp tangent velocity with max speed
      if (tangentSpeed > MaxSpeed) {
        current -= movementPack.Tangent * (tangentSpeed - MaxSpeed);
      }

      break;

    case MovementType.SlopeFall:
      current   += movementPack.SlopeTangent * Config.Acceleration * dt;
      minYSpeed =  -Config.MaxSlopeSpeed;
      break;

    case MovementType.None:
      var lerpFactor = dt * Config.Braking;

      if (current.x != 0) {
        current.x = Mathf.Lerp(current.x, default, lerpFactor);
        if (Mathf.Abs(current.x) < float.Epsilon) {
          current.x = 0;
        }
      }

      if (current.z != 0) {
        current.z = Mathf.Lerp(current.z, default, lerpFactor);
        if (Mathf.Abs(current.z) < float.Epsilon) {
          current.z = 0;
        }
      }

      // we only lerp the vertical velocity back to 0 if the character is not jumping in this exact frame,
      // otherwise it will jump with a lower impulse
      if (current.y != 0 && Jumped == false) {
        current.y = Mathf.Lerp(current.y, default, lerpFactor);
        if (Mathf.Abs(current.y) < float.Epsilon) {
          current.y = 0;
        }
      }

      minYSpeed = 0;
      break;
  }

  // horizontal is clamped elsewhere
  if (movementPack.Type != MovementType.Horizontal) {
    Vector2 h = new Vector2(current.x, current.z);
    if (h.sqrMagnitude > MaxSpeed * MaxSpeed) {
      h = h.normalized * MaxSpeed;
    }

    current.x = h.x;
    current.y = Mathf.Clamp(current.y, minYSpeed, maxYSpeed);
    current.z = h.y;
  }

  Velocity = current;
  // set jump state
  Jumped = false;
}

메인 화면으로

Move()

참조용 구현입니다.

이것은 전체 Move() 함수의 기본 구현입니다. 이동 쿼리를 수행하고 그 결과를 사용하여 새 속도를 계산한 다음 침투 보정 + 속도 적분을 변환 위치에 적용합니다. 회전은 변경되지 않습니다.

  • direction: 의도된 이동 방향, 이동 쿼리 + 가속에 따름.
  • callback: 선택적인 사용자 지정 콜백 객체.
  • layerMask: 선택적인 레이어 마스크. 전달되지 않으면, Config의 기분 구성 하나가 사용됩니다.
public void Move(Vector3 direction, ICallbacks callback = null, LayerMask? layerMask = null) {

    var dt           = Runner.DeltaTime;
    var movementPack = ComputeRawMovement(direction, callback, layerMask);

    ComputeRawSteer(ref movementPack, dt);

    var movement = Velocity * dt;
    if (movementPack.Penetration > float.Epsilon) {
      if (movementPack.Penetration > Config.AllowedPenetration) {
        movement += movementPack.Correction;
      } else {
        movement += movementPack.Correction * Config.PenetrationCorrection;
      }
    }

    _transform.position += movement;

    #if DEBUG
    LastMovement = movementPack;
    #endif
}

메인 화면으로

NetworkMechanimAnimator

NetworkMechanimAnimator는 연결된 유니티 메카니즘 Animator가 보유하고 있는 파라미터들의 값을 동기화합니다.

주의: 애니메이션 자체를 동기화하지는 않습니다!

메인 화면으로

NetworkObjectPool

런타임 메모리 할당을 줄이고 더 중요한 것은 모든 유니티 게임에서 객체 폴링을 사용하여 오래된 게임 객체를 폐기하지 않고 재사용할 수 있도록 해야 한다는 것입니다. 네트워크 개체도 예외가 아닙니다.

모든 게임이 다르고 요구 사항이 다르기 때문에 Fusion(유니티와 같이)은 객체 풀링이 내장되어 있지 않고 대신 풀에서 객체를 가져오는 후크와 사용된 객체를 풀에 반환하는 후크를 제공합니다.

메인 화면으로

NetworkSceneManager

메인 화면으로

NetworkEvents

NetworkRunner.AddCallbacks() 대신 NetworkEvents를 사용할 수 있습니다. - 유니티 인스펙터에서 이벤트 핸들러를 직접 연결합니다. 컴포넌트를 NetworkObject에 추가하고 끌어서 놓기를 사용하여 개별 이벤트 핸들러를 등록하기만 하면 됩니다.

NetworkEvents를 사용할 필요가 없으며 대부분의 게임에서는 필요하지 않지만, 네트워크 이벤트를 유니티 씬에서 구성해야 하는 드문 경우 편의를 위해 포함되어 있습니다.

기술문서 TOP으로 돌아가기