This document is about: QUANTUM 2
SWITCH TO

커맨드

소개

Quantum 커맨드는 Quantum 표준 API에 대한 입력 데이터 경로입니다. 퀀텀 입력과 유사하지만 모든 틱을 전송해야 하는 것은 아닙니다.

Quantum 커맨드는 완전히 결정론적입니다. 서버는 발송 시간에 상관없이 수신인을 항상 승인하고 확인합니다. 이는 절충을 수반합니다. 즉, 명령을 보낸 클라이언트를 포함하여 로컬 클라이언트는 시뮬레이션에서 명령이 수신되는 틱을 예측할 수 없습니다. 필요한 경우 시각적 예측을 표시할 수 있지만, 시뮬레이션은 서버가 틱의 일부로 확인한 커맨드만 수신합니다.

커맨드는 Photon.Deterministic.DeterministicCommand에서 상속되는 일반 C# 클래스로 구현됩니다. 여기에는 직렬화할 수 있는 모든 데이터를 포함할 수 있습니다.

C#

using Photon.Deterministic;
namespace Quantum
{
    public class CommandSpawnEnemy : DeterministicCommand
    {
        public long enemyPrototypeGUID;
        
        public override void Serialize(BitStream stream)
        {
            stream.Serialize(ref enemyPrototypeGUID);   
        }

        public void Execute(Frame f)
        {
            var enemyPrototype = f.FindAsset<EntityPrototype>(enemyPrototypeGUID);
            enemyPrototype.Container.CreateEntity(f);
        }
    }
}

시뮬레이션에서 커맨드 시스템 설정

Quantum에서 커맨드를 전송하려면 해당 커맨드를 위한 시스템을 만들고 사용 가능한 커맨드를 알아야 합니다.

  • Quantum 2.1 이상에서, 커맨드는 CommandSetup.User.cs 내에서 등록되어야 합니다.
  • Quantum 2.0에서, 커맨드는 CommandSetup.cs 내에서 등록되어야 합니다.

DeterministicCommandSetup

커맨드는 CommandSetup.User.cs에 있는 커맨드 팩토리에 추가되어야 런타임에 사용할 수 있습니다.

주의: CommandSetup.Legacy.cs는 이 설정에서 직접 사용되지 않지만 호환성을 위해 2.1에서 유지해야 합니다.

C#

// CommandSetup.User.cs

namespace Quantum {
  public static partial class DeterministicCommandSetup {
    static partial void AddCommandFactoriesUser(ICollection<IDeterministicCommandFactory> factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
      // user commands go here
      // new instances will be created when a FooCommand is received (de-serialized)
      factories.Add(new FooCommand());

      // BazCommand instances will be acquired from/disposed back to a pool automatically
      factories.Add(new DeterministicCommandPool<BazCommand>());
    }
  }
}

----------

// CommandSetup.Legacy.cs

using Photon.Deterministic;
namespace Quantum {
  public static class CommandSetup {
    public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
      return new null;
    }
  }
}

CommandSetup (2.0 에서만!)

Quantum 2.0에서 커맨드는 런타임에서 사용할 수 있도록 CommandSetup.cs내에서 등록되어야합니다.

주의: 이 시스템은 Quantum의 최신 버전에서 더 이상 사용되지 않습니다. 자세한 내용은 DeterministicCommandSetup을 참조하십시오.

C#

using Photon.Deterministic;
namespace Quantum {
  public static class CommandSetup {
    public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
      return new DeterministicCommand[] {

        // user commands go here
        new CommandSpawnEnemy(), 
      };
    }
  }
}

뷰에서 커맨드 전송하기

커맨드는 유니티 안에서 어디에서나 전송할 수 있습니다.

C#

using Quantum;
using UnityEngine;

public class UISpawnEnemy : MonoBehaviour
{
    [SerializeField] private EntityPrototypeAsset enemyPrototype = null;
    private PlayerRef _playerRef;

    public void Initialize(PlayerRef playerRef) {
        _playerRef = playerRef;
    }
    
    public void SpawnEnemy() {
        CommandSpawnEnemy command = new CommandSpawnEnemy()
        {
            enemyPrototypeGUID = enemyPrototype.Settings.Guid.Value,
        };
        QuantumRunner.Default.Game.SendCommand(command);
    }
}

뷰에서 CompoundCommands 전송하기

주의: Quantum 2.1 이상에서만 사용할 수 있습니다.

한 번에 여러 커맨드를 전송하려면 먼저 CompoundCommand를 생성하고 각각의 DeterministicCommand를 추가하기만 하면 됩니다.

C#

    var compound = new Quantum.Core.CompoundCommand();
    compound.Commands.Add(new FooCommand());
    compound.Commands.Add(new BazCommand());

    QuantumRunner.Default.Game.SendCommand(compound);

오버로드

