Using Storybook to Build a

Game Engine?

Design System

Component Library

(better)

MDC 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! 👋

MDC 2020: Using Storybook to Build a Better Game Engine

By Kamran Ayub

MDC 2020: 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

  • 661