Improbable Icon

Forums

How can I move the player?

unity-gdk

#1

I have a 2D game I want to make using Unity and the SpatialOS GDK. I followed the fps tutorial and read over the docs, but I’m still having a hard time wrapping my head around how the client worker sends requests and receives updates from the server worker for sync’ing the components, but specifically for movement.

I saw a suggestion for creating a client visual position component so the client can update before actually telling the server and having it verify if it was a legal move, so here’s what I’d like to do:

  1. User presses key to move, client updates character object’s transform and client’s visual position component
  2. client requests the server to update actual position based on the new visual position
  3. server verifies, updates server object’s transform and spatialOS position component if move was legal, and sends response to client

Schema definition for the VisualPosition:
component VisualPosition{
id = 100;
float x = 1;
float y = 2;
}

Player server behavior:
using System.Collections;
using System.Collections.Generic;
using Improbable.Gdk.Core;
using Improbable;
using Improbable.Gdk.GameObjectRepresentation;
using Improbable.Gdk.TransformSynchronization;
using UnityEngine;
using ClientComponents;
namespace PlayerServer {
public class PlayerServerBehavior : MonoBehaviour {
[Require] private VisualPosition.Requirable.Reader visualPositionReader;
[Require] private Position.Requirable.Writer serverPositionWriter;

    Vector3 previousPos;

    private void OnEnable() {
        previousPos = new Vector2(visualPositionReader.Data.X, visualPositionReader.Data.Y);
    }

    private void Update() {
        CheckVisualPosition();
    }

    private void CheckVisualPosition() {
        if(previousPos.x != visualPositionReader.Data.X || previousPos.y != visualPositionReader.Data.Y) {
            previousPos.x = visualPositionReader.Data.X;
            previousPos.y = visualPositionReader.Data.Y;
            serverPositionWriter.Send(new Improbable.Position.Update()); //says update doesn't exist for position
        }
    }
}

}

Client-Side Movement:
using Improbable;
using Improbable.Gdk.GameObjectRepresentation;
using Improbable.Gdk.TransformSynchronization;
using UnityEngine;
using ClientComponents;

namespace PlayerClient {
[WorkerType(“UnityClient”)]
public class PlayerClientMovement : MonoBehaviour {
[Require] private VisualPosition.Requirable.Writer visualPositionWriter;
[Require] private Position.Requirable.Reader serverPositionReader;

    private void OnEnable() {
        var update = new VisualPosition.Update();
        visualPositionWriter.Send(update);
    }
}

}
I don’t have it taking input from the client keyboard yet. I was wanting to immediately change the position once it’s enabled on the client and then go from there.

Here’s the entity template, almost exactly as it’s provided in the blank project, but with the visual component, and the transform sync components changed to server authority:
private static EntityTemplate CreatePlayerEntityTemplate(string workerId, Improbable.Vector3f position)
{
var clientAttribute = $“workerId:{workerId}”;
var serverAttribute = WorkerType;

        var visualPosition = ClientComponents.VisualPosition.Component.CreateSchemaComponentData(0f, 0f);

        var entityBuilder = EntityBuilder.Begin()
            .AddPosition(0, 0, 0, serverAttribute)
            .AddMetadata("Player", serverAttribute)
            .AddComponent(visualPosition, clientAttribute)
            .SetPersistence(false)
            .SetReadAcl(AllWorkerAttributes)
            .SetEntityAclComponentWriteAccess(serverAttribute)
            .AddPlayerLifecycleComponents(workerId, clientAttribute, serverAttribute)
            .AddTransformSynchronizationComponents(serverAttribute);

        return entityBuilder.Build();
    }

I don’t know if the spatialOS position component even needs to be updated manually like I’m trying to do or if that’s something that’s automatically taken care of through the transformsync components based on the gameobject’s transform. Any help would be great, thanks.


#4

Hi @b_rye! My first thought for this would be that you may not need to use the TransformSynchronization feature module if you’re looking to roll your own implementation with custom server-side validation and visuals.
I think if you remove that from the equation for now it may become a simpler problem to tackle (although looking into the source code for this module may also be beneficial for you).

I’m not sure why you’re seeing “update doesn’t exist for position” as it definitely does and it looks like your namespaces are in order.
The TransformSynchronization feature module would do the Improbable.Position updates for you but as I think you’d be best off without it for now, you would need to add these updates into your server-side code.

You would also need to ensure that updates you send contain the data for that update as currently the updates you’re sending are empty.

Please let me know if you need any further help getting through this!

- Ieuan


#5

Thanks, leuan! This is what my client code looks like now:

