Dr. Strangecode Or:

How I learned to stop worrying and love the DOM

My game making journey, and DOM love-affair.

I wanted to make a JavaScript web game

I tried to follow these guiding principles:

  1. It should lean into the web. success!
  2. It should not use a framework. success!
  3. It should be easy. fail!

I wanted to make a point and click adventure game

Brief explanation of adventure games:

  1. Solve puzzles by interacting with the environment
  2. Usually not time-based
  3. Narrative focused
  4. Pinnacle of game design

Nostalgic screenshots

Indiana Jones and the Fate of AtlantisHugo 2: Whodonit Grim Fandango Quest for Glory Codename: ICEMAN The Secret of Monkey Island Maniac Mansion King's Quest
I didn't know how to make a game, so…

I read:

Game Programming Patterns

And also:

You Don't Know JS: this & Object Prototypes, by Kyle Simpson

Stuff needed to make my game

  1. Rendering system
  2. Inventory system
  3. Text display
  4. Triggers to advance puzzles
  5. Messaging system
  6. Audio system
  7. Saving progress
  8. Title screen
  9. More I'm forgetting
  10. Even more I never thought of
  11. Fun
Panic!

What can make life easier?

Use the DOM

Why the DOM?

The DOM suuuuuucks. —Everyone

The DOM is great because…

  1. It's familiar
  2. It can save work
  3. It fit the game I wanted to make

Things you'd need to re-implement in canvas

  1. Painting
  2. Clicking
  3. Responsive
  4. Accessibility
But it's not all 🌹s.

DOM Problems

  1. Performance is poor
  2. Data synchronization is an issue
  3. API is weird
Panic!

DOM Problems Solutions

  1. Performance is poor: stay static, no touching
  2. Data synchronization is an issue: don't try
  3. API is weird: it is what it is

For this presentation:

Focus on object data, loosely tied to logic, and message passing

End goal: Clicking a thing displays it's description.

Sidebar

Native ES6 Modules: you can use them in development!

I worked real hard on some programmer art.
Gambini Game Screenshot

Data

Dr. Strangelove Computer
When something is clicked, we need to know stuff about it, like graphic, description, etc.

I used SVGs for graphics

They are:

  1. Pure markup
  2. Style control with CSS
  3. Fully animatable
  4. Resolution independent

I used data attributes to store data

They are:

  1. Pure markup
  2. Flexible
  3. Combine with SVG to tie all data together
  4. dataset makes them nicer to work with

DOM data


  <svg
  xmlns="http://www.w3.org/2000/svg"
  id="bed"
  viewBox="0 0 535 338"
  data-description="This is a <em>bed</em>"
  data-can-pickup="false"
  data-audio="https://example.com/bed.mp3">
  data-data="https://example.com/complex-data.json">
  <title>bed</title>
  <desc>This is a <em>bed</em></desc>
  <!-- SVG code -->
  </svg>

Logic

Dr. Strangelove Jack

Thing invocation

const bed = Thing();
bed.init('#bed');

Thing init method

let thing = {
    init: function init(selector) {
        // Map properties.
        this.selector = selector;
        this.domNode = document.querySelector(selector);
        this.description = this.domNode.dataset.description;
        // Messaging.
        observer.subscribe(this.domNode, this.showDescription.bind(this));
    },
    // more properties…
};

Messaging

Dr. Strangelove General

The observer pattern:

“…lets one piece of code announce that something interesting happened without actually caring who receives the notification.” —Game Programming Patterns

Relatable Observer example

objectLayer 1I had a bad dateOh yeah?
My observer pattern is a thin wrapper around event delegation, storing callbacks in a Map.

Observer storage pattern

// Map() where keys are DOM nodes
// and values are arrays of callbacks.
bed: [
  showDescription
],
baseBall: [
  showDescription,
  pickUp
]
Delegated events can pull the necessary callbacks using the DOM node as a key

This only works if you click on the SVG root element…

closest to the rescue

onNotify

function onNotify(event) {
  let key = subscribers.get(event.target.closest(/* no data */));
  if (key) {
      key.forEach(fn => fn(event));
  }
}
Panic!
Who can save us?

A real 💎

farthestViewportElement
farthestViewportElement
Gets a value that represents the farthest ancestor svg element.

Basically, get the SVG root from any child element.

onNotify with farthestViewportElement

function onNotify(event) {
  let key = subscribers.get(event.target.farthestViewportElement);
  if (key) {
    key.forEach(fn => fn(event));
  }
}
Dr Strangelove smoking
Demo

Thank you!

gwengween.com
contact@chrisdeluca.me