Using Storybook to Build a
Game Engine?
Design System
Component Library
(better)
React MN 2020
🎮
😲
👨💻
Erik
Excalibur Main Loop
Update
Phase 1
Draw
Phase 2
describe('A game actor', () => {
let actor: ex.Actor;
let engine: ex.Engine;
beforeEach(() => {
engine = TestUtils.engine({ width: 100, height: 100 });
actor = new ex.Actor();
});
it('should have an old position after an update', () => {
actor.pos.setTo(10, 10);
actor.vel.setTo(10, 10);
actor.update(engine, 1000);
expect(actor.oldPos.x).toBe(10);
expect(actor.oldPos.y).toBe(10);
expect(actor.pos.x).toBe(20);
expect(actor.pos.y).toBe(20);
});
});
expect(actor.oldPos.x).toBe(10);
expect(actor.oldPos.y).toBe(10);
expect(actor.pos.x).toBe(20);
expect(actor.pos.y).toBe(20);
===
???
Image Snapshot Testing
describe('A game actor', () => {
let engine: ex.Engine;
beforeEach(() => {
engine = TestUtils.engine({
width: 62,
height: 64,
suppressHiDPIScaling: true
});
});
it('should not corrupt shared sprite contexts', (done) => {
const texture = new ex.Texture('base/src/spec/images/SpriteSpec/icon.png', true);
texture.load().then(() => {
const actor = new ex.Actor({
pos: new ex.Vector(engine.halfCanvasWidth, engine.halfCanvasHeight),
width: 10,
height: 10,
rotation: Math.PI / 4
});
engine.add(actor);
actor.draw(engine.ctx, 100);
ensureImagesLoaded(
engine.canvas, 'src/spec/images/SpriteSpec/iconrotate.png'
).then(([canvas, image]) => {
expect(canvas).toEqualImage(image, 0.995);
done();
});
});
});
Manual Testing
aka integration testing
Starting server on https://localhost.github.com...
🔑
export const withUsername = () => (
<NavMenu user="kamranayub" />
)
export const withSponsorsDisabled = () => (
<NavMenu enableSponsors={false} />
)
Starting server on https://localhost:3000...
🤔
<canvas id="game"></canvas>
hmm...
But I don't make game engines!
We're both still drawing pixels to the screen.
You can start using Storybook today for your application
export const NavigationMenu = ({ username }) => (
<nav>
<ul>
<li>Signed in: {username}</li>
</ul>
</nav>
)
import { render } from '@testing-library/react'
import { NavigationMenu } from '../NavigationMenu'
describe('<NavigationMenu />', () => {
test('should display username', () => {
const { getByText } = render(<NavigationMenu username="kamranicus" />)
expect(getByText('Signed in: kamranicus')).toBeInTheDocument()
})
})
describe('site navigation', () => {
test('should display username when signed in', () => {
cy.visit('/home')
cy.login()
cy.get('nav > ul > li:first-child').to.have.text('Signed in: kamranicus')
})
})
Storyflow
A workflow using... ahem, Storybook
export const NavigationMenu = ({ username }) => (
<nav>
<ul>
<li>Signed in: {username}</li>
</ul>
</nav>
)
import { NavigationMenu } from './NavigationMenu'
export default {
title: 'Navigation Menu',
component: NavigationMenu
}
export const WithUsername = () => (
<NavigationMenu username="kamranicus" />
)
import { render } from '@testing-library/react'
import { WithUsername } from '../NavigationMenu.stories'
describe('<NavigationMenu />', () => {
test('should display username', () => {
const { getByText } = render(<WithUsername />)
expect(getByText('Signed in: kamranicus')).toBeInTheDocument()
})
})
describe('Navigation Menu', () => {
before(() => {
cy.visit('/iframe.html?id=navigation-menu--with-username')
})
test('should display username', () => {
cy.get('nav > ul > li:first-child').to.have.text('Signed in: kamranicus')
})
})
Benefits
One source of truth for all our tests
🥇
Incentivizes writing more stories
🥈
Promotes more testability
🥉
Storyflow
Stories
Unit tests
UI tests
Design Systems
Component Libraries
Game Engines
and
Line of Business Apps
Thanks! 👋
Slides: bit.ly/StoryflowMDC2020
React MN: Using Storybook to Build a Better Game Engine
By Kamran Ayub
React MN: Using Storybook to Build a Better Game Engine
How we use Storybook to help test and verify features in Excalibur.js, an open source 2D game engine
- 748