This document is about: QUANTUM 3
SWITCH TO

This page is a work in progress and could be pending updates.

Quantum 103 - Player Character


Available in the Gaming Circle and Industries Circle
Circle

Overview

Quantum 103 introduces a character that moves using a character controller. It also provides instructions for getting started with writing gameplay code in Quantum.

Character Entity setup

Create a new character controller entity in the Game scene (Quantum > 3d > Character Controller Entity). Name it PlayerCharacter and set the transform of the object to (2, 2, 0).

Notice that this entity comes with the PhysicsCollider3D enabled and a QPrototypeCharacterController3D component added in the EntityPrototype MonoBehaviour.

  • The QPrototypeCharacterController3D controls the movements of the character. The config field provides a configuration to tweak the behavior of the character controller. The default configuration can be found in QuantumUser/Resources/QuantumDefaultConfigs/DefaultConfigKCC3D. For this tutorial the default configuration will be used so drag it into the Config field of the character controller component.
  • Unlike the Unity CharacterController, character controllers in Quantum don't act as colliders. The PhysicsCollider3D component acts as the collider of the entity and matches the size of the visual sphere.
playercharacter inspector

Often it is required to give entities access to data that is shared between entities and unlike component data not specific a single entity. In Quantum this is done via asset config files. The config files can either be created by code or defined on ScriptableObjects in Unity. The character controller has a config file which stores information about how the character controller moves and interacts.

Press play. As you can see the entity does not move yet and is not affected by gravity. A code implementation is needed to make the character controller work.

Gameplay Code

Navigate to Assets/QuantumUser/Simulation in the Project window. This is the location for all gameplay code. Right-click on the Simulation folder and select Create > Folder and name the folder Platformer.

Now right-click the newly created Platformer folder. Choose Create > C# Script and name it MovementSystem.cs.

add a new directory
Add a new directory and MovementSystem.

Double-click the MovementSystem.cs to open it in your IDE.

Once the file is open remove all code and replace it with:

C#

namespace Quantum
{
    public unsafe class MovementSystem
    {
    
    }
}

The namespace is optional, and another name can be used. When not using the Quantum namespace add using Quantum; to the top.

The unsafe keyword allows for use of unsafe code such as pointers in the system.

The most common pattern in ECS to write gameplay code is to have a system iterate over a collection of entities which contain a set of components. With the Quantum ECS this can be achieved using a system main thread filter.

First add a filter struct to the MovementSystem class:

C#

public struct Filter
{
    public EntityRef Entity;
    public CharacterController3D* CharacterController;
}

Each filter must always contain an EntityRef field and any number of component pointer fields to filter for. Always use pointers for the component types in the filter.

To add the filter have the system inherit from the SystemMainThreadFilter<MovementSystem.Filter> class:

C#

public unsafe class MovementSystem : SystemMainThreadFilter<MovementSystem.Filter>

Add an override of the abstract Update function with the following code:

C#

public override void Update(Frame f, ref Filter filter)
{
    // note: pointer property access via -> instead of .
    filter.CharacterController->Move(f, filter.Entity, default);
}

The update function runs once on each frame for each entity that has all the components in the filter. The frame parameter represents the current frame on which the system runs and gives access to the complete game state for that specific frame.

This code simply calls the move function of the character controller and passes the default zero vector into it. This means the character controller does not attempt to move in any direction, but the entity will still be affected by gravity.

However, this code will not run yet. For a system to run it needs to be registered in the SystemSetup.User.cs file. This file can be found in the QuantumUser/Simulation folder in Unity. Double-click it to open the file and add the following line to the AddSystemsUser function:

C#

// user systems go here
systems.Add(new MovementSystem());

The systems in the SystemSetup are executed in the exact order in which they are listed in.

Return to the Unity Editor and enter play mode. The character sphere is now affected by KCC gravity, but it does not react yet to keyboard inputs.

Input

Right-click the Platformer folder. Choose Create > Quantum > Qtn. Name the file Input.

create the input.qtn
Create the Input.qtn.

Qtn files are Quantum specific files using the Quantum Domain Specific Language (DSL). The syntax of the DSL is similar to C#. The DSL is used to define the entity component data and the input data. The gameplay systems are in C#.

Double-click the input file to open it in the IDE then add the following to it:

C#

input
{
    button Jump;
    FPVector2 Direction;
}

The input is a special keyword in Quantum. Quantum only sends inputs, and not the game state, over the network. The Input defined here is the data that will be sent over the network and used by the gameplay simulation.

Note: Input is sent every tick and is used for inputs that change frequently and affect the real time gameplay. Examples are movement and button presses. For irregular rare inputs, use commands instead.

The FPVector2 type is the Quantum equivalent to the Unity Vector2 type. Quantum uses fixed point (FP) instead of floats to guarantee that the simulation is deterministic across devices. The direction vector will be filled with the horizontal and vertical input axis later.

The jump button will be used to make the player jump. The button type is a Quantum specific type and should be used for all button inputs. Do not use booleans for button inputs. They consume more bandwidth. A button is coded into a single bit through the network.

Note: To add syntax highlighting support for DSL files to your IDE, follow the guide here.

Updating the Movement System

Return to Unity so that the project compiles. This runs the Quantum code generation that makes the Input from the .qtn file available in c# scripts.

Now that input is available open the MovementSystem and replace the code in the Update function with the following code.

C#

public override void Update(Frame f, ref Filter filter)
{
    // gets the input for player 0
    var input = *f.GetPlayerInput(0);

    if (input.Jump.WasPressed)
    {
        filter.CharacterController->Jump(f);
    }

    filter.CharacterController->Move(f, filter.Entity, input.Direction.XOY);
}

Connecting Unity Inputs

Create a new UnityInput script in the QuantumUser/View folder. The View folder is for code that runs on the Unity side of Quantum such as collecting input events from Unity and passing them into the Quantum simulation.

Open the script in the Unity project. This script is responsible for collecting Unity inputs and passing them into the Quantum engine. Replace the code in the script with the following:

C#

using Photon.Deterministic;
using Quantum;
using UnityEngine;

public class UnityInput : MonoBehaviour {
  private void OnEnable() {
    QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback));
  }

  public void PollInput(CallbackPollInput callback) {
    Quantum.Input input = new Quantum.Input();

    // Note: Use GetButton() instead of GetButtonDown/Up. Quantum calculates up/down internally.
    input.Jump = UnityEngine.Input.GetButton("Jump");

    var x = UnityEngine.Input.GetAxis("Horizontal");
    var y = UnityEngine.Input.GetAxis("Vertical");

    // Input that is passed into the simulation needs to be deterministic that's why it's converted to FPVector2.
    input.Direction = new Vector2(x, y).ToFPVector2();

    callback.SetInput(input, DeterministicInputFlags.Repeatable);
  }
}

Return to Unity and go the QuantumLocalInput object in the scene. Remove the DebugInput script and add the new UnityInput script instead.

Next open the QuantumDebugRunner GameObject inspector. Under Local Players add an entry to represent a player. This will have Quantum collect input for the player 0. Currently, the 0 is hard coded into SystemSetup and the player is part of the scene. Later on this will be replaced with a dynamically spawned player entity for each player.

add the player
Add the player.

The player nickname and avatar can be used to pass more information about the player into the simulation.

Now when entering in play mode again the character can move using the WASD keys and jump using the space key.

player movement in unity editor
Back to top