Skip to main content

Making a Spaceship

Let's make a cool spaceship that you can fly around the screen!

Importing assets

We will need to import an image for our ship, just like we did for our backdrop in the previous section.

Creating a folder for images

We are going to have quite a few images in this project, so let's create a folder to keep them organized.

  1. Click the New Folder button in the bottom left of the editor - look for the button with a folder icon on it.
  2. Name your new folder images and press Enter.
  3. Drag-and-drop the file backdrop.png into your new images folder.

You should now have an images folder with just one file in it - backdrop.png.

Importing the ship image

  1. Download the following file by right-clicking the link and choosing Save Link As (Google Chrome) or Download Linked File (Safari):

  2. Drag-and-drop the file hero.png directly into the images folder in the file list on the left side of the editor.

If you did this correctly, you should be able to see a file called hero.png inside the images folder. When you click on it, you should see a preview of a ship image in the preview pane on the right.

note

This asset comes from the Space Shooter Remastered asset pack from Kenney, an excellent resource for free game assets!

Spawning a ship

Let's start by spawning a ship for each player in the game.

In main.easel, insert the highlighted code snippet below into the pub game fn World.Main function, just below the ImageSprite we added previously.

pub game fn World.Main() {
ImageSprite(body=@(0, 0), image=@backdrop.png, radius=7, repeatX=100, repeatY=100, layer=-100)

SpawnEachPlayer owner {
Subspawn unit {
Ship
}
}
}
warning

Before you continue, make sure the code looks exactly the same as above. If it doesn't, you might get some errors later on that will be hard to understand and fix.

In Easel, everything is about spawning entities and attaching components to them. Let's analyse the code we have just inserted:

  • On Line 4, we spawn an entity to represent our player, giving it the name owner.
  • On Line 5, we spawn an entity to represent our spaceship, and giving it the name unit.
  • On Line 6, we call a Ship function to set up our hero's spaceship entity, which we haven't yet defined but we will do that next.
tip

If you are new to programming, you'll notice that Easel code use indentation to show which block each line of code belongs to. This is not for the computer, it is for you! Keeping your code indented will save you time and headaches later on when you are trying to figure out which block of code is causing an error.

You can use press Tab or Shift+Tab to change the indentation of the selected code. Practice this now:

  1. Select the entire Subspawn unit block - all three lines from Subspawn unit { to the closing }.
  2. Press Tab. Watch how the entire block moves to the right, increasing the indentation.
  3. Press Shift+Tab. Watch how the entire block moves back to the left.

Creating a Ship function

Now, we will create the Ship function that we referred to in our Main function. This function will create the components that make up our spaceship.

Create a new file called ship.easel by following these steps:

  1. Click the New File button in the very bottom left of your editor screen - look for the button with a plus sign (+) on it.
  2. Give your new file the name ship.easel.

Now copy-and-paste the code below into your new ship.easel file:

pub fn unit.Ship([owner]) {
use body=unit
let radius=1

Body(pos=@(0,0))
ImageSprite(@hero.png, radius=1.5*radius, angleOffset=0.25rev, shadow=0.5)
}

Now click Preview. You should see a ship in the middle of the screen. It's a bit small! We will fix that next. Click Exit Preview in the top left to return to the editor.

info

This code adds a Body component to the unit entity, giving it a position in the world. It also adds an ImageSprite component, which will display our ship's image attached to the Body. Wherever the body moves, the sprite will move with it.

Zooming in

You will have noticed the ship was a bit small, so let's zoom in. The normal camera radius is 50, so let's change it to 20 to make everything bigger and easier to see.

Switch to main.easel. Insert the following snippet at the top of your Main function, just below your ImageSprite:

pub game fn World.Main() {
ImageSprite(body=@(0, 0), image=@backdrop.png, radius=7, repeatX=100, repeatY=100, layer=-100)
Camera(@(0,0), radius=20)

SpawnEachPlayer owner {
Subspawn unit {
Ship
}
}
}

Click Preview to test your game. You should see the ship is now bigger!

info

We just added a Camera component to the World entity, setting the position and zoom level of the camera.

Moving with arrow keys

It's time to make our ship move! To do this, we are going to be using Easel's built-in physics engine, which will make the movement feel natural and realistic.

Giving the ship a collider

First we must tell the physics engine the shape of our ship. To do this, we must add a collider.

In ship.easel, insert the highlighted line of code below at the bottom of your Ship function, before the closing brace } at the end.

pub fn unit.Ship([owner]) {
use body=unit
let radius=1

Body(pos=@(0,0))
ImageSprite(@hero.png, radius=1.5*radius, angleOffset=0.25rev, shadow=0.5)
PolygonCollider(shape=Circle(radius=), category=Category:Ship)
}

If you try to preview this code right now, you will get an "unknown identifier" error because it is referring to a Category:Ship that we haven't defined yet. We will do that next.

tip

