Adam's Lair Forum

game development and casual madness
It is currently 2017/04/26, 06:01

All times are UTC + 1 hour [ DST ]




Post new topic Reply to topic  [ 12 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: 2016/02/13, 10:23 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
Hey all,

I've built a WebSocket based WCF Host and a client .dll to support server based Multiplayer apps. It works with Duality (although it currently crashes the Dualitor on build/rebuild. I would love to figure out how to prevent this, Adam or anyone want to help me figure that out?). I'm happy to pass this code snippet on; It is purely prototype code to proof that a server can interact with a Duality-based client and will need re-engineering to fit anyone's purposes.

I started by designing a WCF WebSocket Service and a .dll Class Library for the client. I was going to use Unity as a front end for a MMO Space Role-Playing-Shooter (top down) but Xamarin based stuff didn't play well with my .dll and I hated the idea of being stuck in something less than .net 4. I stumbled across Duality and it took a very short time to get this up and going.

The service was running on localhost (ws://localhost:port/PositionService.aspx); I hung the service on Azure as a cloudapp with a few extra clicks to make sure it was really passing web traffic.

I built the basic Duality Getting Started tutorial, shoehorned the .dll in and got it running!

Here's the webcall successfully working, captured with fiddler:
Image

Here's the debugger session showing the values flowing back into the client from the service:
Image

The X and Y in this image were NOT generated on the client, but on the webhost and passed to the client. This is very primitive but can be expanded greatly to support server based multiplayer or MMO style gameplay. Server side logic will need to be implemented, making the Duality client a fairly 'dumb' client that renders server data and transmits control input to the server. This could be blended to give the client more logic control but you loose security when the client is trusted more.

Code:

PositionService WCF Service Application (the host):
IPositionService.cs
Code:
using System;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading.Tasks;

namespace XYR
{
    [ServiceContract(CallbackContract = typeof (IPositionCallback))]
    public interface IPositionService
    {
        [OperationContract]
        bool EstablishConnection(Guid sessionId);

        [OperationContract(IsOneWay = true)]
        void SendInput(string inputValues);

        [OperationContract(IsOneWay = true)]
        Task FetchServerData();
    }

    [ServiceContract]
    public interface IPositionCallback
    {
        // These will be implemented on the Client.
        [OperationContract(IsOneWay = true)]
        Task SendPosition(Guid objectId, SpacePosition location);

        [OperationContract(IsOneWay = true)]
        Task SendMessage(string message);
    }

    [DataContract]
    public class SpacePosition
    {
        [DataMember]
        public int x { get; set; }

        [DataMember]
        public int y { get; set; }

        [DataMember]
        public int r { get; set; }
    }
}


PositionService.svc/
PositionService.svc.cs
Code:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;

namespace XYR
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
    public class PositionService : IPositionService
    {
        private string _inputTestString = string.Empty;
        private int xCoordinate = 0;
        private int yCoordinate = 0;
        private int rotationValue = 0;

        public bool EstablishConnection(Guid sessionId)
        {
            return true;
        }


        public async Task FetchServerData()
        {
            var callback = OperationContext.Current.GetCallbackChannel<IPositionCallback>();
            var random = new Random();

            var location = new SpacePosition();

            while (((IChannel)callback).State == CommunicationState.Opened)
            {
                location.x = xCoordinate;
                location.y = yCoordinate;
                location.r = random.Next();

                await callback.SendPosition(new Guid(), location);
                await callback.SendMessage(_inputTestString);

                await Task.Delay(1000);
            }
        }

        public void SendInput(string inputValues)
        {
            _inputTestString = inputValues;

            // Rudimentary messaging for now...
            switch (inputValues)
            {
                case "N":
                {
                    yCoordinate++;
                    break;
                }
                case "S":
                {
                    yCoordinate--;
                    break;
                }
                case "W":
                {
                    xCoordinate--;
                    break;
                }
                case "E":
                {
                    xCoordinate++;
                    break;
                }
            }
        }
    }
}


Web.config
Code:
<?xml version="1.0"?>
<configuration>
  <system.diagnostics>
    <trace>
      <listeners>
        <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
          name="AzureDiagnostics">
          <filter type="" />
        </add>
      </listeners>
    </trace>
  </system.diagnostics>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <system.serviceModel>
    <protocolMapping>
      <add scheme="http" binding="netHttpBinding"/>
    </protocolMapping>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
        multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"/>
    <!--
        To browse web app root directory during debugging, set the value below to true.
        Set to false before deployment to avoid disclosing web app folder information.
      -->
    <directoryBrowse enabled="true"/>
  </system.webServer>
</configuration>


PositionWebSocket Class Library (the "client" .dll)
WebSocketOperations.cs
Code:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using PositionWebsocket.WebSocketRef;

namespace PositionWebsocket
{

    public class WebSocketOperations
    {
        private InstanceContext context;
        public WebSocketRef.PositionServiceClient client;

        public static SpacePosition LocalPosition = new SpacePosition();
        public static string LocalMessage = string.Empty;
        public static bool Connected = false;
        public static bool Cancelled = false;
        public static List<string> ConsoleList = new List<string>();

        // Constructor sets all of the bits that the app.config needs to run without you having to carry an app.config along with your .dll
        public WebSocketOperations(string serverAddress)
        {
            NetHttpBinding binding = new NetHttpBinding() {Name = "NetHttpBinding_IPositionService"};

            binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;

            //Specify the address to be used for the client.
            //EndpointAddress address =
            //    new EndpointAddress(String.Format("ws://{0}/PositionService.svc", serverAddress));

            EndpointAddress address =
                new EndpointAddress("ws://dualitywshost.cloudapp.net/PositionService.svc");

            context = new InstanceContext(new CallbackHandler());
            client = new WebSocketRef.PositionServiceClient(context, binding, address);
        }

        public void GoDir(string value)
        {
            if(!Cancelled || Connected)
                client.SendInput(value);
        }

        public string GetLastMessage()
        {
            return LocalMessage;
        }

        public void GetServerData()
        {
            client.FetchServerDataAsync();
        }

        public void OpenSocket()
        {
            // Guid will be your user ID in a later implementation...
            if (!client.EstablishConnection(new Guid()))
            {
                ConsoleList.Add("Connection Failed!");
                return;
            }

            Connected = true;
            Cancelled = false;

            ConsoleList.Add("Connection Established!");
        }

        public void CloseSocket()
        {
            Cancelled = false;
            // have an error on client.Close where the async server data arrives after closure, causing .net to freak out.  TODO: fix this.
            //client.Close();
            Connected = false;
            ConsoleList.Add("Connection Closed!");
        }
    }

    // Here is the implementation for the Interface we built on the WCF host.
    class CallbackHandler : WebSocketRef.IPositionServiceCallback
    {
        public void SendPosition(Guid sessionId, SpacePosition location)
        {
            WebSocketOperations.LocalPosition = location;
        }

        public void SendMessage(string message)
        {
            WebSocketOperations.LocalMessage = message;
        }
    }
}


WebSocketRef
- You will need to add a Service Reference to your service so VS will generate all of the clientside code to match the host.

Your application in Duality:
Drop your .dll in the C:/Duality directory or wherever appropriate. Add it to your references.

Player.cs (from the getting started tutorial):
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Duality;
using Duality.Components.Physics;
using Duality.Input;

using PositionWebsocket;

namespace Duality_
{
    [RequiredComponent(typeof(RigidBody))]

    public class Player : Component, ICmpUpdatable, ICmpInitializable
    {

        private WebSocketOperations wsOperations;

        private RigidBody body;

        private bool first = true;

        void ICmpInitializable.OnInit(Component.InitContext context)
        {
            if (first)
            {
                body = this.GameObj.GetComponent<RigidBody>();

                wsOperations = new WebSocketOperations("");

                wsOperations.OpenSocket();

                wsOperations.GetServerData();
            }

            first = false;
        }

        void ICmpInitializable.OnShutdown(Component.ShutdownContext context)
        {

        }

        void ICmpUpdatable.OnUpdate()
        {
            if (DualityApp.Keyboard[Key.Left])
            {
                body.ApplyLocalForce(-0.1f);
                // Send a message to modify your serverside X coordinate one Westward/Left
                wsOperations.GoDir("W");
            }
            else if (DualityApp.Keyboard[Key.Right])
            {
                body.ApplyLocalForce(0.1f);
                // Send a message to modify your serverside X coordinate one Eastward/Right
                wsOperations.GoDir("E");
            }
            else
                body.AngularVelocity -= body.AngularVelocity*0.1f*Time.TimeMult;

            if (DualityApp.Keyboard[Key.Up])
            {
                body.ApplyLocalForce(Vector2.UnitY * -0.1f * body.Mass);
                // Send message to modify Serverside Y Coordinate appropriately
                wsOperations.GoDir("N");
            }
            else if (DualityApp.Keyboard[Key.Down])
            {
                body.ApplyLocalForce(Vector2.UnitY * 0.1f * body.Mass);
                // Send message to modify Serverside Y Coordinate appropriately
                wsOperations.GoDir("S");
            }
        }
    }
}


Last edited by zanco on 2016/02/13, 19:04, edited 2 times in total.

Top
 Profile  
 
PostPosted: 2016/02/13, 15:23 
Forum Adept
Forum Adept

Joined: 2014/12/13, 00:11
Posts: 397
Location: Brazil
Role: Gamer
This is nice! Do you have any gif/video of the example running, or even a link for us to download the modified getting started app? I'd love to see it working!

_________________
I only know the basics of C#. I have no advanced knowledge.


Top
 Profile  
 
PostPosted: 2016/02/13, 20:08 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
Since it's purely demonstrating that data is going back and forth but not influencing the RigidBody, I'm retooling the WCF service to accept multiple clients and render all of the movements. I'll post an update in a bit...


Top
 Profile  
 
PostPosted: 2016/02/13, 22:29 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
Any idea what would be causing this error? Is it possibly a [Serialization] issue? I wouldn't know where to begin...

Errors I get when doing a build/rebuild in VS:

logfile_editor snippet when crash occurs:
...
[Edit] Msg: Initializing editor plugins...
[Edit] Msg: CamView...
[Edit] Msg: DefaultOpenTKEditorBackend...
[Edit] Msg: EditorBase...
[Edit] Msg: HelpAdvisor...
[Edit] Msg: LogView...
[Edit] Msg: ObjectInspector...
[Edit] Msg: PackageManagerFrontend...
[Edit] Msg: ProjectView...
[Edit] Msg: SceneView...
[Core] Msg: Assembly loaded: FarseerDuality
[Core] Msg: Loading Resource 'Data\TestScene.Scene.res'
[Core] Msg: Assembly loaded: PositionWebsocket
[Core] Msg: Assembly loaded: System.Runtime.Serialization
[Core] Msg: Assembly loaded: System.ServiceModel
[Core] Msg: Assembly loaded: SMDiagnostics
[Core] Msg: Assembly loaded: System.ServiceModel.Internals
[Core] Msg: Assembly loaded: Microsoft.VisualStudio.Diagnostics.ServiceModelSink
[Core] Msg: Assembly loaded: System.Web
[Core] Msg: Loading Resource 'Data\SpaceBg.Material.res'
[Core] Msg: Loading Resource 'Data\SpaceBg.Texture.res'
[Core] Msg: Loading Resource 'Data\SpaceBg.Pixmap.res'
[Core] Msg: Loading Resource 'Data\ShipOne.Material.res'
[Core] Msg: Loading Resource 'Data\ShipOne.Texture.res'
[Core] Msg: Loading Resource 'Data\ShipOne.Pixmap.res'
[Core] Msg: Loading Resource 'Data\TestScene.Scene.res'
[Core] Msg: Loading Resource 'Data\TestScene.Scene.res'
[Core] Msg: Assembly loaded: System.IO.Compression
[Core] Msg: Assembly loaded: Windows7.DesktopIntegration
[Edit] Msg: Saving data...
[Core] ERR: Error writing object: XmlException: The ':' character, hexadecimal value 0x3A, cannot be included in a name.
CallStack:
at System.Xml.XmlConvert.VerifyNCName(String name, ExceptionType exceptionType)
at System.Xml.Linq.XName..ctor(XNamespace ns, String localName)
at System.Xml.Linq.XNamespace.GetName(String localName)
at Duality.Serialization.XmlSerializer.CustomSerialIO.Serialize(XmlSerializer formatter, XElement element) in c:\projects\duality\Duality\Serialization\XmlSerializer.cs:line 30
at Duality.Serialization.XmlSerializer.WriteObjectBody(XElement element, Object obj, ObjectHeader header) in c:\projects\duality\Duality\Serialization\XmlSerializer.cs:line 158
at Duality.Serialization.XmlSerializer.WriteObjectData(XElement element, Object obj) in c:\projects\duality\Duality\Serialization\XmlSerializer.cs:line 141



logfile after reload of Dualitor:
...
[Edit] Msg: Deserializing layout: 'Duality.Editor.Plugins.ObjectInspector.ObjectInspector'
[Edit] Msg: Deserializing layout: 'Duality.Editor.Plugins.HelpAdvisor.HelpAdvisor'
[Edit] Msg: Deserializing layout: 'Duality.Editor.Plugins.LogView.LogView'
[Edit] Msg: Deserializing layout: 'Duality.Editor.Plugins.CamView.CamView'
[Edit] Msg: Loading editor user data...
[Edit] Msg: Initializing editor plugins...
[Edit] Msg: CamView...
[Edit] Msg: DefaultOpenTKEditorBackend...
[Edit] Msg: EditorBase...
[Edit] Msg: HelpAdvisor...
[Edit] Msg: LogView...
[Edit] Msg: ObjectInspector...
[Edit] Msg: PackageManagerFrontend...
[Edit] Msg: ProjectView...
[Edit] Msg: SceneView...
[Core] Msg: Assembly loaded: FarseerDuality
[Core] Msg: Assembly loaded: System.IO.Compression

No errors present...


Top
 Profile  
 
PostPosted: 2016/02/13, 22:49 
Site Admin
Site Admin
User avatar

Joined: 2013/05/11, 22:30
Posts: 1948
Location: Germany
Role: Professional
zanco wrote:
Any idea what would be causing this error? Is it possibly a [Serialization] issue? I wouldn't know where to begin...


A plugin reload at runtime consists of saving all data, clearing plugin-related caches, removing the old plugin, loading the new version of the plugin and then loading all data again. The error happens in the saving step, from what I can tell by the logs.

In order to find out what exactly is causing the serialization error, we'll have to find out what's the object that cannot be serialized.

  • Did you do implement any custom serialization methods?
  • Did you implement any custom DualityAppData or DualityUserData classes, or add a tag to any of them?
  • Did you implement any custom Resources?
  • Can you reproduce the same error when saving your test Scene? If yes, what objects are in there?
  • Does the error occur anywhere else, or at any other times?

For reference, this might be one of the last lines in Duality code before running into the error - though it seems like Release mode optimizations skew the picture a bit.

_________________
Blog | GitHub | Twitter (@Adams_Lair)


Top
 Profile  
 
PostPosted: 2016/02/13, 22:54 
Site Admin
Site Admin
User avatar

Joined: 2013/05/11, 22:30
Posts: 1948
Location: Germany
Role: Professional
Just glanced over your code above:

Code:
namespace Duality_
{
    [RequiredComponent(typeof(RigidBody))]
    public class Player : Component, ICmpUpdatable, ICmpInitializable
    {
        private WebSocketOperations wsOperations;
        private RigidBody body;
        private bool first = true;

        // ...
    }
}


It seems you're initializing WebSocketOperations every time as a runtime-only object. It also seems like that object contains references to other objects that are definitely not meant to be serialized. If you add a [DontSerialize] property before the wsOperations field, does that by any chance fix the error?

_________________
Blog | GitHub | Twitter (@Adams_Lair)


Top
 Profile  
 
PostPosted: 2016/02/13, 23:59 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
I'll try [DontSerialize]; I figured that may have something to do with it...

And some progress! It's important to note that since I'm using the Getting Started tutorial, all of the .OnUpdate code is ran in the Player object. This needs to be translated to a Game Controller object of some sort so it can render all of the objects in the world. For now I just drive my player around and ignore all of the other players in game (but the data to position them is passed to the client successfully!

TODO: handle client connection loss or closure better....

Without further ado:
Image


Current Code:
- Server
IPositionService.cs:
Code:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading.Tasks;

namespace XYZR
{
    [ServiceContract(CallbackContract = typeof (IPositionCallback))]
    public interface IPositionService
    {
        [OperationContract]
        bool EstablishConnection(Guid sessionId);

        [OperationContract(IsOneWay = true)]
        void SendInput(Guid sessionId, string inputValues);

        [OperationContract(IsOneWay = true)]
        Task FetchServerData(Guid sessionId);
    }

    [ServiceContract]
    public interface IPositionCallback
    {
        // These will be implemented on the Client.
        [OperationContract(IsOneWay = true)]
        Task SendPosition(Dictionary<Guid,SpacePosition> serverData);

        [OperationContract(IsOneWay = true)]
        Task SendMessage(ChatMessage message);
    }

    [DataContract]
    public class SpacePosition
    {
        [DataMember]
        public Guid ObjectId { get; set; }

        [DataMember]
        public int X { get; set; }

        [DataMember]
        public int Y { get; set; }

        [DataMember]
        public int R { get; set; }

        [DataMember]
        public string TestString { get; set; }
    }

    [DataContract]
    public class ChatMessage
    {
        [DataMember]
        public Guid SenderGuid { get; set; }

        [DataMember]
        public Guid RecieverGuid { get; set; }

        [DataMember]
        public string Message { get; set; }

        [DataMember]
        public ChatMessageTypeEnum MessageType { get; set; }

        [DataMember]
        public DateTime MessageDateTime { get; set; }
    }

    [DataContract(Name = "ChatMessageType")]
    public enum ChatMessageTypeEnum
    {
        [EnumMember]
        Private,
        [EnumMember]
        Public,
        [EnumMember]
        Global,
        [EnumMember]
        Sectoral,
        [EnumMember]
        Universal,
        [EnumMember]
        Administrative
    }
}


PositionService.svc.cs:
Code:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading.Tasks;

namespace XYZR
{

    // A note about Session IDs and player guids:
    // The Session ID should be totally private and never exposed to another player,
    // therefor a Dicitonary of active players with their Session IDs and their
    // entity Guid is kept to translate their provided session ID to their entity Guid.
    // TODO: translate all Session IDs to Guids appropriately, ensuring that input only provides Session IDs and output only provides entity Guids.

    // TODO: figure out the right settings for this Service Behavior.
    [ServiceBehavior(InstanceContextMode = [b]InstanceContextMode.Single[/b], ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
    public class PositionService : IPositionService
    {
        private Dictionary<Guid, SpacePosition> activeObjects = new Dictionary<Guid, SpacePosition>();
        private List<ChatMessage> chatMessages;
        private Dictionary<Guid, Guid> activePlayers = new Dictionary<Guid, Guid>();

        public bool EstablishConnection(Guid sessionId)
        {
            // authentication/authorization stuff here...

            // if Authenticate....
            if (true)
            {
                var PlayerGuid = GetPlayerGuidFromSessionId(sessionId);

                activePlayers.Add(sessionId, PlayerGuid);
                return true;
            }
            return false;
        }

        private Guid GetPlayerGuidFromSessionId(Guid sessionId)
        {
            // TODO: fetch the player's Guid from their session Guid
            return Guid.NewGuid();
        }

        private SpacePosition GetSpawnPosition(Guid sessionId)
        {
            // reach out to the DB or whatever to get the approrpiate spawn positon for injection into the world.
            return new SpacePosition() { ObjectId = sessionId, R = 0, X = 0, Y = 0 };
        }

        public async Task FetchServerData(Guid sessionId)
        {
            var callback = OperationContext.Current.GetCallbackChannel<IPositionCallback>();
            //var random = new Random();

            SpacePosition spawnPoint = GetSpawnPosition(sessionId);

            activeObjects.Add(sessionId, spawnPoint);

            while (((IChannel)callback).State == CommunicationState.Opened)
            {
                await callback.SendPosition(activeObjects);

                //if (chatMessages.Any())
                //{
                //    foreach (ChatMessage chatMessage in chatMessages)
                //    {
                //        // maybe we submit the chat message to logging here??? if we do, we do it async to not lock up the thread with a non-essential DB write.
                //        await callback.SendMessage(chatMessage);
                //        chatMessages.Remove(chatMessage);
                //    }
                //}

                await Task.Delay(100);
            }
        }

        public void SendInput(Guid sessionId, string inputValues)
        {
            // TODO: translate the Session ID to the appropriate enitty Guid
            activeObjects[sessionId].TestString = inputValues;

            switch (inputValues)
            {
                case "U":
                {
                    activeObjects[sessionId].Y++;
                    break;
                }
                case "D":
                {
                    activeObjects[sessionId].Y--;
                    break;
                }
                case "L":
                {
                    activeObjects[sessionId].X--;
                    break;
                }
                case "R":
                {
                    activeObjects[sessionId].X++;
                    break;
                }
                case "Q":
                {
                    activeObjects.Remove(sessionId);
                    activePlayers.Remove(sessionId);
                    break;
                }
                default:
                {
                    // Global Messages: Will flash to all people on current planet/station
                    //if (inputValues.StartsWith("GCHAT:"))
                    //{
                    //    chatMessages.Add(new ChatMessage() { MessageDateTime = DateTime.UtcNow, MessageType = ChatMessageTypeEnum.Public, SenderGuid = sessionId, Message = inputValues.Substring(6) });
                    //}

                    // Public Messages: Will flash to all people on the current session
                    if (inputValues.StartsWith("CHAT:"))
                    {
                        chatMessages.Add(new ChatMessage(){MessageDateTime = DateTime.UtcNow,MessageType = ChatMessageTypeEnum.Public, SenderGuid = sessionId, Message = inputValues.Substring(5)});
                    }

                    // Sectoral Messages: Will flash to all people in the current sector
                    //if (inputValues.StartsWith("SCHAT:"))
                    //{
                    //    chatMessages.Add(new ChatMessage() { MessageDateTime = DateTime.UtcNow, MessageType = ChatMessageTypeEnum.Public, SenderGuid = sessionId, Message = inputValues.Substring(6) });
                    //}

                    // Universal Messages: Will flash to everyone
                    //if (inputValues.StartsWith("UCHAT:"))
                    //{
                    //    chatMessages.Add(new ChatMessage() { MessageDateTime = DateTime.UtcNow, MessageType = ChatMessageTypeEnum.Public, SenderGuid = sessionId, Message = inputValues.Substring(6) });
                    //}

                    break;
                }
            }
        }
    }
}


- Client .dll code
WebSocketOperations.cs:
Code:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using PositionWebsocket.WebSocketRef;

namespace PositionWebsocket
{

    public class WebSocketOperations
    {
        private InstanceContext context;
        private PositionServiceClient client;

        public static SpacePosition playerLocation;
        public static Dictionary<Guid, SpacePosition> ServerData;
        public static string LocalMessage = string.Empty;
       
        private static bool Connected = false;
        private static bool Cancelled = false;
       
        public static List<string> ConsoleList = new List<string>();

        public static List<ChatMessage> ChatBuffer = new List<ChatMessage>();

        public static Guid SessionId;

        public WebSocketOperations(string serverAddress, Guid sessionId)
        {
            NetHttpBinding binding = new NetHttpBinding() {Name = "NetHttpBinding_IPositionService"};

            binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;

            //Specify the address to be used for the client.
            //EndpointAddress address =
            //    new EndpointAddress(String.Format("ws://{0}/PositionService.svc", serverAddress));

            EndpointAddress address;
            address = new EndpointAddress("http://localhost:26341/PositionService.svc");
            //address = new EndpointAddress("ws://dualitywshost.cloudapp.net/PositionService.svc");

            //http://localhost:26341/PositionService.svc
            //ws://dualitywshost.cloudapp.net/PositionService.svc

            context = new InstanceContext(new CallbackHandler());
            client = new WebSocketRef.PositionServiceClient(context, binding, address);

            SessionId = sessionId; //sessionId;
        }

        public void GoDir(string value)
        {
            if (!Cancelled || Connected)
                client.SendInput(SessionId, value);
        }

        public string GetLastMessage()
        {
            return LocalMessage;
        }

        public void GetServerData()
        {
            client.FetchServerDataAsync(SessionId);
        }

        public void OpenSocket()
        {
            if (!client.EstablishConnection(SessionId))
            {
                ConsoleList.Add("Connection Failed!");
                return;
            }

            Connected = true;
            Cancelled = false;

            ConsoleList.Add("Connection Established!");
        }

        public void CloseSocket()
        {
            // TODO: handle client closures better...
            client.SendInput(SessionId, "Q");
            Cancelled = false;
            //client.Close();
            Connected = false;
            ConsoleList.Add("Connection Closed!");
        }
    }

    class CallbackHandler : WebSocketRef.IPositionServiceCallback
    {
        public void SendPosition(Dictionary<Guid, SpacePosition> serverData)
        {
            WebSocketOperations.ServerData = serverData;
        }

        public void SendMessage(ChatMessage message)
        {
            WebSocketOperations.ChatBuffer.Add(message);
        }
    }
}


WebSocketRef - You need to update or create your references to the Web Service...

- Duality client code:
Player.cs:
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Duality;
using Duality.Components;
using Duality.Components.Physics;
using Duality.Input;

using PositionWebsocket;

namespace Duality_
{
    [RequiredComponent(typeof(RigidBody))]

    public class Player : Component, ICmpUpdatable, ICmpInitializable
    {

        private WebSocketOperations wsOperations;

        private RigidBody body;
        private Transform transform;

        private Guid sessionId;

        private bool first = true;

        void ICmpInitializable.OnInit(Component.InitContext context)
        {
            if (first)
            {
                body = this.GameObj.GetComponent<RigidBody>();
                transform = this.GameObj.GetComponent<Transform>();

                sessionId = Guid.NewGuid();

                wsOperations = new WebSocketOperations("",sessionId);

                wsOperations.OpenSocket();

                wsOperations.GetServerData();
            }

            first = false;
        }

        void ICmpInitializable.OnShutdown(Component.ShutdownContext context)
        {

        }

        void ICmpUpdatable.OnUpdate()
        {
            transform.MoveTo(new Vector3() {X = WebSocketOperations.ServerData[sessionId].X, Y = WebSocketOperations.ServerData[sessionId].Y});

            if (DualityApp.Keyboard[Key.Left])
            {
                //body.ApplyLocalForce(-0.1f);
                wsOperations.GoDir("L");
            }
            else if (DualityApp.Keyboard[Key.Right])
            {
                //body.ApplyLocalForce(0.1f);
                wsOperations.GoDir("R");
            }
            else
                //body.AngularVelocity -= body.AngularVelocity*0.1f*Time.TimeMult;

            if (DualityApp.Keyboard[Key.Up])
            {
                //body.ApplyLocalForce(Vector2.UnitY * -0.1f * body.Mass);
                wsOperations.GoDir("U");
            }
            else if (DualityApp.Keyboard[Key.Down])
            {
                //body.ApplyLocalForce(Vector2.UnitY * 0.1f * body.Mass);
                wsOperations.GoDir("D");
            }
        }
   }
}


Top
 Profile  
 
PostPosted: 2016/02/14, 09:20 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
Quote:
If you add a [DontSerialize] property before the wsOperations field, does that by any chance fix the error?


Works like a charm in sandbox and VS Start now, build/rebuild doesn't crash Dualitor! Thanks!

Code:
 public class Player : Component, ICmpUpdatable, ICmpInitializable
    {
        [b][DontSerialize][/b]
        private WebSocketOperations wsOperations;

        private RigidBody body;
        private Transform transform;

        private Guid sessionId;


Top
 Profile  
 
PostPosted: 2016/02/14, 10:26 
Forum Adept
Forum Adept

Joined: 2015/07/08, 16:41
Posts: 414
Role: Gamer
Nice work you've done here! I'm looking forward to seeing it evolve over time. It's a first step to getting working multiplayer in Duality.


Top
 Profile  
 
PostPosted: 2016/02/15, 00:24 
Novice Member
Novice Member

Joined: 2016/02/13, 08:58
Posts: 10
Role: Hobbyist
Thanks, there are some rough waters ahead tho...

Since the server will be the brain, I either need to figure out how to port dualitys libraries to the server to do collision detection calculations which has its own challenges or I need to find another solution (build or find a find a library to do physics...). To fit a true MMO model, an authentication service will need to be designed as well as a Web frontend for acct creation. This will need a DB, which I'm looking at Azure tables for att. The server is a WCF service so will need to be dynamic, loading scene content and maybe running multiple scenes under low load as well as having the ability to push scenes (and it's clients) to a different service. Finally, an extended set of methods needs to be added to the wcf server code to allow administratI've functions and monitoring. This will require a master client app to controlled all of the Web services. A second interface and service pair will be built for these administration methods, which can be left out as the client .dll service reference is generated and added as parent classes when the admin .dll is generated. That should keep malicious clients from being exposed to the administrative methods... the Administrative sessions with the server will be what spawns the logic/physics threads on the server, allowing all of the calculations to be ran from memory on the wcf service in Azure instead of interact with a DB where a webrole or webjob would apply the logic/physics. This will prevent a huge bottleneck and be very cheap to run on the cloud... a DB will be needed for saving status for crash recovery, transferring to another service on warp or service handover as well as inventory and pulling rendering (sprite and other) data for objects in game.

Also this will require the client to have a game controller class instead of a player class that will decipher the server data and render the changes to objects in the world. Handling dynamic player character creation will require nearly naked prefabs where server tells the client the sprites for the player characters. Some sort of HUD with a chat feature will need to be designed (the server and client code for chat is super easy...). A menu system will also need to be grafted in. The choice to stay space bound or to build some sort of in-dock walk around needs to be made; that's starting to sound like feature creep.


Just a short list of stuff to do xD

I'll make a burn down list soon.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ]  Go to page 1, 2  Next

All times are UTC + 1 hour [ DST ]


Who is online

Users browsing this forum: No registered users and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group