using Improbable.Gdk.Core;
using Improbable.Gdk.PlayerLifecycle;
using Improbable.Worker.CInterop;
[WorkerType(“UnityClient”)]
public class PlayerClientMovement : MonoBehaviour {
[Require] private VisualPosition.Requirable.Writer visualPositionWriter;

    private void OnEnable() {
        Vector2 newPos = new Vector2(5f, 5f);
        this.gameObject.GetComponent<Transform>().position = newPos;
        var update = new VisualPosition.Update { X = newPos.x, Y = newPos.y};
        visualPositionWriter.Send(update);
    }

}
Server Code:
using System.Collections;
using System.Collections.Generic;
using Improbable.Gdk.Core;
using Improbable;
using Improbable.Gdk.GameObjectRepresentation;
using UnityEngine;
using ClientComponents;

namespace PlayerServer {
[WorkerType(“UnityGameLogic”)]
public class PlayerServerBehavior : MonoBehaviour {
[Require] private VisualPosition.Requirable.Reader visualPositionReader;

    Vector3 previousPos;

    private void OnEnable() {
        previousPos = new Vector2(visualPositionReader.Data.X, visualPositionReader.Data.Y);
        visualPositionReader.ComponentUpdated += OnVisualPositionUpdated;
    }

    private void OnVisualPositionUpdated(VisualPosition.Update update) {
        UpdateServerTransform();
    }

    private void UpdateServerTransform() {
        this.gameObject.GetComponent<Transform>().position = new Vector2(visualPositionReader.Data.X, visualPositionReader.Data.Y);
    }
}

}
The only thing is, I don’t think it’s linking properly. The GDK is supposed to enable gameobjects once they’ve been successfully injected, right? I have my two workers as gameobjects in the scene like it comes out of the box, and I have a server and client player prefab saved in Assets/Resources/Prefabs/UnityGameLogic and Assets/Resources/Prefabs/UnityClient. I have one of each in the development scene of the blank project instantiated as well. They don’t enable when I run it in the editor, and when I enable them manually, it says the object reference is not set to an instance of an object - but I understand that because they’re not getting injected in the first place.

The tutorial says the GDK GameObject Creation package handles this - even the instantiation, so I assume my player objects don’t need to be in the scene until I hit play. I guess PlayerLifecycle instantiates those specific objects for me? For some reason though, even though the package comes with the blank project, it doesn't recognize the namespace in any of the scripts. Do you know why that might be? I tried restarting unity and double-checked the manifest. - Ignore that last part. I realized I needed to add it into the project’s .asmdef because the blank project doesn’t import it by default. I forgot I added that into the manifest.


#6

Hi @b_rye,

Could you confirm for me what you mean by the gameObjects being injected?

For the example spatialos / gdk-for-unity-blank-project in the UnityGameLogicConnector.cs you can see that we specify for the “Player” prefab to be associated with the Player entity.
This single prefab can have both the managed (server) and external (client) scripts attached to it which will be enabled and disabled individually based on which readers/writers are in each script.
The prefab does not need to be added to the scene, rather it will be added to the world at runtime once the entity is created.

Are you able to get your system working with one prefab as per the example, or do you require them to be split?

- Ieuan


#7

As I understand it, once the entity components are injected into the gameObjects, they’ll be enabled, but that only happens if the worker handling the gameObject, like my player object, has read or write access. So a player object on the client should only have the VisualPosition.Requirable.Writer component injected if it’s the player object controlled by that particular client, right? Otherwise the script that component is in, which handles player input, will not be enabled. I imagine that’s part of what’s going on behind the scenes in this statement in the entity template .AddPlayerLifecycleComponents(workerId, clientAttribute, serverAttribute) since it asks for the workerId.

I was able to get the player entity to appear in the Spatial Inspector and it also instantiated 2 player objects in the development scene, as expected. I was using separate folders with separate player objects for each worker like the FPS tutorial did with the health pickup, but I changed it to use a player object from the common folder as you suggested, and it still seems to be working up to that point. Here’s a screenshot:

On the Client player instance I have selected there, the client movement script should be enabled, right? I am assuming that’s not happening because the VisualPosition writer isn’t getting injected on the client. The same is happening for the reader on the server player object.


#8

Going back and looking at the example project in the GDK, I noticed none of the character scripts include [WorkerType(WorkerUtils.UnityGameLogic)] or [WorkerType(WorkerUtils.UnityClient)] above the monobehaviour classes. I thought that was required for all monobehaviours that the workers interact with directly. Is that not the case?


#9

Hi @b_rye, sorry for the delay, that looks to me like the issue!

