Browse Source

added CdbLevel

ncannasse 9 năm trước cách đây
mục cha
commit
c15305c39b
2 tập tin đã thay đổi với 416 bổ sung13 xóa
  1. 403 0
      h2d/CdbLevel.hx
  2. 13 13
      h2d/Layers.hx

+ 403 - 0
h2d/CdbLevel.hx

@@ -0,0 +1,403 @@
+package h2d;
+
+#if !castle
+"Please compile with -lib castle"
+#end
+
+typedef TileSpec = {
+	var file(default, never) : String;
+	var stride(default, never) : Int;
+	var size(default, never) : Int;
+}
+
+typedef LayerSpec = {
+	var name : String;
+	var data : cdb.Types.TileLayer;
+}
+
+typedef LevelSpec = {
+	var width : Int;
+	var height : Int;
+	var props : cdb.Data.LevelProps;
+	var tileProps(default, null) : Array<Dynamic>;
+	var layers : Array<LayerSpec>;
+}
+
+class LevelTileset {
+	public var stride : Int;
+	public var size : Int;
+	public var res : hxd.res.Image;
+	public var tile : h2d.Tile;
+	public var tiles : Array<h2d.Tile>;
+	public var objects : Array<LevelObject>;
+	public var tilesProps(get, never) : Array<Dynamic>;
+	var props :	cdb.Data.TilesetProps;
+	var tileBuilder : cdb.TileBuilder;
+	public function new() {
+	}
+	inline function get_tilesProps() return props.props;
+	public function getTileBuilder() {
+		if( tileBuilder == null )
+			tileBuilder = new cdb.TileBuilder(props, stride, tiles.length);
+		return tileBuilder;
+	}
+}
+
+class LevelObject {
+	public var tileset : LevelTileset;
+	public var id : Int;
+	public var x : Int;
+	public var y : Int;
+	public var width : Int;
+	public var height : Int;
+	public var props : Dynamic;
+	public var tile : h2d.Tile;
+
+	public function new(tset, x, y, w, h) {
+		this.tileset = tset;
+		this.x = x;
+		this.y = y;
+		id = x + y * tileset.stride;
+		width = w;
+		height = h;
+		var sz = tileset.size;
+		tile = tileset.tile.sub(x * sz, y * sz, width * sz, height * sz);
+	}
+}
+
+class LevelObjectInstance {
+	public var x : Int;
+	public var y : Int;
+	public var rot : Int;
+	public var flip : Bool;
+	public var obj : LevelObject;
+	public function new() {
+	}
+}
+
+enum LevelLayerData {
+	LTiles( data : Array<Int> );
+	LGround( data : Array<Int> );
+	LObjects( objects : Array<LevelObjectInstance> );
+}
+
+class LevelLayer {
+
+	/**
+		Which level this layer belongs to
+	**/
+	public var level : CdbLevel;
+
+	/**
+		The name of the layer, as it was created in CDB
+	**/
+	public var name : String;
+
+	/**
+		CdbLevel extends Layers: this index will tell in which sprite layer this LevelLayer content is added to.
+	**/
+	public var layerIndex(default,null) : Int;
+
+	/**
+		The raw data of the layer. You can read it or modify it then set needRedraw=true to update it on screen.
+	**/
+	public var data : LevelLayerData;
+
+	/**
+		The tileset this layer is using to display its graphics
+	**/
+	public var tileset : LevelTileset;
+
+	/**
+		If the layer needs to be redrawn, it's set to true.
+	**/
+	public var needRedraw = true;
+
+	/**
+		Allows to add objects on the same layerIndex that can behind or in front of the
+	**/
+	public var objectsBehind(default, set) : Bool;
+
+	/**
+		One or several tile groups that will be used to display the layer
+	**/
+	public var contents : Array<h2d.TileGroup>;
+
+	/**
+		Alias to the first element of contents
+	**/
+	public var content(get, never) : h2d.TileGroup;
+
+	public function new(level) {
+		this.level = level;
+	}
+
+	inline function get_content() {
+		return contents[0];
+	}
+
+	/**
+		Entirely removes this layer from the level.
+	**/
+	public function remove() {
+		for( c in contents )
+			c.remove();
+		contents = [];
+		level.layers.remove(this);
+		@:privateAccess level.layersMap.remove(name);
+	}
+
+	/**
+		Returns the data for the given CDB per-tile property based on the data of the current layer.
+		For instance if you have a "collide" per-tile property set for several of your objects or tiles,
+		then calling buildIntProperty("collide") will return you with the collide data for the given layer.
+		In case of objects, if several objects overlaps, the greatest property value overwrites the lowest.
+	**/
+	public function buildIntProperty( name : String ) {
+		var tprops = [for( p in tileset.tilesProps ) p == null ? 0 : Reflect.field(p, name)];
+		var out = [for( i in 0...level.width * level.height ) 0];
+		switch( data ) {
+		case LTiles(data), LGround(data):
+			for( i in 0...level.width * level.height ) {
+				var t = data[i];
+				if( t == 0 ) continue;
+				out[i] = tprops[t - 1];
+			}
+		case LObjects(objects):
+			for( o in objects ) {
+				var ox = Std.int(o.x / tileset.size);
+				var oy = Std.int(o.y / tileset.size);
+				for( dy in 0...o.obj.height )
+					for( dx in 0...o.obj.width ) {
+						var idx = ox + dx + (oy + dy) * level.width;
+						var cur = tprops[o.obj.id + dx + dy * tileset.stride];
+						if( cur > out[idx] )
+							out[idx] = cur;
+					}
+			}
+		}
+		return out;
+	}
+
+	function set_objectsBehind(v) {
+		if( v == objectsBehind )
+			return v;
+		if( v && !data.match(LObjects(_)) )
+			throw "Can only set objectsBehind for 'Objects' Layer Mode";
+		needRedraw = true;
+		return objectsBehind = v;
+	}
+}
+
+/**
+	CdbLevel will decode and display a level created with the CastleDB level editor.
+	See http://castledb.org for more details.
+**/
+class CdbLevel extends Layers {
+
+	public var width(default, null) : Int;
+	public var height(default, null) : Int;
+	public var level(default, null) : LevelSpec;
+	public var layers : Array<LevelLayer>;
+	var tilesets : Map<String, LevelTileset>;
+	var layersMap : Map<String, LevelLayer>;
+	var levelsProps : cdb.Data.LevelsProps;
+
+	public function new(allLevels:cdb.Types.Index<Dynamic>,index:Int,?parent) {
+		super(parent);
+		levelsProps = @:privateAccess allLevels.sheet.props.level;
+		level = allLevels.all[index];
+		width = level.width;
+		height = level.height;
+		tilesets = new Map();
+		layersMap = new Map();
+		layers = [];
+		for( ldat in level.layers ) {
+			var l = loadLayer(ldat);
+			if( l != null ) {
+				@:privateAccess l.layerIndex = layers.length;
+				layers.push(l);
+				layersMap.set(l.name, l);
+
+				var content = new h2d.TileGroup(l.tileset.tile);
+				add(content, l.layerIndex);
+				l.contents = [content];
+			}
+		}
+	}
+
+	public function getLevelLayer( name : String ) : LevelLayer {
+		return layersMap.get(name);
+	}
+
+	public function getTileset( file : String ) : LevelTileset {
+		return tilesets.get(file);
+	}
+
+	function redrawLayer( l : LevelLayer ) {
+		for( c in l.contents )
+			c.clear();
+		l.needRedraw = false;
+		var pos = 0;
+		switch( l.data ) {
+		case LTiles(data), LGround(data):
+			var size = l.tileset.size;
+			var tiles = l.tileset.tiles;
+			var i = 0;
+			var content = l.contents[pos++];
+			for( y in 0...height )
+				for( x in 0...width ) {
+					var t = data[i++];
+					if( t == 0 ) continue;
+					content.add(x * size, y * size, tiles[t - 1]);
+				}
+			if( l.data.match(LGround(_)) ) {
+				var b = l.tileset.getTileBuilder();
+				var grounds = b.buildGrounds(data, width);
+				var glen = grounds.length;
+				var i = 0;
+				while( i < glen ) {
+					var x = grounds[i++];
+					var y = grounds[i++];
+					var t = grounds[i++];
+					content.add(x * size, y * size, tiles[t]);
+				}
+			}
+		case LObjects(objects):
+			if( l.objectsBehind ) {
+				var pos = 0;
+				var byY = [];
+				var curY = -1;
+				var content = null;
+				for( o in objects ) {
+					var baseY = o.y + o.obj.tile.height;
+					if( baseY != curY ) {
+						curY = baseY;
+						content = byY[baseY];
+						if( content == null ) {
+							content = l.contents[pos++];
+							if( content == null ) {
+								content = new h2d.TileGroup(l.tileset.tile);
+								add(content, l.layerIndex);
+								l.contents.push(content);
+							}
+							content.y = baseY;
+							byY[baseY] = content;
+						}
+					}
+					content.add(o.x, o.y - baseY, o.obj.tile);
+				}
+			} else {
+				var content = l.contents[pos++];
+				for( o in objects )
+					content.add(o.x, o.y, o.obj.tile);
+			}
+		}
+		// clean extra lines
+		while( pos > 0 && l.contents[pos] != null )
+			l.contents.pop().remove();
+	}
+
+	function loadTileset( ldat : TileSpec ) : LevelTileset {
+		var t = new LevelTileset();
+		t.size = ldat.size;
+		t.stride = ldat.stride;
+		t.res = hxd.res.Loader.currentInstance.load(ldat.file).toImage();
+		t.tile = t.res.toTile();
+		t.tiles = t.tile.grid(t.size);
+		t.objects = [];
+		var tprops = Reflect.field(levelsProps.tileSets, ldat.file);
+		@:privateAccess t.props = tprops;
+		if( tprops != null ) {
+			var hasBorder = false;
+			for( s in tprops.sets )
+				switch( s.t ) {
+				case Object:
+					var o = new LevelObject(t, s.x, s.y, s.w, s.h);
+					t.objects[o.id] = o;
+				case Group:
+					// TODO : save props
+				case Ground, Border, Tile:
+					// nothing
+				}
+		}
+		return t;
+	}
+
+	function resolveTileset( tdat : TileSpec ) {
+		var t = tilesets.get(tdat.file);
+		if( t == null ) {
+			t = loadTileset(tdat);
+			if( t == null )
+				return null;
+			tilesets.set(tdat.file, t);
+		}
+		if( t.stride != tdat.stride || t.size != tdat.size )
+			throw "Tileset " + tdat.file+" is used with different stride/size";
+		return t;
+	}
+
+	function loadLayer( ldat : LayerSpec ) : LevelLayer {
+		var t = resolveTileset(ldat.data);
+		if( t == null )
+			return null;
+		var l = new LevelLayer(this);
+		l.name = ldat.name;
+		l.tileset = t;
+		var data = ldat.data.data.decode();
+		var lprops = null;
+		for( lp in level.props.layers )
+			if( lp.l == l.name ) {
+				lprops = lp.p;
+				break;
+			}
+		var mode : cdb.Data.LayerMode = lprops != null ? lprops.mode : Tiles;
+		switch( mode ) {
+		case Tiles:
+			l.data = LTiles(data);
+		case Ground:
+			l.data = LGround(data);
+		case Objects:
+			var objs = [];
+			var i = 1;
+			var len = data.length;
+			while( i < len ) {
+				var x = data[i++];
+				var y = data[i++];
+				var t = data[i++];
+				var e = new LevelObjectInstance();
+				e.x = x & 0x7FFF;
+				e.y = y & 0x7FFF;
+				e.rot = (x >> 15) | ((y >> 15) << 1);
+				e.flip = (t >> 15) != 0;
+				t &= 0x7FFF;
+				e.obj = l.tileset.objects[t];
+				if( e.obj == null ) {
+					// create new 1x1 object
+					var o = new LevelObject(l.tileset, t%l.tileset.stride, Std.int(t/l.tileset.stride), 1, 1);
+					e.obj = l.tileset.objects[t] = o;
+				}
+				objs.push(e);
+			}
+			l.data = LObjects(objs);
+			l.objectsBehind = true;
+		}
+		return l;
+	}
+
+	public function redraw() {
+		for( l in layers )
+			if( l.needRedraw )
+				redrawLayer(l);
+	}
+
+	override function draw(ctx:RenderContext) {
+		for( l in layers ) {
+			if( l.needRedraw )
+				redrawLayer(l);
+			if( l.objectsBehind )
+				ysort(l.layerIndex);
+		}
+	}
+
+}

