RSS Amplifier

J Lopes — Developer Portfolio & Creative Writings · Mar 10, 2018

Skifree

0
Sign in to vote or save

jlopes.eu


View on GitHub

Skifree is a Windows 95 game about avoiding trees until a yeti eats you. I tried rebuilding it in Preact with Redux and RxJS for state and stream management.

The player

Every game entity has a sprite array, coordinate objects pointing to a shared spritesheet:

const sprite = [
{ name: 'w3',     x: 492, y: 131, width: 90, height: 128 },
{ name: 'w2',     x: 386, y: 131, width: 90, height: 128 },
{ name: 'w1',     x: 280, y: 131, width: 82, height: 128 },
{ name: 'sw2',    x: 158, y: 131, width: 87, height: 128 },
{ name: 'sw1',    x:  88, y: 131, width: 64, height: 128 },
{ name: 'normal', x:   0, y:   0, width: 60, height: 128 },
// ... southeast, east, ouch, jump, flip1, flip2, getup
];

The trees, rocks, yeti, and every other entity each have their own array like this, all pointing into the same image.

The full game spritesheet: player frames on top, obstacles, yeti, and everything else below

The full game spritesheet.

The illusion

The skier never moves. position.x is always canvas.width / 2, position.y always 200. What changes is center, an offset that every obstacle reads to figure out where to draw itself. In every direction.

store.dispatch({
type: 'UPDATE_CENTER',
payload: state.game.center + state.speed.x
});

It’s a trick used in most side-scrollers. The mountain comes to the skier.

The obstacles

Each frame a random number decides what appears on the slope:

add(y = 0) {
const n = Math.random();
if      (n > 0.80)                                new Tree(y, ...);
else if (n > 0.50)                                new Rock(y, ...);
else if (n > 0.30)                                new Snow(y, ...);
else if (n > 0.10 && !this.obstacles.has('ramp')) new Ramp(y, ...);
else if (n > 0.05)                                new Post(y, ...);
else if (            !this.obstacles.has('cart'))  new Cart(y, ...);
}

Trees at 20%, rocks at 30%, snow mounds at 20% (those bounce you up instead of crashing you). Ramps send you into the air with a jump strength of 100 instead of the usual 25. Only one ramp and one dog sled can exist at a time. When something scrolls off the top it gets deleted and add() fires again.

The yeti

After 60 seconds a Yeti appears and follows the player:

let speedX = (this.position.x <= playerX) ?
 (6 - state.speed.x) / 8 :
-(4 - state.speed.x) / 8;
let speedY = (this.position.y <= playerY) ?
 (10 - state.speed.y) / 8 :
-(10 + state.speed.y) / 8;

It catches up faster from the left than the right, and the vertical gap always closes. Ski fast enough and you can hold it off. Move slower and it eats you.

And from that day on, I wore this little 'F' key pendant everywhere I went.

Redux

Here’s the state setup:

const reducer = combineReducers({
player: playerReducer,
game: gameReducer,
speed: speedReducer
});

Three reducers. For a browser game. The speed reducer alone handles PLAYER_MOVE, PLAYER_MOUSEMOVE, PLAYER_SET_RATIO, PLAYER_JUMP, and GAME_RESET.

It made debug mode easy. store.getState() at any frame tells you everything. Stepping through actions in Redux DevTools to track down a yeti glitch was satisfying.

In retrospective, canvas.addEventListener would have been fine for this game.

Press F.

Read the original on jlopes.eu

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.