From: dsc Date: Mon, 28 Feb 2011 04:36:23 +0000 (-0800) Subject: Adds viewport element to deal with larger levels. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=4d907a11dd03ff837074f79a8a4ba85fbe28692c;p=tanks.git Adds viewport element to deal with larger levels. --- diff --git a/data/types/levels.yaml b/data/types/levels.yaml index af501f8..8fda80c 100644 --- a/data/types/levels.yaml +++ b/data/types/levels.yaml @@ -13,8 +13,7 @@ types: test: name: Da Test desc: A test level. - width: 500 - height: 500 + levelSize: 500 500 bounds: - [-50,-50, 600,50] - [-50,0, 50,500] @@ -76,8 +75,7 @@ types: three_test: name: Three Test desc: A more complex test level. - width: 500 - height: 500 + levelSize: 500 500 # bounds: [[-1,-1], [501,-1], [501,351], [351,351], [351,501], [-1,501]] # bounds: # top: [[-50,-50, 600,50]] @@ -150,8 +148,7 @@ types: autotest: name: Automated AI Test desc: AI test level. - width: 500 - height: 500 + levelSize: 500 500 bounds: - [-50,-50, 600,50] - [-50,0, 50,500] @@ -217,8 +214,7 @@ types: los_test: name: Line-of-Sight Test desc: LOS test level. - width: 450 - height: 450 + levelSize: 450 450 bounds: - [-50,-50, 550,50] - [-50,0, 50,450] diff --git a/src/evt.cjs b/src/evt.cjs index d386765..88918b4 100644 --- a/src/evt.cjs +++ b/src/evt.cjs @@ -118,7 +118,6 @@ function createInitialise(cls){ , proto = cls.fn // use prototype so super() calls pick up the right lists , binds = proto.__bind__ // list of names to bind , inits = proto.__inits__ // init functions - , super = cls.__super__ , args = Y(arguments) ; @@ -127,8 +126,11 @@ function createInitialise(cls){ if ( inits && inits.length ) inits.invoke('apply', instance, args); - else if (super && super.init ) - super.init.apply(instance, args); + else if ( cls.__bases__ ) { + var superInits = super(cls, '__inits__'); + if ( superInits && superInits.length ) + superInits.invoke('apply', instance, args); + } instance.emit('init', instance, { 'instance' : instance, @@ -441,6 +443,30 @@ function mixin(cls, _mxn){ } +/** + * Looks up a key on the first prototype in the chain. + * @param {Class} cls Class on which to perform lookup. + * @param {String} key Key to lookup. + * @return {*} First non-undefined value found in the prototypes of the __bases__ chain. (nb. This does not include cls.) + */ +function super(cls, key){ + if ( !key ) + return; + if ( !(cls instanceof Class) && cls.__class__ ) + return super(cls.__class__, key); // cls = cls.__class__; + return cls.__bases__ // don't check cls.fn[key] -- we want super only + .reduce(function(v, base){ + return (v !== undefined ? v : base.prototype[key]); + }, undefined); +} + +/** + * Aggregate the objects up the prototypes in the __bases__ chain, merging together their properties + * such that the deepest subclasses supersceede their parents. + * @param {Class} cls Class on which to perform lookup. + * @param {String} key Key to lookup. + * @return {Object} Combined values from objects at key found in the prototypes of the __bases__ chain. (nb. This does not include cls.) + */ function aggregate(cls, key){ if ( !(cls instanceof Class) && cls.__class__ ) cls = cls.__class__; @@ -454,6 +480,21 @@ function aggregate(cls, key){ }, {}); } +/** + * Looks up a key on the Classes in the __bases__ chain. + * @param {Class} cls Class on which to perform lookup. + * @param {String} key Key to lookup. + * @return {*} First non-undefined value found in the __bases__ chain. (nb. This does not include cls.) + */ +function superStatic(cls, key){ + if ( !(cls instanceof Class) && cls.__class__ ) + return superStatic(cls.__class__, key); // cls = cls.__class__; + return cls.__bases__ // don't check cls[key] -- we want super only + .reduce(function(v, base){ + return (v !== undefined ? v : base[key]); + }, undefined); +} + Mixin.on('subclass', function onMixinSubclass(evt){ @@ -479,3 +520,4 @@ exports['fabricate'] = Y.fabricate; exports['lookupClass'] = lookupClass; exports['mixin'] = mixin; exports['aggregate'] = aggregate; +exports['superStatic'] = superStatic; diff --git a/src/ezl/layer/layer.cjs b/src/ezl/layer/layer.cjs index a17d7e6..c7d8ec1 100644 --- a/src/ezl/layer/layer.cjs +++ b/src/ezl/layer/layer.cjs @@ -6,13 +6,7 @@ var Y = require('Y') Layer = exports['Layer'] = evt.subclass('Layer', { - __mixins__ : [ Layerable ], - __bind__ : [], - - - init : function initLayer(props, attrs, html){ - // Layer.init.call(this, props, attrs, html); - } + __mixins__ : [ Layerable ] }) ; diff --git a/src/ezl/layer/layerable.cjs b/src/ezl/layer/layerable.cjs index 64ec7f6..5c46bfc 100644 --- a/src/ezl/layer/layerable.cjs +++ b/src/ezl/layer/layerable.cjs @@ -404,6 +404,10 @@ Mixin.subclass('Layerable', { /** Position relative to document. */ offset : makeDelegate('offset'), + scrollLeft : makeDelegate('scrollLeft'), + scrollTop : makeDelegate('scrollTop'), + scrollWidth : function scrollWidth(v){ return this.layer.attr('scrollWidth', v); }, + scrollHeight : function scrollHeight(v){ return this.layer.attr('scrollHeight', v); }, /// Transformations /// @@ -736,8 +740,14 @@ Mixin.subclass('Layerable', { /// Misc /// toString : function(){ - var pos = ((this.layer.length && this.layer.parent()[0]) ? this.position() : {top:NaN, left:NaN}); - return this.className+'['+pos.left+','+pos.top+']( children='+this.children.size()+' )'; + var pos = { 'top':NaN, 'left':NaN } + , kids = this.children ? this.children.length : 0 + , el = this.layer + ; + // Check we're in the DOM, otherwise jQuery vomits + if ( el && el.attr('parentNode') ) + pos = this.position(); + return this.className+'['+pos.left+','+pos.top+']( children='+kids+' )'; } }); diff --git a/src/tanks/game.cjs b/src/tanks/game.cjs index b225073..da01c55 100644 --- a/src/tanks/game.cjs +++ b/src/tanks/game.cjs @@ -9,6 +9,7 @@ var Y = require('Y').Y , EventLoop = require('ezl/loop').EventLoop , Layer = require('ezl/layer/layer').Layer +, HtmlLayer = require('ezl/layer/html').HtmlLayer , Circle = require('ezl/shape').Circle , config = require('tanks/config').config @@ -16,6 +17,7 @@ var Y = require('Y').Y , Level = require('tanks/map/level').Level , Grid = require('tanks/ui/grid').Grid , PathMapUI = require('tanks/ui/pathmapui').PathMapUI +, Viewport = require('tanks/ui/viewport').Viewport , Backpack = require('tanks/ui/inventory/backpack').Backpack , Thing = thing.Thing @@ -53,14 +55,14 @@ evt.subclass('Game', { this.loop = new EventLoop(FRAME_RATE, this); this.root = - new Layer({ hasCanvas:false }, { 'id':'game' }) + new HtmlLayer({ '_layerId':'game' }) .prependTo( $('body') ); this.ui = - new Layer({ hasCanvas:false }, { 'id':'ui' }) + new HtmlLayer({ '_layerId':'ui' }) .appendTo( this.root ); this.viewport = - new Layer({ hasCanvas:false }, { 'id':'viewport' }) + new Viewport({ '_layerId':'viewport' }) .appendTo( this.root ); this.level = Level.create(this.levelId, this, CAPACITY, REF_SIZE) @@ -68,9 +70,6 @@ evt.subclass('Game', { this.grid = new Grid(this.level, REF_SIZE) .prependTo( this.viewport ); - this.viewport - .width(this.level.width) - .height(this.level.height); this.map = this.level.map; this.map.ui = new PathMapUI(this, this.map); @@ -83,6 +82,8 @@ evt.subclass('Game', { this.on('tick', this.tick); this.level.setup(tanks.data); + this.viewport.centerOn(); + this.emit('ready', this); }, diff --git a/src/tanks/map/level.cjs b/src/tanks/map/level.cjs index c34779f..435f1ad 100644 --- a/src/tanks/map/level.cjs +++ b/src/tanks/map/level.cjs @@ -15,6 +15,7 @@ var Y = require('Y').Y , min = Y(Math.min).limit(2) , max = Y(Math.max).limit(2) +, toInt = Y(parseInt).limit(1) , @@ -26,11 +27,11 @@ Rect.subclass('Level', { init : function init(game, capacity, buffer_size){ this.game = game; - this.map = new Map(0,0, this.width, this.height, capacity, buffer_size); + this.map = new Map(0,0, this.levelWidth, this.levelHeight, capacity, buffer_size); - Rect.init.call(this, this.width,this.height); + Rect.init.call(this, this.levelWidth,this.levelHeight); this.fill('transparent'); - // var shape = this.shape = new Rect(this.width,this.height).fill('transparent'); + // var shape = this.shape = new Rect(this.levelWidth,this.levelHeight).fill('transparent'); // shape.layer.attr('class', this._layerClasses); // this.bbox = shape.bbox; }, @@ -68,23 +69,30 @@ Rect.subclass('Level', { }); -proxy({ target:Level, donor:Rect, context:'shape', chain:true, - names:'append appendTo remove invoke clear'.split(' ') }); +// proxy({ target:Level, donor:Rect, context:'shape', chain:true, +// names:'append appendTo remove invoke clear'.split(' ') }); Level.on('speciate', function onSpeciate(evt){ var NewLevel = evt.data.species , proto = NewLevel.fn , bounds = Y(proto.bounds) + , size = proto.levelSize ; - if (!proto.width) { - proto.width = + if (size) { + size = size.split(' ').map(toInt); + proto.levelWidth = size[0]; + proto.levelHeight = size[1]; + } + + if (!proto.levelWidth) { + proto.levelWidth = bounds.right.pluck(0).reduce(max,0) - bounds.left.pluck(0).reduce(min,Infinity); } - if (!proto.height) { - proto.height = + if (!proto.levelHeight) { + proto.levelHeight = bounds.bottom.pluck(1).reduce(max,0) - bounds.top.pluck(1).reduce(min,Infinity); } diff --git a/src/tanks/ui/grid.cjs b/src/tanks/ui/grid.cjs index e1f0cc6..cc82a2c 100644 --- a/src/tanks/ui/grid.cjs +++ b/src/tanks/ui/grid.cjs @@ -21,8 +21,8 @@ Rect.subclass('Grid', { init : function init(level, side){ this.level = level; - this.cols = Math.round(level.width/side); - this.rows = Math.round(level.height/side); + this.cols = Math.round(level.levelWidth/side); + this.rows = Math.round(level.levelHeight/side); this.side = side; Rect.init.call(this, this.cols*side, this.rows*side); }, diff --git a/src/tanks/ui/index.cjs b/src/tanks/ui/index.cjs index c70c957..5c41507 100644 --- a/src/tanks/ui/index.cjs +++ b/src/tanks/ui/index.cjs @@ -1,6 +1,10 @@ var Y = require('Y').Y; - -exports['PathMapUI'] = require('tanks/ui/pathmapui').PathMapUI; -exports['main'] = require('tanks/ui/main'); -exports['config'] = require('tanks/ui/configui'); -exports['inventory'] = require('tanks/ui/inventory'); \ No newline at end of file +Y.core.extend(exports, { + 'config' : require('tanks/ui/configui'), + 'main' : require('tanks/ui/main'), + 'inventory' : require('tanks/ui/inventory'), + + 'Grid' : require('tanks/ui/grid').Grid, + 'Viewport' : require('tanks/ui/viewport').Viewport, + 'PathMapUI' : require('tanks/ui/pathmapui').PathMapUI +}); diff --git a/src/tanks/ui/pathmapui.cjs b/src/tanks/ui/pathmapui.cjs index bb5f017..1f7d5ad 100644 --- a/src/tanks/ui/pathmapui.cjs +++ b/src/tanks/ui/pathmapui.cjs @@ -41,7 +41,7 @@ Rect.subclass('PathMapUI', { var level = game.level , b = this.buffer = map.buffer_size; - Rect.init.call(this, level.width, level.height); + Rect.init.call(this, level.levelWidth, level.levelHeight); // this.posBleed.x = this.posBleed.y = // this.negBleed.x = this.negBleed.y = b; diff --git a/src/tanks/ui/viewport.cjs b/src/tanks/ui/viewport.cjs new file mode 100644 index 0000000..e582d55 --- /dev/null +++ b/src/tanks/ui/viewport.cjs @@ -0,0 +1,115 @@ +var Y = require('Y').Y +, Vec = require('ezl/math/vec').Vec +, HtmlLayer = require('ezl/layer/html').HtmlLayer + +, PAD_X = 1024, PAD_Y = 690 +, min = Y(Math.min).limit(2) +, max = Y(Math.max).limit(2) +, + +Viewport = +exports['Viewport'] = +HtmlLayer.subclass('Viewport', { + _layerId : 'viewport', + + spacer : null, // inner layer to induce scrolling so we can arbitrarily center the elements + + + + init : function initViewport(props, attrs, html){ + HtmlLayer.init.call(this, props, attrs, html); + this.spacer = $('
').appendTo(this.layer); + }, + + get contentWidth(){ return this.children.pluck('layerWidth').reduce(max,0); }, + get contentHeight(){ return this.children.pluck('layerHeight').reduce(max,0); }, + + /** + * Sets the current scroll position of the Viewport. (Unlike the DOM, Viewport has 50% negative scroll-space top and left.) + * @param {Number} x Left offset in pixels. + * @param {Number} y Top offset in pixels. + * @return {this} + */ + /** + * Gets the current scroll position of the Viewport. + * @return {Vec} Current scroll position (left, top). + */ + scroll : function scroll(x,y){ + var el = this.layer; + if (arguments.length === 0) + return new Vec(el.scrollLeft()-PAD_X, el.scrollTop()-PAD_Y); + + if (x instanceof Array) { y=x[1]; x=x[0]; } + + if (x !== undefined) el.scrollLeft( x + PAD_X ); + if (y !== undefined) el.scrollTop( y + PAD_Y ); + + return this; + }, + + + /** + * Scrolls the viewport to place (x,y) at the center. + * Note: Viewport can be centered on values ranging from -100% to +100% of width and height. + * @param {Number} [x=undefined] Desired center x-coordinate. If undefined, half contentWidth will be used. + * @param {Number} [y=undefined] Desired center y-coordinate. If undefined, half contentHeight will be used. + * @return {this} + */ + centerOn : function centerOn(x,y){ + if (x === undefined) x = this.contentWidth/2; + if (y === undefined) y = this.contentHeight/2; + var el = this.layer + , w2 = el.width()/2, h2 = el.height()/2; + return this.scroll(x-w2, y-h2); + }, + + + /// Debug /// + + createGrid : function createGrid(step, maxX, maxY){ + step = step || 50; + maxX = maxX || _X*3; + maxY = maxY || _Y*3; + + // $('.chrome').hide(); + $('#pen, #mapblotter').remove(); + + var map = $('#viewport .map'); + $('
') + .css({ + 'background-color': '#3F3F3F', 'z-index':-1, + 'width':map.width(), 'height':map.height() + }) + .appendTo(this.layer); + var pen = $('
') + .css({ + 'position': 'absolute', + 'left': 0, 'top': 0, 'margin': 0, + 'z-index':-10 + }) + .appendTo(this.layer); + + Y(0,maxX,step).forEach(function(x, xth){ + Y(0,maxY,step).forEach(function(y, yth){ + var isColored = (xth % 2) == (yth % 2); + $('
'+x+','+y+'
') + .css({ + 'opacity': 0.05, + 'position': 'absolute', 'display':'table-cell', + 'left': x, 'top': y, + 'width': step, 'height': step, + 'margin': 0, 'padding': 0, + 'font' : '8px/'+step+'px Menlo', 'text-align' : 'center', + 'color' : (isColored ? '#000' : '#fff'), + 'background-color': (isColored ? '#fff' : 'transparent') + }) + .appendTo(pen) + ; + }); + }); + + return this; + } + +}) +; \ No newline at end of file diff --git a/www/css/lttl.css b/www/css/lttl.css index e142e97..d6496cd 100644 --- a/www/css/lttl.css +++ b/www/css/lttl.css @@ -1,4 +1,4 @@ -html, body { width:100%; height:100%; top:0; left:0; margin:0; padding:0; background-color:#3F3F3F; } +html, body { width:100%; height:100%; top:0; left:0; margin:0; padding:0; background-color:#333; } body { position:absolute; font-family:Geogrotesque,Helvetica; font-size:12pt; color:#fff; } h1, h2, h3, h4, h5 { margin:0.75em 0; font-weight:normal; } ul, ol, li { list-style: none ! important; margin:0; padding:0; } @@ -46,16 +46,25 @@ td { text-align:center; vertical-align:middle; } #notes li { margin-left:1em; } -#game { position:absolute; width:100%; height:100%; margin:0; padding:0; } -#viewport { position:relative; margin:1em auto; cursor:crosshair; width:auto; /* width:500px; height:500px; overflow:hidden; top:50px; left:50px; */ } - #viewport .layer.grid { outline:1px solid rgba(255,255,255,0.1); } +/*** Game and Viewport ***/ + +#game { position:relative; width:100%; height:100%; margin:0; padding:0; } +#viewport { position:relative; width:100%; height:100%; max-width:1024px; max-height:690px; + overflow:hidden; cursor:crosshair; margin:0 auto; background-color:#3F3F3F; } + #viewport > .layer { margin:690px 1024px; } + #viewport > .spacer { position:absolute; z-index:-1; width:1024px; height:690px; /* width:100%; height:100%; */ } + #viewport .layer.grid { outline:1px solid rgba(255,255,255, 0.1); } + #viewport .ezl.layer.map { } + #viewport .ezl.layer.pathmap { z-index:50; } + + .item-container { position:absolute; } .item-container h3 { color:#fff; margin:0.25em; padding:0.5em 0; font-size:0.8em; } .item-container .slot { float:left; position:relative; width:50px; height:50px; margin:0.25em; border:1px solid transparent; } .item-container .slot.drophover { border:1px solid #FFF6AE; } -#ui { position:absolute; bottom:0; right:0; left:auto; top:auto; width:auto; } +#ui { position:absolute; bottom:0; right:0; left:auto; top:auto; width:auto; z-index:500; } #ui > .bag { position:relative; float:right; margin:0 0 1em 1em; } #ui .equipslot .slot { background-position:center; background-repeat:no-repeat; } #ui .equipslot .slot.occupied { background-image:none; } @@ -97,8 +106,6 @@ table.grid td { /* outline:1px solid rgba(255,255,255,0.1); */ margin:0; padding:0; white-space:nowrap; overflow:hidden; } .showGridCoords table.grid td:hover { color:rgba(255,255,255,0.1); } -.ezl.layer.pathmap { z-index:50; } - /* #debug { position:relative; top:1em; right:1em; z-index:5000; } #debug .inner { position:absolute; top:0; right:0; padding:1em; } diff --git a/www/welcome.html b/www/welcome.html index d2c140a..3c7ba77 100644 --- a/www/welcome.html +++ b/www/welcome.html @@ -1,4 +1,4 @@ -
+

The Littlest Battletank

@@ -6,7 +6,7 @@
-