Skip to content

Using AI Tools to Create Actions

Many users like to use AI tools (ChatGPT, Claude, Gemini, etc.) to help generate new custom Actions.

AI and PlayMaker Versions

AI tools are already very good at generating PlayMaker 1–style actions, but PlayMaker 2 introduces an all-new Action API with a different variable system (Var/Ref), new execution patterns, new summary rules, and new update modes.

Without guidance, most AI tools will fall back to PM1 patterns and produce Actions that do not integrate correctly with PlayMaker 2.

The instruction file below teaches AI tools how the PlayMaker 2 Action system works so they can generate correct PM2 Actions.


Why you should use an AI instruction file

Large language models (LLMs) do not automatically know:

  • how PlayMaker 2’s Var/Ref variable system works
  • how to structure Actions correctly
  • how GetSummary placeholders work
  • how True/False (CheckXxx) Actions work
  • which UpdateModes exist
  • how events should be sent

Without guidance, most AI tools will produce code that compiles but does not integrate correctly with the PlayMaker 2 editor or runtime.

To solve this, we provide a minimal, AI-optimized instruction file.
When pasted into your AI tool, it trains the model to generate Actions that follow PM2 conventions.


How to use this with ChatGPT, Claude, Gemini, etc.

  1. Open your AI tool.
  2. Create a new “project”, “workspace” or “persistent instruction” (optional but recommended).
  3. Paste the entire instruction file (below) into:
  4. ChatGPT → Project Instructions
  5. Claude → Project instructions / memory
  6. Gemini → Custom Preferences / System Prompt
  7. Save the settings.
  8. Now ask the AI:

    “Create a PlayMaker 2 Action that does XYZ.”

  9. The generated action will now follow PM2 conventions automatically.

If you update to a newer version of PlayMaker 2, you can re-paste an updated instruction file here.


Copy-Paste Instruction File (for AI tools)

Click to expand:

Click to expand the AI-Only PlayMaker 2 Instruction File
# PlayMaker 2 - AI Instructions (Action Authoring)

Generate PlayMaker 2 actions that use the PlayMaker 2 API, not PlayMaker 1 patterns.

============================================================
1. Base Class, Namespace & Naming
============================================================

Add these using statements:
using System;
using HutongGames.PlayMaker;
using HutongGames.PlayMaker.Actions;
using UnityEngine;

Put custom actions in your own namespace, for example:
namespace MyGame.PlayMaker.Actions

Do not put end-user actions in HutongGames.PlayMaker.Actions.

All actions must inherit from BaseAction unless inheriting from
a specific PM2 base class such as BaseTrueFalseAction.

New action classes should normally be:
[Serializable]
public sealed class MyAction : BaseAction

Omit sealed only when intentionally creating a base class.

Use [SerializeField] private fields for new action parameters.
Public fields exist in older actions but should not be copied by default.

Private fields must use: _camelCase

Examples:
[SerializeField] private FloatVar _speed;
[SerializeField] private TransformVar _target;
[SerializeField, WriteOnly] private Vector3Ref _storeResult;

============================================================
2. Variable System
============================================================

Prefer Var types for read-only inputs:
- FloatVar, IntegerVar, BoolVar
- Vector2Var, Vector3Var, QuaternionVar
- ColorVar, LayerMaskVar
- GameObjectVar, TransformVar
- StringVar, StringListVar, FloatListVar, etc.
- EnumVar with [BaseType(typeof(SomeEnum))]

Use Ref types for outputs, mutable values, and action targets:
- FloatRef, IntegerRef, BoolRef, Vector3Ref, etc.

Var types let users provide constants or variables.
Ref types are required when the action writes to a variable,
mutates an existing value, or needs an explicit variable target.

All output fields must be [WriteOnly].
Optional outputs should also be [OptionalField] and must be
guarded with IsAssigned before writing.

Use [ActionTarget] on the primary value or object the action operates on.
This target may be a Ref if the action reads, writes, or mutates that variable.

Fields are required unless [OptionalField] is applied.

Use the most specific Var/Ref type:
- Prefer TransformVar over GameObjectVar if you need a Transform
- Prefer LayerMaskVar over int when representing layer masks

============================================================
3. Field Attributes
============================================================

Use these attributes when appropriate:

Class-level:
- [Serializable]
- [ActionCategory(Category.X)]
- [ActionDescription("...")]
- [HelpURL("...")]
- [ConvertibleGroup("GroupName")]

