}
function set(o, key, value, def){
- if ( o && key !== undefined ){
- if (def === undefined)
- def = o[key];
+ if ( o && key !== undefined && (value !== undefined || def !== undefined) )
o[key] = (value !== undefined ? value : def);
- }
return o;
}
exports['extend'] = extend;
exports['accessors'] = accessors;
-exports['slice'] = slice;
+exports['slice'] = slice;
\ No newline at end of file
this.target = target || this;
if (parent && parent !== target)
- self.parent = parent;
+ this.parent = parent;
this.decorate(this); // i be so tricky tricky (it binds all methods)
if (target) this.decorate(target);
-var Y = require('Y/y').Y // I *think* this is safe
+var Y = require('Y/y').Y
, del = require('Y/delegate')
, type = require('Y/type')
, op = require('Y/op')
if (acc.indexOf(v) === -1)
acc.push(v);
return acc;
- }, new YArray(), this );
+ }, new YArray(), this);
};
+ YArray.flatten = flatten;
+ this['flatten'] = Y(flatten).methodize();
+ function flatten(A){
+ return A.reduce(function(flat, v){
+ if ( type.isArray(v) )
+ return flat.extend( flatten(v) );
+ else
+ return flat.push(v);
+ }, new YArray(), this);
+ }
+
this['zip'] =
function zip(){
var sequences = new YArray(slice.call(arguments)).unshift(this._o)
, BoundingBox = loc.BoundingBox
, Animation = require('ezl/loop/fx').Animation
,
-CONTEXT_ATTRS = Y([
- 'globalAlpha', 'globalCompositeOperation',
- 'strokeStyle', 'fillStyle',
- 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
- 'shadowOffsetX', 'shadowOffsetY', 'shadowBlur', 'shadowColor'
-])
+CONTEXT_ATTRS = Y('globalAlpha globalCompositeOperation strokeStyle fillStyle lineWidth lineCap lineJoin miterLimit shadowOffsetX shadowOffsetY shadowBlur shadowColor'.split(' ')),
+FAUX_ACCESSORS = Y('width height position stroke fill origin rotate scale translate title'.split(' '))
+,_X = 0, _Y = 1
,
+
+
Layer =
exports['Layer'] =
Y.subclass('Layer', {
/// Attributes ///
+ // set : function set(key, value, def){
+ // if ( key === undefined || (value === undefined && def === undefined) )
+ // return this;
+ //
+ // value = (value !== undefined ? value : def);
+ // if ( FAUX_ACCESSORS.has(key) )
+ // this[key](value);
+ // else
+ // this[key] = value;
+ //
+ // return this;
+ // },
+
attr : Y.attr.methodize(),
size : function size(w,h){
if (x === undefined && y === undefined)
return this.layer.position();
- if ( Y.isPlainObject(x) ){
- y = x.y; x = x.x;
+ if ( x instanceof Array ) {
+ y = x[_Y]; x = x[_X];
+ } else if ( Y.isPlainObject(x) ){
+ y = ('top' in x ? x.top : x.y);
+ x = ('left' in x ? x.left : x.x);
}
var bbox = this.boundingBox.relocate(x,y);
if (arguments.length === 0)
return o.absolute(this.layerWidth, this.layerHeight);
+ if ( x instanceof Array ) {
+ y = x[_Y]; x = x[_X];
+ }
o.x = x;
o.y = y;
return this._applyTransforms();
if (arguments.length === 0)
return ts;
+ if ( sx instanceof Array ) {
+ sy = sx[_Y]; sx = sx[_X];
+ }
ts.x = sx;
ts.y = sy;
this.dirty = true;
/**
* Translates draw calls by (x,y) within this layer only. This allows you to
- * functionally move the coordinate system of the layer.
+ * functionally move the origin of the coordinate system for this layer.
*/
translate : function translate(tx,ty){
var tt = this.transform.translate;
if (arguments.length === 0)
return tt;
+ if ( tx instanceof Array ) {
+ ty = tx[_Y]; tx = tx[_X];
+ }
tt.x = tx;
tt.y = ty;
this.dirty = true;
return this;
},
+ /**
+ * @private
+ */
_applyTransforms : function _applyTransforms(){
var t = this.transform, tfns = [];
* Reduce "up", across this and parents, inner to outer:
* acc = fn.call(context || node, acc, node)
*/
- reduceup : function reduceup(acc, fn, context){
- // if ( Y.isFunction(fn) )
- // acc = fn.call(context || this, acc, this);
- // else
- // acc = this[fn].call(context || this, acc, this);
+ reduceup : function reduceup(fn, acc, context){
acc = fn.call(context || this, acc, this);
- return ( this.parent ? this.parent.reduceup(acc, fn, context) : acc );
+ return ( this.parent ? this.parent.reduceup(fn, acc, context) : acc );
},
/**
* Reduce "down", across this and children, depth-first:
* acc = fn.call(context || node, acc, node)
*/
- reduce : function reduce(acc, fn, context){
+ reduce : function reduce(fn, acc, context){
acc = fn.call(context || this, acc, this);
- return this.children.reduce(acc, fn, context);
+ return this.children.reduce(function(acc, child){
+ return child.reduce(fn, acc, context);
+ }, acc);
},
* * {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.
+ * * {Function} [complete] Function called on animation completion.
+ * * {Function} [step] Function called after each step of the animation.
*/
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 )
+ if ( A.options.queue && this.animActive.length )
this.animQueue.push(A);
else
this.animActive.push(A.start(NOW)); // XXX: Need another way to pass NOW in
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 )
+
+ var childrenRunning = this.children.invoke('tick', elapsed, now).some(Y.op.I)
+ , running = !!this.animActive.length;
+ if ( !running && this.animQueue.length )
this.animActive.push( this.animQueue.shift().start(now) );
+ return childrenRunning || running;
},
/// Debuggging ///
/// Misc ///
toString : function toString(){
- var pos = (this.layer ? this.position() : {top:NaN, left:NaN});
+ var pos = ((this.layer && this.layer.parent()[0]) ? this.position() : {top:NaN, left:NaN});
return this.className+'['+pos.left+','+pos.top+']( children='+this.children.size()+' )';
}
});
var Y = require('Y').Y
, evt = require('evt')
+, math = require('ezl/math')
,
/**
return lookupEasing(name[prop]);
},
- 'linear' : function linearEasing( p, elapsed, start, span, duration ) {
+ 'linear' : function linearEasing( p, start, span, elapsed, duration ) {
return start + span * p;
},
- 'swing' : function swingEasing( p, elapsed, start, span, duration ) {
+ 'swing' : function swingEasing( p, start, span, elapsed, duration ) {
return ((-Math.cos(p*Math.PI)/2) + 0.5) * span + start;
}
}
Fx =
exports['Fx'] =
-Y.subclass('Fx', function setupFx(Fx){
+new Y.Class('Fx', null, function setupFx(Fx){
var VAL_PATTERN = /^([+\-]=)?([\d+.\-]+)(.*)$/;
this['init'] =
this.duration = duration;
this.prop = prop;
this.unit = unit;
- this.easing = Easing[easing];
+ this.easing = easing;
- this.start = this.cur = Y.attr(obj, prop);
+ this.startVal = this.cur = this.getVal();
if (rel) {
- this.span = val*(rel === '-=' ? -1 : 1);
- this.finish = this.start + this.span;
+ this.spanVal = val*(rel === '-=' ? -1 : 1);
+ this.finishVal = this.startVal + this.spanVal;
} else {
- this.span = val - this.start;
- this.finish = val;
+ this.spanVal = val - this.startVal;
+ this.finishVal = val;
}
};
+ this['getVal'] =
+ function getVal(){
+ var attr = Y.attr(this.obj, this.prop);
+ if ( Y.isFunction(attr) )
+ attr = attr.call(this.obj);
+ return attr;
+ };
+
+ this['setVal'] =
+ function setVal(v){
+ var attr = Y.attr(this.obj, this.prop);
+ if ( Y.isFunction(attr) )
+ attr.call(this.obj, v);
+ else
+ Y.attr(this.obj, this.prop, v);
+ return v;
+ };
+
this['start'] =
function start(now){
if (this.running) return this;
if (!this.running)
return false;
- 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 ));
+ var p = math.clamp((now - this.startTime) / this.duration, 0, 1)
+ , v = this.cur = this.startVal + this.easing(p, 0, this.spanVal, elapsed, this.duration) ;
+ this.setVal( this.unit !== null ? v+this.unit : v );
if (p === 1) {
this.running = false;
Animation =
exports['Animation'] =
-new evt.Class('Animation', function setupAnimation(Animation){
+new evt.Class('Animation', null, function setupAnimation(Animation){
Y.extend(this, {
'started': 0,
'running': false
* * {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.
+ * * {Function} [complete] Function called on animation completion.
+ * * {Function} [step] Function called after each step of the animation.
*/
this['init'] =
- function initAnimation(obj, duration, props, options){
+ function initAnimation(obj, duration, props, opt){
this.obj = obj;
this.duration = duration;
this.props = props;
- this.options = options = Y.extend({}, defOptions, options);
- if ( !('queue' in options) )
- options.queue = !options.delay;
+ this.options = opt = Y.extend({}, defOptions, opt);
+ if ( opt.queue === undefined )
+ opt.queue = !opt.delay;
+ if ( !Y.isFunction(opt.step) )
+ delete opt.step;
+ if ( !Y.isFunction(opt.complete) )
+ delete opt.complete;
this.fx = Y(this.props)
.reduce(function(fx, val, prop){
- var easing = Easing.lookup(options.easing, prop);
+ var easing = Easing.lookup(opt.easing, prop);
return fx.push(new Fx(obj, duration, prop, val, easing));
}, Y([]) );
this.started = now || new Date().getTime();
this.running = true;
this.waiting = !!this.options.delay;
+ if (!this.waiting) this.fx.invoke('start', now);
return this;
};
this['tick'] =
function tick(elapsed, now){
+ var obj = this.obj
+ , opt = this.options
+ , step = opt.step
+ , complete = opt.complete
+ ;
this.now = now;
this.elapsed = elapsed;
- if (this.waiting && (now < this.started+this.options.delay))
- return true;
+ if (this.waiting)
+ if (now < this.started+opt.delay)
+ return true;
+ else
+ this.fx.invoke('start', now);
this.waiting = false;
this.fx = this.fx.filter(tickFx, this);
+ if ( step ) step.call(obj, obj, this);
+
+ var moreToDo = !!this.fx.length;
+ if (!moreToDo) {
+ this.running = false;
+ if ( complete ) complete.call(obj, obj, this);
+ }
- return !!this.fx.length;
+ return moreToDo;
};
});
_Object['getOwnPropertyDescriptor'] =
function getOwnPropertyDescriptor(o, prop){
- if (typeof o !== "object" || o === null)
+ var t = typeof o;
+ if (o === null || !(t === "object" || t === "function"))
throw new TypeError("Object.getOwnPropertyDescriptor called on non-object: "+o);
if ( !hasOwn.call(o,prop) )
--- /dev/null
+var Y = require('Y').Y
+, Circle = require('ezl/shape').Circle
+,
+
+Explosion =
+exports['Explosion'] =
+Circle.subclass('Explosion', {
+ _cssClasses : 'ezl layer circle explosion',
+ fillStyle : '#980011',
+
+ ringSizes : [ 0.6, 0.3 ],
+ ringColors : ["#EC5B38", "#F1D950"],
+
+ /**
+ * @param {Number} start Starting diameter value.
+ * @param {Number} end Ending diameter value.
+ * @param {Number} duration Duration (ms) of the animation.
+ */
+ init : function initExplosion(start, end, duration){
+ start = 0.5 * start;
+ end = 0.5 * end;
+
+ Circle.init.call(this, start);
+ this.animate(duration, {'radius':end}, { 'complete':this.remove.bind(this) });
+
+ var prev = this
+ , options = { 'step':this.updateRing }
+ , sizes = (end < 10 ? this.ringSizes.slice(0,1) : this.ringSizes)
+ ;
+
+ sizes.forEach(function(p, idx){
+ var ringStart = p * start
+ , ringEnd = p * end
+ , color = this.ringColors[idx]
+ ;
+ prev = new Circle(ringStart)
+ .fill(color)
+ .animate(duration, { 'radius':ringEnd }, options)
+ .appendTo(prev);
+ // prev.layer.css({ 'top':'50%', 'left':'50%' });
+ }, this);
+ },
+
+ updateRing : function updateRing(shape, anim){
+ var el = shape.layer;
+ el.css({
+ 'top' : '50%',
+ 'left' : '50%',
+ 'margin-top' : -0.5 * el.height(),
+ 'margin-left' : -0.5 * el.width()
+ });
+ }
+
+});
--- /dev/null
+var Y = require('Y').Y;
+
+Y.extend(exports, {
+ 'Explosion' : require('tanks/fx/explosion').Explosion
+});
\ No newline at end of file
Thing.addEventListener('create', this.addUnit);
- Thing.addEventListener('destroy', this.explodeUnit);
+ Thing.addEventListener('destroy', this.killUnit);
this.addEventListener('tick', this.tick);
},
tickAnimations : function tickAnimations(animation){
- return animation.tick(ELAPSED, NOW);
+ var running = animation.tick(ELAPSED, NOW);
+ if (!running)
+ animation.remove();
+ return running;
+ },
+
+ addAnimation : function addAnimation(animation){
+ this.animations.push(animation);
+ this.level.append(animation);
+ return animation;
},
addUnit : function addUnit(unit, col,row){
if (unit instanceof Event)
- unit = unit.instance;
+ unit = unit.trigger;
unit.game = this;
killUnit : function killUnit(unit){
if (unit instanceof Event)
- unit = unit.instance;
+ unit = unit.trigger;
// console.log('killUnit(', unit, ')');
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;
- },
-
/**
var Y = require('Y').Y
, Rect = require('ezl/shape').Rect
+, Thing = require('tanks/thing/thing').Thing
, Tank = require('tanks/thing/tank').Tank
, PlayerTank = require('tanks/thing/player').PlayerTank
, PathMap = require('tanks/map/pathmap').PathMap
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
var Y = require('Y').Y
+, math = require('ezl/math')
, shape = require('ezl/shape')
, Wall = require('tanks/map/wall').Wall
, Thing = require('tanks/thing/thing').Thing
, Trajectory = require('tanks/map/trajectory').Trajectory
+, Explosion = require('tanks/fx/explosion').Explosion
, Line = shape.Line
, Circle = shape.Circle
,
Thing.subclass('Bullet', {
traceTrajectories : true,
+ explDuration : 750,
+ explStart : 1.0,
+ explEnd : 1.5,
+
+
/**
* @param {tanks.Unit} owner
},
remove : function remove(){
- if (this.shape) this.shape.remove();
- if (this.tline) this.tline.remove();
- // if (this.game) this.game.killUnit(this);
+ if (this.shape) { this.shape.remove(); }
+ if (this.tline) { this.tline.remove(); delete this.tline; }
return this;
},
},
onCollide : function onCollide(evt){
- var agent = evt.trigger
+ var unit = evt.trigger
, trj = this.trajectory;
- if ( agent instanceof Wall && trj.bounces <= this.bounceLimit )
+ if ( this.dead || unit instanceof Wall && trj.bounces <= this.bounceLimit )
return;
- agent.dealDamage(this.stats.power, this);
+ unit.dealDamage(this.stats.power, this);
this.destroy();
trj.halt = true;
+
+ // Trigger explosion
+ var loc = this.loc, uloc = unit.loc
+ , x = math.lerp(0.5,loc.x,uloc.x)
+ , y = math.lerp(0.5,loc.y,uloc.y)
+
+ , bsize = Math.max(this.width, this.height)
+ , asize = Math.max(unit.width, unit.height)
+
+ , duration = this.explDuration
+ , start = bsize, end = bsize
+ ;
+
+ if ( unit.dead && asize >= bsize )
+ end = asize;
+
+ start = this.explStart * start;
+ end = this.explEnd * end;
+ this.game.addAnimation(
+ new Explosion(start, end, duration)
+ .position(x,y) );
},
render : function render(parent){
- if (this.tline) { this.tline.remove(); delete this.tline; }
- if (this.shape) this.shape.remove();
+ this.remove();
if (tanks.config.values.pathing.traceTrajectories) {
var t = this.trajectory;
}
var loc = this.loc;
- this.shape = new Circle(3)
+ this.shape = new Circle(this.width/2)
.position(loc.x, loc.y)
.fill('#FFF6AE')
.appendTo( parent );
stats: {
hp : 1, // health
- move : 1.0, // move speed (squares/sec)
+ move : 0.75, // move speed (squares/sec)
rotate : HALF_PI, // rotation speed (radians/sec)
power : 1, // attack power
- /*
- ai : {
- path : 1.0, // calculate a path to enemy
- dodge : 1.0, // dodge an incoming bullet
- shootIncoming : 0.5, // shoot down incoming bullet
- shootEnemy : 0.75 // shoot at enemy tank if in range
- */
-
this['act'] =
function act(allotment){
var ai = this.ai;
lineWidth : 0.5,
strokeStyle : '#6E6E6E',
- createTableGrid : true,
+ createTableGrid : false,
createCanvasGrid : true,
, n_active = LBT.active.size()
, n_units = LBT.units.size()
, n_projs = LBT.bullets.size()
+ , n_anims = LBT.animations.size()
;
$('#info #state').text( loop.running ? 'Running!' : ('Paused (tick '+TICKS+')') );
$('#info [name=fps]').val( fps.toFixed(2) + " / " + loop.framerate );
$('#info [name=frame]').val( loop.frametime().toFixed(3)+" ms" );
- $('#info [name=active]').val( n_active );
- $('#info [name=units]').val( n_units );
+ $('#info [name=active]').val( n_active );
+ $('#info [name=units]').val( n_units );
$('#info [name=bullets]').val( n_projs );
+ $('#info [name=anims]').val( n_anims );
loop.spark.drawTimes();
#info { position:relative; width:250px; }
#info #state { font-weight:bold; }
- #info label { display:block; float:left; width:3em; margin-right:0.5em; color:#787878; }
+ #info label { display:block; float:left; width:5em; margin-right:0.5em; color:#787878; }
#info input { border:0; background-color:transparent; min-width:5em; width:5em; color:#5c5c5c; }
#info .sep { opacity:0.1; background-color:#999; margin:5px 0; height:1px; }
#info .fps-sparkline { width:100%; height:1.5em; margin-top:0.5em; }
<ul id="info">
<li id="state"></li>
<li><div class="fps-sparkline"></div></li>
- <li><label for="info_fps" >fps</label> <input id="info_fps" name="fps" value="" type="text"></li>
- <li><label for="info_frame" >frame</label> <input id="info_frame" name="frame" value="" type="text"></li>
+ <li><label for="info_fps" >fps</label> <input id="info_fps" name="fps" value="" type="text"></li>
+ <li><label for="info_frame" >frame</label> <input id="info_frame" name="frame" value="" type="text"></li>
<li><div class="sep"></div></li>
- <li><label for="info_active" >active</label> <input id="info_active" name="active" value="" type="text"></li>
- <li><label for="info_units" >units</label> <input id="info_units" name="units" value="" type="text"></li>
- <li><label for="info_bullets">bullets</label> <input id="info_bullets" name="bullets" value="" type="text"></li>
+ <li><label for="info_active" >active</label> <input id="info_active" name="active" value="" type="text"></li>
+ <li><label for="info_units" >units</label> <input id="info_units" name="units" value="" type="text"></li>
+ <li><label for="info_bullets">bullets</label> <input id="info_bullets" name="bullets" value="" type="text"></li>
+ <li><label for="info_anims">animations</label> <input id="info_anims" name="anims" value="" type="text"></li>
</ul>
<div class="clearer"></div>
<script src="build/ezl/math/line.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/loop.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>
<script src="build/ezl/layer.js" type="text/javascript"></script>
<script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
<script src="build/tanks/map/wall.js" type="text/javascript"></script>
<script src="build/tanks/ui/grid.js" type="text/javascript"></script>
+<script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
<script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
<script src="build/tanks/thing/tank.js" type="text/javascript"></script>
<script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
C.title(C.boundingBox+'');
grid.draw();
+
});