Built with blockbuilder.org
Last active
October 15, 2015 18:44
-
-
Save adlaika/a4947846696ab623ce53 to your computer and use it in GitHub Desktop.
Run!Shoot!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<style> | |
body { | |
position: relative; | |
background-color: lightgray; | |
} | |
.top-container { | |
width: 650px; | |
} | |
.scoreboard { | |
float: left; | |
background-color: lightgray; | |
} | |
.instructions { | |
float: right; | |
} | |
.board { | |
width: 650px; | |
background-color: lightgray; | |
cursor: none; | |
} | |
.svg-canvas { | |
border: 1px solid black; | |
margin-right: auto; | |
margin-left: auto; | |
} | |
.crosshairs { | |
fill: yellow; | |
} | |
.player { | |
fill: white; | |
} | |
.enemy { | |
fill: green; | |
} | |
.bullet { | |
fill: white; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="top-container"> | |
<div class="scoreboard"> | |
<!-- Modify the scoreboard when important events occur in your game! --> | |
<div class="high">High score: <span id="high">0</span></div> | |
<div class="current">Time Alive: <span id="current">0</span></div> | |
<div class="kills">Kills: <span id ="kills">0</span></div> | |
<div class="health">Health: <span id ="health">100</span></div> | |
</div> | |
<div class="instructions"> | |
<div>WASD controls movement!</div> | |
<div>Mouse controls aim! Click to shoot!</div> | |
<div>Space pauses! And starts the game!</div> | |
<div>Don't touch the green guys!</div> | |
<div>Reload to play again!</div> | |
</div> | |
</div> | |
<div class="board"></div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js'></script> | |
<script src='https://code.jquery.com/jquery-2.1.4.min.js'></script> | |
<script> | |
//---SETTINGS--- | |
var gameOptions = { | |
width: 650, | |
height: 600, | |
friction: 0.9 | |
}; | |
var gameStats = { | |
timeLived: 0, | |
highScore: 0, | |
kills: 0, | |
health: 100, | |
}; | |
var updateScore = function () { | |
d3.select('.scoreboard .current span').text(gameStats.timeLived); | |
d3.select('.scoreboard .highscore span').text(gameStats.highScore); | |
d3.select('.scoreboard .kills span').text(gameStats.kills); | |
}; | |
//---HELPERS--- | |
var pixelize = function (n) { | |
return n + 'px'; | |
}; | |
//Two functions to convert location from coordinates to pixels. | |
//Example: toPixelAxes.x(gameOptions.height) --> 100 | |
var toPixelAxes = { | |
x: d3.scale.linear().domain([0, 100]).range([0, gameOptions.width]).clamp(true), | |
y: d3.scale.linear().domain([0, 100]).range([0, gameOptions.height]).clamp(true) | |
}; | |
//Two functions to convert location from pixels to coordinates | |
var toCoordAxes = { | |
x: d3.scale.linear().domain([0, gameOptions.width]).range([0, 100]).clamp(true), | |
y: d3.scale.linear().domain([0, gameOptions.height]).range([0, 100]).clamp(true) | |
}; | |
//check if in bounds IN PIXELS | |
var inBounds = function (x, y) { | |
var result = false; | |
if (x < gameOptions.height && x > 0 && y < gameOptions.width && y > 0) { | |
result = true; | |
} | |
return result; | |
} | |
var isMaxedOut = function (loc) { | |
if ((loc[0] > gameOptions.width) || (loc[0] < 0) || (loc[1] > gameOptions.height) || (loc[1] < 0)) { | |
return true; | |
} | |
return false; | |
} | |
//return random number between two constraints | |
var rand = function (nBot, nTop) { | |
return Math.floor(Math.random() * nTop) + nBot; | |
}; | |
//return random coords within game board | |
var randX = function () { | |
return rand(0, gameOptions.width); | |
}; | |
var randY = function () { | |
return rand(0, gameOptions.height); | |
}; | |
//---GAME ELEMENT CONSTRUCTORS--- | |
var GameElement = function (loc, size) { | |
this.loc = [loc[0], loc[1]]; | |
this.size = size; | |
this.speed = 1; | |
}; | |
//all game elements have a function that creates them on board | |
GameElement.prototype.draw = function (htmlClass) { | |
board | |
.append('svg:circle') | |
.attr('class', htmlClass) | |
.attr('cx', pixelize(this.loc[0])) | |
.attr('cy', pixelize(this.loc[1])) | |
.attr('stroke', 'black') | |
.attr('r', this.size) | |
}; | |
GameElement.prototype.move = function (x, y) { | |
this.loc[0] += x * this.speed; | |
this.loc[1] += y * this.speed; | |
}; | |
//for player shots, call with 'player.shoot(crosshairs.loc);' | |
var bulletObjs = []; | |
GameElement.prototype.shoot = function (targetLoc) { | |
var x = this.loc[0]; | |
var y = this.loc[1]; | |
var newBullet = new Bullet(this.loc, 2); | |
newBullet.draw('bullet'); | |
newBullet.targetLoc = targetLoc; | |
var run = targetLoc[0] - newBullet.loc[0]; | |
var rise = targetLoc[1] - newBullet.loc[1]; | |
var length = Math.sqrt((rise * rise) + (run * run)); | |
newBullet.unitX = run / length; | |
newBullet.unitY = rise / length; | |
bulletObjs.push(newBullet); | |
} | |
//bullet constructor | |
var Bullet = function () { | |
GameElement.apply(this, arguments); | |
this.speed = 20; | |
}; | |
Bullet.prototype = Object.create(GameElement.prototype); | |
Bullet.prototype.constructor = Bullet; | |
Bullet.prototype.fire = function () { | |
this.move(this.unitX, this.unitY); | |
} | |
Bullet.prototype.die = function () { | |
for (var j = 0; j < bulletObjs.length; j++) { | |
if (this === bulletObjs[j]) { | |
d3.selectAll('.bullet') | |
.filter(function (d, i) { | |
return i === j; | |
}) | |
.remove(); | |
bulletObjs.splice(j, 1); | |
return; | |
} | |
} | |
}; | |
//player constructor | |
var Player = function () { | |
GameElement.apply(this, arguments); | |
this.speed = 2; | |
}; | |
Player.prototype = Object.create(GameElement.prototype); | |
Player.prototype.constructor = Player; | |
//crosshairs constructor | |
var Crosshairs = function () { | |
GameElement.apply(this, arguments); | |
}; | |
Crosshairs.prototype = Object.create(GameElement.prototype); | |
Crosshairs.prototype.constructor = Crosshairs; | |
//enemy constructor | |
var Enemy = function () { | |
GameElement.apply(this, arguments); | |
}; | |
Enemy.prototype = Object.create(GameElement.prototype); | |
Enemy.prototype.constructor = Enemy; | |
Enemy.prototype.chase = function (target) { | |
var run = target.loc[0] - this.loc[0]; | |
var rise = target.loc[1] - this.loc[1]; | |
var length = Math.sqrt((rise * rise) + (run * run)); | |
var unitX = run / length; | |
var unitY = rise / length; | |
this.move(unitX, unitY); | |
}; | |
Enemy.prototype.die = function () { | |
for (var j = 0; j < enemyObjs.length; j++) { | |
if (this === enemyObjs[j]) { | |
enemies | |
.filter(function (d, i) { | |
return i === j; | |
}) | |
.remove(); | |
enemyObjs.splice(j, 1); | |
gameStats.kills++; | |
return; | |
} | |
} | |
}; | |
//---SET UP GAME--- | |
var paused = true; | |
//create board d3 selection | |
var board = d3.select('.board') | |
.append('svg:svg') | |
.attr('width', gameOptions.width) | |
.attr('height', gameOptions.height) | |
.attr('class', 'svg-canvas') | |
//instantiate player and append to board, save as selection | |
var playerObj = new Player([toPixelAxes.x(50), toPixelAxes.y(50)], 10); | |
playerObj.draw('player'); | |
var player = d3.select('.player'); | |
//instantiate and append some enemies | |
var enemyObjs = []; | |
var spawnEnemies = function () { | |
var nEnemies = Math.floor(gameStats.timeLived / 20); | |
var enemySize = Math.max(5, (gameStats.timeLived / 10)); | |
if (gameStats.timeLived % 5 === 0 && enemyObjs.length < nEnemies) { | |
for (var i = 0; i < nEnemies; i++) { | |
var x = randX(); | |
var y = randY(); | |
var newEnemy = new Enemy([x, y], enemySize); | |
enemyObjs.push(newEnemy); | |
newEnemy.draw('enemy'); | |
}; | |
} | |
} | |
var enemies = d3.selectAll('.enemy'); | |
//instantiate crosshairs and append to board | |
var crosshairs = new Crosshairs([toPixelAxes.x(50), toPixelAxes.y(50)], 4); | |
var mouse = { | |
x: gameOptions.width / 2, | |
y: gameOptions.height / 2 | |
}; | |
crosshairs.draw('crosshairs'); | |
//mouse movement controls crosshairs | |
board.on('mousemove', function () { | |
var loc = d3.mouse(this); | |
crosshairs.loc[0] = loc[0]; | |
crosshairs.loc[1] = loc[1]; | |
d3.select('.crosshairs') | |
.attr('cx', pixelize(crosshairs.loc[0])) | |
.attr('cy', pixelize(crosshairs.loc[1])) | |
}); | |
//click fires | |
board.on('click', function () { | |
if (paused) return; | |
//only allow shoot if crosshair not inside player | |
var dx = crosshairs.loc[0] - playerObj.loc[0]; | |
var dy = crosshairs.loc[1] - playerObj.loc[1]; | |
var distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance > crosshairs.size + playerObj.size) { | |
var targetLoc = crosshairs.loc.slice(); | |
playerObj.shoot(targetLoc); | |
} | |
}); | |
var shoot = false; | |
var targetLoc; | |
var drag = d3.behavior.drag() | |
.on('drag', function () { | |
if (paused) return; | |
//only allow shoot if crosshair not inside player | |
var dx = crosshairs.loc[0] - playerObj.loc[0]; | |
var dy = crosshairs.loc[1] - playerObj.loc[1]; | |
var distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance > crosshairs.size + playerObj.size) { | |
targetLoc = crosshairs.loc.slice(); | |
shoot = true; | |
} | |
}); | |
d3.select('.svg-canvas').call(drag); | |
//---RUN ONCE GAME IS LOADED--- | |
$(function () { | |
//WASD controls | |
var keyPushStates = {}; | |
var keyCode; | |
var logKey = function (event) { | |
keyCode = event.keyCode; | |
if (event.type === 'keydown') { | |
keyPushStates[keyCode] = true; | |
} else if (event.type === 'keyup') { | |
keyPushStates[keyCode] = false; | |
} | |
}; | |
d3.select('body').on('keydown', function () { | |
//spacebar, for pausing | |
if (d3.event.keyCode === 32) { | |
if (paused) { | |
console.log('unpausing...'); | |
board.style('background-color', '#242424'); | |
} else { | |
console.log('pausing...'); | |
d3.select('.svg-canvas') | |
board.style('background-color', 'gray'); | |
} | |
paused = !paused; | |
} | |
logKey(d3.event); | |
}); | |
d3.select('body').on('keyup', function () { | |
logKey(d3.event); | |
}); | |
//handle movement | |
var keysHandler = function () { | |
//a && w | |
if (keyPushStates[65] && keyPushStates[87]) { | |
playerObj.move(-1, -1); | |
//a && s | |
} else if (keyPushStates[65] && keyPushStates[83]) { | |
playerObj.move(-1, 1); | |
//w && d | |
} else if (keyPushStates[87] && keyPushStates[68]) { | |
playerObj.move(1, -1); | |
//d && s | |
} else if (keyPushStates[68] && keyPushStates[83]) { | |
playerObj.move(1, 1); | |
//a | |
} else if (keyPushStates[65]) { | |
playerObj.move(-2, 0); | |
//w | |
} else if (keyPushStates[87]) { | |
playerObj.move(0, -2); | |
//d | |
} else if (keyPushStates[68]) { | |
playerObj.move(2, 0); | |
//s | |
} else if (keyPushStates[83]) { | |
playerObj.move(0, 2); | |
} | |
}; | |
var updateLoc = function (targetSelection, loc) { | |
targetSelection.datum(loc) | |
.attr('cx', function (d) { | |
var x = toPixelAxes.x(toCoordAxes.x(d[0])); | |
return pixelize(x); | |
}) | |
.attr('cy', function (d) { | |
var y = toPixelAxes.y(toCoordAxes.y(d[1])); | |
return pixelize(y); | |
}) | |
}; | |
var updateEnemyLoc = function () { | |
enemies = d3.selectAll('.enemy'); | |
enemies.each(function (_, i) { | |
if (enemyObjs[i]) { | |
updateLoc(d3.select(this), enemyObjs[i].loc); | |
} | |
}); | |
}; | |
var moveEnemies = function () { | |
_.each(enemyObjs, function (enemy) { | |
enemy.chase(playerObj); | |
}); | |
}; | |
var moveBullets = function () { | |
_.each(bulletObjs, function (bulletObj) { | |
bulletObj.fire(bulletObj.targetLoc); | |
}); | |
}; | |
var updateMultiLoc = function (selection, objs) { | |
selection.each(function (_, i) { | |
updateLoc(d3.select(this), objs[i].loc); | |
}); | |
}; | |
var updateBulletLoc = function () { | |
d3.selectAll('.bullet').each(function (_, i) { | |
if (bulletObjs[i]) { | |
updateLoc(d3.select(this), bulletObjs[i].loc); | |
if (isMaxedOut(bulletObjs[i].loc)) { | |
bulletObjs[i].die(); | |
} | |
} | |
}); | |
}; | |
//---COLLISION DETECTION--- | |
var prevCollision = false; | |
var detectCollisions = function () { | |
var collision = false; | |
_.each(enemyObjs, function (enemyObj) { | |
// the magic of collision detection | |
var dx = enemyObj.loc[0] - playerObj.loc[0]; | |
var dy = enemyObj.loc[1] - playerObj.loc[1]; | |
var distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < enemyObj.size + playerObj.size) { | |
collision = true; | |
} | |
}); | |
if (collision) { | |
timeLived = 0; | |
board.style('background-color', 'red'); | |
gameStats.health--; | |
d3.select('.scoreboard .health span').text(gameStats.health); | |
if (gameStats.health <= 0) { | |
paused = true; | |
alert('You lose!'); | |
} | |
} else { | |
board.style('background-color', '#242424'); | |
} | |
prevCollision = collision; | |
}; | |
//---HIT DETECTION--- | |
var detectHits = function () { | |
_.each(bulletObjs, function (bulletObj) { | |
for (var i = 0; i < enemyObjs.length; i++) { | |
var enemyObj = enemyObjs[i]; | |
// the magic of collision detection | |
var dx = enemyObj.loc[0] - bulletObj.loc[0]; | |
var dy = enemyObj.loc[1] - bulletObj.loc[1]; | |
var distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < enemyObj.size + bulletObj.size) { | |
enemyObj.die(); | |
} | |
}; | |
}) | |
} | |
//---GAME TIMER--- | |
d3.timer(function () { | |
if (!paused) { | |
if (shoot) { | |
playerObj.shoot(targetLoc); | |
shoot = false; | |
} | |
keysHandler(); | |
updateLoc(player, playerObj.loc); | |
moveEnemies(); | |
updateEnemyLoc(); | |
moveBullets(); | |
// updateMultiLoc(d3.selectAll('.bullets'), bulletObjs); | |
updateBulletLoc(); | |
detectCollisions(); | |
detectHits(); | |
spawnEnemies(); | |
} | |
}); | |
//---SCORE TIMER--- | |
var scoreTicker = function () { | |
if (!paused) { | |
gameStats.timeLived = gameStats.timeLived + 1; | |
gameStats.highScore = Math.max(gameStats.timeLived, gameStats.highScore); | |
updateScore(); | |
} | |
}; | |
setInterval(scoreTicker, 100); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment