initial commit: canny cat basic movement, bouncing, some gridmap tiles for levels

This commit is contained in:
Haze Weathers 2025-02-22 16:48:31 -05:00
commit e1b43c8bc5
120 changed files with 5785 additions and 0 deletions

View file

@ -0,0 +1,66 @@

// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using System;
using Godot;
/// <summary>
/// Wrapper around the compound state node.
/// </summary>
public class CompoundState : StateChartState
{
/// <summary>
/// Called when a child state is entered.
/// </summary>
public event Action ChildStateEntered
{
add => Wrapped.Connect(SignalName.ChildStateEntered, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.ChildStateEntered, Callable.From(value));
}
/// <summary>
/// Called when a child state is exited.
/// </summary>
public event Action ChildStateExited
{
add => Wrapped.Connect(SignalName.ChildStateExited, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.ChildStateExited, Callable.From(value));
}
private CompoundState(Node wrapped) : base(wrapped)
{
}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a compound state. The wrapper object can then be used to interact
/// with the compound state chart from C#.
/// </summary>
/// <param name="state">the node that is the state</param>
/// <returns>a State wrapper.</returns>
/// <throws>ArgumentException if the node is not a state.</throws>
public new static CompoundState Of(Node state)
{
if (state.GetScript().As<Script>() is not GDScript gdScript ||
!gdScript.ResourcePath.EndsWith("compound_state.gd"))
{
throw new ArgumentException("Given node is not a compound state.");
}
return new CompoundState(state);
}
public new class SignalName : StateChartState.SignalName
{
/// <see cref="CompoundState.ChildStateEntered"/>
public static readonly StringName ChildStateEntered = "child_state_entered";
/// <see cref="CompoundState.ChildStateExited"/>
public static readonly StringName ChildStateExited = "child_state_exited";
}
}
}

View file

@ -0,0 +1,49 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
/// <summary>
/// Base class for all wrapper classes. Provides some common functionality. Not to be used directly.
/// </summary>
public abstract class NodeWrapper
{
/// <summary>
/// The wrapped node.
/// </summary>
protected readonly Node Wrapped;
protected NodeWrapper(Node wrapped)
{
Wrapped = wrapped;
}
/// <summary>
/// Allows to connect to signals on the wrapped node.
/// </summary>
/// <param name="signal"></param>
/// <param name="method"></param>
/// <param name="flags"></param>
public Error Connect(StringName signal, Callable method, uint flags = 0u)
{
return Wrapped.Connect(signal, method, flags);
}
/// <summary>
/// Allows to call methods on the wrapped node deferred.
/// </summary>
public Variant CallDeferred(string method, params Variant[] args)
{
return Wrapped.CallDeferred(method, args);
}
/// <summary>
/// Allows to call methods on the wrapped node.
/// </summary>
public Variant Call(string method, params Variant[] args)
{
return Wrapped.Call(method, args);
}
}
}

View file

