Unity+MagicOnion4.1. StreamingHub의 실시간 통신 편 테스트

장절

  • 환경 구축 & 서비스 통신편
  • StreamingHub의 실시간 통신 편
  • EC2 환경에서 서버 편 실행
  • IL2CPP 스마트폰 실기구 건편
  • 이마


  • 환경 구축 & 서비스 통신편의 계속
  • 환경 구축, 전제 등은 상술한 문장
  • 을 참조하십시오.
  • 샘플에 사용된 코드는 기본적으로MagicOn의 README
  • 를 사용했다
    연결 동작 확인으로 제작되었기 때문에 매직넘버를 사용했고 오류도 제대로 처리하지 못한 점 양해 부탁드립니다.

    고객 환경 구축


    클라이언트<-> 서버 일반 클래스 만들기


    Assets/Scripts/MyApp/Shared/MessagePackObjects/Player.cs
    using MessagePack;
    using UnityEngine;
    
    namespace MyApp.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; }
        }
    }
    

    서버 연결을 위한 인터페이스 만들기


    Assets/Scripts/MyApp/Shared/Hubs/IGamingHub.cs
    using System.Threading.Tasks;
    using MagicOnion;
    using MyApp.Shared.MessagePackObjects;
    using UnityEngine;
    
    namespace MyApp.Shared.Hubs
    {
        public interface IGamingHubReceiver
        {
            // return type shuold be `void` or `Task`, parameters are free.
            void OnJoin(Player player);
            void OnLeave(Player player);
            void OnMove(Player player);
        }
    
    // Client -> Server definition
    // implements `IStreamingHub<TSelf, TReceiver>`  and share this type between server and client.
        public interface IGamingHub : IStreamingHub<IGamingHub, IGamingHubReceiver>
        {
            // return type shuold be `Task` or `Task<T>`, parameters are free.
            Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation);
            Task LeaveAsync();
            Task MoveAsync(Vector3 position, Quaternion rotation);
        }
    }
    

    서버에 연결된 클래스 만들기


    Assets/Scripts/MyApp/GamingHubClient.cs
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Grpc.Core;
    using MagicOnion.Client;
    using MyApp.Shared.Hubs;
    using MyApp.Shared.MessagePackObjects;
    using UnityEngine;
    
    namespace MyApp
    {
        public class GamingHubClient : IGamingHubReceiver
        {
            Dictionary<string, GameObject> _players = new Dictionary<string, GameObject>();
            IGamingHub _client;
    
            public async Task<GameObject> ConnectAsync(Channel grpcChannel, string roomName, string playerName)
            {
                _client = StreamingHubClient.Connect<IGamingHub, IGamingHubReceiver>(grpcChannel, this);
    
                var roomPlayers = await _client.JoinAsync(roomName, playerName, Vector3.zero, Quaternion.identity);
                foreach (var player in roomPlayers)
                {
                    (this as IGamingHubReceiver).OnJoin(player);
                }
    
                return _players[playerName];
            }
    
            // methods send to server.
    
            public Task LeaveAsync()
            {
                return _client.LeaveAsync();
            }
    
            public Task MoveAsync(Vector3 position, Quaternion rotation)
            {
                return _client.MoveAsync(position, rotation);
            }
    
            // dispose client-connection before channel.ShutDownAsync is important!
            public Task DisposeAsync()
            {
                return _client.DisposeAsync();
            }
    
            // You can watch connection state, use this for retry etc.
            public Task WaitForDisconnect()
            {
                return _client.WaitForDisconnect();
            }
    
            // Receivers of message from server.
    
            void IGamingHubReceiver.OnJoin(Player player)
            {
                Debug.Log("Join Player:" + player.Name);
    
                var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.name = player.Name;
                cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
                _players[player.Name] = cube;
            }
    
            void IGamingHubReceiver.OnLeave(Player player)
            {
                Debug.Log("Leave Player:" + player.Name);
    
                if (_players.TryGetValue(player.Name, out var cube))
                {
                    GameObject.Destroy(cube);
                    _players.Remove(player.Name);
                }
            }
    
            void IGamingHubReceiver.OnMove(Player player)
            {
                Debug.Log("Move Player:" + player.Name);
    
                if (_players.TryGetValue(player.Name, out var cube))
                {
                    cube.transform.SetPositionAndRotation(player.Position, player.Rotation);
                }
            }
        }
    }
    

    컨트롤러 수정

  • 대본재여기.
  • Assets/Scripts/MyApp/MyFirstController.cs
         public class MyFirstController : MonoBehaviour
         {
             private Channel _channel;
             private IMyFirstService _service;
    +        private GamingHubClient _hub;
    +        private float _moveTimer;
    +        private float _leaveTimer;
    
    Assets/Scripts/MyApp/MyFirstController.cs
             async void Start()
             {
                 var x = Random.Range(0, 1000);
                 var y = Random.Range(0, 1000);
                 var result = await _service.SumAsync(x, y);
                 Debug.Log($"Result: {result}");
    
    +            var id = Random.Range(0, 10000);
    +            _hub = new GamingHubClient();
    +            await _hub.ConnectAsync(_channel, "Room", $"Player-{id}");
            }
    
    Assets/Scripts/MyApp/MyFirstController.cs
    +        async void Update ()
    +        {
    +            if (_hub == null)
    +            {
    +                return;
    +            }
    +            _moveTimer += Time.deltaTime;
    +
    +            if(_moveTimer > 1f){
    +                _moveTimer = 0f;
    +                var x = Random.Range(-10, 10);
    +                var y = Random.Range(-5, 5);
    +                await _hub.MoveAsync(new Vector3(x, y), new Quaternion());
    +            }
    +            if (_leaveTimer > 30f)
    +            {
    +                await _hub.LeaveAsync();
    +            }
    +        }
    
    Assets/Scripts/MyApp/MyFirstController.cs
             async void OnDestroy()
             {
    +            if (_hub != null)
    +            {
    +                await _hub.DisposeAsync();
    +            }
                 if (_channel != null)
                 {
                     await _channel.ShutdownAsync();
                 }
             }
    
  • 완성된 클래스
  • Assets/Scripts/MyApp/MyFirstController.cs
    using Grpc.Core;
    using MagicOnion.Client;
    using MyApp.Shared.Services;
    using UnityEngine;
    
    namespace MyApp
    {
        public class MyFirstController : MonoBehaviour
        {
            private Channel _channel;
            private IMyFirstService _service;
            private GamingHubClient _hub; // 追加
            private float _moveTimer;
            private float _leaveTimer;
    
            void Awake()
            {
                _channel = new Channel("localhost", 5000, ChannelCredentials.Insecure);
                _service = MagicOnionClient.Create<IMyFirstService>(_channel);
            }
    
            async void Start()
            {
                var x = Random.Range(0, 1000);
                var y = Random.Range(0, 1000);
                var result = await _service.SumAsync(x, y);
                Debug.Log($"Result: {result}");
    
                // 追加
                var id = Random.Range(0, 10000);
                _hub = new GamingHubClient();
                await _hub.ConnectAsync(_channel, "Room", $"Player-{id}");
            }
            
            // 追加
            async void Update ()
            {
                if (_hub == null)
                {
                    return;
                }
                _moveTimer += Time.deltaTime;
                _leaveTimer += Time.deltaTime;
    
                if(_moveTimer > 1f){
                    _moveTimer = 0f;
                    var x = Random.Range(-10, 10);
                    var y = Random.Range(-5, 5);
                    await _hub.MoveAsync(new Vector3(x, y), new Quaternion());
                }
    
                if (_leaveTimer > 30f)
                {
                    await _hub.LeaveAsync();
                }
            }
    
            async void OnDestroy()
            {
                // 追加
                if (_hub != null)
                {
                    await _hub.DisposeAsync();
                }
                if (_channel != null)
                {
                    await _channel.ShutdownAsync();
                }
            }
        }
    }
    
    
    지금까지 고객(Unity) 측 작업은 잠시 종료되었습니다.

    서버 환경 구축


    StreamingHub 만들기


    hubs/GamingHub.cs
    using System.Linq;
    using System.Threading.Tasks;
    using MagicOnion.Server.Hubs;
    using MyApp.Shared.Hubs;
    using MyApp.Shared.MessagePackObjects;
    using UnityEngine;
    
    namespace MyApp.Hubs
    {
        public class GamingHub : StreamingHubBase<IGamingHub, IGamingHubReceiver>, IGamingHub
        {
            // this class is instantiated per connected so fields are cache area of connection.
            IGroup _room;
            Player _self;
            IInMemoryStorage<Player> _storage;
    
            public async Task<Player[]> JoinAsync(string roomName, string userName, Vector3 position, Quaternion rotation)
            {
                _self = new Player {Name = userName, Position = position, Rotation = rotation};
    
                // Group can bundle many connections and it has inmemory-storage so add any type per group. 
                (_room, _storage) = await Group.AddAsync(roomName, _self);
    
                // Typed Server->Client broadcast.
                BroadcastExceptSelf(_room).OnJoin(_self);
    
                return _storage.AllValues.ToArray();
            }
    
            public async Task LeaveAsync()
            {
                Broadcast(_room).OnLeave(_self);
                await _room.RemoveAsync(Context);
            }
    
            public async Task MoveAsync(Vector3 position, Quaternion rotation)
            {
                _self.Position = position;
                _self.Rotation = rotation;
                Broadcast(_room).OnMove(_self);
            }
    
            // You can hook OnConnecting/OnDisconnected by override.
            protected override async ValueTask OnDisconnected()
            {
                // on disconnecting, if automatically removed this connection from group.
                await CompletedTask;
            }
        }
    }
    

    연결 확인


    고객 시나리오 실행

  • 가능하면 여러 고객으로 구축된 상태를 이해하기 쉽다
  • 좌표가 서버에 도착하고 모든 클라이언트에게 이동 처리를 하면 OK
  • 좋은 웹페이지 즐겨찾기