Skip to main content

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 the asteroid-images.zip file into the images folder. Easel will automatically unzip all the images into your images folder 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 the images folder.

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.

note

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.

info

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)
}
info

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.

info

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!

info

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.