Skip to content

Commit

Permalink
docs: fix first game sample docs
Browse files Browse the repository at this point in the history
  • Loading branch information
eonarheim committed Jan 17, 2024
1 parent 209c497 commit e20d5ee
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 10 deletions.
233 changes: 224 additions & 9 deletions site/docs/00-your-first-game.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ slug: /getting-started
tags: ["#Tutorial"]
---

```twoslash include ex
/// <reference path="../src/engine/excalibur.d.ts" />
const Engine = ex.Engine;
const Actor = ex.Actor;
const Color = ex.Color;
const CollisionType = ex.CollisionType;
const vec = ex.vec;
```

# Hello Excalibur

## Introduction
Expand Down Expand Up @@ -79,9 +88,22 @@ It is important to call `game.start()` once you are done configuring your game o
import { Engine } from 'excalibur';
```

`embed:sample-breakout/src/main.ts{snippet: "create-engine"}`
```ts twoslash
// @include: ex
// ---cut---
// Create an instance of the engine.
// I'm specifying that the game be 800 pixels wide by 600 pixels tall.
// If no dimensions are specified the game will fit to the screen.
const game = new Engine({
width: 800,
height: 600,
});
```

```typescript
```ts twoslash
// @include: ex
declare const game: ex.Engine;
// ---cut---
// Start the engine to begin the game.
game.start();
```
Expand All @@ -102,15 +124,48 @@ Actors must be added to a scene to be drawn or updated! `game.add(actor)` will a

Below we are going to create the paddle Actor for our Breakout game. Actors can be given many parameters such as position, width, and height.

`embed:sample-breakout/src/main.ts{snippet: "create-paddle"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
// ---cut---
// Create an actor with x position of 150px,
// y position of 40px from the bottom of the screen,
// width of 200px, height and a height of 20px
const paddle = new Actor({
x: 150,
y: game.drawHeight - 40,
width: 200,
height: 20,
// Let's give it some color with one of the predefined
// color constants
color: Color.Chartreuse,
});

// Make sure the paddle can participate in collisions, by default excalibur actors do not collide with each other
// CollisionType.Fixed is like an object with infinite mass, and cannot be moved, but does participate in collision.
paddle.body.collisionType = CollisionType.Fixed;

// `game.add` is the same as calling
// `game.currentScene.add`
game.add(paddle);
```

Open up your favorite browser and you should see something like this:

![Incomplete breakout screenshot](00-welcome/breakout-partial.png)

That’s neat, but this game is way more fun if things move around. Let’s make the paddle follow the mouse around in the x direction. The paddle will be centered on the mouse cursor.

`embed:sample-breakout/src/main.ts{snippet: "mouse-move"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const paddle: ex.Actor;
// ---cut---
// Add a mouse move listener
game.input.pointers.primary.on("move", (evt) => {
paddle.pos.x = evt.worldPos.x;
});
```

:::note

Expand All @@ -128,30 +183,190 @@ In this case we want to handle the resolution ourselves to emulate the the way B

