ButtonStateMachine
is a plugin for the ImpactJS game engine and offers a clean and simple way to add state management to your button entities.
The intent is to separate the state code from your main button implementation keeping your button code clean and easily readable. The modular code allows for re-use without duplication, aswell as easy substition of code with another module or modified state engine.
- Create instance of ButtonStateMachine in your button entity.
- Set the following button state input functions in your button entity (if required, else keep defaults).
isMouseInside
- return true if mouse is inside of button clickable area, else falseisEnabled
- return enabled state of your button, or keep default if your button can't be disabledisMouseDown
- return true if mouse.STATE is down, else false (note mouse state should return down continuosly until button up, not just once on transition)
- Set the following state transition handlers - if required.
Note: these events only get called ONCE on state transition.startHover
- notifies when mouse over clickable areaendHover
- notifies when mouse leaves clickable areastartClick
- notifies when mouse button down on clickable areaendClick
- notifies when mouse button up on clickable areaendClickIgnore
- notifies when mouse button up outside of clickable area
- Call
updateState()
in your button entity's update handler.
-
The sample code below demonstrates how to create a simple button that uses the
ButtonStateMachine
plugin.
For a (bit) more extensive example have a look at the ScaledAlphaHitmask readme. The example code adds improved hit detection for partially transparent buttons. (e.g. a round button, where we only want to register a 'hit' once the mouse hits the solid part of the button) -
To run the demo code
- place the
ButtonStateMachine.js
plugin into your[Project]/lib/plugins
directory. - place the
DemoGamebtn.js
file into your[Project]/lib/game/entities
directory. - place a
samplebtn.png
image into your[Project]/media/btns
directory. - copy relevant code from
main.js
snippet into your ownmain.js
code.
- place the
-
main.js
// ------------------------------------------------------------------------------------------------------------- // main.js // ------------------------------------------------------------------------------------------------------------- ig.module( 'game.main' ) .requires( 'impact.game', // ... // ... 'game.entities.demogamebtn' ) .defines(function(){ MyGame = ig.Game.extend({ // ... // ... init: function() { // ... // ... // Capture Mouse Down events ... ig.input.bind(ig.KEY.MOUSE1, 'click'); // note: we check for 'click' to determine if mouse down (see IsMouseDown() in DemoGameBtn.js) // Test DemoGameBtn var demoGamebtn = ig.game.spawnEntity(EntityDemoGamebtn, 100, 100, { image: 'samplebtn.png', width: 106, height: 120}); window.focus(); ig.system.canvas.onclick = function() { window.focus(); }; } });
-
DemoGamebtn.js
// ------------------------------------------------------------------------------------------------------------- // DemoGamebtn.js // ------------------------------------------------------------------------------------------------------------- ig.module( 'game.entities.demogamebtn' ) .requires( 'plugins.button-state-machine', 'impact.entity' ) .defines(function() { EntityDemoGamebtn = ig.Entity.extend({ size: {x: 16, y: 16}, btnStateMachine: null, // ------------------------------------------------------------------------------------------------------------- // Button States // ------------------------------------------------------------------------------------------------------------- endClick: function() { this.currentAnim = this.anims.btnNormal; }, endClickIgnore: function() { this.currentAnim = this.anims.btnNormal; }, endHover: function() { this.currentAnim = this.anims.btnNormal; }, startClick: function() { this.currentAnim = this.anims.btnDown; }, startHover: function() { this.currentAnim = this.anims.btnHover; }, // ------------------------------------------------------------------------------------------------------------- // Alpha Hit Detection // ------------------------------------------------------------------------------------------------------------- // hittest - returns true if mouse cursor within bounds, else false hittest: function() { if ((ig.input.mouse.x > this.pos.x && ig.input.mouse.x < (this.pos.x + this.size.x)) && (ig.input.mouse.y > this.pos.y && ig.input.mouse.y < (this.pos.y + this.size.y))) { return true; } return false; // hittest failed - mouse outside of entity }, init: function(x, y, settings) { this.animSheet = new ig.AnimationSheet('media/btns/' + settings.image , settings.width, settings.height); this.addAnim('btnNormal', 1, [0]); this.addAnim('btnHover', 2, [1]); this.addAnim('btnDown', 3, [2]); this.addAnim('btnDisabled', 4, [3]); this.currentAnim = this.anims.btnNormal; this.size.x = settings.width; this.size.y = settings.height; // ... // ... this.btnStateMachine = new ig.ButtonStateMachine(); this.btnStateMachine.isMouseInside = this.hittest.bind(this); this.btnStateMachine.isMouseDown = function() { return ig.input.state('click'); }; this.btnStateMachine.startClick = this.startClick.bind(this); this.btnStateMachine.endClick = this.endClick.bind(this); this.btnStateMachine.startHover = this.startHover.bind(this); this.btnStateMachine.endHover = this.endHover.bind(this); this.btnStateMachine.endClickIgnore = this.endClickIgnore.bind(this); this.parent(x, y, settings); }, update: function() { this.parent(); this.btnStateMachine.updateState(); } }); });
- Test disabled button state.
- Test enabling/disabling of buttons while state engine in different states.
- Feedback welcome here ...
- Any thoughts welcome here ...