Spawning Asteroids
Time for some danger! Let's add some asteroids to the game.
Importing assets
First, we need to import the images we will use for our asteroids.
Download the following file by right-clicking the link and choosing Save Link As (Google Chrome) or Download Linked File (Safari):
For variety, there are be multiple images for each size of asteroid, and so this file contains multiple images zipped together. Depending on your browser and operating system, the file may have automatically been unzipped for you, or it may still be in zip format. What you do next depends on which of these two cases applies to you.
Go to your Downloads folder and:
-
If you see a file called
asteroid-images.zip: Drag-and-drop theasteroid-images.zipfile into theimagesfolder. Easel will automatically unzip all the images into yourimagesfolder for you. -
If you see a folder called
asteroid-images: Open the folder, select all the files inside, and drag-and-drop them all into theimagesfolder.
You should now see a bunch of new files in your images folder with names like asteroid-big-1.png, asteroid-med-3.png, etc.
Click on a few of them to see the different asteroid images in the preview pane.
These assets come from the Space Shooter Remastered asset pack from Kenney, an excellent resource for free game assets!
Defining an asteroid
Now, we will create the Asteroid function which will define how our asteroids look and behave.
In our game, we will have 4 different sizes of asteroids from big to tiny, and when you shoot a big asteroid,
it will break into smaller asteroids.
Our one Asteroid function will take a division parameter which lets you choose which of the four sizes of asteroids
you would like it to create.
Create a new file called asteroid.easel and copy-and-paste the highlighted code snippet into it:
pub fn unit.Asteroid(pos, parent?, division=1) {
use body=unit
let radius = 3 / division
let speed = 2 * division
let image = match division {
1 => PickRandom(@asteroid-big-*.png),
2 => PickRandom(@asteroid-med-*.png),
3 => PickRandom(@asteroid-small-*.png),
4 => PickRandom(@asteroid-tiny-*.png),
}
Body(
pos=,
velocity = speed * RandomVector,
heading=1rev*Random,
turnRate=0.1rev*SignedRandom,
)
BoundaryWrappingSystem(radius=)
PolygonCollider(shape=Circle(radius=), category=Category:Asteroid)
ImageSprite(image=, radius=, shadow=0.5)
}
If you try to Preview this code now, it will give you a "unknown identifier" error
because we are referring to a Category:Asteroid that we have not yet defined.
We will fix that in the next section.
We have multiple images for each size of asteroid. PickRandom is a function that lets us choose a random element from an array, in this case our arrays of image assets.
Adding a category
Every physical object needs a category so the physics engine can determine what should collide with what.
Switch to categories.easel. Insert the highlighted code snippet at the bottom:
pub tangible category Category:Ship
pub tangible category Category:Projectile
pub tangible category Category:Asteroid
Asteroid field system
Now that we have defined what an asteroid is, we can spawn them.
To do this, we will create an AsteroidFieldSystem function that will continuously spawn asteroids
from the edge of the screen.
Create a new file called asteroidField.easel. Copy-and-paste the highlighted code snippet into it:
pub fn this.AsteroidFieldSystem() {
on Tick(1s) {
if QueryCount(filter=Category:Asteroid) < 20 {
let radius=5, pos=RandomBoundaryPos(radius=)
if pos.QueryAnyWithinRadius(radius=) { continue }
Spawn unit {
Asteroid(pos=)
}
}
}
}
fn RandomBoundaryPos([radius]) -> vector {
return @(BoundaryRadius + radius, SignedRandom*BoundaryRadius).Rotate(Random.Quantize(0.25) * 1rev)
}
The QueryCount function counts the number of entities in the world that match particular criteria.
Using the AsteroidFieldSystem
Finally, we need to add our AsteroidFieldSystem to an entity so it can start spawning asteroids.
Let's just add it to our World entity because at this point,
we just want to spawn asteroids forever without stopping.
Switch to main.easel. Insert the highlighted code snippet into your Main function at the bottom:
pub game fn World.Main() {
ImageSprite(body=@(0, 0), image=@backdrop.png, radius=7, repeatX=100, repeatY=100, layer=-100)
Camera(@(0,0), radius=BoundaryRadius)
Viewport(aspectRatio=1)
ButtonRemap(KeyW, ArrowUp)
ButtonRemap(KeyA, ArrowLeft)
ButtonRemap(KeyS, ArrowDown)
ButtonRemap(KeyD, ArrowRight)
SpawnEachPlayer owner {
Subspawn unit {
Ship
}
}
AsteroidFieldSystem
}
Click Preview to test your game. You should see asteroids starting to spawn from the edges of the screen. You can shoot at them with your lasers, but they won't do anything when you hit them just yet.
AsteroidFieldSystem is another example of a system - a function
that adds ongoing behavior to an entity.
We added AsteroidFieldSystem to our World entity,
which means it will run forever, continuing to spawn asteroids as long as the game is running.
See Systems to learn more about this important concept.
Recap
In this chapter, you made a new Asteroid entity,
and an AsteroidFieldSystem to spawn them.
In the next section, we will make it so that you can blow up asteroids with your lasers!
You may have noticed we declared Asteroid as fn unit.Asteroid and Ship as fn unit.Ship.
In both cases, we named our entity unit, as opposed to naming them specifically asteroid or ship.
In Easel, you generally want to name entities after the main role they play in the game.
The word unit has no special meaning to Easel,
it is simply the name we have chosen to this kind of entity in our game.
If we were to add aliens, satellites, or starbases to our game, they would probably also be unit as well.
See Roles to learn more on this topic.