Skip to content

Commit

Permalink
[REG-1525] Initial doc changes for states and actions (#54)
Browse files Browse the repository at this point in the history
* [REG-1525] Initial doc changes for states and actions

* u in MonoBehaviour

* [REG-1525] core links, removed whitespace, re-did intro headers

* [REG-1525] update wording and doc links

* Update defining-states.mdx

* [REG-1525] Update for includeflags

* [REG-1525] Update generated filename pattern
nAmKcAz authored Jan 11, 2024
1 parent b9a633a commit e8b32ff
Showing 7 changed files with 643 additions and 120 deletions.
284 changes: 253 additions & 31 deletions docs/integrating-with-unity/defining-actions.mdx
Original file line number Diff line number Diff line change
@@ -10,20 +10,20 @@ import TabItem from '@theme/TabItem';
This section refers to an "action" as any instruction that a bot can issue to its GameObject.
This can be anything from movement to casting abilities to navigating UI components.

It is common for actions to interact with entities in the [game state](./defining-entities-and-their-states) and their corresponding GameObjects.
The [`RGFindUtils`](../creating-bots/csharp/utilities#rgfindutils) class contains utility functions for finding such GameObjects within the scene and is
useful for these types of actions.
It is common for actions to interact with entities in the [game state](./defining-states) and their corresponding GameObjects.

## Using the `RGAction` Attribute
## Using the `[RGAction]` Attribute

Defining an action is as simple as adding the `RGAction` attribute to a method.
Defining an action is as simple as adding the `[RGAction]` attribute to a method.
This allows the Regression Games SDK to recognize this method as the entrypoint into a callable action from your bot code ([C#](../creating-bots/csharp/configuration)) ([JavaScript](../creating-bots/javascript/configuration)).

When this attribute is used in a class, a new source file will be autogenerated by Regression Games to handle calling this action on a target GameObject.

:::info
The `RGAction` attribute must be used within a component, a class that inherits from `MonoBehavior`, or a class whose parent inherits from `MonoBehavior`.
The `[RGAction]` attribute must be used within a component, a class that inherits from `MonoBehaviour`, or a class whose parent inherits from `MonoBehaviour`.
:::

By default, the action's name matches the method's, but this can be overridden by passing a different name to `RGAction`.
By default, the action's name matches the method's, but this can be overridden by passing a different name to `[RGAction]`.
Action names must be unique within your Unity project.

Any parameters that the method accepts will be valid arguments in your bot code.
@@ -58,15 +58,15 @@ public void OpenContainer(bool isLocked)
</TabItem>
</Tabs>

The `RGAction` attribute is designed for ease of integration.
A method with this attribute may execute an action from start to finish, or it may start an action that `MonoBehavior` methods such as `FixedUpdate` should complete.
The `[RGAction]` attribute is designed for ease of integration.
A method with this attribute may execute an action from start to finish, or it may start an action that `MonoBehaviour` methods such as `FixedUpdate` should complete.
Depending on how your code is structured, you may be able to add the `RGAction` attribute to your existing code with minimal refactoring.

<Tabs>
<TabItem value="complete_action" label="RGAction Completes the Action" default>

```cs
public class Player : MonoBehavior
public class Player : MonoBehaviour
{
public float jumpPower = 10f;
private RigidBody _rigidbody;
@@ -88,7 +88,7 @@ public class Player : MonoBehavior
<TabItem value="action_with_update" label="RGAction Starts the Action">

```cs
public class Player : MonoBehavior
public class Player : MonoBehaviour
{
public float speed = 100f;
public float range = 1f;
@@ -130,17 +130,18 @@ public class Player : MonoBehavior
</Tabs>

:::info
We recommend using primitive data types for `RGAction` parameters.
Full support for non-primitive types is coming soon.
If your method requires non-primitive parameters, consider using a proxy method:
For C# bots, we have full support for non-primitive types for `[RGAction]` parameters.

For Javascript bots, we recommend using primitive data types for `[RGAction]` parameters.
If your Javascript bot needs to call a method with non-primitive parameters, consider using a proxy method:

```cs
// Proxy method for the AttackPlayer action
[RGAction]
public void AttackPlayerProxy(int playerId)
{
var player = RGFindUtils.Instance.FindOneByInstanceId<RGState>(playerId);
AttackPlayer(player.gameObject);
var playerGameObject = RGFindUtils.Instance.FindOneByInstanceId(playerId);
AttackPlayer(playerGameObject);
}

// The method we want to expose as an action
@@ -149,27 +150,248 @@ private void AttackPlayer(GameObject player)
// your implementation here...
}
```
:::


# Invoking an `RGAction` on a GameObject
As mentioned above, using the `[RGAction]` attribute causes Regression Games to generate a new code class for handling invoking actions
for that MonoBehaviour.

One of the generated classes is an `RGActionRequest` which can be used by your bot code to quickly invoke an action on a specific game object.

:::info
Note that by default you can only invoke actions on GameObjects for bots owned by your client id, or on global action
handlers registered on the Regression Games overlay.

_Clicking UI buttons is a global action handler available by default on the overlay. Clicking a button is included in the example below._
:::

## Assigning Actions to GameObjects
### `[RGAction]` Code Generation Example

Source File: Player.cs
```cs
public class Player : MonoBehaviour
{
public float speed = 100f;
public float range = 1f;
private RigidBody _rigidbody;
private Vector3? _targetPosition;

The Regression Games SDK relies on generated files to help translate commands sent from your bot into the correct actions within your Unity project.
Whenever you add, remove, or modify actions, you will need to instruct the SDK to generate new scripts by opening the `Regression Games` dropdown and selecting "Generate Action Classes".
void Start()
{
_rigidbody = GetComponent<Rigidbody>();
}

![Generate Action Classes](img/rg-action/generate-action-classes.png)
public void Update()
{
// If we are in range, reset the action
if (targetPosition != null && Vector3.Distance((Vector3) targetPosition, transform.position) < range)
{
targetPosition = null;
}

This should result in a unique generated script for each action you've defined using the `RGAction` attribute.
You can find these files in your Unity project under `RegressionGames/Runtime/GeneratedScripts/RGActions`.
Each of these scripts can then be added as components to the appropriate GameObjects within your scene.
When assigning action scripts to a GameObject, make sure that GameObject has all of the components that the action references.
// Set the target velocity
if (targetPosition != null)
{
Vector3 direction = ((Vector3)targetPosition - transform.position).normalized;
direction.y = 0;
float force = speed * Time.deltaTime;
_rigidbody.AddForce(direction * force);
}
}

![Add Action Script to GameObject](img/rg-action/add-action-script-to-game-object.png)
[RGAction]
public void MoveToPosition(float x, float y, float z)
{
targetPosition = new Vector3(x, y, z);
}
}
```

Generated File: Generated_RGActions_Player.cs
```cs
public class RGActions_Player : IRGActions
{
public static readonly Type BehaviourType = typeof(Player);
public static readonly string EntityTypeName = "Player";
public static readonly IDictionary<string, Delegate> ActionRequestDelegates = new ReadOnlyDictionary<string, Delegate>(new Dictionary<string, Delegate>()
{
{RGActionRequest_Player_MoveToPosition.ActionName, new Action<GameObject, RGActionRequest>(RGAction_Player_MoveToPosition.InvokeOnGameObject)}
});
}

public class RGActionRequest_Player_MoveToPosition : RGActionRequest
{
public RGActionRequest_Player_MoveToPosition(float x, float y, float z): base("MoveToPosition")
{
Input["x"] = x;
Input["y"] = y;
Input["z"] = z;
}

If your bot code attempts to call an action whose script is not assigned to its GameObject,
then the Regression Games SDK will ignore that command and continue executing your bot code as if the action was not called.
public static readonly string EntityTypeName = "Player";
public static readonly string ActionName = "MoveToPosition";
public float x => (float)Input!["x"];
public float y => (float)Input!["y"];
public float z => (float)Input!["z"];
}

public class RGAction_Player_MoveToPosition : IRGAction
{
public static void InvokeOnGameObject(GameObject gameObject, RGActionRequest actionRequest)
{
// optimize this for local C# bots to avoid all the conversions/etc
if (actionRequest is RGActionRequest_Player_MoveToPosition myActionRequest)
{
InvokeOnGameObject(gameObject, myActionRequest.x, myActionRequest.y, myActionRequest.z);
}
else
{
InvokeOnGameObject(gameObject, actionRequest.Input);
}
}

:::caution
Be mindful when removing the `RGAction` attribute from a method, or deleting an action altogether -
this may result in GameObjects missing the corresponding action script the next time you Generate Action Classes.
:::
private static void InvokeOnGameObject(GameObject gameObject, Dictionary<string, object> input)
{
float x = default;
if (input.TryGetValue("x", out var xInput))
{
try
{
float.TryParse(xInput.ToString(), out x);
}
catch (Exception ex)
{
RGDebug.LogError($"Failed to parse 'x' - {ex}");
}
}
else
{
RGDebug.LogError("No parameter 'x' found");
return;
}

float y = default;
if (input.TryGetValue("y", out var yInput))
{
try
{
float.TryParse(yInput.ToString(), out y);
}
catch (Exception ex)
{
RGDebug.LogError($"Failed to parse 'y' - {ex}");
}
}
else
{
RGDebug.LogError("No parameter 'y' found");
return;
}

float z = default;
if (input.TryGetValue("z", out var zInput))
{
try
{
float.TryParse(zInput.ToString(), out z);
}
catch (Exception ex)
{
RGDebug.LogError($"Failed to parse 'z' - {ex}");
}
}
else
{
RGDebug.LogError("No parameter 'z' found");
return;
}

InvokeOnGameObject(gameObject, x, y, z);
}

private static void InvokeOnGameObject(GameObject gameObject, params object[] args)
{
var monoBehaviour = gameObject.GetComponent<Player>();
if (monoBehaviour == null)
{
//TODO (REG-1420): It would be nice if we could link them to the exact game object in the scene quickly.
RGDebug.LogError($"Error: Regression Games internal error... Somehow RGAction: MoveToPosition got registered on a GameObject where MonoBehaviour: Player does not exist.");
return;
}

monoBehaviour.MoveToPosition((float)args[0], (float)args[1], (float)args[2]);
}
}
```

### Invoking Actions from Bot code
To invoke the `RGActionRequest_Player_MoveToPosition` from your bot, your bot code would look something like this.

```cs
public class BotEntryPoint : RGUserBot
{
protected override bool GetIsSpawnable()
{
return false;
}

protected override RGBotLifecycle GetBotLifecycle()
{
return RGBotLifecycle.PERSISTENT;
}

public override void ConfigureBot(RG rgObject)
{
rgObject.CharacterConfig = new Dictionary<string, object>() {
{ "characterType", "Mage" }
};
}

public override void ProcessTick(RG rgObject)
{
switch (rgObject.SceneName)
{
case "CharSelect":
var readyButton = rgObject.GetInteractableButtonByScenePath("/CharacterSelectCanvas/ClassInfoBox/DecorativeFrame/Ready Btn");
if (readyButton != null)
{
rgObject.PerformAction(new RGActionRequest_ClickButton(readyButton.id));
}
break;
case "BossRoom":
try
{
var entities = rgObject.AllEntities();
if (entities.Count > 0)
{
var target = entities[new Random().Next(entities.Count)];
var targetPosition = target.position;
var action = new RGActionRequest_Player_MoveToPosition(
targetPosition.x,
targetPosition.y,
targetPosition.z
);
rgObject.PerformAction(action);
}
else
{
RGDebug.LogWarning("No entities found...");
}
}
catch (Exception ex)
{
RGDebug.LogError($"Error getting target position: {ex}");
}

break;
case "PostGame":
default:
// teardown myself
rgObject.Complete();
break;
}
}

}
```
82 changes: 0 additions & 82 deletions docs/integrating-with-unity/defining-entities-and-their-states.mdx

This file was deleted.

385 changes: 385 additions & 0 deletions docs/integrating-with-unity/defining-states.mdx

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
10 changes: 4 additions & 6 deletions docs/tutorials/building-your-first-bot.md
Original file line number Diff line number Diff line change
@@ -182,13 +182,9 @@ position it within the scene wherever you want to spawn your bot (ideally above
## Add state information to relevant GameObjects

The next step is to indicate what state in your scene is available to bots. This state is used by bots to
make decisions on what actions to take. State is relayed to bots by attaching [`RGState`](../integrating-with-unity/defining-entities-and-their-states)
components to any game object or prefab that you want your bots to know about.
make decisions on what actions to take. For more information about exposing the state of GameObjects [`RGState`](../integrating-with-unity/defining-states).

The base implementation of [`RGState`](../integrating-with-unity/defining-entities-and-their-states) provided by Regression Games relays default
information such as a `type`, `position`, and a unique identifier. In this sample scene, we will use this default
state information, but of course you may need to provide additional state, such as a players team, health, if a door is open, etc...
You can do this by checking out the docs and examples for [`RGState.GetState()`](../integrating-with-unity/defining-entities-and-their-states#public-virtual-dictionarystring-object-getstate).
TODO (REG-1279) : Re - Write this section

In this scene, open the `Player` prefab located in the **Assets > Prefabs** folder by double clicking
the prefab, click **Add Component**, and then add the `RGState` component by searching for "RG State" in the component
@@ -203,6 +199,8 @@ That's all you need to do for your bot to see the state of the game!

## Implement actions your bot can take

TODO (REG-1279) : Re - Write this section

Finally, we need to provide an interface that allows bots to control these characters in your game. This is
done using the [`RGAction`](../integrating-with-unity/defining-actions) interface.

2 changes: 1 addition & 1 deletion sidebars.js
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ const sidebars = {
type: 'category',
label: 'Integrating with Unity',
items: [
'integrating-with-unity/defining-entities-and-their-states',
'integrating-with-unity/defining-states',
'integrating-with-unity/defining-actions',
'integrating-with-unity/BotInformation',
'integrating-with-unity/seating-and-spawning-bots',

0 comments on commit e8b32ff

Please sign in to comment.