├── .DS_Store ├── src ├── .DS_Store ├── images │ ├── .DS_Store │ ├── ad@2x.jpg │ ├── cover.jpg │ ├── red@2x.png │ ├── blue@2x.png │ ├── green@2x.png │ ├── pause@2x.png │ ├── play@2x.png │ ├── purple@2x.png │ ├── yellow@2x.png │ ├── star_red@2x.png │ ├── star_blue@2x.png │ ├── star_green@2x.png │ ├── star_purple@2x.png │ └── star_yellow@2x.png ├── script │ ├── .DS_Store │ ├── lib │ │ ├── .DS_Store │ │ ├── gsap │ │ │ ├── .DS_Store │ │ │ ├── plugins │ │ │ │ ├── AttrPlugin.js │ │ │ │ ├── EndArrayPlugin.js │ │ │ │ ├── RoundPropsPlugin.js │ │ │ │ ├── DirectionalRotationPlugin.js │ │ │ │ ├── CSSRulePlugin.js │ │ │ │ ├── TextPlugin.js │ │ │ │ ├── ModifiersPlugin.js │ │ │ │ ├── TEMPLATE_Plugin.js │ │ │ │ ├── ScrollToPlugin.js │ │ │ │ ├── ColorPropsPlugin.js │ │ │ │ ├── EaselPlugin.js │ │ │ │ ├── RaphaelPlugin.js │ │ │ │ ├── PixiPlugin.js │ │ │ │ └── BezierPlugin.js │ │ │ └── easing │ │ │ │ └── EasePack.js │ │ ├── shuffle.es6 │ │ ├── waveaverage.es6 │ │ ├── Event.es6 │ │ ├── utils.es6 │ │ ├── pixi.extension.js │ │ └── timer.es6 │ ├── Popstar.es6 │ └── core │ │ ├── Control.es6 │ │ ├── Model.es6 │ │ └── View.es6 ├── css │ └── popstar.css └── popstar.html ├── .gitignore ├── package.json ├── webpack.config.js └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/.DS_Store -------------------------------------------------------------------------------- /src/images/ad@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/ad@2x.jpg -------------------------------------------------------------------------------- /src/images/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/cover.jpg -------------------------------------------------------------------------------- /src/images/red@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/red@2x.png -------------------------------------------------------------------------------- /src/script/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/script/.DS_Store -------------------------------------------------------------------------------- /src/images/blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/blue@2x.png -------------------------------------------------------------------------------- /src/images/green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/green@2x.png -------------------------------------------------------------------------------- /src/images/pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/pause@2x.png -------------------------------------------------------------------------------- /src/images/play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/play@2x.png -------------------------------------------------------------------------------- /src/images/purple@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/purple@2x.png -------------------------------------------------------------------------------- /src/images/yellow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/yellow@2x.png -------------------------------------------------------------------------------- /src/script/lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/script/lib/.DS_Store -------------------------------------------------------------------------------- /src/images/star_red@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/star_red@2x.png -------------------------------------------------------------------------------- /src/images/star_blue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/star_blue@2x.png -------------------------------------------------------------------------------- /src/images/star_green@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/star_green@2x.png -------------------------------------------------------------------------------- /src/images/star_purple@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/star_purple@2x.png -------------------------------------------------------------------------------- /src/images/star_yellow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/images/star_yellow@2x.png -------------------------------------------------------------------------------- /src/script/lib/gsap/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leeenx/popstar/HEAD/src/script/lib/gsap/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .temp/ 3 | .rn_temp/ 4 | node_modules/ 5 | node_modules_back/ 6 | base_back/ 7 | backup/ 8 | .DS_Store 9 | .idea 10 | .vscode/ 11 | .prettierrc 12 | npm-debug.log* 13 | yarn-error.log 14 | package-lock.json 15 | -------------------------------------------------------------------------------- /src/script/lib/shuffle.es6: -------------------------------------------------------------------------------- 1 | /* 2 | @ Fisher–Yates(费雪耶兹算法) 3 | */ 4 | 5 | export default function shuffle(a) { 6 | for (let i = a.length; i; i--) { 7 | let j = Math.floor(Math.random() * i); 8 | [a[i - 1], a[j]] = [a[j], a[i - 1]]; 9 | } 10 | return a; 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "popstar", 3 | "version": "1.0.0", 4 | "description": "消灭星星", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config=webpack.config.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/leeenx/popstar.git" 12 | }, 13 | "author": "leeenx", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/leeenx/popstar/issues" 17 | }, 18 | "homepage": "https://github.com/leeenx/popstar#readme", 19 | "dependencies": { 20 | "@babel/cli": "^7.7.0", 21 | "@babel/core": "^7.7.2", 22 | "@babel/polyfill": "^7.7.0", 23 | "@babel/preset-env": "^7.7.1", 24 | "babel-loader": "^8.0.6", 25 | "babel-polyfill": "^6.26.0", 26 | "clean-webpack-plugin": "^3.0.0", 27 | "copy-webpack-plugin": "^5.0.5", 28 | "file-loader": "^4.2.0", 29 | "html-webpack-plugin": "^3.2.0", 30 | "image-loader": "0.0.1", 31 | "webpack": "^4.41.2", 32 | "webpack-cli": "^3.3.11", 33 | "webpack-dev-server": "^3.9.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/script/lib/waveaverage.es6: -------------------------------------------------------------------------------- 1 | // 快速波动均分算法 2 | export default function waveaverage(n = 5, crest = 4, trough = 4, isInteger = true) { 3 | // 平均结果 4 | let list = []; 5 | // 无法进行波动均分,直接返回完全平分 6 | if(crest > (n - 1) * trough || trough > (n - 1) * crest) { 7 | return new Array(n).fill(0); 8 | } 9 | // 最少需要消除的高度 10 | let base = 0; 11 | // 波动量 12 | let wave = 0; 13 | // 高位 14 | let high = crest; 15 | // 低位 16 | let low = -trough; 17 | // 累计量 18 | let sum = 0; 19 | // 剩余数量 20 | let count = n; 21 | 22 | while(--count >= 0) { 23 | // 获取当前的波动量 24 | if(crest > count * trough - sum) { 25 | high = count * trough - sum; 26 | } 27 | if(trough > count * crest + sum) { 28 | low = -sum - count * crest; 29 | } 30 | base = low; 31 | wave = high - low; 32 | // 随机波动量 33 | let rnd; 34 | if(count > 0) { 35 | // 随机波动 36 | rnd = base + Math.random() * (wave + 1); 37 | } 38 | else { 39 | rnd = -sum; 40 | } 41 | if(isInteger === true) { 42 | rnd = Math.floor(rnd); 43 | } 44 | sum += rnd; 45 | list.push(rnd); 46 | } 47 | return list; 48 | } -------------------------------------------------------------------------------- /src/script/Popstar.es6: -------------------------------------------------------------------------------- 1 | /* 2 | @ author: leeenx 3 | @ 消除星星 4 | */ 5 | 6 | // 向前兼容 7 | import '@babel/polyfill'; 8 | 9 | import Model from './core/Model.es6'; 10 | import View from './core/View.es6'; 11 | import Control from './core/Control.es6'; 12 | 13 | class Popstar { 14 | constructor(config) { 15 | // mvc 初始化 16 | this.model = new Model(); 17 | this.view = new View(config.view); 18 | // mv 由于 c 控制 19 | this.constrol = new Control( 20 | this.model, 21 | this.view, 22 | config.total 23 | ); 24 | // 挂载 event 25 | this.event = this.constrol.event; 26 | // 总得分 27 | Reflect.defineProperty( 28 | this, 29 | "total", 30 | { 31 | get: () => this.constrol.total, 32 | set: value => this.constrol.total = value 33 | } 34 | ); 35 | // 当前关卡 ---- 只读 36 | Reflect.defineProperty( 37 | this, 38 | "level", 39 | { 40 | get: () => this.constrol.curLevel 41 | } 42 | ); 43 | } 44 | // 开始游戏 45 | enter(level) { 46 | this.constrol.enter(level); 47 | } 48 | // 下一关 49 | next() { 50 | this.constrol.next(); 51 | } 52 | // 暂停 53 | pause() { 54 | this.constrol.pause(); 55 | } 56 | // 恢复 57 | resume() { 58 | this.constrol.resume(); 59 | } 60 | // 销毁 61 | destroy() { 62 | this.constrol.destroy(); 63 | } 64 | } 65 | 66 | window.Popstar = Popstar; -------------------------------------------------------------------------------- /src/script/lib/Event.es6: -------------------------------------------------------------------------------- 1 | /* 2 | @ author: leeenx 3 | @ 事件封装 4 | @ object.on(event, fn) // 监听一个事件 5 | @ object.off(event, fn) // 取消监听 6 | @ object.once(event, fn) // 只监听一次事件 7 | @ object.dispacth(event, arg) // 触发一个事件 8 | */ 9 | 10 | export default class Events { 11 | constructor() { 12 | // 定义的事件与回调 13 | this.defineEvent = {}; 14 | } 15 | // 注册事件 16 | register(event, cb) { 17 | if(!this.defineEvent[event]) { 18 | (this.defineEvent[event] = [cb]); 19 | } 20 | else { 21 | this.defineEvent[event].push(cb); 22 | } 23 | } 24 | // 派遣事件 25 | dispatch(event, arg) { 26 | if(this.defineEvent[event]) {{ 27 | for(let i=0, len = this.defineEvent[event].length; i this.defineEvent[event].splice(i, 1), 0); 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | 55 | // once 方法,监听一次 56 | once(event, cb) { 57 | let onceCb = () => { 58 | cb && cb(); 59 | this.off(event, onceCb); 60 | } 61 | this.register(event, onceCb); 62 | } 63 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var openInBrowser = process.argv[3] === '--silence' ? false: true 2 | var HtmlWebpackPlugin = require('html-webpack-plugin') 3 | var { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | var CopywebpackPlugin = require('copy-webpack-plugin') 5 | 6 | module.exports = { 7 | mode: 'development', 8 | context: __dirname, 9 | entry: [ 10 | './src/script/lib/pixi.js', 11 | './src/script/lib/gsap/TweenMax.js', 12 | './src/script/Popstar.es6', 13 | ], 14 | output: { 15 | path: __dirname + '/dist/', 16 | filename: '[name]-[chunkhash].js' 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: function (src) { 22 | if ( 23 | src.indexOf('script/lib/pixi.js') > 0 || 24 | src.indexOf('script/lib/gsap/TweenMax.js') > 0 25 | ) { 26 | return false 27 | } 28 | if (/\.es6$|\.js$/.test(src)) { 29 | return true 30 | } 31 | }, 32 | use: { 33 | loader: 'babel-loader', 34 | options: { 35 | presets: ['@babel/preset-env'] 36 | } 37 | } 38 | }, 39 | { 40 | test: /\.png$|\.jpg$|\.jpeg$|\.gif$/i, 41 | use: { 42 | loader: 'file-loader', 43 | query: { 44 | limit: 20000, 45 | name: '[name]-[hash:5].[ext]', 46 | outputPath: './images/', 47 | publicPath: '../images/' 48 | } 49 | }, 50 | } 51 | ] 52 | }, 53 | plugins: [ 54 | new CleanWebpackPlugin(), 55 | new HtmlWebpackPlugin({ 56 | title: 'H5小游戏100例: 消除星星', 57 | template: './src/popstar.html', 58 | filename: './dist/popstar.html' 59 | }), 60 | new CopywebpackPlugin([ 61 | { 62 | from: './src/css/popstar.css', 63 | to: './dist/css/popstar.css' 64 | } 65 | ]) 66 | ], 67 | watch: true, 68 | devServer: { 69 | contentBase: './dist/', 70 | open: openInBrowser, 71 | openPage: './dist/popstar.html' 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/AttrPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.1 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "attr", 19 | API: 2, 20 | version: "0.6.1", 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween, index) { 24 | var p, end; 25 | if (typeof(target.setAttribute) !== "function") { 26 | return false; 27 | } 28 | for (p in value) { 29 | end = value[p]; 30 | if (typeof(end) === "function") { 31 | end = end(index, target); 32 | } 33 | this._addTween(target, "setAttribute", target.getAttribute(p) + "", end + "", p, false, p); 34 | this._overwriteProps.push(p); 35 | } 36 | return true; 37 | } 38 | 39 | }); 40 | 41 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 42 | 43 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 44 | (function(name) { 45 | "use strict"; 46 | var getGlobal = function() { 47 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 48 | }; 49 | if (typeof(module) !== "undefined" && module.exports) { //node 50 | require("../TweenLite.js"); 51 | module.exports = getGlobal(); 52 | } else if (typeof(define) === "function" && define.amd) { //AMD 53 | define(["TweenLite"], getGlobal); 54 | } 55 | }("AttrPlugin")); -------------------------------------------------------------------------------- /src/script/lib/utils.es6: -------------------------------------------------------------------------------- 1 | // PIXI 的扩展 2 | PIXI.DisplayObject.prototype.set = function(arg) { 3 | for(let key in arg) { 4 | this[key] = arg[key]; 5 | } 6 | } 7 | 8 | // scale 属性拍平 9 | Object.defineProperties(PIXI.DisplayObject.prototype, { 10 | scaleX: { 11 | set: function(value) { 12 | this.scale.x = value; 13 | }, 14 | get: function() { 15 | return this.scale.x; 16 | } 17 | }, 18 | scaleY: { 19 | set: function(value) { 20 | this.scale.y = value; 21 | }, 22 | get: function() { 23 | return this.scale.y; 24 | } 25 | }, 26 | pivotX: { 27 | set: function(value) { 28 | this.pivot.x = value; 29 | }, 30 | get: function() { 31 | return this.pivot.x; 32 | } 33 | }, 34 | pivotY: { 35 | set: function(value) { 36 | this.pivot.y = value 37 | }, 38 | get: function() { 39 | return this.pivot.y; 40 | } 41 | }, 42 | anchorX: { 43 | set: function(value) { 44 | this.anchor.x = value; 45 | }, 46 | get: function() { 47 | return this.anchor.x; 48 | } 49 | }, 50 | anchorY: { 51 | set: function(value) { 52 | this.anchor.y = value 53 | }, 54 | get: function() { 55 | return this.anchor.y; 56 | } 57 | } 58 | }); 59 | 60 | // 获取不带描边的boudary 61 | { 62 | let dirty = Symbol("dirty"); 63 | let getContentBox = function() { 64 | if(this[dirty] == this.dirty) return ; 65 | this[dirty] = this.dirty; // 表示已经更新 66 | let cp = this.clone(); 67 | let graphicsData = cp.graphicsData; 68 | for(let graphics of graphicsData) { 69 | graphics.lineWidth = 0; 70 | } 71 | this._cwidth = cp.width; 72 | this._cheight = cp.height; 73 | } 74 | Object.defineProperties(PIXI.Graphics.prototype, { 75 | "_cwidth": {writable: true, value: 0}, 76 | "_cheight": {writable: true, value: 0}, 77 | "cwidth": { 78 | get: function() { 79 | getContentBox.call(this); 80 | return this._cwidth; 81 | } 82 | }, 83 | "cheight": { 84 | get: function() { 85 | getContentBox.call(this); 86 | return this._cheight; 87 | } 88 | } 89 | }); 90 | } -------------------------------------------------------------------------------- /src/css/popstar.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0 auto; 3 | padding: 0; 4 | max-width: 540px; 5 | overflow: hidden; 6 | } 7 | 8 | .popstar { 9 | position: relative; 10 | width: 100%; 11 | height: auto; 12 | } 13 | 14 | .popstar_menu { 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | overflow: hidden; 21 | transform: translate3d(0, 0, 0); 22 | transition: transform .3s ease; 23 | } 24 | 25 | .popstar_menu.hide { 26 | transform: translate3d(-100%, 0, 0); 27 | } 28 | 29 | .popstar_high_score { 30 | font-size: 16px; 31 | color: #fff; 32 | text-align: center; 33 | margin: 20px auto 80px; 34 | height: 24px; 35 | } 36 | 37 | .popstar_btn, .popstar_btn:hover, .popstar_btn:visited { 38 | display: block; 39 | width: 250px; 40 | height: 40px; 41 | line-height: 40px; 42 | color: #fff; 43 | font-size: 20px; 44 | text-align: center; 45 | text-decoration: none; 46 | margin: 15px auto; 47 | border-radius: 20px; 48 | background-color: #6190e8; 49 | text-shadow: 2px 2px 5px #000; 50 | box-shadow: inset -2px -2px 8px rgba(255, 255, 255, .3); 51 | } 52 | 53 | .popstar_btn.disabled, .popstar_btn.disabled:hover, .popstar_btn.disabled:visited { 54 | opacity: .8; 55 | } 56 | 57 | .popstar_ad { 58 | position: fixed; 59 | width: 300px; 60 | height: auto; 61 | top: 50%; 62 | left: 50%; 63 | transform: translate(-50%, -50%); 64 | display: none; 65 | } 66 | 67 | .popstar_ad img { 68 | display: block; 69 | width: 100%; 70 | margin: auto; 71 | } 72 | 73 | .popstar_ad_close { 74 | position: absolute; 75 | width: 24px; 76 | height: 24px; 77 | background-color: rgba(0, 0, 0, .5); 78 | top: -12px; 79 | right: -12px; 80 | border-radius: 100%; 81 | border: 1px solid rgba(255, 255, 255, 1); 82 | } 83 | 84 | .popstar_ad_close::before, .popstar_ad_close::after { 85 | content: ''; 86 | position: absolute; 87 | width: 16px; 88 | height: 1px; 89 | left: 4px; 90 | top: 11px; 91 | background-color: #fff; 92 | transform: rotateZ(45deg); 93 | } 94 | 95 | .popstar_ad_close::after { 96 | transform: rotateZ(-45deg); 97 | } 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/EndArrayPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.1.3 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "endArray", 19 | API: 2, 20 | version: "0.1.3", 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween) { 24 | var i = value.length, 25 | a = this.a = [], 26 | start, end; 27 | this.target = target; 28 | this._mod = 0; 29 | if (!i) { 30 | return false; 31 | } 32 | while (--i > -1) { 33 | start = target[i]; 34 | end = value[i]; 35 | if (start !== end) { 36 | a.push({i:i, s:start, c:end - start}); 37 | } 38 | } 39 | return true; 40 | }, 41 | 42 | mod: function(lookup) { 43 | if (typeof(lookup.endArray) === "function") { 44 | this._mod = lookup.endArray; 45 | } 46 | }, 47 | 48 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 49 | set: function(ratio) { 50 | var target = this.target, 51 | a = this.a, 52 | i = a.length, 53 | mod = this._mod, 54 | e, val; 55 | if (mod) { 56 | while (--i > -1) { 57 | e = a[i]; 58 | target[e.i] = mod(e.s + e.c * ratio, target); 59 | } 60 | } else { 61 | while (--i > -1) { 62 | e = a[i]; 63 | val = e.s + e.c * ratio; 64 | target[e.i] = (val < 0.000001 && val > -0.000001) ? 0 : val; 65 | } 66 | } 67 | } 68 | 69 | }); 70 | 71 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/RoundPropsPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.6.0 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var RoundPropsPlugin = _gsScope._gsDefine.plugin({ 18 | propName: "roundProps", 19 | version: "1.6.0", 20 | priority: -1, 21 | API: 2, 22 | 23 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 24 | init: function(target, value, tween) { 25 | this._tween = tween; 26 | return true; 27 | } 28 | 29 | }), 30 | _roundLinkedList = function(node) { 31 | while (node) { 32 | if (!node.f && !node.blob) { 33 | node.m = Math.round; 34 | } 35 | node = node._next; 36 | } 37 | }, 38 | p = RoundPropsPlugin.prototype; 39 | 40 | p._onInitAllProps = function() { 41 | var tween = this._tween, 42 | rp = (tween.vars.roundProps.join) ? tween.vars.roundProps : tween.vars.roundProps.split(","), 43 | i = rp.length, 44 | lookup = {}, 45 | rpt = tween._propLookup.roundProps, 46 | prop, pt, next; 47 | while (--i > -1) { 48 | lookup[rp[i]] = Math.round; 49 | } 50 | i = rp.length; 51 | while (--i > -1) { 52 | prop = rp[i]; 53 | pt = tween._firstPT; 54 | while (pt) { 55 | next = pt._next; //record here, because it may get removed 56 | if (pt.pg) { 57 | pt.t._mod(lookup); 58 | } else if (pt.n === prop) { 59 | if (pt.f === 2 && pt.t) { //a blob (text containing multiple numeric values) 60 | _roundLinkedList(pt.t._firstPT); 61 | } else { 62 | this._add(pt.t, prop, pt.s, pt.c); 63 | //remove from linked list 64 | if (next) { 65 | next._prev = pt._prev; 66 | } 67 | if (pt._prev) { 68 | pt._prev._next = next; 69 | } else if (tween._firstPT === pt) { 70 | tween._firstPT = next; 71 | } 72 | pt._next = pt._prev = null; 73 | tween._propLookup[prop] = rpt; 74 | } 75 | } 76 | pt = next; 77 | } 78 | } 79 | return false; 80 | }; 81 | 82 | p._add = function(target, p, s, c) { 83 | this._addTween(target, p, s, s + c, p, Math.round); 84 | this._overwriteProps.push(p); 85 | }; 86 | 87 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/popstar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | H5小游戏100例: 消除星星 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 | 29 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/DirectionalRotationPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.3.1 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine.plugin({ 18 | propName: "directionalRotation", 19 | version: "0.3.1", 20 | API: 2, 21 | 22 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 23 | init: function(target, value, tween, index) { 24 | if (typeof(value) !== "object") { 25 | value = {rotation:value}; 26 | } 27 | this.finals = {}; 28 | var cap = (value.useRadians === true) ? Math.PI * 2 : 360, 29 | min = 0.000001, 30 | p, v, start, end, dif, split; 31 | for (p in value) { 32 | if (p !== "useRadians") { 33 | end = value[p]; 34 | if (typeof(end) === "function") { 35 | end = end(index, target); 36 | } 37 | split = (end + "").split("_"); 38 | v = split[0]; 39 | start = parseFloat( (typeof(target[p]) !== "function") ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]() ); 40 | end = this.finals[p] = (typeof(v) === "string" && v.charAt(1) === "=") ? start + parseInt(v.charAt(0) + "1", 10) * Number(v.substr(2)) : Number(v) || 0; 41 | dif = end - start; 42 | if (split.length) { 43 | v = split.join("_"); 44 | if (v.indexOf("short") !== -1) { 45 | dif = dif % cap; 46 | if (dif !== dif % (cap / 2)) { 47 | dif = (dif < 0) ? dif + cap : dif - cap; 48 | } 49 | } 50 | if (v.indexOf("_cw") !== -1 && dif < 0) { 51 | dif = ((dif + cap * 9999999999) % cap) - ((dif / cap) | 0) * cap; 52 | } else if (v.indexOf("ccw") !== -1 && dif > 0) { 53 | dif = ((dif - cap * 9999999999) % cap) - ((dif / cap) | 0) * cap; 54 | } 55 | } 56 | if (dif > min || dif < -min) { 57 | this._addTween(target, p, start, start + dif, p); 58 | this._overwriteProps.push(p); 59 | } 60 | } 61 | } 62 | return true; 63 | }, 64 | 65 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 66 | set: function(ratio) { 67 | var pt; 68 | if (ratio !== 1) { 69 | this._super.setRatio.call(this, ratio); 70 | } else { 71 | pt = this._firstPT; 72 | while (pt) { 73 | if (pt.f) { 74 | pt.t[pt.p](this.finals[pt.p]); 75 | } else { 76 | pt.t[pt.p] = this.finals[pt.p]; 77 | } 78 | pt = pt._next; 79 | } 80 | } 81 | } 82 | 83 | })._autoCSS = true; 84 | 85 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 86 | 87 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 88 | (function(name) { 89 | "use strict"; 90 | var getGlobal = function() { 91 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 92 | }; 93 | if (typeof(module) !== "undefined" && module.exports) { //node 94 | require("../TweenLite.js"); 95 | module.exports = getGlobal(); 96 | } else if (typeof(define) === "function" && define.amd) { //AMD 97 | define(["TweenLite"], getGlobal); 98 | } 99 | }("DirectionalRotationPlugin")); -------------------------------------------------------------------------------- /src/script/core/Control.es6: -------------------------------------------------------------------------------- 1 | import Event from '../lib/Event.es6'; 2 | import timer from '../lib/timer.es6'; 3 | 4 | export default class Constrol { 5 | constructor (model, view, total = 0) { 6 | this.model = model, this.view = view; 7 | 8 | // event 事件 9 | this.event = new Event(); 10 | 11 | // view 与 control 共享一个event 12 | this.view.event = this.event; 13 | 14 | // 当前总分 15 | Reflect.defineProperty(this, "total", { 16 | get: () => this.view.total, 17 | set: value => this.view.total = value 18 | }); 19 | 20 | // view 与 control 共享一个 timer 21 | this.view.timer = timer; 22 | 23 | // 当前关卡 24 | Object.defineProperties( 25 | this, 26 | { 27 | "curLevel": { 28 | get: () => this._curLevel || 0, 29 | set: value => { 30 | this._curLevel = value || 0; 31 | this.view.setLevel(this._curLevel + 1); 32 | } 33 | }, 34 | "goal": { 35 | get: () => this._goal || 0, 36 | set: value => { 37 | this._goal = value || 0; 38 | this.view.setGoal(value); 39 | } 40 | } 41 | } 42 | ) 43 | 44 | // 数据绑定: model.tiles -> view.tiles 45 | model.tiles.forEach(tile => { 46 | Reflect.defineProperty(tile, "index", { 47 | set: value => { 48 | if(value === tile._index) return false; 49 | Reflect.set(tile, "_index", value); 50 | // 与view同步数据 51 | view.update(tile); 52 | }, 53 | get: () => Reflect.get(tile, "_index") 54 | }); 55 | 56 | Reflect.defineProperty(tile, "clr", { 57 | set: value => { 58 | if(value === tile._clr) return false; 59 | Reflect.set(tile, "_clr", value); 60 | // 与view同步数据 61 | view.update(tile); 62 | }, 63 | get: () => Reflect.get(tile, "_clr") 64 | }); 65 | 66 | Reflect.defineProperty(tile, "removed", { 67 | set: value => { 68 | if(value === tile._removed) return false; 69 | Reflect.set(tile, "_removed", value); 70 | // 与view同步数据 71 | view.update(tile); 72 | }, 73 | get: () => Reflect.get(tile, "_removed") || false 74 | }); 75 | 76 | }); 77 | 78 | // 当前分数 79 | this.total = total; 80 | 81 | // 监听点击事件 82 | this.event.on("view-tap", index => { 83 | // 暂停状态下锁屏 84 | if(this.paused === true) return; 85 | // 消除 model 的砖块 86 | let count = this.model.clean(index); 87 | // 显示边消信息 88 | count > 0 && this.view.setCleanInfo(count); 89 | }); 90 | 91 | // 清除相邻的同色砖事件 92 | this.event.on("view-clean", () => { 93 | // 结束压缩数组 94 | this.model.tamp(); 95 | // 游戏进入死局 ---- 清空剩余的色砖 96 | if(this.model.check() === false) { 97 | this.view.showBountyInfo(this.model.tileCount) 98 | .then( 99 | // 显示清空色砖,并计算奖励分数 100 | () => this.model.cleanAll() 101 | ) 102 | } 103 | }); 104 | // 清空所有的色砖事件 105 | this.event.on("view-clean-all", () => { 106 | if(this.total < this.goal) { 107 | // 游戏结束 108 | this.event.dispatch("gameover"); 109 | } 110 | // 通关 111 | else { 112 | this.event.dispatch("pass"); 113 | } 114 | }); 115 | // 按下暂停按钮 116 | this.event.on("view-press-pause", () => { 117 | // this.pause(); 118 | this.event.dispatch("pause"); 119 | }); 120 | // 按下播放按钮 121 | this.event.on("view-press-play", () => { 122 | // this.resume(); 123 | this.event.dispatch("resume"); 124 | }); 125 | 126 | } 127 | // 初关卡 128 | init() { 129 | // 默认五个颜色 130 | this.model.init(); 131 | // 砖块动画 132 | this.view.init(); 133 | } 134 | // 销毁 135 | destroy() { 136 | this.view.destroy(); 137 | // 总分数清空 138 | this.total = 0; 139 | } 140 | // 指定关数 141 | enter(level = 0) { 142 | this.curLevel = level || 0; 143 | // 目标分数 144 | this.goal = level * 2000 + 1000; 145 | // 初始化关卡 146 | this.init(); 147 | } 148 | // 下一关 149 | next() { 150 | this.enter(this.curLevel + 1); 151 | } 152 | // 暂停游戏 153 | pause() { 154 | // 暂停计时 155 | timer.pause(); 156 | // 暂停渲染 157 | this.view.pause(); 158 | // 标记暂停 159 | this.paused = true; 160 | } 161 | // 恢复游戏 162 | resume() { 163 | // 恢复计时 164 | timer.resume(); 165 | // 恢复渲染 166 | this.view.resume(); 167 | // 标记恢复 168 | this.paused = false; 169 | } 170 | } -------------------------------------------------------------------------------- /src/script/lib/pixi.extension.js: -------------------------------------------------------------------------------- 1 | // 扩展 2 | // DisplayObject 的原型 3 | var proto = PIXI.DisplayObject.prototype; 4 | proto.set = function(arg) { 5 | for(let key in arg) { 6 | this[key] = arg[key]; 7 | } 8 | } 9 | 10 | Object.defineProperties(proto, { 11 | scaleX: { 12 | set: function(value) { 13 | this.scale.x = value; 14 | }, 15 | get: function() { 16 | return this.scale.x; 17 | } 18 | }, 19 | scaleY: { 20 | set: function(value) { 21 | this.scale.y = value; 22 | }, 23 | get: function() { 24 | return this.scale.y; 25 | } 26 | }, 27 | pivotX: { 28 | set: function(value) { 29 | this.pivot.x = value; 30 | }, 31 | get: function() { 32 | return this.pivot.x; 33 | } 34 | }, 35 | pivotY: { 36 | set: function(value) { 37 | this.pivot.y = value 38 | }, 39 | get: function() { 40 | return this.pivot.y; 41 | } 42 | }, 43 | anchorX: { 44 | set: function(value) { 45 | this.anchor.x = value; 46 | }, 47 | get: function() { 48 | return this.anchor.x; 49 | } 50 | }, 51 | anchorY: { 52 | set: function(value) { 53 | this.anchor.y = value 54 | }, 55 | get: function() { 56 | return this.anchor.y; 57 | } 58 | }, 59 | origin: { 60 | get: function() { 61 | return [this.pivot.x, this.pivot.y]; 62 | }, 63 | set: function(coord) { 64 | this.pivot.set(coord[0], coord[1]); 65 | this.updatePosition(); 66 | } 67 | }, 68 | left: { 69 | get: function() { 70 | return this._left === undefined ? this.x - this.pivot.x : this._left; 71 | }, 72 | set: function(value) { 73 | this._left = value; 74 | this.x = value + this.pivot.x; 75 | } 76 | }, 77 | right: { 78 | get: function() { 79 | return this._right === undefined ? this._right : this.x - this.pivot.x; 80 | }, 81 | set: function(value) { 82 | if(value === undefined) return ; 83 | this._right = value; 84 | if(this.parent !== null) { 85 | this.x = this.parent.width - this.width - value; 86 | } 87 | else { 88 | this.x = 0; 89 | } 90 | } 91 | }, 92 | top: { 93 | get: function() { 94 | return this._top === undefined ? this.y - this.pivot.y : this._top; 95 | }, 96 | set: function(value) { 97 | this._top = value; 98 | this.y = value + this.pivot.y; 99 | } 100 | }, 101 | bottom: { 102 | get: function() { 103 | return this._bottom === undefined ? this._bottom : this.y - this.pivot.y; 104 | }, 105 | set: function(value) { 106 | if(value === undefined) return ; 107 | this._bottom = value; 108 | if(this.parent !== null) { 109 | this.y = this.parent.height - this.height - value; 110 | } 111 | else { 112 | this.y = 0; 113 | } 114 | } 115 | }, 116 | // 运动时间 117 | time: { 118 | set: function(t) { 119 | let elapsed = t - this._t || t; 120 | this._t = t; 121 | let {velocityX, velocityY, accelerationX, accelerationY} = this; 122 | // 当前速度 123 | this.velocityX += elapsed * accelerationX; 124 | this.velocityY += elapsed * accelerationY; 125 | // 当前位置 126 | this.x += (this.velocityX + velocityX) * elapsed / 2; 127 | this.y += (this.velocityY + velocityY) * elapsed / 2; 128 | }, 129 | get: function() {return this._t} 130 | } 131 | }); 132 | 133 | // 为原型添加速度与加速度属性等 134 | Object.assign( 135 | proto, 136 | { 137 | velocityX: 0, 138 | velocityY: 0, 139 | accelerationX: 0, 140 | accelerationY: 0, 141 | _top: 0, 142 | _left: 0 143 | } 144 | ); 145 | 146 | // 更新 position 147 | proto.updatePosition = function() { 148 | this.top = this._top; 149 | this.right = this._right; 150 | this.bottom = this._bottom; 151 | this.left = this._left; 152 | } 153 | 154 | // 监听 addChild 155 | var _addChild = PIXI.Container.prototype.addChild; 156 | PIXI.Container.prototype.addChild = function() { 157 | var len = arguments.length; 158 | if(len === 0) return ; 159 | _addChild.apply(this, arguments); 160 | // 更新 right & bottom 161 | for(var i = 0; i < len; ++i) { 162 | var child = arguments[i]; 163 | child.right = child._right; 164 | child.bottom = child._bottom; 165 | } 166 | } 167 | var _addChildAt = PIXI.Container.prototype.addChildAt; 168 | PIXI.Container.prototype.addChildAt = function(child, index) { 169 | _addChildAt.call(this, child, index); 170 | // 更新 right & bottom 171 | child.right = child._right; 172 | child.bottom = child._bottom; 173 | } -------------------------------------------------------------------------------- /src/script/lib/timer.es6: -------------------------------------------------------------------------------- 1 | /* 2 | author: leeenx 3 | @ timer 对象 4 | @ 提供 7 个API如下: 5 | @ timer.setTimeout(fun, delay[, id]) 6 | @ timer.clearTimeout(id) 7 | @ timer.setInterval(fun, delay[, id]) 8 | @ timer.clearInterval(id) 9 | @ timer.delete(id) 删除对应id的timeout/interval 10 | @ timer.pause(id) 暂停对应id的timeout/interval 11 | @ timer.resume(id) 恢复对应id的timeout/interval 12 | @ timer.clean() 清空所有 timeout & interval 13 | */ 14 | 15 | class Timer { 16 | // 构造函数 17 | constructor() { 18 | // 暂停状态 - 这是个公共状态,由外部 Ticker 决定。 19 | this.paused = true; 20 | 21 | // 队列 22 | this.queue = new Map(); 23 | 24 | // 正在使用 timer 的 RAF 25 | this.usingRAF = false; 26 | 27 | // useRAF 触发器 28 | Reflect.defineProperty(this, "useRAF", { 29 | set: function(value) { 30 | Reflect.set(this, "usingRAF", value); 31 | value ? Timer.RAF.enable() : Timer.RAF.disable(); 32 | } 33 | }); 34 | } 35 | 36 | // setTimeout 的实现 37 | setTimeout(fn, delay, id = Symbol("timeoutID")) { 38 | // 存入队列 39 | this.queue.set(id, { 40 | fn: fn, 41 | type: 0, 42 | paused: 0, 43 | elapsed: 0, 44 | delay: delay 45 | }); 46 | return id; 47 | } 48 | 49 | // clearTimeout 50 | clearTimeout(id) { 51 | return this.delete(id); 52 | } 53 | 54 | // setInterval 的实现 55 | setInterval(fn, delay, id = Symbol("intervalID")) { 56 | // 存入队列 57 | this.queue.set(id, { 58 | fn: fn, 59 | type: 1, 60 | paused: 0, 61 | elapsed: 0, 62 | delay: delay 63 | }); 64 | return id; 65 | } 66 | 67 | // clearInterval 68 | clearInterval(id) { 69 | return this.delete(id); 70 | } 71 | 72 | // 修改指定id的 delay/fn 73 | set(id, config = {}) { 74 | let item = this.queue.get(id) || {}; 75 | for(let key in config) { 76 | item[key] = config[key]; 77 | } 78 | return true; 79 | } 80 | 81 | // 删除 queue 上的成员 82 | delete(id) { 83 | return this.queue.delete(id); 84 | } 85 | 86 | // 暂停指定id 87 | pause(id) { 88 | id === undefined ? this.pauseAll() : (this.queue.get(id).paused = 1); 89 | return true; 90 | } 91 | 92 | // 恢复指定id 93 | resume(id) { 94 | return this.play(id); 95 | } 96 | 97 | // 播放指定id 98 | play(id) { 99 | id === undefined ? this.playAll() : (this.queue.get(id).paused = 0); 100 | return true; 101 | } 102 | 103 | // 清空timer 104 | clean() { 105 | this.queue = new Map(); 106 | return true; 107 | } 108 | 109 | // 暂停全部 id 110 | pauseAll() { 111 | this.queue.forEach((item) => item.paused = 1); 112 | return true; 113 | } 114 | 115 | // 播放全部 id 116 | playAll() { 117 | this.queue.forEach((item) => item.paused = 0); 118 | return true; 119 | } 120 | 121 | // tick 122 | tick(delta) { 123 | this.paused || this.updateQueue(delta); 124 | } 125 | 126 | // 更新 map 队列 127 | updateQueue(delta) { 128 | this.queue.forEach((item, id) => { 129 | if(item.paused === 1) return; 130 | item.elapsed += delta; 131 | if(item.elapsed >= item.delay) { 132 | item.fn(); 133 | item.type === 0 ? this.delete(id) : (item.elapsed = 0); 134 | } 135 | }); 136 | } 137 | 138 | // 状态更新 139 | update() { 140 | // 第一次调用 update 时主动停用原生接口 141 | this.useRAF = false; 142 | 143 | // 下面是真正的 update 144 | this.update = (delta) => { 145 | if(this.usingRAF) return; 146 | this.tick(delta); 147 | } 148 | } 149 | 150 | } 151 | 152 | class AnimationFrame { 153 | constructor() { 154 | this.time = 0; 155 | this.auto = this.auto.bind(this); 156 | } 157 | auto(elapsed) { 158 | timer.tick(elapsed - this.time); 159 | this.time = elapsed; 160 | this.id = requestAnimationFrame(this.auto); 161 | } 162 | enable() { 163 | timer.paused = false; 164 | this.id = requestAnimationFrame(this.auto); 165 | } 166 | disable() { 167 | cancelAnimationFrame(this.id); 168 | } 169 | } 170 | 171 | // 原生RAF 172 | Timer.RAF = new AnimationFrame(); 173 | 174 | // 对外接口 175 | let timer = new Timer(); 176 | 177 | // 默认使用原生 RAF 178 | timer.useRAF = true; 179 | // 导出timer 180 | export default timer; -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/CSSRulePlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.6 3 | * DATE: 2017-06-29 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine("plugins.CSSRulePlugin", ["plugins.TweenPlugin","TweenLite","plugins.CSSPlugin"], function(TweenPlugin, TweenLite, CSSPlugin) { 18 | 19 | /** @constructor **/ 20 | var CSSRulePlugin = function() { 21 | TweenPlugin.call(this, "cssRule"); 22 | this._overwriteProps.length = 0; 23 | }, 24 | _doc = _gsScope.document, 25 | _superSetRatio = CSSPlugin.prototype.setRatio, 26 | p = CSSRulePlugin.prototype = new CSSPlugin(); 27 | 28 | p._propName = "cssRule"; 29 | p.constructor = CSSRulePlugin; 30 | CSSRulePlugin.version = "0.6.6"; 31 | CSSRulePlugin.API = 2; 32 | 33 | /** 34 | * Searches the style sheets in the document for a particular selector like ".myClass" or "a" or "a:hover" or ":after" and 35 | * returns a reference to that style sheet (or an array of them in the case of a pseudo selector like ":after"). Then you 36 | * can animate the individual properties of the style sheet. 37 | * 38 | * @param {!string} selector a string describing the selector, like ".myClass" or "a" or "a:hover" or ":after" 39 | * @return a reference to the style sheet (or an array of them in the case of a pseudo selector). If none was found, null is returned (or an empty array for a pseudo selector) 40 | */ 41 | CSSRulePlugin.getRule = function(selector) { 42 | var ruleProp = _doc.all ? 'rules' : 'cssRules', 43 | ss = _doc.styleSheets, 44 | i = ss.length, 45 | pseudo = (selector.charAt(0) === ":"), 46 | j, curSS, cs, a; 47 | selector = (pseudo ? "" : ",") + selector.split("::").join(":").toLowerCase() + ","; //note: old versions of IE report tag name selectors as upper case, so we just change everything to lowercase. 48 | if (pseudo) { 49 | a = []; 50 | } 51 | while (--i > -1) { 52 | //Firefox may throw insecure operation errors when css is loaded from other domains, so try/catch. 53 | try { 54 | curSS = ss[i][ruleProp]; 55 | if (!curSS) { 56 | continue; 57 | } 58 | j = curSS.length; 59 | } catch (e) { 60 | console.log(e); 61 | continue; 62 | } 63 | while (--j > -1) { 64 | cs = curSS[j]; 65 | if (cs.selectorText && ("," + cs.selectorText.split("::").join(":").toLowerCase() + ",").indexOf(selector) !== -1) { //note: IE adds an extra ":" to pseudo selectors, so .myClass:after becomes .myClass::after, so we need to strip the extra one out. 66 | if (pseudo) { 67 | a.push(cs.style); 68 | } else { 69 | return cs.style; 70 | } 71 | } 72 | } 73 | } 74 | return a; 75 | }; 76 | 77 | 78 | // @private gets called when the tween renders for the first time. This kicks everything off, recording start/end values, etc. 79 | p._onInitTween = function(target, value, tween) { 80 | if (target.cssText === undefined) { 81 | return false; 82 | } 83 | var div = target._gsProxy = target._gsProxy || _doc.createElement("div"); 84 | this._ss = target; 85 | this._proxy = div.style; 86 | div.style.cssText = target.cssText; 87 | CSSPlugin.prototype._onInitTween.call(this, div, value, tween); //we just offload all the work to the regular CSSPlugin and then copy the cssText back over to the rule in the setRatio() method. This allows us to have all of the updates to CSSPlugin automatically flow through to CSSRulePlugin instead of having to maintain both 88 | return true; 89 | }; 90 | 91 | 92 | 93 | // @private gets called every time the tween updates, passing the new ratio (typically a value between 0 and 1, but not always (for example, if an Elastic.easeOut is used, the value can jump above 1 mid-tween). It will always start and 0 and end at 1. 94 | p.setRatio = function(v) { 95 | _superSetRatio.call(this, v); 96 | this._ss.cssText = this._proxy.cssText; 97 | }; 98 | 99 | 100 | TweenPlugin.activate([CSSRulePlugin]); 101 | return CSSRulePlugin; 102 | 103 | }, true); 104 | 105 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 106 | 107 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 108 | (function(name) { 109 | "use strict"; 110 | var getGlobal = function() { 111 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 112 | }; 113 | if (typeof(module) !== "undefined" && module.exports) { //node 114 | require("../TweenLite.js"); 115 | module.exports = getGlobal(); 116 | } else if (typeof(define) === "function" && define.amd) { //AMD 117 | define(["TweenLite"], getGlobal); 118 | } 119 | }("CSSRulePlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/TextPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.6.1 3 | * DATE: 2017-06-30 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _getText = function(e) { 18 | var type = e.nodeType, 19 | result = ""; 20 | if (type === 1 || type === 9 || type === 11) { 21 | if (typeof(e.textContent) === "string") { 22 | return e.textContent; 23 | } else { 24 | for ( e = e.firstChild; e; e = e.nextSibling ) { 25 | result += _getText(e); 26 | } 27 | } 28 | } else if (type === 3 || type === 4) { 29 | return e.nodeValue; 30 | } 31 | return result; 32 | }, 33 | _emojiStart = 0xD800, 34 | _emojiEnd = 0xDBFF, 35 | _emojiLowStart = 0xDC00, 36 | _emojiRegionStart = 0x1F1E6, 37 | _emojiRegionEnd = 0x1F1FF, 38 | _emojiModStart = 0x1f3fb, 39 | _emojiModEnd = 0x1f3ff, 40 | _emojiPairCode = function(s) { 41 | return ((s.charCodeAt(0) - _emojiStart) << 10) + (s.charCodeAt(1) - _emojiLowStart) + 0x10000; 42 | }, 43 | _emojiSafeSplit = function(text, delimiter) { //like calling String.split(delimiter) except that it keeps emoji characters together. 44 | if (delimiter !== "") { 45 | return text.split(delimiter); 46 | } 47 | var l = text.length, 48 | a = [], 49 | character, i, emojiPair1, emojiPair2, j; 50 | for (i = 0; i < l; i++) { 51 | character = text.charAt(i); 52 | if ((character.charCodeAt(0) >= _emojiStart && character.charCodeAt(0) <= _emojiEnd) || (text.charCodeAt(i+1) >= 0xFE00 && text.charCodeAt(i+1) <= 0xFE0F)) { //special emoji characters use 2 or 4 unicode characters that we must keep together. 53 | emojiPair1 = _emojiPairCode(text.substr(i, 2)); 54 | emojiPair2 = _emojiPairCode(text.substr(i + 2, 2)); 55 | j = ((emojiPair1 >= _emojiRegionStart && emojiPair1 <= _emojiRegionEnd && emojiPair2 >= _emojiRegionStart && emojiPair2 <= _emojiRegionEnd) || (emojiPair2 >= _emojiModStart && emojiPair2 <= _emojiModEnd)) ? 4 : 2; 56 | a.push(text.substr(i, j)); 57 | i += j - 1; 58 | } else { 59 | a.push(character); 60 | } 61 | } 62 | return a; 63 | }, 64 | TextPlugin = _gsScope._gsDefine.plugin({ 65 | propName: "text", 66 | API: 2, 67 | version:"0.6.1", 68 | 69 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 70 | init: function(target, value, tween, index) { 71 | var i = target.nodeName.toUpperCase(), 72 | shrt; 73 | if (typeof(value) === "function") { 74 | value = value(index, target); 75 | } 76 | this._svg = (target.getBBox && (i === "TEXT" || i === "TSPAN")); 77 | if (!("innerHTML" in target) && !this._svg) { 78 | return false; 79 | } 80 | this._target = target; 81 | if (typeof(value) !== "object") { 82 | value = {value:value}; 83 | } 84 | if (value.value === undefined) { 85 | this._text = this._original = [""]; 86 | return true; 87 | } 88 | this._delimiter = value.delimiter || ""; 89 | this._original = _emojiSafeSplit(_getText(target).replace(/\s+/g, " "), this._delimiter); 90 | this._text = _emojiSafeSplit(value.value.replace(/\s+/g, " "), this._delimiter); 91 | this._runBackwards = (tween.vars.runBackwards === true); 92 | if (this._runBackwards) { 93 | i = this._original; 94 | this._original = this._text; 95 | this._text = i; 96 | } 97 | if (typeof(value.newClass) === "string") { 98 | this._newClass = value.newClass; 99 | this._hasClass = true; 100 | } 101 | if (typeof(value.oldClass) === "string") { 102 | this._oldClass = value.oldClass; 103 | this._hasClass = true; 104 | } 105 | i = this._original.length - this._text.length; 106 | shrt = (i < 0) ? this._original : this._text; 107 | this._fillChar = value.fillChar || (value.padSpace ? " " : ""); 108 | if (i < 0) { 109 | i = -i; 110 | } 111 | while (--i > -1) { 112 | shrt.push(this._fillChar); 113 | } 114 | return true; 115 | }, 116 | 117 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 118 | set: function(ratio) { 119 | if (ratio > 1) { 120 | ratio = 1; 121 | } else if (ratio < 0) { 122 | ratio = 0; 123 | } 124 | if (this._runBackwards) { 125 | ratio = 1 - ratio; 126 | } 127 | var l = this._text.length, 128 | i = (ratio * l + 0.5) | 0, 129 | applyNew, applyOld, str; 130 | if (this._hasClass) { 131 | applyNew = (this._newClass && i !== 0); 132 | applyOld = (this._oldClass && i !== l); 133 | str = (applyNew ? "" : "") + this._text.slice(0, i).join(this._delimiter) + (applyNew ? "" : "") + (applyOld ? "" : "") + this._delimiter + this._original.slice(i).join(this._delimiter) + (applyOld ? "" : ""); 134 | } else { 135 | str = this._text.slice(0, i).join(this._delimiter) + this._delimiter + this._original.slice(i).join(this._delimiter); 136 | } 137 | if (this._svg) { //SVG text elements don't have an "innerHTML" in Microsoft browsers. 138 | this._target.textContent = str; 139 | } else { 140 | this._target.innerHTML = (this._fillChar === " " && str.indexOf(" ") !== -1) ? str.split(" ").join("  ") : str; 141 | } 142 | } 143 | 144 | }), 145 | p = TextPlugin.prototype; 146 | 147 | p._newClass = p._oldClass = p._delimiter = ""; 148 | 149 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 150 | 151 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 152 | (function(name) { 153 | "use strict"; 154 | var getGlobal = function() { 155 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 156 | }; 157 | if (typeof(module) !== "undefined" && module.exports) { //node 158 | require("../TweenLite.js"); 159 | module.exports = getGlobal(); 160 | } else if (typeof(define) === "function" && define.amd) { //AMD 161 | define(["TweenLite"], getGlobal); 162 | } 163 | }("TextPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ModifiersPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.0.3 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _cssRatioSetter = function(pt, cssp, mod) { //Takes an individual CSSPropTween and converts it into a type:2 that has a setRatio that does everything the regular CSSPlugin.setRatio() method does but applying the mod() too. We do this to keep the main CSSPlugin.setRatio() as fast as possible (the vast majority of times, no mod() will be necessary) 18 | var type = pt.type, 19 | oldSetRatio = pt.setRatio, 20 | tween = cssp._tween, 21 | target = cssp._target; 22 | pt.type = 2; 23 | pt.m = mod; 24 | pt.setRatio = function(v) { 25 | var min = 0.000001, 26 | val, str, i; 27 | if (v === 1 && (tween._time === tween._duration || tween._time === 0)) { 28 | 29 | if (type !== 2) { 30 | if (pt.r && type !== -1) { 31 | val = Math.round(pt.s + pt.c); 32 | if (!type) { 33 | pt.t[pt.p] = mod(val + pt.xs0, target); 34 | } else if (type === 1) { 35 | str = pt.xs0 + val + pt.xs1; 36 | for (i = 1; i < pt.l; i++) { 37 | str += pt["xn"+i] + pt["xs"+(i+1)]; 38 | } 39 | pt.t[pt.p] = mod(str, target); 40 | } 41 | } else { 42 | pt.t[pt.p] = mod(pt.e, target); 43 | } 44 | } else { 45 | oldSetRatio.call(pt, v); 46 | } 47 | 48 | } else if (v || !(tween._time === tween._duration || tween._time === 0) || tween._rawPrevTime === -0.000001) { 49 | val = pt.c * v + pt.s; 50 | if (pt.r) { 51 | val = Math.round(val); 52 | } else if (val < min) if (val > -min) { 53 | val = 0; 54 | } 55 | if (!type) { 56 | pt.t[pt.p] = mod(val + pt.xs0, target); 57 | } else if (type === 1) { 58 | str = pt.xs0 + val + pt.xs1; 59 | for (i = 1; i < pt.l; i++) { 60 | str += pt["xn"+i] + pt["xs"+(i+1)]; 61 | } 62 | pt.t[pt.p] = mod(str, target); 63 | 64 | } else if (type === -1) { //non-tweening value 65 | pt.t[pt.p] = mod(pt.xs0, target); 66 | 67 | } else if (oldSetRatio) { 68 | oldSetRatio.call(pt, v); 69 | } 70 | 71 | } else { 72 | if (type !== 2) { 73 | pt.t[pt.p] = mod(pt.b, target); 74 | } else { 75 | oldSetRatio.call(pt, v); 76 | } 77 | } 78 | }; 79 | }, 80 | _modCSS = function(lookup, cssp) { 81 | var pt = cssp._firstPT, 82 | hasBezier = (lookup.rotation && cssp._overwriteProps.join("").indexOf("bezier") !== -1); //when a Bezier tween is applying autoRotation, it's a very special case we need to handle differently. 83 | while (pt) { 84 | if (typeof(lookup[pt.p]) === "function") { 85 | _cssRatioSetter(pt, cssp, lookup[pt.p]); 86 | } else if (hasBezier && pt.n === "bezier" && pt.plugin._overwriteProps.join("").indexOf("rotation") !== -1) { 87 | pt.data.mod = lookup.rotation; 88 | } 89 | pt = pt._next; 90 | } 91 | }, 92 | 93 | ModifiersPlugin = _gsScope._gsDefine.plugin({ 94 | propName: "modifiers", 95 | version: "0.0.3", 96 | API: 2, 97 | 98 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 99 | init: function(target, value, tween) { 100 | this._tween = tween; 101 | this._vars = value; 102 | return true; 103 | }, 104 | 105 | initAll: function() { 106 | var tween = this._tween, 107 | lookup = this._vars, 108 | mpt = this, 109 | pt = tween._firstPT, 110 | val, next; 111 | while (pt) { 112 | next = pt._next; //record here, because it may get removed 113 | val = lookup[pt.n]; 114 | if (pt.pg) { 115 | if (pt.t._propName === "css") { //handle CSSPlugin uniquely (for performance, due to the fact that the values almost always are a concatenation of numbers and strings, like suffixes, and we don't want to slow down the regular CSSPlugin setRatio() performance with conditional checks for if the value needs to be modded, so we pull any modding prop out and change it to a type:2 one that simply calls a setRatio() method where we encapsulate the modding and update all together. That way, it says in the main CSSProp linked list and just has some custom logic applied to it inside its setRatio()) 116 | _modCSS(lookup, pt.t); 117 | } else if (pt.t !== mpt) { //don't run modProps on modProps :) 118 | val = lookup[pt.t._propName]; 119 | pt.t._mod((typeof(val) === "object") ? val : lookup); 120 | } 121 | } else if (typeof(val) === "function") { 122 | if (pt.f === 2 && pt.t) { //a blob (text containing multiple numeric values) 123 | pt.t._applyPT.m = val; 124 | } else { 125 | this._add(pt.t, pt.p, pt.s, pt.c, val); 126 | //remove from linked list 127 | if (next) { 128 | next._prev = pt._prev; 129 | } 130 | if (pt._prev) { 131 | pt._prev._next = next; 132 | } else if (tween._firstPT === pt) { 133 | tween._firstPT = next; 134 | } 135 | pt._next = pt._prev = null; 136 | tween._propLookup[pt.n] = mpt; 137 | } 138 | } 139 | pt = next; 140 | } 141 | return false; 142 | } 143 | 144 | }), 145 | p = ModifiersPlugin.prototype; 146 | 147 | p._add = function(target, p, s, c, mod) { 148 | this._addTween(target, p, s, s + c, p, mod); 149 | this._overwriteProps.push(p); 150 | }; 151 | 152 | p = _gsScope._gsDefine.globals.TweenLite.version.split("."); 153 | if (Number(p[0]) <= 1 && Number(p[1]) < 19 && _gsScope.console) { 154 | console.log("ModifiersPlugin requires GSAP 1.19.0 or later."); 155 | } 156 | 157 | 158 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 159 | 160 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 161 | (function(name) { 162 | "use strict"; 163 | var getGlobal = function() { 164 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 165 | }; 166 | if (typeof(module) !== "undefined" && module.exports) { //node 167 | require("../TweenLite.js"); 168 | module.exports = getGlobal(); 169 | } else if (typeof(define) === "function" && define.amd) { //AMD 170 | define(["TweenLite"], getGlobal); 171 | } 172 | }("ModifiersPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/TEMPLATE_Plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.2.0 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * This file is to be used as a simple template for writing your own plugin. See the 7 | * TweenPlugin docs for more details. 8 | * 9 | * You can start by doing a search for "yourCustomProperty" and replace it with whatever the name 10 | * of your property is. This way of defining a plugin was introduced in version 1.9.0 - previous versions 11 | * of TweenLite won't work with this. 12 | * 13 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 14 | * This work is subject to the terms at http://greensock.com/standard-license or for 15 | * Club GreenSock members, the software agreement that was issued with your membership. 16 | * 17 | * @author: Jack Doyle, jack@greensock.com 18 | **/ 19 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 20 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 21 | //ignore the line above this and at the very end - those are for ensuring things load in the proper order 22 | "use strict"; 23 | 24 | _gsScope._gsDefine.plugin({ 25 | propName: "yourCustomProperty", //the name of the property that will get intercepted and handled by this plugin (obviously change it to whatever you want, typically it is camelCase starting with lowercase). 26 | priority: 0, //the priority in the rendering pipeline (0 by default). A priority of -1 would mean this plugin will run after all those with 0 or greater. A priority of 1 would get run before 0, etc. This only matters when a plugin relies on other plugins finishing their work before it runs (or visa-versa) 27 | API: 2, //the API should stay 2 - it just gives us a way to know the method/property structure so that if in the future we change to a different TweenPlugin architecture, we can identify this plugin's structure. 28 | version: "1.0.0", //your plugin's version number 29 | overwriteProps: ["yourCustomProperty"], //an array of property names whose tweens should be overwritten by this plugin. For example, if you create a "scale" plugin that handles both "scaleX" and "scaleY", the overwriteProps would be ["scaleX","scaleY"] so that if there's a scaleX or scaleY tween in-progress when a new "scale" tween starts (using this plugin), it would overwrite the scaleX or scaleY tween. 30 | 31 | /* 32 | * The init function is called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. It receives 3 parameters: 33 | * 1) target [object] - the target of the tween. In cases where the tween's original target is an array (or jQuery object), this target will be the individual object inside that array (a new plugin instance is created for each target in the array). For example, TweenLite.to([obj1, obj2, obj3], 1, {x:100}) the target will be obj1 or obj2 or obj3 rather than the array containing them. 34 | * 2) value [*] - whatever value is passed as the special property value. For example, TweenLite.to(element, 1, {yourCustomProperty:3}) the value would be 3. Or for TweenLite.to(element, 1, {yourCustomProperty:{subProp1:3, subProp2:"whatever"}});, value would be {subProp1:3, subProp2:"whatever"}. 35 | * 3) tween [TweenLite] - the TweenLite (or TweenMax) instance that is managing this plugin instance. This can be useful if you need to check certain state-related properties on the tween (maybe in the set method) like its duration or time. Most of the time, however, you don't need to do anything with the tween. It is provided just in case you want to reference it. 36 | * 4) index [integer] - the index number of the target in the tween. For example, if an array is passed in as the target (or selector text), this would be 0 for the first one, 1 for the second, 2 for the third, etc. This was introduced in GSAP 1.19.0 37 | * 38 | * This function should return true unless you want to have TweenLite/Max skip the plugin altogether and instead treat the property/value like a normal tween (as if the plugin wasn't activated). This is rarely useful, so you should almost always return true. 39 | */ 40 | init: function(target, value, tween, index) { 41 | this._target = target; //we record the target so that we can refer to it in the set method when doing updates. 42 | 43 | /* Next, we create a property tween for "scaleX" and "scaleY" properties of our target 44 | * (we're just using them as a examples of how to set up a property tween with a name, start, and end value). 45 | * the _addTween() method accepts the following parameters: 46 | * 1) target [object] - target object whose property this tween will control. 47 | * 2) property [string] - the name of the property, like "scaleX" or "scaleY" 48 | * 3) start [number] - The starting value of the property. For example, if you're tweening from 0 to 100, start would be 0. 49 | * 4) end [number] - the ending value of the property. For example, if you're tweening from 0 to 100, end would be 100. 50 | * 5) overwriteProperty [string] - the name that gets registered as the overwrite property so that if another concurrent tween of the same target gets created and it is tweening a property with this name, this one will be overwritten. Typically this is the same as "property". 51 | * 6) round [boolean] - if true, the updated value on each update will be rounded to the nearest integer. [false by default] 52 | * You do NOT need to use _addTween() at all. It is merely a convenience. You can record your own values internally or whatever you want. 53 | */ 54 | this._addTween(target, "scaleX", target.scaleX, value, "scaleX", false); 55 | this._addTween(target, "scaleY", target.scaleY, value, "scaleY", false); 56 | 57 | //now, just for kicks, we'll record the starting "alpha" value and amount of change so that we can manage this manually rather than _addTween() (again, totally fictitious, just for an example) 58 | this._alphaStart = target.alpha; 59 | this._alphaChange = value.alpha - target.alpha; 60 | 61 | //always return true unless we want to scrap the plugin and have the value treated as a normal property tween (very uncommon) 62 | return true; 63 | }, 64 | 65 | //[optional] - called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.). If you're using this._super._addTween() for all your tweens and you don't need to do anything special on each frame besides updating those values, you can omit this "set" function altogether. 66 | set: function(ratio) { 67 | //since we used _addTween() inside init function, it created some property tweens that we'll update by calling the parent prototype's setRatio() (otherwise, the property tweens wouldn't get their values updated). this._super refers to the TweenPlugin prototype from which the plugin inherits (not that you need to worry about that). 68 | this._super.setRatio.call(this, ratio); 69 | 70 | //now manually set the alpha 71 | this._target.alpha = this._alphaStart + this._alphaChange * ratio; 72 | } 73 | 74 | }); 75 | 76 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ScrollToPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.9.0 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _doc = (_gsScope.document || {}).documentElement, 18 | _window = _gsScope, 19 | _max = function(element, axis) { 20 | var dim = (axis === "x") ? "Width" : "Height", 21 | scroll = "scroll" + dim, 22 | client = "client" + dim, 23 | body = document.body; 24 | return (element === _window || element === _doc || element === body) ? Math.max(_doc[scroll], body[scroll]) - (_window["inner" + dim] || _doc[client] || body[client]) : element[scroll] - element["offset" + dim]; 25 | }, 26 | _unwrapElement = function(value) { 27 | if (typeof(value) === "string") { 28 | value = TweenLite.selector(value); 29 | } 30 | if (value.length && value !== _window && value[0] && value[0].style && !value.nodeType) { 31 | value = value[0]; 32 | } 33 | return (value === _window || (value.nodeType && value.style)) ? value : null; 34 | }, 35 | _buildGetter = function(e, axis) { //pass in an element and an axis ("x" or "y") and it'll return a getter function for the scroll position of that element (like scrollTop or scrollLeft, although if the element is the window, it'll use the pageXOffset/pageYOffset or the documentElement's scrollTop/scrollLeft or document.body's. Basically this streamlines things and makes a very fast getter across browsers. 36 | var p = "scroll" + ((axis === "x") ? "Left" : "Top"); 37 | if (e === _window) { 38 | if (e.pageXOffset != null) { 39 | p = "page" + axis.toUpperCase() + "Offset"; 40 | } else if (_doc[p] != null) { 41 | e = _doc; 42 | } else { 43 | e = document.body; 44 | } 45 | } 46 | return function() { 47 | return e[p]; 48 | }; 49 | }, 50 | _getOffset = function(element, container) { 51 | var rect = _unwrapElement(element).getBoundingClientRect(), 52 | isRoot = (!container || container === _window || container === document.body), 53 | cRect = (isRoot ? _doc : container).getBoundingClientRect(), 54 | offsets = {x: rect.left - cRect.left, y: rect.top - cRect.top}; 55 | if (!isRoot && container) { //only add the current scroll position if it's not the window/body. 56 | offsets.x += _buildGetter(container, "x")(); 57 | offsets.y += _buildGetter(container, "y")(); 58 | } 59 | return offsets; 60 | }, 61 | _parseVal = function(value, target, axis) { 62 | var type = typeof(value); 63 | return !isNaN(value) ? parseFloat(value) : (type === "number" || (type === "string" && value.charAt(1) === "=")) ? value : (value === "max") ? _max(target, axis) : Math.min(_max(target, axis), _getOffset(value, target)[axis]); 64 | }, 65 | 66 | ScrollToPlugin = _gsScope._gsDefine.plugin({ 67 | propName: "scrollTo", 68 | API: 2, 69 | global: true, 70 | version:"1.9.0", 71 | 72 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 73 | init: function(target, value, tween) { 74 | this._wdw = (target === _window); 75 | this._target = target; 76 | this._tween = tween; 77 | if (typeof(value) !== "object") { 78 | value = {y:value}; //if we don't receive an object as the parameter, assume the user intends "y". 79 | if (typeof(value.y) === "string" && value.y !== "max" && value.y.charAt(1) !== "=") { 80 | value.x = value.y; 81 | } 82 | } else if (value.nodeType) { 83 | value = {y:value, x:value}; 84 | } 85 | this.vars = value; 86 | this._autoKill = (value.autoKill !== false); 87 | this.getX = _buildGetter(target, "x"); 88 | this.getY = _buildGetter(target, "y"); 89 | this.x = this.xPrev = this.getX(); 90 | this.y = this.yPrev = this.getY(); 91 | if (value.x != null) { 92 | this._addTween(this, "x", this.x, _parseVal(value.x, target, "x") - (value.offsetX || 0), "scrollTo_x", true); 93 | this._overwriteProps.push("scrollTo_x"); 94 | } else { 95 | this.skipX = true; 96 | } 97 | if (value.y != null) { 98 | this._addTween(this, "y", this.y, _parseVal(value.y, target, "y") - (value.offsetY || 0), "scrollTo_y", true); 99 | this._overwriteProps.push("scrollTo_y"); 100 | } else { 101 | this.skipY = true; 102 | } 103 | return true; 104 | }, 105 | 106 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 107 | set: function(v) { 108 | this._super.setRatio.call(this, v); 109 | 110 | var x = (this._wdw || !this.skipX) ? this.getX() : this.xPrev, 111 | y = (this._wdw || !this.skipY) ? this.getY() : this.yPrev, 112 | yDif = y - this.yPrev, 113 | xDif = x - this.xPrev, 114 | threshold = ScrollToPlugin.autoKillThreshold; 115 | 116 | if (this.x < 0) { //can't scroll to a position less than 0! Might happen if someone uses a Back.easeOut or Elastic.easeOut when scrolling back to the top of the page (for example) 117 | this.x = 0; 118 | } 119 | if (this.y < 0) { 120 | this.y = 0; 121 | } 122 | if (this._autoKill) { 123 | //note: iOS has a bug that throws off the scroll by several pixels, so we need to check if it's within 7 pixels of the previous one that we set instead of just looking for an exact match. 124 | if (!this.skipX && (xDif > threshold || xDif < -threshold) && x < _max(this._target, "x")) { 125 | this.skipX = true; //if the user scrolls separately, we should stop tweening! 126 | } 127 | if (!this.skipY && (yDif > threshold || yDif < -threshold) && y < _max(this._target, "y")) { 128 | this.skipY = true; //if the user scrolls separately, we should stop tweening! 129 | } 130 | if (this.skipX && this.skipY) { 131 | this._tween.kill(); 132 | if (this.vars.onAutoKill) { 133 | this.vars.onAutoKill.apply(this.vars.onAutoKillScope || this._tween, this.vars.onAutoKillParams || []); 134 | } 135 | } 136 | } 137 | if (this._wdw) { 138 | _window.scrollTo((!this.skipX) ? this.x : x, (!this.skipY) ? this.y : y); 139 | } else { 140 | if (!this.skipY) { 141 | this._target.scrollTop = this.y; 142 | } 143 | if (!this.skipX) { 144 | this._target.scrollLeft = this.x; 145 | } 146 | } 147 | this.xPrev = this.x; 148 | this.yPrev = this.y; 149 | } 150 | 151 | }), 152 | p = ScrollToPlugin.prototype; 153 | 154 | ScrollToPlugin.max = _max; 155 | ScrollToPlugin.getOffset = _getOffset; 156 | ScrollToPlugin.buildGetter = _buildGetter; 157 | ScrollToPlugin.autoKillThreshold = 7; 158 | 159 | p._kill = function(lookup) { 160 | if (lookup.scrollTo_x) { 161 | this.skipX = true; 162 | } 163 | if (lookup.scrollTo_y) { 164 | this.skipY = true; 165 | } 166 | return this._super._kill.call(this, lookup); 167 | }; 168 | 169 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 170 | 171 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 172 | (function(name) { 173 | "use strict"; 174 | var getGlobal = function() { 175 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 176 | }; 177 | if (typeof(module) !== "undefined" && module.exports) { //node 178 | require("../TweenLite.js"); 179 | module.exports = getGlobal(); 180 | } else if (typeof(define) === "function" && define.amd) { //AMD 181 | define(["TweenLite"], getGlobal); 182 | } 183 | }("ScrollToPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/ColorPropsPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: beta 1.5.2 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _numExp = /(\d|\.)+/g, 18 | _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g, 19 | _colorLookup = {aqua:[0,255,255], 20 | lime:[0,255,0], 21 | silver:[192,192,192], 22 | black:[0,0,0], 23 | maroon:[128,0,0], 24 | teal:[0,128,128], 25 | blue:[0,0,255], 26 | navy:[0,0,128], 27 | white:[255,255,255], 28 | fuchsia:[255,0,255], 29 | olive:[128,128,0], 30 | yellow:[255,255,0], 31 | orange:[255,165,0], 32 | gray:[128,128,128], 33 | purple:[128,0,128], 34 | green:[0,128,0], 35 | red:[255,0,0], 36 | pink:[255,192,203], 37 | cyan:[0,255,255], 38 | transparent:[255,255,255,0]}, 39 | _hue = function(h, m1, m2) { 40 | h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h; 41 | return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0; 42 | }, 43 | /** 44 | * @private Parses a color (like #9F0, #FF9900, rgb(255,51,153) or hsl(108, 50%, 10%)) into an array with 3 elements for red, green, and blue or if toHSL parameter is true, it will populate the array with hue, saturation, and lightness values. If a relative value is found in an hsl() or hsla() string, it will preserve those relative prefixes and all the values in the array will be strings instead of numbers (in all other cases it will be populated with numbers). 45 | * @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc. 46 | * @param {(boolean)} toHSL If true, an hsl() or hsla() value will be returned instead of rgb() or rgba() 47 | * @return {Array.} An array containing red, green, and blue (and optionally alpha) in that order, or if the toHSL parameter was true, the array will contain hue, saturation and lightness (and optionally alpha) in that order. Always numbers unless there's a relative prefix found in an hsl() or hsla() string and toHSL is true. 48 | */ 49 | _parseColor = function(v, toHSL) { 50 | var a, r, g, b, h, s, l, max, min, d, wasHSL; 51 | if (!v) { 52 | a = _colorLookup.black; 53 | } else if (typeof(v) === "number") { 54 | a = [v >> 16, (v >> 8) & 255, v & 255]; 55 | } else { 56 | if (v.charAt(v.length - 1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value. 57 | v = v.substr(0, v.length - 1); 58 | } 59 | if (_colorLookup[v]) { 60 | a = _colorLookup[v]; 61 | } else if (v.charAt(0) === "#") { 62 | if (v.length === 4) { //for shorthand like #9F0 63 | r = v.charAt(1); 64 | g = v.charAt(2); 65 | b = v.charAt(3); 66 | v = "#" + r + r + g + g + b + b; 67 | } 68 | v = parseInt(v.substr(1), 16); 69 | a = [v >> 16, (v >> 8) & 255, v & 255]; 70 | } else if (v.substr(0, 3) === "hsl") { 71 | a = wasHSL = v.match(_numExp); 72 | if (!toHSL) { 73 | h = (Number(a[0]) % 360) / 360; 74 | s = Number(a[1]) / 100; 75 | l = Number(a[2]) / 100; 76 | g = (l <= 0.5) ? l * (s + 1) : l + s - l * s; 77 | r = l * 2 - g; 78 | if (a.length > 3) { 79 | a[3] = Number(v[3]); 80 | } 81 | a[0] = _hue(h + 1 / 3, r, g); 82 | a[1] = _hue(h, r, g); 83 | a[2] = _hue(h - 1 / 3, r, g); 84 | } else if (v.indexOf("=") !== -1) { //if relative values are found, just return the raw strings with the relative prefixes in place. 85 | return v.match(_relNumExp); 86 | } 87 | } else { 88 | a = v.match(_numExp) || _colorLookup.transparent; 89 | } 90 | a[0] = Number(a[0]); 91 | a[1] = Number(a[1]); 92 | a[2] = Number(a[2]); 93 | if (a.length > 3) { 94 | a[3] = Number(a[3]); 95 | } 96 | } 97 | if (toHSL && !wasHSL) { 98 | r = a[0] / 255; 99 | g = a[1] / 255; 100 | b = a[2] / 255; 101 | max = Math.max(r, g, b); 102 | min = Math.min(r, g, b); 103 | l = (max + min) / 2; 104 | if (max === min) { 105 | h = s = 0; 106 | } else { 107 | d = max - min; 108 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 109 | h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4; 110 | h *= 60; 111 | } 112 | a[0] = (h + 0.5) | 0; 113 | a[1] = (s * 100 + 0.5) | 0; 114 | a[2] = (l * 100 + 0.5) | 0; 115 | } 116 | return a; 117 | }, 118 | _formatColors = function(s, toHSL) { 119 | var colors = (s + "").match(_colorExp) || [], 120 | charIndex = 0, 121 | parsed = "", 122 | i, color, temp; 123 | if (!colors.length) { 124 | return s; 125 | } 126 | for (i = 0; i < colors.length; i++) { 127 | color = colors[i]; 128 | temp = s.substr(charIndex, s.indexOf(color, charIndex)-charIndex); 129 | charIndex += temp.length + color.length; 130 | color = _parseColor(color, toHSL); 131 | if (color.length === 3) { 132 | color.push(1); 133 | } 134 | parsed += temp + (toHSL ? "hsla(" + color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : "rgba(" + color.join(",")) + ")"; 135 | } 136 | return parsed + s.substr(charIndex); 137 | }, p, _colorStringFilter, 138 | TweenLite = (_gsScope.GreenSockGlobals || _gsScope).TweenLite, 139 | _colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b", //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc. 140 | 141 | ColorPropsPlugin = _gsScope._gsDefine.plugin({ 142 | propName: "colorProps", 143 | version: "1.5.2", 144 | priority: -1, 145 | API: 2, 146 | global: true, 147 | 148 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 149 | init: function(target, value, tween, index) { 150 | var p, proxy, pt, val; 151 | this._target = target; 152 | this._proxy = proxy = ((value.format + "").toUpperCase() === "NUMBER") ? {} : 0; 153 | for (p in value) { 154 | if (p !== "format") { 155 | if (proxy) { 156 | this._firstNumPT = pt = {_next:this._firstNumPT, t:target, p:p, f:(typeof(target[p]) === "function")}; 157 | proxy[p] = "rgb(" + _parseColor(!pt.f ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]()).join(",") + ")"; 158 | val = value[p]; 159 | if (typeof(val) === "function") { 160 | val = val(index, target); 161 | } 162 | this._addTween(proxy, p, "get", ((typeof(val) === "number") ? "rgb(" + _parseColor(val, false).join(",") + ")" : val), p, null, null, _colorStringFilter); 163 | } else { 164 | this._addTween(target, p, "get", value[p], p, null, null, _colorStringFilter, index); 165 | } 166 | 167 | } 168 | } 169 | return true; 170 | }, 171 | 172 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 173 | set: function(v) { 174 | var pt = this._firstNumPT, 175 | val; 176 | this._super.setRatio.call(this, v); 177 | while (pt) { 178 | val = _parseColor(this._proxy[pt.p], false); 179 | val = val[0] << 16 | val[1] << 8 | val[2]; 180 | if (pt.f) { 181 | this._target[pt.p](val); 182 | } else { 183 | this._target[pt.p] = val; 184 | } 185 | pt = pt._next; 186 | } 187 | } 188 | }); 189 | 190 | for (p in _colorLookup) { 191 | _colorExp += "|" + p + "\\b"; 192 | } 193 | _colorExp = new RegExp(_colorExp+")", "gi"); 194 | ColorPropsPlugin.colorStringFilter = _colorStringFilter = function(a) { 195 | var combined = a[0] + " " + a[1], 196 | toHSL; 197 | _colorExp.lastIndex = 0; 198 | if (_colorExp.test(combined)) { 199 | toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1); 200 | a[0] = _formatColors(a[0], toHSL); 201 | a[1] = _formatColors(a[1], toHSL); 202 | } 203 | }; 204 | 205 | if (!TweenLite.defaultStringFilter) { 206 | TweenLite.defaultStringFilter = ColorPropsPlugin.colorStringFilter; 207 | } 208 | 209 | ColorPropsPlugin.parseColor = _parseColor; 210 | p = ColorPropsPlugin.prototype; 211 | p._firstNumPT = null; 212 | p._kill = function(lookup) { 213 | var pt = this._firstNumPT, 214 | prev; 215 | while (pt) { 216 | if (pt.p in lookup) { 217 | if (pt === p._firstNumPT) { 218 | this._firstNumPT = pt._next; 219 | } 220 | if (prev) { 221 | prev._next = pt._next; 222 | } 223 | } else { 224 | prev = pt; 225 | } 226 | pt = pt._next; 227 | } 228 | return this._super._kill(lookup); 229 | }; 230 | 231 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 232 | 233 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 234 | (function(name) { 235 | "use strict"; 236 | var getGlobal = function() { 237 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 238 | }; 239 | if (typeof(module) !== "undefined" && module.exports) { //node 240 | require("../TweenLite.js"); 241 | module.exports = getGlobal(); 242 | } else if (typeof(define) === "function" && define.amd) { //AMD 243 | define(["TweenLite"], getGlobal); 244 | } 245 | }("ColorPropsPlugin")); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # H5小游戏100例:消灭星星 2 | 3 | 「消灭星星」是一款很经典的「消除类游戏」,它的玩法很简单:消除相连通的同色砖块。 4 | 5 | ![demo](http://7xv39r.com1.z0.glb.clouddn.com/popstar.gif) 6 | 7 | ## 1. 游戏规则 8 | 9 | 「消灭星星」存在多个版本,不过它们的规则除了「关卡分值」有些出入外,其它的规则都是一样的。笔者介绍的版本的游戏规则整理如下: 10 | 11 | **1. 色砖分布** 12 | 13 | - 10 x 10 的表格 14 | - 5种颜色 ------ 红、绿、蓝,黄,紫 15 | - 每类色砖个数在指定区间内随机 16 | - 5类色砖在 10 x 10 表格中随机分布 17 | 18 | **2. 消除规则** 19 | 20 | 两个或两个以上同色砖块相连通即是可被消除的砖块。 21 | 22 | **3. 分值规则** 23 | 24 | - 消除总分值 = n * n * 5 25 | - 奖励总分值 = 2000 - n * n * 20 26 | 27 | 「n」表示砖块数量。上面是「总」分值的规则,还有「单」个砖块的分值规则: 28 | 29 | - 消除砖块得分值 = 10 * i + 5 30 | - 剩余砖块扣分值 = 40 * i + 20 31 | 32 | 「i」表示砖块的索引值(从 0 开始)。简单地说,单个砖块「得分值」和「扣分值」是一个等差数列。 33 | 34 | **4. 关卡分值** 35 | 36 | 关卡分值 = 1000 + (level - 1) * 2000;「level」即当前关卡数。 37 | 38 | **5. 通关条件** 39 | 40 | - 可消除色块不存在 41 | - 累计分值 >= 当前关卡分值 42 | 43 | 上面两个条件同时成立游戏才可以通关。 44 | 45 | ## 2. MVC 设计模式 46 | 47 | 笔者这次又是使用了 MVC 模式来写「消灭星星」。星星「砖块」的数据结构与各种状态由 Model 实现,游戏的核心在 Model 中完成;View 映射 Model 的变化并做出对应的行为,它的任务主要是展示动画;用户与游戏的交互由 Control 完成。 48 | 49 | 从逻辑规划上看,Model 很重而View 与 Control 很轻,不过,从代码量上看,View 很重而 Model 与 Control 相对很轻。 50 | 51 | 52 | ## 3. Model 53 | 54 | 10 x 10 的表格用长度为 100 的数组可完美映射游戏的星星「砖块」。 55 | 56 | ```javascript 57 | [ 58 | R, R, G, G, B, B, Y, Y, P, P, 59 | R, R, G, G, B, B, Y, Y, P, P, 60 | R, R, G, G, B, B, Y, Y, P, P, 61 | R, R, G, G, B, B, Y, Y, P, P, 62 | R, R, G, G, B, B, Y, Y, P, P, 63 | R, R, G, G, B, B, Y, Y, P, P, 64 | R, R, G, G, B, B, Y, Y, P, P, 65 | R, R, G, G, B, B, Y, Y, P, P, 66 | R, R, G, G, B, B, Y, Y, P, P, 67 | R, R, G, G, B, B, Y, Y, P, P 68 | ] 69 | ``` 70 | R - 红色,G - 绿色,B - 蓝色,Y - 黄色,P - 紫色。Model 的核心任务是以下四个: 71 | 72 | - 生成砖墙 73 | - 消除砖块 (生成砖块分值) 74 | - 夯实砖墙 75 | - 清除残砖 (生成奖励分值) 76 | 77 | ### 3.1 生成砖墙 78 | 79 | 砖墙分两步生成: 80 | - 色砖数量分配 81 | - 打散色砖 82 | 83 | 理论上,可以将 100 个格子可以均分到 5 类颜色,不过笔者玩过的「消灭星星」都不使用均分策略。通过分析几款「消灭星星」,其实可以发现一个规律 ------ 「**色砖之间的数量差在一个固定的区间内**」。 84 | 85 | 如果把传统意义上的均分称作「完全均分」,那么「消灭星星」的分配是一种在均分线上下波动的「不完全均分」。 86 | 87 | ![正余弦波线图](http://7xv39r.com1.z0.glb.clouddn.com/2017-12-06-waveAverage.gif) 88 | 89 | 笔者把上面的「不完全均分」称作「波动均分」,算法的具体实现可以参见「[波动均分算法](https://aotu.io/notes/2018/01/11/waveaverage/)」。 90 | 91 | 「打散色砖」其实就是将数组乱序的过程,笔者推荐使用「 [费雪耶兹乱序算法](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)」。 92 | 93 | 以下是伪代码的实现: 94 | ```javascript 95 | // 波动均分色砖 96 | waveaverage(5, 4, 4).forEach( 97 | // tiles 即色墙数组 98 | (count, clr) => tiles.concat(generateTiles(count, clr)); 99 | ); 100 | // 打散色砖 101 | shuffle(tiles); 102 | ``` 103 | 104 | ### 3.2 消除砖块 105 | 106 | 「消除砖块」的规则很简单 ------ **相邻相连通相同色即可以消除**。 107 | 108 | ![连通图](http://7xv39r.com1.z0.glb.clouddn.com/20180111-connection.png) 109 | 前两个组合符合「相邻相连通相同色即可以消除」,所以它们可以被消除;第三个组合虽然「相邻相同色」但是不「相连通」所以它不能被消除。 110 | 111 | 「消除砖块」的同时有一个重要的任务:生成砖块对应的分值。在「游戏规则」中,笔者已经提供了对应的数学公式:「消除砖块得分值 = 10 * i + 5」。 112 | 113 | **「消除砖块」算法实现如下:** 114 | ```javascript 115 | function clean(tile) { 116 | let count = 1; 117 | let sameTiles = searchSameTiles(tile); 118 | if(sameTiles.length > 0) { 119 | deleteTile(tile); 120 | while(true) { 121 | let nextSameTiles = []; 122 | sameTiles.forEach(tile => { 123 | nextSameTiles.push(...searchSameTiles(tile)); 124 | makeScore(++count * 10 + 5); // 标记当前分值 125 | deleteTile(tile); // 删除砖块 126 | }); 127 | // 清除完成,跳出循环 128 | if(nextSameTiles.length === 0) break; 129 | else { 130 | sameTiles = nextSameTiles; 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 清除的算法使用「递归」逻辑上会清晰一些,不过「递归」在浏览器上容易「栈溢出」,所以笔者没有使用「递归」实现。 137 | 138 | ### 3.3 夯实砖墙 139 | 140 | 砖墙在消除了部分砖块后,会出现空洞,此时需要对墙体进行夯实: 141 | 142 | | ![向下夯实](http://7xv39r.com1.z0.glb.clouddn.com/20180112-down.gif) | ![向左夯实](http://7xv39r.com1.z0.glb.clouddn.com/20180112-left.gif) | ![先下再左夯实](http://7xv39r.com1.z0.glb.clouddn.com/20180112-down-left.gif) | 143 | | :----: | :----: | :----: | 144 | | **向下夯实** | **向左夯实** | **向左下夯实**(先下后左) | 145 | 146 | 一种快速的实现方案是,每次「消除砖块」后直接遍历砖墙数组(10x10数组)再把空洞夯实,伪代码表示如下: 147 | 148 | ```javascript 149 | for(let row = 0; row < 10; ++row) { 150 | for(let col = 0; col < 10; ++col) { 151 | if(isEmpty(row, col)) { 152 | // 水平方向(向左)夯实 153 | if(isEmptyCol(col)) { 154 | tampRow(col); 155 | } 156 | // 垂直方向(向下)夯实 157 | else { 158 | tampCol(col); 159 | } 160 | break; 161 | } 162 | } 163 | } 164 | ``` 165 | 166 | But... 为了夯实一个空洞对一张大数组进行全量遍历并不是一种高效的算法。在笔者看来影响「墙体夯实」效率的因素有: 167 | 168 | 1. 定位空洞 169 | 2. 砖块移动(夯实) 170 | 171 | 扫描墙体数组的主要目的是「定位空洞」,但能否不扫描墙体数组直接「定位空洞」? 172 | 173 | 墙体的「空洞」是由于「消除砖块」造成的,换种说法 ------ **被消除的砖块留下来的坑位就是墙体的空洞**。在「消除砖块」的同时标记空洞的位置,这样就无须全量扫描墙体数组,伪代码如下: 174 | 175 | ```javascript 176 | function deleteTile(tile) { 177 | // 标记空洞 178 | markHollow(tile.index); 179 | // 删除砖块逻辑 180 | ... 181 | } 182 | ``` 183 | 184 | 在上面的夯实动图,其实可以看到它的夯实过程如下: 185 | 186 | 1. 空洞上方的砖块向下移动 187 | 2. 空列右侧的砖块向左移动 188 | 189 | 墙体在「夯实」过程中,它的边界是实时在变化,如果「夯实」不按真实边界进行扫描,会产生多余的空白扫描: 190 | 191 | ![对比](http://7xv39r.com1.z0.glb.clouddn.com/20180116-clean.gif) 192 | 193 | **如何记录墙体的边界?** 194 | 把墙体拆分成一个个单独的列,那么列最顶部的空白格片段就是墙体的「空白」,而其余非顶部的空白格片段即墙体的「空洞」。 195 | 196 | ![列](http://7xv39r.com1.z0.glb.clouddn.com/20180117-col.gif) 197 | 198 | 笔者使用一组「列集合」来描述墙体的边界并记录墙体的空洞,它的模型如下: 199 | 200 | ```javascript 201 | /* 202 | @ count - 列砖块数 203 | @ start - 顶部行索引 204 | @ end - 底部行索引 205 | @ pitCount - 坑数 206 | @ topPit - 最顶部的坑 207 | @ bottomPit - 最底部的坑 208 | */ 209 | let wall = [ 210 | {count, start, end, pitCount, topPit, bottomPit}, 211 | {count, start, end, pitCount, topPit, bottomPit}, 212 | ... 213 | ]; 214 | ``` 215 | 这个模型可以描述墙体的三个细节: 216 | 217 | - 空列 218 | - 列的连续空洞 219 | - 列的非连续空洞 220 | 221 | ```javascript 222 | // 空列 223 | if(count === 0) { 224 | ... 225 | } 226 | // 连续空洞 227 | else if(bottomPit - topPit + 1 === pitCount) { 228 | ... 229 | } 230 | // 非连续空洞 231 | else { 232 | ... 233 | } 234 | ``` 235 | 236 | 砖块在消除后,映射到单个列上的空洞会有两种分布形态 ------ 连续与非连续。 237 | 238 | ![连续空格与非连续空格](http://7xv39r.com1.z0.glb.clouddn.com/20180117-col2.gif) 239 | 240 | 「连续空洞」与「非连续空洞」的夯实过程如下: 241 | 242 | ![空洞压缩](http://7xv39r.com1.z0.glb.clouddn.com/20180117-tamp.gif) 243 | 244 | 其实「空列」放大于墙体上,也会有「空洞」类似的分布形态 ------ 连续与非连续。 245 | ![空洞压缩](http://7xv39r.com1.z0.glb.clouddn.com/20180117-col3.gif) 246 | 247 | 它的夯实过程与空洞类似,这里就不赘述了。 248 | 249 | ### 4.4 消除残砖 250 | 251 | 上一小节提到了「描述墙体的边界并记录墙体的空洞」的「列集合」,笔者是直接使用这个「列集合」来消除残砖的,伪代码如下: 252 | 253 | ```javascript 254 | function clearAll() { 255 | let count = 0; 256 | for(let col = 0, len = this.wall.length; col < len; ++col) { 257 | let colInfo = this.wall[col]; 258 | for(let row = colInfo.start; row <= colInfo.end; ++row) { 259 | let tile = this.grid[row * this.col + col]; 260 | tile.score = -20 - 40 * count++; // 标记奖励分数 261 | tile.removed = true; 262 | } 263 | } 264 | } 265 | ``` 266 | 267 | ## 4. View 268 | 269 | View 主要的功能有两个: 270 | 271 | - UI 管理 272 | - 映射 Model 的变化(动画) 273 | 274 | UI 管理主要是指「界面绘制」与「资源加载管理」,这两项功能比较常见本文就直接略过了。View 的重头戏是「映射 Model 的变化」并完成对应的动画。动画是复杂的,而映射的原理是简单的,如下伪代码: 275 | 276 | ```javascript 277 | update({originIndex, index, clr, removed, score}) { 278 | // 还没有 originIndex 或没有色值,直接不处理 279 | if(originIndex === undefined || clr === undefined) return ; 280 | let tile = this.tiles[originIndex]; 281 | // tile 存在,判断颜色是否一样 282 | if(tile.clr !== clr) { 283 | this.updateTileClr(tile, clr); 284 | } 285 | // 当前索引变化 ----- 表示位置也有变化 286 | if(tile.index !== index) { 287 | this.updateTileIndex(tile, index); 288 | } 289 | // 设置分数 290 | if(tile.score !== score) { 291 | tile.score = score; 292 | } 293 | 294 | if(tile.removed !== removed) { 295 | // 移除或添加当前节点 296 | true === removed ? this.bomb(tile) : this.area.addChild(tile.sprite); 297 | tile.removed = removed; 298 | } 299 | } 300 | ``` 301 | 302 | Model 的砖块每次数据的更改都会通知到 View 的砖块,View 会根据对应的变化做对应的动作(动画)。 303 | 304 | ## 5. Control 305 | 306 | Control 要处理的事务比较多,如下: 307 | 308 | - 绑定 Model & View 309 | - 生成通关分值 310 | - 判断通关条件 311 | - 对外事件 312 | - 用户交互 313 | 314 | 315 | 初始化时,Control 把 Model 的砖块单向绑定到 View 的砖块了。如下: 316 | 317 | ```javascript 318 | Object.defineProperties(model.tile, 319 | { 320 | originIndex: { 321 | get: () => {...}, 322 | set: () => { 323 | ... 324 | view.update({originIndex}); 325 | } 326 | }, 327 | index: { 328 | get: () => {...}, 329 | set: () => { 330 | ... 331 | view.update({index}); 332 | } 333 | }, 334 | clr: { 335 | get: () => {...}, 336 | set: () => { 337 | ... 338 | view.update({clr}); 339 | } 340 | }, 341 | removed: { 342 | get: () => {...}, 343 | set: () => { 344 | ... 345 | view.update({removed}); 346 | } 347 | }, 348 | score: { 349 | get: () => {...}, 350 | set: () => { 351 | ... 352 | view.update({score}); 353 | } 354 | }, 355 | } 356 | ); 357 | ``` 358 | 359 | 「通关分值」与「判断通关条件」这对逻辑在本文的「游戏规则」中有相关介绍,这里不再赘述。 360 | 361 | 对外事件规划如下: 362 | 363 | | name | detail | 364 | | :-- | :-- | 365 | | pass | 通关 | 366 | | pause | 暂停 | 367 | | resume | 恢复 | 368 | | gameover | 游戏结束 | 369 | 370 | 用户交互 APIs 规划如下: 371 | 372 | | name | type | deltail | 373 | | :-- | :-- | :-- | 374 | | init | method | 初始化游戏 | 375 | | next | method | 进入下一关 | 376 | | enter | method | 进入指定关卡 | 377 | | pause | method | 暂停 | 378 | | resume | method | 恢复 | 379 | | destroy | method | 销毁游戏 | 380 | 381 | ## 6. 问题 382 | 383 | 在知乎有一个关于「消灭星星」的话题:[popstar关卡是如何设计的?](https://www.zhihu.com/question/22530789) 384 | 385 | 这个话题在最后提出了一个问题 ------ **「无法消除和最大得分不满足过关条件的矩阵」**。 386 | 387 | ![两个问题](https://pic1.zhimg.com/50/98d59c105bc5331d0596e20ec2e2e554_hd.jpg) 388 | 389 | 「无法消除的矩阵」其实就是最大得分为0的矩阵,本质上是「最大得分不满足过关条件的矩阵」。 390 | 391 | **最大得分不满足过关条件的矩阵** 392 | 求「矩阵」的最大得分是一个 「[背包问题](https://baike.baidu.com/item/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98)」,求解的算法不难:对当前矩阵用「递归」的形式把所有的消灭分支都执行一次,并取最高分值。但是 javascript 的「递归」极易「栈溢出」导致算法无法执行。 393 | 394 | 其实在知乎的话题中提到一个解决方案: 395 | 396 | > 网上查到有程序提出做个工具随机生成关卡,自动计算,把符合得分条件的关卡筛选出来 397 | 398 | 这个解决方案代价是昂贵的!笔者提供有源码并没有解决这个问题,而是用一个比较取巧的方法:**进入游戏前检查是事为「无法消除矩阵」,如果是重新生成关卡矩阵**。 399 | 400 | 注意:笔者使用的取巧方案并没有解决问题。 401 | 402 | 403 | ## 7. 结语 404 | 405 | 下面是本文介绍的「消灭星星」的线上 DEMO 的二维码: 406 | 407 | ![二维码](http://7xv39r.com1.z0.glb.clouddn.com/qr.jpg) 408 | 409 | 游戏的源码托管在:[https://github.com/leeenx/popstar](https://github.com/leeenx/popstar) 410 | 411 | 感谢耐心阅读完本文章的读者。本文仅代表笔者的个人观点,如有不妥之处请不吝赐教。 412 | 如果对「H5游戏开发」感兴趣,欢迎关注我们的[专栏](https://zhuanlan.zhihu.com/snsgame)。 413 | 414 | ## 参考资料 415 | 416 | - [Knapsack problem](https://en.wikipedia.org/wiki/Knapsack_problem) 417 | - [NP-completeness](https://en.wikipedia.org/wiki/NP-completeness) 418 | - [popstar关卡是如何设计的?](https://www.zhihu.com/question/22530789) 419 | - [费雪耶兹乱序算法](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) 420 | - [波动均分算法](https://aotu.io/notes/2018/01/11/waveaverage/) 421 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/EaselPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.2.2 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _numExp = /(\d|\.)+/g, 18 | _ColorFilter, _ColorMatrixFilter, 19 | _colorProps = ["redMultiplier","greenMultiplier","blueMultiplier","alphaMultiplier","redOffset","greenOffset","blueOffset","alphaOffset"], 20 | _colorLookup = {aqua:[0,255,255], 21 | lime:[0,255,0], 22 | silver:[192,192,192], 23 | black:[0,0,0], 24 | maroon:[128,0,0], 25 | teal:[0,128,128], 26 | blue:[0,0,255], 27 | navy:[0,0,128], 28 | white:[255,255,255], 29 | fuchsia:[255,0,255], 30 | olive:[128,128,0], 31 | yellow:[255,255,0], 32 | orange:[255,165,0], 33 | gray:[128,128,128], 34 | purple:[128,0,128], 35 | green:[0,128,0], 36 | red:[255,0,0], 37 | pink:[255,192,203], 38 | cyan:[0,255,255], 39 | transparent:[255,255,255,0]}, 40 | _parseColor = function(color) { 41 | if (color === "" || color == null || color === "none") { 42 | return _colorLookup.transparent; 43 | } else if (_colorLookup[color]) { 44 | return _colorLookup[color]; 45 | } else if (typeof(color) === "number") { 46 | return [color >> 16, (color >> 8) & 255, color & 255]; 47 | } else if (color.charAt(0) === "#") { 48 | if (color.length === 4) { //for shorthand like #9F0 49 | color = "#" + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2) + color.charAt(3) + color.charAt(3); 50 | } 51 | color = parseInt(color.substr(1), 16); 52 | return [color >> 16, (color >> 8) & 255, color & 255]; 53 | } 54 | return color.match(_numExp) || _colorLookup.transparent; 55 | }, 56 | _parseColorFilter = function(t, v, pg) { 57 | if (!_ColorFilter) { 58 | _ColorFilter = (_gsScope.ColorFilter || _gsScope.createjs.ColorFilter); 59 | if (!_ColorFilter) { 60 | throw("EaselPlugin error: The EaselJS ColorFilter JavaScript file wasn't loaded."); 61 | } 62 | } 63 | var filters = t.filters || [], 64 | i = filters.length, 65 | c, s, e, a, p; 66 | while (--i > -1) { 67 | if (filters[i] instanceof _ColorFilter) { 68 | s = filters[i]; 69 | break; 70 | } 71 | } 72 | if (!s) { 73 | s = new _ColorFilter(); 74 | filters.push(s); 75 | t.filters = filters; 76 | } 77 | e = s.clone(); 78 | if (v.tint != null) { 79 | c = _parseColor(v.tint); 80 | a = (v.tintAmount != null) ? Number(v.tintAmount) : 1; 81 | e.redOffset = Number(c[0]) * a; 82 | e.greenOffset = Number(c[1]) * a; 83 | e.blueOffset = Number(c[2]) * a; 84 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - a; 85 | } else { 86 | for (p in v) { 87 | if (p !== "exposure") if (p !== "brightness") { 88 | e[p] = Number(v[p]); 89 | } 90 | } 91 | } 92 | if (v.exposure != null) { 93 | e.redOffset = e.greenOffset = e.blueOffset = 255 * (Number(v.exposure) - 1); 94 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1; 95 | } else if (v.brightness != null) { 96 | a = Number(v.brightness) - 1; 97 | e.redOffset = e.greenOffset = e.blueOffset = (a > 0) ? a * 255 : 0; 98 | e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - Math.abs(a); 99 | } 100 | i = 8; 101 | while (--i > -1) { 102 | p = _colorProps[i]; 103 | if (s[p] !== e[p]) { 104 | pg._addTween(s, p, s[p], e[p], "easel_colorFilter"); 105 | } 106 | } 107 | pg._overwriteProps.push("easel_colorFilter"); 108 | if (!t.cacheID) { 109 | throw("EaselPlugin warning: for filters to display in EaselJS, you must call the object's cache() method first. " + t); 110 | } 111 | }, 112 | 113 | _idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0], 114 | _lumR = 0.212671, 115 | _lumG = 0.715160, 116 | _lumB = 0.072169, 117 | 118 | _applyMatrix = function(m, m2) { 119 | if (!(m instanceof Array) || !(m2 instanceof Array)) { 120 | return m2; 121 | } 122 | var temp = [], 123 | i = 0, 124 | z = 0, 125 | y, x; 126 | for (y = 0; y < 4; y++) { 127 | for (x = 0; x < 5; x++) { 128 | z = (x === 4) ? m[i + 4] : 0; 129 | temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z; 130 | } 131 | i += 5; 132 | } 133 | return temp; 134 | }, 135 | 136 | _setSaturation = function(m, n) { 137 | if (isNaN(n)) { 138 | return m; 139 | } 140 | var inv = 1 - n, 141 | r = inv * _lumR, 142 | g = inv * _lumG, 143 | b = inv * _lumB; 144 | return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m); 145 | }, 146 | 147 | _colorize = function(m, color, amount) { 148 | if (isNaN(amount)) { 149 | amount = 1; 150 | } 151 | var c = _parseColor(color), 152 | r = c[0] / 255, 153 | g = c[1] / 255, 154 | b = c[2] / 255, 155 | inv = 1 - amount; 156 | return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m); 157 | }, 158 | 159 | _setHue = function(m, n) { 160 | if (isNaN(n)) { 161 | return m; 162 | } 163 | n *= Math.PI / 180; 164 | var c = Math.cos(n), 165 | s = Math.sin(n); 166 | return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m); 167 | }, 168 | 169 | _setContrast = function(m, n) { 170 | if (isNaN(n)) { 171 | return m; 172 | } 173 | n += 0.01; 174 | return _applyMatrix([n,0,0,0,128 * (1 - n), 0,n,0,0,128 * (1 - n), 0,0,n,0,128 * (1 - n), 0,0,0,1,0], m); 175 | }, 176 | 177 | _parseColorMatrixFilter = function(t, v, pg) { 178 | if (!_ColorMatrixFilter) { 179 | _ColorMatrixFilter = (_gsScope.ColorMatrixFilter || _gsScope.createjs.ColorMatrixFilter); 180 | if (!_ColorMatrixFilter) { 181 | throw("EaselPlugin error: The EaselJS ColorMatrixFilter JavaScript file wasn't loaded."); 182 | } 183 | } 184 | var filters = t.filters || [], 185 | i = filters.length, 186 | matrix, startMatrix, s; 187 | while (--i > -1) { 188 | if (filters[i] instanceof _ColorMatrixFilter) { 189 | s = filters[i]; 190 | break; 191 | } 192 | } 193 | if (!s) { 194 | s = new _ColorMatrixFilter(_idMatrix.slice()); 195 | filters.push(s); 196 | t.filters = filters; 197 | } 198 | startMatrix = s.matrix; 199 | matrix = _idMatrix.slice(); 200 | if (v.colorize != null) { 201 | matrix = _colorize(matrix, v.colorize, Number(v.colorizeAmount)); 202 | } 203 | if (v.contrast != null) { 204 | matrix = _setContrast(matrix, Number(v.contrast)); 205 | } 206 | if (v.hue != null) { 207 | matrix = _setHue(matrix, Number(v.hue)); 208 | } 209 | if (v.saturation != null) { 210 | matrix = _setSaturation(matrix, Number(v.saturation)); 211 | } 212 | 213 | i = matrix.length; 214 | while (--i > -1) { 215 | if (matrix[i] !== startMatrix[i]) { 216 | pg._addTween(startMatrix, i, startMatrix[i], matrix[i], "easel_colorMatrixFilter"); 217 | } 218 | } 219 | 220 | pg._overwriteProps.push("easel_colorMatrixFilter"); 221 | if (!t.cacheID) { 222 | throw("EaselPlugin warning: for filters to display in EaselJS, you must call the object's cache() method first. " + t); 223 | } 224 | 225 | pg._matrix = startMatrix; 226 | }; 227 | 228 | 229 | _gsScope._gsDefine.plugin({ 230 | propName: "easel", 231 | priority: -1, 232 | version: "0.2.2", 233 | API: 2, 234 | 235 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 236 | init: function(target, value, tween, index) { 237 | this._target = target; 238 | var p, pt, tint, colorMatrix, end, labels, i; 239 | for (p in value) { 240 | 241 | end = value[p]; 242 | if (typeof(end) === "function") { 243 | end = end(index, target); 244 | } 245 | if (p === "colorFilter" || p === "tint" || p === "tintAmount" || p === "exposure" || p === "brightness") { 246 | if (!tint) { 247 | _parseColorFilter(target, value.colorFilter || value, this); 248 | tint = true; 249 | } 250 | 251 | } else if (p === "saturation" || p === "contrast" || p === "hue" || p === "colorize" || p === "colorizeAmount") { 252 | if (!colorMatrix) { 253 | _parseColorMatrixFilter(target, value.colorMatrixFilter || value, this); 254 | colorMatrix = true; 255 | } 256 | 257 | } else if (p === "frame") { 258 | this._firstPT = pt = {_next:this._firstPT, t:target, p:"gotoAndStop", s:target.currentFrame, f:true, n:"frame", pr:0, type:0, m:Math.round}; 259 | if (typeof(end) === "string" && end.charAt(1) !== "=" && (labels = target.labels)) { 260 | for (i = 0; i < labels.length; i++) { 261 | if (labels[i].label === end) { 262 | end = labels[i].position; 263 | } 264 | } 265 | } 266 | pt.c = (typeof(end) === "number") ? end - pt.s : parseFloat((end+"").split("=").join("")); 267 | if (pt._next) { 268 | pt._next._prev = pt; 269 | } 270 | 271 | } else if (target[p] != null) { 272 | this._firstPT = pt = {_next:this._firstPT, t:target, p:p, f:(typeof(target[p]) === "function"), n:p, pr:0, type:0}; 273 | pt.s = (!pt.f) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ](); 274 | pt.c = (typeof(end) === "number") ? end - pt.s : (typeof(end) === "string") ? parseFloat(end.split("=").join("")) : 0; 275 | 276 | if (pt._next) { 277 | pt._next._prev = pt; 278 | } 279 | } 280 | 281 | } 282 | return true; 283 | }, 284 | 285 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 286 | set: function(v) { 287 | var pt = this._firstPT, 288 | min = 0.000001, 289 | val; 290 | while (pt) { 291 | val = pt.c * v + pt.s; 292 | if (pt.m) { 293 | val = pt.m(val, pt.t); 294 | } else if (val < min && val > -min) { 295 | val = 0; 296 | } 297 | if (pt.f) { 298 | pt.t[pt.p](val); 299 | } else { 300 | pt.t[pt.p] = val; 301 | } 302 | pt = pt._next; 303 | } 304 | if (this._target.cacheID) { 305 | this._target.updateCache(); 306 | } 307 | } 308 | 309 | }); 310 | 311 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 312 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 313 | (function(name) { 314 | "use strict"; 315 | var getGlobal = function() { 316 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 317 | }; 318 | if (typeof(module) !== "undefined" && module.exports) { //node 319 | require("../TweenLite.js"); 320 | module.exports = getGlobal(); 321 | } else if (typeof(define) === "function" && define.amd) { //AMD 322 | define(["TweenLite"], getGlobal); 323 | } 324 | }("EaselPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/easing/EasePack.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.15.6 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | _gsScope._gsDefine("easing.Back", ["easing.Ease"], function(Ease) { 18 | 19 | var w = (_gsScope.GreenSockGlobals || _gsScope), 20 | gs = w.com.greensock, 21 | _2PI = Math.PI * 2, 22 | _HALF_PI = Math.PI / 2, 23 | _class = gs._class, 24 | _create = function(n, f) { 25 | var C = _class("easing." + n, function(){}, true), 26 | p = C.prototype = new Ease(); 27 | p.constructor = C; 28 | p.getRatio = f; 29 | return C; 30 | }, 31 | _easeReg = Ease.register || function(){}, //put an empty function in place just as a safety measure in case someone loads an OLD version of TweenLite.js where Ease.register doesn't exist. 32 | _wrap = function(name, EaseOut, EaseIn, EaseInOut, aliases) { 33 | var C = _class("easing."+name, { 34 | easeOut:new EaseOut(), 35 | easeIn:new EaseIn(), 36 | easeInOut:new EaseInOut() 37 | }, true); 38 | _easeReg(C, name); 39 | return C; 40 | }, 41 | EasePoint = function(time, value, next) { 42 | this.t = time; 43 | this.v = value; 44 | if (next) { 45 | this.next = next; 46 | next.prev = this; 47 | this.c = next.v - value; 48 | this.gap = next.t - time; 49 | } 50 | }, 51 | 52 | //Back 53 | _createBack = function(n, f) { 54 | var C = _class("easing." + n, function(overshoot) { 55 | this._p1 = (overshoot || overshoot === 0) ? overshoot : 1.70158; 56 | this._p2 = this._p1 * 1.525; 57 | }, true), 58 | p = C.prototype = new Ease(); 59 | p.constructor = C; 60 | p.getRatio = f; 61 | p.config = function(overshoot) { 62 | return new C(overshoot); 63 | }; 64 | return C; 65 | }, 66 | 67 | Back = _wrap("Back", 68 | _createBack("BackOut", function(p) { 69 | return ((p = p - 1) * p * ((this._p1 + 1) * p + this._p1) + 1); 70 | }), 71 | _createBack("BackIn", function(p) { 72 | return p * p * ((this._p1 + 1) * p - this._p1); 73 | }), 74 | _createBack("BackInOut", function(p) { 75 | return ((p *= 2) < 1) ? 0.5 * p * p * ((this._p2 + 1) * p - this._p2) : 0.5 * ((p -= 2) * p * ((this._p2 + 1) * p + this._p2) + 2); 76 | }) 77 | ), 78 | 79 | 80 | //SlowMo 81 | SlowMo = _class("easing.SlowMo", function(linearRatio, power, yoyoMode) { 82 | power = (power || power === 0) ? power : 0.7; 83 | if (linearRatio == null) { 84 | linearRatio = 0.7; 85 | } else if (linearRatio > 1) { 86 | linearRatio = 1; 87 | } 88 | this._p = (linearRatio !== 1) ? power : 0; 89 | this._p1 = (1 - linearRatio) / 2; 90 | this._p2 = linearRatio; 91 | this._p3 = this._p1 + this._p2; 92 | this._calcEnd = (yoyoMode === true); 93 | }, true), 94 | p = SlowMo.prototype = new Ease(), 95 | SteppedEase, RoughEase, _createElastic; 96 | 97 | p.constructor = SlowMo; 98 | p.getRatio = function(p) { 99 | var r = p + (0.5 - p) * this._p; 100 | if (p < this._p1) { 101 | return this._calcEnd ? 1 - ((p = 1 - (p / this._p1)) * p) : r - ((p = 1 - (p / this._p1)) * p * p * p * r); 102 | } else if (p > this._p3) { 103 | return this._calcEnd ? 1 - (p = (p - this._p3) / this._p1) * p : r + ((p - r) * (p = (p - this._p3) / this._p1) * p * p * p); 104 | } 105 | return this._calcEnd ? 1 : r; 106 | }; 107 | SlowMo.ease = new SlowMo(0.7, 0.7); 108 | 109 | p.config = SlowMo.config = function(linearRatio, power, yoyoMode) { 110 | return new SlowMo(linearRatio, power, yoyoMode); 111 | }; 112 | 113 | 114 | //SteppedEase 115 | SteppedEase = _class("easing.SteppedEase", function(steps, immediateStart) { 116 | steps = steps || 1; 117 | this._p1 = 1 / steps; 118 | this._p2 = steps + (immediateStart ? 0 : 1); 119 | this._p3 = immediateStart ? 1 : 0; 120 | }, true); 121 | p = SteppedEase.prototype = new Ease(); 122 | p.constructor = SteppedEase; 123 | p.getRatio = function(p) { 124 | if (p < 0) { 125 | p = 0; 126 | } else if (p >= 1) { 127 | p = 0.999999999; 128 | } 129 | return (((this._p2 * p) | 0) + this._p3) * this._p1; 130 | }; 131 | p.config = SteppedEase.config = function(steps, immediateStart) { 132 | return new SteppedEase(steps, immediateStart); 133 | }; 134 | 135 | 136 | //RoughEase 137 | RoughEase = _class("easing.RoughEase", function(vars) { 138 | vars = vars || {}; 139 | var taper = vars.taper || "none", 140 | a = [], 141 | cnt = 0, 142 | points = (vars.points || 20) | 0, 143 | i = points, 144 | randomize = (vars.randomize !== false), 145 | clamp = (vars.clamp === true), 146 | template = (vars.template instanceof Ease) ? vars.template : null, 147 | strength = (typeof(vars.strength) === "number") ? vars.strength * 0.4 : 0.4, 148 | x, y, bump, invX, obj, pnt; 149 | while (--i > -1) { 150 | x = randomize ? Math.random() : (1 / points) * i; 151 | y = template ? template.getRatio(x) : x; 152 | if (taper === "none") { 153 | bump = strength; 154 | } else if (taper === "out") { 155 | invX = 1 - x; 156 | bump = invX * invX * strength; 157 | } else if (taper === "in") { 158 | bump = x * x * strength; 159 | } else if (x < 0.5) { //"both" (start) 160 | invX = x * 2; 161 | bump = invX * invX * 0.5 * strength; 162 | } else { //"both" (end) 163 | invX = (1 - x) * 2; 164 | bump = invX * invX * 0.5 * strength; 165 | } 166 | if (randomize) { 167 | y += (Math.random() * bump) - (bump * 0.5); 168 | } else if (i % 2) { 169 | y += bump * 0.5; 170 | } else { 171 | y -= bump * 0.5; 172 | } 173 | if (clamp) { 174 | if (y > 1) { 175 | y = 1; 176 | } else if (y < 0) { 177 | y = 0; 178 | } 179 | } 180 | a[cnt++] = {x:x, y:y}; 181 | } 182 | a.sort(function(a, b) { 183 | return a.x - b.x; 184 | }); 185 | 186 | pnt = new EasePoint(1, 1, null); 187 | i = points; 188 | while (--i > -1) { 189 | obj = a[i]; 190 | pnt = new EasePoint(obj.x, obj.y, pnt); 191 | } 192 | 193 | this._prev = new EasePoint(0, 0, (pnt.t !== 0) ? pnt : pnt.next); 194 | }, true); 195 | p = RoughEase.prototype = new Ease(); 196 | p.constructor = RoughEase; 197 | p.getRatio = function(p) { 198 | var pnt = this._prev; 199 | if (p > pnt.t) { 200 | while (pnt.next && p >= pnt.t) { 201 | pnt = pnt.next; 202 | } 203 | pnt = pnt.prev; 204 | } else { 205 | while (pnt.prev && p <= pnt.t) { 206 | pnt = pnt.prev; 207 | } 208 | } 209 | this._prev = pnt; 210 | return (pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c); 211 | }; 212 | p.config = function(vars) { 213 | return new RoughEase(vars); 214 | }; 215 | RoughEase.ease = new RoughEase(); 216 | 217 | 218 | //Bounce 219 | _wrap("Bounce", 220 | _create("BounceOut", function(p) { 221 | if (p < 1 / 2.75) { 222 | return 7.5625 * p * p; 223 | } else if (p < 2 / 2.75) { 224 | return 7.5625 * (p -= 1.5 / 2.75) * p + 0.75; 225 | } else if (p < 2.5 / 2.75) { 226 | return 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375; 227 | } 228 | return 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375; 229 | }), 230 | _create("BounceIn", function(p) { 231 | if ((p = 1 - p) < 1 / 2.75) { 232 | return 1 - (7.5625 * p * p); 233 | } else if (p < 2 / 2.75) { 234 | return 1 - (7.5625 * (p -= 1.5 / 2.75) * p + 0.75); 235 | } else if (p < 2.5 / 2.75) { 236 | return 1 - (7.5625 * (p -= 2.25 / 2.75) * p + 0.9375); 237 | } 238 | return 1 - (7.5625 * (p -= 2.625 / 2.75) * p + 0.984375); 239 | }), 240 | _create("BounceInOut", function(p) { 241 | var invert = (p < 0.5); 242 | if (invert) { 243 | p = 1 - (p * 2); 244 | } else { 245 | p = (p * 2) - 1; 246 | } 247 | if (p < 1 / 2.75) { 248 | p = 7.5625 * p * p; 249 | } else if (p < 2 / 2.75) { 250 | p = 7.5625 * (p -= 1.5 / 2.75) * p + 0.75; 251 | } else if (p < 2.5 / 2.75) { 252 | p = 7.5625 * (p -= 2.25 / 2.75) * p + 0.9375; 253 | } else { 254 | p = 7.5625 * (p -= 2.625 / 2.75) * p + 0.984375; 255 | } 256 | return invert ? (1 - p) * 0.5 : p * 0.5 + 0.5; 257 | }) 258 | ); 259 | 260 | 261 | //CIRC 262 | _wrap("Circ", 263 | _create("CircOut", function(p) { 264 | return Math.sqrt(1 - (p = p - 1) * p); 265 | }), 266 | _create("CircIn", function(p) { 267 | return -(Math.sqrt(1 - (p * p)) - 1); 268 | }), 269 | _create("CircInOut", function(p) { 270 | return ((p*=2) < 1) ? -0.5 * (Math.sqrt(1 - p * p) - 1) : 0.5 * (Math.sqrt(1 - (p -= 2) * p) + 1); 271 | }) 272 | ); 273 | 274 | 275 | //Elastic 276 | _createElastic = function(n, f, def) { 277 | var C = _class("easing." + n, function(amplitude, period) { 278 | this._p1 = (amplitude >= 1) ? amplitude : 1; //note: if amplitude is < 1, we simply adjust the period for a more natural feel. Otherwise the math doesn't work right and the curve starts at 1. 279 | this._p2 = (period || def) / (amplitude < 1 ? amplitude : 1); 280 | this._p3 = this._p2 / _2PI * (Math.asin(1 / this._p1) || 0); 281 | this._p2 = _2PI / this._p2; //precalculate to optimize 282 | }, true), 283 | p = C.prototype = new Ease(); 284 | p.constructor = C; 285 | p.getRatio = f; 286 | p.config = function(amplitude, period) { 287 | return new C(amplitude, period); 288 | }; 289 | return C; 290 | }; 291 | _wrap("Elastic", 292 | _createElastic("ElasticOut", function(p) { 293 | return this._p1 * Math.pow(2, -10 * p) * Math.sin( (p - this._p3) * this._p2 ) + 1; 294 | }, 0.3), 295 | _createElastic("ElasticIn", function(p) { 296 | return -(this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * this._p2 )); 297 | }, 0.3), 298 | _createElastic("ElasticInOut", function(p) { 299 | return ((p *= 2) < 1) ? -0.5 * (this._p1 * Math.pow(2, 10 * (p -= 1)) * Math.sin( (p - this._p3) * this._p2)) : this._p1 * Math.pow(2, -10 *(p -= 1)) * Math.sin( (p - this._p3) * this._p2 ) * 0.5 + 1; 300 | }, 0.45) 301 | ); 302 | 303 | 304 | //Expo 305 | _wrap("Expo", 306 | _create("ExpoOut", function(p) { 307 | return 1 - Math.pow(2, -10 * p); 308 | }), 309 | _create("ExpoIn", function(p) { 310 | return Math.pow(2, 10 * (p - 1)) - 0.001; 311 | }), 312 | _create("ExpoInOut", function(p) { 313 | return ((p *= 2) < 1) ? 0.5 * Math.pow(2, 10 * (p - 1)) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); 314 | }) 315 | ); 316 | 317 | 318 | //Sine 319 | _wrap("Sine", 320 | _create("SineOut", function(p) { 321 | return Math.sin(p * _HALF_PI); 322 | }), 323 | _create("SineIn", function(p) { 324 | return -Math.cos(p * _HALF_PI) + 1; 325 | }), 326 | _create("SineInOut", function(p) { 327 | return -0.5 * (Math.cos(Math.PI * p) - 1); 328 | }) 329 | ); 330 | 331 | _class("easing.EaseLookup", { 332 | find:function(s) { 333 | return Ease.map[s]; 334 | } 335 | }, true); 336 | 337 | //register the non-standard eases 338 | _easeReg(w.SlowMo, "SlowMo", "ease,"); 339 | _easeReg(RoughEase, "RoughEase", "ease,"); 340 | _easeReg(SteppedEase, "SteppedEase", "ease,"); 341 | 342 | return Back; 343 | 344 | }, true); 345 | 346 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 347 | 348 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 349 | (function() { 350 | "use strict"; 351 | var getGlobal = function() { 352 | return (_gsScope.GreenSockGlobals || _gsScope); 353 | }; 354 | if (typeof(module) !== "undefined" && module.exports) { //node 355 | require("../TweenLite.js"); 356 | module.exports = getGlobal(); 357 | } else if (typeof(define) === "function" && define.amd) { //AMD 358 | define(["TweenLite"], getGlobal); 359 | } 360 | }()); -------------------------------------------------------------------------------- /src/script/core/Model.es6: -------------------------------------------------------------------------------- 1 | /* 2 | @ author: leeenx 3 | @ 模式 4 | */ 5 | 6 | import shuffle from '../lib/shuffle.es6'; 7 | import waveaverage from '../lib/waveaverage.es6'; 8 | 9 | export default class Model { 10 | constructor() { 11 | // 行列数 - 固定 12 | this.row = this.col = 10; 13 | /* 14 | @ 墙体信息 - 因为砖块是向下沉的 15 | @ count - 列砖块数 16 | @ start - 顶部行索引 17 | @ end - 底部行索引(好像没什么意思,因为它是个固定值) 18 | @ pitCount - 坑数 19 | @ topPit - 最顶部的坑 20 | @ bottomPit - 最底部的坑 21 | @ 如果 pitCount > 0 && bottomPit - topPit + 1 === pitCount 表示坑位是连续的 22 | */ 23 | this.wall = new Array(this.col); 24 | for(let i = 0; i < this.col; ++i) { 25 | this.wall[i] = { 26 | count: this.row, 27 | start: 0, 28 | end: this.row - 1, 29 | pitCount: 0, 30 | topPit: this.row, 31 | bottomPit: -1 32 | } 33 | } 34 | 35 | // 需要更新的列 - 记录哪几个列被消除了砖块 36 | this.updatedColSet = new Set(); 37 | 38 | // 表格总数 10x10 39 | this.gridCellCount = 100; 40 | 41 | // 星星的表格 42 | this.grid = []; 43 | 44 | // 砖块 45 | this.tiles = new Array(this.gridCellCount); 46 | for(let i = 0; i < this.gridCellCount; ++i) { 47 | this.tiles[i] = { 48 | // 原始索引 ---- 永远不变(相当于id) 49 | originIndex: undefined, 50 | // 实时索引 ---- 反映在 grid 的位置 51 | index: undefined, 52 | // 颜色索引 53 | clr: 0, 54 | // 分值 55 | score: 0, 56 | // 是否移除 57 | removed: false 58 | }; 59 | } 60 | 61 | // 可见色砖总数 62 | this.tileCount = this.gridCellCount; 63 | } 64 | // 填充数组 ---- count 表示几种颜色 65 | init() { 66 | // 色砖小计数 67 | let subtotal = 0; 68 | // 波动均分色块 69 | waveaverage(5, 4, 4).forEach( 70 | (count, clr) => { 71 | count += 20; 72 | // 色砖数量 73 | while(0 < count--) { 74 | let tile = this.tiles[subtotal++]; 75 | // 删除 originIndex ---- 提前删除可以提升性能 76 | delete tile.originIndex; 77 | tile.clr = clr; 78 | } 79 | } 80 | ); 81 | 82 | // 打散 tiles 83 | shuffle(this.tiles); 84 | 85 | // 存入 grid 86 | this.grid = this.tiles.map( 87 | (tile, index) => { 88 | // 实时索引 89 | tile.index = index; 90 | // 原索引 91 | tile.originIndex = index; 92 | // 默认在舞台上 93 | tile.removed = false; 94 | return tile; 95 | } 96 | ); 97 | 98 | // 可见色砖总数重置 99 | this.tileCount = this.gridCellCount; 100 | 101 | // 生成新的列信息 102 | for(let i = 0; i < this.col; ++i) { 103 | this.wall[i] = { 104 | count: this.row, 105 | start: 0, 106 | end: this.row - 1, 107 | pitCount: 0, 108 | topPit: this.row, 109 | bottomPit: -1 110 | } 111 | } 112 | 113 | // 死局 ---- 重新生成关卡 114 | if(this.check() === false) { 115 | this.init(); 116 | } 117 | } 118 | // 上节点颜色对比 119 | compareTopTile(index, rowIndex, colIndex, clr) { 120 | // 上节点索引 121 | let topIndex = index - this.col; 122 | // 上边界「行」索引 123 | let topBoundary = this.wall[colIndex].start; 124 | // 在最顶部 125 | if(rowIndex === topBoundary) return false; 126 | // 非最顶部 127 | else if(this.grid[topIndex] !== undefined && this.grid[topIndex].clr === clr) { 128 | return topIndex; 129 | } 130 | // 颜色不匹配 131 | else { 132 | return false; 133 | } 134 | } 135 | // 右节点颜色对比 136 | compareRightTile(index, rowIndex, colIndex, clr) { 137 | // 右节点索引 138 | let rightIndex = index + 1; 139 | // 右边界「列」索引 140 | let rightBoundary = this.wall.length - 1; 141 | // 在最右部 142 | if(colIndex === rightBoundary) return false; 143 | // 非最右部 144 | else if(this.grid[rightIndex] !== undefined && this.grid[rightIndex].clr === clr) { 145 | return rightIndex; 146 | } 147 | // 颜色不匹配 148 | else { 149 | return false; 150 | } 151 | } 152 | // 下节点颜色对比 153 | compareBottomTile(index, rowIndex, colIndex, clr) { 154 | // 下节点索引 155 | let bottomIndex = index + this.col; 156 | // 下边界「行」索引 157 | let bottomBoundary = this.wall[colIndex].end; 158 | // 在最底部 159 | if(rowIndex === bottomBoundary) return false; 160 | // 非最底部 161 | else if(this.grid[bottomIndex] !== undefined && this.grid[bottomIndex].clr === clr) { 162 | return bottomIndex; 163 | } 164 | // 颜色不匹配 165 | else { 166 | return false; 167 | } 168 | } 169 | // 左节点颜色对比 170 | compareLeftTile(index, rowIndex, colIndex, clr) { 171 | // 左节点索引 172 | let leftIndex = index - 1; 173 | // 右边界「列」索引 174 | let leftBoundary = 0; 175 | // 在最左部 176 | if(colIndex === leftBoundary) return false; 177 | // 非最左部 178 | else if(this.grid[leftIndex] !== undefined && this.grid[leftIndex].clr === clr) { 179 | return leftIndex; 180 | } 181 | // 颜色不匹配 182 | else { 183 | return false; 184 | } 185 | } 186 | // 检查是否存在相邻的同色砖块 187 | hasNeighbour(index, rowIndex, colIndex) { 188 | let tile = this.grid[index]; 189 | if(tile === undefined) return false; 190 | let clr = tile.clr; 191 | if(this.compareTopTile(index, rowIndex, colIndex, clr) !== false) return true; 192 | else if(this.compareRightTile(index, rowIndex, colIndex, clr) !== false) return true; 193 | else if(this.compareBottomTile(index, rowIndex, colIndex, clr) !== false) return true; 194 | else if(this.compareLeftTile(index, rowIndex, colIndex, clr) !== false) return true; 195 | else return false; 196 | } 197 | // 寻找相邻同色砖块 198 | searchSameClr(index, rowIndex, colIndex, clr) { 199 | let sameClrTiles = [ 200 | this.compareTopTile(index, rowIndex, colIndex, clr), 201 | this.compareRightTile(index, rowIndex, colIndex, clr), 202 | this.compareBottomTile(index, rowIndex, colIndex, clr), 203 | this.compareLeftTile(index, rowIndex, colIndex, clr) 204 | ]; 205 | return sameClrTiles.filter(index => index !== false); 206 | } 207 | // 删除指字的单元格 208 | deleteCell(index, rowIndex, colIndex, count) { 209 | let tile = this.grid[index]; 210 | // 表示已经被删除过了 211 | if(tile === undefined) return false; 212 | // 标记分值 213 | tile.score = count * 10 + 5; 214 | // 标记被移去 215 | tile.removed = true; 216 | // 从 grid 上删除 217 | delete this.grid[index]; 218 | // 更新colInfo 219 | this.updateColInfo(index, rowIndex, colIndex); 220 | return true; 221 | } 222 | // 清除指定索引的色块及其相邻的同色块 223 | clean(index) { 224 | // 当前的行列坐标 - 纯粹是为了提高一点性能 225 | let colIndex = index % this.col, rowIndex = index / this.col >> 0; 226 | // 周边没有相同颜色砖块,直接中断 227 | if(this.hasNeighbour(index, rowIndex, colIndex) === false) return 0; 228 | // 周边有相同颜色砖块 229 | else { 230 | // 当前砖块 231 | let tile = this.grid[index], clr = tile.clr; 232 | 233 | // 同色砖块 234 | let sameClrTiles = this.searchSameClr(index, rowIndex, colIndex, clr); 235 | 236 | // 被删除的个数 237 | let count = 0; 238 | 239 | // 删除砖块 240 | this.deleteCell(index, rowIndex, colIndex, count) && ++count; 241 | 242 | while(true) { 243 | // 下一次循环的 sameClrTiles 数组 244 | let nextSameClrTiles = []; 245 | sameClrTiles.forEach(index => { 246 | // 当前的行列坐标 - 纯粹是为了提高一点性能 247 | let colIndex = index % this.col, rowIndex = index / this.col >> 0; 248 | nextSameClrTiles.push(...this.searchSameClr(index, rowIndex, colIndex, clr)); 249 | this.deleteCell(index, rowIndex, colIndex, count) && ++count; 250 | }); 251 | // 下一次的同色砖块数量为0,表示清除结束 252 | if(nextSameClrTiles.length === 0) break; 253 | // 数组替换 254 | sameClrTiles = nextSameClrTiles; 255 | } 256 | // 夯实数组 257 | // this.tamp(); 258 | return count; 259 | } 260 | } 261 | // 清空所有的色砖 262 | cleanAll() { 263 | // 减分倍数 264 | let count = 0; 265 | for(let col = 0, len = this.wall.length; col < len; ++col) { 266 | let colInfo = this.wall[col]; 267 | for(let row = colInfo.start; row <= colInfo.end; ++row) { 268 | let tile = this.grid[row * this.col + col]; 269 | tile.score = -20 - 40 * count++; 270 | tile.removed = true; 271 | } 272 | } 273 | } 274 | // 更新列信息 275 | updateColInfo(index, rowIndex, colIndex) { 276 | this.updatedColSet.has(colIndex) || this.updatedColSet.add(colIndex); 277 | let colInfo = this.wall[colIndex]; 278 | // 当前列砖块数量减1 279 | --colInfo.count; 280 | // 列的空洞数加1 281 | ++colInfo.pitCount; 282 | // 更新顶坑位置 283 | colInfo.topPit = Math.min(colInfo.topPit, rowIndex); 284 | // 更新底坑位置 285 | colInfo.bottomPit = Math.max(colInfo.bottomPit, rowIndex); 286 | // 可见砖块总数更新 287 | --this.tileCount; 288 | } 289 | // 夯实数组 290 | tamp() { 291 | // 空列数 292 | let emptyCol = []; 293 | // 空列的最小与最大索引 294 | let min = this.col, max = -1; 295 | for(let colIndex of this.updatedColSet) { 296 | let colInfo = this.wall[colIndex]; 297 | let {start, end, pitCount, topPit, bottomPit, count} = colInfo; 298 | 299 | // 垂直方法压缩 300 | if(pitCount > 0) { 301 | // 连续空洞 302 | if(bottomPit - topPit + 1 === pitCount) { 303 | // topPit 上面有砖块(如果上面没有砖块就已经是压缩状态) 304 | if(topPit > start) { 305 | // 起始索引,其实是顶坑索引的正上方 306 | let fromIndex = (topPit - 1) * this.col + colIndex; 307 | // 目标索引 308 | let toIndex = start * this.col + colIndex; 309 | // 底坑索引 - 在夯实过程中会变 310 | let bottomPitIndex = bottomPit * this.col + colIndex; 311 | for(let i = fromIndex; i >= toIndex; i -= this.col, bottomPitIndex -= this.col) { 312 | // 执行压缩 313 | let tile = this.grid[bottomPitIndex] = this.grid[i]; 314 | delete this.grid[i]; 315 | // 更新自身索引 316 | tile.index = bottomPitIndex; 317 | } 318 | } 319 | } 320 | // 非连续空洞 321 | else { 322 | // 底坑索引 - 在夯实过程中会变 323 | let bottomPitIndex = bottomPit * this.col + colIndex; 324 | // 起始索引 - 底坑索引的正上方 325 | let fromIndex = bottomPitIndex - this.col; 326 | // 目标索引 327 | let toIndex = start * this.col + colIndex; 328 | for(let i = fromIndex; i >= toIndex; i -= this.col) { 329 | let tile = this.grid[i]; 330 | // 当前位置有砖块,执行压缩 331 | if(tile !== undefined) { 332 | // 执行压缩 333 | this.grid[bottomPitIndex] = tile; 334 | delete this.grid[i]; 335 | // 更新自身索引 336 | tile.index = bottomPitIndex; 337 | // 底坑索引上升 338 | bottomPitIndex -= this.col 339 | } 340 | } 341 | } 342 | } 343 | 344 | // 更新当前列的顶部行索引 345 | colInfo.start += pitCount; 346 | // 当前列不存在色砖 347 | if(colInfo.count === 0) { 348 | // 空列数 349 | emptyCol.push(colIndex); 350 | // 动态记录最大最小索引 351 | max = Math.max(colIndex, max); 352 | min = Math.min(colIndex, min); 353 | } 354 | // 当前列仍有色砖 355 | else { 356 | // 重置坑洞信息 357 | colInfo.pitCount = 0; 358 | colInfo.topPit = colInfo.count; 359 | colInfo.bottomPit = -1; 360 | } 361 | } 362 | // 空列总数 363 | let emptyColCount = emptyCol.length; 364 | // 当前列数 365 | let colCount = this.wall.length; 366 | // 有空列,水平方向压缩 367 | if(emptyColCount > 0) { 368 | // 连续的空列 369 | if(emptyColCount === 1 || max - min + 1 === emptyColCount) { 370 | // 空列不处在最右边 - 空列在最右边表示已经是压缩状态,删除最右边的空列信息 371 | if(max !== colCount - 1) { 372 | for(let i = max + 1; i < colCount; ++i) { 373 | let colInfo = this.wall[i]; 374 | let {start, end} = colInfo; 375 | // 压缩 376 | for(let j = start; j <= end; ++j) { 377 | // 压缩前索引 378 | let indexA = j * this.col + i; 379 | // 压缩后索引 -- 其实就是往左移动 emptyColCoount 行 380 | let indexB = indexA - emptyColCount; 381 | // 招行压缩 382 | let tile = this.grid[indexB] = this.grid[indexA]; 383 | delete this.grid[indexA]; 384 | // 更新索引 385 | tile.index = indexB; 386 | } 387 | // wall 信息更新 388 | this.wall[i - emptyColCount] = colInfo; 389 | delete this.wall[i]; 390 | } 391 | } 392 | } 393 | // 非连续的空列 394 | else { 395 | // 最左边的空列 - 压缩过程会变化 396 | let leftEmptyColIndex = min; 397 | for(let i = min + 1; i < colCount; ++i) { 398 | let colInfo = this.wall[i]; 399 | let {start, end, count} = colInfo; 400 | // 当前列到最左边空列的距离 401 | let distance = i - leftEmptyColIndex; 402 | // 非空行 - 压缩 403 | if(count > 0) { 404 | for(let j = start; j <= end; ++j) { 405 | // 压缩前索引 406 | let indexA = j * this.col + i; 407 | // 压缩后的索引 -- 其实就是移动到最左边的空列 408 | let indexB = indexA - distance; 409 | // 招行压缩 410 | let tile = this.grid[indexB] = this.grid[indexA]; 411 | delete this.grid[indexA]; 412 | // 更新索引 413 | tile.index = indexB; 414 | } 415 | 416 | // wall 信息更新 417 | this.wall[leftEmptyColIndex] = colInfo; 418 | delete this.wall[i]; 419 | 420 | // 最左边的空理右移一列 421 | ++leftEmptyColIndex; 422 | } 423 | } 424 | } 425 | 426 | // 删除最右边的空列 427 | this.wall.splice(colCount - emptyColCount, emptyColCount); 428 | 429 | } 430 | 431 | // 清空 updatedColSet 432 | this.updatedColSet.clear(); 433 | 434 | } 435 | /* 436 | @ 检查是否死局 437 | @ 非死局会返回一个索引值 438 | @ 死局返回 false 439 | */ 440 | check() { 441 | if(this.tileCount === 0) return false; 442 | // 取一个随机「列」样本 443 | let patternCol = Math.random() * this.wall.length >> 0; 444 | let {start, end} = this.wall[patternCol]; 445 | // 取一个随机「行」样式 446 | let patternRow = Math.random() * (end - start + 1) + start >> 0; 447 | 448 | // 向左扫描「列」 449 | for(let col = patternCol; col >=0; --col) { 450 | let colInfo = this.wall[col]; 451 | // 行索引 452 | let rowIndex = (patternCol === col ? patternRow : colInfo.start); 453 | // 向下扫描「行」 454 | for(let row = rowIndex; row <= colInfo.end; ++row) { 455 | let index = row * this.col + col; 456 | // 有同色砖块,直接中断 457 | if(this.hasNeighbour(index, row, col)) { 458 | return index; 459 | } 460 | } 461 | } 462 | // 向右扫描「列」 463 | for(let col = patternCol, len = this.wall.length; col < len; ++col) { 464 | let colInfo = this.wall[col]; 465 | // 行索引 466 | let rowIndex = (patternCol === col ? patternRow - 1 : colInfo.end); 467 | // 向上扫描「行」 468 | for(let row = rowIndex; row >= colInfo.start; --row) { 469 | let index = row * this.col + col; 470 | // 有同色砖块,直接中断 471 | if(this.hasNeighbour(index, row, col)) { 472 | return index; 473 | } 474 | } 475 | } 476 | return false; 477 | } 478 | } 479 | 480 | -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/RaphaelPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.2.2 3 | * DATE: 2017-01-17 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _NaNExp = /[^\d\-\.]/g, 18 | _DEG2RAD = Math.PI / 180, 19 | _numExp = /(\d|\.)+/g, 20 | _colorLookup = {aqua:[0,255,255], 21 | lime:[0,255,0], 22 | silver:[192,192,192], 23 | black:[0,0,0], 24 | maroon:[128,0,0], 25 | teal:[0,128,128], 26 | blue:[0,0,255], 27 | navy:[0,0,128], 28 | white:[255,255,255], 29 | fuchsia:[255,0,255], 30 | olive:[128,128,0], 31 | yellow:[255,255,0], 32 | orange:[255,165,0], 33 | gray:[128,128,128], 34 | purple:[128,0,128], 35 | green:[0,128,0], 36 | red:[255,0,0], 37 | pink:[255,192,203], 38 | cyan:[0,255,255], 39 | transparent:[255,255,255,0]}, 40 | //parses a color (like #9F0, #FF9900, or rgb(255,51,153)) into an array with 3 elements for red, green, and blue. Also handles rgba() values (splits into array of 4 elements of course) 41 | _parseColor = function(color) { 42 | if (typeof(color) === "number") { 43 | return [color >> 16, (color >> 8) & 255, color & 255]; 44 | } else if (color === "" || color == null || color === "none" || typeof(color) !== "string") { 45 | return _colorLookup.transparent; 46 | } else if (_colorLookup[color]) { 47 | return _colorLookup[color]; 48 | } else if (color.charAt(0) === "#") { 49 | if (color.length === 4) { //for shorthand like #9F0 50 | color = "#" + color.charAt(1) + color.charAt(1) + color.charAt(2) + color.charAt(2) + color.charAt(3) + color.charAt(3); 51 | } 52 | color = parseInt(color.substr(1), 16); 53 | return [color >> 16, (color >> 8) & 255, color & 255]; 54 | } 55 | return color.match(_numExp) || _colorLookup.transparent; 56 | }, 57 | 58 | _transformMap = {scaleX:1, scaleY:1, tx:1, ty:1, rotation:1, shortRotation:1, skewX:1, skewY:1, scale:1}, 59 | 60 | //parses the transform values for an element, returning an object with x, y, scaleX, scaleY, rotation, skewX, and skewY properties. Note: by default (for performance reasons), all skewing is combined into skewX and rotation but skewY still has a place in the transform object so that we can record how much of the skew is attributed to skewX vs skewY. Remember, a skewY of 10 looks the same as a rotation of 10 and skewX of -10. 61 | _getTransform = function(t, rec) { 62 | var s = t.matrix, 63 | min = 0.000001, 64 | a = s.a, 65 | b = s.b, 66 | c = s.c, 67 | d = s.d, 68 | m = rec ? t._gsTransform || {skewY:0} : {skewY:0}, 69 | invX = (m.scaleX < 0); //in order to interpret things properly, we need to know if the user applied a negative scaleX previously so that we can adjust the rotation and skewX accordingly. Otherwise, if we always interpret a flipped matrix as affecting scaleY and the user only wants to tween the scaleX on multiple sequential tweens, it would keep the negative scaleY without that being the user's intent. 70 | 71 | m.tx = s.e - (m.ox || 0); //ox is the offset x that we record in setRatio() whenever we apply a custom transform that might use a pivot point. Remember, s.e and s.f get affected by things like scale. For example, imagine an object whose top left corner is at 100,100 and then we scale it up to 300% using the center as the pivot point - that corner would now be very different even though to the user, they didn't intend to change/tween the x/y position per se. Therefore, we record whatever offsets we make so that we can compensate when reading the values back. 72 | m.ty = s.f - (m.oy || 0); //oy is the offset y (see note above) 73 | m.scaleX = Math.sqrt(a * a + b * b); 74 | m.scaleY = Math.sqrt(d * d + c * c); 75 | m.rotation = (a || b) ? Math.atan2(b, a) : m.rotation || 0; //note: if scaleX is 0, we cannot accurately measure rotation. Same for skewX with a scaleY of 0. Therefore, we default to the previously recorded value (or zero if that doesn't exist). 76 | m.skewX = (c || d) ? Math.atan2(c, d) + m.rotation : m.skewX || 0; 77 | if (Math.abs(m.skewX) > Math.PI / 2) { 78 | if (invX) { 79 | m.scaleX *= -1; 80 | m.skewX += (m.rotation <= 0) ? Math.PI : -Math.PI; 81 | m.rotation += (m.rotation <= 0) ? Math.PI : -Math.PI; 82 | } else { 83 | m.scaleY *= -1; 84 | m.skewX += (m.skewX <= 0) ? Math.PI : -Math.PI; 85 | } 86 | } 87 | //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases. The conditional logic here is faster than calling Math.abs(). 88 | if (m.rotation < min) if (m.rotation > -min) if (a || b) { 89 | m.rotation = 0; 90 | } 91 | if (m.skewX < min) if (m.skewX > -min) if (b || c) { 92 | m.skewX = 0; 93 | } 94 | if (rec) { 95 | t._gsTransform = m; //record to the object's _gsTransform which we use so that tweens can control individual properties independently (we need all the properties to accurately recompose the matrix in the setRatio() method) 96 | } 97 | return m; 98 | }, 99 | 100 | //takes a value and a default number, checks if the value is relative, null, or numeric and spits back a normalized number accordingly. Primarily used in the _parseTransform() function. 101 | _parseVal = function(v, d) { 102 | return (v == null) ? d : (typeof(v) === "string" && v.indexOf("=") === 1) ? parseInt(v.charAt(0)+"1", 10) * Number(v.substr(2)) + d : Number(v); 103 | }, 104 | 105 | //translates strings like "40deg" or "40" or 40rad" or "+=40deg" to a numeric radian angle, optionally relative to a default value (if "+=" or "-=" prefix is found) 106 | _parseAngle = function(v, d) { 107 | var m = (v.indexOf("rad") === -1) ? _DEG2RAD : 1, 108 | r = (v.indexOf("=") === 1); 109 | v = Number(v.replace(_NaNExp, "")) * m; 110 | return r ? v + d : v; 111 | }, 112 | 113 | 114 | RaphaelPlugin = _gsScope._gsDefine.plugin({ 115 | propName: "raphael", 116 | version: "0.2.2", 117 | API: 2, 118 | 119 | //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 120 | init: function(target, value, tween) { 121 | if (!target.attr) { //raphael must have attr() method 122 | return false; 123 | } 124 | this._target = target; 125 | this._tween = tween; 126 | this._props = target._gsProps = target._gsProps || {}; 127 | var p, s, v, pt, clr1, clr2, rel; 128 | 129 | for (p in value) { 130 | 131 | v = value[p]; 132 | 133 | if (p === "transform") { 134 | this._parseTransform(target, v); 135 | continue; 136 | } else if (_transformMap[p] || p === "pivot") { 137 | this._parseTransform(target, value); 138 | continue; 139 | } 140 | 141 | s = target.attr(p); 142 | 143 | //Some of these properties are in place in order to conform with the standard PropTweens in TweenPlugins so that overwriting and roundProps occur properly. For example, f and r may seem unnecessary here, but they enable other functionality. 144 | //_next:* next linked list node [object] 145 | //t: * target [object] 146 | //p: * property (camelCase) [string] 147 | //s: * starting value [number] 148 | //c: * change value [number] 149 | //f: * is function [boolean] 150 | //n: * name (for overwriting) [string] 151 | //b: beginning value [string] 152 | //i: intermediate value [string] 153 | //e: ending value [string] 154 | //r: * round [boolean] 155 | //type: 0=normal, 1=color, 2=rgba, -1=non-tweening prop [number] 156 | this._firstPT = pt = {_next:this._firstPT, 157 | t:this._props, 158 | p:p, 159 | b:s, 160 | f:false, 161 | n:"raphael_" + p, 162 | r:false, 163 | type:0}; 164 | 165 | //color values must be split apart into their R, G, B (and sometimes alpha) values and tweened independently. 166 | if (p === "fill" || p === "stroke") { 167 | clr1 = _parseColor(s); 168 | clr2 = _parseColor(v); 169 | pt.e = v; 170 | pt.s = Number(clr1[0]); //red starting value 171 | pt.c = Number(clr2[0]) - pt.s; //red change 172 | pt.gs = Number(clr1[1]); //green starting value 173 | pt.gc = Number(clr2[1]) - pt.gs; //green change 174 | pt.bs = Number(clr1[2]); //blue starting value 175 | pt.bc = Number(clr2[2]) - pt.bs; //blue change 176 | if (clr1.length > 3 || clr2.length > 3) { //detect an rgba() value 177 | pt.as = (clr1.length < 4) ? 1 : Number(clr1[3]); 178 | pt.ac = ((clr2.length < 4) ? 1 : Number(clr2[3])) - pt.as; 179 | pt.type = 2; //2 = rgba() tween 180 | } else { 181 | pt.type = 1; //1 = color tween, -1 = no tween, just set the value at the end because there's no changes 182 | } 183 | 184 | } else { 185 | 186 | s = (typeof(s) === "string") ? parseFloat(s.replace(_NaNExp, "")) : Number(s); 187 | 188 | if (typeof(v) === "string") { 189 | rel = (v.charAt(1) === "="); 190 | v = parseFloat(v.replace(_NaNExp, "")); 191 | } else { 192 | rel = false; 193 | } 194 | 195 | pt.e = (v || v === 0) ? (rel ? v + s : v) : value[p]; //ensures that any += or -= prefixes are taken care of. 196 | 197 | if ((s || s === 0) && (v || v === 0) && (pt.c = (rel ? v : v - s))) { //faster than isNaN(). Also, we set pt.c (change) here because if it's 0, we'll just treat it like a non-tweening value. can't do (v !== start) because if it's a relative value and the CHANGE is identical to the START, the condition will fail unnecessarily. 198 | pt.s = s; 199 | } else { 200 | pt.type = -1; 201 | pt.i = value[p]; //intermediate value is typically the same as the end value. 202 | pt.s = pt.c = 0; 203 | } 204 | 205 | } 206 | 207 | this._overwriteProps.push("raphael_" + p); 208 | if (pt._next) { 209 | pt._next._prev = pt; 210 | } 211 | } 212 | 213 | return true; 214 | }, 215 | 216 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 217 | set: function(v) { 218 | var pt = this._firstPT, val; 219 | 220 | while (pt) { 221 | val = pt.c * v + pt.s; 222 | if (pt.r) { 223 | val = Math.round(val); 224 | } 225 | if (!pt.type) { 226 | pt.t[pt.p] = val; 227 | } else if (pt.type === 1) { //rgb() 228 | pt.t[pt.p] = "rgb(" + (val >> 0) + ", " + ((pt.gs + (v * pt.gc)) >> 0) + ", " + ((pt.bs + (v * pt.bc)) >> 0) + ")"; 229 | } else if (pt.type === 2) { //rgba() 230 | pt.t[pt.p] = "rgba(" + (val >> 0) + ", " + ((pt.gs + (v * pt.gc)) >> 0) + ", " + ((pt.bs + (v * pt.bc)) >> 0) + ", " + (pt.as + (v * pt.ac)) + ")"; 231 | } else if (pt.type === -1) { //non-tweening 232 | pt.t[pt.p] = pt.i; 233 | } 234 | pt = pt._next; 235 | } 236 | 237 | this._target.attr(this._props); 238 | 239 | //apply transform values like x, y, scaleX, scaleY, rotation, skewX, or skewY. We do these after looping through all the PropTweens because those are where the changes are made to scaleX/scaleY/rotation/skewX/skewY/x/y. 240 | if (this._transform) { 241 | pt = this._transform; //to improve speed and reduce size, reuse the pt variable as an alias to the _transform property 242 | var ang = pt.rotation, 243 | skew = ang - pt.skewX, 244 | a = Math.cos(ang) * pt.scaleX, 245 | b = Math.sin(ang) * pt.scaleX, 246 | c = Math.sin(skew) * -pt.scaleY, 247 | d = Math.cos(skew) * pt.scaleY, 248 | min = 0.000001, 249 | pxl = this._pxl, 250 | pyl = this._pyl; 251 | 252 | //some browsers have a hard time with very small values like 2.4492935982947064e-16 (notice the "e-" towards the end) and would render the object slightly off. So we round to 0 in these cases for both b and c. The conditional logic here is faster than calling Math.abs(). 253 | if (b < min) if (b > -min) { 254 | b = 0; 255 | } 256 | if (c < min) if (c > -min) { 257 | c = 0; 258 | } 259 | pt.ox = this._pxg - (pxl * a + pyl * c); //we must record the offset x/y that we're making from the regular tx/ty (matrix.e and f) so that we can correctly interpret positional data in _getTransform(). See note there on tx and ox. 260 | pt.oy = this._pyg - (pxl * b + pyl * d); 261 | this._target.transform("m" + a + "," + b + "," + c + "," + d + "," + (pt.tx + pt.ox) + "," + (pt.ty + pt.oy)); 262 | } 263 | 264 | } 265 | 266 | }), 267 | p = RaphaelPlugin.prototype; 268 | 269 | //compares the beginning x, y, scaleX, scaleY, rotation, and skewX properties with the ending ones and adds PropTweens accordingly wherever necessary. We must tween them individually (rather than just tweening the matrix values) so that elgant overwriting can occur, like if one tween is controlling scaleX, scaleY, and rotation and then another one starts mid-tween that is trying to control the scaleX only - this tween should continue tweening scaleY and rotation. 270 | p._parseTransform = function(t, v) { 271 | if (this._transform) { return; } //only need to parse the transform once, and only if the browser supports it. 272 | 273 | var m1 = this._transform = _getTransform(t, true), 274 | min = 0.000001, 275 | m2, skewY, p, pt, copy, dx, dy, mtx, pivot; 276 | 277 | if (typeof(v) === "object") { //for values like scaleX, scaleY, rotation, x, y, skewX, and skewY or transform:{...} (object) 278 | 279 | m2 = {scaleX:_parseVal((v.scaleX != null) ? v.scaleX : v.scale, m1.scaleX), 280 | scaleY:_parseVal((v.scaleY != null) ? v.scaleY : v.scale, m1.scaleY), 281 | tx:_parseVal(v.tx, m1.tx), 282 | ty:_parseVal(v.ty, m1.ty)}; 283 | 284 | if (v.shortRotation != null) { 285 | m2.rotation = (typeof(v.shortRotation) === "number") ? v.shortRotation * _DEG2RAD : _parseAngle(v.shortRotation, m1.rotation); 286 | var dif = (m2.rotation - m1.rotation) % (Math.PI * 2); 287 | if (dif !== dif % Math.PI) { 288 | dif += Math.PI * ((dif < 0) ? 2 : -2); 289 | } 290 | m2.rotation = m1.rotation + dif; 291 | 292 | } else { 293 | m2.rotation = (v.rotation == null) ? m1.rotation : (typeof(v.rotation) === "number") ? v.rotation * _DEG2RAD : _parseAngle(v.rotation, m1.rotation); 294 | } 295 | m2.skewX = (v.skewX == null) ? m1.skewX : (typeof(v.skewX) === "number") ? v.skewX * _DEG2RAD : _parseAngle(v.skewX, m1.skewX); 296 | 297 | //note: for performance reasons, we combine all skewing into the skewX and rotation values, ignoring skewY but we must still record it so that we can discern how much of the overall skew is attributed to skewX vs. skewY. Otherwise, if the skewY would always act relative (tween skewY to 10deg, for example, multiple times and if we always combine things into skewX, we can't remember that skewY was 10 from last time). Remember, a skewY of 10 degrees looks the same as a rotation of 10 degrees plus a skewX of -10 degrees. 298 | m2.skewY = (v.skewY == null) ? m1.skewY : (typeof(v.skewY) === "number") ? v.skewY * _DEG2RAD : _parseAngle(v.skewY, m1.skewY); 299 | if ((skewY = m2.skewY - m1.skewY)) { 300 | m2.skewX += skewY; 301 | m2.rotation += skewY; 302 | } 303 | //don't allow rotation/skew values to be a SUPER small decimal because when they're translated back to strings for setting the css property, the browser reports them in a funky way, like 1-e7. Of course we could use toFixed() to resolve that issue but that hurts performance quite a bit with all those function calls on every frame, plus it is virtually impossible to discern values that small visually (nobody will notice changing a rotation of 0.0000001 to 0, so the performance improvement is well worth it). 304 | if (m2.skewY < min) if (m2.skewY > -min) { 305 | m2.skewY = 0; 306 | } 307 | if (m2.skewX < min) if (m2.skewX > -min) { 308 | m2.skewX = 0; 309 | } 310 | if (m2.rotation < min) if (m2.rotation > -min) { 311 | m2.rotation = 0; 312 | } 313 | 314 | pivot = v.localPivot || v.globalPivot; 315 | 316 | if (typeof(pivot) === "string") { 317 | copy = pivot.split(","); 318 | dx = Number(copy[0]); 319 | dy = Number(copy[1]); 320 | } else if (typeof(pivot) === "object") { 321 | dx = Number(pivot.x); 322 | dy = Number(pivot.y); 323 | } else if (v.localPivot) { 324 | copy = t.getBBox(true); 325 | dx = copy.width / 2; 326 | dy = copy.height / 2; 327 | } else { 328 | copy = t.getBBox(); 329 | dx = copy.x + copy.width / 2; 330 | dy = copy.y + copy.height / 2; 331 | } 332 | 333 | if (v.localPivot) { 334 | mtx = t.matrix; 335 | dx += t.attr("x"); 336 | dy += t.attr("y"); 337 | this._pxl = dx; 338 | this._pyl = dy; 339 | this._pxg = dx * mtx.a + dy * mtx.c + mtx.e - m1.tx; 340 | this._pyg = dx * mtx.b + dy * mtx.d + mtx.f - m1.ty; 341 | } else { 342 | mtx = t.matrix.invert(); 343 | this._pxl = dx * mtx.a + dy * mtx.c + mtx.e; 344 | this._pyl = dx * mtx.b + dy * mtx.d + mtx.f; 345 | this._pxg = dx - m1.tx; 346 | this._pyg = dy - m1.ty; 347 | } 348 | 349 | } else if (typeof(v) === "string") { //for values like transform:"rotate(60deg) scale(0.5, 0.8)" 350 | copy = this._target.transform(); 351 | t.transform(v); 352 | m2 = _getTransform(t, false); 353 | t.transform(copy); 354 | } else { 355 | return; 356 | } 357 | 358 | for (p in _transformMap) { 359 | if (m1[p] !== m2[p]) if (p !== "shortRotation") if (p !== "scale") { 360 | this._firstPT = pt = {_next:this._firstPT, t:m1, p:p, s:m1[p], c:m2[p] - m1[p], n:p, f:false, r:false, b:m1[p], e:m2[p], type:0}; 361 | if (pt._next) { 362 | pt._next._prev = pt; 363 | } 364 | this._overwriteProps.push("raphael_" + p); 365 | } 366 | } 367 | }; 368 | 369 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } -------------------------------------------------------------------------------- /src/script/core/View.es6: -------------------------------------------------------------------------------- 1 | import '../lib/pixi.extension'; 2 | 3 | export default class view { 4 | constructor(view) { 5 | // 高宽比 6 | let ratiio = 375 / 603; 7 | // 当前高度与 ip6的高度比 8 | let ip6Ratio = 1; 9 | // 更新宽高比 10 | let updateRatio = () => { 11 | // 浏览器的宽高 12 | let cw = document.documentElement.clientWidth, ch = document.documentElement.clientHeight; 13 | // 横屏 14 | if(cw > ch) { 15 | [cw, ch] = [ch, cw]; 16 | } 17 | let curRatio = ch / cw; 18 | if(curRatio !== ratiio) { 19 | ratiio = curRatio; 20 | ip6Ratio = ch / 603; 21 | this.height = this.width * ratiio; 22 | this.app && app.renderer.resize(this.width, this.height); 23 | } 24 | } 25 | // 界面的宽高 26 | this.width = 375; 27 | this.height = 603; 28 | // 按当前浏览器适配 29 | updateRatio(); 30 | // 监听 resize 事件 31 | window.addEventListener("resize", updateRatio); 32 | 33 | const app = new PIXI.Application( 34 | { 35 | width: this.width, 36 | height: this.height, 37 | resolution: 2, 38 | backgroundColor: 0x333333, 39 | view: view 40 | } 41 | ); 42 | 43 | // 挂载属性 44 | Object.assign(this, app); 45 | this.ticker = app.ticker; 46 | 47 | // 原始砖色 48 | this.originClrs = [ 49 | // 红色 50 | 0xe45c5b, 51 | // 绿色 52 | 0x64df6a, 53 | // 蓝色 54 | 0x3d91e1, 55 | // 黄色 56 | 0xffdf7b, 57 | // 紫色 58 | 0xec3ffc 59 | ] 60 | 61 | // 砖块数组 62 | this.tiles = new Array(100); 63 | 64 | // 游戏区域 65 | this.area = new PIXI.Container(); 66 | let emptySprite = PIXI.Sprite.from(PIXI.Texture.EMPTY); 67 | emptySprite.set({width: this.width, height: this.width}); 68 | this.area.addChild(emptySprite); 69 | this.area.set({x: .5, y: this.height - this.width}); 70 | 71 | // 砖块尺寸 72 | this.tileSize = 36.5; 73 | 74 | // 表格尺寸 75 | this.gridWidth = 37.5; 76 | this.gridHeight = 37.5; 77 | 78 | // 表格的行列数 79 | this.col = this.row = 10; 80 | 81 | // X轴中心点 82 | this.centerX = this.width / 2; 83 | 84 | // 游戏单独一个容器 85 | this.game = new PIXI.Container(); 86 | 87 | // 添加到舞台 88 | this.stage.addChild(this.game); 89 | 90 | // 添加到舞台 91 | this.game.addChild(this.area); 92 | 93 | // 默认不显示游戏界面 94 | this.hideGame(); 95 | 96 | // 添加点击事件 97 | this.area.on("ontouchstart" in document ? "tap" : "click", (e) => { 98 | // 暂停不触发事件 99 | if(this.paused === true) return; 100 | let x = e.data.global.x, y = e.data.global.y - this.area.y; 101 | let col = x / this.gridWidth >> 0, row = y / this.gridHeight >> 0; 102 | let position = col + row * 10; 103 | this.event.dispatch("view-tap", position); 104 | }); 105 | 106 | // 关卡信息 107 | this.level = new PIXI.Text( 108 | "关卡:0", 109 | { 110 | fontFamily: "Arial", 111 | fontSize: 16, 112 | fill: 0xffffff 113 | } 114 | ); 115 | this.level.set({top: 16, left: 16}); 116 | 117 | // 目标分数 118 | this.goal = new PIXI.Text( 119 | "目标:0", 120 | { 121 | fontFamily: "Arial", 122 | fontSize: 16, 123 | fill: 0xffffff, 124 | align: "center" 125 | } 126 | ); 127 | 128 | this.goal.set( 129 | { 130 | anchorX: .5, 131 | anchorY: .5, 132 | x: this.centerX, 133 | top: 25 134 | } 135 | ); 136 | 137 | // 当前分数 138 | this.totalLabel = new PIXI.Text( 139 | "0", 140 | { 141 | fontFamily: "Arial", 142 | fontSize: 24, 143 | fill: 0xf7f408, 144 | align: "center" 145 | } 146 | ); 147 | 148 | this.totalLabel.set( 149 | { 150 | anchorX: .5, 151 | anchorY: .5, 152 | x: this.centerX, 153 | top: 50 154 | } 155 | ); 156 | 157 | Reflect.defineProperty( 158 | this, 159 | "total", 160 | { 161 | get: () => this._total || 0, 162 | set: value => { 163 | this._total = value; 164 | this.totalLabel.text = value; 165 | } 166 | } 167 | ); 168 | 169 | // 消除信息 170 | this.cleanInfo = new PIXI.Text( 171 | "", 172 | { 173 | fontFamily: "Arial", 174 | fontSize: 16, 175 | fill: 0xffffff, 176 | align: "center" 177 | } 178 | ); 179 | 180 | this.cleanInfo.set( 181 | { 182 | anchorX: .5, 183 | anchorY: .5, 184 | x: this.centerX, 185 | top: 78 186 | } 187 | ); 188 | 189 | // 奖励信息 190 | this.bountyLabel = new PIXI.Text( 191 | "奖励 ", 192 | { 193 | fontFamily: "Arial", 194 | fontSize: 16, 195 | fill: 0xffffff, 196 | align: "center" 197 | } 198 | ); 199 | this.bountyLabel.set( 200 | { 201 | anchorX: .5, 202 | anchorY: .5, 203 | x: this.centerX, 204 | top: 410 * ip6Ratio, 205 | defaultTop: 410 * ip6Ratio, 206 | renderable: false 207 | } 208 | ); 209 | 210 | // 默认的奖励分数 211 | this._bounty = 2000; 212 | 213 | Reflect.defineProperty( 214 | this, 215 | "bounty", 216 | { 217 | get: () => this._bounty, 218 | set: value => { 219 | this._bounty = value; 220 | this.bountyLabel.text = "奖励 " + value; 221 | } 222 | } 223 | ); 224 | 225 | // 剩余星星信息 226 | this.starInfo = new PIXI.Text( 227 | "", 228 | { 229 | fontFamily: "Arial", 230 | fontSize: 12, 231 | fill: 0xffffff, 232 | align: "center" 233 | } 234 | ); 235 | this.starInfo.set( 236 | { 237 | anchorX: .5, 238 | anchorY: .5, 239 | x: this.centerX, 240 | top: 435 * ip6Ratio, 241 | renderable: false 242 | } 243 | ); 244 | 245 | // 进入关卡前的屏幕信息 246 | this.levelInfo = new PIXI.Container(); 247 | this.levelInfo.title = new PIXI.Text( 248 | "关卡 n", 249 | { 250 | fontFamily: "Arial", 251 | fontSize: 30, 252 | fill: 0xffffff, 253 | align: "center" 254 | } 255 | ); 256 | this.levelInfo.title.set( 257 | { 258 | anchorX: .5, 259 | anchorY: .5, 260 | x: this.centerX, 261 | top: 260 * ip6Ratio 262 | } 263 | ); 264 | this.levelInfo.goal = new PIXI.Text( 265 | "目标分数 xxxx", 266 | { 267 | fontFamily: "Arial", 268 | fontSize: 14, 269 | fill: 0xffffff, 270 | align: "center" 271 | } 272 | ); 273 | 274 | this.levelInfo.goal.set( 275 | { 276 | anchorX: .5, 277 | anchorY: .5, 278 | x: this.centerX, 279 | top: 300 * ip6Ratio 280 | } 281 | ); 282 | 283 | this.levelInfo.addChild( 284 | this.levelInfo.title, 285 | this.levelInfo.goal 286 | ); 287 | 288 | this.game.addChild( 289 | this.level, 290 | this.goal, 291 | this.totalLabel, 292 | this.cleanInfo, 293 | this.bountyLabel, 294 | this.starInfo 295 | ); 296 | 297 | // 生成砖块 Texture 298 | this.generateTileTextures(); 299 | 300 | // 生成星星 Texture 301 | this.generateStarTextures(); 302 | 303 | // 爆星队列 304 | this.bombList = []; 305 | // 加载图片 306 | PIXI.loader 307 | .add( 308 | [ 309 | {name: "red", url: require("../../images/red@2x.png")}, 310 | {name: "green", url: require("../../images/green@2x.png")}, 311 | {name: "blue", url: require("../../images/blue@2x.png")}, 312 | {name: "yellow", url: require("../../images/yellow@2x.png")}, 313 | {name: "purple", url: require("../../images/purple@2x.png")}, 314 | {name: "star_red", url: require("../../images/star_red@2x.png")}, 315 | {name: "star_green", url: require("../../images/star_green@2x.png")}, 316 | {name: "star_blue", url: require("../../images/star_blue@2x.png")}, 317 | {name: "star_yellow", url: require("../../images/star_yellow@2x.png")}, 318 | {name: "star_purple", url: require("../../images/star_purple@2x.png")}, 319 | {name: "pause", url: require("../../images/pause@2x.png")}, 320 | {name: "play", url: require("../../images/play@2x.png")}, 321 | {name: "cover", url: require("../../images/cover.jpg")} 322 | ] 323 | ) 324 | .load( 325 | () => { 326 | ["red", "green", "blue", "yellow", "purple"].forEach( 327 | (clr, index) => { 328 | this.tileTextures[index].baseTexture = PIXI.utils.TextureCache[clr].baseTexture; 329 | this.starTextures[index].baseTexture = PIXI.utils.TextureCache["star_" + clr].baseTexture; 330 | } 331 | ) 332 | // 暂停与播放按钮 333 | let btn = new PIXI.Sprite(PIXI.utils.TextureCache["pause"]); 334 | btn.interactive = true; 335 | btn.on("ontouchstart" in document ? "tap" : "click", () => { 336 | let name = btn.texture.textureCacheIds[0]; 337 | this.event.dispatch("view-press-" + name); 338 | }); 339 | btn.set({top: 12, left: 325}); 340 | this.game.addChild(btn); 341 | this.btn = btn; 342 | // 背景图片 343 | this.stage.addChildAt(new PIXI.Sprite(PIXI.utils.TextureCache["cover"]), 0); 344 | } 345 | ); 346 | } 347 | 348 | // 初始化 349 | init() { 350 | // 开启 ticker 351 | this.ticker.started === false && this.ticker.start(); 352 | // 隐藏奖励信息 353 | this.area.renderable = this.bountyLabel.renderable = this.starInfo.renderable = false; 354 | // 显示游戏界面 355 | this.showGame(); 356 | this.showLevelInfo().then(() => { 357 | // 开启点击 358 | this.area.interactive = true; 359 | // 显示砖块 360 | this.area.renderable = true; 361 | // 做下掉的动画 362 | for(let len =this.tiles.length, i = len - 1; i >= 0; --i) { 363 | let tile = this.tiles[i]; 364 | TweenMax.from( 365 | tile.sprite, 366 | .5, 367 | { 368 | y: "-=" + (600 - Math.random() * this.gridHeight >> 0), 369 | delay: ((len - i) / this.col >> 0) * .05, 370 | ease: Linear.easeNone 371 | } 372 | ) 373 | } 374 | }); 375 | } 376 | 377 | // 销毁 378 | destroy() { 379 | this.ticker.stop(); 380 | // 清空 timer 381 | this.timer.clean(); 382 | // 暂停状态重置 383 | this.paused = false; 384 | // 隐藏游戏界面 385 | this.game.renderable = false; 386 | this.renderer.render(this.stage); 387 | } 388 | 389 | // 显示关卡信息 390 | showLevelInfo() { 391 | this.game.addChild(this.levelInfo); 392 | return new Promise( 393 | (resolve, reject) => { 394 | // 显示当前关卡信息 395 | TweenMax.fromTo( 396 | this.levelInfo, 397 | .6, 398 | {x: this.width}, 399 | {x: 0} 400 | ); 401 | TweenMax.to( 402 | this.levelInfo, 403 | .6, 404 | { 405 | x: -this.width, 406 | delay: 3, 407 | onComplete: () => { 408 | this.levelInfo.parent.removeChild(this.levelInfo); 409 | resolve(); 410 | } 411 | }, 412 | ); 413 | TweenMax.fromTo( 414 | this.levelInfo.goal, 415 | .6, 416 | {x: this.centerX + this.width}, 417 | {x: this.centerX, delay: .6} 418 | ); 419 | TweenMax.to( 420 | this.goal, 421 | .15, 422 | { 423 | alpha: 0, 424 | delay: 1.2, 425 | repeat: 9, 426 | yoyo: true, 427 | ease: Linear.easeNone 428 | } 429 | ); 430 | } 431 | ); 432 | } 433 | 434 | // 生成对应的砖块 435 | generateTileSprite(clr = 0) { 436 | var tile = new PIXI.Sprite(this.tileTextures[clr]); 437 | return tile; 438 | } 439 | 440 | // 生成对应的星星 441 | generateStar(clr = 5) { 442 | var star = new PIXI.Sprite(this.starTextures[clr % 5]); 443 | return star; 444 | } 445 | 446 | // 更改砖块的颜色 447 | updateTileClr(tile, clr) { 448 | if(clr === undefined) return; 449 | tile.sprite.texture = this.tileTextures[clr]; 450 | tile.clr = clr; 451 | } 452 | 453 | // 砖块位置变化 454 | updateTileIndex(tile, index) { 455 | let x = (index % this.col) * this.gridWidth; 456 | let y = (index / this.col >> 0) * this.gridHeight; 457 | tile.index = index; 458 | // 游戏过程,有动画 459 | TweenMax.to( 460 | tile.sprite, 461 | .08, 462 | {x: x, y: y, ease: Linear.easeNone} 463 | ); 464 | } 465 | 466 | // 生成砖块的 Texture 467 | generateTileTextures() { 468 | this.tileTextures = this.originClrs.map( 469 | clr => { 470 | let tile = new PIXI.Graphics() 471 | .beginFill(clr) 472 | .drawRoundedRect(0, 0, this.tileSize, this.tileSize, 6); 473 | return tile.generateCanvasTexture(); 474 | } 475 | ); 476 | } 477 | 478 | // 生成星星的 Texture 479 | generateStarTextures() { 480 | this.starTextures = this.originClrs.map( 481 | clr => { 482 | let star = new PIXI.Graphics() 483 | .beginFill(clr) 484 | .drawPolygon([20, 0, 26.5, 16, 40, 15, 28, 25, 32, 38, 20, 30, 8, 38, 12, 23, 12, 24, 0, 15, 15, 15]); 485 | return star.generateCanvasTexture(); 486 | } 487 | ) 488 | } 489 | 490 | // 更新砖块 491 | update({originIndex, index, clr, removed, score}) { 492 | // 还没有 originIndex 或没有色值,直接不处理 493 | if(originIndex === undefined || clr === undefined) return ; 494 | let tile = this.tiles[originIndex]; 495 | // tile 不存在,生成对应砖块 496 | if(tile === undefined) { 497 | this.tiles[originIndex] = tile = { 498 | sprite: this.generateTileSprite(clr), 499 | clr: clr, 500 | index: index, 501 | score: 0, 502 | originIndex: originIndex, 503 | removed: true 504 | }; 505 | // 定位 506 | this.updateTileIndex(tile); 507 | // 添加到舞台 508 | this.area.addChild(tile.sprite); 509 | } 510 | // tile 存在,判断颜色是否一样 511 | else if(tile.clr !== clr) { 512 | this.updateTileClr(tile, clr); 513 | } 514 | 515 | // 当前索引变化 ----- 表示位置也有变化 516 | if(tile.index !== index) { 517 | this.updateTileIndex(tile, index); 518 | } 519 | 520 | // 设置分数 521 | if(tile.score !== score) { 522 | tile.score = score; 523 | } 524 | 525 | if(tile.removed !== removed) { 526 | // 移除或添加当前节点 527 | true === removed ? this.bomb(tile) : this.area.addChild(tile.sprite); 528 | tile.removed = removed; 529 | } 530 | } 531 | 532 | // 消除色砖前的彩花 533 | bomb(tile) { 534 | // 爆炸锁定 535 | if(this.bombLock === true) { 536 | // 存入数组 537 | this.bombList.push(tile); 538 | return ; 539 | } 540 | this.bombLock = true; 541 | // 砖块的位置 542 | let {index, clr, sprite, sprite: {x, y}} = tile; 543 | // 水平方向 1 向左, -1 向右 544 | let directionX = 1; 545 | for(let i = 0, len = 12; i <= len; ++i) { 546 | let star = this.generateStar(clr); 547 | this.area.addChild(star); 548 | directionX *= -1; 549 | // 统一设置属性 550 | star.set( 551 | { 552 | origin: [star.width / 2, star.height / 2], 553 | top: y, 554 | left: x, 555 | scaleX: .5, 556 | scaleY: .5, 557 | alpha: .9, 558 | // 水平速度 559 | velocityX: (Math.random() * 10 + 1) * directionX, 560 | velocityY: Math.random() * 10 - 20, 561 | accelerationY: Math.random() + 2 562 | } 563 | ); 564 | 565 | 566 | // 喷发星星 567 | TweenMax.to( 568 | star, 569 | 1.5, 570 | { 571 | scaleX: .15, 572 | scaleY: .15, 573 | alpha: .1, 574 | time: .6, 575 | ease: Linear.easeNone, 576 | onComplete: () => star.destroy() 577 | } 578 | ); 579 | } 580 | 581 | // 爆下一个砖块的函数 582 | let bombNextTile = () => { 583 | let nextTile = this.bombList.shift(); 584 | // 解除锁定 585 | this.bombLock = false; 586 | // 删除当前砖块 587 | sprite.parent.removeChild(sprite); 588 | if(nextTile !== undefined) { 589 | this.bomb(nextTile); 590 | } 591 | // 没砖块了 592 | else { 593 | // 游戏过程 594 | if(tile.score > 0) { 595 | this.event.dispatch("view-clean"); 596 | } 597 | // 游戏结束 598 | else { 599 | // 奖励分数不为0,奖励文案上提 600 | if(this.bounty > 0) { 601 | // 将分数均分十份 602 | let bountyStep = this.bounty / 10; 603 | TweenMax.to( 604 | this.bountyLabel, 605 | .6, 606 | { 607 | top: 80, 608 | onComplete: () => { 609 | this.timer.setInterval( 610 | () => { 611 | this.total += bountyStep; 612 | this.bounty -= bountyStep; 613 | if(this.bounty === 0) { 614 | this.timer.delete("bounty"); 615 | this.timer.setTimeout( 616 | () => this.event.dispatch("view-clean-all"), 617 | 1400 618 | ); 619 | } 620 | }, 621 | 80, 622 | "bounty" 623 | ); 624 | } 625 | } 626 | ) 627 | } 628 | // 奖励分数为0,直接结束 629 | else { 630 | this.timer.setTimeout( 631 | () => this.event.dispatch("view-clean-all"), 632 | 1400 633 | ); 634 | } 635 | } 636 | } 637 | } 638 | 639 | // 得分 640 | if(tile.score > 0) { 641 | this.showTileScore(tile); 642 | } 643 | // 扣除奖励 644 | else { 645 | if(this.bounty > 0) { 646 | this.bounty += tile.score; 647 | } 648 | // 没有奖励分数后,真接全爆 649 | else { 650 | return bombNextTile(); 651 | } 652 | } 653 | 654 | // 爆下一个砖块 655 | this.timer.setTimeout(bombNextTile, 120); 656 | } 657 | 658 | // 显示色砖分数 659 | showTileScore(tile) { 660 | let score = new PIXI.Text( 661 | tile.score, 662 | { 663 | fontFamily: "Arial", 664 | fontSize: 32, 665 | fill: 0xffffff, 666 | align: "center" 667 | } 668 | ); 669 | score.set( 670 | { 671 | anchorX: .5, 672 | anchorY: .5, 673 | x: tile.sprite.x, 674 | y: tile.sprite.y 675 | } 676 | ); 677 | TweenMax.to( 678 | score, 679 | .4, 680 | { 681 | x: this.centerX, 682 | y: 603 - this.height - 158, 683 | scaleX: .5, 684 | scaleY: .5, 685 | ease: Linear.easeNone, 686 | onComplete: () => { 687 | score.parent.removeChild(score); 688 | this.total += tile.score; 689 | } 690 | } 691 | ) 692 | this.area.addChild(score); 693 | } 694 | 695 | // 显示连消信息 696 | setCleanInfo(count) { 697 | this.cleanInfo.tween && this.cleanInfo.tween.kill(); 698 | this.cleanInfo.text = `${count}连消 ${count * count * 5}分`; 699 | this.cleanInfo.tween = TweenMax.fromTo( 700 | this.cleanInfo, 701 | .3, 702 | { 703 | alpha: 0 704 | }, 705 | { 706 | alpha: 1, 707 | repeat: 1, 708 | yoyo: true, 709 | repeatDelay: 1 710 | } 711 | ) 712 | } 713 | 714 | // 当前关卡的文案 715 | setLevel(level) { 716 | this.level.text = "关卡:" + level; 717 | this.levelInfo.title.text = "关卡" + level; 718 | } 719 | 720 | // 当前目标分数 721 | setGoal(goal) { 722 | this.goal.text = "目标:" + goal; 723 | this.levelInfo.goal.text = "目标分数 " + goal; 724 | } 725 | 726 | // 显示奖励功能 727 | showBountyInfo(count) { 728 | this.bountyLabel.renderable = this.starInfo.renderable = true; 729 | this.starInfo.text = `剩余 ${count} 个星星`; 730 | // 初始化奖励分数 731 | this.bounty = 2000; 732 | // 位置重置 733 | this.bountyLabel.top = this.bountyLabel.defaultTop; 734 | // area 闪一闪 735 | return new Promise( 736 | (resolve, reject) => { 737 | TweenMax.fromTo( 738 | this.area, 739 | .1, 740 | { 741 | alpha: 1 742 | }, 743 | { 744 | alpha: 0, 745 | yoyo: true, 746 | repeat: 7, 747 | ease: Linear.easeNone, 748 | onComplete: () => resolve() 749 | } 750 | ); 751 | } 752 | ); 753 | } 754 | 755 | 756 | // 不显示游戏界面 757 | hideGame() { 758 | this.game.renderable = false; 759 | } 760 | 761 | // 显示游戏界面 762 | showGame() { 763 | this.game.renderable = true; 764 | } 765 | 766 | // 暂停渲染 767 | pause() { 768 | // this.stop(); 769 | TweenMax.pauseAll(); 770 | this.paused = true; 771 | this.btn.texture = PIXI.utils.TextureCache["play"]; 772 | } 773 | // 恢复渲染 774 | resume() { 775 | // this.start(); 776 | TweenMax.resumeAll(); 777 | this.paused = false; 778 | this.btn.texture = PIXI.utils.TextureCache["pause"]; 779 | } 780 | } -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/PixiPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 0.1.2 3 | * DATE: 2017-06-29 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * PixiPlugin is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | */ 12 | var _gsScope = (typeof module !== "undefined" && module.exports && typeof global !== "undefined") ? global : this || window; 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push(function () { 14 | "use strict"; 15 | 16 | var _numExp = /(\d|\.)+/g, 17 | _relNumExp = /(?:\d|\-\d|\.\d|\-\.\d|\+=\d|\-=\d|\+=.\d|\-=\.\d)+/g, 18 | _colorLookup = {aqua:[0,255,255], 19 | lime:[0,255,0], 20 | silver:[192,192,192], 21 | black:[0,0,0], 22 | maroon:[128,0,0], 23 | teal:[0,128,128], 24 | blue:[0,0,255], 25 | navy:[0,0,128], 26 | white:[255,255,255], 27 | fuchsia:[255,0,255], 28 | olive:[128,128,0], 29 | yellow:[255,255,0], 30 | orange:[255,165,0], 31 | gray:[128,128,128], 32 | purple:[128,0,128], 33 | green:[0,128,0], 34 | red:[255,0,0], 35 | pink:[255,192,203], 36 | cyan:[0,255,255], 37 | transparent:[255,255,255,0]}, 38 | _hue = function(h, m1, m2) { 39 | h = (h < 0) ? h + 1 : (h > 1) ? h - 1 : h; 40 | return ((((h * 6 < 1) ? m1 + (m2 - m1) * h * 6 : (h < 0.5) ? m2 : (h * 3 < 2) ? m1 + (m2 - m1) * (2 / 3 - h) * 6 : m1) * 255) + 0.5) | 0; 41 | }, 42 | /** 43 | * @private Parses a color (like #9F0, #FF9900, rgb(255,51,153) or hsl(108, 50%, 10%)) into an array with 3 elements for red, green, and blue or if "format" parameter is "hsl", it will populate the array with hue, saturation, and lightness values. Or if "format" is "number", it'll return a number like 0xFF0000 instead of an array. If a relative value is found in an hsl() or hsla() string, it will preserve those relative prefixes and all the values in the array will be strings instead of numbers (in all other cases it will be populated with numbers). 44 | * @param {(string|number)} v The value the should be parsed which could be a string like #9F0 or rgb(255,102,51) or rgba(255,0,0,0.5) or it could be a number like 0xFF00CC or even a named color like red, blue, purple, etc. 45 | * @param {(string)} format If "hsl", an hsl() or hsla() value will be returned instead of rgb() or rgba(). Or if "number", then a numeric value will be returned, like 0xFF0000. Default is rgb. 46 | * @return {(array|number)} An array containing red, green, and blue (and optionally alpha) in that order, or if the format parameter was "hsl", the array will contain hue, saturation and lightness (and optionally alpha) in that order. Or if "format" is defined as "number", it'll return a number like 0xFF0000. Always numbers unless there's a relative prefix found in an hsl() or hsla() string and "format" is "hsl". 47 | */ 48 | _parseColor = function(v, format) { 49 | var toHSL = (format === "hsl"), 50 | a, r, g, b, h, s, l, max, min, d, wasHSL; 51 | if (!v) { 52 | a = _colorLookup.black; 53 | } else if (typeof(v) === "number") { 54 | a = [v >> 16, (v >> 8) & 255, v & 255]; 55 | } else { 56 | if (v.charAt(v.length - 1) === ",") { //sometimes a trailing comma is included and we should chop it off (typically from a comma-delimited list of values like a textShadow:"2px 2px 2px blue, 5px 5px 5px rgb(255,0,0)" - in this example "blue," has a trailing comma. We could strip it out inside parseComplex() but we'd need to do it to the beginning and ending values plus it wouldn't provide protection from other potential scenarios like if the user passes in a similar value. 57 | v = v.substr(0, v.length - 1); 58 | } 59 | if (_colorLookup[v]) { 60 | a = _colorLookup[v]; 61 | } else if (v.charAt(0) === "#") { 62 | if (v.length === 4) { //for shorthand like #9F0 63 | r = v.charAt(1); 64 | g = v.charAt(2); 65 | b = v.charAt(3); 66 | v = "#" + r + r + g + g + b + b; 67 | } 68 | v = parseInt(v.substr(1), 16); 69 | a = [v >> 16, (v >> 8) & 255, v & 255]; 70 | } else if (v.substr(0, 3) === "hsl") { 71 | a = wasHSL = v.match(_numExp); 72 | if (!toHSL) { 73 | h = (Number(a[0]) % 360) / 360; 74 | s = Number(a[1]) / 100; 75 | l = Number(a[2]) / 100; 76 | g = (l <= 0.5) ? l * (s + 1) : l + s - l * s; 77 | r = l * 2 - g; 78 | if (a.length > 3) { 79 | a[3] = Number(v[3]); 80 | } 81 | a[0] = _hue(h + 1 / 3, r, g); 82 | a[1] = _hue(h, r, g); 83 | a[2] = _hue(h - 1 / 3, r, g); 84 | } else if (v.indexOf("=") !== -1) { //if relative values are found, just return the raw strings with the relative prefixes in place. 85 | return v.match(_relNumExp); 86 | } 87 | } else { 88 | a = v.match(_numExp) || _colorLookup.transparent; 89 | } 90 | a[0] = Number(a[0]); 91 | a[1] = Number(a[1]); 92 | a[2] = Number(a[2]); 93 | if (a.length > 3) { 94 | a[3] = Number(a[3]); 95 | } 96 | } 97 | if (toHSL && !wasHSL) { 98 | r = a[0] / 255; 99 | g = a[1] / 255; 100 | b = a[2] / 255; 101 | max = Math.max(r, g, b); 102 | min = Math.min(r, g, b); 103 | l = (max + min) / 2; 104 | if (max === min) { 105 | h = s = 0; 106 | } else { 107 | d = max - min; 108 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 109 | h = (max === r) ? (g - b) / d + (g < b ? 6 : 0) : (max === g) ? (b - r) / d + 2 : (r - g) / d + 4; 110 | h *= 60; 111 | } 112 | a[0] = (h + 0.5) | 0; 113 | a[1] = (s * 100 + 0.5) | 0; 114 | a[2] = (l * 100 + 0.5) | 0; 115 | } 116 | return (format === "number") ? (a[0] << 16 | a[1] << 8 | a[2]) : a; 117 | }, 118 | _formatColors = function(s, toHSL) { 119 | var colors = (s + "").match(_colorExp) || [], 120 | charIndex = 0, 121 | parsed = "", 122 | i, color, temp; 123 | if (!colors.length) { 124 | return s; 125 | } 126 | for (i = 0; i < colors.length; i++) { 127 | color = colors[i]; 128 | temp = s.substr(charIndex, s.indexOf(color, charIndex)-charIndex); 129 | charIndex += temp.length + color.length; 130 | color = _parseColor(color, (toHSL ? "hsl" : "rgb")); 131 | if (color.length === 3) { 132 | color.push(1); 133 | } 134 | parsed += temp + (toHSL ? "hsla(" + color[0] + "," + color[1] + "%," + color[2] + "%," + color[3] : "rgba(" + color.join(",")) + ")"; 135 | } 136 | return parsed + s.substr(charIndex); 137 | }, _colorStringFilter, 138 | TweenLite = (_gsScope.GreenSockGlobals || _gsScope).TweenLite, 139 | _colorExp = "(?:\\b(?:(?:rgb|rgba|hsl|hsla)\\(.+?\\))|\\B#(?:[0-9a-f]{3}){1,2}\\b", //we'll dynamically build this Regular Expression to conserve file size. After building it, it will be able to find rgb(), rgba(), # (hexadecimal), and named color values like red, blue, purple, etc. 140 | 141 | _idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0], 142 | _lumR = 0.212671, 143 | _lumG = 0.715160, 144 | _lumB = 0.072169, 145 | 146 | _applyMatrix = function(m, m2) { 147 | var temp = [], 148 | i = 0, 149 | z = 0, 150 | y, x; 151 | for (y = 0; y < 4; y++) { 152 | for (x = 0; x < 5; x++) { 153 | z = (x === 4) ? m[i + 4] : 0; 154 | temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z; 155 | } 156 | i += 5; 157 | } 158 | return temp; 159 | }, 160 | 161 | _setSaturation = function(m, n) { 162 | var inv = 1 - n, 163 | r = inv * _lumR, 164 | g = inv * _lumG, 165 | b = inv * _lumB; 166 | return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m); 167 | }, 168 | 169 | _colorize = function(m, color, amount) { 170 | var c = _parseColor(color), 171 | r = c[0] / 255, 172 | g = c[1] / 255, 173 | b = c[2] / 255, 174 | inv = 1 - amount; 175 | return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m); 176 | }, 177 | 178 | _setHue = function(m, n) { 179 | n *= Math.PI / 180; 180 | var c = Math.cos(n), 181 | s = Math.sin(n); 182 | return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m); 183 | }, 184 | 185 | _setContrast = function(m, n) { 186 | return _applyMatrix([n,0,0,0,0.5 * (1 - n), 0,n,0,0,0.5 * (1 - n), 0,0,n,0,0.5 * (1 - n), 0,0,0,1,0], m); 187 | }, 188 | 189 | _getFilter = function(t, type) { 190 | var filterClass = _gsScope.PIXI.filters[type], 191 | filters = t.filters || [], 192 | i = filters.length, 193 | filter; 194 | if (!filterClass) { 195 | throw("PixiPlugin error: " + type + " isn't present."); 196 | } 197 | while (--i > -1) { 198 | if (filters[i] instanceof filterClass) { 199 | return filters[i]; 200 | } 201 | } 202 | filter = new filterClass(); 203 | if (type === "BlurFilter") { 204 | filter.blur = 0; 205 | } 206 | filters.push(filter); 207 | t.filters = filters; 208 | return filter; 209 | }, 210 | 211 | _addColorMatrixFilterCacheTween = function(p, pg, cache, vars) { //we cache the ColorMatrixFilter components in a _gsColorMatrixFilter object attached to the target object so that it's easy to grab the current value at any time. 212 | pg._addTween(cache, p, cache[p], vars[p], p); 213 | pg._overwriteProps.push(p); 214 | }, 215 | 216 | _applyBrightnessToMatrix = function(brightness, matrix) { 217 | var temp = new _gsScope.PIXI.filters.ColorMatrixFilter(); 218 | temp.matrix = matrix; 219 | temp.brightness(brightness, true); 220 | return temp.matrix; 221 | }, 222 | 223 | _CMFdefaults = {contrast:1, saturation:1, colorizeAmount:0, colorize:"rgb(255,255,255)", hue:0, brightness:1}, 224 | 225 | _parseColorMatrixFilter = function(t, v, pg) { 226 | var filter = _getFilter(t, "ColorMatrixFilter"), 227 | cache = t._gsColorMatrixFilter = t._gsColorMatrixFilter || {contrast:1, saturation:1, colorizeAmount:0, colorize:"rgb(255,255,255)", hue:0, brightness:1}, 228 | combine = v.combineCMF && !("colorMatrixFilter" in v && !v.colorMatrixFilter), 229 | i, matrix, startMatrix; 230 | startMatrix = filter.matrix; 231 | if (v.matrix && v.matrix.length === startMatrix.length) { 232 | matrix = v.matrix; 233 | if (cache.contrast !== 1) { 234 | _addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults); 235 | } 236 | if (cache.hue) { 237 | _addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults); 238 | } 239 | if (cache.brightness !== 1) { 240 | _addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults); 241 | } 242 | if (cache.colorizeAmount) { 243 | _addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults); 244 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults); 245 | } 246 | if (cache.saturation !== 1) { 247 | _addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults); 248 | } 249 | 250 | } else { 251 | matrix = _idMatrix.slice(); 252 | if (v.contrast != null) { 253 | matrix = _setContrast(matrix, Number(v.contrast)); 254 | _addColorMatrixFilterCacheTween("contrast", pg, cache, v); 255 | } else if (cache.contrast !== 1) { 256 | if (combine) { 257 | matrix = _setContrast(matrix, cache.contrast); 258 | } else { 259 | _addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults); 260 | } 261 | } 262 | if (v.hue != null) { 263 | matrix = _setHue(matrix, Number(v.hue)); 264 | _addColorMatrixFilterCacheTween("hue", pg, cache, v); 265 | } else if (cache.hue) { 266 | if (combine) { 267 | matrix = _setHue(matrix, cache.hue); 268 | } else { 269 | _addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults); 270 | } 271 | } 272 | if (v.brightness != null) { 273 | matrix = _applyBrightnessToMatrix(Number(v.brightness), matrix); 274 | _addColorMatrixFilterCacheTween("brightness", pg, cache, v); 275 | } else if (cache.brightness !== 1) { 276 | if (combine) { 277 | matrix = _applyBrightnessToMatrix(cache.brightness, matrix); 278 | } else { 279 | _addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults); 280 | } 281 | } 282 | if (v.colorize != null) { 283 | v.colorizeAmount = ("colorizeAmount" in v) ? Number(v.colorizeAmount) : 1; 284 | matrix = _colorize(matrix, v.colorize, v.colorizeAmount); 285 | _addColorMatrixFilterCacheTween("colorize", pg, cache, v); 286 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, v); 287 | } else if (cache.colorizeAmount) { 288 | if (combine) { 289 | matrix = _colorize(matrix, cache.colorize, cache.colorizeAmount); 290 | } else { 291 | _addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults); 292 | _addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults); 293 | } 294 | } 295 | if (v.saturation != null) { 296 | matrix = _setSaturation(matrix, Number(v.saturation)); 297 | _addColorMatrixFilterCacheTween("saturation", pg, cache, v); 298 | } else if (cache.saturation !== 1) { 299 | if (combine) { 300 | matrix = _setSaturation(matrix, cache.saturation); 301 | } else { 302 | _addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults); 303 | } 304 | } 305 | } 306 | i = matrix.length; 307 | while (--i > -1) { 308 | if (matrix[i] !== startMatrix[i]) { 309 | pg._addTween(startMatrix, i, startMatrix[i], matrix[i], "colorMatrixFilter"); 310 | } 311 | } 312 | pg._overwriteProps.push("colorMatrixFilter"); 313 | }, 314 | 315 | _addColorTween = function(target, p, value, colorSetter, plugin) { 316 | var pt = colorSetter._firstPT = {_next:colorSetter._firstPT, t:target, p:p, proxy:{}, f:(typeof(target[p]) === "function")}; 317 | pt.proxy[p] = "rgb(" + _parseColor(!pt.f ? target[p] : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ]()).join(",") + ")"; 318 | plugin._addTween(pt.proxy, p, "get", ((typeof(value) === "number") ? "rgb(" + _parseColor(value, false).join(",") + ")" : value), p, null, null, _colorStringFilter); 319 | }, 320 | 321 | //to improve performance, when a color is sensed, we hijack the setRatio() method of the plugin instance with a new function that this method spits back. This is a special method that handles parsing color values on-the-fly and turns them into numeric values which PixiJS requires. In other words, instead of "rgb(255, 0, 0)", PixiJS wants 0xFF0000. This also works with hsl() values. 322 | _buildColorSetter = function(tween, plugin) { 323 | var setRatio = plugin.setRatio, //save the original (super) setRatio() function 324 | func = function(v) { 325 | var pt = func._firstPT, 326 | val; 327 | setRatio.call(plugin, v); 328 | while (pt) { 329 | val = _parseColor(pt.proxy[pt.p], "number"); 330 | if (pt.f) { 331 | pt.t[pt.p](val); 332 | } else { 333 | pt.t[pt.p] = val; 334 | } 335 | pt = pt._next; 336 | } 337 | if (func.graphics) { //in order for PixiJS to actually redraw GraphicsData, we've gotta increment the "dirty" and "clearDirty" values. If we don't do this, the values will be tween properly, but not rendered. 338 | func.graphics.dirty++; 339 | func.graphics.clearDirty++; 340 | } 341 | }; 342 | plugin.setRatio = func; 343 | return func; 344 | }, 345 | 346 | 347 | _colorProps = {tint:1, lineColor:1, fillColor:1}, 348 | _xyContexts = "position,scale,skew,pivot,anchor,tilePosition,tileScale".split(","), 349 | _contexts = {x:"position", y:"position", tileX:"tilePosition", tileY:"tilePosition"}, 350 | _colorMatrixFilterProps = {colorMatrixFilter:1, saturation:1, contrast:1, hue:1, colorize:1, colorizeAmount:1, brightness:1, combineCMF:1}, 351 | _DEG2RAD = Math.PI / 180, 352 | _degreesToRadians = function(value) { 353 | return (typeof(value) === "string" && value.charAt(1) === "=") ? value.substr(0, 2) + (parseFloat(value.substr(2)) * _DEG2RAD) : value * _DEG2RAD; 354 | }, i, p; 355 | 356 | //context setup... 357 | for (i = 0; i < _xyContexts.length; i++) { 358 | p = _xyContexts[i]; 359 | _contexts[p + "X"] = p; 360 | _contexts[p + "Y"] = p; 361 | } 362 | 363 | //color parsing setup... 364 | for (p in _colorLookup) { 365 | _colorExp += "|" + p + "\\b"; 366 | } 367 | _colorExp = new RegExp(_colorExp+")", "gi"); 368 | _colorStringFilter = function(a) { 369 | var combined = a[0] + " " + a[1], 370 | toHSL; 371 | _colorExp.lastIndex = 0; 372 | if (_colorExp.test(combined)) { 373 | toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1); 374 | a[0] = _formatColors(a[0], toHSL); 375 | a[1] = _formatColors(a[1], toHSL); 376 | } 377 | }; 378 | 379 | if (!TweenLite.defaultStringFilter) { 380 | TweenLite.defaultStringFilter = _colorStringFilter; 381 | } 382 | 383 | var PixiPlugin = _gsScope._gsDefine.plugin({ 384 | propName: "pixi", 385 | priority: 0, 386 | API: 2, 387 | global: true, 388 | version: "0.1.2", 389 | 390 | init: function (target, values, tween, index) { 391 | if (!target instanceof _gsScope.PIXI.DisplayObject) { 392 | return false; 393 | } 394 | var context, axis, value, colorMatrix, filter, p, padding, colorSetter, i, data; 395 | for (p in values) { 396 | context = _contexts[p]; 397 | value = values[p]; 398 | if (typeof(value) === "function") { 399 | value = value(index || 0, target); 400 | } 401 | if (context) { 402 | axis = (p.charAt(p.length-1).toLowerCase().indexOf("x") !== -1) ? "x" : "y"; 403 | this._addTween(target[context], axis, target[context][axis], (context === "skew") ? _degreesToRadians(value) : value, p); 404 | } else if (p === "scale" || p === "anchor" || p === "pivot" || p === "tileScale") { 405 | this._addTween(target[p], "x", target[p].x, value, p + "X"); 406 | this._addTween(target[p], "y", target[p].y, value, p + "Y"); 407 | } else if (p === "rotation") { //PIXI expects rotation in radians, but as a convenience we let folks define it in degrees and we do the conversion. 408 | this._addTween(target, p, target.rotation, _degreesToRadians(value), p); 409 | 410 | } else if (_colorMatrixFilterProps[p]) { 411 | if (!colorMatrix) { 412 | _parseColorMatrixFilter(target, values.colorMatrixFilter || values, this); 413 | colorMatrix = true; 414 | } 415 | } else if (p === "blur" || p === "blurX" || p === "blurY" || p === "blurPadding") { 416 | filter = _getFilter(target, "BlurFilter"); 417 | this._addTween(filter, p, filter[p], value, p); 418 | if (values.blurPadding !== 0) { 419 | padding = values.blurPadding || Math.max(filter[p], value) * 2; 420 | i = target.filters.length; 421 | while (--i > -1) { 422 | target.filters[i].padding = Math.max(target.filters[i].padding, padding); //if we don't expand the padding on all the filters, it can look clipped. 423 | } 424 | } 425 | } else if (_colorProps[p]) { 426 | if (!colorSetter) { 427 | colorSetter = _buildColorSetter(tween, this); 428 | } 429 | if ((p === "lineColor" || p === "fillColor") && target instanceof _gsScope.PIXI.Graphics) { 430 | data = target.graphicsData; 431 | i = data.length; 432 | while (--i > -1) { 433 | _addColorTween(data[i], p, value, colorSetter, this); 434 | } 435 | colorSetter.graphics = target; 436 | } else { 437 | _addColorTween(target, p, value, colorSetter, this); 438 | } 439 | } else { 440 | this._addTween(target, p, target[p], value, p); 441 | } 442 | this._overwriteProps.push(p); 443 | } 444 | return true; 445 | } 446 | }); 447 | 448 | PixiPlugin.colorProps = _colorProps; 449 | PixiPlugin.parseColor = _parseColor; 450 | PixiPlugin.formatColors = _formatColors; 451 | PixiPlugin.colorStringFilter = _colorStringFilter; 452 | 453 | 454 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 455 | 456 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 457 | (function(name) { 458 | "use strict"; 459 | var getGlobal = function() { 460 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 461 | }; 462 | if (typeof(module) !== "undefined" && module.exports) { //node 463 | require("../TweenLite.js"); 464 | module.exports = getGlobal(); 465 | } else if (typeof(define) === "function" && define.amd) { //AMD 466 | define(["TweenLite"], getGlobal); 467 | } 468 | }("PixiPlugin")); -------------------------------------------------------------------------------- /src/script/lib/gsap/plugins/BezierPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * VERSION: 1.3.8 3 | * DATE: 2017-06-19 4 | * UPDATES AND DOCS AT: http://greensock.com 5 | * 6 | * @license Copyright (c) 2008-2017, GreenSock. All rights reserved. 7 | * This work is subject to the terms at http://greensock.com/standard-license or for 8 | * Club GreenSock members, the software agreement that was issued with your membership. 9 | * 10 | * @author: Jack Doyle, jack@greensock.com 11 | **/ 12 | var _gsScope = (typeof(module) !== "undefined" && module.exports && typeof(global) !== "undefined") ? global : this || window; //helps ensure compatibility with AMD/RequireJS and CommonJS/Node 13 | (_gsScope._gsQueue || (_gsScope._gsQueue = [])).push( function() { 14 | 15 | "use strict"; 16 | 17 | var _RAD2DEG = 180 / Math.PI, 18 | _r1 = [], 19 | _r2 = [], 20 | _r3 = [], 21 | _corProps = {}, 22 | _globals = _gsScope._gsDefine.globals, 23 | Segment = function(a, b, c, d) { 24 | if (c === d) { //if c and d match, the final autoRotate value could lock at -90 degrees, so differentiate them slightly. 25 | c = d - (d - b) / 1000000; 26 | } 27 | if (a === b) { //if a and b match, the starting autoRotate value could lock at -90 degrees, so differentiate them slightly. 28 | b = a + (c - a) / 1000000; 29 | } 30 | this.a = a; 31 | this.b = b; 32 | this.c = c; 33 | this.d = d; 34 | this.da = d - a; 35 | this.ca = c - a; 36 | this.ba = b - a; 37 | }, 38 | _correlate = ",x,y,z,left,top,right,bottom,marginTop,marginLeft,marginRight,marginBottom,paddingLeft,paddingTop,paddingRight,paddingBottom,backgroundPosition,backgroundPosition_y,", 39 | cubicToQuadratic = function(a, b, c, d) { 40 | var q1 = {a:a}, 41 | q2 = {}, 42 | q3 = {}, 43 | q4 = {c:d}, 44 | mab = (a + b) / 2, 45 | mbc = (b + c) / 2, 46 | mcd = (c + d) / 2, 47 | mabc = (mab + mbc) / 2, 48 | mbcd = (mbc + mcd) / 2, 49 | m8 = (mbcd - mabc) / 8; 50 | q1.b = mab + (a - mab) / 4; 51 | q2.b = mabc + m8; 52 | q1.c = q2.a = (q1.b + q2.b) / 2; 53 | q2.c = q3.a = (mabc + mbcd) / 2; 54 | q3.b = mbcd - m8; 55 | q4.b = mcd + (d - mcd) / 4; 56 | q3.c = q4.a = (q3.b + q4.b) / 2; 57 | return [q1, q2, q3, q4]; 58 | }, 59 | _calculateControlPoints = function(a, curviness, quad, basic, correlate) { 60 | var l = a.length - 1, 61 | ii = 0, 62 | cp1 = a[0].a, 63 | i, p1, p2, p3, seg, m1, m2, mm, cp2, qb, r1, r2, tl; 64 | for (i = 0; i < l; i++) { 65 | seg = a[ii]; 66 | p1 = seg.a; 67 | p2 = seg.d; 68 | p3 = a[ii+1].d; 69 | 70 | if (correlate) { 71 | r1 = _r1[i]; 72 | r2 = _r2[i]; 73 | tl = ((r2 + r1) * curviness * 0.25) / (basic ? 0.5 : _r3[i] || 0.5); 74 | m1 = p2 - (p2 - p1) * (basic ? curviness * 0.5 : (r1 !== 0 ? tl / r1 : 0)); 75 | m2 = p2 + (p3 - p2) * (basic ? curviness * 0.5 : (r2 !== 0 ? tl / r2 : 0)); 76 | mm = p2 - (m1 + (((m2 - m1) * ((r1 * 3 / (r1 + r2)) + 0.5) / 4) || 0)); 77 | } else { 78 | m1 = p2 - (p2 - p1) * curviness * 0.5; 79 | m2 = p2 + (p3 - p2) * curviness * 0.5; 80 | mm = p2 - (m1 + m2) / 2; 81 | } 82 | m1 += mm; 83 | m2 += mm; 84 | 85 | seg.c = cp2 = m1; 86 | if (i !== 0) { 87 | seg.b = cp1; 88 | } else { 89 | seg.b = cp1 = seg.a + (seg.c - seg.a) * 0.6; //instead of placing b on a exactly, we move it inline with c so that if the user specifies an ease like Back.easeIn or Elastic.easeIn which goes BEYOND the beginning, it will do so smoothly. 90 | } 91 | 92 | seg.da = p2 - p1; 93 | seg.ca = cp2 - p1; 94 | seg.ba = cp1 - p1; 95 | 96 | if (quad) { 97 | qb = cubicToQuadratic(p1, cp1, cp2, p2); 98 | a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]); 99 | ii += 4; 100 | } else { 101 | ii++; 102 | } 103 | 104 | cp1 = m2; 105 | } 106 | seg = a[ii]; 107 | seg.b = cp1; 108 | seg.c = cp1 + (seg.d - cp1) * 0.4; //instead of placing c on d exactly, we move it inline with b so that if the user specifies an ease like Back.easeOut or Elastic.easeOut which goes BEYOND the end, it will do so smoothly. 109 | seg.da = seg.d - seg.a; 110 | seg.ca = seg.c - seg.a; 111 | seg.ba = cp1 - seg.a; 112 | if (quad) { 113 | qb = cubicToQuadratic(seg.a, cp1, seg.c, seg.d); 114 | a.splice(ii, 1, qb[0], qb[1], qb[2], qb[3]); 115 | } 116 | }, 117 | _parseAnchors = function(values, p, correlate, prepend) { 118 | var a = [], 119 | l, i, p1, p2, p3, tmp; 120 | if (prepend) { 121 | values = [prepend].concat(values); 122 | i = values.length; 123 | while (--i > -1) { 124 | if (typeof( (tmp = values[i][p]) ) === "string") if (tmp.charAt(1) === "=") { 125 | values[i][p] = prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)); //accommodate relative values. Do it inline instead of breaking it out into a function for speed reasons 126 | } 127 | } 128 | } 129 | l = values.length - 2; 130 | if (l < 0) { 131 | a[0] = new Segment(values[0][p], 0, 0, values[0][p]); 132 | return a; 133 | } 134 | for (i = 0; i < l; i++) { 135 | p1 = values[i][p]; 136 | p2 = values[i+1][p]; 137 | a[i] = new Segment(p1, 0, 0, p2); 138 | if (correlate) { 139 | p3 = values[i+2][p]; 140 | _r1[i] = (_r1[i] || 0) + (p2 - p1) * (p2 - p1); 141 | _r2[i] = (_r2[i] || 0) + (p3 - p2) * (p3 - p2); 142 | } 143 | } 144 | a[i] = new Segment(values[i][p], 0, 0, values[i+1][p]); 145 | return a; 146 | }, 147 | bezierThrough = function(values, curviness, quadratic, basic, correlate, prepend) { 148 | var obj = {}, 149 | props = [], 150 | first = prepend || values[0], 151 | i, p, a, j, r, l, seamless, last; 152 | correlate = (typeof(correlate) === "string") ? ","+correlate+"," : _correlate; 153 | if (curviness == null) { 154 | curviness = 1; 155 | } 156 | for (p in values[0]) { 157 | props.push(p); 158 | } 159 | //check to see if the last and first values are identical (well, within 0.05). If so, make seamless by appending the second element to the very end of the values array and the 2nd-to-last element to the very beginning (we'll remove those segments later) 160 | if (values.length > 1) { 161 | last = values[values.length - 1]; 162 | seamless = true; 163 | i = props.length; 164 | while (--i > -1) { 165 | p = props[i]; 166 | if (Math.abs(first[p] - last[p]) > 0.05) { //build in a tolerance of +/-0.05 to accommodate rounding errors. 167 | seamless = false; 168 | break; 169 | } 170 | } 171 | if (seamless) { 172 | values = values.concat(); //duplicate the array to avoid contaminating the original which the user may be reusing for other tweens 173 | if (prepend) { 174 | values.unshift(prepend); 175 | } 176 | values.push(values[1]); 177 | prepend = values[values.length - 3]; 178 | } 179 | } 180 | _r1.length = _r2.length = _r3.length = 0; 181 | i = props.length; 182 | while (--i > -1) { 183 | p = props[i]; 184 | _corProps[p] = (correlate.indexOf(","+p+",") !== -1); 185 | obj[p] = _parseAnchors(values, p, _corProps[p], prepend); 186 | } 187 | i = _r1.length; 188 | while (--i > -1) { 189 | _r1[i] = Math.sqrt(_r1[i]); 190 | _r2[i] = Math.sqrt(_r2[i]); 191 | } 192 | if (!basic) { 193 | i = props.length; 194 | while (--i > -1) { 195 | if (_corProps[p]) { 196 | a = obj[props[i]]; 197 | l = a.length - 1; 198 | for (j = 0; j < l; j++) { 199 | r = (a[j+1].da / _r2[j] + a[j].da / _r1[j]) || 0; 200 | _r3[j] = (_r3[j] || 0) + r * r; 201 | } 202 | } 203 | } 204 | i = _r3.length; 205 | while (--i > -1) { 206 | _r3[i] = Math.sqrt(_r3[i]); 207 | } 208 | } 209 | i = props.length; 210 | j = quadratic ? 4 : 1; 211 | while (--i > -1) { 212 | p = props[i]; 213 | a = obj[p]; 214 | _calculateControlPoints(a, curviness, quadratic, basic, _corProps[p]); //this method requires that _parseAnchors() and _setSegmentRatios() ran first so that _r1, _r2, and _r3 values are populated for all properties 215 | if (seamless) { 216 | a.splice(0, j); 217 | a.splice(a.length - j, j); 218 | } 219 | } 220 | return obj; 221 | }, 222 | _parseBezierData = function(values, type, prepend) { 223 | type = type || "soft"; 224 | var obj = {}, 225 | inc = (type === "cubic") ? 3 : 2, 226 | soft = (type === "soft"), 227 | props = [], 228 | a, b, c, d, cur, i, j, l, p, cnt, tmp; 229 | if (soft && prepend) { 230 | values = [prepend].concat(values); 231 | } 232 | if (values == null || values.length < inc + 1) { throw "invalid Bezier data"; } 233 | for (p in values[0]) { 234 | props.push(p); 235 | } 236 | i = props.length; 237 | while (--i > -1) { 238 | p = props[i]; 239 | obj[p] = cur = []; 240 | cnt = 0; 241 | l = values.length; 242 | for (j = 0; j < l; j++) { 243 | a = (prepend == null) ? values[j][p] : (typeof( (tmp = values[j][p]) ) === "string" && tmp.charAt(1) === "=") ? prepend[p] + Number(tmp.charAt(0) + tmp.substr(2)) : Number(tmp); 244 | if (soft) if (j > 1) if (j < l - 1) { 245 | cur[cnt++] = (a + cur[cnt-2]) / 2; 246 | } 247 | cur[cnt++] = a; 248 | } 249 | l = cnt - inc + 1; 250 | cnt = 0; 251 | for (j = 0; j < l; j += inc) { 252 | a = cur[j]; 253 | b = cur[j+1]; 254 | c = cur[j+2]; 255 | d = (inc === 2) ? 0 : cur[j+3]; 256 | cur[cnt++] = tmp = (inc === 3) ? new Segment(a, b, c, d) : new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c); 257 | } 258 | cur.length = cnt; 259 | } 260 | return obj; 261 | }, 262 | _addCubicLengths = function(a, steps, resolution) { 263 | var inc = 1 / resolution, 264 | j = a.length, 265 | d, d1, s, da, ca, ba, p, i, inv, bez, index; 266 | while (--j > -1) { 267 | bez = a[j]; 268 | s = bez.a; 269 | da = bez.d - s; 270 | ca = bez.c - s; 271 | ba = bez.b - s; 272 | d = d1 = 0; 273 | for (i = 1; i <= resolution; i++) { 274 | p = inc * i; 275 | inv = 1 - p; 276 | d = d1 - (d1 = (p * p * da + 3 * inv * (p * ca + inv * ba)) * p); 277 | index = j * resolution + i - 1; 278 | steps[index] = (steps[index] || 0) + d * d; 279 | } 280 | } 281 | }, 282 | _parseLengthData = function(obj, resolution) { 283 | resolution = resolution >> 0 || 6; 284 | var a = [], 285 | lengths = [], 286 | d = 0, 287 | total = 0, 288 | threshold = resolution - 1, 289 | segments = [], 290 | curLS = [], //current length segments array 291 | p, i, l, index; 292 | for (p in obj) { 293 | _addCubicLengths(obj[p], a, resolution); 294 | } 295 | l = a.length; 296 | for (i = 0; i < l; i++) { 297 | d += Math.sqrt(a[i]); 298 | index = i % resolution; 299 | curLS[index] = d; 300 | if (index === threshold) { 301 | total += d; 302 | index = (i / resolution) >> 0; 303 | segments[index] = curLS; 304 | lengths[index] = total; 305 | d = 0; 306 | curLS = []; 307 | } 308 | } 309 | return {length:total, lengths:lengths, segments:segments}; 310 | }, 311 | 312 | 313 | 314 | BezierPlugin = _gsScope._gsDefine.plugin({ 315 | propName: "bezier", 316 | priority: -1, 317 | version: "1.3.8", 318 | API: 2, 319 | global:true, 320 | 321 | //gets called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run. 322 | init: function(target, vars, tween) { 323 | this._target = target; 324 | if (vars instanceof Array) { 325 | vars = {values:vars}; 326 | } 327 | this._func = {}; 328 | this._mod = {}; 329 | this._props = []; 330 | this._timeRes = (vars.timeResolution == null) ? 6 : parseInt(vars.timeResolution, 10); 331 | var values = vars.values || [], 332 | first = {}, 333 | second = values[0], 334 | autoRotate = vars.autoRotate || tween.vars.orientToBezier, 335 | p, isFunc, i, j, prepend; 336 | 337 | this._autoRotate = autoRotate ? (autoRotate instanceof Array) ? autoRotate : [["x","y","rotation",((autoRotate === true) ? 0 : Number(autoRotate) || 0)]] : null; 338 | for (p in second) { 339 | this._props.push(p); 340 | } 341 | 342 | i = this._props.length; 343 | while (--i > -1) { 344 | p = this._props[i]; 345 | 346 | this._overwriteProps.push(p); 347 | isFunc = this._func[p] = (typeof(target[p]) === "function"); 348 | first[p] = (!isFunc) ? parseFloat(target[p]) : target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ](); 349 | if (!prepend) if (first[p] !== values[0][p]) { 350 | prepend = first; 351 | } 352 | } 353 | this._beziers = (vars.type !== "cubic" && vars.type !== "quadratic" && vars.type !== "soft") ? bezierThrough(values, isNaN(vars.curviness) ? 1 : vars.curviness, false, (vars.type === "thruBasic"), vars.correlate, prepend) : _parseBezierData(values, vars.type, first); 354 | this._segCount = this._beziers[p].length; 355 | 356 | if (this._timeRes) { 357 | var ld = _parseLengthData(this._beziers, this._timeRes); 358 | this._length = ld.length; 359 | this._lengths = ld.lengths; 360 | this._segments = ld.segments; 361 | this._l1 = this._li = this._s1 = this._si = 0; 362 | this._l2 = this._lengths[0]; 363 | this._curSeg = this._segments[0]; 364 | this._s2 = this._curSeg[0]; 365 | this._prec = 1 / this._curSeg.length; 366 | } 367 | 368 | if ((autoRotate = this._autoRotate)) { 369 | this._initialRotations = []; 370 | if (!(autoRotate[0] instanceof Array)) { 371 | this._autoRotate = autoRotate = [autoRotate]; 372 | } 373 | i = autoRotate.length; 374 | while (--i > -1) { 375 | for (j = 0; j < 3; j++) { 376 | p = autoRotate[i][j]; 377 | this._func[p] = (typeof(target[p]) === "function") ? target[ ((p.indexOf("set") || typeof(target["get" + p.substr(3)]) !== "function") ? p : "get" + p.substr(3)) ] : false; 378 | } 379 | p = autoRotate[i][2]; 380 | this._initialRotations[i] = (this._func[p] ? this._func[p].call(this._target) : this._target[p]) || 0; 381 | this._overwriteProps.push(p); 382 | } 383 | } 384 | this._startRatio = tween.vars.runBackwards ? 1 : 0; //we determine the starting ratio when the tween inits which is always 0 unless the tween has runBackwards:true (indicating it's a from() tween) in which case it's 1. 385 | return true; 386 | }, 387 | 388 | //called each time the values should be updated, and the ratio gets passed as the only parameter (typically it's a value between 0 and 1, but it can exceed those when using an ease like Elastic.easeOut or Back.easeOut, etc.) 389 | set: function(v) { 390 | var segments = this._segCount, 391 | func = this._func, 392 | target = this._target, 393 | notStart = (v !== this._startRatio), 394 | curIndex, inv, i, p, b, t, val, l, lengths, curSeg; 395 | if (!this._timeRes) { 396 | curIndex = (v < 0) ? 0 : (v >= 1) ? segments - 1 : (segments * v) >> 0; 397 | t = (v - (curIndex * (1 / segments))) * segments; 398 | } else { 399 | lengths = this._lengths; 400 | curSeg = this._curSeg; 401 | v *= this._length; 402 | i = this._li; 403 | //find the appropriate segment (if the currently cached one isn't correct) 404 | if (v > this._l2 && i < segments - 1) { 405 | l = segments - 1; 406 | while (i < l && (this._l2 = lengths[++i]) <= v) { } 407 | this._l1 = lengths[i-1]; 408 | this._li = i; 409 | this._curSeg = curSeg = this._segments[i]; 410 | this._s2 = curSeg[(this._s1 = this._si = 0)]; 411 | } else if (v < this._l1 && i > 0) { 412 | while (i > 0 && (this._l1 = lengths[--i]) >= v) { } 413 | if (i === 0 && v < this._l1) { 414 | this._l1 = 0; 415 | } else { 416 | i++; 417 | } 418 | this._l2 = lengths[i]; 419 | this._li = i; 420 | this._curSeg = curSeg = this._segments[i]; 421 | this._s1 = curSeg[(this._si = curSeg.length - 1) - 1] || 0; 422 | this._s2 = curSeg[this._si]; 423 | } 424 | curIndex = i; 425 | //now find the appropriate sub-segment (we split it into the number of pieces that was defined by "precision" and measured each one) 426 | v -= this._l1; 427 | i = this._si; 428 | if (v > this._s2 && i < curSeg.length - 1) { 429 | l = curSeg.length - 1; 430 | while (i < l && (this._s2 = curSeg[++i]) <= v) { } 431 | this._s1 = curSeg[i-1]; 432 | this._si = i; 433 | } else if (v < this._s1 && i > 0) { 434 | while (i > 0 && (this._s1 = curSeg[--i]) >= v) { } 435 | if (i === 0 && v < this._s1) { 436 | this._s1 = 0; 437 | } else { 438 | i++; 439 | } 440 | this._s2 = curSeg[i]; 441 | this._si = i; 442 | } 443 | t = ((i + (v - this._s1) / (this._s2 - this._s1)) * this._prec) || 0; 444 | } 445 | inv = 1 - t; 446 | 447 | i = this._props.length; 448 | while (--i > -1) { 449 | p = this._props[i]; 450 | b = this._beziers[p][curIndex]; 451 | val = (t * t * b.da + 3 * inv * (t * b.ca + inv * b.ba)) * t + b.a; 452 | if (this._mod[p]) { 453 | val = this._mod[p](val, target); 454 | } 455 | if (func[p]) { 456 | target[p](val); 457 | } else { 458 | target[p] = val; 459 | } 460 | } 461 | 462 | if (this._autoRotate) { 463 | var ar = this._autoRotate, 464 | b2, x1, y1, x2, y2, add, conv; 465 | i = ar.length; 466 | while (--i > -1) { 467 | p = ar[i][2]; 468 | add = ar[i][3] || 0; 469 | conv = (ar[i][4] === true) ? 1 : _RAD2DEG; 470 | b = this._beziers[ar[i][0]]; 471 | b2 = this._beziers[ar[i][1]]; 472 | 473 | if (b && b2) { //in case one of the properties got overwritten. 474 | b = b[curIndex]; 475 | b2 = b2[curIndex]; 476 | 477 | x1 = b.a + (b.b - b.a) * t; 478 | x2 = b.b + (b.c - b.b) * t; 479 | x1 += (x2 - x1) * t; 480 | x2 += ((b.c + (b.d - b.c) * t) - x2) * t; 481 | 482 | y1 = b2.a + (b2.b - b2.a) * t; 483 | y2 = b2.b + (b2.c - b2.b) * t; 484 | y1 += (y2 - y1) * t; 485 | y2 += ((b2.c + (b2.d - b2.c) * t) - y2) * t; 486 | 487 | val = notStart ? Math.atan2(y2 - y1, x2 - x1) * conv + add : this._initialRotations[i]; 488 | 489 | if (this._mod[p]) { 490 | val = this._mod[p](val, target); //for modProps 491 | } 492 | 493 | if (func[p]) { 494 | target[p](val); 495 | } else { 496 | target[p] = val; 497 | } 498 | } 499 | } 500 | } 501 | } 502 | }), 503 | p = BezierPlugin.prototype; 504 | 505 | 506 | BezierPlugin.bezierThrough = bezierThrough; 507 | BezierPlugin.cubicToQuadratic = cubicToQuadratic; 508 | BezierPlugin._autoCSS = true; //indicates that this plugin can be inserted into the "css" object using the autoCSS feature of TweenLite 509 | BezierPlugin.quadraticToCubic = function(a, b, c) { 510 | return new Segment(a, (2 * b + a) / 3, (2 * b + c) / 3, c); 511 | }; 512 | 513 | BezierPlugin._cssRegister = function() { 514 | var CSSPlugin = _globals.CSSPlugin; 515 | if (!CSSPlugin) { 516 | return; 517 | } 518 | var _internals = CSSPlugin._internals, 519 | _parseToProxy = _internals._parseToProxy, 520 | _setPluginRatio = _internals._setPluginRatio, 521 | CSSPropTween = _internals.CSSPropTween; 522 | _internals._registerComplexSpecialProp("bezier", {parser:function(t, e, prop, cssp, pt, plugin) { 523 | if (e instanceof Array) { 524 | e = {values:e}; 525 | } 526 | plugin = new BezierPlugin(); 527 | var values = e.values, 528 | l = values.length - 1, 529 | pluginValues = [], 530 | v = {}, 531 | i, p, data; 532 | if (l < 0) { 533 | return pt; 534 | } 535 | for (i = 0; i <= l; i++) { 536 | data = _parseToProxy(t, values[i], cssp, pt, plugin, (l !== i)); 537 | pluginValues[i] = data.end; 538 | } 539 | for (p in e) { 540 | v[p] = e[p]; //duplicate the vars object because we need to alter some things which would cause problems if the user plans to reuse the same vars object for another tween. 541 | } 542 | v.values = pluginValues; 543 | pt = new CSSPropTween(t, "bezier", 0, 0, data.pt, 2); 544 | pt.data = data; 545 | pt.plugin = plugin; 546 | pt.setRatio = _setPluginRatio; 547 | if (v.autoRotate === 0) { 548 | v.autoRotate = true; 549 | } 550 | if (v.autoRotate && !(v.autoRotate instanceof Array)) { 551 | i = (v.autoRotate === true) ? 0 : Number(v.autoRotate); 552 | v.autoRotate = (data.end.left != null) ? [["left","top","rotation",i,false]] : (data.end.x != null) ? [["x","y","rotation",i,false]] : false; 553 | } 554 | if (v.autoRotate) { 555 | if (!cssp._transform) { 556 | cssp._enableTransforms(false); 557 | } 558 | data.autoRotate = cssp._target._gsTransform; 559 | data.proxy.rotation = data.autoRotate.rotation || 0; 560 | cssp._overwriteProps.push("rotation"); 561 | } 562 | plugin._onInitTween(data.proxy, v, cssp._tween); 563 | return pt; 564 | }}); 565 | }; 566 | 567 | p._mod = function(lookup) { 568 | var op = this._overwriteProps, 569 | i = op.length, 570 | val; 571 | while (--i > -1) { 572 | val = lookup[op[i]]; 573 | if (val && typeof(val) === "function") { 574 | this._mod[op[i]] = val; 575 | } 576 | } 577 | }; 578 | 579 | p._kill = function(lookup) { 580 | var a = this._props, 581 | p, i; 582 | for (p in this._beziers) { 583 | if (p in lookup) { 584 | delete this._beziers[p]; 585 | delete this._func[p]; 586 | i = a.length; 587 | while (--i > -1) { 588 | if (a[i] === p) { 589 | a.splice(i, 1); 590 | } 591 | } 592 | } 593 | } 594 | a = this._autoRotate; 595 | if (a) { 596 | i = a.length; 597 | while (--i > -1) { 598 | if (lookup[a[i][2]]) { 599 | a.splice(i, 1); 600 | } 601 | } 602 | } 603 | return this._super._kill.call(this, lookup); 604 | }; 605 | 606 | }); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); } 607 | 608 | //export to AMD/RequireJS and CommonJS/Node (precursor to full modular build system coming at a later date) 609 | (function(name) { 610 | "use strict"; 611 | var getGlobal = function() { 612 | return (_gsScope.GreenSockGlobals || _gsScope)[name]; 613 | }; 614 | if (typeof(module) !== "undefined" && module.exports) { //node 615 | require("../TweenLite.js"); 616 | module.exports = getGlobal(); 617 | } else if (typeof(define) === "function" && define.amd) { //AMD 618 | define(["TweenLite"], getGlobal); 619 | } 620 | }("BezierPlugin")); 621 | --------------------------------------------------------------------------------