Quantum 103 - Player Character
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 theConfig
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.
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
.
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
.
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.
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.