Schemas
Data files normally can only consist of a few simple types like Strings and Numbers.
In your game, you will sometimes want more complex types, for example Symbols, Colors or Assets.
In your import statement, use the as keyword to convert your data file to a given schema:
// Everything after the 'as' keyword is the schema
pub import Level1Data from @level1.json as {
width as Number,
height as Number,
entities as [{
type as Symbol,
image as Asset,
pos = @(x, y),
quantity? = qty as Number,
}],
}
Below is an example JSON file that could be imported using the schema above:
{
"width": 28,
"height": 42,
"entities": [
{
"type": "cheese",
"image": "cheese.svg",
"x": 10,
"y": 20,
"qty": 5
},
{
"type": "mouse",
"image": "mouse.svg",
"x": 30,
"y": 40,
}
]
}
This is an example main.easel file that uses the imported data to spawn entities in the game world:
pub game fn World.Main() {
for entity in Level1Data.entities {
match entity.type {
$cheese => Spawn unit {
Cheese(pos=entity.pos, image=entity.image, quantity=entity.quantity)
},
$mouse => Spawn unit {
Mouse(pos=entity.pos, image=entity.image)
}
}
}
}
List of Available Types
This section lists all supported types you can use with an import ... as statement.
Arrays
Use square brackets [ ] to import data into an Array.
Within the brackets [ ], you can specify a schema to which all elements in the Array will be converted:
pub import Level1Data from @level1.json as {
rings as [], // no conversion, any Array will be accepted
tiles as [Number], // all elements of the Array will be converted to a Number
entities as [{
type as Symbol,
power as Number,
}], // all elements of the Array will be converted to a Map of the specified schema
}
Maps
Use curly braces { } to import data into a Map.
Within the braces { }, you specify a list of fields, and the schema to use for each field.
pub import Level1Data from @level1.json as {
width as Number,
height as Number,
entities as [{
name as String,
type as Symbol,
image as Asset,
}],
}
There are a number of advanced ways to specify a field in a Map schema. These are detailed below.
Unconverted fields
Simply omit the as clause to import a field without any conversion:
pub import HeroData from @hero.json as {
name, // no 'as', so will be imported as-is, without any conversion
}
Renaming fields
Use = to import a field from the data file under a different name in Easel:
pub import HeroData from @hero.json as {
agility = agi as Number,
}
Optional fields
Normally, if a field is missing or null in the data file, Easel will throw a compile-time error.
To avoid this, use ? to mark a field as optional.
pub import HeroData from @hero.json as {
quantity? as Number,
wisdom? = wis as Number,
}
Nested fields
You can import nested fields from a Map in the data file by using dot notation:
pub import HeroData from @hero.json as {
strength = attributes.strength as Number,
}
This is an example of a JSON file that could be imported using the schema above:
{
"attributes": {
"strength": 10
}
}
Quoted field names
If you need to refer to fields that would be invalid identifiers in Easel, you can use quotes to refer to them:
pub import Hero from @hero.json as {
dexterity = 'Dexterity-Points' as Number,
}
Both single quotes ' and double quotes " can be used, but they must match.
Where possible, we recommend using single quotes here for field names, because double quotes look like String literals.
Map key types
Any Keyable value can be used as a map key. Below is an example of using Colors as map keys:
pub import TeamNameByColorLookup from @teamNames.json as {
// Using Colors as map keys
#ff0000 = redTeamName as String,
#0000ff = blueTeamName as String,
}
pub game fn World.Main() {
SpawnEachPlayer owner {
PlayerColor = PickRandom(TeamNameByColorLookup.MapKeys)
Print {
"You have joined the " + TeamNameByColorLookup[PlayerColor] + " team!"
}
}
}
Here is an example of a JSON file that could be imported using the schema above:
{
redTeamName: "Red Rubicons",
blueTeamName: "Blue Bibliophiles",
}
Assets
Let's say each row of your data file represents an item that can be collected in your game. In this case, one of the fields may reference an associated image Asset file that will depict the item in the game world.
pub import Level1Data from @level1.json as {
entities as [{
image as Asset,
}],
}
When converting from String to Asset, the Asset will be matched using the same fuzzy matching process used for resolving Asset literals in Easel code. That is:
- You can specify just the filename or part of the path and Easel will search your project to find the best match.
If multiple files match, for example if there are multiple
fireball.svgfiles, the one closest to the data file will be selected. - You can use wildcard patterns to match multiple files, for example
@fireball-*.svgwill matchfireball-1.svgandfireball-2.svg.
Here is an example of a JSON file that could be imported using the schema above:
{
"entities": [
{ "image": "cheese.svg" },
{ "image": "waterfall-*.png" },
{ "image": "rings/warrior.png" },
]
}
If a matching asset file cannot be found, and your field is non-optional, this will cause a compile-time error.
To ignore this error, make your field optional: image? as Asset.
Colors
pub import Level1Data from @level1.json as {
backgroundColor as Color,
}
- A String will be parsed as a hex code (e.g.
#ff0000,#f00,#ff880044). - A Number will be parsed as a 24-bit integer (e.g.
0xff0000) where the highest 8 bits are the red component, the next 8 bits are the green component, and the lowest 8 bits are the blue component. So0xff0000is red.
Keycodes
pub import SpecialAbilityData from @abilities.json as {
btn as Keycode,
}
This will match a String to one of the available Keycodes.
If the String does not match any of the available Keycodes,
and your field is non-optional, this will cause a compile-time error.
To ignore the error, make your field optional: btn? as Keycode,
Numbers
pub import Level1Data from @level1.json as {
numEnemies as Number,
}
Strings
pub import Level1Data from @level1.json as {
levelName as String,
}
Symbols
pub import RingData from @rings.csv as {
type as Symbol,
power as Number,
}
When a String is converted to a Symbol, Easel will either create or reuse the Symbol with the same name as the String. This always succeeds, even if the String contains characters which would normally be considered invalid for an Easel identifier.
- If your code happens to use Symbols with the same name, then you will find that the imported Symbol is equal to the Symbol in your code.
- If other data files use the same Symbol name, then they will also be equal to each other.
There is no issue with Symbol names containing invalid characters because the only thing that matters is whether it is equivalent to another Symbol or not, regardless of what name it has.
It is possible for data files to create and use Symbols that do not exist anywhere in your Easel code. If necessary, you can use functions like SymbolName or SymbolFromName to convert between Symbols and Strings at runtime.
Inline symbols
When a String beginning with a lowercase letter is converted to a Symbol,
it is like specifying an inline symbol with the $ prefix in Easel code.
For example, if we have the following CSV data file with lowercase symbol names:
type,power
fire,10
water,20
Importing this with the schema above would be similar to declaring the following constant in Easel code:
pub const RingData = [
{ type = $fire, power = 10 },
{ type = $water, power = 20 },
]
Static symbols
Let's say we have the following CSV data file with symbol names beginning with uppercase letters:
type,power
Fire,10
Water,20
Let's say we have also declared the following symbols somewhere in our Easel code:
pub symbol Fire
pub symbol Water
Importing the above CSV file with the schema above would be similar to declaring RingData like this in Easel code:
pub const RingData = [
{ type = Fire, power = 10 },
{ type = Water, power = 20 },
]
Non-existent symbols
Let's reuse the above example, but let's say the Water symbol has not been declared anywhere in our Easel code.
We will only declare the Fire symbol:
pub symbol Fire
// Water symbol not declared
In this case, RingData will still contain a Symbol for Water,
but nothing in our code will be equal to it.
We could use SymbolFromName("Water") to look up the Symbol for Water at runtime.
This would return the same Symbol as the one in RingData, even though it was not declared anywhere in our code.
pub game fn World.Main() {
Assert(SymbolFromName("Water") == RingData[1].type) // true
// compile-time error because Water symbol not declared in Easel code
Assert(Water == RingData[1].type)
}
See also: SymbolFromName.
Vectors
Vectors can be imported using the @(x, y) syntax, which constructs a Vector from two different fields in the data file:
pub import Level1Data from @level1.json as {
entities as [{
pos = @(x, y),
}],
}
Here is an example of a JSON file that could be imported using the schema above:
{
"entities": [
{
"x": 10,
"y": 20
}
]
}
Compile Time
The data file is parsed at compile time, and if the data does not match the schema you specify, Easel will throw a compile-time error. This helps you design data files and schemas that are consistent with each other.
Only the data that is referenced by the schema will be imported, and everything else will be ignored. This minimizes the amount of data that is needed to be loaded into memory at runtime, which can improve performance and reduce memory usage.