timetocode Posted January 29, 2015 Share Posted January 29, 2015 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: create a class called Pirate, inherits from DisplayObjectContainer add a main ship sprite, alpha set to 1 add an explosion effect (itself a complicated entity), disabled initially and alpha set to 0 add debris sprites that break apart when the ship is destroyed, alpha set to 0 add a hitpoint bar add matrix+filters for a mouse over effect when player targets the pirate check a 'state' variable once per frame, to see when pirate has died 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=t3udYwZOO50function 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 = PirateHere'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 Quote Link to comment Share on other sites More sharing options...
digibo Posted January 29, 2015 Share Posted January 29, 2015 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. Quote Link to comment Share on other sites More sharing options...
xerver Posted January 30, 2015 Share Posted January 30, 2015 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.