Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Actions can now be (temporarily) disabled per action instead of action type #295

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions Demo/Assets/CrashKonijn/GOAP/Demos/Complex/Actions/EatAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using CrashKonijn.Goap.Demos.Complex.Goap;
using CrashKonijn.Goap.Demos.Complex.Interfaces;
using CrashKonijn.Goap.Runtime;
using UnityEngine;

namespace CrashKonijn.Goap.Demos.Complex.Actions
{
Expand All @@ -17,10 +16,8 @@ public void Inject(GoapInjector injector)
{
this.instanceHandler = injector.instanceHandler;
}

public override void Created()
{
}

public override void Created() { }

public override void Start(IMonoAgent agent, Data data)
{
Expand All @@ -32,7 +29,7 @@ public override bool IsValid(IActionReceiver agent, Data data)
{
if (data.Eatable == null)
return false;

return true;
}

Expand All @@ -47,18 +44,20 @@ public override IActionRunState Perform(IMonoAgent agent, Data data, IActionCont

if (data.Eatable.NutritionValue > 0)
return ActionRunState.Continue;

data.Inventory.Remove(data.Eatable);
this.instanceHandler.QueueForDestroy(data.Eatable);

return ActionRunState.Completed;
}

public override void End(IMonoAgent agent, Data data)
{
this.Disable(ActionDisabler.ForTime(5f));

if (data.Eatable == null)
return;

if (data.Eatable.NutritionValue > 0)
data.Inventory.Add(data.Eatable);
}
Expand All @@ -67,12 +66,12 @@ public class Data : IActionData
{
public ITarget Target { get; set; }
public IEatable Eatable { get; set; }

[GetComponent]
public ComplexInventoryBehaviour Inventory { get; set; }

[GetComponent]
public ComplexHungerBehaviour ComplexHunger { get; set; }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ namespace CrashKonijn.Goap.Demos.Simple.Behaviours
{
public class SimpleHungerBehaviour : MonoBehaviour
{
private AgentBehaviour agent;
private GoapActionProvider actionProvider;
public float hunger = 50;

[Button(nameof(EnablePickup))]
public int enableButton;

[Button(nameof(DisablePickup))]
public int disableButton;

private void Awake()
{
this.hunger = Random.Range(0, 100f);
this.agent = this.GetComponent<AgentBehaviour>();
this.actionProvider = this.GetComponent<GoapActionProvider>();
}

private void FixedUpdate()
Expand All @@ -29,12 +29,18 @@ private void FixedUpdate()

public void EnablePickup()
{
this.agent.EnableAction<PickupAppleAction>();
foreach (var pickupAppleAction in this.actionProvider.GetActions<PickupAppleAction>())
{
pickupAppleAction.Enable();
}
}

public void DisablePickup()
{
this.agent.DisableAction<PickupAppleAction>();
foreach (var pickupAppleAction in this.actionProvider.GetActions<PickupAppleAction>())
{
pickupAppleAction.Disable(ActionDisabler.ForTime(1f));
}
}
}
}
}
1 change: 1 addition & 0 deletions Demo/CrashKonijn.Agent.Runtime.csproj.DotSettings
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=_002E_002E_005Cpackage_005Cruntime_005Ccrashkonijn_002Eagent_002Eruntime_005Cdisablers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=_002E_002E_005Cpackage_005Cruntime_005Ccrashkonijn_002Eagent_002Eruntime_005Creferences/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
38 changes: 38 additions & 0 deletions Package/Documentation/Classes/Actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,44 @@ The action class defines the behavior of the action. It should be stateless sinc

The `IActionRunState` interface is a crucial component in the GOAP system. It defines the contract for action run states, which are responsible for determining the behavior of actions during their execution. These states decide when an action should be updated, stopped, performed, completed, or even resolved. They can also be used to 'pause' running an action, and come back later to continue it.

### Enabling/Disabling Actions
Each action can be enabled or disabled using the `action.Enable()` or `action.Disable(IActionDisabler)` methods. By default the following disablers are available:

{% code lineNumbers="true" %}
```csharp
public static class ActionDisabler
{
public static IActionDisabler Forever => new ForeverActionDisabler();
public static IActionDisabler ForTime(float time) => new ForTimeActionDisabler(time);
}
```
{% endcode %}

#### Examples

{% code lineNumbers="true" %}
```csharp
foreach (var pickupAppleAction in this.actionProvider.GetActions<PickupAppleAction>())
{
pickupAppleAction.Disable(ActionDisabler.Forever);
}
```
{% endcode %}

{% code lineNumbers="true" %}
```csharp
public class EatAction : GoapActionBase<EatAction.Data>
{
// Other methods omitted for brevity
public override void End(IMonoAgent agent, Data data)
{
// This will disable the action for 5 seconds
this.Disable(ActionDisabler.ForTime(5f));
}
}
```
{% endcode %}

#### Methods

- `void Update(IAgent agent, IActionContext context)`: Updates the state of the action based on the current context and agent state. This method is called every frame during the action's execution.
Expand Down
2 changes: 2 additions & 0 deletions Package/Runtime/CrashKonijn.Agent.Core/Interfaces/IAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,7 @@ public interface IAction

bool IsExecutable(IActionReceiver agent, bool conditionsMet);
bool IsEnabled(IActionReceiver agent);
void Enable();
void Disable(IActionDisabler disabler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CrashKonijn.Agent.Core
{
public interface IActionDisabler
{
bool IsDisabled(IAgent agent);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions Package/Runtime/CrashKonijn.Agent.Core/Interfaces/IAgent.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;

namespace CrashKonijn.Agent.Core
{
public interface IAgent : IActionReceiver
{
List<Type> DisabledActions { get; }
AgentState State { get; }
AgentMoveState MoveState { get; }

Expand All @@ -26,8 +24,6 @@ public interface IAgent : IActionReceiver

void CompleteAction(bool resolveAction = true);
void ResolveAction();
void EnableAction<TAction>() where TAction : IAction;
void DisableAction<TAction>() where TAction : IAction;

[Obsolete("Use GoapActionProvider.CurrentPlan.Goal instead")]
object CurrentGoal { get; set; }
Expand Down
23 changes: 21 additions & 2 deletions Package/Runtime/CrashKonijn.Agent.Runtime/ActionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public abstract class AgentActionBase<TActionData, TActionProperties>
where TActionData : IActionData, new()
where TActionProperties : class, IActionProperties, new()
{
private IActionDisabler disabler;

public IActionData GetData()
{
return this.CreateData();
Expand All @@ -29,11 +31,28 @@ public bool IsEnabled(IActionReceiver agent)
return this.IsEnabled(agent, agent.Injector);
}

public void Enable()
{
this.disabler = null;
}

public void Disable(IActionDisabler disabler)
{
this.disabler = disabler;
}

public virtual bool IsEnabled(IActionReceiver receiver, IComponentReference references)
{
if (receiver is IMonoAgent agent)
return !agent.DisabledActions.Contains(this.GetType());
if (this.disabler == null)
return true;

if (receiver is not IMonoAgent agent)
return true;

if (this.disabler.IsDisabled(agent))
return false;

this.Enable();
return true;
}

Expand Down
14 changes: 4 additions & 10 deletions Package/Runtime/CrashKonijn.Agent.Runtime/AgentBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using CrashKonijn.Agent.Core;
using UnityEngine;

Expand Down Expand Up @@ -30,7 +29,6 @@ public IActionProvider ActionProvider

public AgentState State { get; private set; } = AgentState.NoAction;
public AgentMoveState MoveState { get; private set; } = AgentMoveState.Idle;
public List<Type> DisabledActions { get; } = new();

[Obsolete("Use ActionState.Action instead.")]
public IAction CurrentAction => this.ActionState.Action;
Expand Down Expand Up @@ -232,22 +230,18 @@ private void ResetAction()
this.UpdateTarget();
}

[Obsolete("Enable actions from within the action itself, or disable using actionProvider.GetActions<TAction>.ForEach((action) => action.Enable(IActionDisabler))")]
public void EnableAction<TAction>()
where TAction : IAction
{
if (!this.DisabledActions.Contains(typeof(TAction)))
return;

this.DisabledActions.Remove(typeof(TAction));
throw new Exception("Enable actions from within the action itself, or disable using actionProvider.GetActions<TAction>.ForEach((action) => action.Enable(IActionDisabler))");
}

[Obsolete("Disable actions from within the action itself, or disable using actionProvider.GetActions<TAction>.ForEach((action) => action.Disable(IActionDisabler))")]
public void DisableAction<TAction>()
where TAction : IAction
{
if (this.DisabledActions.Contains(typeof(TAction)))
return;

this.DisabledActions.Add(typeof(TAction));
throw new Exception("Disable actions from within the action itself, or disable using actionProvider.GetActions<TAction>.ForEach((action) => action.Disable(IActionDisabler))");
}
}
}
3 changes: 3 additions & 0 deletions Package/Runtime/CrashKonijn.Agent.Runtime/Disablers.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using CrashKonijn.Agent.Core;

namespace CrashKonijn.Agent.Runtime
{
public static class ActionDisabler
{
public static IActionDisabler Forever => new ForeverActionDisabler();
public static IActionDisabler ForTime(float time) => new ForTimeActionDisabler(time);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CrashKonijn.Agent.Core;
using UnityEngine;

namespace CrashKonijn.Agent.Runtime
{
public class ForTimeActionDisabler : IActionDisabler
{
private readonly float enableAt;

public ForTimeActionDisabler(float time)
{
this.enableAt = Time.time + time;
}

public bool IsDisabled(IAgent agent)
{
return Time.time < this.enableAt;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CrashKonijn.Agent.Core;

namespace CrashKonijn.Agent.Runtime
{
public class ForeverActionDisabler : IActionDisabler
{
public bool IsDisabled(IAgent agent) => true;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ public interface IAgentType
void Unregister(IMonoGoapActionProvider actionProvider);
List<IConnectable> GetAllNodes();
List<IGoapAction> GetActions();
List<TAction> GetActions<TAction>() where TAction : IGoapAction;
List<IGoal> GetGoals();

TGoal ResolveGoal<TGoal>()
where TGoal : IGoal;

bool AllConditionsMet(IGoapActionProvider actionProvider, IGoapAction action);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using CrashKonijn.Agent.Core;
using UnityEngine;

Expand Down Expand Up @@ -53,6 +54,7 @@ public void RequestGoal<TGoal1, TGoal2, TGoal3, TGoal4, TGoal5>(bool endAction)
void ClearGoal();
void StopAction(bool resolveAction = true);
void SetDistanceMultiplierSpeed(float speed);
List<TAction> GetActions<TAction>() where TAction : IGoapAction;

#region Obsolete

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ private void ValidateSetup()
throw new GoapException($"There is no ActionReceiver assigned to the agent '{this.name}'! You're probably missing the ActionProvider on the AgentBehaviour.");
}

public List<TAction> GetActions<TAction>() where TAction : IGoapAction => this.AgentType.GetActions<TAction>();

#region Obsolete Methods

[Obsolete("Use CurrentPlan.Goal instead")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public bool AllConditionsMet(IGoapActionProvider actionProvider, IGoapAction act

public List<IConnectable> GetAllNodes() => this.actions.Cast<IConnectable>().Concat(this.goals).ToList();
public List<IGoapAction> GetActions() => this.actions;
public List<TAction> GetActions<TAction>() where TAction : IGoapAction => this.actions.OfType<TAction>().ToList();
public List<IGoal> GetGoals() => this.goals;
}
}
Loading