The storyboard allows for state logic to be written into specific classes and eliminates the
need to override the hamonengine.core.engine class.
Creating a frame is as simple as overriding the hamonengine.events.frame class and responds to
many of the same events.
This demo provides a more complex implementation of the storyboard with nested frames.
Once the demo starts, hovering over a bird will show its name and clicking on it will cause it
to sing or perform a call.
NOTES:
-
This demo demonstrates how to navigate between frames using the storyboard.jump,
storyboard.goNext, & storyboard.goPrev methods.
-
This demo uses a startFrame to manage all global resources.
-
This demo uses a titleFrame to mange the title screen
logic.
-
This demo uses a menuFrame to mange the menu screen logic.
-
This demo uses a birdFrame to mange the main bird demo
logic.
-
This startFrame sets the property options.resourceFrame to true to force the engine to block
until all its needed resources are loaded.
-
Resource frames are given loading priority to the other frames, which will load in parallel
once the resource frame has completed loading.
-
Frames support the following events:
-
onFrameInitiating - Logic the 1st time a frame runs. This event occurs only once.
-
onFrameStarting - Logic that is called when switching to the current frame but
before rendering has started. This event can be ignored.
-
onFrame - The core logic of the current frame.
-
onFrameStopping - Logic that is called when switching away from the current frame.
This event can be ignored.
The setup is a bit more complicated with one parent frame and several child frames.
-
Note each frame is unique.
-
The preloadAllFrames argument is true so that all other frames will wait on the
resourceFrame to complete loading.
const storyboard = new hamonengine.events.storyboard({ preloadAllFrames: true, frames: [
new startFrame({ frames: [
new titleFrame(),
new menuFrame(),
new birdFrame()
]})
]});
(await new hamonengine.core.engine({storyboard}).load()).start();
The startFrame is the 1st registered frame in the storyboard so will be the 1st
to run.
Note that this frame is marked as a resourceFrame meaning all other frames will wait for it to
complete loading.
This allows for global resource data, such as spritesheets, audio, etc to load that the other
frames may be dependent upon.
constructor(options = {}) {
//NOTE: This is a resource frame. All other frames will wait on this one until it has completed loading.
//Non-resource frames will be loaded in parallel.
options.resourceFrame = true;
options.name = 'start';
super(options);
...
}
/**
* An event that occurs when attempting to load resources.
* @param {object} storyboard calling the load operation.
* @return {Object} a promise that the resource has loaded successfully.
*/
async onloadResources(storyboard) {
//Wait for the webfonts to complete loading or fetching metrics for a font will return the wrong information.
const customFont = document.fonts.load("24px Walter Turncoat");
//Load all of our sprites and music early.
const spriteSheetPromise = this.birdReferences.spriteSheet.load();
const albumPromise = this.birdReferences.album.load();
const terrainPromise = this.terrain.load();
return Promise.all([terrainPromise, spriteSheetPromise, albumPromise, customFont]);
}
Once the startFrame runs it will jump to the the titleFrame.
Hierarchy syntax in the jump method uses periods to separate each level.
/**
* An onFrame event that is triggered when this item is active.
* @param {number} elapsedTimeInMilliseconds since the last frame.
* @param {object} storyboard used to invoke this onFrame event.
* @param {number} totalTimeInMilliseconds is the total time that has elapsed since the engine has started.
* @param {object} lastFrame contains the last frame before transitioning to this one. NOTE: This can be null or undefined.
*/
onFrame(elapsedTimeInMilliseconds, storyboard, totalTimeInMilliseconds, lastFrame) {
//Go directly to the title after resources have loaded.
storyboard.jump('start.title');
}
The titleFrame uses the event onFrameInitiating to setup the title.
This logic runs only once.
constructor(options = {}) {
options.name = 'title';
super(options);
...
}
/**
* An onFrameInitiating event that is triggered when frame is starting for the 1st time.
* This event occurs only once.
* @param {number} elapsedTimeInMilliseconds since the last frame.
* @param {object} storyboard used to invoke this onFrame event.
* @param {number} totalTimeInMilliseconds is the total time that has elapsed since the engine has started.
* @param {object} lastFrame contains the last frame before transitioning to this one. NOTE: This can be null or undefined.
*/
onFrameInitiating(elapsedTimeInMilliseconds, storyboard, totalTimeInMilliseconds, lastFrame) {
//Set the initial state of the birds.
this.birds.reset(this.terrain, storyboard.engine.primaryScreen);
}
The frame will then transition to the menuFrame based on the proper user input.
/**
* Processes keyboard events.
* @param {object} storyboard used to invoke this onKeyEvent event.
* @param {string} type of keyboard event such as 'up' or 'down' for keyup and keydown.
* @param {string} keyCode of the key (see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code)
* @param {object} e KeyboardEvent (see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
* @param {object} caller that triggered the event that can be a HTMLElement, instance of the HamonEngine, or a screen (see hamonengine.graphics.screen).
*/
onKeyEvent(storyboard, type, keyCode, e, caller) {
if (type === 'down') {
switch (keyCode) {
//End the demo.
case 'Escape':
storyboard.engine.stop();
break;
}
}
}
/**
* Processes mouse & touch events if captureTouchAsMouseEvents is set to true.
* @param {object} storyboard used to invoke this onMouseEvent event.
* @param {string} type of mouse event such as: 'click', 'up', 'down', 'move', 'enter', 'leave'.
* @param {object} v an instance of vector2 object that contain the x & y coordinates (see hamonengine.math.vector2).
* @param {object} e MouseEvent (see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)
* @param {object} caller that triggered the event that can be a HTMLElement, instance of the HamonEngine, or a screen (see hamonengine.graphics.screen).
*/
onMouseEvent(storyboard, type, v, e, caller) {
if (type === 'up') {
//Go to the menuFrame.
storyboard.goNext();
}
}
The menuFrame uses the onFrameStarting event to toggle specific menu options based on global conditions.
Note that this event is called every time the storyboard loads this frame.
constructor(options = {}) {
options.name = 'menu';
super(options);
...
}
/**
* An onFrameStarting event that is triggered when frame is starting.
* @param {number} elapsedTimeInMilliseconds since the last frame.
* @param {object} storyboard used to invoke this onFrame event.
* @param {number} totalTimeInMilliseconds is the total time that has elapsed since the engine has started.
* @param {object} lastFrame contains the last frame before transitioning to this one. NOTE: This can be null or undefined.
*/
onFrameStarting(elapsedTimeInMilliseconds, storyboard, totalTimeInMilliseconds, lastFrame) {
//Only capture the screen from the birdFrame, as the titleFrame has too much text that can conflict with the menu items.
this.lastScreen = lastFrame?.name === 'bird' ? storyboard.engine.primaryScreen.toLayer() : this.terrain.terrainLayer;
//Hide the Start mneu item once we start and replace it with the resume option.
if (this.resources.globalStateData.hasStarted) {
this._menuItems.find(item => item.name === 'start').visible = false;
this._menuItems.find(item => item.name === 'resume').visible = true;
}
//NOTE: Since this method overrides the base class event, then the base class event needs to be invoked to complete the frame starting event.
super.onFrameStarting(elapsedTimeInMilliseconds, storyboard, totalTimeInMilliseconds);
}