Collectors
A collector is a special kind of property that collects values from multiple entities, and then merges them into a single value. It can be used to create temporary effects like speed boosts or damage buffs. It can also be used to create a collection of values from across the world, such as a list of buildings that belong to a player.
Declaration
A collector is declared using the collect
keyword:
pub collect owner.FruitBasket
This declares a collector called FruitBasket
on the owner
Entity.
When written like this without any additional parameters,
it will simply collect all values it is given into an Array.
Giving values to the collector
Entities give values to a collector using the give
keyword.
give owner.FruitBasket("Apple")
This makes the current entity this
give the String value "Apple"
to the FruitBasket
on the owner
Entity.
Note that the current entity this
is implicitly found from context.
Whenever an entity despawns, any values it was giving are removed.
This is a powerful feature of collectors that makes it easy to manage temporary effects.
For example, this is how we can give an "Apple"
that only lasts for 5 seconds:
Spawn {
give owner.FruitBasket("Apple")
once Tick(5s) { Expire }
}
Omitting the object parameter
If omitted, the object parameter will be found from context, if possible:
SpawnEachPlayer owner {
give FruitBasket("Apple") // `owner` omitted, will be found from context
}
Reading the value of the collector
The value of the collector can be read by simply calling it directly.
let fruits = owner.FruitBasket
This returns an Array of all the values in the FruitBasket
.
Waiting for change
You can use the await
keyword on the collector to wait until it changes.
let fruits = await owner.FruitBasket
The above line will suspend execution of the current function until FruitBasket
changes,
then it will return its new value.
More commonly, you would use a with, on or once block to wait on a separate thread, rather than awaiting it directly:
with FruitBasket {
"You have: " + FruitBasket.Join(", ")
}
Merge functions
By default, collect
will just return an Array of all its collected values.
Sometimes, you want to take all the collected values and merge them into a single value.
For example, you might want to know the Sum of all the active boosts belonging to a player.
To do this, you can provide a merge function after a ~
character.
The function will be given an array of values as input, and should return a value.
pub collect owner.NumBoosts ~ Sum
The above code merges all the collected values into a single Number value using the Sum function.
Now, whenever the NumBoosts
collector is read, it will return the sum of all the collected values.
Any function that takes an array of values and returns a value can be used as a merge function. You can even write your own merge function like this:
pub collect owner.PowerLevel ~ SumExtremes
fn SumExtremes(values) -> value {
return values.FindMax + values.FindMin
}
Common built-in functions that are useful for merging include: Sum, Count, FindMax and FindMin.
Inline custom merge functions
If needed, you can specify your own merge function inline using the Callback syntax:
pub collect owner.AbsorbProportion ~ |values| values.FindMin ?? 1
Picking out one value
Sometimes, you only need to look at one of the values in the collector,
and it doesn't matter which one.
Easel has a special syntax for situation.
If you specify a default value for the collector using the =
character,
it will either return the first value if it exists, or the default value you provide:
pub collect owner.IsInvisible = false
Imagine a game where your hero can pick up an invisibility powerup.
When activated, the powerup will give IsInvisible(true)
.
Since all the collected values are the same, it doesn't matter which one we look at.
We can just check the first value in the collector to see if the hero is invisible,
and we don't need to look at the rest.
In this situation, the above code snippet will return either true
if we have
any invisibility powerup, regardless of how many we have, or false
if we have none.
While you can achieve the same effect using the custom merge function below, using a default value is much more performant as it has a special code path in the engine.
pub collect owner.IsInvisible ~ |values| values[0] ?? false
Managing given values
Deleting given values
If you want to delete a value from a collector, you can use a delete give
statement.
You must provide the same ID to both the give
and delete give
statements.
give<cherry> augustus.FruitBasket("Cherry")
delete give<cherry>
Replacing given values using IDs
Each give
statement must have a unique ID.
However, give
will generate an Automatic IDs
whenever you don't provide one, so most of the time you will not need to worry about this.
Sometimes you may want to use your own ID to replace or delete previous give
s:
give<special> augustus.FruitBasket("Cherry")
give<special> augustus.FruitBasket("Date") // replaces the previous `give<special>`
delete give<special> // deletes the previous `give<special>`
Omitting the give
keyword
If your give
statement only has a value parameter and nothing else,
you can omit the give
keyword. The following two lines are identical:
give FruitBasket("Apple")
FruitBasket("Apple")
This only works in the simple case above.
In all other cases, the give
keyword is required.
If you need an object parameter or an ID,
you must use the give
keyword.
If you get an error must use the 'give' keyword when passing an object argument to a 'collect'
,
what should you do?
You might have written code similar to the following:
augustus.FruitBasket("Apple")
To fix it, change this to:
give augustus.FruitBasket("Apple")
It must be written this way because in Easel,
the object parameter implies the lifespan,
and augustus
is not the lifespan in this case, it is the current entity this
.
Legacy: Buffs
Collectors are a more replacement for the old Buff system. Collectors are more flexible and powerful and so should be used for all new code. Buffs will no longer be updated but will continue to be supported. For advice on how to switch from Buffs to Collectors, see the Buff Migration Guide.
Example: Collecting fruit
Below is an example game where the player can collect fruit.
The fruit are given to the FruitBasket
collector.
pub collect owner.FruitBasket
pub game fn World.Main() {
SpawnEachPlayer owner {
Content {
P { "Press A for an Apple, B for a Banana" }
P {
with FruitBasket {
"You have: " + FruitBasket.Join(", ")
}
}
}
on ButtonDown(KeyA) {
Spawn {
give FruitBasket("Apple") // give an apple to the `FruitBasket`
once Tick(5s) { Expire } // the apple despawns and remove itself from `FruitBasket` after 5 seconds
}
}
on ButtonDown(KeyB) {
Spawn {
give FruitBasket("Banana") // give a banana to the `FruitBasket`
once Tick(5s) { Expire } // the banana despawns and remove itself from `FruitBasket` after 5 seconds
}
}
}
}
Notice that when the fruit expires after 5 seconds, it is automatically removed from the FruitBasket
.
This is a powerful feature of collectors that makes it easy to manage temporary effects.
Example: Total boosts
Here is a full example of a game where the player can collect boosts:
pub collect owner.NumBoosts ~ Sum
pub game fn World.Main() {
SpawnEachPlayer owner {
Content {
P { "Press X to boost!" }
P {
with NumBoosts {
"Your current boost level: " + NumBoosts
}
}
}
on ButtonDown(KeyX) {
Subspawn {
give NumBoosts(1) // give an additional value to `NumBoosts`
once Tick(2s) { Expire } // the boost expires after 2 seconds
}
}
}
}
Example: Invisibility
Below is an example of a game where the player can become invisible.
pub collect owner.IsInvisible = false
pub game fn World.Main() {
SpawnEachPlayer owner {
Content {
P { "Press V to become invisible!" }
with IsInvisible {
if IsInvisible {
"You are currently invisible!"
}
}
}
on ButtonDown(KeyV) {
Subspawn {
give IsInvisible(true) // give invisibility
once Tick(3s) { Expire } // invisibility expires after 3 seconds
}
}
}
}