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?