Featured image of post Tabletop Game Pt3

Tabletop Game Pt3

This post explain how to read the dice faces with Marker3D

To read the number on the dice faces I will use Marker3D node. I will assign a number to each marker, then I will read the assigned number from a marker that is facing upward.

Here is the scene for the marker. I named it FaceMarker.

Then, I assign a script to it. The script is simply to export a number, so it can be set from Godot interface.

using Godot;

public partial class FaceMarker : Marker3D
{
	[Export]
	public int Number;
}

Don’t forget to rebuild after that, so the attribute is displayed on Godot!

Now, I will attach 6 markers to each face, under a Node3D. Make sure that all the Y axis(green arc) are upfront.

I use Node3D instead of Node because Node3D will also rotate when the Dice is rotated, causing the marker to be rotated too. And Node is the opposite.

Here, the marker number 6 is rotated along the number 6 face:

Meanwhile, if I use Node as the parent, the markers won’t follow the face:

To read the number, I will add a field called faces in my Dice script, and set it with Faces children on _Ready cycle.

public partial class Dice : RigidBody3D {
    ...
    ...
    private FaceMarker[] faces = new FaceMarker[6];
    ...
    ...

    public override void _Ready() {
        ...
        ...
        Godot.Collections.Array<Node> _faces = GetNode("Faces").GetChildren();
		for (int i = 0; i < _faces.Count; i++)
		{
			faces[i] = (FaceMarker)_faces[i];
		}
    }
}

And here is the function to read the faces:

    public override void _IntegrateForces(PhysicsDirectBodyState3D state)
    {
		if (state.LinearVelocity == Vector3.Zero) {
			canRoll = true;
			ReadFace();
		}
    }

	private void ReadFace() {
		foreach (var face in faces)
		{
			if (Mathf.RoundToInt(face.GlobalTransform.Basis.Y.Y) == 1) {
				GD.Print(face.Number, " " , Name);
			}
		}
	}

Here, I only print the face number only if the face number Y basis is 1 if rounded relative to global transform. It is important to round it, because when facing upward, the Y basis may not exactly 1, but really close to it(~0.9).

To improve it, the total number of the dices should be able to be read by Dices node. I think, it is a good time to try using custom signal. The signal should emit when the dice velocity reached 0. And the Dices node should be capable to capture that signal, and when it does, read the number and add it.

First, I setup a signal in Dices node:

public partial class Dices : Node
{
	[Signal]
	public delegate void LandingEventHandler(Dice dice);
	....
}

I will need the dice parameter so I know which dice emit the signal to read its number.

Rebuild it, and the signal will appear on Godot interface. And then I connect the signal with _onLanding method.

public partial class Dices : Node
{
	[Signal]
	public delegate void LandingEventHandler(Dice dice);
	....

	private void _onLanding(Dice dice) {
		dice.ReadFace();
	}
}

When the dice is touching the ground the parent Node(Dices) will read its number.

Now, it is time to emit the signal. Here is the code that I added in Dice Node:

public partial class Dice : RigidBody3D
{
	...
	private Dices group;
	...

	public override void _Ready()
	{
		....
		group = GetNode<Dices>("..");
		...
	}

	public override void _IntegrateForces(PhysicsDirectBodyState3D state)
    {
		if (state.LinearVelocity == Vector3.Zero && state.Sleeping && !isFloating) {
			canRoll = true;
			group.EmitSignal(Dices.SignalName.Landing, this);
		}
    }
}

Here, I get the Dices node first and assign it to group. I also make the landing requirement stricter, before it was only state.LinearVelocity == Vector3.Zero. Now, I add another condition to check whether the physics have “stopped”, and whether the dice is not floating. And then I emit the group’s signal called Landing. I still manage canRoll field in the Dice rather than Dices, because I think it is more explicit this way.

Now, let’s improve the Landing signal. Currently, it is just simply read and print the Dice, not calculating the total. It will also read the dice when the roll is not initiated. Meanwhile, the number should be read only when a roll has been initiated, and should be printed only when all the dices have been rolled.

First thing first, the ReadFace method should be udpated to return the number instead of printing it.

	public int ReadFace() {
		foreach (var face in faces)
		{
			if (Mathf.RoundToInt(face.GlobalTransform.Basis.Y.Y) == 1) {
				return face.Number;
			}
		}
		return 0;
	}

Onto the next codes…

public partial class Dices : Node
{
	[Signal]
	public delegate void LandingEventHandler(Dice dice);
	private List<Dice> dices = new();
	private int totalNumber = 0;
	private bool rollInitiated = false;
	private bool canRoll = false;


	public override void _Ready()
	{
		foreach (var dice in GetChildren()) {
			dices.Add((Dice) dice);
		}
	}

	public override void _PhysicsProcess(double delta)
	{
		if (Input.IsActionJustPressed("ui_accept")) {
			rollInitiated = true;
			if (canRoll) {
				canRoll = false;
				totalNumber = 0;
				foreach (var dice in dices)
				{
					dice.StartTimer();
				}
			}
		}
	}

	private void _onLanding(Dice dice) {
		canRoll = dices.All(_dice=>_dice.canRoll);
		if (!rollInitiated) {
			return;
		}
		totalNumber += dice.ReadFace();
		if (canRoll) {
			GD.Print(totalNumber);
		}
	}
}

In the Dices class, I added two attributes rollInitiated and canRoll. rollInitiated will be switched to true when user press space. And canRoll will only be true if all dices has landed(dice.canRoll = true, I should named it better 🤦). And when the roll start, I reset canRoll to false, and totalNumber to zero immediately.

And, here is the result:

I may make the ground(table) texture next, and adding a token 🤔