Field-level:
- [Tooltip("...")]
- [DefaultValue(...)]
- [VarRange(min,max)]
- [VarSlider(min,max)]
- [OptionalField]
- [WriteOnly]       // for outputs
- [ActionTarget]    // for the main target
- [OwnerDefaultValue]
- [AllowWriteConversion]
- [GlobalEvent]
- [DefaultName("...")]
- [BaseType(typeof(SomeEnum))] // for EnumVar
- [FormerlySerializedAs("OldFieldName")] // when renaming serialized fields

============================================================
4. Update Modes (AI Reference)
============================================================

UpdateMode is a flags enum. Valid names are:

None
EveryFrame
Update
FixedUpdate
LateUpdate
OnEventUpdate
InputEveryFrame       // obsolete; avoid in new actions
OnAnimatorMove
AllUpdates
Blocking
AllowFinish
IgnorePerSecond
UpdateEveryFrame      // Update | EveryFrame
FixedUpdateEveryFrame // FixedUpdate | EveryFrame

Most commonly used in actions:
Update, FixedUpdate, LateUpdate, EveryFrame,
UpdateEveryFrame, FixedUpdateEveryFrame.

BaseAction defaults to Update if DefaultUpdateMode is not overridden.

Use DefaultUpdateMode only when the action normally expects a specific mode.
Examples:
- Physics or Rigidbody movement: FixedUpdateEveryFrame
- Continuous realtime logic: UpdateEveryFrame
- One-shot actions: usually leave the default Update

Use RequiredUpdateModes only when the action cannot function without
specific flags. Do not use it casually in examples.

If an action uses PerSecond in Execute(), set:
public override bool CanUsePerSecond => true;

PerSecond equals deltaTime when the action runs every frame with
per-second timing enabled by the user; otherwise it equals 1.

If an action can run over time and complete, set:
public override bool CanFinish => true;

Then call Finish() when the action is done.
Do not call Finish() from ordinary one-shot actions.

============================================================
5. Execution Pattern
============================================================

Parameter validation:
public override bool CanExecute() =>
    CheckParameters(_field1, _field2, _field3);

Use CheckParameters for required inputs and required outputs that
must be assigned.

Do not pass optional fields to CheckParameters.
Do not use CheckParameters for inputs where null, empty, or unassigned
values are valid behavior, such as Is Null checks, creators,
initializers, and watcher-style actions. Use custom validation instead.

Use ErrorCheck() for editor-visible cross-field validation,
especially actions that require at least one output or event.

Core work:
public override void Execute()
{
    // main action logic
}

Events:
SendEvent(EventRef);
BroadcastEvent(EventRef); // global events only

Logging:
LogInfo("message");
LogWarning("message");
LogError("message");

Lifecycle:
- Use OnStart for setup or subscriptions.
- Use OnStop or OnStateExit for cleanup.
- Use OnStateEnter only for read-only prepass work.

Use Reset() only for special-case initialization.
Prefer [DefaultValue(...)] for defaults.

============================================================
6. GetSummary Rules (IMPORTANT)
============================================================

GetSummary() must return a template string using placeholders.

Placeholders must match the serialized field name EXACTLY,
including the leading underscore for private fields.

Examples:
Field: [SerializeField] private FloatVar _speed;
Placeholder: {_speed}

Field: [SerializeField] private Vector3Ref _storeResult;
Placeholder: {_storeResult}

Inherited public base fields use their actual names without underscores:
{TrueEvent}, {FalseEvent}, {StoreResult}

Do NOT invent new names. Do NOT remove underscores.
The placeholder must be {<exact-field-name>}.

Use "->" to indicate required outputs.
Prefer :output for optional outputs so the output arrow and field
appear only when assigned:
{_optionalOutput:output}

Boolean placeholders can use :option to show only when true:
{_useGravity:option}

Use :hide only when the summary reads naturally with and without the token.
Use {PerSecond} when the action exposes CanUsePerSecond and scales by PerSecond.

Examples:
"{_target} * {_speed} -> {_storeResult}"
"{_from} to {_to} {_path:output}"
"{_a} + {_b} -> {_result} {_useGravity:option} {PerSecond}"

Do not embed runtime results or resolved values in GetSummary.
Conditional static fragments based on action configuration are allowed
when they keep the summary readable.

============================================================
7. Canonical Action Example (Pattern Only)
============================================================

using System;
using HutongGames.PlayMaker;
using HutongGames.PlayMaker.Actions;
using UnityEngine;