@ -0,0 +1,129 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
using System;
/// <summary>
/// Wrapper around the GDScript state chart node. Allows interacting with the state chart.
/// </summary>
public class StateChart : NodeWrapper
{
/// <summary>
/// Emitted when the state chart receives an event. This will be
/// emitted no matter which state is currently active and can be
/// useful to trigger additional logic elsewhere in the game
/// without having to create a custom event bus. It is also used
/// by the state chart debugger. Note that this will emit the
/// events in the order in which they are processed, which may
/// be different from the order in which they were received. This is
/// because the state chart will always finish processing one event
/// fully before processing the next. If an event is received
/// while another is still processing, it will be enqueued.
/// </summary>
public event Action<StringName> EventReceived
{
add => Wrapped.Connect(SignalName.EventReceived, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.EventReceived, Callable.From(value));
}
protected StateChart(Node wrapped) : base(wrapped)
{
}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a state chart. The wrapper object can then be used to interact
/// with the state chart from C#.
/// </summary>
/// <param name="stateChart">the node that is the state chart</param>
/// <returns>a StateChart wrapper.</returns>
/// <throws>ArgumentException if the node is not a state chart.</throws>
public static StateChart Of(Node stateChart)
{
if (stateChart.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("state_chart.gd"))
{
throw new ArgumentException("Given node is not a state chart.");
}
return new StateChart(stateChart);
}
/// <summary>
/// Sends an event to the state chart node.
/// </summary>
/// <param name="eventName">the name of the event to send</param>
public void SendEvent(string eventName)
{
Call(MethodName.SendEvent, eventName);
}
/// <summary>
/// Sets an expression property on the state chart node for later use with expression guards.
/// </summary>
/// <param name="name">the name of the property to set. This is case sensitive.</param>
/// <param name="value">the value to set the property to.</param>
public void SetExpressionProperty(string name, Variant value)
{
Call(MethodName.SetExpressionProperty, name, value);
}
/// <summary>
/// Returns the value of an expression property on the state chart node.
/// </summary>
/// <param name="name">the name of the proeprty to read. This is case sensitive. </param>
/// <param name="defaultValue">the default value to be returned if no such property exists</param>
/// <returns>the value of the property</returns>
public T GetExpressionProperty<[MustBeVariant]T>(string name, T defaultValue = default)
{
return Call(MethodName.GetExpressionProperty, name, Variant.From(defaultValue)).As<T>();
}
/// <summary>
/// Steps the state chart node. This will invoke all <code>state_stepped</code> signals on the
/// currently active states in the state charts. See the "Stepping Mode" section of the manual
/// for more details.
/// </summary>
public void Step()
{
Call(MethodName.Step);
}
public class SignalName : Node.SignalName
{
/// <see cref="StateChart.EventReceived"/>
///
/// </summary>
public static readonly StringName EventReceived = "event_received";
}
public new class MethodName : Node.MethodName
{
/// <summary>
/// Sends an event to the state chart node.
/// </summary>
public static readonly StringName SendEvent = "send_event";
/// <summary>
/// Sets an expression property on the state chart node for later use with expression guards.
/// </summary>
public static readonly StringName SetExpressionProperty = "set_expression_property";
/// <summary>
/// Returns the value of an expression property on the state chart node.
/// </summary>
public static readonly StringName GetExpressionProperty = "get_expression_property";
/// <summary>
/// Steps the state chart node. This will invoke all <code>state_stepped</code> signals on the
/// currently active states in the state charts. See the "Stepping Mode" section of the manual
/// for more details.
/// </summary>
public static readonly StringName Step = "step";
}
}
}

View file

@ -0,0 +1,65 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
using System;
/// <summary>
/// Wrapper around the state chart debugger node.
/// </summary>
public class StateChartDebugger : NodeWrapper
{
private StateChartDebugger(Node wrapped) : base(wrapped) {}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a state chart debugger. The wrapper object can then be used to interact
/// with the state chart debugger from C#.
/// </summary>
/// <param name="stateChartDebugger">the node that is the state chart debugger</param>
/// <returns>a StateChartDebugger wrapper.</returns>
/// <throws>ArgumentException if the node is not a state chart debugger.</throws>
public static StateChartDebugger Of(Node stateChartDebugger)
{
if (stateChartDebugger.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("state_chart_debugger.gd"))
{
throw new ArgumentException("Given node is not a state chart debugger.");
}
return new StateChartDebugger(stateChartDebugger);
}
/// <summary>
/// Sets the node that the state chart debugger should debug.
/// </summary>
/// <param name="node">the the node that should be debugged. Can be a state chart or any
/// node above a state chart. The debugger will automatically pick the first state chart
/// node below the given one.</param>
public void DebugNode(Node node)
{
Call(MethodName.DebugNode, node);
}
/// <summary>
/// Adds a history entry to the history output.
/// </summary>
/// <param name="text">the text to add</param>
public void AddHistoryEntry(string text)
{
Call(MethodName.AddHistoryEntry, text);
}
public new class MethodName : Node.MethodName
{
/// <summary>
/// Sets the node that the state chart debugger should debug.
/// </summary>
public static readonly string DebugNode = "debug_node";
/// <summary>
/// Adds a history entry to the history output.
/// </summary>
public static readonly string AddHistoryEntry = "add_history_entry";
}
}
}

View file

