diff --git a/demo/character.js b/demo/game/character.js similarity index 78% rename from demo/character.js rename to demo/game/character.js index 7f06052..d28403b 100644 --- a/demo/character.js +++ b/demo/game/character.js @@ -6,7 +6,7 @@ import Matter from 'matter-js'; import { Body, Sprite, -} from '../src'; +} from '../../src'; const gamepad = new Gamepad(); @@ -15,6 +15,7 @@ export default class Character extends Component { static propTypes = { keys: PropTypes.object, + onEnterBuilding: PropTypes.func, store: PropTypes.object, }; @@ -35,7 +36,26 @@ export default class Character extends Component { { x: 0, y: -0.15 }, ); Matter.Body.set(body, 'friction', 0); - } + }; + + enterBuilding = (body) => { + let doorIndex = null; + + const doorPositions = [...Array(6).keys()].map((a) => { + return [(512 * a) + 224, (512 * a) + 288]; + }); + + doorPositions.forEach((dp, di) => { + if (body.position.x + 64 > dp[0] && body.position.x + 64 < dp[1]) { + doorIndex = di; + } + }); + + if (doorIndex !== null) { + Matter.Events.off(this.context.engine, 'afterUpdate', this.update); + this.props.onEnterBuilding(doorIndex); + } + }; update = () => { const { keys, store } = this.props; @@ -44,14 +64,14 @@ export default class Character extends Component { const midPoint = Math.abs(store.stageX) + 448; const shouldMoveStageLeft = body.position.x < midPoint && store.stageX < 0; - const shouldMoveStageRight = body.position.x > midPoint && store.stageX > -1024; + const shouldMoveStageRight = body.position.x > midPoint && store.stageX > -2048; - if (body.velocity.y === 0) { + if (body.velocity.y === 0 || body.velocity.y < -100) { this.isJumping = false; Matter.Body.set(body, 'friction', 1); } - if (keys && !this.isJumping) { + if (!this.isJumping) { let characterState = 2; @@ -61,6 +81,10 @@ export default class Character extends Component { this.jump(body); } + if (keys.isDown(keys.UP) || gamepad.button(0, 'button 12')) { + this.enterBuilding(body); + } + if (keys.isDown(keys.LEFT) || gamepad.button(0, 'button 14')) { if (shouldMoveStageLeft) { store.setStageX(store.stageX + 5); @@ -128,10 +152,12 @@ export default class Character extends Component { } render() { + const x = this.props.store.characterPosition.x; + return (
{ this.body = b; }} > diff --git a/demo/game/fade.js b/demo/game/fade.js new file mode 100644 index 0000000..4342778 --- /dev/null +++ b/demo/game/fade.js @@ -0,0 +1,17 @@ +import React, { PropTypes } from 'react'; + +const Fade = (props) => ( +
+); + +Fade.propTypes = { + visible: PropTypes.bool, +}; + +Fade.defaultProps = { + visible: true, +}; + +export default Fade; diff --git a/demo/demo.js b/demo/game/index.js similarity index 73% rename from demo/demo.js rename to demo/game/index.js index 868d8fc..39a6fe9 100644 --- a/demo/demo.js +++ b/demo/game/index.js @@ -1,26 +1,25 @@ import React, { Component } from 'react'; +import Matter from 'matter-js'; import { Loop, Stage, KeyListener, World, -} from '../src'; +} from '../../src'; import Character from './character'; import Level from './level'; -import GameStore from './stores/game-store'; - -import './index.css'; +import Fade from './fade'; -import Matter from 'matter-js'; +import GameStore from './stores/game-store'; -export default class Demo extends Component { +export default class Game extends Component { physicsInit = (engine) => { const ground = Matter.Bodies.rectangle( - 448 * 2, 448, - 1024 * 2, 64, + 512 * 3, 448, + 1024 * 3, 64, { isStatic: true, }, @@ -35,7 +34,7 @@ export default class Demo extends Component { ); const rightWall = Matter.Bodies.rectangle( - 1984, 288, + 3008, 288, 64, 576, { isStatic: true, @@ -46,25 +45,43 @@ export default class Demo extends Component { Matter.World.addBody(engine.world, leftWall); Matter.World.addBody(engine.world, rightWall); } + + handleEnterBuilding = () => { + this.setState({ + fade: true, + }); + } + constructor(props) { super(props); + this.state = { + fade: true, + }; this.keyListener = new KeyListener(); } + componentDidMount() { + this.setState({ + fade: false, + }); + this.keyListener.subscribe([ this.keyListener.LEFT, this.keyListener.RIGHT, + this.keyListener.UP, this.keyListener.SPACE, ]); } + componentWillUnmount() { this.keyListener.unsubscribe(); } + render() { return ( - + @@ -72,11 +89,13 @@ export default class Demo extends Component { store={GameStore} /> + ); } diff --git a/demo/game/level.js b/demo/game/level.js new file mode 100644 index 0000000..61f029a --- /dev/null +++ b/demo/game/level.js @@ -0,0 +1,83 @@ +import React, { Component, PropTypes } from 'react'; +import { autorun } from 'mobx'; + +import { + TileMap, +} from '../../src'; + +import GameStore from './stores/game-store'; + +export default class Level extends Component { + + static contextTypes = { + scale: PropTypes.number, + }; + + constructor(props) { + super(props); + + this.state = { + stageX: 0, + }; + } + + componentDidMount() { + this.cameraWatcher = autorun(() => { + const targetX = Math.round(GameStore.stageX * this.context.scale); + this.setState({ + stageX: targetX, + }); + }); + } + + componentWillReceiveProps(nextProps, nextContext) { + const targetX = Math.round(GameStore.stageX * nextContext.scale); + this.setState({ + stageX: targetX, + }); + } + + componentWillUnmount() { + this.cameraWatcher(); + } + + getWrapperStyles() { + return { + position: 'absolute', + transform: `translate(${this.state.stageX}px, 0px) translateZ(0)`, + transformOrigin: 'top left', + }; + } + + render() { + return ( +
+ + +
+ ); + } +} diff --git a/demo/stores/game-store.js b/demo/game/stores/game-store.js similarity index 86% rename from demo/stores/game-store.js rename to demo/game/stores/game-store.js index e83706c..a312666 100644 --- a/demo/stores/game-store.js +++ b/demo/game/stores/game-store.js @@ -12,8 +12,8 @@ class GameStore { setStageX(x) { if (x > 0) { this.stageX = 0; - } else if (x < -1024) { - this.stageX = -1024; + } else if (x < -2048) { + this.stageX = -2048; } else { this.stageX = x; } diff --git a/demo/index.css b/demo/index.css deleted file mode 100644 index 140a5d7..0000000 --- a/demo/index.css +++ /dev/null @@ -1,11 +0,0 @@ -html, body, #root { - background: black; - height: 100%; - width: 100%; - margin: 0; - padding: 0; -} - -* { - box-sizing: border-box; -} \ No newline at end of file diff --git a/demo/index.js b/demo/index.js index c8aede6..3b6a4d3 100644 --- a/demo/index.js +++ b/demo/index.js @@ -1,8 +1,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Demo from './demo'; +import Presentation from './presentation'; ReactDOM.render( - , + , document.getElementById('root') ); diff --git a/demo/intro.js b/demo/intro.js new file mode 100644 index 0000000..d6c94e4 --- /dev/null +++ b/demo/intro.js @@ -0,0 +1,64 @@ +import React, { Component, PropTypes } from 'react'; + +import Gamepad from 'html5-gamepad'; + +const gamepad = new Gamepad(); + +export default class Intro extends Component { + static propTypes = { + onStart: PropTypes.func, + }; + + startUpdate = () => { + gamepad.update(); + if (gamepad.button(0, 'left stick')) { + this.props.onStart(); + return; + } + this.animationFrame = requestAnimationFrame(this.startUpdate); + } + + handleKeyPress = (e) => { + if (e.keyCode === 13) { + this.props.onStart(); + } + } + + constructor(props) { + super(props); + + this.state = { + blink: false, + }; + } + + componentDidMount() { + window.addEventListener('keypress', this.handleKeyPress); + this.animationFrame = requestAnimationFrame(this.startUpdate); + this.interval = setInterval(() => { + this.setState({ + blink: !this.state.blink, + }); + }, 500); + } + + componentWillUnmount() { + window.removeEventListener('keypress', this.handleKeyPress); + cancelAnimationFrame(this.animationFrame); + clearInterval(this.interval); + } + + render() { + return ( +
+ +

+ Press Start +

+
+ ); + } +} diff --git a/demo/level.js b/demo/level.js deleted file mode 100644 index 1bf4ceb..0000000 --- a/demo/level.js +++ /dev/null @@ -1,80 +0,0 @@ -import React, { Component, PropTypes } from 'react'; -import { autorun } from 'mobx'; - -import { - TileMap, -} from '../src'; - -import GameStore from './stores/game-store'; - -export default class Level extends Component { - - static contextTypes = { - scale: PropTypes.number, - }; - - constructor(props) { - super(props); - - this.state = { - stageX: 0, - }; - } - - componentDidMount() { - this.cameraWatcher = autorun(() => { - const targetX = Math.round(GameStore.stageX * this.context.scale); - this.setState({ - stageX: targetX, - }); - }); - } - - componentWillUnmount() { - this.cameraWatcher(); - } - - getWrapperStyles() { - return { - position: 'absolute', - transform: `translate(${this.state.stageX}px, 0px) translateZ(0)`, - transformOrigin: 'top left', - }; - } - - render() { - return ( -
- -
- ); - } -} diff --git a/demo/presentation.js b/demo/presentation.js new file mode 100644 index 0000000..0d756b7 --- /dev/null +++ b/demo/presentation.js @@ -0,0 +1,32 @@ +import React, { Component } from 'react'; + +import Intro from './intro'; +import Game from './game'; + +export default class Presentation extends Component { + handleStart = () => { + this.setState({ + mode: 1, + }); + } + constructor(props) { + super(props); + + this.state = { + mode: 0, + }; + } + render() { + let componentToRender; + switch (this.state.mode) { + case 0: { + componentToRender = ; + break; + } + case 1: { + componentToRender = ; + } + } + return componentToRender; + } +} diff --git a/package.json b/package.json index aff9f77..f77046d 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,8 @@ }, "author": "Ken Wheeler", "license": "MIT", - "repository": "https://github.com/FormidableLabs/react-music", + "repository": "https://github.com/FormidableLabs/react-game-kit", "dependencies": { - "html5-gamepad": "^0.1.4", "matter-js": "^0.10.0" }, "peerDependencies": { @@ -32,6 +31,7 @@ "devDependencies": { "babel-cli": "^6.10.1", "babel-core": "^6.10.4", + "html5-gamepad": "^0.1.4", "babel-eslint": "^6.1.2", "babel-loader": "^6.2.4", "babel-plugin-transform-decorators-legacy": "^1.3.4", diff --git a/public/assets/8BITWONDERNominal.eot b/public/assets/8BITWONDERNominal.eot new file mode 100644 index 0000000..e2c9e2a Binary files /dev/null and b/public/assets/8BITWONDERNominal.eot differ diff --git a/public/assets/8BITWONDERNominal.ttf b/public/assets/8BITWONDERNominal.ttf new file mode 100644 index 0000000..6d9b397 Binary files /dev/null and b/public/assets/8BITWONDERNominal.ttf differ diff --git a/public/assets/8BITWONDERNominal.woff b/public/assets/8BITWONDERNominal.woff new file mode 100644 index 0000000..3973ff5 Binary files /dev/null and b/public/assets/8BITWONDERNominal.woff differ diff --git a/public/assets/background.png b/public/assets/background.png deleted file mode 100644 index 9dbd5eb..0000000 Binary files a/public/assets/background.png and /dev/null differ diff --git a/public/assets/boardwalktile.png b/public/assets/boardwalktile.png new file mode 100644 index 0000000..ed34ef6 Binary files /dev/null and b/public/assets/boardwalktile.png differ diff --git a/public/assets/buildings.png b/public/assets/buildings.png new file mode 100644 index 0000000..740ff17 Binary files /dev/null and b/public/assets/buildings.png differ diff --git a/public/assets/intro.png b/public/assets/intro.png new file mode 100644 index 0000000..f375935 Binary files /dev/null and b/public/assets/intro.png differ diff --git a/public/index.css b/public/index.css index e69de29..3064f2f 100644 --- a/public/index.css +++ b/public/index.css @@ -0,0 +1,55 @@ +@font-face { + font-family: '8BIT WONDER'; + src: url('assets/8BITWONDERNominal.eot'); + src: url('assets/8BITWONDERNominal.eot?#iefix') format('embedded-opentype'), + url('assets/8BITWONDERNominal.woff') format('woff'), + url('assets/8BITWONDERNominal.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +html, body, #root { + background: black; + height: 100%; + width: 100%; + margin: 0; + padding: 0; + color: white; +} + +.intro { + margin: auto; + width: 100%; + max-width: 1024px; + display: block; +} + +.start { + font-family: '8BIT WONDER'; + display: block; + width: 400px; + font-size: 24px; + margin: -1% auto 0px; + text-align: center; + color: white; +} + +.fade { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: black; + -webkit-transition: 1s opacity linear; + transition: 1s opacity linear; + opacity: 0; +} + +.fade.active { + opacity: 1; +} + +* { + box-sizing: border-box; +} \ No newline at end of file diff --git a/src/components/body.js b/src/components/body.js index ccab375..1fe57ca 100644 --- a/src/components/body.js +++ b/src/components/body.js @@ -31,7 +31,7 @@ export default class Body extends Component { } componentWillUnmount() { - World.removeBody(this.context.engine.world, this.body); + World.remove(this.context.engine.world, this.body); } getChildContext() { diff --git a/src/components/sprite.js b/src/components/sprite.js index e140309..4e966b8 100644 --- a/src/components/sprite.js +++ b/src/components/sprite.js @@ -9,6 +9,7 @@ export default class Sprite extends Component { src: PropTypes.string, state: PropTypes.number, states: PropTypes.array, + style: PropTypes.object, ticksPerFrame: PropTypes.number, tileHeight: PropTypes.number, tileWidth: PropTypes.number, @@ -38,22 +39,12 @@ export default class Sprite extends Component { this.state = { currentStep: 0, - src: null, }; } componentDidMount() { - const image = new Image(); - image.onload = () => { - this.setState({ - src: this.props.src, - }); - - const animate = this.animate.bind(this, this.props); - this.loopID = this.context.loop.subscribe(animate); - }; - - image.src = this.props.src; + const animate = this.animate.bind(this, this.props); + this.loopID = this.context.loop.subscribe(animate); } componentWillReceiveProps(nextProps) { @@ -105,7 +96,6 @@ export default class Sprite extends Component { return { position: 'absolute', transform: `translate(-${left}px, -${top}px)`, - visibility: this.state.src ? 'visible' : 'hidden', }; } @@ -123,10 +113,10 @@ export default class Sprite extends Component { render() { return ( -
+
); diff --git a/src/components/stage.js b/src/components/stage.js index 121552f..38be0d3 100644 --- a/src/components/stage.js +++ b/src/components/stage.js @@ -59,10 +59,7 @@ export default class Stage extends Component { } getScale() { - const { container } = this; - const { dimensions } = this.state; - - const [vwidth, vheight] = dimensions; + const [vwidth, vheight] = this.state.dimensions; const { height, width } = this.props; let targetWidth; @@ -79,7 +76,7 @@ export default class Stage extends Component { targetScale = vwidth / width; } - if (!container) { + if (!this.container) { return { height, width, @@ -119,7 +116,7 @@ export default class Stage extends Component { render() { return (
{ this.container = c; }}> -
+
{this.props.children}
diff --git a/src/components/tile-map.js b/src/components/tile-map.js index 68e27a6..e2ec93f 100644 --- a/src/components/tile-map.js +++ b/src/components/tile-map.js @@ -10,6 +10,7 @@ export default class TileMap extends Component { rows: PropTypes.number, scale: PropTypes.number, src: PropTypes.string, + style: PropTypes.object, tileSize: PropTypes.number, }; @@ -68,7 +69,6 @@ export default class TileMap extends Component { } getTileData(row, column, index) { - const { scale } = this.context; const { tileSize } = this.props; const size = tileSize; @@ -135,7 +135,7 @@ export default class TileMap extends Component { render() { const layers = this.generateMap(); return ( -
+
{ layers.map((layer, index) => { return (
diff --git a/src/utils/shallow-equal.js b/src/utils/shallow-equal.js index 545e937..198de82 100644 --- a/src/utils/shallow-equal.js +++ b/src/utils/shallow-equal.js @@ -8,20 +8,20 @@ export default function shallowEqual(objA, objB) { return false; } - var keysA = Object.keys(objA); - var keysB = Object.keys(objB); + 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. - var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); - for (var i = 0; i < keysA.length; i++) { + 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; -} \ No newline at end of file +} diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 6cd3c84..7b5fd28 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -12,6 +12,11 @@ module.exports = { }, plugins: [ new webpack.NoErrorsPlugin(), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + }), ], module: { loaders: [{