From: dsc Date: Sun, 12 Dec 2010 11:24:12 +0000 (-0800) Subject: Checkpoint on animations. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=53b51cb4456ada8128ab2b1a087ec24bf943e7dd;p=tanks.git Checkpoint on animations. --- diff --git a/src/Y/types/array.cjs b/src/Y/types/array.cjs index 0bd0b4f..375d78e 100644 --- a/src/Y/types/array.cjs +++ b/src/Y/types/array.cjs @@ -17,12 +17,17 @@ YCollection.subclass('YArray', function(YArray){ // Add YArray to the things that count as arrays type.isArray.types.push(YArray); + var newYArray = YArray.instantiate.bind(YArray); + + mixin(YArray, { donor:_Array, + names:'indexOf lastIndexOf shift pop join'.split(' ') }); mixin(YArray, { donor:_Array, chain:true, names:'push unshift sort splice reverse'.split(' ') }); - mixin(YArray, { donor:_Array, wrap:YArray.instantiate.bind(YArray), - names:'map forEach filter slice'.split(' ') }); - mixin(YArray, { donor:_Array, - names:'reduce some every indexOf lastIndexOf shift pop join'.split(' ') }); + mixin(YArray, { donor:_Array, fnFirst:true, wrap:newYArray, + names:'map forEach filter'.split(' ') }); + mixin(YArray, { donor:_Array, fnFirst:true, names:['reduce', 'some', 'every'] }); + mixin(YArray, { donor:_Array, wrap:newYArray, names:['slice'] }); + this['init'] = @@ -35,10 +40,12 @@ YCollection.subclass('YArray', function(YArray){ return new YArray(this._o.slice(0)); }; + var size = this['size'] = function size(){ return this._o.length; }; + Object.defineProperty(this, 'length', { 'get':size }); this['toString'] = function toString(){ diff --git a/src/Y/types/string.cjs b/src/Y/types/string.cjs index 5b26886..993d4c5 100644 --- a/src/Y/types/string.cjs +++ b/src/Y/types/string.cjs @@ -25,6 +25,9 @@ YCollection.subclass('YString', function(YString){ return s; } + function size(){ return this._o.length; } + Object.defineProperty(this, 'length', { 'get':size }); + core.extend(this, { 'init' : function init(o){ if (!o) o = ""; @@ -35,6 +38,8 @@ YCollection.subclass('YString', function(YString){ return YString(this._o); }, + 'size' : size, + /** * As Array.slice -- replaces `howMany` elements starting at `idx` * with the concatenation of the rest of the arguments. diff --git a/src/Y/utils.cjs b/src/Y/utils.cjs index cf0a5e0..9933e47 100644 --- a/src/Y/utils.cjs +++ b/src/Y/utils.cjs @@ -29,7 +29,8 @@ var defaults = { 'names' : null, 'override' : true, 'wrap' : false, - 'chain' : false + 'chain' : false, + 'fnFirst' : false }; function mixin(o, options){ var opt = core.extend({}, defaults, options), Donor = opt.donor @@ -43,7 +44,9 @@ function mixin(o, options){ var fn = proto[name]; if ( isFunction(fn) && (opt.override || !(name in target)) ) target[name] = function(){ - var r = fn.apply(this._o || target, arguments); + var r, A = arguments; + if (opt.fnFirst) A[0] = Function.toFunction(A[0]); + r = fn.apply(this._o || target, A); return (opt.chain ? this : (opt.wrap ? opt.wrap(r) : r)); }; }); diff --git a/src/evt.cjs b/src/evt.cjs index 010fc87..7981385 100644 --- a/src/evt.cjs +++ b/src/evt.cjs @@ -215,7 +215,7 @@ function Class(className, Parent, members){ Class.__super__ = Object; Class.__emitter__ = new Emitter(Class); Class.fn = Class.prototype; -Class.fn.__class__ = Class; +Class.fn.__class__ = Class.fn.constructor = Class; Class.className = Class.fn.className = "Class"; /* Class Methods */ diff --git a/src/ezl/layer.cjs b/src/ezl/layer.cjs index 19d5396..1f75fd9 100644 --- a/src/ezl/layer.cjs +++ b/src/ezl/layer.cjs @@ -6,6 +6,7 @@ var undefined , loc = require('ezl/loc') , Loc = loc.Loc , BoundingBox = loc.BoundingBox +, Animation = require('ezl/loop/fx').Animation , CONTEXT_ATTRS = Y([ 'globalAlpha', 'globalCompositeOperation', @@ -20,13 +21,15 @@ exports['Layer'] = Y.subclass('Layer', { _cssClasses : 'ezl layer', - canvas : null, + canvas : null, - parent : null, - children : null, + parent : null, + children : null, - ctx : null, - dirty : true, + dirty : true, + ctx : null, + animActive : null, + animQueue : null, layerWidth : 0, canvasWidth : 0, @@ -53,7 +56,9 @@ Y.subclass('Layer', { /// Setup /// init : function init(){ - this.children = new Y.YArray(); + this.children = new Y.YArray(); + this.animActive = new Y.YArray(); + this.animQueue = new Y.YArray(); this.loc = new Loc(0,0); this.negBleed = new Loc(0,0); @@ -77,6 +82,8 @@ Y.subclass('Layer', { this.layer[0].layer = this.canvas[0].layer = this; + + this.tick = this.tick.bind(this); }, /// Scene Graph Heirarchy /// @@ -500,7 +507,55 @@ Y.subclass('Layer', { return this; }, - animate : function animate(props, options) { + /** + * @param {Number} duration Duration (ms) of animation. + * @param {Object} props Object whose keys are the property-names to animate + * paired with the target value. Animated properties can also be relative. + * If a value is supplied with a leading += or -= sequence of characters, + * then the target value is computed by adding or subtracting the given + * number from the current value of the property. + * @param {Object} [options] Animation options: + * * {Number} [delay=0] Milliseconds to wait before beginning animation. + * * {Boolean} [queue=true] Indicates whether to place the animation + * in the effects queue to run when other animations have finished. + * If false, the animation will begin after `delay` milliseconds; + * likewise, if `delay` is non-zero `queue` defaults to `false` + * instead. + * * {String|Function|Object} [easing='linear'] Name of easing function + * used to calculate each step value, or a function, or a map of + * properties to either of the above. + */ + animate : function animate(duration, props, options) { + options = Y.extend({ + 'delay' : 0, + 'queue' : undefined, + 'easing' : 'linear', + 'complete' : null, + 'step' : null + }, options); + + if ( options.queue === undefined ) + options.queue = !options.delay; + + var A = new Animation(this, duration, props, options); + if ( options.queue && this.animActive.length ) + this.animQueue.push(A); + else + this.animActive.push(A.start(NOW)); // XXX: Need another way to pass NOW in + return this; + }, + + tick : function tick(elapsed, now){ + if (elapsed instanceof Event) { + var d = evt.data; + now = d.now; + elapsed = d.elapsed; + } + this.animActive = this.animActive.filter(function(anim){ + return anim.tick(elapsed, now); + }); + if ( !this.animActive.length && this.animQueue.length ) + this.animActive.push( this.animQueue.shift().start(now) ); }, diff --git a/src/ezl/loop/animation.cjs b/src/ezl/loop/fx.cjs similarity index 79% rename from src/ezl/loop/animation.cjs rename to src/ezl/loop/fx.cjs index 46589ea..8cfa22b 100644 --- a/src/ezl/loop/animation.cjs +++ b/src/ezl/loop/fx.cjs @@ -13,6 +13,16 @@ var Y = require('Y').Y */ Easing = exports['Easing'] = { + 'lookup' : function lookupEasing(name, prop){ + if ( Y.isFunction(name) ) + return name; + if (name in Easing) + return Easing[name]; + if ( !(Y.isPlainObject(name) && prop in name) ) + throw new Error('Unknown easing function '+name+'!'); + return lookupEasing(name[prop]); + }, + 'linear' : function linearEasing( p, elapsed, start, span, duration ) { return start + span * p; }, @@ -24,20 +34,6 @@ exports['Easing'] = { , -lookupEasing = -exports['lookupEasing'] = -function lookupEasing(name, prop){ - if ( Y.isFunction(name) ) - return name; - if (name in Easing) - return Easing[name]; - if ( !(Y.isPlainObject(name) && prop in name) ) - throw new Error('Unknown easing function '+name+'!'); - return lookupEasing(name[prop]); -} -, - - Fx = exports['Fx'] = Y.subclass('Fx', function setupFx(Fx){ @@ -69,14 +65,16 @@ Y.subclass('Fx', function setupFx(Fx){ this['start'] = function start(now){ - if (this.running) return; + if (this.running) return this; this.startTime = now || new Date().getTime(); this.running = true; + return this; }; this['stop'] = function stop(){ this.running = false; + return this; }; this['tick'] = @@ -84,10 +82,15 @@ Y.subclass('Fx', function setupFx(Fx){ if (!this.running) return false; - var p = (now - this.startTime) / this.duration + var p = Math.max(1, (now - this.startTime) / this.duration) , v = this.cur = this.start + this.easing(p, 0, this.span, this.duration) ; Y.attr(this.obj, this.prop, ( this.unit !== null ? v+this.unit : v )); + if (p === 1) { + this.running = false; + return false; + } + return true; }; }) @@ -140,29 +143,45 @@ new evt.Class('Animation', function setupAnimation(Animation){ this.fx = Y(this.props) .reduce(function(fx, val, prop){ - var easing = lookupEasing(options.easing, prop); + var easing = Easing.lookup(options.easing, prop); return fx.push(new Fx(obj, duration, prop, val, easing)); }, Y([]) ); - // TODO: Handle Queuing -- should happen in target objects who provide the API + this.tick = this.tick.bind(this); }; this['start'] = function start(now){ - if (this.running) return; + if (this.running) return this; this.started = now || new Date().getTime(); this.running = true; this.waiting = !!this.options.delay; + return this; + }; + + this['stop'] = + function stop(){ + this.running = false; + return this; }; + // Runs in the context of the Animation + function tickFx(fx, idx){ + return fx.tick(this.elapsed, this.now); + } + this['tick'] = function tick(elapsed, now){ + this.now = now; + this.elapsed = elapsed; + if (this.waiting && (now < this.started+this.options.delay)) return true; - this.waiting = false; - return true; + this.fx = this.fx.filter(tickFx, this); + + return !!this.fx.length; }; }); diff --git a/src/ezl/loop/index.cjs b/src/ezl/loop/index.cjs index cd02e22..f530e1b 100644 --- a/src/ezl/loop/index.cjs +++ b/src/ezl/loop/index.cjs @@ -1,7 +1,12 @@ -var Y = require('Y').Y; +var Y = require('Y').Y +, fx = require('ezl/loop/fx') +; Y.extend(exports, { 'EventLoop' : require('ezl/loop/eventloop').EventLoop, 'Cooldown' : require('ezl/loop/cooldown').Cooldown, - 'FpsSparkline' : require('ezl/loop/fps').FpsSparkline + 'FpsSparkline' : require('ezl/loop/fps').FpsSparkline, + 'Animation' : fx.Animation, + 'Easing' : fx.Easing, + 'Fx' : fx.Fx }); diff --git a/src/tanks/game.cjs b/src/tanks/game.cjs index d21ef32..af5cc82 100644 --- a/src/tanks/game.cjs +++ b/src/tanks/game.cjs @@ -4,6 +4,7 @@ var Y = require('Y').Y , Event = require('Y/modules/y.event').Event , EventLoop = require('ezl/loop').EventLoop +, Circle = require('ezl/shape').Circle , config = require('tanks/config') , map = require('tanks/map') @@ -28,6 +29,7 @@ Y.subclass('Game', { this.active = new Y.YArray(); this.units = new Y.YArray(); this.bullets = new Y.YArray(); + this.animations = new Y.YArray(); this.viewport = $(viewport || GRID_ELEMENT); this.loop = new EventLoop(this, FRAME_RATE); @@ -45,7 +47,7 @@ Y.subclass('Game', { Thing.addEventListener('create', this.addUnit); - Thing.addEventListener('destroy', this.killUnit); + Thing.addEventListener('destroy', this.explodeUnit); this.addEventListener('tick', this.tick); @@ -90,6 +92,7 @@ Y.subclass('Game', { this.active.invoke('updateCooldowns', NOW); this.active.invoke('act'); + this.animations = this.animations.filter(this.tickAnimations, this); this.draw(); @@ -104,6 +107,9 @@ Y.subclass('Game', { }, + tickAnimations : function tickAnimations(animation){ + return animation.tick(ELAPSED, NOW); + }, @@ -166,6 +172,24 @@ Y.subclass('Game', { return agent; }, + explodeUnit : function explodeUnit(unit){ + if (unit instanceof Event) + unit = unit.instance; + + this.killUnit(unit); + var loc = unit.loc + , size = Math.max(unit.width, unit.height) + , exp = new Circle(3) + .position(loc.x,loc.y) + .fill('#980011') + .animate(1000, { 'radius':size }) + .appendTo(this.level); + + this.animations.push(exp); + + return unit; + }, + /** diff --git a/src/tanks/thing/bullet.cjs b/src/tanks/thing/bullet.cjs index 1bd529e..404c5bf 100644 --- a/src/tanks/thing/bullet.cjs +++ b/src/tanks/thing/bullet.cjs @@ -54,7 +54,7 @@ Thing.subclass('Bullet', { remove : function remove(){ if (this.shape) this.shape.remove(); if (this.tline) this.tline.remove(); - if (this.game) this.game.killUnit(this); + // if (this.game) this.game.killUnit(this); return this; }, diff --git a/src/tanks/thing/thing.cjs b/src/tanks/thing/thing.cjs index 8c29cf9..efd59a6 100644 --- a/src/tanks/thing/thing.cjs +++ b/src/tanks/thing/thing.cjs @@ -83,7 +83,7 @@ new evt.Class('Thing', { remove : function remove(){ if (this.shape) this.shape.remove(); - if (this.game) this.game.killUnit(this); + // if (this.game) this.game.killUnit(this); return this; }, diff --git a/www/deps.html b/www/deps.html index 6a71653..a5a00ab 100644 --- a/www/deps.html +++ b/www/deps.html @@ -23,11 +23,13 @@ + - + + @@ -41,7 +43,6 @@ -