A recreation of the Battle of Yavin using threejs.
Explosions: http://codepen.io/Xanmia/pen/DoljI Planets: https://github.com/jeromeetienne/threex.planets
A Pen by Carson Britt on CodePen.
A recreation of the Battle of Yavin using threejs.
Explosions: http://codepen.io/Xanmia/pen/DoljI Planets: https://github.com/jeromeetienne/threex.planets
A Pen by Carson Britt on CodePen.
nav.grid | |
.col-1-4 | |
ul.controls-left | |
li: button.btn-pause.active | |
li: button.btn-audio | |
.col-1-2.center.banner | |
h1 Death Star Trench Run | |
a(target="_blank", href="https://www.youtube.com/watch?v=SEEOcqbeclg") For full effect, open this in another tab | |
p | |
.control: <strong>Move:</strong> WASD or ARROWS | |
.control: <strong>Laser:</strong> SPACE | |
.control: <strong>Computer:</strong> TAB | |
p Fend off the rebels until their base is within firing range! Press Any Key To Start! | |
.col-1-4 | |
ul.controls-right.right | |
li: button.btn-laser | |
li: button.btn-computer <i></i> | |
.computer.hidden | |
.hits.left | |
img(src="data:image/png;base64,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") | |
.x × | |
.xwing-count.number.number-first 0 | |
.xwing-count.number.number-last 0 | |
.counter | |
span.number.number-0 0 | |
span.number.number-1 0 | |
span.number.number-2 0 | |
span.number.number-3 0 | |
span.number.number-4 0 | |
span.number.number-5 0 |
var scene, camera, renderer, controls, | |
light, shadowLight, backLight, trench, | |
stars, yavin, moon, lasers, laser_lights, | |
beam, xwing, fighters; | |
//========================================= | |
// SETTINGS | |
//========================================= | |
var KEYS = { | |
W: {code: '87', pressed: false}, | |
A: {code: '65', pressed: false}, | |
S: {code: '83', pressed: false}, | |
D: {code: '68', pressed: false}, | |
P: {code: '80', pressed: false}, | |
up: {code: '38', pressed: false}, | |
down: {code: '40', pressed: false}, | |
left: {code: '37', pressed: false}, | |
right: {code: '39', pressed: false}, | |
space: {code: '32', pressed: false}, | |
tab: {code: '9', pressed: false} | |
}; | |
var SOUNDS = { | |
tiefighter: new Audio("https://s3.amazonaws.com/cbritt/codepen/fighter.mp3"), | |
computer: new Audio("https://s3.amazonaws.com/cbritt/codepen/computer.mp3"), | |
explosion: new Audio("https://s3.amazonaws.com/cbritt/codepen/exp-2.mp3"), | |
xwing: new Audio("https://s3.amazonaws.com/cbritt/codepen/xwing.mp3"), | |
laser: new Audio("https://s3.amazonaws.com/cbritt/codepen/laser.mp3"), | |
lasers: [] | |
}; | |
var MODES = { | |
COMPUTER: 'computer', | |
SOLID: 'solid', | |
LOSE: 'lose', | |
WIN: 'win' | |
}; | |
var COLORS = { | |
WHITE: 0xffffff, | |
WHITE_BLUE: 0xDBE6FF, | |
BLACK: 0x000000, | |
GREY: 0x999999, | |
RED: 0xFF3333, | |
GREEN: 0x00FF00, | |
BLUE: 0x2E70FF, | |
YELLOW: 0xE0B300, | |
DARK_GREY: 0x0e0e11, | |
ORANGE: 0xFFAD14, | |
BRIGHT_RED: 0xFF0000 | |
}; | |
var TEXTURES = { | |
moon: "https://s3.amazonaws.com/cbritt/codepen/ZQhIPUP.jpg", | |
stars: "https://s3.amazonaws.com/cbritt/codepen/Y1ocEbG.png", | |
yavin: "https://s3.amazonaws.com/cbritt/codepen/m41aDH8.jpg" | |
}; | |
var CONFIG = { | |
debug: false, | |
paused: true, | |
audio: true, | |
speed: 3, | |
fov: 90, | |
view_distance: 5000, | |
mode: MODES.SOLID, | |
fog_density: 0.01, | |
module_count: 40, | |
module_width: 5, | |
module_height: 50, | |
module_length: 51, | |
trench_width: 40, | |
detail_max: 20, | |
detail_offset: 4, | |
laser_speed: 14, | |
laser_max: 8, | |
laser_length: 50, | |
laser_color: COLORS.GREEN, | |
laser_beam_offset: 1.5, | |
strafe_speed: 0.7, | |
strafe_max: 12, | |
fighter_max: 1, | |
fighter_speed: 3, | |
xwing_remaining: 30, | |
xwing_hits: 0, | |
explosion_speed: 3, | |
explosion_radius: 50, | |
explosion_particles: 200, | |
explosion_color: COLORS.ORANGE, | |
moon_speed: 0.1, | |
beam_speed: 100, | |
counter: 5400 | |
}; | |
CONFIG._counter = CONFIG.counter; | |
CONFIG.trench_length = CONFIG.module_count*CONFIG.module_length; | |
CONFIG.camera_start = new THREE.Vector3(0, CONFIG.module_height/4, CONFIG.trench_length-CONFIG.module_length); | |
CONFIG.laser_light_start = CONFIG.trench_length+CONFIG.laser_length/2; | |
//========================================= | |
// SETUP | |
//========================================= | |
//create scene, renderer, and camera | |
function setupScene() { | |
THREE.ImageUtils.crossOrigin = ''; | |
var ratio = window.innerWidth / window.innerHeight; | |
scene = new THREE.Scene(); | |
scene.fog = new THREE.FogExp2( COLORS.DARK_GREY, CONFIG.fog_density ); | |
renderer = new THREE.WebGLRenderer({alpha: true,antialias: true, premultipliedAlpha: false }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMapEnabled = true; | |
renderer.shadowMapType = THREE.PCFSoftShadowMap; | |
document.body.appendChild(renderer.domElement); | |
camera = new THREE.PerspectiveCamera( CONFIG.fov, ratio, 1, CONFIG.view_distance); | |
camera.position.set(CONFIG.camera_start.x, CONFIG.camera_start.y, CONFIG.camera_start.z); | |
camera.lookAt(new THREE.Vector3(0, camera.position.y, 0)); | |
controls = new THREE.OrbitControls( camera, renderer.domElement ); | |
controls.enableDamping = true; | |
controls.dampingFactor = 0.25; | |
controls.enabled = false; | |
stats = new Stats(); | |
stats.domElement.style.position = 'absolute'; | |
stats.domElement.style.top = '0px'; | |
window.addEventListener('resize', resize, false); | |
if(CONFIG.debug){ | |
controls.enabled = true; | |
document.body.appendChild( stats.domElement ); | |
} | |
} | |
//window resize event | |
function resize() { | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
} | |
//create lights | |
function setupLights() { | |
//global illumination light | |
light = new THREE.HemisphereLight(COLORS.WHITE_BLUE, COLORS.WHITE_BLUE, 0.4); | |
//primary shadow light | |
shadowLight = new THREE.DirectionalLight(COLORS.WHITE, 0.5); | |
shadowLight.position.set(-CONFIG.trench_width-50, CONFIG.module_height+30, CONFIG.trench_length/2); | |
shadowLight.target.position.set(0, CONFIG.module_height/2-20, CONFIG.trench_length/2); | |
shadowLight.target.updateMatrixWorld(); | |
shadowLight.shadowCameraFar = 150; | |
shadowLight.shadowCameraLeft = -(CONFIG.trench_length/2+CONFIG.module_length); | |
shadowLight.shadowCameraRight = (CONFIG.trench_length/2+CONFIG.module_length); | |
shadowLight.shadowCameraBottom = -20; | |
shadowLight.shadowCameraTop = 50; | |
shadowLight.shadowDarkness = 0.85; | |
shadowLight.shadowBias = 0.005; | |
shadowLight.castShadow = true; | |
//secondary shadow light | |
backLight = new THREE.DirectionalLight(COLORS.WHITE, 0.4); | |
backLight.position.set(50, CONFIG.module_height, CONFIG.trench_length/2); | |
backLight.target.position.set(0, 0, CONFIG.trench_length/2); | |
backLight.target.updateMatrixWorld(); | |
//laser lights need to be setup before the lasers are created at runtime | |
//to render properly, their positions are set to out of frame | |
laser_lights = []; | |
for(var i=0;i<CONFIG.laser_max;i++){ | |
var l = new THREE.PointLight(CONFIG.laser_color, 1, 40); | |
l.position.set(0, 10, CONFIG.laser_light_start); | |
laser_lights.push(l); | |
scene.add(l); | |
} | |
scene.add(light, backLight, shadowLight); | |
} | |
//preload sounds so multiple versions can be played | |
function setupSounds(){ | |
for(var i=0;i<CONFIG.laser_max;i++){ | |
var sound = new Audio('https://dl.dropboxusercontent.com/u/3656711/blaster.mp3'); | |
sound.volume = 0.2; | |
SOUNDS.lasers.push(sound); | |
} | |
SOUNDS.computer.volume = 0.5; | |
SOUNDS.computer.loop = true; | |
SOUNDS.tiefighter.volume = 0.3; | |
SOUNDS.xwing.volume = 0.4; | |
SOUNDS.explosion.volume = 0.3; | |
} | |
//========================================= | |
// OBJECTS | |
//========================================= | |
//main trench | |
var Trench = function(){ | |
this.child_index = CONFIG.module_count-1; | |
this.group = new THREE.Object3D(); | |
this.dx = 0; | |
for(i=0;i<CONFIG.module_count;i++){ | |
var section = new THREE.Object3D(); | |
var mod = new Module(i*CONFIG.module_length); | |
section.add(mod.group); | |
section.add(mod.lights); | |
section.add(mod.edges); | |
this.group.add(section); | |
} | |
scene.add(this.group); | |
this.update = function(){ | |
this.group.position.z += CONFIG.speed; | |
this.dx += CONFIG.speed; | |
//one modules' distance travled | |
//shift the furthest module to the beginning | |
//note: module length needs to be divisible by speed or the trench will slowly shift back | |
if(this.dx >= CONFIG.module_length){ | |
var mod = this.group.children[this.child_index]; | |
mod.position.z -= CONFIG.trench_length; | |
//mod.position.z = 0; | |
mod.updateMatrix(); | |
this.child_index = (this.child_index <= 0) ? CONFIG.module_count-1 : this.child_index-1; | |
this.dx = 0; | |
} | |
}; | |
}; | |
//trench section | |
var Module = function(z) { | |
this.parts = []; | |
//shorthand | |
var w = CONFIG.module_width; | |
var h = CONFIG.module_height; | |
var l = CONFIG.module_length; | |
var t = CONFIG.trench_width; | |
var w_half = w/2; | |
var h_half = h/2; | |
var l_half = l/2; | |
var t_half = t/2; | |
//basic trench walls | |
var left = getBoxMesh(w, h, l, -t_half - w_half, h_half, z ); | |
var right = getBoxMesh(w, h, l, t_half + w_half, h_half, z ); | |
var bottom = getBoxMesh(t, w, l, 0, -w_half, z ); | |
//detail left wall | |
var detail_left = getDetail(l, h-w); | |
detail_left.position.set(t_half, h_half, z); | |
detail_left.rotation.y = Math.PI/2; | |
//detail right wall | |
var detail_right = getDetail(l, h-w); | |
detail_right.position.set(-t_half, h_half, z); | |
detail_right.rotation.y = -Math.PI/2; | |
//detail bottom | |
var detail_bottom = getDetail(l, t); | |
detail_bottom.position.set(0, 0, z); | |
detail_bottom.rotation.x = Math.PI/2; | |
//detail lights | |
this.lights = getDetailLights(l, t); | |
this.lights.position.set(0, 0, z); | |
//merged mesh | |
this.parts.push(left, right, bottom, detail_left, detail_right, detail_bottom); | |
this.group = mergeMeshes(this.parts); | |
this.edges = new THREE.EdgesHelper( this.group.clone(), COLORS.YELLOW ); | |
this.edges.visible = false; | |
this.group.name = "solid"; | |
this.edges.name = "edges"; | |
this.lights.name = "lights"; | |
}; | |
//the planet yavin | |
var Yavin = function(){ | |
var yavin_geometry = new THREE.SphereGeometry(2000, 32, 32); | |
var yavin_texture = THREE.ImageUtils.loadTexture(TEXTURES.yavin); | |
yavin_texture.repeat.set( 2, 2 ); | |
var yavin_material = new THREE.MeshPhongMaterial({ map: yavin_texture, fog: false }); | |
this.group = new THREE.Mesh(yavin_geometry, yavin_material); | |
this.group.position.set(-1200, 2400, 800); | |
this.group.rotation.z = Math.PI/4; | |
this.group.rotation.x = Math.PI/3; | |
scene.add(this.group); | |
}; | |
//yavin 4 | |
var Moon = function(){ | |
var moon_geometry = new THREE.SphereGeometry(300, 32, 32); | |
var moon_texture = THREE.ImageUtils.loadTexture(TEXTURES.moon); | |
var moon_material = new THREE.MeshPhongMaterial({map: moon_texture,fog: false }); | |
this.group = new THREE.Mesh(moon_geometry, moon_material); | |
this.group.position.set(200, 1800, -700); | |
this.group.rotation.x = -Math.PI/2; | |
this.group.rotation.z = -Math.PI/2; | |
scene.add(this.group); | |
this.update = function(){ | |
if(!this.explosion){ | |
this.group.position.x += CONFIG.moon_speed; | |
this.group.position.y -= CONFIG.moon_speed/2; | |
} | |
else{ | |
this.explosion.update(); | |
} | |
}; | |
this.explode = function(){ | |
scene.remove(this.group); | |
this.explosion = new Explosion(this.group.position.clone(), { | |
particles: 500, | |
radius: 1000, | |
size: 10, | |
speed: 15, | |
mute: true | |
}, function(){ | |
win(); | |
}); | |
}; | |
}; | |
//background stars | |
var Starfield = function(){ | |
var stars_geometry = new THREE.SphereGeometry(CONFIG.view_distance/2, 32, 32); | |
var stars_texture = THREE.ImageUtils.loadTexture(TEXTURES.stars); | |
var stars_material = new THREE.MeshBasicMaterial({ map: stars_texture, side: THREE.BackSide, fog:false }); | |
this.group = new THREE.Mesh(stars_geometry, stars_material); | |
scene.add(this.group); | |
}; | |
//single laser burst | |
var Laser = function(){ | |
this.group = new THREE.Object3D(); | |
//z is bigger than the x, y threshold | |
//to account for the speed of the laser | |
var hit_threshold = 2.5; | |
var hit_threshold_z = 15; | |
this.start = new THREE.Vector3(camera.position.x, camera.position.y - 5, 0); | |
var material = new THREE.LineBasicMaterial({ color: CONFIG.laser_color, linewidth: 2, fog:false }); | |
var geometry = new THREE.Geometry(); | |
geometry.vertices.push(new THREE.Vector3(0, this.start.y, 0)); | |
geometry.vertices.push(new THREE.Vector3(0, this.start.y, CONFIG.laser_length)); | |
//A laser contains two beams | |
var line_l = new THREE.Line(geometry, material); | |
var line_r = new THREE.Line(geometry, material); | |
line_l.position.x = this.start.x - CONFIG.laser_beam_offset; | |
line_r.position.x = this.start.x + CONFIG.laser_beam_offset; | |
this.group.add(line_l, line_r); | |
this.group.position.z = CONFIG.trench_length; | |
this.update = function(){ | |
this.group.position.z -= CONFIG.laser_speed; | |
}; | |
//collision detection with xwing | |
this.hit = function(){ | |
//no xwing to hit | |
if(!xwing) return false; | |
//xwing currently exploding | |
if(xwing.explosion && xwing.explosion.exploding) return false; | |
//chcek collsion | |
var dx = Math.abs(this.start.x - xwing.group.position.x); | |
var dy = Math.abs(this.start.y - xwing.group.position.y); | |
var dz = Math.abs(this.group.position.z - xwing.group.position.z); | |
//console.log("diff: "+dx.toFixed(1)+"-"+dy.toFixed(1)+"-"+dz ); | |
if(dx <= hit_threshold && dy <= hit_threshold && dz <= hit_threshold_z ){ | |
registerHit(); | |
return true; | |
} | |
}; | |
}; | |
//create lasers dynamically | |
var LaserSpawner = function(){ | |
this.items = []; | |
this.fire = function(){ | |
if(this.items.length<CONFIG.laser_max && !KEYS.space.pressed && !CONFIG.paused){ | |
var laser = new Laser(); | |
//get first available laser light. | |
var light = _.find(laser_lights, function(l){ | |
return l.position.z >= CONFIG.laser_light_start; | |
}); | |
scene.add(laser.group); | |
this.items.push({ | |
laser: laser, | |
light: light | |
}); | |
if(CONFIG.audio) { | |
var sound = _.find(SOUNDS.lasers, function(s){ | |
return s.paused; | |
}); | |
sound.play(); | |
} | |
} | |
}; | |
this.update = function(){ | |
var self = this; | |
self.items.forEach(function(item){ | |
if(item.laser.hit()){ | |
item.light.position.z = CONFIG.laser_light_start; | |
scene.remove(item.laser.group); | |
_.pull(self.items, item); | |
xwing.explode(); | |
} | |
else if(item.laser.group.position.z < 0){ | |
item.light.position.z = CONFIG.laser_light_start; | |
scene.remove(item.laser.group); | |
_.pull(self.items, item); | |
} | |
else{ | |
item.laser.update(); | |
if(CONFIG.mode != MODES.COMPUTER){ | |
item.light.position.z -= CONFIG.laser_speed; | |
} | |
} | |
}); | |
}; | |
}; | |
//Tie fighter | |
var TieFighter = function(start, end, squad){ | |
this.parts = []; | |
//movement vector | |
this.dir = start.clone().sub(end).normalize().multiplyScalar(CONFIG.fighter_speed); | |
var geometry = new THREE.SphereGeometry( 2, 32, 32 ); | |
var material = new THREE.MeshLambertMaterial( {color: 0x999999, fog:false } ); | |
var core = new THREE.Mesh( geometry, material ); | |
//struts | |
var s1 = getBoxMesh(5, 1, 1, -2, 0, 0); | |
var s2 = getBoxMesh(5, 1, 1, 2, 0, 0); | |
//wings | |
var w1 = getBoxMesh(0.5, 12, 7, 5, 0, 0); | |
var w2 = getBoxMesh(0.5, 12, 7, -5, 0, 0); | |
//merged | |
this.parts.push(core, s1, s2, w1, w2); | |
this.mesh = mergeMeshes(this.parts); | |
this.group = this.mesh; | |
//add more fighters flying in unison | |
if(squad){ | |
this.mesh_2 = this.mesh.clone(); | |
this.mesh_3 = this.mesh_2.clone(); | |
if(chance(80)){ | |
this.mesh_2.position.x += _.random(-20, 20); | |
this.mesh_2.position.y += _.random(20, 50); | |
this.mesh_2.position.z += _.random(-20, 20); | |
this.group.add(this.mesh_2); | |
} | |
if(chance(50)){ | |
this.mesh_3.position.x += _.random(30, 50); | |
this.mesh_3.position.y += _.random(-5, 5); | |
this.mesh_3.position.z += _.random(-50, -60); | |
this.group.add(this.mesh_3); | |
} | |
} | |
this.group.position.set(start.x, start.y, start.z); | |
this.group.lookAt(end); | |
this.update = function(){ | |
this.group.position.x -= this.dir.x; | |
this.group.position.z -= this.dir.z; | |
}; | |
}; | |
//random tiefighters flying overhead | |
var TieFighterSpawner = function(){ | |
this.items = []; | |
this.locked = false; | |
var x_threshold = 150; | |
var max_z = CONFIG.trench_length-100; | |
var min_z = CONFIG.trench_length-400; | |
this.spawn = function(){ | |
var dir = Math.round(Math.random()) * 2 - 1; | |
var y = CONFIG.module_height*2; | |
var start = new THREE.Vector3(dir*x_threshold, y, _.random(min_z, max_z)); | |
var end = new THREE.Vector3(-dir*x_threshold, y, _.random(min_z, max_z)); | |
var fighter = new TieFighter(start, end, true); | |
this.items.push(fighter); | |
scene.add(fighter.group); | |
if(CONFIG.audio && !CONFIG.paused && CONFIG.mode != MODES.COMPUTER) SOUNDS.tiefighter.play(); | |
this.locked = false; | |
}; | |
this.update = function(){ | |
var self = this; | |
if(self.items.length < CONFIG.fighter_max && !self.locked && CONFIG.mode != MODES.COMPUTER){ | |
self.locked = true; | |
setTimeout(function(){ self.spawn(); }, _.random(5000, 10000)); | |
} | |
self.items.forEach(function(fighter){ | |
if(Math.abs(fighter.group.position.x) > x_threshold){ | |
scene.remove(fighter.group); | |
_.pull(self.items, fighter); | |
} | |
else{ | |
fighter.update(); | |
} | |
}); | |
}; | |
}; | |
//xwing fighter | |
var Xwing = function(){ | |
var z = CONFIG.trench_length-130; | |
var y = 15; | |
var x = 0; | |
this.parts = []; | |
this.inside_trench = true; | |
this.strafe_active = false; | |
this.strafe_dir = new THREE.Vector3(x, y, z); | |
this.strafe_end = new THREE.Vector3(x, y, z); | |
this.strafe_start = new THREE.Vector3(x, y, z); | |
//core | |
var core = getBoxMesh(2, 2, 10, 0, 0, -5); | |
//wings | |
var wing_1 = getBoxMesh(12, 0.3, 1.3, 0, 0, -0.7); | |
var wing_2 = getBoxMesh(12, 0.3, 1.3, 0, 0, -0.7); | |
wing_1.rotation.z = Math.PI/7; | |
wing_2.rotation.z = -Math.PI/7; | |
//guns | |
var g1 = getCylinderMesh(0.3, 0.3, 4, -5.6, 2.5, -2); | |
var g2 = getCylinderMesh(0.3, 0.3, 4, -5.6, -2.5, -2); | |
var g3 = getCylinderMesh(0.3, 0.3, 4, 5.6, 2.5, -2); | |
var g4 = getCylinderMesh(0.3, 0.3, 4, 5.6, -2.5, -2); | |
//engines | |
var e1 = getCylinderMesh(0.7, 0.7, 2, -1.5, -1, -1); | |
var e2 = getCylinderMesh(0.7, 0.7, 2, -1.5, 1, -1); | |
var e3 = getCylinderMesh(0.7, 0.7, 2, 1.5, -1, -1); | |
var e4 = getCylinderMesh(0.7, 0.7, 2, 1.5, 1, -1); | |
//lights | |
var l1 = new THREE.PointLight(COLORS.RED, 35, 0.7); | |
var l2 = new THREE.PointLight(COLORS.RED, 35, 0.7); | |
var l3 = new THREE.PointLight(COLORS.RED, 35, 0.7); | |
var l4 = new THREE.PointLight(COLORS.RED, 35, 0.7); | |
l1.position.set(1.5, 1, 0.3); | |
l2.position.set(1.5, -1, 0.3); | |
l3.position.set(-1.5, 1, 0.3); | |
l4.position.set(-1.5, -1, 0.3); | |
//shadow of xwing in trench | |
var shadow = new THREE.SpotLight(COLORS.WHITE, 10); | |
shadow.onlyShadow = true; | |
shadow.castShadow = true; | |
shadow.position.set(x, y+35, z); | |
shadow.target.position.set(x,y,z); | |
shadow.target.updateMatrixWorld(); | |
//shadow.shadowCameraVisible = true; | |
shadow.shadowDarkness = 0.7; | |
shadow.shadowCameraNear = 1; | |
shadow.shadowCameraFar = 80; | |
scene.add(shadow); | |
//lights | |
this.lights = new THREE.Object3D(); | |
this.lights.add( l1, l2, l3, l4); | |
this.lights.name = "lights"; | |
//merged mesh | |
this.parts.push(core, wing_1, wing_2); | |
this.parts.push(g1, g2, g3, g4); | |
this.parts.push(e1, e2, e3, e4); | |
this.mesh = mergeMeshes(this.parts); | |
this.mesh.receiveShadow = false; | |
this.mesh.material = new THREE.MeshLambertMaterial({ color: 0xcccccc }); | |
this.mesh.name = "mesh"; | |
//edges | |
this.edges = new THREE.EdgesHelper( this.mesh.clone(), COLORS.BRIGHT_RED ); | |
this.edges.visible = false; | |
this.edges.name = "edges"; | |
//group | |
this.group = new THREE.Object3D(); | |
this.group.add(this.mesh, this.lights, this.edges); | |
this.group.position.set(x, y, z); | |
scene.add(this.group); | |
this.update = function(){ | |
if(this.explosion){ | |
this.explosion.update(); | |
} | |
if(!this.inside_trench){ | |
this.enterTrench(); | |
} | |
else{ | |
this.strafe(); | |
} | |
}; | |
//move arround randomly on the x y axis | |
this.strafe = function(){ | |
var max_x = 14; | |
var max_y = 20; | |
//no destination, get a new point | |
if(!this.strafe_active){ | |
this.strafe_start = new THREE.Vector3(this.group.position.x, this.group.position.y, this.group.position.z); | |
this.strafe_end.x = x - max_x + (Math.random()*(max_x*2)); | |
this.strafe_end.y = (y-5) + (Math.random()*(max_y*2)); | |
this.strafe_active = true; | |
} | |
//move to the point | |
else{ | |
var strafe_speed = this.getSpeed(this.group.position, this.strafe_end); | |
this.strafe_dir = this.strafe_start.clone().sub(this.strafe_end).normalize().multiplyScalar(strafe_speed); | |
this.group.position.x -= this.strafe_dir.x; | |
this.group.position.y -= this.strafe_dir.y; | |
this.group.position.z = z; | |
var xdiff = Math.abs(this.group.position.x - this.strafe_end.x) < 2; | |
var ydiff = Math.abs(this.group.position.y - this.strafe_end.y) < 2; | |
var xbound = Math.abs(this.group.position.x) > x+max_x; | |
var ybound = Math.abs(this.group.position.y) > y+max_y; | |
//arrived or out of bounds | |
if((xdiff && ydiff) || xbound || ybound){ | |
this.strafe_active = false; | |
} | |
} | |
}; | |
//get speed for sudo smooth movement | |
this.getSpeed = function(prev_vector, next_vector){ | |
var x = Math.abs(prev_vector.x - next_vector.x); | |
var y = Math.abs(prev_vector.y - next_vector.y); | |
var greatest =( x > y ? x : y).toFixed(2); | |
//increase this for a smoother strafe movement | |
var speedModifier = 16; | |
var speed = (greatest/speedModifier).toFixed(2); | |
return speed; | |
}; | |
//move from outside to inside trench | |
this.enterTrench = function(){ | |
if(this.group.position.y < y){ | |
this.inside_trench = true; | |
return; | |
} | |
this.group.position.y-=0.4; | |
this.group.position.z-=0.43; | |
}; | |
//when explosion occurs reset to starting position outside trench | |
this.reset = function(){ | |
this.group.position.y += (CONFIG.module_height+CONFIG.module_height/4); | |
this.group.position.z += 70; | |
this.group.position.x = 0; | |
if(CONFIG.audio && !CONFIG.paused) SOUNDS.xwing.play(); | |
this.inside_trench = false; | |
}; | |
//create exposion at location of xwing | |
this.explode = function(){ | |
if(this.explosion) scene.remove(this.explosion.particles); | |
this.explosion = new Explosion(new THREE.Vector3(this.group.position.x, this.group.position.y, this.group.position.z)); | |
this.reset(); | |
}; | |
}; | |
//create an explosion: http://codepen.io/Xanmia/pen/DoljI | |
var Explosion = function(position, opts, cb){ | |
cb = cb || function(){}; | |
opts = opts || {}; | |
var radius = opts.radius || CONFIG.explosion_radius; | |
var particles = opts.particles || CONFIG.explosion_particles; | |
var speed = opts.speed || CONFIG.explosion_speed; | |
var size = opts.size || 0.4; | |
var mute = opts.mute || false; | |
this.dirs = []; | |
var material = new THREE.PointCloudMaterial({ size: size, color: CONFIG.explosion_color, fog: false}); | |
var geometry = new THREE.Geometry(); | |
for (var i=0;i<particles;i++){ | |
var vertex = new THREE.Vector3(); | |
vertex.x = position.x; | |
vertex.y = position.y; | |
vertex.z = position.z; | |
geometry.vertices.push(vertex); | |
this.dirs.push({ | |
x:(Math.random() * speed)-(speed/2), | |
y:(Math.random() * speed)-(speed/2), | |
z:(Math.random() * speed)-(speed/2) | |
}); | |
} | |
this.exploding = true; | |
this.particles = new THREE.PointCloud(geometry, material); | |
scene.add(this.particles); | |
if(CONFIG.audio && !mute) SOUNDS.explosion.play(); | |
this.update = function(){ | |
if (this.exploding){ | |
var pCount = particles; | |
while(pCount--) { | |
var particle = this.particles.geometry.vertices[pCount]; | |
var dx = Math.abs(particle.x) > Math.abs(position.x) + radius; | |
var dy = Math.abs(particle.y) > Math.abs(position.y) + radius; | |
var dz = Math.abs(particle.z) > Math.abs(position.z) + radius; | |
if(dx || dy || dz){ | |
this.exploding = false; | |
cb(); | |
return; | |
} | |
particle.x += this.dirs[pCount].x; | |
particle.y += this.dirs[pCount].y; | |
particle.z += this.dirs[pCount].z; | |
} | |
this.particles.geometry.verticesNeedUpdate = true; | |
} | |
else{ | |
scene.remove(this.particles); | |
} | |
}; | |
}; | |
//fire the superweapon | |
var Beam = function(){ | |
//create the full beam out of frame | |
//and them translate it rather than scaling | |
this.firing = true; | |
this.end = moon.group.position.clone(); | |
this.start = new THREE.Vector3(400,0,CONFIG.trench_length/2); | |
//total lenght of the beam | |
this.dist = (this.start.clone().sub(this.end).length())*2; | |
//current distance to the moon | |
this.dist_to_moon = 100000000; | |
//direction vector | |
this.dir = this.start.clone().sub(this.end).normalize(); | |
//reset the beam behing the start point at creation | |
this.initial_scale = this.dir.clone().multiplyScalar(this.dist/2); | |
//how much to translate by at each frame | |
this.update_scale = this.dir.clone().multiplyScalar(CONFIG.beam_speed); | |
var material = new THREE.MeshBasicMaterial({ color: COLORS.GREEN, fog:false }); | |
var geom = new THREE.CylinderGeometry(7, 7, this.dist); | |
this.mesh = new THREE.Mesh(geom, material); | |
this.mesh.rotation.x = Math.PI/2; | |
this.group = new THREE.Object3D(); | |
this.group.add(this.mesh); | |
this.group.position.x = this.start.x+this.initial_scale.x; | |
this.group.position.y = this.start.y+this.initial_scale.y; | |
this.group.position.z = this.start.z+this.initial_scale.z; | |
this.group.lookAt(this.end); | |
scene.add(this.group); | |
if(CONFIG.audio) SOUNDS.laser.play(); | |
this.update = function(){ | |
if(!this.firing) return; | |
if(this.hit()){ | |
this.firing = false; | |
scene.remove(this.group); | |
moon.explode(); | |
} | |
else{ | |
this.group.position.x -= this.update_scale.x; | |
this.group.position.y -= this.update_scale.y; | |
this.group.position.z -= this.update_scale.z; | |
} | |
}; | |
this.hit = function(){ | |
var old_dist = this.dist_to_moon; | |
this.dist_to_moon = this.group.position.clone().sub(this.end).length() + (this.dist/2); | |
return (this.dist_to_moon > old_dist); | |
}; | |
}; | |
//========================================= | |
// HELPER FUNCTIONS | |
//========================================= | |
//returns true percent of the time | |
function chance(percent){ | |
return (Math.random() < percent/100.0); | |
} | |
//create a box mesh with a geometry and material | |
function getBoxMesh(w, h, l, x, y, z) { | |
var material = new THREE.MeshLambertMaterial({ color: COLORS.WHITE, fog:false }); | |
var geom = new THREE.BoxGeometry(w, h, l); | |
var mesh = new THREE.Mesh(geom, material); | |
mesh.position.set(x || 0, y || 0, z || 0); | |
mesh.receiveShadow = true; | |
mesh.castShadow = true; | |
return mesh; | |
} | |
//create a Cylinder mesh with a geometry and material | |
function getCylinderMesh(t, b, l, x, y, z) { | |
var material = new THREE.MeshLambertMaterial({ color: COLORS.WHITE, fog:false }); | |
var geom = new THREE.CylinderGeometry(t, b, l); | |
var mesh = new THREE.Mesh(geom, material); | |
mesh.rotation.x = Math.PI/2; | |
mesh.position.set(x || 0, y || 0, z || 0); | |
mesh.receiveShadow = true; | |
mesh.castShadow = true; | |
return mesh; | |
} | |
//merge geometries of meshes | |
function mergeMeshes (meshes) { | |
var material = meshes[0].material; | |
var combined = new THREE.Geometry(); | |
for (var i = 0; i < meshes.length; i++) { | |
meshes[i].updateMatrix(); | |
combined.merge(meshes[i].geometry, meshes[i].matrix); | |
} | |
var mesh = new THREE.Mesh(combined, material); | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
return mesh; | |
} | |
//add detail (smaller boxes) to the walls | |
function getDetail(lw, lh){ | |
var i, w, h, x, y; | |
var details = []; | |
//add some big details | |
for(i=0;i<CONFIG.detail_max;i++){ | |
var depth = _.random(CONFIG.detail_offset/4, CONFIG.detail_offset, true); | |
w = _.random(lw/5, lw/3*2, true); | |
h = _.random(lh/5, lh/3*2, true); | |
x = _.random(-lw+w/2, lw-w/2, true); | |
y = _.random(-(lh)+(h/2), (lh/2)-(h/6), true); | |
details.push(getBoxMesh(w, h, depth, x, y, -depth/2)); | |
} | |
//add some smaller details | |
for(i=0;i<CONFIG.detail_max;i++){ | |
var depth = _.random(CONFIG.detail_offset, CONFIG.detail_offset+1, true); | |
w = _.random(lw/15, lw/12, true); | |
h = _.random(lh/10, lh/5, true); | |
x = _.random(-lw+w/2, lw-w/2, true); | |
y = _.random(-(lh)+(h/2), (lh/2)-(h/4), true); | |
details.push(getBoxMesh(w, h, depth, x, y, -depth/2)); | |
} | |
return mergeMeshes(details); | |
} | |
//add detail light particles on the modules | |
function getDetailLights(lw, lh){ | |
var x, y, z; | |
var geom = new THREE.Geometry(); | |
var max = 20; | |
//left | |
for(var i=0; i<max;i++){ | |
var x = (-lh/2)+CONFIG.detail_offset; | |
var y = _.random(0, CONFIG.module_height-5, true); | |
var z = _.random(-lh/2, lh/2, true); | |
geom.vertices.push(new THREE.Vector3( x, y, z)); | |
} | |
//right | |
for(var i=0; i<max;i++){ | |
x = (lh/2)-CONFIG.detail_offset; | |
y = _.random(0, CONFIG.module_height-5, true); | |
z = _.random(-lh/2, lh/2, true); | |
geom.vertices.push(new THREE.Vector3( x, y, z)); | |
} | |
//bottom | |
for(var i=0; i<max;i++){ | |
x = _.random(-lw/2, lw/2, true); | |
y = CONFIG.detail_offset-1; | |
z = _.random(-lh/2, lh/2, true); | |
geom.vertices.push(new THREE.Vector3( x, y, z)); | |
} | |
var material = new THREE.PointCloudMaterial({ color: 0xCCCCCC, size:0.1 }); | |
var dots = new THREE.PointCloud( geom, material ); | |
return dots; | |
} | |
//update the distance value for computer mode | |
function updateCounter(){ | |
if(CONFIG.speed){ | |
CONFIG.counter = CONFIG.counter < 0 ? 0 : CONFIG.counter-1; | |
//time out fire the weapon | |
if(CONFIG.counter < 1 && !beam) { beam = new Beam(); } | |
var num = ("000000"+CONFIG.counter); | |
//force monospace by updating each character | |
//in a seperate element with fixed width | |
num = num.substr(num.length-6).split(""); | |
for(var i=0;i<num.length;i++){ | |
document.querySelector('.number-'+i).innerHTML = num[i]; | |
} | |
} | |
} | |
//toggle modes | |
function toggleMode(){ | |
if(CONFIG.paused) togglePause(); | |
document.querySelector('body').classList.toggle('mode-computer'); | |
document.querySelector('.computer').classList.toggle('hidden'); | |
document.querySelector('.btn-computer').classList.toggle('active'); | |
mode = (CONFIG.mode == MODES.SOLID) ? MODES.COMPUTER : MODES.SOLID; | |
CONFIG.mode = mode; | |
stars.group.visible = !stars.group.visible; | |
for(var i=0;i<trench.group.children.length;i++){ | |
var solid = trench.group.children[i].getObjectByName("solid"); | |
var edges = trench.group.children[i].getObjectByName("edges"); | |
var lights = trench.group.children[i].getObjectByName("lights"); | |
edges.visible = !edges.visible; | |
lights.visible = !lights.visible; | |
} | |
if(mode == MODES.COMPUTER){ | |
if(CONFIG.audio && !CONFIG.paused) SOUNDS.computer.play(); | |
scene.fog.density = 0; | |
scene.remove(light, shadowLight, backLight); | |
CONFIG.laser_color = COLORS.BRIGHT_RED; | |
CONFIG.explosion_color = COLORS.BRIGHT_RED; | |
} | |
else { | |
SOUNDS.computer.pause(); | |
scene.fog.density = CONFIG.fog_density; | |
scene.add(light, shadowLight, backLight); | |
CONFIG.laser_color = COLORS.GREEN; | |
CONFIG.explosion_color = COLORS.ORANGE; | |
} | |
var xm = xwing.group.getObjectByName("mesh"); | |
var xl = xwing.group.getObjectByName("lights"); | |
var xe = xwing.group.getObjectByName("edges"); | |
xm.visible = !xm.visible; | |
xl.visible = !xl.visible; | |
xe.visible = !xe.visible; | |
} | |
//mute or unmute audio | |
function toggleAudio(){ | |
document.querySelector('.btn-audio').classList.toggle('active'); | |
if(CONFIG.mode == MODES.COMPUTER && !CONFIG.paused) toggleSound(SOUNDS.computer); | |
CONFIG.audio = !CONFIG.audio; | |
} | |
//pause the speed | |
function togglePause(){ | |
document.querySelector('html').classList.toggle('paused'); | |
document.querySelector('.btn-pause').classList.toggle('active'); | |
if(CONFIG.mode == MODES.COMPUTER && CONFIG.audio) toggleSound(SOUNDS.computer); | |
CONFIG.paused = !CONFIG.paused; | |
} | |
//toggle a sound file | |
function toggleSound(sound){ | |
if(sound.paused) sound.play(); | |
else sound.pause(); | |
} | |
//move camera arround in the trench on the xy axies | |
function strafe(){ | |
var up = KEYS.up.pressed || KEYS.W.pressed; | |
var down = KEYS.down.pressed || KEYS.S.pressed; | |
var left = KEYS.left.pressed || KEYS.A.pressed; | |
var right = KEYS.right.pressed || KEYS.D.pressed; | |
if(up && camera.position.y < CONFIG.camera_start.y + CONFIG.strafe_max+7){ | |
camera.position.y += CONFIG.strafe_speed; | |
} | |
if(down && camera.position.y > CONFIG.camera_start.y-2){ | |
camera.position.y -= CONFIG.strafe_speed; | |
} | |
if(left && camera.position.x > CONFIG.camera_start.x - CONFIG.strafe_max){ | |
camera.position.x -= CONFIG.strafe_speed; | |
} | |
if(right && camera.position.x < CONFIG.camera_start.x + CONFIG.strafe_max){ | |
camera.position.x += CONFIG.strafe_speed; | |
} | |
} | |
//turn on debug mode with orbit camera and stats | |
function enableDebug(){ | |
CONFIG.debug = KEYS.P.pressed ? !CONFIG.debug : CONFIG.debug; | |
document.body.appendChild( stats.domElement ); | |
controls.enabled = true; | |
} | |
//register a laser hit on an xwing | |
function registerHit(){ | |
CONFIG.xwing_hits++; | |
//var remaining = CONFIG.xwing_remaining - CONFIG.xwing_hits; | |
//if(remaining < 1) win(); | |
if(CONFIG.xwing_hits > 9){ | |
document.querySelector('.xwing-count.number-first').innerHTML = CONFIG.xwing_hits.toString().charAt(0); | |
document.querySelector('.xwing-count.number-last').innerHTML = CONFIG.xwing_hits.toString().charAt(1); | |
} | |
else{ | |
document.querySelector('.xwing-count.number-last').innerHTML = CONFIG.xwing_hits; | |
} | |
} | |
//win state all fighters destroyed | |
function win(){ | |
reset(); | |
} | |
//reset to starting conditions | |
function reset(){ | |
//reset it | |
} | |
//========================================= | |
// EVENTS | |
//========================================= | |
//pause button click | |
document.querySelector('.btn-pause').addEventListener('click', function() { | |
togglePause(); | |
}); | |
//computer button click | |
document.querySelector('.btn-computer').addEventListener('click', function() { | |
toggleMode(); | |
}); | |
//laser button click | |
document.querySelector('.btn-laser').addEventListener('click', function() { | |
lasers.fire(); | |
}); | |
//audio button click | |
document.querySelector('.btn-audio').addEventListener('click', function() { | |
toggleAudio(); | |
}); | |
//keydown events | |
document.onkeydown = function(e){ | |
//unpause once any key is pressed | |
if(CONFIG.paused) togglePause(); | |
//fire laser | |
if (e.keyCode == KEYS.space.code) { | |
e.preventDefault(); | |
lasers.fire(); | |
} | |
//enable debug | |
if (e.keyCode == KEYS.P.code) { | |
enableDebug(); | |
} | |
//toggle computer mode | |
if (e.keyCode == KEYS.tab.code) { | |
e.preventDefault(); | |
toggleMode(); | |
} | |
//if the pressed key is in the KEYS obj set its pressed val to true | |
var matches = _.pick(KEYS, function(val){ return val.code == e.keyCode; }); | |
if (Object.keys(matches).length){ | |
KEYS[Object.keys(matches)[0]].pressed = true; | |
} | |
}; | |
//keyup events | |
document.onkeyup = function(e){ | |
//if the pressed key is in the KEYS obj set its pressed val to false | |
var matches = _.pick(KEYS, function(val){ return val.code == e.keyCode; }); | |
if (Object.keys(matches).length){ | |
KEYS[Object.keys(matches)[0]].pressed = false; | |
} | |
}; | |
//========================================= | |
// MAIN | |
//========================================= | |
//initial setup | |
var init = function(){ | |
setupScene(); | |
setupLights(); | |
setupSounds(); | |
trench = new Trench(); | |
stars = new Starfield(); | |
yavin = new Yavin(); | |
moon = new Moon(); | |
lasers = new LaserSpawner(); | |
fighters = new TieFighterSpawner(); | |
xwing = new Xwing(); | |
}; | |
//main animation loop | |
var render = function() { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
stats.update(); | |
controls.update(); | |
if(!CONFIG.paused){ | |
strafe(); | |
updateCounter(); | |
if(moon) moon.update(); | |
if(trench) trench.update(); | |
if(trench) lasers.update(); | |
if(fighters) fighters.update(); | |
if(xwing) xwing.update(); | |
if(beam) beam.update(); | |
} | |
}; | |
//run it | |
init(); | |
render(); |
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.5.0/lodash.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script> | |
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script> | |
<script src="http://threejs.org/examples/js/libs/stats.min.js"></script> |
$red:#F21313; | |
$green:#00FF6E; | |
$yellow:#E0B300; | |
$button:rgba(white, 0.8); | |
$button-bg:rgba(white, 0.2); | |
$button-active:rgba($red, 1); | |
$button-active-bg:rgba($red, 0.5); | |
$text:#555; | |
$pad: 10px; | |
.hidden{ display:none; } | |
.left{float:left;} | |
.right{float:right;} | |
.center{text-align: center;} | |
.sudo{ | |
content:""; | |
position:absolute; | |
display:block; | |
} | |
*, *:before, *:after{ | |
outline:none; | |
box-sizing: border-box; | |
} | |
//GRID | |
//=============================== | |
.grid:after{ | |
content: ""; | |
display: table; | |
clear: both; | |
} | |
[class*='col-'] { | |
float: left; | |
padding-right: $pad; | |
.grid &:last-of-type { | |
padding-right: 0; | |
} | |
} | |
.col-2-3 { width: 66.66%; } | |
.col-1-3 { width: 33.33%; } | |
.col-1-2 { width: 50%; } | |
.col-1-4 { width: 25%; } | |
.col-1-8 { width: 12.5%; } | |
.grid-pad { | |
padding: $pad 0 $pad $pad; | |
[class*='col-']:last-of-type { | |
padding-right: $pad; | |
} | |
} | |
body{ | |
font-family:helvetica, arial, sans-serif; | |
background: black; | |
overflow:hidden; | |
font-size: 14px; | |
color:$text; | |
padding:0px; | |
margin:0px; | |
} | |
//NAV | |
//=============================== | |
nav{ | |
top:0; | |
left:0; | |
width:100%; | |
padding:15px; | |
position:absolute; | |
transition:all 0.3s; | |
background: transparent; | |
border-bottom-width:3px; | |
border-bottom-style:solid; | |
border-bottom-color:transparent; | |
} | |
nav ul{ list-style:none; padding:0; margin:0; } | |
nav ul li {display:inline-block; vertical-align: top; } | |
.paused nav {background:rgba(black, 0.9); border-bottom-color:#111;} | |
.controls-right li{ margin-left:15px; } | |
.controls-left li{ margin-right:15px; } | |
.banner{ opacity:0; transition:all 0.3s;} | |
.paused .banner{ opacity:1; } | |
.banner h1{ margin:0 0 5px 0; font-size: 24px;} | |
.banner p{ margin:0; } | |
.banner .control{display: inline-block; padding:0 10px;} | |
.banner a{ | |
display: inline-block; | |
text-decoration: none; | |
background:$text; | |
color:black; | |
padding:8px 15px; | |
border-radius:4px; | |
margin:12px 0; | |
&:hover{background:darken($text, 10%);} | |
} | |
//BUTTONS | |
//=============================== | |
button{ | |
width:53px; | |
height:53px; | |
display:block; | |
cursor: pointer; | |
position:relative; | |
border-width:3px; | |
border-radius: 2px; | |
border-style:solid; | |
border-color:$button; | |
background:$button-bg; | |
transition:all 0.2s; | |
&:before, &:after, i:after, i:before{ | |
transition:all 0.2s; | |
border-color:$button; | |
background:$button; | |
} | |
} | |
button.active, button:active{ | |
border-color:$button-active; | |
background:$button-active-bg; | |
&:before, &:after, i:after, i:before{ | |
border-color:$button-active; | |
background:$button-active; | |
} | |
} | |
//BUTTON ICONS | |
//=============================== | |
.btn-pause:before{ | |
@extend .sudo; | |
width:10px; | |
height:25px; | |
top:11px; | |
left:26px; | |
} | |
.btn-pause:after{ | |
@extend .sudo; | |
width:10px; | |
height:25px; | |
top:11px; | |
left:11px; | |
border:none; | |
} | |
.btn-pause.active:before{ | |
display:none; | |
} | |
.btn-pause.active:after{ | |
@extend .sudo; | |
width: 0; | |
height: 0; | |
top: 9px; | |
left: 13px; | |
border-style: solid; | |
border-width: 15px 0 15px 25px; | |
background:transparent; | |
border-color: transparent transparent transparent $button-active; | |
} | |
.btn-computer:before{ | |
@extend .sudo; | |
height:10px; | |
width:30px; | |
left:9px; | |
top:9px; | |
} | |
.btn-computer:after{ | |
@extend .sudo; | |
height:10px; | |
width:30px; | |
left:9px; | |
top:30px; | |
} | |
.btn-computer i:before{ | |
@extend .sudo; | |
height:5px; | |
width:5px; | |
left:9px; | |
top:22px; | |
} | |
.btn-computer i:after{ | |
@extend .sudo; | |
height:5px; | |
width:5px; | |
left:34px; | |
top:22px; | |
} | |
.btn-laser:before{ | |
@extend .sudo; | |
width:30px; | |
height:15px; | |
bottom:10px; | |
left:9px; | |
border-width:1px; | |
border-radius: 0 0 30px 30px; | |
} | |
.btn-laser:after{ | |
@extend .sudo; | |
width:0; | |
height:0; | |
top: 9px; | |
left: 17px; | |
border-style: solid; | |
border-width: 0 7.5px 10px 7.5px; | |
background:transparent; | |
border-color: transparent transparent $button transparent; | |
} | |
.btn-laser:active:after{ | |
background:transparent; | |
border-color: transparent transparent $button-active transparent; | |
} | |
.btn-audio:before{ | |
@extend .sudo; | |
width: 10px; | |
height: 15px; | |
bottom: 16px; | |
left: 7px; | |
border-radius: 10px 0 0 10px; | |
} | |
.btn-audio:after{ | |
@extend .sudo; | |
width: 0; | |
height: 35px; | |
top: 6px; | |
left: 20px; | |
border-style: solid; | |
border-width: 10px 17px 10px 0; | |
background:transparent; | |
border-color: transparent $button transparent transparent; | |
} | |
.btn-audio.active:after, .btn-audio:active:after{ | |
background:transparent; | |
border-color: transparent $button-active transparent transparent !important; | |
} | |
//TARGETING COMPUTER | |
//=============================== | |
.computer > *{ | |
position:absolute; | |
background:black; | |
padding:15px 25px 6px 25px; | |
font-size: 24px; | |
color:red; | |
bottom:0; | |
} | |
.computer .label{ | |
margin-bottom:10px; | |
} | |
.computer .hits{ | |
left:0; | |
border-top: 2px solid $yellow; | |
border-right: 2px solid $yellow; | |
border-top-right-radius:10px; | |
> *{display: inline-block; vertical-align: top;} | |
.x{ margin:0 10px; font-size:20px; margin-top:15px;} | |
.xwing-count{ margin-top: 3px; position:relative;} | |
.xwing-count:before{ | |
@extend .sudo; | |
background:black; | |
height:5px; | |
width:100%; | |
top:46%; | |
} | |
} | |
.computer .counter{ | |
right:0; | |
text-align: center; | |
border-top: 2px solid $yellow; | |
border-left: 2px solid $yellow; | |
border-top-left-radius:10px; | |
} | |
.computer .counter:after{ | |
@extend .sudo; | |
background: black; | |
height:5px; | |
width:100%; | |
top:50%; | |
left:0; | |
} | |
.computer .number{ | |
width:35px; | |
text-align: center; | |
display:inline-block; | |
font-weight: bold; | |
font-family: 'Poiret One', sans-serif; | |
text-shadow: 0 0 4px rgba(red, 0.6); | |
font-size:40px; | |
color:red; | |
} |