Open in JSFiddleOpen in JSFiddle or use the embedded snippet below:
var Degree = Math.PI / 180;
var showDebugOverlays = false;
var Turret = {
gunAngle: -10 * Degree,
maxGunAngle: 85 * Degree,
adaptedGunXOffset: null,
rotateToTarget: function (target) {
var delta = Vec.subtract(this.position, target.position);
var dist = Vec.length(delta);
var angle = Vec.angle(delta);
theta = Math.asin(this.adaptedGunXOffset / dist) + this.gunAngle;
this.rotation = -(angle + theta);
this.updateGunRay(target);
},
setGunAngle: function (angle) {
var angle = this.clampGunAngle(angle);
this.gunAngle = angle;
this.gun.rotation = angle;
// Account for the fact that the origin of the gun also has an y offset
// relative to the turret origin
var extraXOffset = this.gun.position.y * Math.tan(angle);
var gunXOffset = this.gun.position.x + extraXOffset;
// This equals "a * cos(δ')" in the angle formula
this.adaptedGunXOffset = gunXOffset * Math.cos(-angle);
if (showDebugOverlays) {
// Show x offsets
this.removeChild(this.xOffsetOverlay);
this.removeChild(this.extraXOffsetOverlay);
this.xOffsetOverlay = addRect(this, 0, 0, this.gun.position.x, 1, 0xf6ff00);
this.extraXOffsetOverlay = addRect(this, this.gun.position.x, 0, extraXOffset, 1, 0xff00ae);
}
},
rotateGun: function (angleDelta) {
this.setGunAngle(this.gunAngle + angleDelta);
},
updateGunRay: function (target) {
var delta = this.gun.toLocal(target.position);
var dist = Vec.length(delta);
this.gun.removeChild(this.gun.ray);
this.gun.ray = makeLine(0, 0, 0, -dist);
this.gun.addChildAt(this.gun.ray, 0);
},
clampGunAngle: function (angle) {
if (angle > this.maxGunAngle) {
return this.maxGunAngle;
}
if (angle < -this.maxGunAngle) {
return -this.maxGunAngle;
}
return angle;
}
}
function makeTurret() {
var turret = new PIXI.Sprite.fromImage('http://i.imgur.com/gPtlPJh.png');
var gunPos = new PIXI.Point(25, -25)
turret.anchor.set(0.5, 0.5);
var gun = new PIXI.Container();
var gunImg = new PIXI.Sprite.fromImage('http://i.imgur.com/RE45GEY.png');
gun.ray = makeLine(0, 0, 0, -250);
gun.addChild(gun.ray);
gunImg.anchor.set(0.5, 0.6);
gun.addChild(gunImg);
gun.position = gunPos;
// Turret forward vector
turret.addChild(makeLine(0, -38, 0, -90, 0x38ce2c));
turret.addChild(gun);
turret.gun = gun;
Object.setPrototypeOf(Turret, Object.getPrototypeOf(turret));
Object.setPrototypeOf(turret, Turret);
turret.setGunAngle(turret.gunAngle);
if (showDebugOverlays) {
addRect(turret, 0, 0, 1, 1); // Show turret origin
addRect(gun, -1, 1, 2, 2, 0xff0096); // Show gun origin
}
return turret;
}
function makeTarget() {
var target = new PIXI.Graphics();
target.beginFill(0xd92f8f);
target.drawCircle(0, 0, 9);
target.endFill();
return target;
}
var CursorKeys = {
map: { ArrowLeft: -1, ArrowRight: 1 },
pressedKeyDirection: null,
onKeyDown: function (keyEvent) {
var key = this.map[keyEvent.key];
if (key) {
this.pressedKeyDirection = key;
}
},
onKeyUp: function (keyEvent) {
var key = this.map[keyEvent.key];
if (key) {
if (this.pressedKeyDirection == key) {
this.pressedKeyDirection = null;
}
}
}
}
document.body.addEventListener("keydown", CursorKeys.onKeyDown.bind(CursorKeys));
document.body.addEventListener("keyup", CursorKeys.onKeyUp.bind(CursorKeys));
function makeLine(x1, y1, x2, y2, color) {
if (color == undefined) {
color = 0x66CCFF;
}
var line = new PIXI.Graphics();
line.lineStyle(1.5, color, 1);
line.moveTo(x1, y1);
line.lineTo(x2, y2);
return line;
}
function addRect(parent, x, y, w, h, color) {
if (color == undefined) {
color = 0x66CCFF;
}
var rectangle = new PIXI.Graphics();
rectangle.beginFill(color);
rectangle.drawRect(x, y, w, h);
rectangle.endFill();
parent.addChild(rectangle);
return rectangle;
}
var Vec = {
subtract: function (a, b) {
return { x: a.x - b.x,
y: a.y - b.y };
},
length: function (v) {
return Math.sqrt(v.x * v.x + v.y * v.y);
},
angle: function (v) {
return Math.atan2(v.x, v.y)
}
}
Math.clamp = function(n, min, max) {
return Math.max(min, Math.min(n, max));
}
Math.clamp = function(n, min, max) {
return Math.max(min, Math.min(n, max));
}
var renderer;
var stage;
var turret;
var target;
function run() {
renderer = PIXI.autoDetectRenderer(600, 300, { antialias: true });
renderer.backgroundColor = 0x2a2f34;
document.body.appendChild(renderer.view);
stage = new PIXI.Container();
stage.interactive = true;
target = makeTarget();
target.position = { x: renderer.width * 0.2, y: renderer.height * 0.3 };
turret = makeTurret();
turret.position = { x: renderer.width * 0.65, y: renderer.height * 0.3 };
turret.rotateToTarget(target);
stage.addChild(turret);
stage.addChild(target);
var message = new PIXI.Text(
"Controls: Mouse, left/right cursor keys",
{font: "18px Arial", fill: "#7c7c7c"}
);
message.position.set(10, 10);
stage.addChild(message);
stage.on('mousemove', function(e) {
var pos = e.data.global;
target.position.x = Math.clamp(pos.x, 0, renderer.width);
target.position.y = Math.clamp(pos.y, 0, renderer.height);
turret.rotateToTarget(target);
})
animate();
}
function animate() {
requestAnimationFrame(animate);
if (CursorKeys.pressedKeyDirection) {
turret.rotateGun(3 * Degree * CursorKeys.pressedKeyDirection);
turret.rotateToTarget(target);
}
renderer.render(stage);
}
run();
body {
padding: 0;
margin: 0;
}
#capture_focus {
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.2.2/pixi.min.js"></script>
<div id="capture_focus" />