I didn’t like the idea of a child node controlling a parent node, it seems fundamentally incorrect from my understanding of node structures. I found another resource that really helped.
Separate control input from movement.
This video does it without calling it the Command Pattern. My error is resolved, and I can verify that my class references are passed properly. Here’s some basic code for future generations.
My node structure looks like this:
PlayerInput
>Player
>Sprite2D
>CollisionShape2D
Player.cs
public partial class Player : Character
{
[Export]
public float Speed {get; set;} = 300.0f;
[Export]
public float JumpVelocity = -400.0f;
public override void _Ready()
{
base._Ready();
GD.Print($"player obj ref {this}");
}
public override void _PhysicsProcess(double delta)
{
Vector2 velocity = Velocity;
// Add the gravity.
if (!IsOnFloor())
{
velocity += GetGravity() * (float)delta;
}
Velocity = velocity;
MoveAndSlide();
}
new public void Jump()
{
GD.Print("Inside Player.Jump()");
if (IsOnFloor())
{
Vector2 velocity = Velocity;
velocity.Y += JumpVelocity;
Velocity = velocity;
}
}
new public void Run()
{
GD.Print("Inside Player.Run()");
float horizontalInput = Input.GetAxis("moveLeft", "moveRight");
Vector2 _velocity = Velocity;
if(horizontalInput != 0)
{
_velocity.X = horizontalInput * _speed;
}
else
{
_velocity.X = 0;
}
Velocity = _velocity;
}
}
PlayerInput.cs
public partial class PlayerInput : Node
{
public Player _player;
public PlayerController _controller;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
_player = GetNode<Player>("Player");
GD.Print($"_player is {_player}");
ICommand runCommand = new RunCommand(_player);
ICommand jumpCommand = new JumpCommand(_player);
System.Collections.Generic.Dictionary<string, ICommand> commandDict = new()
{
{"run", runCommand},
{"jump", jumpCommand}
};
_controller = new PlayerController(commandDict);
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
if (Input.GetAxis("moveLeft", "moveRight") != 0)
{
_controller.Run();
}
if (Input.IsActionJustReleased("moveLeft") || Input.IsActionJustReleased("moveRight"))
{
_controller.Run();
}
if (Input.IsActionJustPressed("jump"))
{
_controller.Jump();
}
}
}
There’s a couple more parts to make this work as I have it written (and as I understand proper Command Pattern structure)
A basic interface called ICommand.cs
public interface ICommand
{
void Execute();
}
A set of commands that use the interface called PlayerCommands.cs
class JumpCommand : ICommand
{
Player _player;
public JumpCommand(Player player)
{
_player = player;
}
public void Execute()
{
_player.Jump();
}
}
class RunCommand : ICommand
{
Player _player;
public RunCommand(Player player)
{
_player = player;
}
public void Execute()
{
_player.Run();
}
}
and an invoker called PlayerController.cs
public class PlayerController
{
private Dictionary<string, ICommand> _commandDict;
public PlayerController(Dictionary<string, ICommand> commandDict)
{
_commandDict = commandDict;
}
public void Run()
{
_commandDict["run"].Execute();
}
public void Jump()
{
_commandDict["jump"].Execute();
}
}
I need to go through and fine-tune this code, but this all works together. I hope it helps someone out there.