+ 13 - 13
h2d/Layers.hx

@@ -3,12 +3,12 @@ package h2d;
 class Layers extends Sprite {
 
 	// the per-layer insert position
-	var layers : Array<Int>;
+	var layersIndexes : Array<Int>;
 	var layerCount : Int;
 
 	public function new(?parent) {
 		super(parent);
-		layers = [];
+		layersIndexes = [];
 		layerCount = 0;
 	}
 
@@ -29,10 +29,10 @@ class Layers extends Sprite {
 		}
 		// new layer
 		while( layer >= layerCount )
-			layers[layerCount++] = childs.length;
-		super.addChildAt(s,layers[layer]);
+			layersIndexes[layerCount++] = childs.length;
+		super.addChildAt(s,layersIndexes[layer]);
 		for( i in layer...layerCount )
-			layers[i]++;
+			layersIndexes[i]++;
 	}
 
 	override function removeChild( s : Sprite ) {
@@ -42,8 +42,8 @@ class Layers extends Sprite {
 				if( s.allocated ) s.onDelete();
 				s.parent = null;
 				var k = layerCount - 1;
-				while( k >= 0 && layers[k] > i ) {
-					layers[k]--;
+				while( k >= 0 && layersIndexes[k] > i ) {
+					layersIndexes[k]--;
 					k--;
 				}
 				break;
@@ -55,7 +55,7 @@ class Layers extends Sprite {
 		for( i in 0...childs.length )
 			if( childs[i] == s ) {
 				var pos = 0;
-				for( l in layers )
+				for( l in layersIndexes )
 					if( l > i )
 						break;
 					else
@@ -73,7 +73,7 @@ class Layers extends Sprite {
 	public function over( s : Sprite ) {
 		for( i in 0...childs.length )
 			if( childs[i] == s ) {
-				for( l in layers )
+				for( l in layersIndexes )
 					if( l > i ) {
 						for( p in i...l-1 )
 							childs[p] = childs[p + 1];
@@ -89,8 +89,8 @@ class Layers extends Sprite {
 		if( layer >= layerCount )
 			a = [];
 		else {
-			var start = layer == 0 ? 0 : layers[layer - 1];
-			var max = layers[layer];
+			var start = layer == 0 ? 0 : layersIndexes[layer - 1];
+			var max = layersIndexes[layer];
 			a = childs.slice(start, max);
 		}
 		return new hxd.impl.ArrayIterator(a);
@@ -98,8 +98,8 @@ class Layers extends Sprite {
 
 	public function ysort( layer : Int ) {
 		if( layer >= layerCount ) return;
-		var start = layer == 0 ? 0 : layers[layer - 1];
-		var max = layers[layer];
+		var start = layer == 0 ? 0 : layersIndexes[layer - 1];
+		var max = layersIndexes[layer];
 		if( start == max )
 			return;
 		var pos = start;