* Restructure tutorials and add two new tutorials - Non-generated documentation review and edit - Consistent terminology - Stylistic changes - Minor structure changes - Minor clarifications - Typo fixes - Split basic tutorial into two - Tutorials are easier to follow when they are short and to the point - Added event and variable instancer tutorials - Had to bump node version for the docker container to work * #189 - Fixing minor nitpicks Co-authored-by: Adam Ramberg <adam@mambojambostudios.com>
@ -1,4 +1,4 @@
|
|||||||
FROM node:8.11.4
|
FROM node:12.18.3
|
||||||
|
|
||||||
WORKDIR /app/website
|
WORKDIR /app/website
|
||||||
|
|
||||||
|
@ -3,11 +3,18 @@
|
|||||||
- Introduction
|
- Introduction
|
||||||
- [Quick start: adding Unity Atoms your project](./introduction/quick-start.md)
|
- [Quick start: adding Unity Atoms your project](./introduction/quick-start.md)
|
||||||
- [Overview and philosopy](./introduction/overview.md)
|
- [Overview and philosopy](./introduction/overview.md)
|
||||||
- [Basic tutorial](./introduction/basic-tutorial.md)
|
|
||||||
- [Usage with UniRX](./introduction/unirx.md)
|
|
||||||
- [Level up using the Generator](./introduction/generator.md)
|
|
||||||
- [Advanced example](./introduction/advanced-example.md)
|
|
||||||
- [Preferences](./introduction/preferences.md)
|
- [Preferences](./introduction/preferences.md)
|
||||||
|
- Tutorials
|
||||||
|
- [Variables](./tutorials/variables.md)
|
||||||
|
- [Events](./tutorials/events.md)
|
||||||
|
- [Listeners](./tutorials/listeners.md)
|
||||||
|
- [Actions](./tutorials/actions.md)
|
||||||
|
- [Mono Hooks](./tutorials/mono-hooks.md)
|
||||||
|
- [Variable Instancer](./tutorials/variable-instancer.md)
|
||||||
|
- [Event Instancer](./tutorials/event-instancer.md)
|
||||||
|
- [Generator](./tutorials/generator.md)
|
||||||
|
- [Advanced example](./tutorials/advanced-example.md)
|
||||||
|
- [Usage with UniRX](./tutorials/unirx.md)
|
||||||
- API
|
- API
|
||||||
- [UnityAtoms](./api/unityatoms.md)
|
- [UnityAtoms](./api/unityatoms.md)
|
||||||
- [UnityAtoms.Editor](./api/unityatoms.editor.md)
|
- [UnityAtoms.Editor](./api/unityatoms.editor.md)
|
||||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
BIN
docs/assets/event-instancer/death-event-assigned.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
docs/assets/event-instancer/death-event-reference.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/assets/event-instancer/death-listener.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
docs/assets/event-instancer/hurt-all-monsters.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/assets/event-instancer/monsters-took-damage.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/event-instancer/orc-death-message.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/assets/event-instancer/raise-damage-event.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/assets/event-instancer/unique-death-messages.png
Normal file
After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 231 KiB |
BIN
docs/assets/unity-atoms-update.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
docs/assets/variable-instancer/add-int-var-instancer-1.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/assets/variable-instancer/add-int-var-instancer-2.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/assets/variable-instancer/add-log-health.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
docs/assets/variable-instancer/create-prefabs.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
docs/assets/variable-instancer/drag-variable-instancer.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/assets/variable-instancer/init-health.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
docs/assets/variable-instancer/monster-reports.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/assets/variable-instancer/monster-start-healths.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
docs/assets/variable-instancer/monsters-report.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
docs/assets/variable-instancer/use-variable-instancer.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/assets/variable-instancer/wyvern-variant.png
Normal file
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -1,201 +0,0 @@
|
|||||||
---
|
|
||||||
id: basic-tutorial
|
|
||||||
title: Basic tutorial
|
|
||||||
hide_title: true
|
|
||||||
sidebar_label: Basic tutorial
|
|
||||||
---
|
|
||||||
|
|
||||||
# Basic tutorial
|
|
||||||
|
|
||||||
Below follows a step-by-step example of managing a player's health using Unity Atoms. If you haven't read the [Overview and philosopy](./overview.md) section you should do that before proceeding.
|
|
||||||
|
|
||||||
_NOTE: This tutorial is based on [this](https://medium.com/@adamramberg/unity-atoms-tiny-modular-pieces-utilizing-the-power-of-scriptable-objects-e8add1b95201) blog post._
|
|
||||||
|
|
||||||
## Decouple your scripts using Variables and Constants
|
|
||||||
|
|
||||||
Variables are storing data, for example primitives, reference types or structs as Scriptable Objects. Because Variables are stored as Scriptable Objects they are not part of any scene, but could instead be seen as part of the game’s global shared state. Variables are designed to make it easy to inject them (via the Unity Inspector) and share them between your MonoBehaviours. A Constant is a trimmed down version of a Variable and it's value can't be altered at runtime. Lets see an example on how to use Variables and Constants!
|
|
||||||
|
|
||||||
Imagine you have a `PlayerHealth.cs` script that contains the health of the game’s player. We will attach the script to a `GameObject` with a `SpriteRenderer`, `BoxCollider2D` and a `Rigidbody2D` called Player. The health is represented by an int, which corresponds to an `IntVariable` in Unity Atoms. The script will look like this:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class PlayerHealth : MonoBehaviour
|
|
||||||
{
|
|
||||||
public IntVariable Health;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the game the player’s health will decrease when hitting something harmful. We will attach this `Harmful.cs` script to a GameObject called Harmful that also has a `SpriteRenderer` and a `BoxCollider2D` (as a trigger):
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class Harmful : MonoBehaviour
|
|
||||||
{
|
|
||||||
void OnTriggerEnter2D(Collider2D collider)
|
|
||||||
{
|
|
||||||
if (collider.tag == "Player")
|
|
||||||
{
|
|
||||||
collider.GetComponent<Player>().Health.Value -= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally we will add an UI `HealthBar.cs` script that we attach to a `GameObject` (inside a UI Canvas) with a `RectTransforn`, `CanvasRenderer` and UI `Image` component. The `HealthBar.cs` script will update the `Image` representing the health bar when the player’s health is changing:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class HealthBar : MonoBehaviour
|
|
||||||
{
|
|
||||||
[SerializeField]
|
|
||||||
private IntVariable Health;
|
|
||||||
[SerializeField]
|
|
||||||
private IntConstant MaxHealth;
|
|
||||||
|
|
||||||
void Update()
|
|
||||||
{
|
|
||||||
GetComponent<Image>().fillAmount = 1.0f * Health.Value / MaxHealth.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Since the player's health is going to change at runtime we make `Health` an `IntVariable` while `MaxHealth` is not going to be changed at runtime is therefore created as an `IntConstant`, They are both global assets stored as `.assets` files that are (or could be) shared between scripts. To create these `.assets` files we can right click somewhere in the Project window, and go _Create / Unity Atoms / Variables / Int_ to create the Variable and go _Create / Unity Atoms / Constants / Int_ to create the Constant. The Variable looks like this in the Unity Inspector:
|
|
||||||
|
|
||||||
![int-variable_player-health-v1](assets/int-variable_player-health-v1.png)
|
|
||||||
|
|
||||||
And the Constant looks like this:
|
|
||||||
|
|
||||||
![int-variable_player-health](assets/int-constant_max-player-health.png)
|
|
||||||
|
|
||||||
The `Developer Description` is a text describing the Variable in order to document it, the `Value` is the actual value of the Variable, and `Old Value` is the last value the Variable had after it was changed via code. `Changed` and `Changed With History` will be explained later in this tutorial. We name the `IntVariable` created to `Health` and the `IntConstant` to `MaxHealth` and set both their initial value to 100. After they are created we can drop them on the `PlayerHealth` and `HealthBar` components via Unity’s inspector like this:
|
|
||||||
|
|
||||||
![player-health-script](assets/player-health-script.png)
|
|
||||||
|
|
||||||
![healthbar-script-v1](assets/healthbar-script-v1.png)
|
|
||||||
|
|
||||||
Variables gives us a way of separating our game’s shared state from the actual implementation. It also makes our code less coupled since we do not need to reference other MonoBehaviours in our scripts, eg. we do not need to reference the `PlayerHealth.cs` script in our `HealthBar.cs` script like this:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
[SerializeField]
|
|
||||||
private Player player;
|
|
||||||
```
|
|
||||||
|
|
||||||
Hurray for less coupled code! 🎉
|
|
||||||
|
|
||||||
## Make it more data driven using Events
|
|
||||||
|
|
||||||
Events are things that happens in our game that other scripts or entities could listen and subscribe to. Events are (like Variables) also Scriptable Objects that lives outside of a specific scene. In Unity Atoms Events can be of different types and thereby pass a long data to listeners. Variables do by default have the possibility to raise two specific Events:
|
|
||||||
|
|
||||||
- `Changed` — raised every time a Variable’s value is changed. The Event contains the new value.
|
|
||||||
- `Changed With History` — also raised every time a Variable’s value is changed. However, this Event contains both the new and the old value.
|
|
||||||
|
|
||||||
This makes it easier to make our game more data driven than just using Variables. Lets take a look at how that looks in our last example. We can create a new `IntEvent` as a `.asset` file by right clicking and go _Create / Unity Atoms / Event / Int_ and name it `HealthChangedEvent`:
|
|
||||||
|
|
||||||
![health-changed-event](assets/health-changed-event.png)
|
|
||||||
|
|
||||||
And then drop it on our `IntVariable` for the player’s health like this:
|
|
||||||
|
|
||||||
![int-variable_player-health-v2](assets/int-variable_player-health-v2.png)
|
|
||||||
|
|
||||||
We can then modify our `HealthBar.cs` script to look like this:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class HealthBar : MonoBehaviour
|
|
||||||
{
|
|
||||||
[SerializeField]
|
|
||||||
private IntEvent HealthChangedEvent;
|
|
||||||
[SerializeField]
|
|
||||||
private IntVariable MaxHealth;
|
|
||||||
|
|
||||||
void Start()
|
|
||||||
{
|
|
||||||
HealthChangedEvent.Register(this.ChangeFillAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnDestroy()
|
|
||||||
{
|
|
||||||
HealthChangedEvent.Unregister(this.ChangeFillAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChangeFillAmount(int health)
|
|
||||||
{
|
|
||||||
GetComponent<Image>().fillAmount = 1.0f * health / MaxHealth.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And then inject the `HealthChangedEvent` to our `HealthBar` component:
|
|
||||||
|
|
||||||
![healthbar-script-v2](assets/healthbar-script-v2.png)
|
|
||||||
|
|
||||||
We now react to global state changes instead of checking the Variable value each Update tick. In other words we only update our `Image` component when we actually need to. That is pretty sweet!
|
|
||||||
|
|
||||||
## Seperate concerns using Listeners
|
|
||||||
|
|
||||||
There is still an issue that the `HealthBar.cs` script is in charge of registering itself as a listener and at the same time defining what happens when a Event is raised. We need to seperate its concerns! This brings us to the third concept of Unity Atoms, Listeners. A Listener listens (sometimes also referred to as observes or subscribes) to a Event and responds by firing off zero to many responses. Listeners are MonoBehaviours and therefore lives in a scene. They can be seen as the glue between Events and Actions (see the next section of this post).
|
|
||||||
|
|
||||||
The `HealthBar.cs` script from our last example is actually a Listener, but a very specific implementation of it. We can do better than that! Lets create a Game Object in our scene and call it `HealthListener`. Unity Atoms comes with some predefined Listeners. In this case we want to listen to an `IntEvent` so we will press the Add Component button on our `HealthListener`, create an IntListener and drop in the `HealthChangedEvent`:
|
|
||||||
|
|
||||||
![health-listener](assets/health-listener-v1.png)
|
|
||||||
|
|
||||||
We can now shave off some of the code in our `HealthBar.cs` script to look like this:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class HealthBar : MonoBehaviour
|
|
||||||
{
|
|
||||||
[SerializeField]
|
|
||||||
private IntVariable MaxHealth;
|
|
||||||
|
|
||||||
public void ChangeFillAmount(int health)
|
|
||||||
{
|
|
||||||
GetComponent<Image>().fillAmount = 1.0f * health / MaxHealth.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And then go back to our `HealthListener`’s IntListener component, press the + to add an Unity Event Response, drop in the `HealthBar` component (from the scene) and point out the `HealthChanged` function defined above:
|
|
||||||
|
|
||||||
![health-listener](assets/health-listener-v2.png)
|
|
||||||
|
|
||||||
The `HealthBar.cs` script is now only responsible for what happens when our player’s health is changing. Pretty great, huh?
|
|
||||||
|
|
||||||
## Create an Action as a response
|
|
||||||
|
|
||||||
The `HealthChanged` function created above is actually a Response of type `UnityEvent`. However, in Unity Atoms there is also the possibility to create Responses as Scriptable Objects called Actions. An Action is a function as a Scriptable Object that does not return a value. As a side note there are also Functions in Unity Atoms, which are exactly like Actions, but does return a value. To demonstrate the concept of an Action as a Response lets create an Action called `HealthLogger.cs` that gives some love to the console and logs the player’s health whenever it changes:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
[CreateAssetMenu(menuName = "Unity Atoms/Examples/Health Logger")]
|
|
||||||
public class HealthLogger : IntAction
|
|
||||||
{
|
|
||||||
public override void Do(int health)
|
|
||||||
{
|
|
||||||
Debug.Log("<3: " + health);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It is possible to create the `HealthLogger` by right clicking and go _Create / Unity Atoms / Examples / Health Logger_ (this is available due to the call to `CreateAssetMenu`). When created we can add it as an Action Response to the `HealthListener`:
|
|
||||||
|
|
||||||
Every time the player’s health is changed we now log out the player’s health. This particular example is pretty simple, but I’m sure you can come up with lots of other use cases for it (for example play a sound or emit some particles).
|
|
||||||
|
|
||||||
That is it! We have covered the four most fundamental core pieces of Unity Atoms.
|
|
||||||
|
|
||||||
## Use Mono Hooks to keep yourself DRY
|
|
||||||
|
|
||||||
Mono Hooks are Unity's lifecycles as Events. A great use for Mono Hooks in our example would allow us to remove the `Harmful.cs` script created earlier. We could instead attach a `OnTrigger2DHook.cs` to the Harmful GameObject and toggle on `Trigger On Enter` like this:
|
|
||||||
|
|
||||||
![mono-hooks-trigger-2d](assets/mono-hooks-trigger-2d.png)
|
|
||||||
|
|
||||||
We could then create a Collider2DAction called `DecreasePlayersHealth.cs` and add it to a Collder2D Listener attached to the Harmful GameObject:
|
|
||||||
|
|
||||||
```cs
|
|
||||||
public class DecreasePlayersHealth : Collider2DAction
|
|
||||||
{
|
|
||||||
public override void Do(Collider2D collider)
|
|
||||||
{
|
|
||||||
if (collider.tag == "Player")
|
|
||||||
{
|
|
||||||
collider.GetComponent<PlayerHealth>().Health.Value -= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
![mono-hooks-listener](assets/mono-hooks-listener.png)
|
|
@ -7,87 +7,95 @@ sidebar_label: Overview and philosopy
|
|||||||
|
|
||||||
# Overview and philosopy
|
# Overview and philosopy
|
||||||
|
|
||||||
|
This chapter outlines the theoretical concepts behind Unity Atoms. This knowledge helps you better understand a new way of thinking about data and state in your project.
|
||||||
|
|
||||||
|
## Fundamentals
|
||||||
|
|
||||||
Unity Atoms is an event based system that encourages the game to be as data-driven as possible. The four most fundamental parts of Unity Atoms are:
|
Unity Atoms is an event based system that encourages the game to be as data-driven as possible. The four most fundamental parts of Unity Atoms are:
|
||||||
|
|
||||||
- Data
|
- Data
|
||||||
- Events
|
- Events
|
||||||
- Listeners
|
- Listeners
|
||||||
- Functions
|
- Responses
|
||||||
- Collections
|
- Collections
|
||||||
|
|
||||||
## Data
|
## Data
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
|
|
||||||
Variables are data / variables stored as Scriptable Objects. Because Variables are stored as Scriptable Objects they are not part of any scene, but could be instead be seen as part of a global shared game state. Variables are also designed to make it easy to inject (via the Unity Inspector) to your MonoBehaviours.
|
Variables are data stored as [Unity's Scriptable Objects](https://docs.unity3d.com/Manual/class-ScriptableObject.html).
|
||||||
|
|
||||||
It is possible to attach an event to a Variable that gets raised when its updated. This makes it possible to write more data-driven code.
|
Because Variables are stored as Scriptable Objects they are not part of any scene, but could be instead be seen as part of a global shared game state. Variables are also designed to make them easy to inject (via the Unity Inspector) to your MonoBehaviours.
|
||||||
|
|
||||||
It is also possible to attach another event to a Variable that also gets raised when a Variable is changed, but that contains both the old and the new value of the Variable. Unity Atoms generates Pairs (a simple struct) for Atoms containing 2 variables of the same type for this purpose.
|
It is possible to attach an Event to a Variable that gets raised when its updated. This makes it possible to write more data-driven code. An Event attached to a Variable could contain only the new value (`Changed`) or contain both the new and the old value (`Changed With History`)
|
||||||
|
|
||||||
|
#### Pre Change Transformers
|
||||||
|
|
||||||
You can also add pre change transformers to a Variable. A pre change transformer is an AtomFunction that takes the value type of the Variable, performs some logic, and returns a new value of the same type. It's called on `OnEnable` as well as before setting a new Value of a Variable. An example of a pre change transformer is `ClampInt`, an `IntIntFunction` that clamps the Variable's value between two values.
|
You can also add pre change transformers to a Variable. A pre change transformer is an AtomFunction that takes the value type of the Variable, performs some logic, and returns a new value of the same type. It's called on `OnEnable` as well as before setting a new Value of a Variable. An example of a pre change transformer is `ClampInt`, an `IntIntFunction` that clamps the Variable's value between two values.
|
||||||
|
|
||||||
Unity Atoms also offer some variations / additions to Variables such as Contants, References and Lists.
|
Your pre change transformers can contain as much or as little logic as necessary for your project and you can chain them in the Inspector.
|
||||||
|
|
||||||
### Constants
|
### Constants
|
||||||
|
|
||||||
Exactly the same as Variables, but can not be changed via script and therefore does not contain the change events that Variables does. The idea is to use Constants for for example tags instead of hard coding tags in your scripts.
|
Constants behave exactly the same as Variables, but can not be changed via script and therefore do not contain the change Events that Variables do.
|
||||||
|
|
||||||
### References
|
### References
|
||||||
|
|
||||||
References are values that can be toggled between `Use Value`, `Use Constant`, `Use Variable` or `Use Variable Instancer` via the Unity Inspector. When a Reference is set to `Use Value` it functions exactly like a regular serialized variable in a MonoBehaviour script. However, when it is set to `Use Variable` or `Use Constant` it uses a Variable or a Constant. When it's set to `Use Variable Instancer` you can drag and drop a Variable Instancer of the correct type.
|
References are values that can be toggled between `Use Value`, `Use Constant`, `Use Variable` or `Use Variable Instancer` via the Unity Inspector.
|
||||||
|
|
||||||
|
When a Reference is set to `Use Value` it functions exactly like a regular serialized variable in a MonoBehaviour script. However, when it is set to `Use Variable` or `Use Constant` it uses a Variable or a Constant. When it's set to `Use Variable Instancer` you can drag and drop a Variable Instancer of the correct type.
|
||||||
|
|
||||||
### Variable Instancers
|
### Variable Instancers
|
||||||
|
|
||||||
This is a MonoBehaviour that takes a base Variable and makes an in memory copy of it `OnEnable`. This is particular useful when working with prefabs that is going to be instantiated at runtime. For example, when creating an enemy prefab you can use an `IntVariableInstancer` that creates an in memory copy of the enemy's health that you then can use in your scripts on the enemy prefab (using References). You can also give it a reference to a List or a Collection. If that is done the instancer will add the in memory Variable on Start to the List / Collection and then remove it OnDestroy.
|
This is a MonoBehaviour that takes a base Variable and makes an in memory copy of it `OnEnable`. This is particular useful when working with prefabs that are going to be instantiated at runtime. You can also give the Variable Instancer a reference to a List or a Collection. If you do that the Variable Instancer will add the in memory Variable on `Start` to the List or Collection and then later remove it on `OnDestroy`.
|
||||||
|
|
||||||
|
### Pairs
|
||||||
|
|
||||||
|
Pairs are simple structs containing two variables of the same type, used for example in Variables' `Changed With History` Event.
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
An event is a thing that happens in the game that others can listen / subscribe to. Events in Unity Atoms are also Scriptable Objects that lives outside of a specific scene. It is possible to raise an Event from the Unity Inspector for debugging purposes.
|
An Event is a thing that happens in the game that Listeners can listen for. Events in Unity Atoms are also Scriptable Objects that live outside of a specific scene. It is possible to raise an Event from the Unity Inspector for debugging purposes.
|
||||||
|
|
||||||
### Pair Events
|
### Pair Events
|
||||||
|
|
||||||
Like Event, but for pairs. Pairs are simple structs containing 2 variables of the same type, used for example in Variables' Changed With History Event.
|
Like Event, but for pairs.
|
||||||
|
|
||||||
### Event References
|
### Event References
|
||||||
|
|
||||||
Event References are events that can be toggled between `Use Event`, `Use Event Instancer`, `Use Variable` or `Use Variable Instancer` via the Unity Inspector. When an Event Reference is set to `Use Event` it functions exactly like a regular serialized Event in a MonoBehaviour script. When it is set to `Use Event Instancer` you can drag and drop an Event Instancer whos Event the Event Reference will use. When it is set to `Use Variable` it is going to use the Event associated with the Variable's Changed event. When it's set to `Use Variable Instancer` you can drag and drop a Variable Instancer of the correct type and it will use its associated Changed event.
|
Event References are Events that can be toggled between `Use Event`, `Use Event Instancer`, `Use Variable` or `Use Variable Instancer` via the Unity Inspector. When an Event Reference is set to `Use Event` it functions exactly like a regular serialized Event in a MonoBehaviour script. When it is set to `Use Event Instancer` you can drag and drop an Event Instancer whose Event the Event Reference will use. When it is set to `Use Variable` it is going to use the Event associated with the Variable's Changed Event. When it's set to `Use Variable Instancer` you can drag and drop a Variable Instancer of the correct type and it will use its associated Changed Event.
|
||||||
|
|
||||||
### Event Instancers
|
### Event Instancers
|
||||||
|
|
||||||
This is a MonoBehaviour that takes a base Event and makes an in memory copy of it `OnEnable`. This is particular useful when working with prefabs that is going to be instantiated at runtime, for example when working with Mono Hooks on your prefabs.
|
This is a MonoBehaviour that takes a base Event and makes an in memory copy of it on `OnEnable`. This is particularly useful when working with prefabs that are going to be instantiated at runtime, for example when working with Mono Hooks on your prefabs.
|
||||||
|
|
||||||
### Pair Event Instancers
|
### Pair Event Instancers
|
||||||
|
|
||||||
Like Event Instancer, but for pairs. Pairs are simple structs containing 2 variables of the same type, used for example in Variables' Changed With History Event.
|
Like Event Instancer, but for pairs.
|
||||||
|
|
||||||
## Listeners
|
## Listeners
|
||||||
|
|
||||||
### Event Reference Listeners
|
### Event Reference Listeners
|
||||||
|
|
||||||
A Listener listens / observes / subscribes to an event reference and raises / invokes zero to many responses to that Event Reference. Listeners are MonoBehaviours and lives in a scene. See below for more information on the type of responses there are.
|
A Listener listens to an Event reference and raises zero to many responses to that Event Reference. Listeners are MonoBehaviours that live in a scene. See below for more information on the type of responses that is supported.
|
||||||
|
|
||||||
### Pair Event Reference Listeners
|
### Pair Event Reference Listeners
|
||||||
|
|
||||||
Like Event Reference Listeners, but for pairs. Pairs are simple structs containing 2 variables of the same type, used for example in Variables' Changed With History Event.
|
Like Event Reference Listeners, but for pairs.
|
||||||
|
|
||||||
### Event Reference Listeners
|
|
||||||
|
|
||||||
Like Event Reference Listeners, but are using a regular Event instead of an Event Reference. This makes more sense in listeners observing for example Collections (see more info below).
|
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|
||||||
A Responses are raised by a Listener in Response to an event. Responses can live both in the scene as [UnityEvents](https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html) or outside the scene as a Scriptable Object in the shape of an Action.
|
Responses are raised by a Listener in response to an Event. Responses can live both in the scene as [UnityEvents](https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html) or outside the scene as a Scriptable Object in the shape of an Action.
|
||||||
|
|
||||||
### Actions
|
### Actions
|
||||||
|
|
||||||
An Action in Unity Atoms is a C# function as a Scriptable Object. An Action can be used as a response in a Listener.
|
An Action in Unity Atoms is a C# function as a Scriptable Object. An Action can be used as a response in a Listener. Since Scriptable Objects can be created as assets in the project, Actions are well suited for responses that may have different default values.
|
||||||
|
|
||||||
#### Pair Actions
|
### Pair Actions
|
||||||
|
|
||||||
Like Actions, but for pairs. Pairs are simple structs containing 2 variables of the same type, used for example in Variables' Changed With History Event.
|
Like Actions, but for pairs.
|
||||||
|
|
||||||
### Functions
|
### Functions
|
||||||
|
|
||||||
@ -95,11 +103,11 @@ A Function in Unity Atoms is basically the same as an Action, but while an Actio
|
|||||||
|
|
||||||
## Collections
|
## Collections
|
||||||
|
|
||||||
Collections stores multiple values. For all collections in Unity Atoms there is the possibility to add Events for when the following:
|
Collections store multiple values. For all collections in Unity Atoms there is the possibility to add Events for the following:
|
||||||
|
|
||||||
- For when an item is added.
|
- An item is added.
|
||||||
- For when an item is removed.
|
- An item is removed.
|
||||||
- For when the collection is cleared.
|
- The collection is cleared.
|
||||||
|
|
||||||
### Value Lists
|
### Value Lists
|
||||||
|
|
||||||
@ -107,8 +115,8 @@ A Value List is an array of values that is stored as a Scriptable Object.
|
|||||||
|
|
||||||
### Lists
|
### Lists
|
||||||
|
|
||||||
A List is an array of Variables that is stored as a Scriptable Object. The Variables can be different types since it's using `AtomBaseVariable`.
|
A List is an array of Variables that is stored as a Scriptable Object. The Variables stored in a List can be of different types.
|
||||||
|
|
||||||
### Collections
|
### Collections
|
||||||
|
|
||||||
A collection is a set of Variables associated with a StringReference key and is stored as a Scriptable Object. The Variables can be different types since it's using `AtomBaseVariable`.
|
A collection is a set of Variables associated with a StringReference key and is stored as a Scriptable Object. The Variables stored in a Collection can be of different types.
|
||||||
|
@ -7,13 +7,13 @@ sidebar_label: Preferences
|
|||||||
|
|
||||||
# Preferences
|
# Preferences
|
||||||
|
|
||||||
Preferences can be reached via `Unity/Preferences`.
|
Preferences can be reached via _Unity / Preferences_.
|
||||||
|
|
||||||
![preferences-location](assets/preferences-location.png)
|
![preferences-location](../assets/preferences/location.png)
|
||||||
|
|
||||||
And it looks like this:
|
And it looks like this:
|
||||||
|
|
||||||
![preferences-overview](assets/preferences-overview.png)
|
![preferences-overview](../assets/preferences/overview.png)
|
||||||
|
|
||||||
## Debug mode
|
## Debug mode
|
||||||
|
|
||||||
|
@ -84,10 +84,18 @@ Add the following to your `manifest.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
Updating Unity Atoms to a new release when using the Unity Package Manager is as easy as opening the Package Manager window and clicking on `Update` on the selected package.
|
||||||
|
|
||||||
|
![updating](../assets/unity-atoms-update.png)
|
||||||
|
|
||||||
|
Follow [Unity Atoms on Github](https://github.com/AdamRamberg/unity-atoms) to stay up-to-date on the current version.
|
||||||
|
|
||||||
## Create your first Atom
|
## Create your first Atom
|
||||||
|
|
||||||
You are now ready to create your first Atom. Simply right click somewhere in the Project window and go to **Create / Unity Atoms** and pick the Atom of your choice:
|
You are now ready to create your first Atom. Simply right click somewhere in the Project window and go to **Create / Unity Atoms** and pick the Atom of your choice:
|
||||||
|
|
||||||
![create-your-first-atom](assets/create-your-first-atom.png)
|
![create-your-first-atom](../assets/quick-start/create-your-first-atom.png)
|
||||||
|
|
||||||
Now you are ready to go to [Overview and philosopy](./overview.md) to learn more about Unity Atoms!
|
Now you are ready to go to [Overview and philosopy](./overview.md) to learn more about Unity Atoms!
|
||||||
|
27
docs/tutorials/actions.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
id: actions
|
||||||
|
title: Actions
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Actions
|
||||||
|
---
|
||||||
|
|
||||||
|
# Actions
|
||||||
|
|
||||||
|
The `ChangeFillAmount` function created in the listeners tutorial is actually a Response of type `UnityEvent`. However, in Unity Atoms there is also the possibility to create Responses as Scriptable Objects called Actions. An Action is a function as a Scriptable Object that does not return a value. As a side note there are also Functions in Unity Atoms, which are exactly like Actions, but does return a value. To demonstrate the concept of an Action as a Response lets create an Action called `HealthLogger.cs` that gives some love to the console and logs the player’s health whenever it changes:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
[CreateAssetMenu(menuName = "Unity Atoms/Examples/Health Logger")]
|
||||||
|
public class HealthLogger : IntAction
|
||||||
|
{
|
||||||
|
public override void Do(int health)
|
||||||
|
{
|
||||||
|
Debug.Log("<3: " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to create the `HealthLogger` by right clicking and go _Create / Unity Atoms / Examples / Health Logger_ (this is available due to the call to `CreateAssetMenu`). When created we can add it as an Action Response to the `HealthListener`:
|
||||||
|
|
||||||
|
![health-listener-v3](../assets/actions/health-listener-v3.png)
|
||||||
|
|
||||||
|
Every time the player’s health is changed we now log out the player’s health. This particular example is pretty simple, but I’m sure you can come up with lots of other use cases for it (for example play a sound or emit some particles).
|
186
docs/tutorials/event-instancer.md
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
id: event-instancer
|
||||||
|
title: Event Instancer
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Event Instancer
|
||||||
|
---
|
||||||
|
|
||||||
|
# Event Instancer
|
||||||
|
|
||||||
|
Similar to Variables, Events can also be instantiated for use in prefabs. In this tutorial we will build a "death" Event for the monster prefab created in the variable instancer tutorial. The death Event will be fired once as the monster health drops below zero.
|
||||||
|
|
||||||
|
Some setup is necessary in our example to demonstrate how an Event Instancer works.
|
||||||
|
|
||||||
|
## Damage the monster
|
||||||
|
|
||||||
|
To inflict damage on the monster, we can add a method called `Damage` to the `LogHealth` script that subtracts from the current health of the monster:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityAtoms.BaseAtoms;
|
||||||
|
|
||||||
|
public class LogHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntReference health;
|
||||||
|
[SerializeField]
|
||||||
|
private IntConstant startHealth;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
health.Value = startHealth.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Damage(int damage)
|
||||||
|
{
|
||||||
|
health.Value -= damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportHealth(int health)
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " reports: " +
|
||||||
|
"My health changed to " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point `LogHealth` as a name for the component is starting to lose it's meaning, but we'll keep extending the script for simplicity's sake.
|
||||||
|
|
||||||
|
## Global damage event
|
||||||
|
|
||||||
|
To trigger the `Damage` method, create a new `IntEvent` called `HurtAllMonsters`. Add a new `Int Event Reference Listener` component to the `Monster` base prefab. Have the listener call `Damage` of `LogHealth`:
|
||||||
|
|
||||||
|
![hurt-all-monsters](../assets/event-instancer/hurt-all-monsters.png)
|
||||||
|
|
||||||
|
> **Note:** Make sure the `Damage` method is selected from the `Dynamic` list of methods.
|
||||||
|
|
||||||
|
## Raising events from the inspector
|
||||||
|
|
||||||
|
Run the scene and find the `HurtAllMonsters` Event from your asset folders. Set the `Inspector Raise Value` to any number and click the `Raise` button.
|
||||||
|
|
||||||
|
![raise-damage-event](../assets/event-instancer/raise-damage-event.png)
|
||||||
|
|
||||||
|
The monsters in the scene will take the entered amount of damage. Click the button multiple times to repeat inflicting the damage:
|
||||||
|
|
||||||
|
![monsters-took-damage](../assets/event-instancer/monsters-took-damage.png)
|
||||||
|
|
||||||
|
The setup is complete. We are now ready to instantiate a death Event for each monster.
|
||||||
|
|
||||||
|
## Death event instancer
|
||||||
|
|
||||||
|
When the monster health reaches zero or lower, we want to fire a `VoidEvent` to signal that this monster has perished. Listening for this Event in the prefab allows you to customize how each monster type dies. In this example, each monster reports a different death message.
|
||||||
|
|
||||||
|
We need logic in the `Damage` method to detect when they monster died and a reference to an Event we want to raise when that happens:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityAtoms.BaseAtoms;
|
||||||
|
|
||||||
|
public class LogHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntReference health;
|
||||||
|
[SerializeField]
|
||||||
|
private IntConstant startHealth;
|
||||||
|
[SerializeField]
|
||||||
|
private VoidBaseEventReference death;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
health.Value = startHealth.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Damage(int damage)
|
||||||
|
{
|
||||||
|
if(health.Value > 0)
|
||||||
|
{
|
||||||
|
health.Value -= damage;
|
||||||
|
|
||||||
|
if(health.Value <= 0)
|
||||||
|
{
|
||||||
|
death.Event.Raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportHealth(int health)
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " reports: " +
|
||||||
|
"My health changed to " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the `Monster` prefab's `LogHealth` component, select `Use Event Instancer` on the `Death` Event reference field:
|
||||||
|
|
||||||
|
![death-event-reference](../assets/event-instancer/death-event-reference.png)
|
||||||
|
|
||||||
|
Add a `Void Event Instancer` component and a create a `Void Event` base Event in an asset folder. Select the base Event asset in the `Base` field and drag the `Void Event Instancer` component on the `Death` field of `LogHealth`:
|
||||||
|
|
||||||
|
![death-event-assigned](../assets/event-instancer/death-event-assigned.png)
|
||||||
|
|
||||||
|
## Listening for the instantiated `Death` Event
|
||||||
|
|
||||||
|
To demonstrate the uniqueness of the `Death` Event to each prefab, we'll add a method to `LogHealth` called `AnnounceDeath` that logs a customizable death message for the monster:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityAtoms.BaseAtoms;
|
||||||
|
|
||||||
|
public class LogHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntReference health;
|
||||||
|
[SerializeField]
|
||||||
|
private IntConstant startHealth;
|
||||||
|
[SerializeField]
|
||||||
|
private VoidBaseEventReference death;
|
||||||
|
[SerializeField]
|
||||||
|
private string deathMessage;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
health.Value = startHealth.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AnnounceDeath()
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " " + deathMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Damage(int damage)
|
||||||
|
{
|
||||||
|
if(health.Value > 0)
|
||||||
|
{
|
||||||
|
health.Value -= damage;
|
||||||
|
|
||||||
|
if(health.Value <= 0)
|
||||||
|
{
|
||||||
|
death.Event.Raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportHealth(int health)
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " reports: " +
|
||||||
|
"My health changed to " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now add a `Void Base Event Reference Listener` to `Monster` prefab. Wire it to the `Void Event Instancer` component and `AnnounceDeath` method using what you learned in this tutorial. The end result looks like this:
|
||||||
|
|
||||||
|
![death-listener](../assets/event-instancer/death-listener.png)
|
||||||
|
|
||||||
|
## Testing the `Event Instancer`
|
||||||
|
|
||||||
|
Since all of the work was done in a base prefab `Monster`, the changes have automatically propagated to the `Wyvern` and `Orc` variants. Visit the variants and write a different death message for each in the `deathMessage` field of `LogHealth`:
|
||||||
|
|
||||||
|
![orc-death-message](../assets/event-instancer/orc-death-message.png)
|
||||||
|
|
||||||
|
Run the project and inflict damage using the `Raise` button in `HurtAllMonsters` Event's Inspector. Eventually the monsters will run out of health one by one and they should announce their unique death messages:
|
||||||
|
|
||||||
|
![unique-death-messages](../assets/event-instancer/unique-death-messages.png)
|
||||||
|
|
||||||
|
That's it! You've created an extensible system where every new prefab variant can easily be customized and all the Atoms included in the prefab are automatically wired to correct responses. Use the monster's death Event Listener to play a sound, play an animation, and add experience points for the player. Only your imagination is the limit!
|
54
docs/tutorials/events.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
id: events
|
||||||
|
title: Events
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Events
|
||||||
|
---
|
||||||
|
|
||||||
|
# Events
|
||||||
|
|
||||||
|
Events are things that happens in our game that other scripts or entities could listen and subscribe to. Events are (like Variables) also Scriptable Objects that live outside of a specific scene. In Unity Atoms Events can be of different types and thereby pass along data to listeners. Variables do by default have the possibility to raise two specific Events:
|
||||||
|
|
||||||
|
- `Changed` — raised every time a Variable’s value is changed. The Event contains the new value.
|
||||||
|
- `Changed With History` — also raised every time a Variable’s value is changed. However, this Event contains both the new and the old value.
|
||||||
|
|
||||||
|
This makes it easier to make our game more data driven than just using Variables. Lets take a look at how that looks in our last example. We can create a new `IntEvent` as a `.asset` file by right clicking and go _Create / Unity Atoms / Event / Int_ and name it `HealthChangedEvent`:
|
||||||
|
|
||||||
|
![health-changed-event](../assets/events/health-changed-event.png)
|
||||||
|
|
||||||
|
And then drop it on our `IntVariable` for the player’s health like this:
|
||||||
|
|
||||||
|
![int-variable_player-health-v2](../assets/events/int-variable_player-health-v2.png)
|
||||||
|
|
||||||
|
We can then modify our `HealthBar.cs` script to look like this:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class HealthBar : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntEvent HealthChangedEvent;
|
||||||
|
[SerializeField]
|
||||||
|
private IntVariable MaxHealth;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
HealthChangedEvent.Register(this.ChangeFillAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
HealthChangedEvent.Unregister(this.ChangeFillAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeFillAmount(int health)
|
||||||
|
{
|
||||||
|
GetComponent<Image>().fillAmount = 1.0f * health / MaxHealth.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then inject the `HealthChangedEvent` to our `HealthBar` component:
|
||||||
|
|
||||||
|
![healthbar-script-v2](../assets/events/healthbar-script-v2.png)
|
||||||
|
|
||||||
|
We now react to global state changes instead of checking the Variable value each Update tick. In other words we only update our `Image` component when we actually need to. That is pretty sweet!
|
@ -7,13 +7,17 @@ sidebar_label: Generator
|
|||||||
|
|
||||||
# Generator
|
# Generator
|
||||||
|
|
||||||
The Generator is a powerful tool that allows you to generate your own Atoms fast and easy via an Unity editor window. It liberates you from writing otherwise tedious boilerplate code and lets you instead focus on what is important. The Generator can be found by clicking on _Tools / Unity Atoms / Generator_ in the top menu bar:
|
Almost any project will eventually have custom data or reference types. These types can also be used as Unity Atoms variables or passed along as event data. Use the generator to create atoms for your own project specific types.
|
||||||
|
|
||||||
![generator_top-bar-menu](assets/generator_top-bar-menu.png)
|
The Generator is a powerful tool that allows you to generate your own Atoms fast and easy via an Unity editor window. It liberates you from writing otherwise tedious boilerplate code and lets you instead focus on what is important.
|
||||||
|
|
||||||
|
The Generator can be found by clicking on _Tools / Unity Atoms / Generator_ in the top menu bar:
|
||||||
|
|
||||||
|
![generator_top-bar-menu](../assets/generator/top-bar-menu.png)
|
||||||
|
|
||||||
The Generator looks like this:
|
The Generator looks like this:
|
||||||
|
|
||||||
![generator_window](assets/generator_window.png)
|
![generator_window](../assets/generator/window.png)
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ By default the Atoms that gets generated will be under the namespace `UnityAtoms
|
|||||||
|
|
||||||
### Type(s) to generate
|
### Type(s) to generate
|
||||||
|
|
||||||
This is a list of Atom types that you want to generate. Simply select the Atoms that you want to generate. Some Atoms depends on other Atoms. If you unselect an Atom that other Atoms depends on, then the Generator will unselect those depending Atoms. Below you find the dependency graph:
|
This is a list of Atom types that you want to generate. Simply select the Atoms that you want to generate. Some Atoms depend on other Atoms. If you unselect an Atom that other Atoms depend on, then the Generator will unselect those depending Atoms. Below you find the dependency graph:
|
||||||
|
|
||||||
- Pair Action - depends on Pair
|
- Pair Action - depends on Pair
|
||||||
- Pair Event - depends on Pair
|
- Pair Event - depends on Pair
|
35
docs/tutorials/listeners.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
id: listeners
|
||||||
|
title: Listeners
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Listeners
|
||||||
|
---
|
||||||
|
|
||||||
|
# Listeners
|
||||||
|
|
||||||
|
There is still an issue that the `HealthBar.cs` script is in charge of registering itself as a listener and at the same time defining what happens when a Event is raised. We need to seperate its concerns! This brings us to the third concept of Unity Atoms, Listeners. A Listener listens (sometimes also referred to as observes or subscribes) to an Event and responds by firing off zero to many responses. Listeners are MonoBehaviours and therefore live in a scene. They can be seen as the glue between Events and Actions.
|
||||||
|
|
||||||
|
The `HealthBar.cs` script from our last example is actually a Listener, but a very specific implementation of it. We can do better than that! Lets create a Game Object in our scene and call it `HealthListener`. Unity Atoms comes with some predefined Listeners. In this case we want to listen to an `IntEvent` so we will press the Add Component button on our `HealthListener`, create an IntListener and drop in the `HealthChangedEvent`:
|
||||||
|
|
||||||
|
![health-listener](../assets/listeners/health-listener-v1.png)
|
||||||
|
|
||||||
|
We can now shave off some of the code in our `HealthBar.cs` script to look like this:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class HealthBar : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntVariable MaxHealth;
|
||||||
|
|
||||||
|
public void ChangeFillAmount(int health)
|
||||||
|
{
|
||||||
|
GetComponent<Image>().fillAmount = 1.0f * health / MaxHealth.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then go back to our `HealthListener`’s IntListener component, press the `+` to add an Unity Event Response, drop in the `HealthBar` component (from the scene) and point out the `ChangeFillAmount` function defined above:
|
||||||
|
|
||||||
|
![health-listener](../assets/listeners/health-listener-v2.png)
|
||||||
|
|
||||||
|
The `HealthBar.cs` script is now only responsible for what happens when our player’s health is changing. Pretty great, huh?
|
35
docs/tutorials/mono-hooks.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
id: mono-hooks
|
||||||
|
title: Mono Hooks
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Mono Hooks
|
||||||
|
---
|
||||||
|
|
||||||
|
# Mono Hooks
|
||||||
|
|
||||||
|
Mono Hooks save the effort of writing boilerplate code to raise Atoms Events from [Unity's Event Functions](https://docs.unity3d.com/Manual/EventFunctions.html).
|
||||||
|
|
||||||
|
A great use for Mono Hooks in our example would allow us to remove the `Harmful.cs` script created earlier. We could instead attach a `OnTrigger2DHook.cs` to the Harmful GameObject and toggle on `Trigger On Enter` like this:
|
||||||
|
|
||||||
|
![mono-hooks-trigger-2d](../assets/mono-hooks/trigger-2d.png)
|
||||||
|
|
||||||
|
We could then create a Collider2DAction called `DecreasePlayersHealth.cs` and add it to a Collider2D Listener attached to the Harmful GameObject:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class DecreasePlayersHealth : Collider2DAction
|
||||||
|
{
|
||||||
|
public override void Do(Collider2D collider)
|
||||||
|
{
|
||||||
|
if (collider.tag == "Player")
|
||||||
|
{
|
||||||
|
collider.GetComponent<PlayerHealth>().Health.Value -= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
![mono-hooks-listener](../assets/mono-hooks/listener.png)
|
||||||
|
|
||||||
|
There is much less code written and the responses can be edited in the Editor.
|
||||||
|
|
||||||
|
That is it! We have covered the most fundamental pieces of Unity Atoms and a way to use them with Unity's built-in functionality. You can get far with these alone, but there are many more features in Unity Atoms and the subpackages to explore.
|
119
docs/tutorials/variable-instancer.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
id: variable-instancer
|
||||||
|
title: Variable Instancer
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Variable Instancer
|
||||||
|
---
|
||||||
|
|
||||||
|
# Variable Instancer
|
||||||
|
|
||||||
|
Unity Atoms have no problems with being used in a prefab. However, it will quickly become apparent that all instances of the prefab refer to the one and the same Atom asset, such as a specific `IntVariable` in the asset folder.
|
||||||
|
|
||||||
|
For example, changing the value of a Variable applies to all prefab instances that use the Variable. Triggering the `Changed` event also causes all prefab instances to react. While this still has it's uses, the behaviour is not always desirable.
|
||||||
|
|
||||||
|
To break free from global Atoms, Variables can be instantiated as an in-memory copy when a prefab is created. To do this, we need a `VariableInstancer`.
|
||||||
|
|
||||||
|
> **Note:** A `VariableInstancer` is unique to the type of the Variable. The generator can generate a `VariableInstancer` of any type needed. Unity Atoms comes with a set of predefined basic types.
|
||||||
|
|
||||||
|
## Add a Variable Instancer and assign a base Variable
|
||||||
|
|
||||||
|
Start by creating an empty `GameObject` called `Monster` and add an `Int Variable Instancer` component to it:
|
||||||
|
|
||||||
|
![add-int-var-instancer](../assets/variable-instancer/add-int-var-instancer-1.png)
|
||||||
|
|
||||||
|
The `VariableInstancer` needs a base Variable to instantiate. Create any `IntVariable` and use the inspector to add the variable to the `Base` field. You can also click on `Create` to use a shortcut for type-appropriate Variable creation from the instancer itself.
|
||||||
|
|
||||||
|
![add-int-var-instancer](../assets/variable-instancer/add-int-var-instancer-2.png)
|
||||||
|
|
||||||
|
## Listening for `Changed` event
|
||||||
|
|
||||||
|
The `VariableInstancer` instantiates the appropriate `Changed` and `Changed With History` event instances for this Variable. That's why it's possible to listen for the `Changed` event in an `Int Event Reference Listener`. Add an `Int Event Reference Listener` as a component and use the three dots next to `Event Reference` to select `Use Variable Instancer`:
|
||||||
|
|
||||||
|
![use-variable-instancer](../assets/variable-instancer/use-variable-instancer.png)
|
||||||
|
|
||||||
|
Grabbing from the title of the component, drag the `Int Variable Instancer` on the `Event Reference` field:
|
||||||
|
|
||||||
|
![drag-variable-instancer](../assets/variable-instancer/drag-variable-instancer.png)
|
||||||
|
|
||||||
|
This event listener now listens to the instantiated `Changed` event from the `Int Variable Instancer`.
|
||||||
|
|
||||||
|
## `Changed` Event response
|
||||||
|
|
||||||
|
Let's add a simple script to use a method as a `Unity Event Response` in the listener:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class LogHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
public void ReportHealth(int health)
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " reports: " +
|
||||||
|
"My health changed to " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the `LogHealth` component and click `+` on the `Unity Event Response`, drag the `LogHealth` to the response field and choose the `ReportHealth` method from the list:
|
||||||
|
|
||||||
|
![add-log-health](../assets/variable-instancer/add-log-health.png)
|
||||||
|
|
||||||
|
> **Note:** If the method does not appear under the `Dynamic` section with the data type of the listener, the method signature is wrong. The Unity response method can only have one parameter which corresponds to the listener type. Using a method with static parameters will still get called, but won't have access to the event data.
|
||||||
|
|
||||||
|
## Usage in a script
|
||||||
|
|
||||||
|
To demonstrate that the instancer works as intended, let's add a method to `LogHealth` that initializes the health value to a value from an `IntConstant` on `Start`. To use Variable Instancers the type of the health Variable is `IntReference` instead of `IntVariable`:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityAtoms.BaseAtoms;
|
||||||
|
|
||||||
|
public class LogHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntReference health;
|
||||||
|
[SerializeField]
|
||||||
|
private IntConstant startHealth;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
health.Value = startHealth.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportHealth(int health)
|
||||||
|
{
|
||||||
|
Debug.Log(gameObject.name + " reports: " +
|
||||||
|
"My health changed to " + health);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the inspector use the three dots next to `Health` of `LogHealth` and select `Variable Instancer`. As before, drag the same Variable Instancer on the field. Create an `IntConstant` Atom in your asset folder and name it. In this case, it is called DefaultStartHealth and has a value of 100:
|
||||||
|
|
||||||
|
![init-health](../assets/variable-instancer/init-health.png)
|
||||||
|
|
||||||
|
Run the project. The `Monster` `GameObject` reports the health immediately on `Start`. Read the stack trace from bottom to up to see how the calls were made:
|
||||||
|
|
||||||
|
![monster-reports](../assets/variable-instancer/monster-reports.png)
|
||||||
|
|
||||||
|
This however does not prove the instances are unique. You can achieve the same result with global Atoms. Let's see how we can use prefabs to make sure the instancer works.
|
||||||
|
|
||||||
|
## Prefabs
|
||||||
|
|
||||||
|
Drag the `Monster` `GameObject` to an asset folder and create two `Prefab Variants` from the context menu of the `Monster` prefab. Name them `Orc` and `Wyvern`:
|
||||||
|
|
||||||
|
![create-prefabs](../assets/variable-instancer/create-prefabs.png)
|
||||||
|
|
||||||
|
Create two more `IntConstant` Atoms called `WyvernStartHealth` and `OrcStartHealth`:
|
||||||
|
|
||||||
|
![monster-start-healths](../assets/variable-instancer/monster-start-healths.png)
|
||||||
|
|
||||||
|
Open the `Wyvern` prefab variant and replace the `DefaultStartHealth` with `WyvernStartHealth` constant in the `LogHealth` script's inspector:
|
||||||
|
|
||||||
|
![wyvern-variant](../assets/variable-instancer/wyvern-variant.png)
|
||||||
|
|
||||||
|
Do the same for the `Orc` variant and drag an instance of both on the scene and run the project. If everything went according to plan, the console should log the following messages:
|
||||||
|
|
||||||
|
![monsters-report](../assets/variable-instancer/monsters-report.png)
|
||||||
|
|
||||||
|
Both Variable instances are therefore unique. You can now use the Variable Instancer as a Variable in any `IntReference` in your scripts. For example, a damage script would subtract from the Variable value and only this instance of the prefab would have it's `Changed` event triggered.
|
80
docs/tutorials/variables.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
id: variables
|
||||||
|
title: Variables
|
||||||
|
hide_title: true
|
||||||
|
sidebar_label: Variables
|
||||||
|
---
|
||||||
|
|
||||||
|
# Variables and constants
|
||||||
|
|
||||||
|
Below follows a step-by-step example of managing a player's health using Unity Atoms. If you haven't read the [Overview and philosopy](../introduction/overview.md) section you should do that before proceeding.
|
||||||
|
|
||||||
|
_NOTE: This tutorial is based on [this](https://medium.com/@adamramberg/unity-atoms-tiny-modular-pieces-utilizing-the-power-of-scriptable-objects-e8add1b95201) blog post._
|
||||||
|
|
||||||
|
## Decouple your scripts using Variables and Constants
|
||||||
|
|
||||||
|
Variables are storing data, for example primitives, reference types or structs as Scriptable Objects. Because Variables are stored as Scriptable Objects they are not part of any scene, but could instead be seen as part of the game’s global shared state. Variables are designed to make it easy to inject them (via the Unity Inspector) and share them between your MonoBehaviours. A Constant is a trimmed down version of a Variable and it's value can't be altered at runtime. Lets see an example on how to use Variables and Constants!
|
||||||
|
|
||||||
|
Imagine you have a `PlayerHealth.cs` script that contains the health of the game’s player. We will attach the script to a `GameObject` with a `SpriteRenderer`, `BoxCollider2D` and a `Rigidbody2D` called Player. The health is represented by an int, which corresponds to an `IntVariable` in Unity Atoms. The script will look like this:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class PlayerHealth : MonoBehaviour
|
||||||
|
{
|
||||||
|
public IntVariable Health;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the game the player’s health will decrease when hitting something harmful. We will attach this `Harmful.cs` script to a GameObject called Harmful that also has a `SpriteRenderer` and a `BoxCollider2D` (as a trigger):
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class Harmful : MonoBehaviour
|
||||||
|
{
|
||||||
|
void OnTriggerEnter2D(Collider2D collider)
|
||||||
|
{
|
||||||
|
if (collider.tag == "Player")
|
||||||
|
{
|
||||||
|
collider.GetComponent<Player>().Health.Value -= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally we will add an UI `HealthBar.cs` script that we attach to a `GameObject` (inside a UI Canvas) with a `RectTransforn`, `CanvasRenderer` and UI `Image` component. The `HealthBar.cs` script will update the `Image` representing the health bar when the player’s health is changing:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public class HealthBar : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField]
|
||||||
|
private IntVariable Health;
|
||||||
|
[SerializeField]
|
||||||
|
private IntConstant MaxHealth;
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
GetComponent<Image>().fillAmount = 1.0f * Health.Value / MaxHealth.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since the player's health is going to change at runtime we make `Health` an `IntVariable` while `MaxHealth` is not going to be changed at runtime is therefore created as an `IntConstant`, They are both global assets stored as `.asset` files that could be shared between scripts. To create these `.asset` files we can right click somewhere in the Project window, and go _Create / Unity Atoms / Variables / Int_ to create the Variable and go _Create / Unity Atoms / Constants / Int_ to create the Constant. The Variable looks like this in the Unity Inspector:
|
||||||
|
|
||||||
|
![int-variable_player-health-v1](../assets/variables/int-variable_player-health-v1.png)
|
||||||
|
|
||||||
|
And the Constant looks like this:
|
||||||
|
|
||||||
|
![int-variable_player-health](../assets/variables/int-constant_max-player-health.png)
|
||||||
|
|
||||||
|
The `Developer Description` is a text describing the Variable in order to document it, the `Value` is the actual value of the Variable, and `Old Value` is the last value the Variable had after it was changed via code. `Changed` and `Changed With History` will be explained later in this tutorial. We name the `IntVariable` created to `Health` and the `IntConstant` to `MaxHealth` and set both their initial value to 100. After they are created we can drop them on the `PlayerHealth` and `HealthBar` components via Unity’s inspector like this:
|
||||||
|
|
||||||
|
![player-health-script](../assets/variables/player-health-script.png)
|
||||||
|
|
||||||
|
![healthbar-script-v1](../assets/variables/healthbar-script-v1.png)
|
||||||
|
|
||||||
|
Variables gives us a way of separating our game’s shared state from the actual implementation. It also makes our code less coupled since we do not need to reference other MonoBehaviours in our scripts, eg. we do not need to reference the `PlayerHealth.cs` script in our `HealthBar.cs` script like this:
|
||||||
|
|
||||||
|
```cs
|
||||||
|
[SerializeField]
|
||||||
|
private PlayerHealth playerHealth;
|
||||||
|
```
|
||||||
|
|
||||||
|
Hurray for less coupled code! 🎉
|
@ -61,18 +61,6 @@
|
|||||||
"title": "UnityAtoms.UI",
|
"title": "UnityAtoms.UI",
|
||||||
"sidebar_label": "UnityAtoms.UI"
|
"sidebar_label": "UnityAtoms.UI"
|
||||||
},
|
},
|
||||||
"introduction/advanced-example": {
|
|
||||||
"title": "Advanced example",
|
|
||||||
"sidebar_label": "Advanced example"
|
|
||||||
},
|
|
||||||
"introduction/basic-tutorial": {
|
|
||||||
"title": "Basic tutorial",
|
|
||||||
"sidebar_label": "Basic tutorial"
|
|
||||||
},
|
|
||||||
"introduction/generator": {
|
|
||||||
"title": "Generator",
|
|
||||||
"sidebar_label": "Generator"
|
|
||||||
},
|
|
||||||
"introduction/overview": {
|
"introduction/overview": {
|
||||||
"title": "Overview and philosopy",
|
"title": "Overview and philosopy",
|
||||||
"sidebar_label": "Overview and philosopy"
|
"sidebar_label": "Overview and philosopy"
|
||||||
@ -85,10 +73,6 @@
|
|||||||
"title": "Quick start",
|
"title": "Quick start",
|
||||||
"sidebar_label": "Quick start"
|
"sidebar_label": "Quick start"
|
||||||
},
|
},
|
||||||
"introduction/unirx": {
|
|
||||||
"title": "Usage with UniRX",
|
|
||||||
"sidebar_label": "Usage with UniRX"
|
|
||||||
},
|
|
||||||
"README": {
|
"README": {
|
||||||
"title": "README"
|
"title": "README"
|
||||||
},
|
},
|
||||||
@ -119,6 +103,46 @@
|
|||||||
"subpackages/ui": {
|
"subpackages/ui": {
|
||||||
"title": "UI",
|
"title": "UI",
|
||||||
"sidebar_label": "UI"
|
"sidebar_label": "UI"
|
||||||
|
},
|
||||||
|
"tutorials/actions": {
|
||||||
|
"title": "Actions",
|
||||||
|
"sidebar_label": "Actions"
|
||||||
|
},
|
||||||
|
"tutorials/advanced-example": {
|
||||||
|
"title": "Advanced example",
|
||||||
|
"sidebar_label": "Advanced example"
|
||||||
|
},
|
||||||
|
"tutorials/event-instancer": {
|
||||||
|
"title": "Event Instancer",
|
||||||
|
"sidebar_label": "Event Instancer"
|
||||||
|
},
|
||||||
|
"tutorials/events": {
|
||||||
|
"title": "Events",
|
||||||
|
"sidebar_label": "Events"
|
||||||
|
},
|
||||||
|
"tutorials/generator": {
|
||||||
|
"title": "Generator",
|
||||||
|
"sidebar_label": "Generator"
|
||||||
|
},
|
||||||
|
"tutorials/listeners": {
|
||||||
|
"title": "Listeners",
|
||||||
|
"sidebar_label": "Listeners"
|
||||||
|
},
|
||||||
|
"tutorials/mono-hooks": {
|
||||||
|
"title": "Mono Hooks",
|
||||||
|
"sidebar_label": "Mono Hooks"
|
||||||
|
},
|
||||||
|
"tutorials/unirx": {
|
||||||
|
"title": "Usage with UniRX",
|
||||||
|
"sidebar_label": "Usage with UniRX"
|
||||||
|
},
|
||||||
|
"tutorials/variable-instancer": {
|
||||||
|
"title": "Variable Instancer",
|
||||||
|
"sidebar_label": "Variable Instancer"
|
||||||
|
},
|
||||||
|
"tutorials/variables": {
|
||||||
|
"title": "Variables",
|
||||||
|
"sidebar_label": "Variables"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@ -128,6 +152,7 @@
|
|||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"Introduction": "Introduction",
|
"Introduction": "Introduction",
|
||||||
|
"Tutorials": "Tutorials",
|
||||||
"API Reference": "API Reference",
|
"API Reference": "API Reference",
|
||||||
"Subpackages": "Subpackages"
|
"Subpackages": "Subpackages"
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,38 @@
|
|||||||
"Introduction": [
|
"Introduction": [
|
||||||
"introduction/quick-start",
|
"introduction/quick-start",
|
||||||
"introduction/overview",
|
"introduction/overview",
|
||||||
"introduction/basic-tutorial",
|
|
||||||
"introduction/generator",
|
|
||||||
"introduction/unirx",
|
|
||||||
"introduction/advanced-example",
|
|
||||||
"introduction/preferences"
|
"introduction/preferences"
|
||||||
],
|
],
|
||||||
|
"Tutorials": [
|
||||||
|
{
|
||||||
|
"type": "subcategory",
|
||||||
|
"label": "Basics",
|
||||||
|
"ids": [
|
||||||
|
"tutorials/variables",
|
||||||
|
"tutorials/events",
|
||||||
|
"tutorials/listeners",
|
||||||
|
"tutorials/actions",
|
||||||
|
"tutorials/mono-hooks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "subcategory",
|
||||||
|
"label": "Intermediate",
|
||||||
|
"ids": [
|
||||||
|
"tutorials/variable-instancer",
|
||||||
|
"tutorials/event-instancer",
|
||||||
|
"tutorials/generator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "subcategory",
|
||||||
|
"label": "Advanced",
|
||||||
|
"ids": [
|
||||||
|
"tutorials/advanced-example",
|
||||||
|
"tutorials/unirx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"API Reference": [
|
"API Reference": [
|
||||||
"api/unityatoms",
|
"api/unityatoms",
|
||||||
"api/unityatoms.editor",
|
"api/unityatoms.editor",
|
||||||
|