Jump to content

depth sort and multiple groups


Recommended Posts

Hi, I'm new to Phaser, and I like it so far. 


I'm in the middle of optimizing my game, and ended up with several groups. I have an array with several objects representing sections of the map, and a handful of groups in each of those sections. The structure looks something like this,

[  0: {    map: <group>,    herbs: <group>,    sheep: <group>,    players: <group>  },  ..  30: {    map: <group>,    herbs: <group>,    sheep: <group>,    players: <group>  }]

I plan on making the map much larger in the future, and thus, make the amount of sections scale accordingly.


My problem is with depth-sorting. I move sprites from group-to-group several times by abstracting my sections array with functions that modify several groups at once, and this works great! Before I optimized, I performed depth-sort by making every object a part of the same group. Now that I have n groups, I'm wondering how I'm going to modify the order in which the individual sprites are drawn each update.


So, I created yet another array, called inView, which contains all of the sprites that are visible. Items are added/removed from the inView array every update by comparing their bounds with the camera. The goal is to display only the sprites in view of the camera (neglecting to loop through any sprites that aren't in view) and draw them in a custom order (regardless of which groups are drawn first).


I've successfully sorted the individual sprites in my inView array, however this does nothing in terms of sorting their depth.


Is there a way I can change a property in each of these sprites (all from arbitrary groups), that causes them to display in order?


PS: Note that up to 4 sections may be in view of the camera at once- similar to how a person can be standing in 4 of the 50 United States at once in real life, so making a single group for each section wouldn't completely solve my current problem.


PSS: I first tried making 'inView' a group, and moving the sprites to/from this group when visibility changes. The problem here, is I lose all of the group-related logic for the sprites.

Link to comment
Share on other sites

  • 2 weeks later...

Update: I solved my problem. I put all my game-objects in one phaser group, and I created my own kind-of group thing, to add/remove group properties, create/destroy methods, etc.


If anyone is interested, this is how I did it: (Sorry about the messy code, I haven't refactored yet.

var visibleObjects = 0;var main = {	game: game,	sections: [],	hideOffCamera: function (sprite) {		if (!this.intersects(this.game.camera, sprite)) {			if (sprite.visible) {				if (sprite.name === "sheep") {					console.log("making lamb invisible");				}				visibleObjects -= 1;				sprite.visible = false;				if (main.objects.getIndex(sprite) === -1) {					// ignore non-game-objects after this point.					return;				}				this.inView.remove(sprite);				this.getGroup('inCamera', 0).remove(sprite);			}		}		else {			if (!sprite.visible) {				if (sprite.key === "sheep") {					console.log("making lamb visible");				}				visibleObjects += 1;				sprite.visible = true;				if (main.objects.getIndex(sprite) === -1) {					// ignore non-game-objects after this point.					return;				}				this.inView.add(sprite);				this.getGroup('inCamera', 0).add(sprite);			}		}	},	intersects: function (a,  {		return !(a.x + a.width < b.x ||			a.y + a.height < b.y ||			b.x + b.width < a.x ||			b.y + b.height < a.y);	},	randBool: function () {		return Math.floor(Math.random() * 2) ? true : false;	},	randInRangeGrid: function randInRangeGrid (start, end, gridSize) {		var rand = Math.floor(Math.random() * end) - start;		var half = gridSize / 2;		var mod = rand % gridSize;		//console.log(rand, half, mod);		if (gridSize === 0) {			return rand;		}		if (mod > half) {			return rand + (gridSize - mod);		} else {			return rand - mod;		}	},	getSection: function (rect) {		var i;		for (i = 0; i < this.sections.length; i += 1) {			if (this.intersects(rect, this.sections[i])) {				return i;			}		}		console.log(rect);		throw new Error("Object not in section!");	},	_SectionGroup: function (sectionIndex) {		var self = this;		var groupProps = {};		var groupMethodsInit = {};		var groupMethodsDestroy = {};		self.children = [];		function getDeepContext (obj, prop) {			var i, context, chain;			if (prop.indexOf('.') === -1) {				return obj;			}			chain = prop.split('.');			context = obj;			//console.log("context:", context, chain);			for (i = 0; i < chain.length - 1; i += 1) {				context = context[chain[i]];				//console.log("context:", context);			}			return context[chain[chain.length - 1]];		}		function setDeepContext (obj, prop, val) {			var lastDot, context;			lastDot = prop.lastIndexOf('.');			// If there's no '.' in the property, no deep-context search is needed.			if (lastDot === -1) {				return obj[prop] = val;			}			context = getDeepContext(obj, prop.substr(0, lastDot));			return context[prop.substr(lastDot + 1)] = val;		}		function callMethod (obj, method, context, args) {			var ctx = getDeepContext(obj, method);			if (context === null || context === void 0) {				context = obj;			} else {				context = getDeepContext(obj, context);			}			//console.log(method, ctx, context, args);			ctx.apply(context, args);		}		self.getAt = function (i) {			return self.children[i];		}		self.add = function (sprite) {			self.children.push(sprite);			var key;			for (key in groupProps) {				setDeepContext(sprite, key, groupProps[key]);			}			for (key in groupMethodsInit) {				callMethod(sprite, key, groupMethodsInit[key][0], groupMethodsInit[key][1])			}			return sprite;		}		self.create = function (x, y, name) {			if (x === void 0) {				x = 0;			}			if (y === void 0) {				y = 0;			}			if (name === void 0) {				if (this.name === void 0) {					throw new Error("No suitable name");				}				name = this.name;			}			var sprite = main.objects.create(x, y, name);			return this.add(sprite);		}		self.remove = function (sprite) {			var spriteIndex = self.children.indexOf(sprite);			var key;			self.children.splice(spriteIndex, 1);			for (key in groupProps) {				setDeepContext(sprite, key, void 0);			}			for (key in groupMethodsDestroy) {				callMethod(sprite, key, groupMethodsDestroy[key][0], groupMethodsDestroy[key][1])			}		}		self.setAll = function (prop, val) {			var i;			groupProps[prop] = val;			for (i = 0; i < this.children.length; i += 1) {				setDeepContext(this.children[i], prop, val);			}		}		self.callAll = function (methodInit, contextInit, argsInit, methodDestroy, contextDestroy, argsDestroy) {			var i;			groupMethodsInit[methodInit] = [contextInit, argsInit];			groupMethodsDestroy[methodDestroy] = [contextDestroy, argsDestroy];			for (i = 0; i < this.children.length; i += 1) {				callMethod(this.children[i], methodInit, contextInit, argsInit);			}		}		self.alive = true;		self.alpha = 1;		self.exists = true;		self.sectionIndex = sectionIndex;		self.visible = true;		Object.defineProperty(self, "length", {			get: function () {				return self.children.length;			}		});		self.forEach = self.children.forEach;	},	_addSectionGroup: function (name, sectionIndex) {		this.sections[sectionIndex].groups[name] = new this._SectionGroup(sectionIndex);	},	createGroup: function (name) {		var i;		for (i = 0; i < this.sections.length; i += 1) {			this._addSectionGroup(name, i);		}	},	getGroup: function (name, section) {		return this.sections[section].groups[name];	},	setGroupProp: function (name, prop, val) {		var i, group;		for (i = 0; i < this.sections.length; i += 1) {			group = this.sections[i].groups[name];			group.setAll(prop, val);		}	},	callGroupMethod: function (name, methodInit, contextInit, argsInit, methodDestroy, contextDestroy, argsDestroy) {		var i, group;		for (i = 0; i < this.sections.length; i += 1) {			group = this.sections[i].groups[name];			group.callAll(methodInit, contextInit, argsInit, methodDestroy, contextDestroy, argsDestroy);		}	},	changeSection: function (sprite, groupName, oldIndex, newIndex) {		this.getGroup(groupName, oldIndex).remove(sprite);		this.getGroup(groupName, newIndex).add(sprite);	}};

I created an inCamera group and added a mouseDown event to each sprite in that group. I needed a way to deconstruct the event-handler (so that objects traveling off-camera no longer have input-support- greatly improving performance).

var lastClickedObj = null;main.onInputDown = function onInputDown (obj) {	lastClickedObj = obj.data;	console.log("last clicked obj:", lastClickedObj);}// ...function create () {	// ...	main.createGroup('inCamera');	main.setGroupProp('inCamera', 'inputEnabled', true);	main.setGroupProp('inCamera', 'input.useHandCursor', true);	main.callGroupMethod('inCamera', 		'events.onInputDown.add', 'events.onInputDown', [main.onInputDown],		'events.onInputDown.remove', 'events.onInputDown', [main.onInputDown]	);	// ...}

A big difference between my custom groups, and phaser's native groups, is the callMethod() implementation. My groups require a deconstruction method, where phaser groups do not. I notice the groups in phaser seem to call the method on the group object, instead of each individual sprite. My group object is not a PIXI display object, so I call the method on each sprite in the group, and every new sprite that is added to the group. The problem is when sprites are removed- the method still hangs around. To handle this, I require a deconstruction method to be supplied along-side the method-to-be-called. See `main.callGroupMethod` above. As far as properties go, once a sprite is removed from a sectionGroup, the values that the group supplied to that sprite are redefined as `void 0` (undefined)- which is different from simply deleting the property, but in my case functions (almost) exactly the same. The main difference is setting the value to undefined rather than deleting the property, prevents the property from being collected by the garbage-collector- which is ideal because there's a possibility the property will just be redefined again later if the sprite is re-added to the group it was removed from.


A bug I foresee, is when a sprite belongs to more than one group, (say 2). If groupA sets property 'foo' to 5, and then the sprite is added to groupB, who sets 'foo' to 7. If the sprite is then removed from groupB, 'foo' will have a value of undefined. The expected result would be to use the value given to the sprite from groupA after removal from groupB. I'll figure out a fix for this issue when I come to it- but atm I'm not encountering any unexpected behavior from this system.


Then to create an 'herbs' group for each section, I need only do this:


Later, to create a sprite inside this group, I do this:

// Note: Once the sprite is created, you may getSection() using the sprite or sprite-body- as main.getSection simply accepts an object with typical bounding-box params (x,y,width,height).// In this case, the sprite cannot be created until we figure out which section to put it in, so this solves a little chicken-egg problem.self.section = main.getSection({	x: x, 	y: y, 	width: 25, 	height: 25});// ...// Now that we know which section we're using, let's grab the group from that section, and create an herb inside it!var herb = main.	getGroup('herbs', self.section).	create(x, y, 'herb');// herb is a reference to the sprite. Note: only one sprite is created!
Link to comment
Share on other sites


  • Recently Browsing   0 members

    • No registered users viewing this page.
  • Create New...