├── .gitignore ├── app ├── constants │ └── AppConstants.js ├── displayobjects │ ├── Thingie │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ └── Thingie.js │ ├── Logo │ │ ├── logo@2x.png │ │ └── Logo.js │ ├── RedLine │ │ ├── line.png │ │ └── RedLine.js │ ├── Background │ │ ├── soft.jpg │ │ ├── diagnostic.png │ │ └── Background.js │ └── ScaledContainer │ │ └── ScaledContainer.js ├── utils.js ├── filters │ └── color │ │ ├── color.frag │ │ ├── color.vert │ │ └── color.js ├── stores │ ├── Store.js │ ├── AnimationStore.js │ ├── AppStore.js │ └── RendererStore.js ├── index.html ├── Renderer │ └── Renderer.js ├── screens │ ├── Loader.js │ └── Example.js └── entry.js ├── .babelrc ├── .prettierrc ├── .editorconfig ├── templates ├── store.js ├── displayobject.js └── reactman.config.js ├── .eslintrc ├── LICENSE ├── webpack.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | *.log* 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /app/constants/AppConstants.js: -------------------------------------------------------------------------------- 1 | export const canvasWidth = 1920; 2 | export const canvasHeight = 1080; 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-proposal-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } -------------------------------------------------------------------------------- /app/displayobjects/Thingie/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Thingie/1.png -------------------------------------------------------------------------------- /app/displayobjects/Thingie/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Thingie/2.png -------------------------------------------------------------------------------- /app/displayobjects/Thingie/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Thingie/3.png -------------------------------------------------------------------------------- /app/displayobjects/Thingie/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Thingie/4.png -------------------------------------------------------------------------------- /app/displayobjects/Thingie/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Thingie/5.png -------------------------------------------------------------------------------- /app/displayobjects/Logo/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Logo/logo@2x.png -------------------------------------------------------------------------------- /app/displayobjects/RedLine/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/RedLine/line.png -------------------------------------------------------------------------------- /app/displayobjects/Background/soft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Background/soft.jpg -------------------------------------------------------------------------------- /app/displayobjects/Background/diagnostic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwinwebb/pixi-seed/HEAD/app/displayobjects/Background/diagnostic.png -------------------------------------------------------------------------------- /app/utils.js: -------------------------------------------------------------------------------- 1 | export const checkScreen = (w, h, cw, ch) => w !== cw || h !== ch; 2 | export const randomRange = (m, x) => Math.random() * (x - m) + m; 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{js,json,jshintrc,html}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /app/filters/color/color.frag: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | varying vec2 vTextureCoord; 4 | uniform sampler2D uSampler; 5 | uniform vec3 color; 6 | 7 | void main(void) { 8 | vec4 c = vec4(color.r, color.g, color.b, 1.0); 9 | vec4 txtC = texture2D(uSampler, vTextureCoord); 10 | gl_FragColor = txtC * c; 11 | } -------------------------------------------------------------------------------- /templates/store.js: -------------------------------------------------------------------------------- 1 | const NEU = 'seed/animation/NEU'; 2 | 3 | export default (state = {}, action = {}) => { 4 | switch (action.type) { 5 | case NEU: 6 | return { 7 | ...state 8 | }; 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export const neu = () => ({ type: NEU }); 15 | -------------------------------------------------------------------------------- /app/filters/color/color.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | 4 | uniform mat3 projectionMatrix; 5 | varying vec2 vTextureCoord; 6 | 7 | void main(void){ 8 | gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); 9 | vTextureCoord = aTextureCoord; 10 | } -------------------------------------------------------------------------------- /app/stores/Store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers } from 'redux'; 2 | import Animation from './AnimationStore'; 3 | import Renderer from './RendererStore'; 4 | import App from './AppStore'; 5 | 6 | const Combi = combineReducers({ 7 | Renderer, 8 | App, 9 | }); 10 | 11 | export const AnimationStore = createStore(Animation); 12 | 13 | export default createStore(Combi); 14 | -------------------------------------------------------------------------------- /app/filters/color/color.js: -------------------------------------------------------------------------------- 1 | import { Filter, utils } from 'pixi.js'; 2 | import VERT from './color.vert'; 3 | import FRAG from './color.frag'; 4 | 5 | export default class ColorFilter extends Filter { 6 | constructor(color = 0xff00ff) { 7 | super(VERT, FRAG); 8 | this.color = color; 9 | } 10 | 11 | set color(color) { 12 | if (typeof color === 'number') { 13 | this.uniforms.color = utils.hex2rgb(color); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/displayobjects/Background/Background.js: -------------------------------------------------------------------------------- 1 | import { Sprite } from 'pixi.js'; 2 | import SOFT from './soft.jpg'; 3 | import ScaledContainer from '../ScaledContainer/ScaledContainer'; 4 | 5 | /** 6 | * Loads the adds the diagnostic image 7 | * 8 | * @exports Background 9 | * @extends ScaledContainer 10 | */ 11 | export default class Background extends ScaledContainer { 12 | constructor() { 13 | super(); 14 | 15 | const bg = Sprite.from(SOFT); 16 | 17 | this.addChild(bg); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/displayobject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * {%=o.description%} 3 | * 4 | * @exports {%=o.exports%} 5 | * @extends {%=o.extends%} 6 | */ 7 | {% if (o.extendpixi) { %} 8 | import { {%=o.extends%} } from 'pixi.js'; 9 | {% } else { %} 10 | import { %=o.extends% } from '../{%=o.extends%}/{%=o.extends%}.js'; 11 | {% } %} 12 | 13 | export default class {%=o.exports%} extends {%=o.extends%} { 14 | 15 | constructor(...args) { 16 | super(...args); 17 | } 18 | {% for(var i = 0; i < o.functions.length; i++) { %} 19 | {%= o.functions[i] %}() { 20 | 21 | } 22 | {% } %} 23 | } 24 | -------------------------------------------------------------------------------- /app/displayobjects/Logo/Logo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pixi Seed Logo 3 | * 4 | * @exports Logo 5 | * @extends Sprite 6 | */ 7 | 8 | import { Sprite, Texture } from 'pixi.js'; 9 | import LOGO from './logo@2x.png'; 10 | import ScaledContainer from '../ScaledContainer/ScaledContainer'; 11 | 12 | export default class Logo extends ScaledContainer { 13 | constructor() { 14 | const texture = Texture.from(LOGO); 15 | const sprite = new Sprite(texture); 16 | super(); 17 | this.addChild(sprite); 18 | sprite.anchor.x = 0.5; 19 | sprite.anchor.y = 0.5; 20 | sprite.position.set(1920 / 2, 1080 / 2); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/stores/AnimationStore.js: -------------------------------------------------------------------------------- 1 | // template : https://github.com/erikras/ducks-modular-redux 2 | 3 | const TICK = 'seed/animation/TICK'; 4 | 5 | const defaultState = { 6 | tick: 1, 7 | previousTick: 0, 8 | startTime: window.performance.now(), 9 | currentTime: window.performance.now(), 10 | }; 11 | 12 | export default (state = defaultState, action = {}) => { 13 | switch (action.type) { 14 | case TICK: 15 | return { 16 | ...state, 17 | tick: state.tick + 1, 18 | previousTick: state.tick, 19 | currentTime: window.performance.now(), 20 | }; 21 | default: 22 | return state; 23 | } 24 | }; 25 | 26 | export const tick = () => ({ type: TICK }); 27 | -------------------------------------------------------------------------------- /app/stores/AppStore.js: -------------------------------------------------------------------------------- 1 | const FILTER_COLOR = 'seed/animation/FILTER_COLOR'; 2 | const FILTER_ON = 'seed/animation/FILTER_ON'; 3 | 4 | export default ( 5 | state = { 6 | color: 0x9c0a3c, 7 | coloron: false, 8 | }, 9 | action = {} 10 | ) => { 11 | switch (action.type) { 12 | case FILTER_COLOR: 13 | return { 14 | ...state, 15 | color: parseInt(action.value.replace('#', '0x')), 16 | }; 17 | case FILTER_ON: 18 | return { 19 | ...state, 20 | coloron: action.value, 21 | }; 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | export const updateFilterColor = (value) => ({ type: FILTER_COLOR, value }); 28 | export const updateFilterIsOn = (value) => ({ type: FILTER_ON, value }); 29 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "extends": ["eslint:recommended", "prettier"], 9 | "rules": { 10 | "strict": [2, "never"], 11 | "quotes": [ 12 | 2, 13 | "single" 14 | ], 15 | "no-unused-vars": [2, { 16 | "vars": "local", 17 | "args": "after-used" 18 | }], 19 | "no-use-before-define": 2, 20 | "no-console": 2, 21 | "no-debugger": 2, 22 | "no-alert": 2, 23 | "no-trailing-spaces": 2, 24 | "key-spacing": [2, { 25 | "beforeColon": false, 26 | "afterColon": true 27 | }], 28 | "no-extra-semi": 2, 29 | "no-func-assign": 2, 30 | "no-invalid-regexp": 2, 31 | "no-dupe-keys": 2, 32 | "comma-dangle": [2, "never"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/displayobjects/RedLine/RedLine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Red line gfx 3 | * 4 | * Popmotion Tween Example 5 | * 6 | * @exports RedLine 7 | * @extends Sprite 8 | */ 9 | 10 | import { Sprite, Texture } from 'pixi.js'; 11 | import LINE from './line.png'; 12 | import { randomRange } from '../../utils'; 13 | import { easeInOut, animate } from 'popmotion'; 14 | 15 | export default class RedLine extends Sprite { 16 | constructor(x, y) { 17 | const texture = Texture.from(LINE); 18 | super(texture); 19 | const offset = randomRange(-500, 500); 20 | this.alpha = randomRange(0.2, 0.4); 21 | this.position.set(x, randomRange(y - 100, y + 200)); 22 | this.scale.set(randomRange(0.8, 1.2), randomRange(0.7, 1.4)); 23 | animate({ 24 | from: this.y, 25 | to: y + offset, 26 | duration: randomRange(200000, 400000), 27 | ease: easeInOut, 28 | flip: Infinity, 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/stores/RendererStore.js: -------------------------------------------------------------------------------- 1 | // template : https://github.com/erikras/ducks-modular-redux 2 | 3 | import { canvasWidth, canvasHeight } from '../constants/AppConstants'; 4 | 5 | const windowSize = () => ({ 6 | width: window.innerWidth, 7 | height: window.innerHeight, 8 | resolution: window.devicePixelRatio, 9 | stageCenter: { x: window.innerWidth / 2, y: window.innerHeight / 2 }, 10 | }); 11 | 12 | const defaultState = { 13 | canvasHeight, 14 | canvasWidth, 15 | canvasCenter: { 16 | x: canvasWidth / 2, 17 | y: canvasHeight / 2, 18 | }, 19 | ...windowSize(), 20 | }; 21 | 22 | const RESIZE = 'seed/animation/TICK'; 23 | 24 | export default (state = defaultState, action = {}) => { 25 | switch (action.type) { 26 | case RESIZE: 27 | return { 28 | ...state, 29 | ...windowSize(), 30 | }; 31 | default: 32 | return state; 33 | } 34 | }; 35 | 36 | export const resize = () => ({ type: RESIZE }); 37 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pixi Seed Project 7 | 35 | 36 | 37 |
38 | 39 | 40 | GLSL Color Mod 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Edwin Webb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var DEBUG = process.env.NODE_ENV !== 'production'; 3 | 4 | module.exports = { 5 | entry: { 6 | index: ['./app/entry.js'] 7 | }, 8 | output: { 9 | filename: 'bundle.js', 10 | path: path.resolve(__dirname, 'build') 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /node_modules/, 17 | use: { 18 | loader: 'babel-loader' 19 | } 20 | }, 21 | { 22 | test: /\.html$/, 23 | use: { 24 | loader: 'file-loader', 25 | options: { 26 | name: 'index.html' 27 | } 28 | } 29 | }, 30 | { 31 | test: /\.jpe?g$|\.svg$|\.png$/, 32 | use: { 33 | loader: 'file-loader' 34 | } 35 | }, 36 | { 37 | test: /\.(shader|vert|frag|geom)$/i, 38 | use: 'raw-loader' 39 | } 40 | ] 41 | }, 42 | devServer: { 43 | static: path.join(__dirname, 'build'), 44 | compress: true, 45 | port: 8080 46 | }, 47 | mode: DEBUG ? 'development' : 'production', 48 | devtool: DEBUG ? 'source-map' : false 49 | }; 50 | -------------------------------------------------------------------------------- /app/Renderer/Renderer.js: -------------------------------------------------------------------------------- 1 | import { Renderer } from 'pixi.js'; 2 | import Store, { AnimationStore } from '../stores/Store'; 3 | import { tick } from '../stores/AnimationStore'; 4 | import { resize } from '../stores/RendererStore'; 5 | 6 | /** 7 | * GL Renderer with hooks into a Store 8 | * 9 | * Manages main animation loop 10 | * 11 | * @exports AnimatedRenderer 12 | * @extends Renderer 13 | */ 14 | export default class AnimatedRenderer extends Renderer { 15 | constructor(options) { 16 | super(options); 17 | 18 | window.addEventListener('resize', this.resizeHandler.bind(this)); 19 | 20 | this.resizeHandler(); 21 | } 22 | 23 | /** 24 | * Dispatch resize 25 | * @return {null} 26 | */ 27 | resizeHandler() { 28 | Store.dispatch(resize()); 29 | this.resize(window.innerWidth, window.innerHeight); 30 | } 31 | 32 | /** 33 | * Start the animation loop 34 | * @return {null} 35 | */ 36 | start() { 37 | this.active = true; 38 | window.requestAnimationFrame(this.animate.bind(this)); 39 | } 40 | 41 | /** 42 | * Stop the animation loop 43 | * @return {null} 44 | */ 45 | stop() { 46 | this.active = false; 47 | } 48 | 49 | /** 50 | * Main animation loop, updates animation store 51 | * @return {null} 52 | */ 53 | animate() { 54 | if (this.active) { 55 | window.requestAnimationFrame(this.animate.bind(this)); 56 | AnimationStore.dispatch(tick()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/screens/Loader.js: -------------------------------------------------------------------------------- 1 | import { Graphics, Loader } from 'pixi.js'; 2 | import { AnimationStore } from '../stores/Store'; 3 | import Store from '../stores/Store'; 4 | import ScaledContainer from '../displayobjects/ScaledContainer/ScaledContainer'; 5 | 6 | /** 7 | * Loading Screen 8 | * 9 | * @exports LoaderScreen 10 | * @extends ScaledContainer 11 | */ 12 | 13 | export default class LoaderScreen extends ScaledContainer { 14 | constructor() { 15 | const { canvasWidth, canvasHeight } = Store.getState().Renderer; 16 | 17 | super(); 18 | 19 | this.loader = new Loader(); 20 | this.done = () => {}; 21 | 22 | // set up a bar 23 | this.bar = new Graphics().beginFill(0xff0000).drawRect(0, -2.5, 200, 5); 24 | this.bar.x = canvasWidth / 2 - 100; 25 | this.bar.y = canvasHeight / 2; 26 | this.bar.scale.x = 0; 27 | this.progress = 0; 28 | this.ease = 0; 29 | 30 | // animate it 31 | this.unsubscribe = AnimationStore.subscribe(() => { 32 | this.ease += (this.progress - this.ease) * 0.03; 33 | this.bar.scale.x = this.ease; 34 | }); 35 | 36 | this.addChild(this.bar); 37 | } 38 | 39 | start(assets = []) { 40 | this.loader.add(assets); 41 | this.loader.load(); 42 | this.loader.onProgress.add(this.onUpdate.bind(this)); 43 | this.loader.onComplete.add(this.onComplete.bind(this)); 44 | } 45 | 46 | onUpdate(ldr) { 47 | this.progress = ldr.progress / 100; 48 | } 49 | 50 | onComplete() { 51 | this.done(); 52 | this.unsubscribe(); 53 | } 54 | 55 | onLoaded(callback = () => {}) { 56 | this.done = callback; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /templates/reactman.config.js: -------------------------------------------------------------------------------- 1 | function commaSplit(i) { 2 | i = i.replace(" ", ""); 3 | return i.split(","); 4 | } 5 | 6 | module.exports = { 7 | "templatesFolder" : "./templates/", 8 | "outputFolder" : "./app/", 9 | "scripts" : { 10 | "displayobject" : { 11 | "files" : { 12 | "displayobject.js" : "displayobjects/{%=o.exports%}/{%=o.exports%}.js" 13 | }, 14 | "script" : [{ 15 | "name": "exports", 16 | "message": "Exports", 17 | "required": true, 18 | "default": "Exports", 19 | "type": "input" 20 | }, { 21 | "name": "extendpixi", 22 | "message": "Extend Pixi.js?", 23 | "default": true, 24 | "required": true, 25 | "type": "confirm" 26 | }, { 27 | "name": "extends", 28 | "message": "Extends", 29 | "default": "Extends", 30 | "required": true, 31 | "type": "input" 32 | }, { 33 | "name": "description", 34 | "message": "Description", 35 | "default": "A display object", 36 | "required": true, 37 | "type": "input" 38 | }, { 39 | "name": "functions", 40 | "message": "Comma seperated list of functions", 41 | "default": "", 42 | "required": false, 43 | "type": "input", 44 | "filter" : commaSplit 45 | }] 46 | }, 47 | "store" : { 48 | "files" : { 49 | "store.js" : "stores/{%=o.exports%}.js" 50 | }, 51 | "script" : [{ 52 | "name": "exports", 53 | "message": "Exports", 54 | "required": true, 55 | "default": "Exports", 56 | "type": "input" 57 | }] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/displayobjects/Thingie/Thingie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A small shape 3 | * 4 | * @exports Thingie 5 | * @extends Sprite 6 | */ 7 | 8 | import { Sprite, Texture, Point } from 'pixi.js'; 9 | import ONE from './1.png'; 10 | import TWO from './2.png'; 11 | import FOUR from './4.png'; 12 | import FIVE from './5.png'; 13 | 14 | const assets = [ONE, TWO, FOUR, FIVE]; 15 | 16 | export default class Thingie extends Sprite { 17 | constructor() { 18 | const asset = assets[Math.floor(Math.random() * assets.length)]; 19 | const texture = Texture.from(asset); 20 | super(texture); 21 | this.speed = Math.random() / 2 + 0.25; 22 | this.offset = new Point(0, 0); 23 | this.targetOffset = new Point(0, 0); 24 | this.originPosition = new Point(0, 0); 25 | this.alpha = 0.9; 26 | } 27 | 28 | setInitialPoint(x, y) { 29 | this.position.set(x, y); 30 | this.originPosition.set(x, y); 31 | } 32 | 33 | update(mousepos) { 34 | const { x, y } = mousepos; 35 | const x1 = this.originPosition.x; 36 | const y1 = this.originPosition.y; 37 | const xDist = x1 - x; 38 | const yDist = y1 - y; 39 | const dist = Math.sqrt(xDist * xDist + yDist * yDist); 40 | if (dist < 200) { 41 | const angle = Math.atan2(yDist, xDist); 42 | const xaDist = Math.cos(angle) * dist; 43 | const yaDist = Math.sin(angle) * dist; 44 | this.targetOffset.set(xaDist, yaDist); 45 | } else { 46 | this.targetOffset.set(0, 0); 47 | } 48 | this.offset.x += (this.targetOffset.x - this.offset.x) * 0.01; 49 | this.offset.y += (this.targetOffset.y - this.offset.y) * 0.01; 50 | this.position.set( 51 | this.originPosition.x + this.offset.x, 52 | this.originPosition.y + this.offset.y 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixi-seed", 3 | "version": "0.3.8", 4 | "repository": "https://github.com/edwinwebb/pixi-seed", 5 | "description": "Pixi.js project seed / boiler-plate with ES6, Webpack and Redux", 6 | "keywords": [ 7 | "webgl", 8 | "pixi", 9 | "pixijs", 10 | "pixi.js", 11 | "seed", 12 | "boilerplate", 13 | "es6", 14 | "glsl" 15 | ], 16 | "contributors": [ 17 | "Edwin Webb " 18 | ], 19 | "scripts": { 20 | "start": "webpack serve", 21 | "webpack": "webpack --mode development", 22 | "build": "webpack --mode production", 23 | "format": "find app -name '*.js' | xargs -I{} ./node_modules/.bin/prettier --write --single-quote {}", 24 | "prewebpack": "npm run clean", 25 | "precommit": "lint-staged", 26 | "clean": "rm -rf ./build && mkdir ./build", 27 | "lint": "eslint ./app/**/*.js" 28 | }, 29 | "dependencies": { 30 | "pixi.js": "^6.3.0", 31 | "popmotion": "^11.0.3", 32 | "redux": "^4.1.2" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.17.8", 36 | "@babel/plugin-proposal-object-rest-spread": "^7.17.3", 37 | "@babel/preset-env": "^7.16.11", 38 | "babel-eslint": "^10.1.0", 39 | "babel-loader": "^8.2.4", 40 | "eslint": "^8.12.0", 41 | "eslint-config-prettier": "^8.5.0", 42 | "eslint-plugin-prettier": "^4.0.0", 43 | "file-loader": "^6.2.0", 44 | "husky": "^7.0.4", 45 | "lint-staged": "^12.3.7", 46 | "prettier": "2.6.1", 47 | "raw-loader": "^4.0.2", 48 | "rimraf": "^3.0.2", 49 | "webpack": "^5.70.0", 50 | "webpack-cli": "^4.9.2", 51 | "webpack-dev-server": "^4.7.4" 52 | }, 53 | "engines": { 54 | "node": ">=8.0.0" 55 | }, 56 | "plugins": [ 57 | "prettier" 58 | ], 59 | "lint-staged": { 60 | "*.{js,json}": [ 61 | "prettier --write --single-quote", 62 | "git add" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/screens/Example.js: -------------------------------------------------------------------------------- 1 | import { Container, Point } from 'pixi.js'; 2 | import { canvasWidth, canvasHeight } from '../constants/AppConstants'; 3 | import { AnimationStore } from '../stores/Store'; 4 | import Logo from '../displayobjects/Logo/Logo'; 5 | import Background from '../displayobjects/Background/Background.js'; 6 | import Thingie from '../displayobjects/Thingie/Thingie'; 7 | import RedLine from '../displayobjects/RedLine/RedLine'; 8 | 9 | const isNear = (p1, p2) => { 10 | const a = p1.x - p2.x; 11 | const b = p1.y - p2.y; 12 | const c = Math.sqrt(a * a + b * b); 13 | return c < 100; 14 | }; 15 | 16 | /** 17 | * Main Display Object 18 | * 19 | * @exports Example 20 | * @extends Container 21 | */ 22 | export default class Example extends Container { 23 | constructor(...args) { 24 | var bg = new Background(); 25 | 26 | super(...args); 27 | 28 | const logo = new Logo(); 29 | this.addChild(bg); 30 | this.addLines(); 31 | this.addThingies(); 32 | this.addChild(logo); 33 | this.mousepos = new Point(500, 500); 34 | } 35 | 36 | addThingies() { 37 | this.thingies = []; 38 | for (let index = 0; index < 200; index++) { 39 | const t = new Thingie(); 40 | t.setInitialPoint( 41 | canvasWidth * Math.random(), 42 | (canvasHeight + 300) * Math.random() - 300 43 | ); 44 | const near = this.thingies.some((t2) => isNear(t.position, t2.position)); 45 | if (!near) { 46 | this.thingies.push(t); 47 | this.addChild(t); 48 | } 49 | } 50 | 51 | AnimationStore.subscribe(() => { 52 | this.thingies.forEach((t) => t.update(this.mousepos)); 53 | }); 54 | 55 | this.interactive = true; 56 | } 57 | 58 | addLines() { 59 | const count = 100; 60 | for (let index = 0; index < count; index++) { 61 | const y = Math.sin(index * 2) * canvasHeight - 500; 62 | const step = (canvasWidth / count) * index; 63 | const l = new RedLine(step, y); 64 | this.addChild(l); 65 | } 66 | } 67 | 68 | mousemove(e) { 69 | const { x, y } = e.data.global; 70 | if (this.mousepos.x !== x && this.mousepos.y !== y) { 71 | this.mousepos.set(x, y); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/displayobjects/ScaledContainer/ScaledContainer.js: -------------------------------------------------------------------------------- 1 | import { Container, Point } from 'pixi.js'; 2 | import Store from '../../stores/Store'; 3 | import { resize } from '../../stores/RendererStore'; 4 | import { checkScreen } from '../../utils'; 5 | 6 | /** 7 | * ScaledContainer 8 | * 9 | * A DisplayObjectContainer which attempts to scale and best-fit into the 10 | * window size dispatched from the RendererStore 11 | * 12 | * @extends Container 13 | * @exports ScaledContainer 14 | */ 15 | export default class ScaledContainer extends Container { 16 | /** 17 | * Set target size 18 | * @param {Number} target_w width 19 | * @param {number} target_h height 20 | * @return {null} 21 | */ 22 | constructor(...args) { 23 | super(...args); 24 | 25 | this.currentSize = { 26 | w: 0, 27 | h: 0, 28 | }; 29 | 30 | // TODO : init resize should come from renderer 31 | this.resizeHandler( 32 | window.innerWidth, 33 | window.innerHeight, 34 | Store.getState().Renderer.canvasWidth, 35 | Store.getState().Renderer.canvasHeight 36 | ); 37 | 38 | Store.subscribe(() => { 39 | const { width, height, canvasWidth, canvasHeight } = Store.getState().Renderer; 40 | const { w, h } = this.currentSize; 41 | const needsResize = checkScreen(width, height, w, h); 42 | 43 | if (needsResize) { 44 | this.resizeHandler(width, height, canvasWidth, canvasHeight); 45 | } 46 | 47 | this.currentSize = { 48 | w: width, 49 | h: height, 50 | }; 51 | }); 52 | } 53 | 54 | /** 55 | * Scales and positions Container to best-fit to target dimensions 56 | * @return {null} 57 | */ 58 | resizeHandler(rw, rh, tw = 1920, th = 1080) { 59 | const Xratio = rw / tw; 60 | const Yratio = rh / th; 61 | let scaleRatio = rw > rh ? Xratio : Yratio; 62 | let scale = new Point(scaleRatio, scaleRatio); 63 | let offsetX = rw / 2 - (tw * scaleRatio) / 2; 64 | let offsetY = rh / 2 - (th * scaleRatio) / 2; 65 | 66 | if (th * scaleRatio < rh) { 67 | scaleRatio = Yratio; 68 | scale = new Point(scaleRatio, scaleRatio); 69 | offsetX = rw / 2 - (tw * scaleRatio) / 2; 70 | offsetY = rh / 2 - (th * scaleRatio) / 2; 71 | } 72 | 73 | this.position.x = offsetX; 74 | this.position.y = offsetY; 75 | this.scale = scale; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/entry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App.js 3 | * 4 | * The main entry point from WebPack 5 | * - Appends render canvas to DOM 6 | * - Calls renderer.render 7 | * - Add Loading Screen and loads assets 8 | * - Adds Example Screen once loading is complete 9 | * - Subscribes and Dispatches to AppStore & DOM 10 | */ 11 | import { utils, Container } from 'pixi.js'; 12 | import './index.html'; 13 | import Renderer from './Renderer/Renderer'; 14 | import { AnimationStore } from './stores/Store'; 15 | import Store from './stores/Store'; 16 | import Example from './screens/Example'; 17 | import Loader from './screens/Loader'; 18 | import ColorFilter from './filters/color/color'; 19 | import { updateFilterColor, updateFilterIsOn } from './stores/AppStore'; 20 | import BG from './displayobjects/Background/soft.jpg'; 21 | import LOGO from './displayobjects/Logo/logo@2x.png'; 22 | 23 | const renderer = new Renderer({ resolution: window.devicePixelRatio }); // an extension of WebGLRenderer which dispatches to RendererStore 24 | const app = new Container(); // Auto scale to screen size, subscribed to RendererStore 25 | const loader = new Loader(); // Basic Loading screen 26 | 27 | // Controls for filter/DOM Redux example 28 | const colorOnInput = document.querySelector('#checkbox'); 29 | const colorValueInput = document.querySelector('#color'); 30 | const colorFilter = new ColorFilter(); 31 | 32 | // append renderer to DOM 33 | document.body.appendChild(renderer.view); 34 | 35 | // animate loop for render 36 | AnimationStore.subscribe(() => { 37 | renderer.render(app); 38 | }); 39 | 40 | // Update DOM and App.filters from AppStore 41 | Store.subscribe(() => { 42 | const { color, coloron } = Store.getState().App; 43 | colorOnInput.checked = coloron; 44 | colorValueInput.value = utils.hex2string(color); 45 | colorFilter.color = color; 46 | app.filters = coloron ? [colorFilter] : []; 47 | }); 48 | 49 | // Dispatch from DOM to AppStore 50 | colorValueInput.addEventListener('change', (v) => 51 | Store.dispatch(updateFilterColor(v.currentTarget.value)) 52 | ); 53 | colorOnInput.addEventListener('change', (v) => 54 | Store.dispatch(updateFilterIsOn(v.currentTarget.checked)) 55 | ); 56 | 57 | // Add loader to App Display Object and start loading assets 58 | app.addChild(loader); 59 | loader.start([BG, LOGO]); 60 | 61 | // remove loader then show example once asset loading is complete 62 | loader.onLoaded(() => { 63 | const example = new Example(); 64 | app.removeChild(loader); 65 | app.addChild(example); 66 | }); 67 | 68 | // start the render loop 69 | renderer.start(); 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PixiSeed](https://github.com/edwinwebb/pixi-seed/raw/master/app/displayobjects/Logo/logo%402x.png "Pixi Seed") 2 | 3 | # Pixi Seed 4 | 5 | [Demo](http://edwinwebb.github.io/pixi-seed/) 6 | 7 | This project is designed to bootstrap your [Pixi.js](https://github.com/pixijs/pixi.js) development with modern tooling, technology and project organisation. Use as boilerplate for your next project. 8 | 9 | Webpack with ES6 provides a more class based approach to Pixi.js development and allows you to include assets within your JS. [Reactman](https://www.npmjs.com/package/reactman) enables you to quickly add code to your project and the using Redux Stores helps keep your data in one place. 10 | 11 | The project comes with Render and Animation stores and a ScaledContainer to help work across multiple devices with a ‘best-fit’ rendering methodology. The code contains demos for TweenJS and renderer update loops. 12 | 13 | ## V3 Updates 14 | * Change stores to REDUX /w ducks 15 | * Update to Webpack V2 16 | * Update to Pixi V4.6 17 | * Added a loader screen 18 | * Added a custom [glsl filter example](https://github.com/edwinwebb/pixi-seed/tree/master/app/filters/color) 19 | * PopMotion with minor [examples](https://github.com/edwinwebb/pixi-seed/tree/master/app/displayobjects/RedLine/RedLine.js) 20 | * Animation loop [examples](https://github.com/edwinwebb/pixi-seed/tree/master/app/displayobjects/THingie/Thingie.js) 21 | * HTML forms to Redux/Pixi.js examples 22 | 23 | ## TODO 24 | * Move loader to REDUX 25 | * Script to redo package.json on new project 26 | * Add a screen manager 27 | 28 | ## Getting started 29 | 30 | Clone the project, remove the git repository and install to get going: 31 | 32 | ```bash 33 | git clone --depth=1 https://github.com/edwinwebb/pixi-seed.git my-project 34 | cd my-project 35 | rm -rf .git 36 | npm install 37 | npm start 38 | ``` 39 | 40 | Then visit http://localhost:8080 41 | 42 | ## Strapped Files 43 | You can configure your canvas size in the AppConstants.js file. 44 | 45 | ```js 46 | export const canvasWidth = 1920; 47 | export const canvasHeight = 1080; 48 | ``` 49 | 50 | The ScaledObjectContainer will try a best fit approach. Used in Logo and Background to auto scale. 51 | 52 | RedLine.js gives a Popmotion example 53 | 54 | Thingie.js gives an animation loop update example. 55 | 56 | 57 | ## npm scripts 58 | 59 | * `npm start` - Build and start the app in development mode at http://localhost:8080 60 | * `npm run build` - Run a production build, outputs to ./build/ 61 | * `npm run lint` - Lint your code 62 | * `npm run reactman` - Generate code for a DisplayObject or Store from command prompt. See [here](http://edwinwebb.github.io/reactman/). 63 | 64 | ## Static assets 65 | 66 | `import` asset files from within your JavaScript component files. To add more 67 | filetypes, look at the webpack.config.js and add a file loader. 68 | 69 | ```javascript 70 | // Filename: app.js 71 | import assetURL from './logo.png'; 72 | ``` 73 | 74 | ## License 75 | 76 | Copyright (c) 2017 Edwin Webb 77 | 78 | MIT (http://opensource.org/licenses/MIT) 79 | --------------------------------------------------------------------------------