@ -0,0 +1,156 @@
// ReSharper disable once CheckNamespace
namespace GodotStateCharts
{
using Godot;
using System;
/// <summary>
/// A wrapper around the state node that allows interacting with it from C#.
/// </summary>
public class StateChartState : NodeWrapper
{
/// <summary>
/// Called when the state is entered.
/// </summary>
public event Action StateEntered
{
add => Wrapped.Connect(SignalName.StateEntered, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateEntered, Callable.From(value));
}
/// <summary>
/// Called when the state is exited.
/// </summary>
public event Action StateExited
{
add => Wrapped.Connect(SignalName.StateExited, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateExited, Callable.From(value));
}
/// <summary>
/// Called when the state receives an event. Only called if the state is active.
/// </summary>
public event Action<StringName> EventReceived
{
add => Wrapped.Connect(SignalName.EventReceived, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.EventReceived, Callable.From(value));
}
/// <summary>
/// Called when the state is processing.
/// </summary>
public event Action<float> StateProcessing
{
add => Wrapped.Connect(SignalName.StateProcessing, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateProcessing, Callable.From(value));
}
/// <summary>
/// Called when the state is physics processing.
/// </summary>
public event Action<float> StatePhysicsProcessing
{
add => Wrapped.Connect(SignalName.StatePhysicsProcessing, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StatePhysicsProcessing, Callable.From(value));
}
/// <summary>
/// Called when the state chart <code>Step</code> function is called.
/// </summary>
public event Action StateStepped
{
add => Wrapped.Connect(SignalName.StateStepped, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateStepped, Callable.From(value));
}
/// <summary>
/// Called when the state is receiving input.
/// </summary>
public event Action<InputEvent> StateInput
{
add => Wrapped.Connect(SignalName.StateInput, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateInput, Callable.From(value));
}
/// <summary>
/// Called when the state is receiving unhandled input.
/// </summary>
public event Action<InputEvent> StateUnhandledInput
{
add => Wrapped.Connect(SignalName.StateUnhandledInput, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.StateUnhandledInput, Callable.From(value));
}
/// <summary>
/// Called every frame while a delayed transition is pending for this state.
/// Returns the initial delay and the remaining delay of the transition.
/// </summary>
public event Action<float,float> TransitionPending
{
add => Wrapped.Connect(SignalName.TransitionPending, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.TransitionPending, Callable.From(value));
}
protected StateChartState(Node wrapped) : base(wrapped) {}
/// <summary>
/// Creates a wrapper object around the given node and verifies that the node
/// is actually a state. The wrapper object can then be used to interact
/// with the state chart from C#.
/// </summary>
/// <param name="state">the node that is the state</param>
/// <returns>a State wrapper.</returns>
/// <throws>ArgumentException if the node is not a state.</throws>
public static StateChartState Of(Node state)
{
if (state.GetScript().As<Script>() is not GDScript gdScript ||
!gdScript.ResourcePath.EndsWith("state.gd"))
{
throw new ArgumentException("Given node is not a state.");
}
return new StateChartState(state);
}
/// <summary>
/// Returns true if this state is currently active.
/// </summary>
public bool Active => Wrapped.Get("active").As<bool>();
public class SignalName : Godot.Node.SignalName
{
/// <see cref="StateChartState.StateEntered"/>
public static readonly StringName StateEntered = "state_entered";
/// <see cref="StateChartState.StateExited"/>
public static readonly StringName StateExited = "state_exited";
/// <see cref="StateChartState.EventReceived"/>
public static readonly StringName EventReceived = "event_received";
/// <see cref="StateChartState.StateProcessing"/>
public static readonly StringName StateProcessing = "state_processing";
/// <see cref="StateChartState.StatePhysicsProcessing"/>
public static readonly StringName StatePhysicsProcessing = "state_physics_processing";
/// <see cref="StateChartState.StateStepped"/>
public static readonly StringName StateStepped = "state_stepped";
/// <see cref="StateChartState.StateInput"/>
public static readonly StringName StateInput = "state_input";
/// <see cref="StateChartState.StateUnhandledInput"/>
public static readonly StringName StateUnhandledInput = "state_unhandled_input";
/// <see cref="StateChartState.TransitionPending"/>
public static readonly StringName TransitionPending = "transition_pending";
}
}
}

View file

@ -0,0 +1,53 @@
using System;
namespace GodotStateCharts
{
using Godot;
/// <summary>
/// A transition between two states.
/// </summary>
public class Transition : NodeWrapper {
/// <summary>
/// Called when the transition is taken.
/// </summary>
public event Action Taken {
add => Wrapped.Connect(SignalName.Taken, Callable.From(value));
remove => Wrapped.Disconnect(SignalName.Taken, Callable.From(value));
}
private Transition(Node transition) : base(transition) {}
public static Transition Of(Node transition) {
if (transition.GetScript().As<Script>() is not GDScript gdScript
|| !gdScript.ResourcePath.EndsWith("transition.gd"))
{
throw new ArgumentException("Given node is not a transition.");
}
return new Transition(transition);
}
/// <summary>
/// Takes the transition. The transition will be taken immediately by
/// default, even if it has a delay. If you want to wait for the delay
/// to pass, you can set the immediately parameter to false.
/// </summary>
public void Take(bool immediately = true) {
Call(MethodName.Take, immediately);
}
public class SignalName : Godot.Node.SignalName
{
/// <see cref="Transition.Taken"/>
public static readonly StringName Taken = "taken";
}
public class MethodName : Godot.Node.MethodName
{
/// <see cref="Transition.Take"/>
public static readonly StringName Take = "take";
}
}
}