├── public
├── favicon.ico
├── sprite
│ ├── kp_run.png
│ ├── kp_out_of_breath.png
│ ├── kp_run.json
│ └── kp_out_of_breath.json
├── manifest.json
└── index.html
├── README.md
├── src
├── pixi
│ ├── constants.js
│ ├── loader.js
│ ├── state.js
│ ├── Render.js
│ └── Controller.js
├── index.js
├── index.css
└── App.js
├── .gitignore
├── config
├── jest
│ ├── fileTransform.js
│ └── cssTransform.js
├── polyfills.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
├── webpack.config.dev.js
└── webpack.config.prod.js
├── scripts
├── test.js
├── start.js
└── build.js
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiunhau/rx-pixi/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #rx-pixi
2 |
3 | A game engine experiment with Rxjs as controller, Pixijs as renderer.
4 |
--------------------------------------------------------------------------------
/public/sprite/kp_run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiunhau/rx-pixi/HEAD/public/sprite/kp_run.png
--------------------------------------------------------------------------------
/src/pixi/constants.js:
--------------------------------------------------------------------------------
1 | export const KEYCODE = {
2 | left: 37,
3 | up: 38,
4 | right: 39,
5 | down: 40
6 | }
7 |
--------------------------------------------------------------------------------
/public/sprite/kp_out_of_breath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chiunhau/rx-pixi/HEAD/public/sprite/kp_out_of_breath.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/pixi/loader.js:
--------------------------------------------------------------------------------
1 | import * as PIXI from 'pixi.js';
2 |
3 | const loader = PIXI.loader;
4 |
5 | loader
6 | .add('/sprite/kp_run.json')
7 | .add('/sprite/kp_out_of_breath.json')
8 |
9 | export default loader;
10 |
--------------------------------------------------------------------------------
/src/pixi/state.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | kp: {
3 | x: window.innerWidth / 2,
4 | y: window.innerHeight * 0.8,
5 | vx: 0,
6 | vy: 0,
7 | side: 'RIGHT',
8 | ani: 'STAND'
9 | }
10 | }
11 |
12 | export default state;
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | // This is a custom Jest transformer turning file imports into filenames.
6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
7 |
8 | module.exports = {
9 | process(src, filename) {
10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | padding: 0;
5 | margin: 0;
6 | overflow: hidden;
7 | }
8 |
9 | canvas {
10 | image-rendering: optimizeSpeed;
11 | image-rendering: -moz-crisp-edges;
12 | image-rendering: -o-crisp-edges;
13 | image-rendering: -webkit-optimize-contrast;
14 | image-rendering: optimize-contrast;
15 | image-rendering: crisp-edges;
16 | image-rendering: pixelated;
17 | -ms-interpolation-mode: nearest-neighbor;
18 | }
19 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import * as PIXI from 'pixi.js';
3 | import Controller from './pixi/Controller';
4 | import loader from './pixi/loader';
5 |
6 | class App extends Component {
7 | componentDidMount() {
8 | const renderer = new PIXI.autoDetectRenderer({
9 | width: window.innerWidth,
10 | height: window.innerHeight,
11 | backgroundColor: 0x1c3192,
12 | roundPixels: true
13 | });
14 |
15 | document.getElementById('pixi').appendChild(renderer.view);
16 | PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
17 |
18 | loader.load(() => {
19 | new Controller(renderer)
20 | })
21 | }
22 | render() {
23 | return (
24 |
27 | );
28 | }
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 | const jest = require('jest');
19 | const argv = process.argv.slice(2);
20 |
21 | // Watch unless on CI or in coverage mode
22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) {
23 | argv.push('--watch');
24 | }
25 |
26 |
27 | jest.run(argv);
28 |
--------------------------------------------------------------------------------
/config/polyfills.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | if (typeof Promise === 'undefined') {
4 | // Rejection tracking prevents a common issue where React gets into an
5 | // inconsistent state due to an error, but it gets swallowed by a Promise,
6 | // and the user has no idea what causes React's erratic future behavior.
7 | require('promise/lib/rejection-tracking').enable();
8 | window.Promise = require('promise/lib/es6-extensions.js');
9 | }
10 |
11 | // fetch() polyfill for making API calls.
12 | require('whatwg-fetch');
13 |
14 | // Object.assign() is commonly used with React.
15 | // It will use the native implementation if it's present and isn't buggy.
16 | Object.assign = require('object-assign');
17 |
18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet.
19 | // We don't polyfill it in the browser--this is user's responsibility.
20 | if (process.env.NODE_ENV === 'test') {
21 | require('raf').polyfill(global);
22 | }
23 |
--------------------------------------------------------------------------------
/public/sprite/kp_run.json:
--------------------------------------------------------------------------------
1 | {"frames":{"kp_run0.png":{"frame":{"x":0,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_run1.png":{"frame":{"x":50,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_run2.png":{"frame":{"x":100,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_run3.png":{"frame":{"x":150,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_run4.png":{"frame":{"x":200,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_run5.png":{"frame":{"x":250,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}}},"meta":{"app":"https://github.com/juliandescottes/piskel/","version":"1.0","image":"kp_run.png","format":"RGBA8888","size":{"w":300,"h":50}}}
2 |
--------------------------------------------------------------------------------
/public/sprite/kp_out_of_breath.json:
--------------------------------------------------------------------------------
1 | {"frames":{"kp_out_of_breath0.png":{"frame":{"x":0,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath1.png":{"frame":{"x":50,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath2.png":{"frame":{"x":100,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath3.png":{"frame":{"x":150,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath4.png":{"frame":{"x":200,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath5.png":{"frame":{"x":250,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}},"kp_out_of_breath6.png":{"frame":{"x":300,"y":0,"w":50,"h":50},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":50,"h":50},"sourceSize":{"w":50,"h":50}}},"meta":{"app":"https://github.com/juliandescottes/piskel/","version":"1.0","image":"kp_out_of_breath.png","format":"RGBA8888","size":{"w":350,"h":50}}}
2 |
--------------------------------------------------------------------------------
/src/pixi/Render.js:
--------------------------------------------------------------------------------
1 | import * as PIXI from 'pixi.js';
2 |
3 | class Render {
4 | constructor(state, renderer) {
5 | this.kp = new PIXI.extras.AnimatedSprite(getAnimatedTextures('kp_out_of_breath', 7));
6 | this.kp.animationSpeed = 0.18;
7 | this.kp.anis = [];
8 | this.kp.currentAni = 'STAND';
9 | this.kp.anis['STAND'] = getAnimatedTextures('kp_out_of_breath', 7);
10 | this.kp.anis['RUN'] = getAnimatedTextures('kp_run', 6);
11 | this.kp.anchor.set(0.5, 0.5);
12 | this.kp.scale.set(2);
13 | this.kp.x = state.kp.x;
14 | this.kp.y = state.kp.y;
15 | this.kp.play();
16 |
17 | this.stage = new PIXI.Container();
18 | this.stage.addChild(this.kp);
19 | renderer.render(this.stage);
20 | }
21 |
22 | render(state, renderer) {
23 | this.kp.x += state.kp.vx;
24 | this.kp.y += state.kp.vy;
25 | if (state.kp.ani !== this.kp.currentAni) {
26 | this.kp.currentAni = state.kp.ani;
27 | this.kp.textures = this.kp.anis[this.kp.currentAni];
28 | this.kp.play();
29 | }
30 | this.kp.scale.x = state.kp.side === 'LEFT' ? -2 : 2;
31 |
32 | renderer.render(this.stage);
33 | }
34 | }
35 |
36 | function getAnimatedTextures(ref, frames) {
37 | let textures = [];
38 | for(let i = 0; i < frames; i ++) {
39 | textures.push(PIXI.Texture.fromFrame(`${ref}${i}.png`))
40 | }
41 |
42 | return textures;
43 | }
44 |
45 | export default Render;
46 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/pixi/Controller.js:
--------------------------------------------------------------------------------
1 | import Rx from 'rx';
2 | import initalState from './state';
3 | import Render from './Render';
4 | import { KEYCODE } from './constants';
5 |
6 | class Controller {
7 | constructor(renderer) {
8 | let pixiRender = new Render(initalState, renderer)
9 |
10 | const keydown$ = Rx.Observable
11 | .fromEvent(document, 'keydown', e => {
12 | switch (e.keyCode) {
13 | case KEYCODE.left:
14 | return -1
15 | case KEYCODE.right:
16 | return 1
17 | default:
18 | }
19 | });
20 |
21 | const keyup$ = Rx.Observable
22 | .fromEvent(document, 'keyup', e => {
23 | switch (e.keyCode) {
24 | case KEYCODE.left:
25 | return -1
26 | case KEYCODE.right:
27 | return 1
28 | default:
29 | }
30 | })
31 | .withLatestFrom(keydown$)
32 | .filter(([keyup, keydown]) => {
33 | return keyup === keydown
34 | })
35 | .map(x => 0)
36 |
37 | const input$ = Rx.Observable
38 | .merge(keydown$, keyup$)
39 | .startWith(0)
40 | .distinctUntilChanged();
41 |
42 | const ticker$ = Rx.Observable
43 | .interval(0, Rx.Scheduler.requestAnimationFrame)
44 |
45 | const state$ = ticker$
46 | .withLatestFrom(input$)
47 | .scan(({kp}, [ticker, input]) => {
48 | switch (input) {
49 | case -1:
50 | kp.vx = -1;
51 | kp.side = 'LEFT';
52 | kp.ani = 'RUN';
53 | break;
54 | case 1:
55 | kp.vx = 1;
56 | kp.side = 'RIGHT';
57 | kp.ani = 'RUN';
58 | break;
59 | case 0:
60 | kp.vx = 0;
61 | kp.ani = 'STAND';
62 | break;
63 | default:
64 | }
65 |
66 | return {kp}
67 | }, initalState)
68 |
69 | ticker$
70 | .withLatestFrom(state$)
71 | .subscribe(([ticker, state]) => {
72 | pixiRender.render(state, renderer)
73 | })
74 | }
75 | }
76 |
77 | export default Controller;
78 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebookincubator/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(path, needsSlash) {
15 | const hasSlash = path.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return path.substr(path, path.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${path}/`;
20 | } else {
21 | return path;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right