Skip to main content

Flipping

Open this Example

Flipping example

When my character is walking left instead of right, how can I flip my sprite horizontally so it faces the way it is walking?

In this example, we will create a simple character that can walk around the screen. Since our image is drawn facing right, when they are walking left, we will flip the sprite horizontally.

To do this, we will create a new property called Facing and set it to 1 when the character is facing right, and -1 when the character is facing left, and then we will make the ImageSprite respond to it accordingly.

Step 1: Create a Facing property

Create a new property called Facing and set it to 1 by default:

pub prop unit.Facing = 1
pub prop unit.IsWalking = false

To make this example more complete, we have also added a property called IsWalking which will be set to true when the character is walking, enabling us to switch between a walking and idle animation.

Step 2: Update the Facing property

Every tick, we will check the joystick input and update the Facing property accordingly:

pub fn unit.Hero([owner]) {
// ...

// set the walking/facing state based on the joystick input
on BeforePhysics {
if Joystick == @(0, 0) {
IsWalking = false
} else {
IsWalking = true
Move(0.1 * Joystick)

if Joystick.X != 0 { // must be non-zero so we don't zero out the facing when moving vertically
Facing = Joystick.X.Sign
}
}
}
}

The Sign function returns 1 for positive numbers, -1 for negative numbers, and 0 for zero. We use it to set the Facing property to 1 when the joystick is pushed right, and -1 when it is pushed left. When the joystick is pushed up or down, we don't change the Facing property, so the character continues to face the last direction it was moving in.

info

Even though we are using the Joystick input in this example, the arrow keys will also work because they are automatically mapped to Joystick. See Input Unification to learn more about this.

Step 3: React to the Facing property

The way to flip a sprite is to use the bodyScale parameter. By default, bodyScale=@(1,1) which means the sprite is drawn normally. Changing the X-component to -1, i.e. bodyScale=@(-1,1) will flip the sprite horizontally.

pub fn unit.Hero([owner]) {
use body=this, radius=1, shape=Rectangle(width=radius, height=2*radius)
Body(pos=@(0, 0))

// react to property changes using a `with` block
with IsWalking, Facing {
let bodyScale = @(Facing, 1) // flips the character horizontally when Facing is -1
ImageSprite(
image = IsWalking ? @character_maleAdventurer_walk*.png : @character_maleAdventurer_idle.png,
noRotation=true, // keeps our character upright
bodyScale=,
)
PolygonCollider(category=Category:Hero, bodyScale=)
}
}

Every time the Facing property changes, we will need to update the bodyScale of the ImageSprite and PolygonCollider to match it. To do this, we use a with block to automatically rerun the code whenever the relevant properties change.

It's done! Now the character will flip horizontally when walking left or right.

Why must I make a property?

Why do I need to make the Facing property myself? Why is it not built in to the engine?

It might seem like something as simple and common as flipping a sprite should be a built-in engine property, but there is a reason that it is not.

Easel is a reactive programming language which means the state of your game lives in your code, not in the engine. This allows you to choose the state representation that makes the most sense for your game. Holding the state in your code allows you to easily hook into it in multiple places, and to derive other state from it. Even in this simple example, you can see how even though we have one property, we have hooked it to two different places: the ImageSprite and the PolygonCollider. In an even more complex game, you might have many more places that need to know which way the character is facing.

tip

Properties are a fundamental part of Easel. A lot of making an Easel game is thinking about how to represent your game state in properties, and how to react to changes in those properties. This requires learning a new way to think about programming, but once you grasp it, you will find that it is a very powerful yet natural way to make games.

Full Code Listing

Thanks to Kenney for the assets used in this example.

pub tangible category Category:Hero
pub tangible category Category:Coin

pub game fn World.Main() {
Camera(body=@(0, 0), radius=5)
SolidBackground(#446)

SpawnEachPlayer owner {
Subspawn unit {
Hero
}
}

on Tick {
if QueryAny(filter=Category:Coin) { continue }
Spawn coin {
Coin
}
}
}

// Store the walking/facing state using properties so we can easily respond when it changes
pub prop unit.IsWalking = false
pub prop unit.Facing = 1
pub fn unit.Hero([owner]) {
use body=this, radius=1, shape=Rectangle(width=radius, height=2*radius)
Body(pos=@(0, 0))

// react to property changes using a `with` block
with IsWalking, Facing {
let bodyScale = @(Facing, 1) // flips the character horizontally when Facing is -1
ImageSprite(
image = IsWalking ? @character_maleAdventurer_walk*.png : @character_maleAdventurer_idle.png,
noRotation=true, // keeps our character upright
bodyScale=,
)
PolygonCollider(category=Category:Hero, bodyScale=)
}

// set the walking/facing state based on the joystick input
on BeforePhysics {
if Joystick == @(0, 0) {
IsWalking = false
} else {
IsWalking = true
Move(0.1 * Joystick)

if Joystick.X != 0 { // must be non-zero so we don't zero out the facing when moving vertically
Facing = Joystick.X.Sign
}
}
}
}

pub fn coin.Coin() {
use body=this, radius=0.75, shape=Circle
Body(pos=@(5*SignedRandom, 5*SignedRandom))
PolygonCollider(category=Category:Coin, collideWith=Category:Hero)
ImageSprite(image=@coinGold.png)

on BeforeCollide that {
if that.Category.Overlaps(Category:Hero) {
repeat 10 {
Spark(color=#fc0, speed=3, radius=0.05, luminous=1, bloom=1, glare=0.5, dissipate=0.5s)
}
Expire
}
}
}
Open in Editor