mirror of
https://github.com/kevinroast/CanvasMark.git
synced 2025-12-08 19:46:11 +00:00
448 lines
14 KiB
JavaScript
448 lines
14 KiB
JavaScript
/**
|
|
* Player actor class.
|
|
*
|
|
* @namespace Arena
|
|
* @class Arena.Player
|
|
*/
|
|
(function()
|
|
{
|
|
Arena.Player = function(p, v, h)
|
|
{
|
|
Arena.Player.superclass.constructor.call(this, p, v);
|
|
|
|
this.energy = this.ENERGY_INIT;
|
|
this.radius = 20;
|
|
this.heading = h;
|
|
|
|
// setup weapons
|
|
this.primaryWeapons = [];
|
|
this.primaryWeapons["main"] = new Arena.PrimaryWeapon(this);
|
|
|
|
// 3D sprite object - must be created after constructor call
|
|
var obj = new K3D.K3DObject();
|
|
with (obj)
|
|
{
|
|
drawmode = "wireframe";
|
|
shademode = "depthcue";
|
|
depthscale = 32;
|
|
linescale = 3;
|
|
perslevel = 256;
|
|
|
|
addphi = -1.0; //addphi = 1.0; addtheta = -1.0; addgamma = -0.75;
|
|
scale = 0.8; // TODO: pre-scale points? (this is only done once for player, but enemies...)
|
|
init(
|
|
[{x:-30,y:-20,z:0}, {x:-15,y:-25,z:20}, {x:15,y:-25,z:20}, {x:30,y:-20,z:0}, {x:15,y:-25,z:-20}, {x:-15,y:-25,z:-20}, {x:0,y:35,z:0}],
|
|
[{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:4}, {a:4,b:5}, {a:5,b:0}, {a:1,b:6}, {a:2,b:6}, {a:4,b:6}, {a:5,b:6}, {a:0,b:6}, {a:3,b:6}],
|
|
[{vertices:[0,1,6]}, {vertices:[1,2,6]}, {vertices:[2,3,6]}, {vertices:[3,4,6]}, {vertices:[4,5,6]}, {vertices:[5,0,6]}, {vertices:[0,1,2,3,4,5]}]
|
|
);
|
|
}
|
|
this.setK3DObject(obj);
|
|
|
|
return this;
|
|
};
|
|
|
|
extend(Arena.Player, Arena.K3DActor,
|
|
{
|
|
MAX_PLAYER_VELOCITY: 15.0,
|
|
THRUST_DELAY: 1,
|
|
ENERGY_INIT: 100,
|
|
|
|
/**
|
|
* Player heading
|
|
*/
|
|
heading: 0,
|
|
|
|
/**
|
|
* Player energy level
|
|
*/
|
|
energy: 0,
|
|
|
|
/**
|
|
* Primary weapon list
|
|
*/
|
|
primaryWeapons: null,
|
|
|
|
/**
|
|
* Engine thrust recharge counter
|
|
*/
|
|
thrustRecharge: 0,
|
|
|
|
/**
|
|
* True if the engine thrust graphics should be rendered next frame
|
|
*/
|
|
engineThrust: false,
|
|
|
|
/**
|
|
* Frame that the player was killed on - to cause a delay before respawning the player
|
|
*/
|
|
killedOnFrame: 0,
|
|
|
|
/**
|
|
* Power up settings - primary weapon bounce
|
|
*/
|
|
bounceWeapons: false,
|
|
|
|
/**
|
|
* Player rendering method
|
|
*
|
|
* @param ctx {object} Canvas rendering context
|
|
* @param world {object} World metadata
|
|
*/
|
|
onRender: function onRender(ctx, world)
|
|
{
|
|
var headingRad = this.heading * RAD;
|
|
|
|
// transform world to screen - non-visible returns null
|
|
var viewposition = Game.worldToScreen(this.position, world, this.radius);
|
|
if (viewposition)
|
|
{
|
|
// render engine thrust?
|
|
if (this.engineThrust)
|
|
{
|
|
ctx.save();
|
|
// scale ALL graphics... - translate to position apply canvas scaling
|
|
ctx.translate(viewposition.x, viewposition.y);
|
|
ctx.scale(world.scale, world.scale);
|
|
ctx.rotate(headingRad);
|
|
ctx.translate(0, -4); // slight offset so that collision radius is centered
|
|
ctx.globalAlpha = 0.4 + Rnd() * 0.5;
|
|
ctx.shadowColor = ctx.fillStyle = "rgb(25,125,255)";
|
|
ctx.beginPath();
|
|
ctx.moveTo(-12, 20);
|
|
ctx.lineTo(12, 20);
|
|
ctx.lineTo(0, 50 + Rnd() * 20);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.restore();
|
|
this.engineThrust = false;
|
|
}
|
|
|
|
// render player graphic
|
|
ctx.save();
|
|
ctx.shadowColor = "rgb(255,255,255)";
|
|
ctx.translate(viewposition.x, viewposition.y);
|
|
ctx.scale(world.scale, world.scale);
|
|
ctx.rotate(headingRad);
|
|
ctx.translate(0, -4); // slight offset so that collision radius is centered
|
|
|
|
// render 3D sprite
|
|
this.renderK3D(ctx);
|
|
|
|
ctx.restore();
|
|
}
|
|
//if (DEBUG && !viewposition) console.log("non-visible: " + new Date().getTime());
|
|
},
|
|
|
|
/**
|
|
* Handle key input to rotate and move the player
|
|
*/
|
|
handleInput: function handleInput(input)
|
|
{
|
|
var h = this.heading % 360;
|
|
|
|
// TODO: hack, fix this to maintain +ve heading or change calculation below...
|
|
if (h < 0) h += 360;
|
|
|
|
// first section tweens the current rendered heading of the player towards
|
|
// the desired heading - but the next section actually applies a vector
|
|
// TODO: this seems over complicated - must be an easier way to do this...
|
|
if (input.left)
|
|
{
|
|
if (h > 270 || h < 90)
|
|
{
|
|
if (h > 270) this.heading -= ((h - 270) * 0.2);
|
|
else this.heading -= ((h + 90) * 0.2);
|
|
}
|
|
else this.heading += ((270 - h) * 0.2);
|
|
}
|
|
if (input.right)
|
|
{
|
|
if (h < 90 || h > 270)
|
|
{
|
|
if (h < 90) this.heading += ((90 - h) * 0.2);
|
|
else this.heading += ((h - 90) * 0.2);
|
|
}
|
|
else this.heading -= ((h - 90) * 0.2);
|
|
}
|
|
if (input.up)
|
|
{
|
|
if (h < 180)
|
|
{
|
|
this.heading -= (h * 0.2);
|
|
}
|
|
else this.heading += ((360 - h) * 0.2);
|
|
}
|
|
if (input.down)
|
|
{
|
|
if (h < 180)
|
|
{
|
|
this.heading += ((180 - h) * 0.2);
|
|
}
|
|
else this.heading -= ((h - 180) * 0.2);
|
|
}
|
|
|
|
// second section applies the direct thrust angled vector
|
|
// this ensures a snappy control method with the above heading effect
|
|
var angle = null;
|
|
if (input.left)
|
|
{
|
|
if (input.up) angle = 315;
|
|
else if (input.down) angle = 225;
|
|
else angle = 270;
|
|
}
|
|
else if (input.right)
|
|
{
|
|
if (input.up) angle = 45;
|
|
else if (input.down) angle = 135;
|
|
else angle = 90;
|
|
}
|
|
else if (input.up)
|
|
{
|
|
if (input.left) angle = 315;
|
|
else if (input.right) angle = 45;
|
|
else angle = 0;
|
|
}
|
|
else if (input.down)
|
|
{
|
|
if (input.left) angle = 225;
|
|
else if (input.right) angle = 135;
|
|
else angle = 180;
|
|
}
|
|
if (angle !== null)
|
|
{
|
|
this.thrust(angle);
|
|
}
|
|
else
|
|
{
|
|
// reduce thrust over time if player isn't actively moving
|
|
this.vector.scale(0.9);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Execute player forward thrust request
|
|
* Automatically a delay is used between each application - to ensure stable thrust on all machines.
|
|
*/
|
|
thrust: function thrust(angle)
|
|
{
|
|
// now test we did not thrust too recently - to stop fast key repeat issues
|
|
if (GameHandler.frameCount - this.thrustRecharge > this.THRUST_DELAY)
|
|
{
|
|
// update last frame count
|
|
this.thrustRecharge = GameHandler.frameCount;
|
|
|
|
// generate a small thrust vector
|
|
var t = new Vector(0.0, -2.00);
|
|
|
|
// rotate thrust vector by player current heading
|
|
t.rotate(angle * RAD);
|
|
|
|
// add player thrust vector to position
|
|
this.vector.add(t);
|
|
|
|
// player can't exceed maximum velocity - scale vector down if
|
|
// this occurs - do this rather than not adding the thrust at all
|
|
// otherwise the player cannot turn and thrust at max velocity
|
|
if (this.vector.length() > this.MAX_PLAYER_VELOCITY)
|
|
{
|
|
this.vector.scale(this.MAX_PLAYER_VELOCITY / this.vector.length());
|
|
}
|
|
}
|
|
// mark so that we know to render engine thrust graphics
|
|
this.engineThrust = true;
|
|
},
|
|
|
|
damageBy: function damageBy(enemy)
|
|
{
|
|
this.energy -= enemy.playerDamage;
|
|
if (this.energy <= 0)
|
|
{
|
|
this.energy = 0;
|
|
this.kill();
|
|
}
|
|
},
|
|
|
|
kill: function kill()
|
|
{
|
|
this.alive = false;
|
|
this.killedOnFrame = GameHandler.frameCount;
|
|
},
|
|
|
|
/**
|
|
* Fire primary weapon(s)
|
|
* @param bulletList {Array} to add bullet(s) to on success
|
|
* @param heading {Number} bullet heading
|
|
*/
|
|
firePrimary: function firePrimary(bulletList, vector, heading)
|
|
{
|
|
// attempt to fire the primary weapon(s)
|
|
// first ensure player is alive
|
|
if (this.alive)
|
|
{
|
|
for (var w in this.primaryWeapons)
|
|
{
|
|
var b = this.primaryWeapons[w].fire(vector, heading);
|
|
if (b)
|
|
{
|
|
for (var i=0; i<b.length; i++)
|
|
{
|
|
bulletList.push(b[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
reset: function reset(persistPowerUps)
|
|
{
|
|
// reset energy, alive status, weapons and power up flags
|
|
this.alive = true;
|
|
if (!persistPowerUps)
|
|
{
|
|
// reset weapons
|
|
this.primaryWeapons = [];
|
|
this.primaryWeapons["main"] = new Arena.PrimaryWeapon(this);
|
|
|
|
// reset powerup settings
|
|
this.bounceWeapons = false;
|
|
}
|
|
this.energy = this.ENERGY_INIT;
|
|
}
|
|
});
|
|
})();
|
|
|
|
|
|
/**
|
|
* Player score multiplier collectable class.
|
|
*
|
|
* @namespace Arena
|
|
* @class Arena.Multiplier
|
|
*/
|
|
(function()
|
|
{
|
|
Arena.Multiplier = function(p, v, h)
|
|
{
|
|
Arena.Multiplier.superclass.constructor.call(this, p, v, this.LIFESPAN);
|
|
this.radius = 10;
|
|
this.rotation = 0;
|
|
return this;
|
|
};
|
|
|
|
extend(Arena.Multiplier, Game.EffectActor,
|
|
{
|
|
LIFESPAN: 250,
|
|
FADE_LENGTH: 16,
|
|
rotation: 0,
|
|
|
|
/**
|
|
* Multiplier collectable rendering method
|
|
*
|
|
* @param ctx {object} Canvas rendering context
|
|
* @param world {object} World metadata
|
|
*/
|
|
onRender: function onRender(ctx, world)
|
|
{
|
|
// transform world to screen - non-visible returns null
|
|
var viewposition = Game.worldToScreen(this.position, world, this.radius);
|
|
if (viewposition)
|
|
{
|
|
var r = this.radius * 0.6;
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
if (this.lifespan < this.FADE_LENGTH)
|
|
{
|
|
ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
|
|
}
|
|
ctx.strokeStyle = ctx.shadowColor = "rgb(255,180,0)";
|
|
ctx.translate(viewposition.x, viewposition.y);
|
|
ctx.scale(world.scale, world.scale);
|
|
ctx.rotate(this.rotation);
|
|
ctx.strokeRect(-r, -r, this.radius*1.2, this.radius*1.2);
|
|
ctx.restore();
|
|
this.rotation += 0.02;
|
|
}
|
|
},
|
|
|
|
collected: function collected(game, player, scene)
|
|
{
|
|
if (++game.scoreMultiplier % 10 === 0)
|
|
{
|
|
// display multiplier to player every large increment
|
|
var vec = new Vector(0, -5.0).add(this.vector);
|
|
scene.effects.push(new Arena.TextIndicator(
|
|
this.position.clone(), vec, "x" + game.scoreMultiplier, 32, "white", 32));
|
|
}
|
|
}
|
|
});
|
|
})();
|
|
|
|
|
|
/**
|
|
* Player energy boost powerup collectable class.
|
|
*
|
|
* @namespace Arena
|
|
* @class Arena.EnergyBoostPowerup
|
|
*/
|
|
(function()
|
|
{
|
|
Arena.EnergyBoostPowerup = function(p, v, h)
|
|
{
|
|
Arena.EnergyBoostPowerup.superclass.constructor.call(this, p, v, this.LIFESPAN);
|
|
this.radius = 12;
|
|
this.rotation = 0;
|
|
return this;
|
|
};
|
|
|
|
extend(Arena.EnergyBoostPowerup, Game.EffectActor,
|
|
{
|
|
LIFESPAN: 350,
|
|
FADE_LENGTH: 16,
|
|
rotation: 0,
|
|
|
|
/**
|
|
* EnergyBoostPowerup collectable rendering method
|
|
*
|
|
* @param ctx {object} Canvas rendering context
|
|
* @param world {object} World metadata
|
|
*/
|
|
onRender: function onRender(ctx, world)
|
|
{
|
|
// transform world to screen - non-visible returns null
|
|
var viewposition = Game.worldToScreen(this.position, world, this.radius);
|
|
if (viewposition)
|
|
{
|
|
var r = this.radius * 0.6;
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
if (this.lifespan < this.FADE_LENGTH)
|
|
{
|
|
ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
|
|
}
|
|
ctx.lineWidth = 2.0;
|
|
ctx.strokeStyle = ctx.shadowColor = "rgb(100,255,0)";
|
|
ctx.translate(viewposition.x, viewposition.y);
|
|
ctx.scale(world.scale, world.scale);
|
|
ctx.rotate(this.rotation);
|
|
ctx.strokeRect(-r, -r, this.radius*1.2, this.radius*1.2);
|
|
ctx.restore();
|
|
this.rotation += 0.05;
|
|
}
|
|
},
|
|
|
|
collected: function collected(game, player, scene)
|
|
{
|
|
// increment player energy
|
|
player.energy += 25;
|
|
if (player.energy > player.ENERGY_INIT)
|
|
{
|
|
player.energy = player.ENERGY_INIT;
|
|
}
|
|
|
|
// display indicator
|
|
var vec = new Vector(0, -5.0).add(this.vector);
|
|
scene.effects.push(new Arena.TextIndicator(
|
|
this.position.clone(), vec, "Energy Boost!", 32, "white", 32));
|
|
}
|
|
});
|
|
})();
|