diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/demo/game/character.js b/demo/game/character.js index d28403b..487e710 100644 --- a/demo/game/character.js +++ b/demo/game/character.js @@ -4,6 +4,7 @@ import Gamepad from 'html5-gamepad'; import Matter from 'matter-js'; import { + AudioPlayer, Body, Sprite, } from '../../src'; @@ -24,11 +25,18 @@ export default class Character extends Component { scale: PropTypes.number, }; + handlePlayStateChanged = (state) => { + this.setState({ + spritePlaying: state ? true : false, + }); + }; + move = (body, x) => { Matter.Body.setVelocity(body, { x, y: 0 }); }; jump = (body) => { + this.jumpNoise.play(); this.isJumping = true; Matter.Body.applyForce( body, @@ -38,6 +46,14 @@ export default class Character extends Component { Matter.Body.set(body, 'friction', 0); }; + punch = () => { + this.isPunching = true; + this.setState({ + characterState: 4, + loop: false, + }); + } + enterBuilding = (body) => { let doorIndex = null; @@ -52,12 +68,15 @@ export default class Character extends Component { }); if (doorIndex !== null) { - Matter.Events.off(this.context.engine, 'afterUpdate', this.update); + this.setState({ + characterState: 3, + }); + this.isLeaving = true; this.props.onEnterBuilding(doorIndex); } }; - update = () => { + checkKeys = () => { const { keys, store } = this.props; const { body } = this.body; @@ -66,49 +85,69 @@ export default class Character extends Component { const shouldMoveStageLeft = body.position.x < midPoint && store.stageX < 0; const shouldMoveStageRight = body.position.x > midPoint && store.stageX > -2048; - if (body.velocity.y === 0 || body.velocity.y < -100) { - this.isJumping = false; - Matter.Body.set(body, 'friction', 1); - } + let characterState = 2; - if (!this.isJumping) { + if (keys.isDown(65) || gamepad.button(0, 'b')) { + return this.punch(); + } - let characterState = 2; + if (keys.isDown(keys.SPACE) || gamepad.button(0, 'a')) { + this.jump(body); + } - gamepad.update(); + if (keys.isDown(keys.UP) || gamepad.button(0, 'button 12')) { + return this.enterBuilding(body); + } - if (keys.isDown(keys.SPACE) || gamepad.button(0, 'a')) { - this.jump(body); + if (keys.isDown(keys.LEFT) || gamepad.button(0, 'button 14')) { + if (shouldMoveStageLeft) { + store.setStageX(store.stageX + 5); } - if (keys.isDown(keys.UP) || gamepad.button(0, 'button 12')) { - this.enterBuilding(body); + this.move(body, -5); + + characterState = 1; + } else if (keys.isDown(keys.RIGHT) || gamepad.button(0, 'button 15')) { + if (shouldMoveStageRight) { + store.setStageX(store.stageX - 5); } - if (keys.isDown(keys.LEFT) || gamepad.button(0, 'button 14')) { - if (shouldMoveStageLeft) { - store.setStageX(store.stageX + 5); - } + this.move(body, 5); - this.move(body, -5); + characterState = 0; + } - characterState = 1; - } else if (keys.isDown(keys.RIGHT) || gamepad.button(0, 'button 15')) { - if (shouldMoveStageRight) { - store.setStageX(store.stageX - 5); - } + this.setState({ + characterState, + loop: characterState < 2, + }); + } - this.move(body, 5); + update = () => { + const { store } = this.props; + const { body } = this.body; - characterState = 0; - } + const midPoint = Math.abs(store.stageX) + 448; - store.setCharacterPosition(body.position); + const shouldMoveStageLeft = body.position.x < midPoint && store.stageX < 0; + const shouldMoveStageRight = body.position.x > midPoint && store.stageX > -2048; - this.setState({ - characterState, - }); + if (body.velocity.y === 0 || body.velocity.y < -100) { + this.isJumping = false; + Matter.Body.set(body, 'friction', 1); + } + + if (!this.isJumping && !this.isPunching && !this.isLeaving) { + gamepad.update(); + + this.checkKeys(); + + store.setCharacterPosition(body.position); } else { + if (this.isPunching && this.state.spritePlaying === false) { + this.isPunching = false; + } + const targetX = store.stageX + (this.lastX - body.position.x); if (shouldMoveStageLeft || shouldMoveStageRight) { store.setStageX(targetX); @@ -123,14 +162,19 @@ export default class Character extends Component { this.loopID = null; this.isJumping = false; + this.isPunching = false; + this.isLeaving = false; this.lastX = 0; this.state = { characterState: 2, + loop: false, + spritePlaying: true, }; } componentDidMount() { + this.jumpNoise = new AudioPlayer('/assets/jump.wav'); Matter.Events.on(this.context.engine, 'afterUpdate', this.update); } @@ -163,10 +207,12 @@ export default class Character extends Component { > diff --git a/demo/game/index.js b/demo/game/index.js index 39a6fe9..a600db5 100644 --- a/demo/game/index.js +++ b/demo/game/index.js @@ -1,7 +1,8 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; import Matter from 'matter-js'; import { + AudioPlayer, Loop, Stage, KeyListener, @@ -16,6 +17,10 @@ import GameStore from './stores/game-store'; export default class Game extends Component { + static propTypes = { + onLeave: PropTypes.func, + }; + physicsInit = (engine) => { const ground = Matter.Bodies.rectangle( 512 * 3, 448, @@ -50,6 +55,9 @@ export default class Game extends Component { this.setState({ fade: true, }); + setTimeout(() => { + this.props.onLeave(); + }, 500); } constructor(props) { @@ -59,9 +67,15 @@ export default class Game extends Component { fade: true, }; this.keyListener = new KeyListener(); + window.AudioContext = window.AudioContext || window.webkitAudioContext; + window.context = new AudioContext(); } componentDidMount() { + this.player = new AudioPlayer('/assets/music.wav', () => { + this.stopMusic = this.player.play({ loop: true, offset: 1, volume: 0.35 }); + }); + this.setState({ fade: false, }); @@ -75,6 +89,7 @@ export default class Game extends Component { } componentWillUnmount() { + this.stopMusic(); this.keyListener.unsubscribe(); } diff --git a/demo/intro.js b/demo/intro.js index d6c94e4..424c63e 100644 --- a/demo/intro.js +++ b/demo/intro.js @@ -1,7 +1,8 @@ import React, { Component, PropTypes } from 'react'; - import Gamepad from 'html5-gamepad'; +import { AudioPlayer } from '../src'; + const gamepad = new Gamepad(); export default class Intro extends Component { @@ -12,6 +13,7 @@ export default class Intro extends Component { startUpdate = () => { gamepad.update(); if (gamepad.button(0, 'left stick')) { + this.startNoise.play(); this.props.onStart(); return; } @@ -20,6 +22,7 @@ export default class Intro extends Component { handleKeyPress = (e) => { if (e.keyCode === 13) { + this.startNoise.play(); this.props.onStart(); } } @@ -33,6 +36,7 @@ export default class Intro extends Component { } componentDidMount() { + this.startNoise = new AudioPlayer('/assets/start.wav'); window.addEventListener('keypress', this.handleKeyPress); this.animationFrame = requestAnimationFrame(this.startUpdate); this.interval = setInterval(() => { diff --git a/demo/presentation.js b/demo/presentation.js index 0d756b7..2d2de3f 100644 --- a/demo/presentation.js +++ b/demo/presentation.js @@ -2,13 +2,28 @@ import React, { Component } from 'react'; import Intro from './intro'; import Game from './game'; +import Slides from './slides'; export default class Presentation extends Component { + handleStart = () => { this.setState({ mode: 1, }); - } + }; + + handleDone = () => { + this.setState({ + mode: 1, + }); + }; + + handleLeave = () => { + this.setState({ + mode: 3, + }); + }; + constructor(props) { super(props); @@ -20,11 +35,16 @@ export default class Presentation extends Component { let componentToRender; switch (this.state.mode) { case 0: { - componentToRender = ; + componentToRender = ; break; } case 1: { - componentToRender = ; + componentToRender = ; + break; + } + case 3: { + componentToRender = ; + break; } } return componentToRender; diff --git a/demo/slides/index.js b/demo/slides/index.js new file mode 100644 index 0000000..c5c94f8 --- /dev/null +++ b/demo/slides/index.js @@ -0,0 +1,48 @@ +import React, { Component, PropTypes } from 'react'; +import Gamepad from 'html5-gamepad'; + +const gamepad = new Gamepad(); + +export default class Slides extends Component { + + static propTypes = { + onDone: PropTypes.func, + }; + + startUpdate = () => { + gamepad.update(); + if (gamepad.button(0, 'y')) { + this.props.onDone(); + return; + } + this.animationFrame = requestAnimationFrame(this.startUpdate); + } + + handleKeyPress = (e) => { + if (e.keyCode === 27) { + this.props.onDone(); + } + } + + constructor(props) { + super(props); + } + + componentDidMount() { + window.addEventListener('keypress', this.handleKeyPress); + this.animationFrame = requestAnimationFrame(this.startUpdate); + } + + componentWillUnmount() { + window.removeEventListener('keypress', this.handleKeyPress); + cancelAnimationFrame(this.animationFrame); + } + + render() { + return ( +
+

Ayy whatup

+
+ ); + } +} diff --git a/public/assets/background.wav b/public/assets/background.wav new file mode 100644 index 0000000..bd02f43 Binary files /dev/null and b/public/assets/background.wav differ diff --git a/public/assets/character-sprite.png b/public/assets/character-sprite.png index 11f0e94..bf2d2e6 100644 Binary files a/public/assets/character-sprite.png and b/public/assets/character-sprite.png differ diff --git a/public/assets/jt.wav b/public/assets/jt.wav new file mode 100644 index 0000000..55df539 Binary files /dev/null and b/public/assets/jt.wav differ diff --git a/public/assets/jump.wav b/public/assets/jump.wav new file mode 100644 index 0000000..f97d2e6 Binary files /dev/null and b/public/assets/jump.wav differ diff --git a/public/assets/music.wav b/public/assets/music.wav new file mode 100644 index 0000000..448160c Binary files /dev/null and b/public/assets/music.wav differ diff --git a/public/assets/old.png b/public/assets/old.png new file mode 100644 index 0000000..11f0e94 Binary files /dev/null and b/public/assets/old.png differ diff --git a/public/assets/start.wav b/public/assets/start.wav new file mode 100644 index 0000000..b6d443b Binary files /dev/null and b/public/assets/start.wav differ diff --git a/public/index.css b/public/index.css index 3064f2f..a2836bd 100644 --- a/public/index.css +++ b/public/index.css @@ -41,7 +41,7 @@ html, body, #root { bottom: 0; right: 0; background: black; - -webkit-transition: 1s opacity linear; + -webkit-transition: 500ms opacity linear; transition: 1s opacity linear; opacity: 0; } diff --git a/src/components/sprite.js b/src/components/sprite.js index 4e966b8..60f9a14 100644 --- a/src/components/sprite.js +++ b/src/components/sprite.js @@ -4,7 +4,9 @@ export default class Sprite extends Component { static propTypes = { animating: PropTypes.bool, + loop: PropTypes.bool, offset: PropTypes.array, + onPlayStateChanged: PropTypes.func, scale: PropTypes.number, src: PropTypes.string, state: PropTypes.number, @@ -17,7 +19,9 @@ export default class Sprite extends Component { static defaultProps = { animating: false, + loop: true, offset: [0, 0], + onPlayStateChanged: () => {}, src: '', state: 0, states: [], @@ -36,6 +40,7 @@ export default class Sprite extends Component { this.loopID = null; this.tickCount = 0; + this.finished = false; this.state = { currentStep: 0, @@ -43,12 +48,15 @@ export default class Sprite extends Component { } componentDidMount() { + this.props.onPlayStateChanged(1); const animate = this.animate.bind(this, this.props); this.loopID = this.context.loop.subscribe(animate); } componentWillReceiveProps(nextProps) { if (nextProps.state !== this.props.state) { + this.finished = false; + this.props.onPlayStateChanged(1); this.context.loop.unsubscribe(this.loopID); this.tickCount = 0; @@ -66,9 +74,9 @@ export default class Sprite extends Component { } animate(props) { - const { ticksPerFrame, state, states } = props; + const { loop, ticksPerFrame, state, states } = props; - if (this.tickCount === ticksPerFrame) { + if (this.tickCount === ticksPerFrame && !this.finished) { if (states[state] !== 0) { const { currentStep } = this.state; const lastStep = states[state]; @@ -77,6 +85,11 @@ export default class Sprite extends Component { this.setState({ currentStep: nextStep, }); + + if (currentStep === lastStep && loop === false) { + this.finished = true; + this.props.onPlayStateChanged(0); + } } this.tickCount = 0; diff --git a/src/index.js b/src/index.js index b52832f..3d99de3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +import AudioPlayer from './utils/audio-player.js'; import Body from './components/body.js'; import Loop from './components/loop.js'; import KeyListener from './utils/key-listener.js'; @@ -7,6 +8,7 @@ import TileMap from './components/tile-map.js'; import World from './components/world.js'; export { + AudioPlayer, Body, Loop, KeyListener, diff --git a/src/utils/audio-player.js b/src/utils/audio-player.js new file mode 100644 index 0000000..f3ea4de --- /dev/null +++ b/src/utils/audio-player.js @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ +export default class AudioPlayer { + constructor(url, callback) { + this.url = url || null; + this.callback = callback || function () {}; + + this.buffer = null; + + window.AudioContext = window.AudioContext || window.webkitAudioContext; + this.context = window.context || new AudioContext(); + + this.loadBuffer(); + } + + play = (options) => { + const volume = options && options.volume; + const offset = options && options.offset; + const loop = options && options.loop; + + const source = this.context.createBufferSource(); + const gainNode = this.context.createGain(); + gainNode.gain.value = volume || 0.5; + + gainNode.connect(this.context.destination); + source.connect(gainNode); + + source.buffer = this.buffer; + source.start(offset ? this.context.currentTime + offset : 0); + source.loop = loop || false; + return source.stop.bind(source); + } + + loadBuffer = () => { + const request = new XMLHttpRequest(); + request.open('GET', this.url, true); + request.responseType = 'arraybuffer'; + + request.onload = () => { + this.context.decodeAudioData( + request.response, + (buffer) => { + if (!buffer) { + console.error(`error decoding file data: ${this.url}`); + return; + } + this.buffer = buffer; + this.callback(); + }, + (error) => { + console.error('decodeAudioData error', error); + } + ); + }; + + request.onerror = function onError() { + console.error('BufferLoader: XHR error'); + }; + + request.send(); + } +} diff --git a/src/utils/shallow-equal.js b/src/utils/shallow-equal.js deleted file mode 100644 index 198de82..0000000 --- a/src/utils/shallow-equal.js +++ /dev/null @@ -1,27 +0,0 @@ -export default function shallowEqual(objA, objB) { - if (objA === objB) { - return true; - } - - if (typeof objA !== 'object' || objA === null || - typeof objB !== 'object' || objB === null) { - return false; - } - - let keysA = Object.keys(objA); - let keysB = Object.keys(objB); - - if (keysA.length !== keysB.length) { - return false; - } - - // Test for A's keys different from B. - let bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); - for (let i = 0; i < keysA.length; i++) { - if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { - return false; - } - } - - return true; -}