Those WorkerType class attributes are not used in the GDK and I’ve just done a test on a personal project to confirm that adding them actually disables the scripts.
Would you be able to remove them and see if there’s any change or progress?

They were used in the SDK so there may have been some information you got from the docs that wasn’t meant for the GDK.

We no longer need them as we’re simplifying the ACLs with the new Runtime such that using readers and writers should allow you to clearly and fully control where certain scripts are run.

Hopefully this unblocks you!

- Ieuan


#10

I also had a thought, if you’re making a 2D game you might want to consider that currently SpatialOS loadbalancers operate on the XZ plane.
There are plans in motion to allow fully customised loadbalancing strategies that aren’t necessarily spatial in design, but until that is available, the existing strategies will attempt to divide up entity/component load to workers with the XZ in mind.

Two possible solutions I can think of to this are:

a). Develop your 2D game in Unity using the XZ planes.
b). Develop your 2D game in Unity using the XY planes, but before sending and after receiving positional updates to SpatialOS translate the positions so that SpatialOS is able to loadbalance as normal and you’re still able to develop using XY if that’s preferable to you.

// Example Writing
var spatialPositionUpdate = new Position.Update
{
	X = transform.position.X,
	Y = transform.position.Z,
	Z = transform.position.Y,
}
spatialPositionWriter.Send(spatialPositionUpdate);

// Example Receiving
var spatialPosition = spatialPositionReader.Data;
transform.position = new Vector3(
	spatialPosition.X,
	spatialPosition.Z,
	spatialPosition.Y
);

Another benefit to this is that using the inspector would then give you a much better idea of what’s going on in your game world! :slight_smile:


#11

Hey leuan,

That’s good to know! I removed the attributes and it’s still not enabling the scripts though. Hm… I’m using a workerutils file now like in the gdk project. I’ve commented out a lot of the systems for now. Here’s that:

namespace BlankProject {
public static class WorkerUtils {
    public const string UnityClient = "UnityClient";
    public const string UnityGameLogic = "UnityGameLogic";
    public const string AndroidClient = "AndroidClient";
    public const string iOSClient = "iOSClient";

    public static readonly List<string> AllWorkerAttributes =
        new List<string>
        {
            UnityGameLogic,
            UnityClient,
            AndroidClient,
            iOSClient
        };

    public static void AddClientSystems(World world) {
        //AddLifecycleSystems(world);
        //TransformSynchronizationHelper.AddClientSystems(world);
        PlayerLifecycleHelper.AddClientSystems(world);
        //GameObjectRepresentationHelper.AddSystems(world);
        GameObjectCreationHelper.EnableStandardGameObjectCreation(world);
        //world.GetOrCreateManager<ProcessColorChangeSystem>();
    }

    public static void AddGameLogicSystems(World world) {
        //AddLifecycleSystems(world);
        //TransformSynchronizationHelper.AddServerSystems(world);
        PlayerLifecycleHelper.AddServerSystems(world);
        //GameObjectRepresentationHelper.AddSystems(world);
        GameObjectCreationHelper.EnableStandardGameObjectCreation(world);
        //world.GetOrCreateManager<CubeMovementSystem>();
    }

}
}

Here’s my client connector (ignore the squib GameObject - that’s an object I was trying to link up without the player lifecycle).

namespace BlankProject{
public class UnityClientConnector : DefaultWorkerConnector
{
    public const string WorkerType = WorkerUtils.UnityClient;
    
    [SerializeField] private GameObject squib;

    private async void Start()
    {
        await Connect(WorkerType, new ForwardingDispatcher()).ConfigureAwait(false);
    }

    protected override void HandleWorkerConnectionEstablished()
    {
        WorkerUtils.AddClientSystems(Worker.World);
    }

    protected override string SelectDeploymentName(DeploymentList deployments)
    {
        return deployments.Deployments[0].DeploymentName;
    }
}
}

And here’s the GameLogic connector, pretty much the same as before but with workerutils added in:

namespace BlankProject{
public class UnityGameLogicConnector : DefaultWorkerConnector
{
    public const string WorkerType = WorkerUtils.UnityGameLogic;
    
    private static readonly List<string> AllWorkerAttributes =
        new List<string> { UnityClientConnector.WorkerType, WorkerType };
    
    private async void Start()
    {
        await Connect(WorkerType, new ForwardingDispatcher()).ConfigureAwait(false);
    }

    protected override void HandleWorkerConnectionEstablished()
    {
        Worker.World.GetOrCreateManager<MetricSendSystem>();
        WorkerUtils.AddGameLogicSystems(Worker.World);
        PlayerLifecycleConfig.CreatePlayerEntityTemplate = CreatePlayerEntityTemplate;
    }

