Jump to content

DisplayObjectContainer per complex entity?


timetocode
 Share

Recommended Posts

I've just been making it up as I go with pixi, and I wanted a sanity check for my approach to entities. My entities have a lot of functionality and effects, but cause FPS drops when about 100 are on screen. Is that appropriate given how complex they are? Is it most likely caused by one specific thing? Is any of this approach decent?

 

As a basic overview, here's how my Pirate class goes:

 

  1. create a class called Pirate, inherits from DisplayObjectContainer
  2. add a main ship sprite, alpha set to 1
  3. add an explosion effect (itself a complicated entity), disabled initially and alpha set to 0
  4. add debris sprites that break apart when the ship is destroyed, alpha set to 0
  5. add a hitpoint bar
  6. add matrix+filters for a mouse over effect when player targets the pirate
  7. check a 'state' variable once per frame, to see when pirate has died
  8. once per frame, run the appropriate logic for the state the pirate is in (alive: do nothing, dead: play explosion effect, etc)

So to summarize, entities are DisplayObjectContainers containing a bunch of art assets, most of which are invisible at any given point in time. A state variable is monitored by the client, and used to decided which art, effects, etc. to show at any given time. The design has been moving more and more to a component-based pattern, as shown here it is still just a hybrid.

 

Oh, and before I forget, the entities in the game are only clientside graphics! All actual logic relating to the ai/movement/etc occurs on the server at 20 fps. The client interpolates all entities at 60 fps for nice smooth movement. I don't think this causes the main performance issue. When profiled, 18% of the total time is spent in WebGLRenderer.render, and 1.5% in the interpolation.

 

Here's an example of how I create my jellyfish pirate ship. No need to read the code too thoroughly, it's all just a rough outline. There are sounds (howler.js) and object pooling, but I've cut most of that stuff out for now.  Here's a very short video of what it looks like: https://www.youtube.com/watch?v=t3udYwZOO50

