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.
- Click the New Folder button in the bottom left of the editor - look for the button with a folder icon on it.
- Name your new folder
imagesand pressEnter. - Drag-and-drop the file
backdrop.pnginto your newimagesfolder.
You should now have an images folder with just one file in it - backdrop.png.
Importing the ship image
-
Download the following file by right-clicking the link and choosing Save Link As (Google Chrome) or Download Linked File (Safari):
-
Drag-and-drop the file
hero.pngdirectly into theimagesfolder 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.
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
}
}
}
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
Shipfunction to set up our hero's spaceship entity, which we haven't yet defined but we will do that next.
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:
- Select the entire
Subspawn unitblock - all three lines fromSubspawn unit {to the closing}. - Press
Tab. Watch how the entire block moves to the right, increasing the indentation. - 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:
- Click the New File button in the very bottom left of your editor screen - look for the button with a plus sign (
+) on it. - 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.
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!
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.
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.
We have just added a PolygonCollider component to our unit entity.
This allows it to collide with other objects in a natural and realistic manner.
Both ImageSprite and PolygonCollider need a body to determine their position in the world.
The use body=unit statement lets us declare it once,
instead of repeatedly passing body=unit separately into both functions.
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.
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 {
let direction = @(0, 0)
if IsButtonDown(ArrowLeft) { direction -= @(1, 0) }
if IsButtonDown(ArrowRight) { direction += @(1, 0) }
if IsButtonDown(ArrowUp) { direction -= @(0, 1) }
if IsButtonDown(ArrowDown) { direction += @(0, 1) }
if direction == @(0, 0) { continue }
Velocity += 0.25 * direction
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))
}
}
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!
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).
We're also using the Spark function here to create a cool engine plume effect. This is one of the ways you can make 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.
Pointing towards the mouse
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 {
let direction = @(0, 0)
if IsButtonDown(ArrowLeft) { direction -= @(1, 0) }
if IsButtonDown(ArrowRight) { direction += @(1, 0) }
if IsButtonDown(ArrowUp) { direction -= @(0, 1) }
if IsButtonDown(ArrowDown) { direction += @(0, 1) }
if direction == @(0, 0) { continue }
Velocity += 0.25 * direction
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!
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.
WASD controls
Many games give players the option to use WASD keys so it easier to keep your left hand on the left side keyboard and right hand on the mouse, for those who prefer that. Let's add support for this too!
Switch back to main.easel. Insert the following snippet at the top of the Main function,
just after your Camera.
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)
ButtonRemap(KeyW, ArrowUp)
ButtonRemap(KeyA, ArrowLeft)
ButtonRemap(KeyS, ArrowDown)
ButtonRemap(KeyD, ArrowRight)
SpawnEachPlayer owner {
Subspawn unit {
Ship
}
}
}
Click Preview to test your game. You should be able to move your ship using the WASD keys or the arrow keys, whichever you prefer!
The ButtonRemap component lets us support multiple alternative keybindings in one place,
instead of having to repeatedly check for both KeyW and ArrowUp everywhere in our code.
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.
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.