namespace MyGame.PlayMaker.Actions
{
    [Serializable]
    [ActionCategory(Category.GameObject)]
    [ActionDescription("Example action using PerSecond and summary placeholders.")]
    public sealed class ExampleAction : BaseAction
    {
        public override bool CanUsePerSecond => true;
        public override UpdateMode DefaultUpdateMode => UpdateMode.UpdateEveryFrame;

        [ActionTarget]
        [Tooltip("Target Transform.")]
        [SerializeField] private TransformVar _target;

        [Tooltip("Speed multiplier." + Strings.PerSecondNote)]
        [SerializeField, DefaultValue(1f)] private FloatVar _speed;

        [Tooltip("Store the calculated result.")]
        [SerializeField, WriteOnly] private Vector3Ref _storeResult;

        [Tooltip("Optionally store the normalized result.")]
        [SerializeField, OptionalField, WriteOnly]
        private Vector3Ref _normalizedResult;

        public override bool CanExecute() =>
            CheckParameters(_target, _speed, _storeResult);

        public override void Execute()
        {
            var result = _target.Value.position * _speed.Value * PerSecond;
            _storeResult.Value = result;

            if (_normalizedResult.IsAssigned)
            {
                _normalizedResult.Value = result.normalized;
            }
        }

        public override string GetSummary() =>
            "{_target} * {_speed} -> {_storeResult} {_normalizedResult:output} {PerSecond}";
    }
}

============================================================
8. True/False (CheckXxx) Actions
============================================================

Any action that checks a condition and sends True/False events
or stores a Bool result should follow this pattern:

- Class name starts with: CheckXxx
- Inherits from: BaseTrueFalseAction

Must override:

protected override bool Test();
protected override string TrueSummary  { get; }
protected override string FalseSummary { get; }

TrueSummary and FalseSummary must use exact field-name placeholders:
"{_integer} == {_equalTo}"
"{_fromObject} distance to {_toObject} is {_check} {_distance}"

TrueSummary and FalseSummary should be clause fragments because
BaseTrueFalseAction assembles the final summary.

Do not redeclare TrueEvent, FalseEvent, or StoreResult.
The base class already handles:
- Sending TrueEvent / FalseEvent
- Optional BoolRef StoreResult
- Summary and validation pattern

---------------------------
Simple example:
---------------------------

[Serializable]
[ActionCategory(Category.Logic)]
[ActionDescription("Check if an integer is equal to a value.")]
public sealed class CheckIntEquals : BaseTrueFalseAction
{
    [ActionTarget]
    [Tooltip("The integer to check.")]
    [SerializeField] private IntegerRef _integer;

    [Tooltip("The value to compare to.")]
    [SerializeField] private IntegerVar _equalTo;

    protected override string TrueSummary  => "{_integer} == {_equalTo}";
    protected override string FalseSummary => "{_integer} != {_equalTo}";

    public override bool CanExecute() =>
        CheckParameters(_integer, _equalTo);

    protected override bool Test() =>
        _integer.Value == _equalTo.Value;
}

---------------------------
Complex example:
---------------------------

[Serializable]
[ActionCategory(Category.Logic)]
[ActionDescription("Check the distance between two objects.")]
public sealed class CheckDistance : BaseTrueFalseAction
{
    [Tooltip("The object to measure from.")]
    [SerializeField] private TransformVar _fromObject;

    [Tooltip("The object to measure to.")]
    [SerializeField] private TransformVar _toObject;

    [Tooltip("How to compare the computed distance.")]
    [SerializeField] private NumericComparisonOperation _check;

    [Tooltip("The distance to compare against.")]
    [SerializeField] private FloatVar _distance;

    private float _computedDistance;

    protected override string TrueSummary  =>
        "{_fromObject} distance to {_toObject} is {_check} {_distance}";

    protected override string FalseSummary =>
        "{_fromObject} distance to {_toObject} is not {_check} {_distance}";

    public override bool CanExecute() =>
        CheckParameters(_fromObject, _toObject, _distance);

    protected override bool Test()
    {
        _computedDistance = Vector3.Distance(
            _fromObject.Value.position,
            _toObject.Value.position);

        return _check.Evaluate(_computedDistance, _distance.Value);
    }

#if UNITY_EDITOR
    public override bool HasDebugInfo => true;

    public override string GetDebugInfo() =>
        $"Distance: {_computedDistance:0.##}";
#endif
}

============================================================
END OF FILE
============================================================

Tips for best results

  • Put the instruction file into persistent project instructions so you never need to paste it again.
  • Start a new chat when generating a new Action - don’t reuse heavily contextual conversations.
  • If the AI ever forgets the pattern, simply say:

    “Follow the PlayMaker 2 AI instructions.”