function Pirate() {	PIXI.DisplayObjectContainer.call(this)	// for clientside prediction of collisions        this.collider = new CircleCollider(this.x, this.y, 15)        // shows a medium explosion when destroyed	this.explosionEffect = new ExplosionEffect(2)	this.addChild(this.explosionEffect)        // anything that moves/rotates with the ship goes in shipContainer	this.shipContainer = new PIXI.DisplayObjectContainer()        // the art for the ship when alive and pristine	this.ship = new PIXI.Sprite(PIXI.Texture.fromFrame('jellyfish-spaceship.png'))	//this.ship.tint = 0x8888ff	this.ship.anchor.x = this.ship.anchor.y = 0.5        // broken up pieces of the ship        // these sprites all carefully line up to create a whole ship	this.debris = []	this.debris.push(		new PIXI.Sprite(PIXI.Texture.fromFrame('jellyfish-debris0.png')))	this.debris.push(		new PIXI.Sprite(PIXI.Texture.fromFrame('jellyfish-debris1.png')))	this.debris.push(		new PIXI.Sprite(PIXI.Texture.fromFrame('jellyfish-debris2.png')))	this.debris.push(		new PIXI.Sprite(PIXI.Texture.fromFrame('jellyfish-debris3.png')))        // initialize the debris with an alpha of 0	for (var i = 0; i < this.debris.length; i++) {		var debris = this.debris[i]		this.shipContainer.addChild(debris)		debris.alpha = 0		debris.anchor.x = debris.anchor.y = 0.5	}	this.hpBar = new HitpointBar()	/* // debug, most recent server position w/o entity interp	this.serverPosition = new PIXI.Graphics()	this.serverPosition.lineStyle(2, 0xff0000, 1)	this.serverPosition.drawCircle(0, 0, 15)	*/	this.shipContainer.addChild(this.ship)	this.addChild(this.shipContainer)        // the hp bar goes outside of shipContainer, so that it does not rotate	this.addChild(this.hpBar)	//this.addChild(this.serverPosition)	this._shipRot = null	this.interactive = true	        // used for mouse over highlight effects	var colorMatrix =  [	    1,0,0,0,	    0,1,0,0,	    0,0,1,0,	    0,0,0,1	];	var filter = new PIXI.ColorMatrixFilter();	filter.matrix = colorMatrix;        // skipped code: mouse over effects that create a highlight via color matrix        // skipped code: events fired that cause gui to change cursor}Pirate.prototype = Object.create(PIXI.DisplayObjectContainer.prototype)Pirate.protoype.constructor = Pirate

Here's where I hackishly check for a state change, and begin the death animation effects:

Object.defineProperty(Pirate.prototype, 'state', {	get: function() {		return this._state	},	set: function(value) {		// Spawn to Alive		if (this._state === EntityState.Spawn 			&& value === EntityState.Alive) {			// no spawn effect for this entity		}		// Alive to Dead		if (this._state === EntityState.Alive 			&& value === EntityState.Dead) {	                // hide the pristine ship, show explosion			this.ship.alpha = 0			this.explosionEffect.alpha = 1			this.explosionEffect.begin()                                                // show the debris and break the ship apart			for (var i = 0; i < this.debris.length; i++) {				var debris = this.debris[i]				debris.alpha = 1.0                                // vx, vy represent velocity				debris.vx = MathEx.random(-15, 15)				debris.vy = MathEx.random(-15, 15)				debris.rotVelocity = MathEx.random(-5, 5)			}		}		this._state = value	}})

I'll note that the above code is a getter/setter with side effects, which I've been told is bad form. I intend to move an identical check into update() and then abstract it away into a base entity class. Then when I do make a concrete class, it might have the same logic in sections like onSpawn, onDeath, etc.

 

And then here is the somewhat messy update() function that runs once per frame:

Pirate.prototype.update = function(delta) {        // update HP bar //TODO: encapsulate the hp bar so we can call hpBar.update(delta) and be done        // the hp bar effect is created by growing the empty part	this.hpBar.empty.scale.x = -(this.maxHullHp - this.hullHp) / this.maxHullHp        // logic if dying	if (this.state === EntityState.Dead) {		this.explosionEffect.update(delta)                // debris moves slowly apart and spins, fades to nothing slowly		for (var i = 0; i < this.debris.length; i++) {			var debris = this.debris[i]			debris.x += debris.vx * delta * 0.1			debris.y += debris.vy * delta * 0.1			debris.rotation += debris.rotVelocity * delta * 0.1			debris.alpha -= 0.35 * delta		}                // fade out the hp bar		this.hpBar.alpha -= 0.35 * delta	}        // this ship doesn't have a particular animation that goes with it, but        // here is approximately how the animation code would go if it did	/*	if (Date.now() - this.lastFrameTimestamp > this.animationFrameDelay) {			this.frameIndex++		this.ship.gotoAndStop(this.frameIndex)		this.lastFrameTimestamp = Date.now()		if (this.frameIndex > this.totalFrameCount) {			this.frameIndex = 0		}	}	*/        // skipped: call to this.finalize() which marks this entity for return to        // the object pool}

So basically if the entityState is dead, the explosion and debris spreading apart gets to play. Other entities in my game have more significant logic for other states such as spawning effects or animations while alive.

 

Thoughts?? Suggestions?

Thanks for reading

Link to comment
Share on other sites

The first thing I would try would be to set visible = false to Sprites/Containers that shouldn't be rendered and only set it to true when they should appear.

 

I haven't looked at the PIXI code, but it is very possible that there is still some processing/rendering being done for objects with alpha=0. I've seen engines that work this way.

Link to comment
Share on other sites

The first thing I would try would be to set visible = false to Sprites/Containers that shouldn't be rendered and only set it to true when they should appear.

 

I haven't looked at the PIXI code, but it is very possible that there is still some processing/rendering being done for objects with alpha=0. I've seen engines that work this way.

 

We still update the object transforms when alpha = 0. So yes stuff still happens.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

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