    private static EntityTemplate CreatePlayerEntityTemplate(string workerId, Improbable.Vector3f position)
    {
        var clientAttribute = $"workerId:{workerId}";
        var serverAttribute = WorkerType;

        var visualPosition = ClientComponents.VisualPosition.Component.CreateSchemaComponentData(0f, 0f);

        var entityBuilder = EntityBuilder.Begin()
            .AddPosition(0, 0, 0, serverAttribute)
            .AddMetadata("Player", serverAttribute)
            .AddComponent(visualPosition, clientAttribute)
            .SetPersistence(false)
            .SetReadAcl(AllWorkerAttributes)
            .SetEntityAclComponentWriteAccess(serverAttribute)
            .AddPlayerLifecycleComponents(workerId, clientAttribute, serverAttribute);

        return entityBuilder.Build();
    }
}
}

I wonder if I’m assigning authority incorrectly? Persistence shouldn’t matter here since I’m not instantiating it from the snapshot, right? I know it’s provided by default, but am I maybe doing something wrong with the client attribute when I give it authority over my visual position? I see that the client attribute refers to a specific client in the entity template. Is it supposed to be the general “UnityClient” worker name instead? I understand why the playerlifecyclecomponent needs the worker id, but it also takes clientAttribute as input, which is just the worker id with the string “workerId:” added to the front of it. And I also see that the SetReadAcl method takes AllWorkerAttributes, which does just refer to the general worker names of “UnityGameLogic” and “UnityClient” which really has me wondering which version of the client attribute I need to use for components.

That’s also good to know about the XZ planes. I’ll definitely decide on one of those options - probably the second. Thanks.


#12

Okay, so I got it updating the VisualPosition properly, but now that I added the position update logic back in, I’m getting that issue I had before where it says update doesn’t exist for position.

I’m not sure exactly how I got the scripts enabling correctly on the player object, but I added in the metricsendsystem on both the client and server workers for one thing, so I guess that’s required.

Here’s a screenshot of the error in the serverbehavior:


#13

Hi @b_rye! Ieuan is on holidays, so I’ll be taking a look at your issues for now :slight_smile:

Could you please confirm whether you are intending to use the Improbable’s Position as it comes with the GDK or a Position defined/modified by yourself? If it is defined/modified by you, could you send over the Position’s schema definition please?

Secondly, could you please send over your Unity Editor log files (check this link out for advice on where to find them). This will help us identify whether there is anything else funky going on.

Thanks for you patience,
Ieva


#14

Hi Ieva,

I’m using the GDK’s position. The only unusual thing I thing I’m doing with it is taking leuan’s suggestion and setting the Position’s z-coordinate to the transform’s y position.

Here’s my log file. Let me know if that’s not what you need or if there’s anything I can do to condense it. I appreciate you guys being so on top of my issue!Editor.log (3.9 MB)


#15

Hi @b_rye!

In your Unity Editor logs I see the following errors:

Assets\BlankProject\Scripts\PlayerServerBehavior.cs(31,17): error CS0117: 'Position.Update' does not contain a definition for 'Z'
 
(Filename: Assets\BlankProject\Scripts\PlayerServerBehavior.cs Line: 31)

Assets\BlankProject\Scripts\PlayerServerBehavior.cs(30,17): error CS0117: 'Position.Update' does not contain a definition for 'X'
 
(Filename: Assets\BlankProject\Scripts\PlayerServerBehavior.cs Line: 30) 

Could you try wrapping the updates in a coordinate object like this:

var spatialPositionUpdate = new Position.Update {
    Coords = new Coordinates
    {
        X = visualPositionReader.Data.X,
        Z = visualPositionReader.Data.Z
    }
};

Hope this works, but do let us know if you keep experiencing issues!


#16

Thanks, Ieva. I guess I didn’t think there’d be a different issue in the editor so I didn’t even check that after seeing the error in VS. That’s a good lesson for me. So it did fix that error, but it’s still telling me Update doesn’t exist for Position, and the console is showing no errors now.


#17

Hey @b_rye! Have you tried running your worker or building it? If the Unity Editor is not showing any errors, it could just be Visual Studio being misleading.

Let me know how it goes,
Ieva


#18

You could also try restarting Visual Studio after Unity has compiled your code to see whether that clears the errors.


#19

It’s totally working now. Thanks so much! I didn’t realize I could run it with VS errors (I’m still fairly new to Unity). That’s such a leap forward for me. I appreciate the patience you guys had and hope I can contribute more down the line.


#20

Great to hear it’s working now! And I’m excited to see what you make with SpatialOS + Unity! Keep us posted with any questions, feedback and updates on your game! :slight_smile: