Checkpoint on animations.
authordsc <david.schoonover@gmail.com>
Sun, 12 Dec 2010 11:24:12 +0000 (03:24 -0800)
committerdsc <david.schoonover@gmail.com>
Sun, 12 Dec 2010 11:24:12 +0000 (03:24 -0800)
src/Y/types/array.cjs
src/Y/types/string.cjs
src/Y/utils.cjs
src/evt.cjs
src/ezl/layer.cjs
src/ezl/loop/fx.cjs [moved from src/ezl/loop/animation.cjs with 79% similarity]
src/ezl/loop/index.cjs
src/tanks/game.cjs
src/tanks/thing/bullet.cjs
src/tanks/thing/thing.cjs
www/deps.html

index 0bd0b4f..375d78e 100644 (file)
@@ -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(){
index 5b26886..993d4c5 100644 (file)
@@ -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.
index cf0a5e0..9933e47 100644 (file)
@@ -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));
             };
     });
index 010fc87..7981385 100644 (file)
@@ -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 */
index 19d5396..1f75fd9 100644 (file)
@@ -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) );
         
     },
     
similarity index 79%
rename from src/ezl/loop/animation.cjs
rename to src/ezl/loop/fx.cjs
index 46589ea..8cfa22b 100644 (file)
@@ -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;
     };
     
 });
index cd02e22..f530e1b 100644 (file)
@@ -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
 });
index d21ef32..af5cc82 100644 (file)
@@ -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;
+    },
+    
     
     
     /**
index 1bd529e..404c5bf 100644 (file)
@@ -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;
     },
     
index 8c29cf9..efd59a6 100644 (file)
@@ -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;
     },
     
index 6a71653..a5a00ab 100644 (file)
 <script src="build/ezl/math/vec.js" type="text/javascript"></script>
 <script src="build/ezl/loop/cooldown.js" type="text/javascript"></script>
 <script src="build/ezl/loop/eventloop.js" type="text/javascript"></script>
+<script src="build/evt.js" type="text/javascript"></script>
 <script src="build/ezl/loc/loc.js" type="text/javascript"></script>
 <script src="build/ezl/math/rect.js" type="text/javascript"></script>
 <script src="build/ezl/math/line.js" type="text/javascript"></script>
-<script src="build/ezl/loop.js" type="text/javascript"></script>
 <script src="build/ezl/math.js" type="text/javascript"></script>
+<script src="build/ezl/loop/fx.js" type="text/javascript"></script>
+<script src="build/ezl/loop.js" type="text/javascript"></script>
 <script src="build/ezl/loc/boundingbox.js" type="text/javascript"></script>
 <script src="build/ezl/loc/square.js" type="text/javascript"></script>
 <script src="build/ezl/loc.js" type="text/javascript"></script>
@@ -41,7 +43,6 @@
 <script src="build/ezl.js" type="text/javascript"></script>
 <script src="build/jquery.hotkeys.min.js" type="text/javascript"></script>
 <script src="build/tanks/globals.js" type="text/javascript"></script>
-<script src="build/evt.js" type="text/javascript"></script>
 <script src="build/ezl/util/binaryheap.js" type="text/javascript"></script>
 <script src="build/ezl/util/astar.js" type="text/javascript"></script>
 <script src="build/tanks/config.js" type="text/javascript"></script>