diff --git a/compile.hxml b/compile.hxml index 2056d411..5d6af326 100644 --- a/compile.hxml +++ b/compile.hxml @@ -3,5 +3,6 @@ -lib hlsdl -lib polygonal-ds -hl marblegame.hl +-D windowSize=1280x720 --main Main -debug \ No newline at end of file diff --git a/src/DtsObject.hx b/src/DtsObject.hx index 2e20b1b9..70dc8e1b 100644 --- a/src/DtsObject.hx +++ b/src/DtsObject.hx @@ -1,5 +1,6 @@ package src; +import shaders.Billboard; import collision.BoxCollisionEntity; import shaders.DtsTexture; import h3d.shader.AlphaMult; diff --git a/src/InstanceManager.hx b/src/InstanceManager.hx index 63e0d406..461ee896 100644 --- a/src/InstanceManager.hx +++ b/src/InstanceManager.hx @@ -1,5 +1,6 @@ package src; +import shaders.Billboard; import shaders.DtsTexture; import h3d.mat.Pass; import h3d.shader.AlphaMult; diff --git a/src/Main.hx b/src/Main.hx index b474e54e..43abad4f 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -1,5 +1,12 @@ package; +import h3d.mat.Data.Blend; +import src.ParticleSystem.ParticleEmitterOptions; +import src.ParticleSystem.ParticleEmitter; +import src.ParticleSystem.Particle; +import src.ParticleSystem.ParticleManager; +import src.ParticleSystem.ParticleData; +import src.ParticleSystem.ParticleData; import shapes.Helicopter; import shapes.ShockAbsorber; import shapes.SuperBounce; @@ -116,7 +123,45 @@ class Main extends hxd.App { ag.y = 6; world.addDtsObject(ag); + // var le:ParticleEmitterOptions = { + // ejectionPeriod: 0.01, + // ambientVelocity: new Vector(0, 0, 0), + // ejectionVelocity: 0.5, + // velocityVariance: 0.25, + // emitterLifetime: 1e8, + // inheritedVelFactor: 0.2, + // particleOptions: { + // texture: 'particles/smoke.png', + // blending: Add, + // spinSpeed: 40, + // spinRandomMin: -90, + // spinRandomMax: 90, + // lifetime: 1, + // lifetimeVariance: 0.15, + // dragCoefficient: 0.8, + // acceleration: 0, + // colors: [new Vector(0.56, 0.36, 0.26, 1), new Vector(0.56, 0.36, 0.26, 0)], + // sizes: [0.5, 1], + // times: [0, 1] + // } + // }; + + // var p1 = new ParticleData(); + // p1.identifier = "testparticle"; + // p1.texture = ResourceLoader.getTexture("data/particles/smoke.png"); + + // // var emitter = new ParticleEmitter(le, p1, world.particleManager); // var p = new Particle(); + // world.particleManager.createEmitter(le, p1, new Vector()); + + // p.position = new Vector(); + // p.color = new Vector(255, 255, 255); + // p.rotation = Math.PI; + // p.scale = 5; + + // world.particleManager.addParticle(p1, p); + // for (i in 0...10) { + // for (j in 0...10) { // var trapdoor = new Tornado(); // trapdoor.x = i * 2; diff --git a/src/Marble.hx b/src/Marble.hx index 57b9cefc..e0933644 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,7 @@ package src; +import src.ParticleSystem.ParticleData; +import src.ParticleSystem.ParticleEmitterOptions; import src.DtsObject; import sdl.Cursor; import hxd.Cursor; @@ -36,6 +38,29 @@ class Move { public function new() {} } +final bounceParticleOptions:ParticleEmitterOptions = { + ejectionPeriod: 1, + ambientVelocity: new Vector(0, 0, 0.0), + ejectionVelocity: 2.6, + velocityVariance: 0.25 * 0.5, + emitterLifetime: 4, + inheritedVelFactor: 0, + particleOptions: { + texture: 'particles/star.png', + blending: Alpha, + spinSpeed: 90, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 500, + lifetimeVariance: 100, + dragCoefficient: 0.5, + acceleration: -2, + colors: [new Vector(0.9, 0, 0, 1), new Vector(0.9, 0.9, 0, 1), new Vector(0.9, 0.9, 0, 0)], + sizes: [0.25, 0.25, 0.25], + times: [0, 0.75, 1] + } +}; + class Marble extends GameObject { public var camera:CameraController; public var cameraObject:Object; @@ -86,6 +111,8 @@ class Marble extends GameObject { var shockAbsorberEnableTime:Float = -1e8; var helicopterEnableTime:Float = -1e8; + var bounceEmitterData:ParticleData; + public function new() { super(); var geom = Sphere.defaultUnitSphere(); @@ -100,6 +127,10 @@ class Marble extends GameObject { this.camera = new CameraController(cast this); this.collider = new SphereCollisionEntity(cast this); + + this.bounceEmitterData = new ParticleData(); + this.bounceEmitterData.identifier = "MarbleBounceParticle"; + this.bounceEmitterData.texture = ResourceLoader.getTexture("data/particles/star.png"); } public function init(level:MarbleWorld) { @@ -307,6 +338,7 @@ class Marble extends GameObject { var velocityAdd = surfaceVel.multiply(-(1 + restitution)); var vAtC = sVel.add(this.omega.cross(contacts[i].normal.multiply(-this._radius))); var normalVel = -contacts[i].normal.dot(sVel); + bounceEmitter(sVel.length() * restitution, contacts[i].normal); vAtC = vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel))); var vAtCMag = vAtC.length(); if (vAtCMag != 0) { @@ -473,6 +505,10 @@ class Marble extends GameObject { return [A, a]; } + function bounceEmitter(speed:Float, normal:Vector) { + this.level.particleManager.createEmitter(bounceParticleOptions, this.bounceEmitterData, this.getAbsPos().getPosition()); + } + function ReportBounce(pos:Vector, normal:Vector, speed:Float) { if (this._bounceYet && speed < this._bounceSpeed) { return; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index b108210a..3f0e59de 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import src.ParticleSystem.ParticleManager; import src.Util; import h3d.Quat; import shapes.PowerUp; @@ -21,6 +22,7 @@ import src.Marble; class MarbleWorld { public var collisionWorld:CollisionWorld; public var instanceManager:InstanceManager; + public var particleManager:ParticleManager; public var interiors:Array = []; public var pathedInteriors:Array = []; @@ -49,6 +51,7 @@ class MarbleWorld { this.collisionWorld = new CollisionWorld(); this.scene = scene; this.instanceManager = new InstanceManager(scene); + this.particleManager = new ParticleManager(cast this); this.sky = new Sky(); sky.dmlPath = "data/skies/sky_day.dml"; sky.init(cast this); @@ -112,6 +115,7 @@ class MarbleWorld { marble.update(currentTime, dt, collisionWorld, this.pathedInteriors); } this.instanceManager.update(dt); + this.particleManager.update(1000 * currentTime, dt); currentTime += dt; if (this.marble != null) { callCollisionHandlers(marble); diff --git a/src/ParticleSystem.hx b/src/ParticleSystem.hx new file mode 100644 index 00000000..4e2658b7 --- /dev/null +++ b/src/ParticleSystem.hx @@ -0,0 +1,363 @@ +package src; + +import h3d.prim.UV; +import h3d.parts.Data.BlendMode; +import src.MarbleWorld; +import src.Util; +import h3d.mat.Data.Wrap; +import shaders.Billboard; +import hxd.IndexBuffer; +import h3d.col.Point; +import h3d.prim.Polygon; +import h3d.prim.MeshPrimitive; +import h3d.mat.Texture; +import h3d.scene.Scene; +import h3d.mat.Material; +import h3d.Vector; +import h3d.scene.MeshBatch; +import h3d.scene.Mesh; + +@:publicFields +class ParticleData { + var texture:Texture; + var identifier:String; + + public function new() {} +} + +@:publicFields +class Particle { + var data:ParticleData; + var manager:ParticleManager; + var o:ParticleOptions; + var position:Vector; + var vel:Vector; + var rotation:Float; + var color:Vector; + var scale:Float; + var lifeTime:Float; + var initialSpin:Float; + var spawnTime:Float; + var currentAge:Float = 0; + var acc:Vector = new Vector(); + + public function new(options:ParticleOptions, manager:ParticleManager, data:ParticleData, spawnTime:Float, pos:Vector, vel:Vector) { + this.o = options; + this.manager = manager; + this.data = data; + this.spawnTime = spawnTime; + this.position = pos; + this.vel = vel; + this.acc = this.vel.multiply(options.acceleration); + + this.lifeTime = this.o.lifetime + this.o.lifetimeVariance * (Math.random() * 2 - 1); + this.initialSpin = Util.lerp(this.o.spinRandomMin, this.o.spinRandomMax, Math.random()); + } + + public function update(time:Float, dt:Float) { + var t = dt; + var a = this.acc; + a = a.sub(this.vel.multiply(this.o.dragCoefficient)); + this.vel = this.vel.add(a.multiply(dt)); + this.position = this.position.add(this.vel.multiply(dt)); + + this.currentAge += dt; + + var elapsed = time - this.spawnTime; + var completion = Util.clamp(elapsed / this.lifeTime, 0, 1); + + if (currentAge > this.lifeTime) // Again, rewind needs this + { + this.manager.removeParticle(this.data, this); + return; + } + + if (completion == 1) { + // The particle can die + this.manager.removeParticle(this.data, this); + return; + } + + // var velElapsed = elapsed / 1000; + // velElapsed *= 0.001; + // velElapsed = Math.pow(velElapsed, (1 - this.o.dragCoefficient)); // Somehow slow down velocity over time based on the drag coefficient + + // // Compute the position + // var pos = this.position.add(this.vel.multiply(velElapsed + this.o.acceleration * (velElapsed * velElapsed) / 2)); + // this.position = pos; + + this.rotation = (this.initialSpin + this.o.spinSpeed * elapsed / 1000) * Math.PI / 180; + + // Check where we are in the times array + var indexLow = 0; + var indexHigh = 1; + for (i in 2...this.o.times.length) { + if (this.o.times[indexHigh] >= completion) + break; + + indexLow = indexHigh; + indexHigh = i; + } + + if (this.o.times.length == 1) + indexHigh = indexLow; + var t = (completion - this.o.times[indexLow]) / (this.o.times[indexHigh] - this.o.times[indexLow]); + + // Adjust color + var color = Util.lerpThreeVectors(this.o.colors[indexLow], this.o.colors[indexHigh], t); + this.color = color; + // this.material.opacity = color.a * * 1.5; // Adjusted because additive mixing can be kind of extreme + + // Adjust sizing + this.scale = Util.lerp(this.o.sizes[indexLow], this.o.sizes[indexHigh], t); + } +} + +typedef ParticleBatch = { + var instances:Array; + var meshBatch:MeshBatch; +} + +/** The options for a single particle. */ +typedef ParticleOptions = { + var texture:String; + + /** Which blending mode to use. */ + var blending:h3d.mat.BlendMode; + + /** The spinning speed in degrees per second. */ + var spinSpeed:Float; + + var spinRandomMin:Float; + var spinRandomMax:Float; + var lifetime:Float; + var lifetimeVariance:Float; + var dragCoefficient:Float; + + /** Acceleration along the velocity vector. */ + var acceleration:Float; + + var colors:Array; + var sizes:Array; + + /** Determines at what percentage of lifetime the corresponding colors and sizes are in effect. */ + var times:Array; +}; + +/** The options for a particle emitter. */ +typedef ParticleEmitterOptions = { + /** The time between particle ejections. */ + var ejectionPeriod:Float; /** A fixed velocity to add to each particle. */ + + var ambientVelocity:Vector; /** The particle is ejected in a random direction with this velocity. */ + + var ejectionVelocity:Float; + + var velocityVariance:Float; + var emitterLifetime:Float; + + /** How much of the emitter's own velocity the particle should inherit. */ + var inheritedVelFactor:Float; /** Computes a spawn offset for each particle. */ + + var ?spawnOffset:Void->Vector; + + var particleOptions:ParticleOptions; +} + +@:publicFields +class ParticleEmitter { + var o:ParticleEmitterOptions; + var data:ParticleData; + var manager:ParticleManager; + var spawnTime:Float; + var lastEmitTime:Float; + var currentWaitPeriod:Float; + var lastPos:Vector; + var lastPosTime:Float; + var currPos:Vector; + var currPosTime:Float; + var creationTime:Float; + var vel = new Vector(); + var getPos:Void->Vector; + + public function new(options:ParticleEmitterOptions, data:ParticleData, manager:ParticleManager, ?getPos:Void->Vector) { + this.o = options; + this.manager = manager; + this.getPos = getPos; + this.data = data; + } + + public function spawn(time:Float) { + this.spawnTime = time; + this.emit(time); + } + + public function tick(time:Float) { + // Cap the amount of particles emitted in such a case to prevent lag + if (time - this.lastEmitTime >= 1000) + this.lastEmitTime = time - 1000; + // Spawn as many particles as needed + while (this.lastEmitTime + this.currentWaitPeriod <= time) { + this.emit(this.lastEmitTime + this.currentWaitPeriod); + var completion = Util.clamp((this.lastEmitTime - this.spawnTime) / this.o.emitterLifetime, 0, 1); + if (completion == 1) { + this.manager.removeEmitter(this); + return; + } + } + } + + /** Emit a single particle. */ + public function emit(time:Float) { + this.lastEmitTime = time; + this.currentWaitPeriod = this.o.ejectionPeriod; + var pos = this.getPosAtTime(time).clone(); + if (this.o.spawnOffset != null) + pos.add(this.o.spawnOffset()); // Call the spawnOffset function if it's there + // This isn't necessarily uniform but it's fine for the purpose. + var randomPointOnSphere = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1).normalized(); + // Compute the total velocity + var vel = this.vel.multiply(this.o.inheritedVelFactor) + .add(randomPointOnSphere.multiply(this.o.ejectionVelocity + this.o.velocityVariance * (Math.random() * 2 - 1))) + .add(this.o.ambientVelocity); + var particle = new Particle(this.o.particleOptions, this.manager, this.data, time, pos, vel); + this.manager.addParticle(data, particle); + } + + /** Computes the interpolated emitter position at a point in time. */ + public function getPosAtTime(time:Float) { + if (this.lastPos == null) + return this.currPos; + var completion = Util.clamp((time - this.lastPosTime) / (this.currPosTime - this.lastPosTime), 0, 1); + return Util.lerpThreeVectors(this.lastPos, this.currPos, completion); + } + + public function setPos(pos:Vector, time:Float) { + this.lastPos = this.currPos; + this.lastPosTime = this.currPosTime; + this.currPos = pos.clone(); + this.currPosTime = time; + this.vel = this.currPos.sub(this.lastPos).multiply(1000 / (this.currPosTime - this.lastPosTime)); + } +} + +class ParticleManager { + var particlebatches:Map = new Map(); + var level:MarbleWorld; + var scene:Scene; + var currentTime:Float; + + var emitters:Array = []; + + public function new(level:MarbleWorld) { + this.level = level; + this.scene = level.scene; + } + + public function update(currentTime:Float, dt:Float) { + this.tick(); + this.currentTime = currentTime; + for (obj => batch in particlebatches) { + for (instance in batch.instances) + instance.update(currentTime, dt); + } + for (obj => batch in particlebatches) { + batch.meshBatch.begin(batch.instances.length); + for (instance in batch.instances) { + if (instance.currentAge != 0) { + batch.meshBatch.setPosition(instance.position.x, instance.position.y, instance.position.z); + var particleShader = batch.meshBatch.material.mainPass.getShader(Billboard); + particleShader.scale = instance.scale; + particleShader.rotation = instance.rotation; + batch.meshBatch.material.blendMode = instance.o.blending; + batch.meshBatch.material.color.load(instance.color); + batch.meshBatch.shadersChanged = true; + batch.meshBatch.setScale(instance.scale); + batch.meshBatch.emitInstance(); + } + } + } + } + + public function addParticle(particleData:ParticleData, particle:Particle) { + if (particlebatches.exists(particleData.identifier)) { + particlebatches.get(particleData.identifier).instances.push(particle); + } else { + var pts = [ + new Point(-0.5, -0.5, 0), + new Point(-0.5, 0.5, 0), + new Point(0.5, -0.5, 0), + new Point(0.5, 0.5) + ]; + var prim = new Polygon(pts); + prim.idx = new IndexBuffer(); + prim.idx.push(0); + prim.idx.push(1); + prim.idx.push(2); + prim.idx.push(1); + prim.idx.push(3); + prim.idx.push(2); + prim.uvs = [new UV(0, 0), new UV(0, 1), new UV(1, 0), new UV(1, 1)]; + prim.addNormals(); + var mat = Material.create(particleData.texture); + mat.mainPass.addShader(new h3d.shader.pbr.PropsValues(1, 0, 0, 1)); + mat.texture.wrap = Wrap.Repeat; + mat.blendMode = Alpha; + var billboardShader = new Billboard(); + mat.mainPass.addShader(billboardShader); + var mb = new MeshBatch(prim, mat, this.scene); + var batch:ParticleBatch = { + instances: [particle], + meshBatch: mb + }; + particlebatches.set(particleData.identifier, batch); + } + } + + public function removeParticle(particleData:ParticleData, particle:Particle) { + if (particlebatches.exists(particleData.identifier)) { + particlebatches.get(particleData.identifier).instances.remove(particle); + } + } + + public function getTime() { + return this.currentTime; + } + + public function createEmitter(options:ParticleEmitterOptions, data:ParticleData, initialPos:Vector, ?getPos:Void->Vector) { + var emitter = new ParticleEmitter(options, data, cast this, getPos); + emitter.currPos = (getPos != null) ? getPos() : initialPos.clone(); + if (emitter.currPos == null) + emitter.currPos = initialPos.clone(); + emitter.currPosTime = this.getTime(); + emitter.creationTime = this.getTime(); + emitter.spawn(this.getTime()); + this.emitters.push(emitter); + return emitter; + } + + public function removeEmitter(emitter:ParticleEmitter) { + this.emitters.remove(emitter); + } + + public function removeEverything() { + for (particle in this.particlebatches) { + particle.instances = []; + } + for (emitter in this.emitters) + this.removeEmitter(emitter); + } + + public function tick() { + var time = this.getTime(); + for (emitter in this.emitters) { + if (emitter.getPos != null) + emitter.setPos(emitter.getPos(), time); + emitter.tick(time); + // Remove the artifact that was created in a different future cause we rewinded and now we shifted timelines + if (emitter.creationTime > time) { + this.removeEmitter(emitter); + } + } + } +} diff --git a/src/Util.hx b/src/Util.hx index d34c9a15..59b02e55 100644 --- a/src/Util.hx +++ b/src/Util.hx @@ -33,7 +33,7 @@ class Util { } public static function lerpThreeVectors(v1:Vector, v2:Vector, t:Float) { - return new Vector(lerp(v1.x, v2.x, t), lerp(v1.y, v2.y, t), lerp(v1.z, v2.z, t)); + return new Vector(lerp(v1.x, v2.x, t), lerp(v1.y, v2.y, t), lerp(v1.z, v2.z, t), lerp(v1.w, v2.w, t)); } public static function rotateImage(bitmap:BitmapData, angle:Float) { diff --git a/src/shaders/Billboard.hx b/src/shaders/Billboard.hx new file mode 100644 index 00000000..537cd719 --- /dev/null +++ b/src/shaders/Billboard.hx @@ -0,0 +1,33 @@ +package shaders; + +class Billboard extends hxsl.Shader { + static var SRC = { + @input var input:{ + var uv:Vec2; + }; + @global var camera:{ + var view:Mat4; + var proj:Mat4; + }; + @global var global:{ + @perObject var modelView:Mat4; + }; + @param var scale:Float; + @param var rotation:Float; + var relativePosition:Vec3; + var projectedPosition:Vec4; + var calculatedUV:Vec2; + function vertex() { + var mid = 0.5; + var uv = input.uv; + calculatedUV.x = cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid; + calculatedUV.y = cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid; + } + function billboard(pos:Vec2, scale:Vec2):Vec4 { + return (vec4(0, 0, 0, 1) * (global.modelView * camera.view) + vec4(pos * scale, 0, 0)); + } + function __init__() { + projectedPosition = billboard(relativePosition.xy, vec2(scale, scale)) * camera.proj; + } + } +} diff --git a/src/shapes/SuperJump.hx b/src/shapes/SuperJump.hx index 475d6e73..47c9ed90 100644 --- a/src/shapes/SuperJump.hx +++ b/src/shapes/SuperJump.hx @@ -1,14 +1,45 @@ package shapes; +import src.ResourceLoader; +import src.ParticleSystem.ParticleData; +import h3d.Vector; import src.DtsObject; +final superJumpParticleOptions:src.ParticleSystem.ParticleEmitterOptions = { + ejectionPeriod: 10, + ambientVelocity: new Vector(0, 0, 0.05), + ejectionVelocity: 1 * 0.5, + velocityVariance: 0.25 * 0.5, + emitterLifetime: 1000, + inheritedVelFactor: 0.1, + particleOptions: { + texture: 'particles/twirl.png', + blending: Add, + spinSpeed: 90, + spinRandomMin: -90, + spinRandomMax: 90, + lifetime: 1000, + lifetimeVariance: 150, + dragCoefficient: 0.25, + acceleration: 0, + colors: [new Vector(0, 0.5, 1, 0), new Vector(0, 0.6, 1, 1), new Vector(0, 0.5, 1, 0)], + sizes: [0.25, 0.25, 0.5], + times: [0, 0.75, 1] + } +}; + class SuperJump extends PowerUp { + var sjEmitterParticleData:ParticleData; + public function new() { super(); this.dtsPath = "data/shapes/items/superjump.dts"; this.isCollideable = false; this.isTSStatic = false; this.identifier = "SuperJump"; + sjEmitterParticleData = new ParticleData(); + sjEmitterParticleData.identifier = "superJumpParticle"; + sjEmitterParticleData.texture = ResourceLoader.getTexture("data/particles/twirl.png"); } public function pickUp():Bool { @@ -18,6 +49,7 @@ class SuperJump extends PowerUp { public function use(time:Float) { var marble = this.level.marble; marble.velocity = marble.velocity.add(this.level.currentUp.multiply(20)); + this.level.particleManager.createEmitter(superJumpParticleOptions, this.sjEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); diff --git a/src/shapes/SuperSpeed.hx b/src/shapes/SuperSpeed.hx index fba09272..6d19b6cc 100644 --- a/src/shapes/SuperSpeed.hx +++ b/src/shapes/SuperSpeed.hx @@ -1,10 +1,42 @@ package shapes; +import src.ResourceLoader; +import src.ParticleSystem.ParticleData; +import src.ParticleSystem.ParticleEmitterOptions; import h3d.Quat; import h3d.Vector; import src.DtsObject; +final superSpeedParticleOptions:ParticleEmitterOptions = { + ejectionPeriod: 5, + ambientVelocity: new Vector(0, 0, 0.2), + ejectionVelocity: 1 * 0.5, + velocityVariance: 0.25 * 0.5, + emitterLifetime: 1100, + inheritedVelFactor: 0.25, + particleOptions: { + texture: 'particles/spark.png', + blending: Add, + spinSpeed: 0, + spinRandomMin: 0, + spinRandomMax: 0, + lifetime: 1500, + lifetimeVariance: 150, + dragCoefficient: 0.25, + acceleration: 0, + colors: [ + new Vector(0.8, 0.8, 0, 0), + new Vector(0.8, 0.8, 0, 1), + new Vector(0.8, 0.8, 0, 0) + ], + sizes: [0.25, 0.25, 1], + times: [0, 0.25, 1] + } +}; + class SuperSpeed extends PowerUp { + var ssEmitterParticleData:ParticleData; + public function new() { super(); this.dtsPath = "data/shapes/items/superspeed.dts"; @@ -12,6 +44,9 @@ class SuperSpeed extends PowerUp { this.isTSStatic = false; this.identifier = "SuperSpeed"; this.useInstancing = true; + ssEmitterParticleData = new ParticleData(); + ssEmitterParticleData.identifier = "superSpeedParticle"; + ssEmitterParticleData.texture = ResourceLoader.getTexture("data/particles/spark.png"); } public function pickUp():Bool { @@ -37,7 +72,7 @@ class SuperSpeed extends PowerUp { // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); - // this.level.particles.createEmitter(superJumpParticleOptions, null, () => Util.vecOimoToThree(marble.body.getPosition())); + this.level.particleManager.createEmitter(superSpeedParticleOptions, this.ssEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); // this.level.deselectPowerUp(); } }