SendCommand() 는 2개의 오버로드가 있습니다.

C#

void SendCommand(DeterministicCommand command);
void SendCommand(Int32 player, DeterministicCommand command);

여러 명의 플레이어가 동일한 컴퓨터에서 제어되는 경우 플레이어 인덱스(PlayerRef)를 지정합니다. 로컬 플레이어가 한 명뿐인 게임은 플레이어 인덱스 필드를 무시할 수 있습니다.

시뮬레이션으로부터 커맨드 폴링 하기

시뮬레이션 내부의 커맨드를 수신하고 처리하려면 특정 플레이어에 대한 프레임을 폴링 합니다:

C#

using Photon.Deterministic;
namespace Quantum
{
    public class PlayerCommandsSystem : SystemMainThread
    {
        public override void Update(Frame f)
        {
            for (int i = 0; i < f.PlayerCount; i++)
            {
                 var command = f.GetPlayerCommand(i) as CommandSpawnEnemy;
                 command?.Execute(f);
            }
        }
    }
}

다른 시스템과 마찬가지로 커맨드 폴링 및 처리하는 시스템을 SystemSetup.cs에 포함해야 합니다.

C#

namespace Quantum {
  public static class SystemSetup {
    public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
      return new SystemBase[] {
        // pre-defined core systems
        [...]
        
        // user systems go here
        new PlayerCommandsSystem(), 
    
      };
    }
  }
}

노트

API는 명령에 대한 특정 콜백 메커니즘이나 설계 패턴을 적용하거나 구현하지 않습니다. 개발자는 커맨드를 신호로 인코딩하거나, 책임 체인을 사용하거나, 커맨드 실행을 메소드로 구현하는 등 커맨드를 사용, 해석 및 실행하는 방법을 선택할 수 있습니다.

콜렉션용 예제

리스트

C#

using System.Collections.Generic;
using Photon.Deterministic;

namespace Quantum
{
    public class ExampleCommand : DeterministicCommand
    {
        public List<EntityRef> Entities = new List<EntityRef>();
        
        public override void Serialize(BitStream stream)
        {
            var count = Entities.Count;
            stream.Serialize(ref count);
            if (stream.Writing)
            {
                foreach (var e in Entities)
                {
                    var copy = e;
                    stream.Serialize(ref copy.Index);
                    stream.Serialize(ref copy.Version);
                }
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    EntityRef readEntity = default;
                    stream.Serialize(ref readEntity.Index);
                    stream.Serialize(ref readEntity.Version);
                    Entities.Add(readEntity);
                }   
            }
        }
    }
}

배열

C#

using Photon.Deterministic;

namespace Quantum
{
    public class ExampleCommand : DeterministicCommand
    {
        public EntityRef[] Entities;
        
        public override void Serialize(BitStream stream)
        {
            stream.SerializeArrayLength(ref Entities);
            for (int i = 0; i < Cars.Length; i++)
            {
                EntityRef e = Entities[i];
                stream.Serialize(ref e.Index);
                stream.Serialize(ref e.Version);
                Entities[i] = e;
            }
        }
    }
}

복합 명령어 예제

입력 스트림에는 틱당 하나의 커맨드만 연결할 수 있습니다. 클라이언트가 틱당 여러 결정론적 커맨드를 전송할 수 있더라도 커맨드는 동일한 틱에서 시뮬레이션에 도달하지 않고 연속된 틱에서 개별적으로 도착합니다. 이 제한을 해결하려면 여러 결정론적 커맨드를 단일 복합 커맨드로 패킹할 수 있습니다.

복합 커맨드를 사용하는 첫 번째 단계는 이 클래스를 quantum_code에서 선언하는 것입니다. 주의: 이 클래스는 기본 Quantum 클래스가 아닙니다!

C#

public class CompoundCommand : DeterministicCommand {
  public static DeterministicCommandSerializer CommandSerializer;
  public List<DeterministicCommand> Commands = new List<DeterministicCommand>();

  public override void Serialize(BitStream stream) {
    if (CommandSerializer == null) {
      CommandSerializer = new DeterministicCommandSerializer();
      CommandSerializer.RegisterPrototypes(CommandSetup.CreateCommands(null, null));
    }
    
    var count = Commands.Count;
    stream.Serialize(ref count);

    if (stream.Reading) {
      Commands.Clear();
    }

    for (var i = 0; i < count; i++) {
      if (stream.Reading) {
        CommandSerializer.ReadNext(stream, out var cmd);
        Commands.Add(cmd);
      } else {
        CommandSerializer.PackNext(stream, Commands[i]);
      }
    }
  }
}

복합 커맨드 클래스는 단일 시뮬레이션 틱 내에서 많은 커맨드를 보내고 받는 데 유용합니다. 커맨드의 유형이 다르더라도 말입니다.

Back to top