Skip to main content

Step 14 - Ground Graphics

Finally the ground graphics, feel free to use and remix.

pixel art pipe graphic

We want the ground to tile and repeat horizontally we can take advantage of the ex.ImageWrapping.Repeat to accomplish this

typescript
// resources.ts
export const Resources = {
// Relative to /public in vite
...
GroundImage: new ex.ImageSource('./images/ground.png', {
wrapping: ex.ImageWrapping.Repeat
})
} as const;
typescript
// resources.ts
export const Resources = {
// Relative to /public in vite
...
GroundImage: new ex.ImageSource('./images/ground.png', {
wrapping: ex.ImageWrapping.Repeat
})
} as const;

When the ex.ImageWrapping.Repeat mode is set, specifying a bigger sourceView than the original image has a tiling effect, the original image gets repeated over and over.

typescript
// ground.ts
onInitialize(engine: ex.Engine): void {
this.groundSprite.sourceView.width = engine.screen.drawWidth;
this.groundSprite.destSize.width = engine.screen.drawWidth;
this.graphics.use(this.groundSprite);
}
typescript
// ground.ts
onInitialize(engine: ex.Engine): void {
this.groundSprite.sourceView.width = engine.screen.drawWidth;
this.groundSprite.destSize.width = engine.screen.drawWidth;
this.graphics.use(this.groundSprite);
}

To make the ground appear scroll to the left, we can do a nifty trick to move the sourceView.x by the speed of our Pipe

typescript
onPostUpdate(_engine: ex.Engine, elapsedMs: number): void {
if (!this.moving) return;
this.groundSprite.sourceView.x += Config.PipeSpeed * (elapsedMs / 1000);
this.groundSprite.sourceView.x = this.groundSprite.sourceView.x % Resources.GroundImage.width;
}
typescript
onPostUpdate(_engine: ex.Engine, elapsedMs: number): void {
if (!this.moving) return;
this.groundSprite.sourceView.x += Config.PipeSpeed * (elapsedMs / 1000);
this.groundSprite.sourceView.x = this.groundSprite.sourceView.x % Resources.GroundImage.width;
}

Putting it all together

typescript
// ground.ts
export class Ground extends ex.Actor {
groundSprite = Resources.GroundImage.toSprite();
moving = false;
constructor(pos: ex.Vector) {
super({
pos,
anchor: ex.vec(0, 0),
height: 64,
width: 400,
z: 1
})
}
onInitialize(engine: ex.Engine): void {
this.groundSprite.sourceView.width = engine.screen.drawWidth;
this.groundSprite.destSize.width = engine.screen.drawWidth;
this.graphics.use(this.groundSprite);
}
onPostUpdate(_engine: ex.Engine, elapsedMs: number): void {
if (!this.moving) return;
this.groundSprite.sourceView.x += Config.PipeSpeed * (elapsedMs / 1000);
this.groundSprite.sourceView.x = this.groundSprite.sourceView.x % Resources.GroundImage.width;
}
start() {
this.moving = true;
}
stop() {
this.moving = false;
}
}
typescript
// ground.ts
export class Ground extends ex.Actor {
groundSprite = Resources.GroundImage.toSprite();
moving = false;
constructor(pos: ex.Vector) {
super({
pos,
anchor: ex.vec(0, 0),
height: 64,
width: 400,
z: 1
})
}
onInitialize(engine: ex.Engine): void {
this.groundSprite.sourceView.width = engine.screen.drawWidth;
this.groundSprite.destSize.width = engine.screen.drawWidth;
this.graphics.use(this.groundSprite);
}
onPostUpdate(_engine: ex.Engine, elapsedMs: number): void {
if (!this.moving) return;
this.groundSprite.sourceView.x += Config.PipeSpeed * (elapsedMs / 1000);
this.groundSprite.sourceView.x = this.groundSprite.sourceView.x % Resources.GroundImage.width;
}
start() {
this.moving = true;
}
stop() {
this.moving = false;
}
}