Read more about the different [CollisionTypes](/docs/physics/#collision-types) that Excalibur supports.

`embed:sample-breakout/src/main.ts{snippet: "create-ball"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const paddle: ex.Actor;
// ---cut---
// Create a ball at pos (100, 300) to start
const ball = new Actor({
x: 100,
y: 300,
// Use a circle collider with radius 10
radius: 10,
// Set the color
color: Color.Red,
});
// Start the serve after a second
const ballSpeed = vec(100, 100);
setTimeout(() => {
// Set the velocity in pixels per second
ball.vel = ballSpeed;
}, 1000);

// Set the collision Type to passive
// This means "tell me when I collide with an emitted event, but don't let excalibur do anything automatically"
ball.body.collisionType = CollisionType.Passive;
// Other possible collision types:
// "ex.CollisionType.PreventCollision - this means do not participate in any collision notification at all"
// "ex.CollisionType.Active - this means participate and let excalibur resolve the positions/velocities of actors after collision"
// "ex.CollisionType.Fixed - this means participate, but this object is unmovable"

// Add the ball to the current scene
game.add(ball);
```

The ball is now setup to move at 100 pixels per second down and right. Next we will make the ball bounce off the side of the screen. Let’s take advantage of the `postupdate` event.

`embed:sample-breakout/src/main.ts{snippet: "screen-collision"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const ball: ex.Actor;
declare const ballSpeed: ex.Vector;
// ---cut---
// Wire up to the postupdate event
ball.on("postupdate", () => {
// If the ball collides with the left side
// of the screen reverse the x velocity
if (ball.pos.x < ball.width / 2) {
ball.vel.x = ballSpeed.x;
}

// If the ball collides with the right side
// of the screen reverse the x velocity
if (ball.pos.x + ball.width / 2 > game.drawWidth) {
ball.vel.x = ballSpeed.x * -1;
}

// If the ball collides with the top
// of the screen reverse the y velocity
if (ball.pos.y < ball.height / 2) {
ball.vel.y = ballSpeed.y;
}
});
```

## Creating the bricks with Actors

Breakout needs some bricks to break. To do this we calculate our brick layout and add them to the current scene.

`embed:sample-breakout/src/main.ts{snippet: "create-bricks"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const ball: ex.Actor;
declare const ballSpeed: ex.Vector;
declare type Actor = any;
// ---cut---
// Build Bricks

// Padding between bricks
const padding = 20; // px
const xoffset = 65; // x-offset
const yoffset = 20; // y-offset
const columns = 5;
const rows = 3;

const brickColor = [Color.Violet, Color.Orange, Color.Yellow];

// Individual brick width with padding factored in
const brickWidth = game.drawWidth / columns - padding - padding / columns; // px
const brickHeight = 30; // px
const bricks: Actor[] = [];
for (let j = 0; j < rows; j++) {
for (let i = 0; i < columns; i++) {
bricks.push(
new Actor({
x: xoffset + i * (brickWidth + padding) + padding,
y: yoffset + j * (brickHeight + padding) + padding,
width: brickWidth,
height: brickHeight,
color: brickColor[j % brickColor.length],
})
);
}
}

bricks.forEach(function (brick) {
// Make sure that bricks can participate in collisions
brick.body.collisionType = CollisionType.Active;

// Add the brick to the current scene to be drawn
game.add(brick);
});
```

When the ball collides with bricks, we want to remove them from the scene. We use the `collisionstart` handler to accomplish this. This handler fires when objects first touch, if you want to know every time resolution is completed use `postcollision`.

`embed:sample-breakout/src/main.ts{snippet: "ball-brick-collision"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const ball: ex.Actor;
declare const ballSpeed: ex.Vector;
declare type Actor = any;
declare const bricks: ex.Actor[];
// ---cut---
// On collision remove the brick, bounce the ball
let colliding = false;
ball.on("collisionstart", function (ev) {
if (bricks.indexOf(ev.other) > -1) {
// kill removes an actor from the current scene
// therefore it will no longer be drawn or updated
ev.other.kill();
}

// reverse course after any collision
// intersections are the direction body A has to move to not be clipping body B
// `ev.content.mtv` "minimum translation vector" is a vector `normalize()` will make the length of it 1
// `negate()` flips the direction of the vector
var intersection = ev.contact.mtv.normalize();

// Only reverse direction when the collision starts
// Object could be colliding for multiple frames
if (!colliding) {
colliding = true;
// The largest component of intersection is our axis to flip
if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
ball.vel.x *= -1;
} else {
ball.vel.y *= -1;
}
}
});

ball.on("collisionend", () => {
// ball has separated from whatever object it was colliding with
colliding = false;
});

```

Finally, if the ball leaves the screen by getting past the paddle, the player loses!

`embed:sample-breakout/src/main.ts{snippet: "lose-condition"}`
```ts twoslash
// @include: ex
declare const game: ex.Engine;
declare const ball: ex.Actor;
declare const ballSpeed: ex.Vector;
declare type Actor = any;
declare const bricks: ex.Actor[];
// ---cut---
// Loss condition
ball.on("exitviewport", () => {
alert("You lose!");
});
```

![Final Breakout screenshot](00-welcome/breakout-final.png)

Congratulations! You have just created your first game in Excalibur! You can download this example [here](https://github.com/excaliburjs/sample-breakout).

Some things you could do on your own to take this sample further
* Add [[Sprite|sprite]] [[Graphic|graphics]] to the paddle, ball, and bricks
* Add a fun background instead of the blue
* Interpolate the paddle position between move events for a smoother look
* Make the ball ricochet differently depending where the paddle hits it


It's time to [get introduced](/docs/engine) to the Engine for more examples. Once you're ready, you can browse the [API Reference](/docs/api/edge).

<CodeSandboxEmbed
Expand Down
2 changes: 1 addition & 1 deletion site/docs/02-fundamentals/03-actors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ An actor has a basic lifecycle that dictates how it is initialized, updated, and

## Updating actors

In most games, things are happening on screen: the background is parallaxing, your hero responds to input, and enemies shoot bullets. In Excalibur, the logic that updates game state is run during the [update loop](/docs/engine#engine-lifecycle). Actors are a way to encapsulate that logic, such as a `Player`, `Enemy`, and `MenuButton`. Actors are designed to be pretty much anything!
In most games, things are happening on screen: the background is parallax-ing, your hero responds to input, and enemies shoot bullets. In Excalibur, the logic that updates game state is run during the [update loop](/docs/engine#engine-lifecycle). Actors are a way to encapsulate that logic, such as a `Player`, `Enemy`, and `MenuButton`. Actors are designed to be pretty much anything!

### Initialization

Expand Down

0 comments on commit e20d5ee

Please sign in to comment.