If you are having trouble getting the indentation to match the example, don't forget you can press Tab or Shift+Tab to change the indentation of the currently selected lines.

info

A standard entity in Easel has three components:

  • A Body, which gives it a position in space.
  • An ImageSprite or PolygonSprite, which gives it a visual representation on screen.
  • A PolygonCollider, which gives it a physical shape that can collide with other entities and push them around.

Because our ship entity has their own Body, the function begins with a use body=ship statement. This makes the sprite, collider and any future components (e.g. sounds, cameras, particle effects) automatically follow the ship's position and rotation. See Context to learn more.

Adding a category

Every physical object needs to be given a category, so that we can later tell the physics engine what is allowed to collide with what. Let's define a category for our ship.

Create a new file called categories.easel. You can do this by clicking the New File button in the bottom left of the editor (look for a button with a + on it). Copy and paste the following code into your new categories.easel file:

pub tangible category Category:Ship

Click Preview to test your game. You should see no errors, but you also won't see any difference in the game yet. But it's good to check regularly to make sure you haven't accidentally introduced any errors accidentally.

info

This category is tangible because we want it to physically collide with other objects in the world, rather than just passing through them. See Categories to learn more.

Accelerate with arrow keys

Now our ship has a shape and mass that the physics engine can understand, we can apply forces to it to make it move. Let's make it so that the ship will accelerate when the arrow keys are pressed.

In ship.easel, insert the following snippet at the bottom of your Ship function, just before the closing brace }.

pub fn unit.Ship([owner]) {
use body=unit
let radius=1

Body(pos=@(0,0))
ImageSprite(@hero.png, radius=1.5*radius, angleOffset=0.25rev, shadow=0.5)
PolygonCollider(shape=Circle(radius=), category=Category:Ship)

on BeforePhysics {
if Joystick == @(0, 0) { continue }
Velocity += 0.25 * Joystick
}
}

Now, click the Preview button at the top right of the editor to test your game. Try pressing the arrow keys to move your ship around the screen! If you prefer, you can also move with the WASD keys, or with a gamepad if you have one connected.

info

The on block is a component that runs a block of code every time it receives a signal. In this case, just before the physics simulation step (see Anatomy of a Tick).

info

By default, Easel maps the arrow keys, WASD keys, and gamepad joysticks to the Joystick property. See Input Unification to learn more.

Engine sparks

Our ship would look a lot cooler if it had some engine sparks coming out of the back when it accelerates.

In ship.easel, insert the following snippet at the bottom of your Ship function, inside the on BeforePhysics block:

pub fn unit.Ship([owner]) {
use body=unit
let radius=1

Body(pos=@(0,0))
ImageSprite(@hero.png, radius=1.5*radius, angleOffset=0.25rev, shadow=0.5)
PolygonCollider(shape=Circle(radius=), category=Category:Ship)

on BeforePhysics {
if Joystick == @(0, 0) { continue }
Velocity += 0.25 * Joystick

Spark(
color=#ff8800, dissipate=0.5s, radius=0.5*radius,
luminous=1, shine=0.5, bloom=1, layer=-5,
splatter=1, velocity=-25*Direction(Heading))
}
}

Click Preview to test your game. Try moving around. You should see some sparks coming out of the back of the ship when it moves!

info

Spark is one of the ways you can create a particle system in Easel.

Steering

You might notice the ship is always pointing to the right. Let's make the ship point towards the mouse cursor.

In ship.easel, insert the following snippet at the bottom of your Ship function, just before the closing brace }.

pub fn unit.Ship([owner]) {
use body=unit
let radius=1

Body(pos=@(0,0))
ImageSprite(@hero.png, radius=1.5*radius, angleOffset=0.25rev, shadow=0.5)
PolygonCollider(shape=Circle(radius=), category=Category:Ship)

on BeforePhysics {
if Joystick == @(0, 0) { continue }
Velocity += 0.25 * Joystick

Spark(
color=#ff8800, dissipate=0.5s, radius=0.5*radius,
luminous=1, shine=0.5, bloom=1, layer=-5,
splatter=1, velocity=-25*Direction(Heading))
}

with Pointer {
Heading = Angle(Pointer - Pos)
}
}

Click Preview to test your game. Move your mouse around, and you should see the ship always points towards the mouse cursor!

info

The Pointer property gives us the position of the mouse cursor in the game world. It is constantly changing as the mouse moves, so we use a with block to always keep the Heading up-to-date with the Pointer.

Function reference

We have now created a spaceship that you can fly around the screen with the mouse! We did this by spawning an entity and attaching various components to it. This was done by writing the code to call various functions, and we even defined a one of our own functions. A full comprehensive list of all functions is always available in the Function Reference. You can always easily find the reference by clicking the Reference link in the toolbar at the top of this page.

tip

Whenever you are working on a game in Easel, keep the Easel Function Reference open in a separate tab so you can easily look up functions as you need them.