ã¯ããã« ã¢ãã€ã«ãã¡ã¯ããªãŒ Advent Calendar 2019 ã®16æ¥ç®æ
åœã® shioiyan ã§ãã ã¢ãã€ã«ãã¡ã¯ããªãŒã«ã¯éšæŽ»åå¶åºŠããããèªåã¯ã²ãŒã ãžã£ã éšãšããäžå®æã«ã²ãŒã ãžã£ã ãè¡ã£ããããéšæŽ»ã«æå±ããŠããŸãã æè¿ã²ãŒã ãžã£ã éšã§ã.NET Coreããã³Unityçšã®ãªã¢ã«ã¿ã€ã /APIãšã³ãžã³ã® MagicOnion ã䜿ã£ãŠUnityã§ãªã¢ã«ã¿ã€ã éä¿¡ãå®è£
ããŠã¿ãã®ã§ãã®æ¹æ³ã玹ä»ããŠã¿ãŸãã ãã®èšäºãèªãã§ãããããš MagicOnionã®StreamingHubã䜿ã£ããªã¢ã«ã¿ã€ã éä¿¡ã®å®è£
æ¹æ³ãããã MagicOnionãå©çšããã¡ãªãã ã¯ã©ã€ã¢ã³ããšãµãŒãã§APIã®å®çŸ©ãå
±æã§ãã å
±æããã€ã³ã¿ãŒãã§ã€ã¹ãä»ããŠéä¿¡ã§ãããã, çŽæ¥ã¯ã©ã€ã¢ã³ããããµãŒã, ãµãŒãããã¯ã©ã€ã¢ã³ãã®ã³ãŒããåŒã³åºãããšãã§ãã C#ã®ãªããžã§ã¯ããMessgePackã«ãã£ãŠçŽæ¥ãããšãã§ãã ã¯ã©ã€ã¢ã³ããšãµãŒãã§éä¿¡å
容ã®éœéœ¬ãçºçããªã, éä¿¡ããããŒã¿ããã®ãŸãŸå©çšå¯èœãªããå¹çç éä¿¡ã®ããã«ããŒã¿ãå å·¥ããããšãããŸãèããªããŠè¯ã ã¯ã©ã€ã¢ã³ããšãµãŒããå
±éã®èšèª(C#)ã§éçºã§ã, å
¥åè£å®ã®æ©æµãåããããŸãã ã€ã³ã¿ãŒãã§ã€ã¹ã®å®è£
æŒããã¡ãœããå, åŒæ°ã®ãã¹ãªã©ãçºçãããšã³ã³ãã€ã«ãšã©ãŒã«ãªããããšã©ãŒã«æ°ãã€ãããšã容æã§ãã APIã®å®çŸ©ãéä¿¡ã«å©çšããããŒã¿ãå
±æã§ãããã, ã¯ã©ã€ã¢ã³ããšãµãŒãã§éä¿¡ããŠãããšããæèŠãããŸãæããã«å®è£
ããããšãå¯èœã§ãã ä»åäœããã® ãã¬ã€ã€ãŒã®äœçœ®ãåããè€æ°ã®ã¯ã©ã€ã¢ã³ãã§åæãããã²ãŒã ååå
¥å, ãã£ã©ã¯ã¿ãŒéžæç»é¢ ãã¬ã€ã€ãŒãåæãããã²ãŒã ç»é¢ éçºããŒãžã§ã³ Unity v2019.1.14f1 MagicOnion 2.6.2 MessagePack 1.7.3.7 å®è£
MagicOnionãUnityãããžã§ã¯ãã«å°å
¥ããã«ã¯MagicOnion, MessagePack for C#, gRPC packageã®å°å
¥ãå¿
èŠã§ãã ä»åå°å
¥æé ã«ã€ããŠã¯å²æããŸãã åŠçã®æµã ä»åã¯å€§ããåããŠ, ãã«ãŒã ã«åå ãããã£ã©ã¯ã¿ãŒç§»åã®åæããå®è£
ããŸãã ãŸãã¯ããã«åŠçã®æµãã玹ä»ããŸãã ã«ãŒã ã«åå ã, åå è
ãã¯ã©ã€ã¢ã³ãã«è¡šç€º ã¯ã©ã€ã¢ã³ã(SceneA): ååå
¥åãšãã£ã©ã¯ã¿ãŒéžæããã ã¯ã©ã€ã¢ã³ã: ã·ãŒã³åãæ¿ã(SceneA -> SceneB) â»ä»¥äžSceneB ã¯ã©ã€ã¢ã³ã: ãµãŒãã«æ¥ç¶ã, å
¥åããååãšãã£ã©ã¯ã¿ãŒããµãŒãã«éä¿¡ ãµãŒã: Playerã¯ã©ã¹ãçæã, ã«ãŒã ã«åå ããŠã«ãŒã ãä¿æ ãµãŒã: ã«ãŒã ã®åå è
æ
å ±ãè¿åŽ ã¯ã©ã€ã¢ã³ã: ã«ãŒã ã®åå è
æ
å ±ãå
ã«ãã¬ã€ã€ãŒ(GameObject)ãçæ ç§»åããèªãã£ã©ã¯ã¿ãŒãä»ã®ã¯ã©ã€ã¢ã³ãã«åæ ã¯ã©ã€ã¢ã³ã: ãã£ã©ã¯ã¿ãŒãç§»å ã¯ã©ã€ã¢ã³ã: nç§ã«äžåºŠãµãŒãã«ãã¬ã€ã€ãŒã®äœçœ®ãšåããéä¿¡ ãµãŒã: ä¿æããŠããPlayerãæŽæ°ããæŽæ°ããPlayerãè¿åŽ ã¯ã©ã€ã¢ã³ã: è¿åŽãããPlayerã®äœçœ®ãåãã䜿ã£ãŠãã¬ã€ã€ãŒ(GameObject)ãç§»åããã èªåãã¯ã©ã€ã¢ã³ãã§æäœãããã¬ã€ã€ãŒã®GameObjectã®æ
å ±ããµãŒãã«éä¿¡ã, ä»ã®ãã¬ã€ã€ãŒã®æ
å ±ãåãåã, ã¯ã©ã€ã¢ã³ãã§ä»ãã¬ã€ã€ãŒã®GameObjectãæç»ããŸãã ãã€ã³ãã¯ã¯ã©ã€ã¢ã³ã<->ãµãŒãã§Playeræ
å ±ããããšãã, åã¯ã©ã€ã¢ã³ãã§ã®åæãè¡ãããšã§ãã ã¯ã©ã€ã¢ã³ããšãµãŒãå
±éã®ã³ãŒã ã¯ã©ã€ã¢ã³ã<->ãµãŒãã§éä¿¡ããPlayerã¯ã©ã¹ãäœæ using MessagePack; using UnityEngine; namespace Sample.Shared.MessagePackObjects { [MessagePackObject] public class Player { [Key( 0 )] public string Name { get ; set ; } [Key( 1 )] public Vector3 Position { get ; set ; } [Key( 2 )] public Quaternion Rotation { get ; set ; } [Key( 3 )] public string UUID { get ; set ; } [Key( 4 )] public PlayerConfig.PrefabName PrefabName { get ; set ; } } } public class PlayerConfig { public enum PrefabName { Prefab1, Prefeb2 } } äžèšã®ãããªç¬èªãªåãVector3ãªã©ãMessagePackã§éä¿¡ã§ã, ã¯ã©ã€ã¢ã³ããšãµãŒãã§ãã®ãŸãŸäœ¿ãããšãã§ããŸãã ã¯ã©ã€ã¢ã³ããšãµãŒããšå
±æããã€ã³ã¿ãŒãã§ã€ã¹ãå®çŸ© ã¯ã©ã€ã¢ã³ã -> ãµãŒãã®éä¿¡ãè¡ãHubãš, ãµãŒã -> ã¯ã©ã€ã¢ã³ãã®éä¿¡ãè¡ãReceiverã®ã€ã³ã¿ãŒãã§ã€ã¹ãå®çŸ©ããŸãã Hubã¯ãµãŒã, Receiverã¯ã¯ã©ã€ã¢ã³ãã§å®è£
ã, ããããã¯ã©ã€ã¢ã³ã, ãµãŒãããåŒã°ããããšã«ãªããŸãã using MagicOnion; using Sample.Shared.MessagePackObjects; using System.Threading.Tasks; using UnityEngine; namespace Sample.Shared.Hubs { /// < summary > /// ã¯ã©ã€ã¢ã³ã -> ãµãŒã /// </ summary > public interface IGameHub : IStreamingHub<IGameHub, IGameHubReceiver> { /// < summary > /// ã²ãŒã ã«æ¥ç¶ããããšããµãŒãã«äŒãã /// </ summary > Task<Player[]> JoinAsync( string name, PlayerConfig.PrefabName prefabName); /// < summary > /// ã²ãŒã ããåæããããšããµãŒãã«äŒãã /// </ summary > Task LeaveAsync(); /// < summary > /// ç§»åããããšããµãŒãã«äŒãã /// </ summary > Task MoveAsync(Vector3 position, Quaternion rotation); } /// < summary > /// ãµãŒã -> ã¯ã©ã€ã¢ã³ã /// </ summary > public interface IGameHubReceiver { /// < summary > /// 誰ããã²ãŒã ã«æ¥ç¶ããããšãã¯ã©ã€ã¢ã³ãã«äŒãã /// </ summary > void OnJoin(Player player); /// < summary > /// 誰ããã²ãŒã ããåæããããšãã¯ã©ã€ã¢ã³ãã«äŒãã /// </ summary > void OnLeave( string uuid); /// < summary > /// 誰ããç§»åããäºãã¯ã©ã€ã¢ã³ãã«äŒãã /// </ summary > void OnMove(Player player); } } ã¯ã©ã€ã¢ã³ãã®å®è£
ã¯ã©ã€ã¢ã³ãã§ã¯, ãµãŒãããåŒã°ããReceiverã®å®è£
ãè¡ããŸãã ã¯ã©ã€ã¢ã³ãããã¯ãµãŒããšã®æ¥ç¶ãšãµãŒãã§å®è£
ãããŠããHubã®åŒã³åºããè¡ããŸãã ãµãŒãããåãåã£ãæ
å ±ãå
ã«ã¯ã©ã€ã¢ã³ããæç»ããŸãã using Grpc.Core; using MagicOnion.Client; ... namespace Sample { public class GameController : MonoBehaviour, IGameHubReceiver { private Channel channel; private IGameHub gameHub; private List<ClientPlayer> clientPlayers = new List<ClientPlayer>(); // ã«ãŒã ã®ãã¬ã€ã€ãŒæ
å ±ãšã¯ã©ã€ã¢ã³ãã§å©çšããGameObjectãä¿æ private ClientPlayer _myClientPlayer = null ; // æäœãããã¬ã€ã€ãŒ public float intervalSeconds; public string Name = "" ; // å
¥åãããŠãã¬ã€ã€ãŒå public PlayerConfig.PrefabName PrefabName; // ãã¬ã€ã€ãŒãéžãã ãã£ã©ã¯ã¿ãŒ async void Start() { // ãµãŒãã«æ¥ç¶ this .channel = new Channel( "localhost:12345" , ChannelCredentials.Insecure); this .gameHub = StreamingHubClient.Connect<IGameHub, IGameHubReceiver>( this .channel, this ); // ã«ãŒã ã«åå ããŠã«ãŒã ã®åå è
äžèЧãåãåã Player[] players = await this .gameHub.JoinAsync( this .Name, this .PrefabName); // ã«ãŒã åå è
æ
å ±ãå
ã«ãã¬ã€ã€ãŒ(GameObject)ãçæ this .AddClientPlayers(players); // äžå®æéããšã«ãµãŒãã«äœçœ®ãšåããéã this .UpdateAsObservable() .ThrottleFirst(TimeSpan.FromSeconds( this .intervalSeconds)) .Subscribe(_ => this .Move()) .AddTo( this ); } private void AddClientPlayers(Player[] players) { foreach (Player player in players) { // Playerã®PrefabName, Position, Rotationã«å¿ããŠInstantiateãè¡ã ClientPlayer clientPlayer = new ClientPlayer { GameObject = Instantiate(...), Player = player }; this .clientPlayers.Add(clientPlayer); ... } } async void Move() { // èªåã®æäœãã¬ã€ã€ãŒã®äœçœ®ãšåãããµãŒãã«éä¿¡ await this .gameHub.MoveAsync( this ._myClientPlayer.GameObject.transform.localPosition, this ._myClientPlayer.GameObject.transform.localRotation); } ... #region ãªã¢ã«ã¿ã€ã éä¿¡ã§ãµãŒããŒããåŒã°ããã¡ãœãã(IGameHubReceiverã®å®è£
) public void OnJoin(Player player) { Debug.Log( $" { player.Name } ãããå
¥å®€ããŸãã" ); // å
¥å®€åŸ, ä»ã®äººãåå ãããã¯ã©ã€ã¢ã³ãã§è¡šç€ºãããã¬ã€ã€ãŒã远å AddClientPlayers( new Player[] { player }); } public void OnLeave( string uuid) { Debug.Log( $" { name } ãããé宀ããŸãã" ); this .clientPlayers.RemoveAll(_ => _.Player.UUID == uuid); } public void OnMove(Player player) { ClientPlayer clientPlayer = this .GetPlayerByUUID(player.UUID); clientPlayer.Player = player; // äœçœ®ãçŽæ¥äžæžããããšã¯ãŒãããŠèŠããã®ã§æ»ããã«ç§»åããããã«DOTweenãäœ¿çš clientPlayer.GameObject.transform.DOLocalMove(player.Position, this .intervalSeconds); clientPlayer.GameObject.transform.DORotate(player.Rotation.eulerAngles, this .intervalSeconds); } } } using Sample.Shared.MessagePackObjects; using UnityEngine; namespace Sample { // ã«ãŒã ã®Playerã¯ã©ã¹ãšã¯ã©ã€ã¢ã³ãã§æç»ããŠãããã¬ã€ã€ãŒã®GameObjectããŸãšããŠç®¡çããã¯ã©ã¹ public class ClientPlayer { public GameObject GameObject; public Player Player; } } ãµãŒãã®å®è£
ãµãŒãã§ã¯, ã¯ã©ã€ã¢ã³ãããåŒã°ããHubã®å®è£
ãè¡ããŸãã ã¯ã©ã€ã¢ã³ããæ¥ç¶ããã«ãŒã ã®æ
å ±ãä¿æã, ã¯ã©ã€ã¢ã³ãã®ReceiverãåŒã³åºããŸãã Receiverã®åŒã³åºãã¯ã«ãŒã ã«åå ããŠããå
šå¡ã®ReceiverãåŒã³åºã Broadcast() ãèªå以å€ã®ã«ãŒã åå è
ã察象ã«ãã BroadcastExceptSelf() ãªã©ãéããŠè¡ããŸãã( åè ) using MagicOnion.Server.Hubs; using Sample.Shared.Hubs; using Sample.Shared.MessagePackObjects; using System; using System.Linq; using System.Threading.Tasks; using UnityEngine; public class GameHub : StreamingHubBase<IGameHub, IGameHubReceiver>, IGameHub { IGroup room; Player me; // ã«ãŒã ã«å
¥å®€ããŠãããã¬ã€ã€ãŒå
šå¡ã®æ
å ±ãä¿æ IInMemoryStorage<Player> storage; public async Task<Player[]> JoinAsync( string name, PlayerConfig.PrefabName prefabName) { const string roomName = "Room" ; //èªåã®æ
å ±ãäœæã, ä¿æ me = new Player { Name = name, Position = new Vector3( 0 , 0 , 0 ), Rotation = new Quaternion( 0 , 0 , 0 , 0 ), UUID = Guid.NewGuid().ToString( "N" ), PrefabName = prefabName }; //ã«ãŒã ã«åå ã, ã«ãŒã ãä¿æ ( this .room, this .storage) = await this .Group.AddAsync(roomName, me); //åå ããããšãèªå以å€ã®ã«ãŒã ã«åå ããŠããã¡ã³ããŒã«éç¥ this .BroadcastExceptSelf(room).OnJoin(me); // ã«ãŒã ã«å
¥å®€ããŠããä»ãŠãŒã¶å
šå¡ã®æ
å ±ãé
åã§ååŸãã return this .storage.AllValues.ToArray(); } public async Task LeaveAsync() { //ã«ãŒã å
ã®ã¡ã³ããŒããèªåãåé€ await room.RemoveAsync( this .Context); //é宀ããããšãå
šã¡ã³ããŒã«éç¥ this .Broadcast(room).OnLeave(me.UUID); } public async Task MoveAsync(Vector3 position, Quaternion rotation) { // ãµãŒããŒäžã®æ
å ±ãæŽæ° me.Position = position; me.Rotation = rotation; // æŽæ°ãããã¬ã€ã€ãŒã®æ
å ±ãèªå以å€ã®ã¡ã³ããŒã«éç¥ this .BroadcastExceptSelf(room).OnMove(me); } } ãŸãšã MagicOnionãå©çšããŠ, ã«ãŒã ã«æ¥ç¶ããã¯ã©ã€ã¢ã³ãå士ã§ãã¬ã€ã€ãŒã®åæãè¡ããŸããã C#ã§ãµãŒãã®ã³ãŒããæžãããšãã§ã, ãããã¯ã©ã€ã¢ã³ããšã³ãŒããå
±æã§ãããšããã®ã¯æ°ããäœéšã§ããã ãŸã è²ã
æ€èšŒããŠãããšããã§ãã, åŒãç¶ãMagicOnionãè§Šã£ãŠãããããšæããŸãã