├── README.md ├── endlessvoid.js ├── helpers.js ├── index.html ├── objects.js ├── style.css └── weapons.js /README.md: -------------------------------------------------------------------------------- 1 | # endlessvoid :rocket: 2 | 3 | . . . 4 | ' /\ . ' 5 | . . / \ . + . 6 | ' / \ ' 7 | + / .. \ . ' 8 | ' / .' '. \ . 9 | . /_/ ' \_\ 10 | . . . . . 11 | 12 | Infinite space to explore with a space ship on a HTML5 Canvas. 13 | The idea is to develop this into a game like direction. 14 | The ultimate goal is making it multi player. 15 | 16 | ## Demo! 17 | 18 | [Test it out here.](http://richrd.github.io/) (http://richrd.github.io/) 19 | *The current version is still really bare.* 20 | 21 | 22 | ## TODO 23 | 24 | * [ ] Implement a proper timestep (ticks per second) 25 | * [ ] Fix stars and add parallax effect. TODO: reposition all stars on hit 26 | * [ ] Optimize rendering and pre-render common graphics (pickups, etc.) 27 | * [ ] Clean up code and refactor it into proper modern js 28 | * [ ] ~~Improve planet generation~~ Design sun, planet and moon generation. 29 | * [ ] Different looking planets with more variation in size (planet colors and possibly craters etc) 30 | * [ ] Different looking ships 31 | * [ ] Asteroids with polygons 32 | * [ ] Some other flying stuff?! 33 | * [ ] Create proper game logic 34 | * [ ] Scale all movement by fps to get consistent speeds across platforms 35 | * [ ] Spawn on a planet (and respawn on previously visited planet on death) 36 | * [ ] Collision detection (space ships, bullets, planets) 37 | * [ ] Simple circle and rectangle based collisions to improve performance 38 | * [ ] Point collision for bullets 39 | * [ ] HUD displaying ship health, upgrades 40 | * [ ] Pickups for ship upgrades, points etc 41 | * [ ] Stars with orbiting planets (and planets with moons?) 42 | * [ ] Players (Ships) 43 | * [ ] Name 44 | * [ ] Health 45 | * [ ] Upgrades 46 | * [ ] Shields 47 | * [ ] Lighter shields against bullets 48 | * [ ] Harder shields to survive crashing into planets 49 | * [ ] Invisibility 50 | * [ ] Weapons 51 | * [X] Minigun 52 | * [X] Spread gun 53 | * [X] Cannon 54 | * [X] Bullet Ring 55 | * [ ] Missiles 56 | * [ ] Heat seeking missiles 57 | * [ ] Bombs (that are 'dropped') 58 | * [ ] Laser 59 | * [ ] Force field or pulse to bump other ships away from you 60 | * [ ] ... 61 | * [ ] Mines? 62 | * [ ] Turrets? 63 | 64 | * [X] Bullets (weapons fire different kinds of bullets, rename to Particle?) 65 | * [X] Name 66 | * [X] Radius 67 | * [X] Fire selay 68 | * [ ] Color 69 | * [ ] Damage amount 70 | * [ ] Bullet 'ownership' (who is dealing the damage) 71 | * [ ] Lifetime (prevent stray bullets from never disappearing if followed etc) 72 | * [ ] Twinkling bullets, like stars (as if they're burning) 73 | * [ ] Blast/Hit velocity (add directional velocity to target) 74 | * [ ] Posibility to emit more particles while traveling or when explding. 75 | * [X] Separate update and render operations 76 | * [X] Pause game feature (Only in single player mode) 77 | 78 | ## Multiplayer implementation 79 | * Logic 80 | * The game must be deterministic. That means: 81 | * Drop floating point operations? :( 82 | * With certain inputs the output is always the same 83 | * Enables fully accurate client side simulation 84 | * Networking / Connection 85 | * UDP is the DOPE for game dev, but unfortunately it's only in WebRTC, wich isn't widely supperted. 86 | * Try WebSockets first instead (TCP sucks for real time) 87 | * Client and server interaction options: 88 | * Deterministic lockstep: client only sends inputs, server updates game. 89 | * Game speed depends on slowest player :( 90 | * State syncing 91 | * Client can simulate future events 92 | * and server sends corrections (delta game state) 93 | * 94 | * If latency is a problem try to migrate to WebRTC 95 | * If there still are serious latency problems: 96 | * First implement LAN playing with suitable abstraction 97 | * Improve the abstraction to support latency 98 | * If all above fails, and multiplayer wont work: 99 | * Scrap the web version and migrate to Go 100 | * No browser limitations 101 | * UDP Support 102 | * Easy to read and write 103 | * Efficent 104 | * Model 105 | * Server tracks the game model 106 | * Server syncs the model state for only visible changes 107 | * Player state: 108 | * Position, angle and speed 109 | * Turning direction and acceleration 110 | * 111 | 112 | ## WISHLIST! (Comming some time 2015!) 113 | 1. [ ] Learn node.js (io.js) 114 | 2. [ ] Create multi player server 115 | 3. [ ] Implement game model at server 116 | 4. [ ] And input and rendering on the client side 117 | 5. [ ] If all above is not feasible: rewrite entire game in Go 118 | 119 | ## Bugs 120 | [ ] Ship explosion (and resetting stars) should happen at the end of next frame. 121 | Currently stars positions are not properly reset. 122 | 123 | ## Brainstorming! 124 | * Different ambient soundscapes based on location 125 | * Currency for buying and building things etc. 126 | * Big distances after which new areas occur 127 | * 3 worlds: one for mining (inhabited by neutral AI), one for building, one for fighting against the evil (inhabited by STI aka Solid State Intelligence and where player against player combat is possible) 128 | * Evil space worms 129 | * Black holes: different ones (for example: random teleport -blackhole) 130 | * Home base which you can change and teleport to (possibly last planet that you landed on) 131 | * Different kind of techologies: for attacking, for mining and gathering other resources, for defence, for navigation (maps etc.) and for traveling 132 | * Portals 133 | * Missiles (can only detonate themselves) and spy vehicles (invisible) _which you can control_. Defence tech for detechting spies and destroying missiles) 134 | * Fuel: you cant travel forever fast --> better fuel as you build your base and dig deeper --> super tech later (teleporting etc) 135 | * Allianced which start building their cities from zero (newcomers can build their own or join ones already existing) 136 | * Different kind of alliances: anarchist, hierarchial, capitalist etc. For example: anarchist-communist alliance has no private owning of resources, 137 | no central leadership, can swap the space ship they fly [no-one own ships, but they can require skill which must be personally acquired], and no _central_ resources, lots of negotiance etc. 138 | * Different game strategies: individual vs collective and centralized against noncentralized --> both strategies are good in their own ways 139 | * Different kind of politics (depending on kind of the alliance) which affect the way you make decisions between co-players (discussion and voting, consensus or despotic) 140 | * You can dig and find precious metals and minerals inside planets 141 | * Trick s: you can for example travel without fuel by using storm currents and gravity force of planets 142 | * UFO's: which can bring messages from unknown galaxies and new technology or sudden destruction (which can also be caused by meteorites) 143 | * Epic lore!!!11111 144 | * For history of humans and different alliances 145 | * Already existing kindoms! which you can join (or then be an anarchist): all new players start as people who are exploring unkown frontiers which still have plenty minerals 146 | * History and explanations for different UFO-races, STI etc. 147 | * References to Tesla and Reich 148 | * Comm tech to inform and command own bases. 149 | * Technology which makes mining richer planets, which enables mining more valuable resources 150 | * AI-Kindoms: you can communicate with them, trade with them, join them, be in war with them (if you are an anarchist) --> different options when communicating 151 | * Possibility to build ships on owned planets, and to control desired ship at any time. 152 | 153 | -------------------------------------------------------------------------------- /endlessvoid.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Canvas 2D Space 3 | * Richard Lewis 2015 4 | * http://bittemple.net 5 | */ 6 | 7 | function EndlessVoid() { 8 | this.canvas_id = "#bg-canvas" 9 | this.pressed_keys = []; 10 | 11 | this.interval_ms = 40; 12 | this.interval_id = null; 13 | this.paused = false; 14 | this.last_update = new Date; 15 | 16 | this.origin = new Vector(); 17 | this.space_ship = new SpaceShip(this); 18 | this.edge_threshhold = new Vector(window.innerWidth*.2, window.innerHeight*.2); 19 | 20 | this.minimap_scale = .0100 21 | this.minimap_center = new Vector(130, 130); 22 | this.minimap_radius = 90 23 | 24 | this.stars = []; 25 | this.star_amount = 100; 26 | 27 | this.planets = []; 28 | this.planet_amount = 150; 29 | 30 | this.bullets = []; 31 | this.bullets_max = 100; 32 | } 33 | 34 | EndlessVoid.prototype.load = function() { 35 | $(document.body).keydown($.proxy(function (evt) { 36 | var key = evt.keyCode; 37 | if(!this.pressed_keys[key]) { 38 | this.pressed_keys[key] = true; 39 | } 40 | }, this)); 41 | 42 | $(document.body).keyup($.proxy(function (evt) { 43 | var key = evt.keyCode; 44 | var val = this.pressed_keys[key]; 45 | if (val) { 46 | this.pressed_keys[evt.keyCode] = false; 47 | } 48 | if (key == 19) { // Pause 49 | this.pause(); 50 | } 51 | if(key == 87) { // W 52 | this.space_ship.switch_weapon(); 53 | } 54 | }, this)); 55 | } 56 | 57 | EndlessVoid.prototype.tick = function() { 58 | this.handleKeys() 59 | if (!this.paused) { 60 | this.update() 61 | this.render(); 62 | } 63 | this.last_update = new Date; 64 | } 65 | 66 | EndlessVoid.prototype.start = function() { 67 | if(this.bgLoad()) { 68 | this.bgRender(); 69 | } 70 | this.interval_id = setInterval($.proxy(this.tick, this), this.interval_ms); 71 | } 72 | 73 | EndlessVoid.prototype.pause = function() { 74 | if(!this.paused) { 75 | this.paused = 1 76 | return 77 | } 78 | this.paused = 0 79 | } 80 | 81 | EndlessVoid.prototype.updateStars = function(add) { 82 | for (var i = this.stars.length - 1; i >= 0; i--) { 83 | star = this.stars[i] 84 | star.move(add.mul(star.z_multiplier)) 85 | }; 86 | } 87 | 88 | EndlessVoid.prototype.resetStars = function() { 89 | for (var i = this.stars.length - 1; i >= 0; i--) { 90 | new_pos = new Vector() 91 | new_pos.random(new Vector(window.innerWidth, window.innerHeight, 99), false); 92 | star = this.stars[i] 93 | star.set(new_pos) 94 | }; 95 | } 96 | 97 | EndlessVoid.prototype.bgLoad = function() { 98 | // Create stars. 99 | for (var i = 0; i < this.star_amount; i++) { 100 | mul = 1 101 | if (i > this.star_amount/2) { 102 | //x=1 103 | mul = 0.98 104 | } 105 | p = new BgStar(); 106 | p.random(new Vector(window.innerWidth, window.innerHeight, 99), false); 107 | p.z_multiplier = mul 108 | this.stars.push(p) 109 | }; 110 | 111 | // Create planets. 112 | p = new Planet(this); 113 | //p.random(new Vector(0, 0, 99)); 114 | p.set_radius(150); 115 | this.planets.push(p) 116 | this.space_ship.y = -160; 117 | this.space_ship.angle = -90; 118 | for (var i = 0; i < this.planet_amount; i++) { 119 | p = new Planet(this); 120 | p.random(new Vector(window.innerWidth*40, window.innerHeight*40, 99)); 121 | this.planets.push(p) 122 | }; 123 | 124 | this.space_ship.rotateShapes(); 125 | this.space_ship.speed.random(new Vector(.05,.05,.05)); 126 | } 127 | 128 | EndlessVoid.prototype.minimapTranslate = function(v) { 129 | center = new Vector(window.innerWidth/2, window.innerHeight/2); 130 | // WTF? 131 | return v.copy().add(this.space_ship.copy().invert()).mul(this.minimap_scale).add(this.minimap_center); 132 | } 133 | 134 | EndlessVoid.prototype.bgTranslate = function(v) { 135 | center = new Vector(window.innerWidth/2, window.innerHeight/2); 136 | return v.copy().add(center).add(this.origin); 137 | } 138 | 139 | EndlessVoid.prototype.renderMinimap = function(ctx) { 140 | ctx.strokeStyle = "rgba(180,220,255, .3)"; 141 | ctx.fillStyle = "rgba(180,220,255, .05)"; 142 | 143 | ctx.beginPath(); 144 | ctx.arc(this.minimap_center.x, this.minimap_center.y, this.minimap_radius, 0, Math.PI*2, true); 145 | ctx.stroke(); 146 | ctx.fill() 147 | ctx.beginPath() 148 | ctx.fillStyle = "rgb(255,255,255)"; 149 | ctx.arc(this.minimap_center.x, this.minimap_center.y, 2, 0, Math.PI*2, true); 150 | ctx.fill() 151 | 152 | d1 = rotate_point(this.minimap_radius, 0, 0, 0, this.space_ship.angle) 153 | d1 = new Vector(d1.x, d1.y) 154 | d1.add(this.minimap_center) 155 | 156 | d2 = rotate_point(this.minimap_radius-(this.minimap_radius*.1), 0, 0, 0, this.space_ship.angle) 157 | d2 = new Vector(d2.x, d2.y) 158 | d2.add(this.minimap_center) 159 | 160 | ctx.arc(d2.x, d2.y, 2, 0, Math.PI*2, true); 161 | ctx.strokeStyle = "rgba(255,255,255, 1)"; 162 | ctx.beginPath(); 163 | ctx.moveTo(d2.x, d2.y) 164 | ctx.lineTo(d1.x, d1.y) 165 | 166 | ctx.stroke(); 167 | //ctx.strokeStyle = "rgba(180, 220, 255, .3)"; 168 | 169 | ctx.strokeStyle = "rgba(180, 255, 180, .4)"; 170 | ctx.fillStyle = "rgba(180, 255, 180, .4)"; 171 | for (var i = this.planets.length - 1; i >= 0; i--) { 172 | p = this.minimapTranslate(this.planets[i]) 173 | if(this.minimap_center.dist(p) > this.minimap_radius) {continue} 174 | ctx.beginPath(); 175 | ctx.arc(p.x, p.y, this.planets[i].radius*this.minimap_scale,0,Math.PI*2,true); 176 | ctx.stroke(); 177 | ctx.fill() 178 | }; 179 | } 180 | 181 | EndlessVoid.prototype.update = function() { 182 | // Update spaceship 183 | this.space_ship.update() 184 | // Update planets (and gravity effect on space ship) 185 | for (var i = this.planets.length - 1; i >= 0; i--) { 186 | this.planets[i].update() 187 | } 188 | // Update bullets 189 | for (var i = this.bullets.length - 1; i >= 0; i--) { 190 | this.bullets[i].update() 191 | } 192 | } 193 | 194 | EndlessVoid.prototype.render = function() { 195 | if(window.innerWidth+window.innerHeight < 1400) {return;} 196 | 197 | this.edge_threshhold.x = window.innerWidth * .4 198 | this.edge_threshhold.y = window.innerHeight * .4 199 | 200 | var ctx = $(this.canvas_id)[0].getContext('2d'); 201 | ctx.canvas.width = window.innerWidth; 202 | ctx.canvas.height = window.innerHeight; 203 | center = new Vector(window.innerWidth/2, window.innerHeight/2); 204 | 205 | ctx.font = "20px Georgia"; 206 | ctx.fillStyle = "rgb(255,255,255)"; 207 | ctx.lineWidth = "1.5"; 208 | 209 | // ctx.fillText("ship: "+parseInt(space_ship.x)+","+parseInt(space_ship.y),10,50); 210 | // ctx.fillText("orig ang: "+angle_to_target(space_ship,new Vector()),10,75); 211 | 212 | ctx.strokeStyle = "rgba(240,240,210, .98)"; 213 | for (var i = this.stars.length - 1; i >= 0; i--) { 214 | star = this.stars[i] 215 | star.update() 216 | value = .4 + (star.z-1) / 2 / 100.0; 217 | ctx.strokeStyle = "rgba("+star.color[0]+","+star.color[1]+","+star.color[2]+","+value+")"; 218 | ctx.beginPath(); 219 | ctx.arc(star.x, star.y, .2+value, 0, Math.PI*2, true); 220 | ctx.stroke(); 221 | }; 222 | 223 | ctx.strokeStyle = "rgba(180,255,180, 1)"; 224 | ctx.fillStyle = "rgba(0,10,0, 1)"; 225 | for (var i = this.planets.length - 1; i >= 0; i--) { 226 | planet = this.planets[i] 227 | ctx.beginPath(); 228 | p = this.bgTranslate(planet) 229 | // Replace checking center coordinates 230 | // with checking the bounding rects 231 | // if(!isOnScreen(p)) {continue;} 232 | ctx.arc(p.x, p.y, planet.radius, 0, Math.PI*2, true); 233 | ctx.fill(); 234 | ctx.stroke(); 235 | ang = angle_to_target(planet,this.space_ship) 236 | rot = rotate_point(planet.radius, 0, 0, 0, ang) 237 | v = planet.copy().add(new Vector(rot.x, rot.y)) 238 | v = this.bgTranslate(v) 239 | ctx.beginPath(); 240 | ctx.moveTo(p.x, p.y); 241 | ctx.moveTo(v.x, v.y); 242 | ctx.stroke() 243 | }; 244 | 245 | ctx.fillStyle = "rgba(200,255,255, 1)"; 246 | for (var i = this.bullets.length - 1; i >= 0; i--) { 247 | bullet = this.bullets[i] 248 | ctx.beginPath(); 249 | p = this.bgTranslate(bullet) 250 | if(!isOnScreen(p)) 251 | { 252 | this.bullets.splice(i, 1); 253 | continue; 254 | } 255 | ctx.arc(p.x, p.y, bullet.radius, 0, Math.PI*2, true); 256 | ctx.fill(); 257 | }; 258 | 259 | 260 | 261 | ctx.strokeStyle = "rgba(180,180,255, .5)"; 262 | ctx.beginPath(); 263 | p = this.bgTranslate(new Vector()); 264 | ctx.arc(p.x, p.y, 20, 0, Math.PI*2, true); 265 | ctx.stroke(); 266 | 267 | ctx.beginPath(); 268 | ctx.arc(p.x, p.y, 2, 0, Math.PI*2, true); 269 | ctx.stroke(); 270 | 271 | this.space_ship.render(ctx) 272 | this.renderMinimap(ctx) 273 | 274 | // Testing what a pickup item could look like 275 | //ctx.strokeStyle = "rgba(255,80,10, .9)"; 276 | //ctx.beginPath() 277 | //p = new Vector(500, 500); 278 | //ctx.arc(p.x, p.y, 10, 0, Math.PI*2, true); 279 | //ctx.fillRect(p.x-1, p.y-1, 3, 3); 280 | //ctx.stroke(); 281 | 282 | 283 | now = new Date; 284 | fps = 1000 / (now - this.last_update); 285 | ctx.fillText(Math.round(fps) + " FPS", 10, 20); 286 | ctx.fillText(this.bullets.length + "BULLETS", 100, 20); 287 | } 288 | 289 | EndlessVoid.prototype.handleKeys = function() { 290 | // Handle keys. 291 | if(this.pressed_keys[37]) { // Arrow Left 292 | this.space_ship.turn(-1) 293 | } 294 | if(this.pressed_keys[39]) { // Arrow Right 295 | this.space_ship.turn(1) 296 | } 297 | if(this.pressed_keys[38]) { // Arrow Up 298 | this.space_ship.accelerate(1) 299 | } 300 | if(this.pressed_keys[40]) { // Arrow Down 301 | this.space_ship.accelerate(-1) 302 | } 303 | if(this.pressed_keys[32]) { // Space 304 | this.space_ship.shoot() 305 | } 306 | if(this.pressed_keys[90]) { // Z 307 | this.space_ship.turbo() 308 | } 309 | if(this.pressed_keys[48]) { // 0 310 | this.space_ship.x = 0 311 | this.space_ship.y = 0 312 | this.space_ship.speed = new Vector(); 313 | } 314 | } 315 | 316 | $(function () { 317 | app = new EndlessVoid(); 318 | app.load() 319 | app.start() 320 | }); 321 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper functions 3 | * 4 | */ 5 | 6 | /** 7 | * Rotate 2d coordinates around a point and get new coordinates 8 | */ 9 | function rotate_point(pointX, pointY, originX, originY, angle) { 10 | angle = angle * Math.PI / 180.0; 11 | return { 12 | x: Math.cos(angle) * (pointX-originX) - Math.sin(angle) * (pointY-originY) + originX, 13 | y: Math.sin(angle) * (pointX-originX) + Math.cos(angle) * (pointY-originY) + originY 14 | }; 15 | 16 | } 17 | 18 | /** 19 | * Rotate each cordinate in an array and return a new array 20 | */ 21 | function rotate_points(points, origin, angle) { 22 | new_p = []; 23 | for (var i = points.length - 1; i >= 0; i--) { 24 | point = points[i] 25 | r = rotate_point(point.x,point.y,origin.x,origin.y,angle) 26 | new_p.push(r) 27 | } 28 | new_p.reverse(); 29 | return new_p; 30 | } 31 | 32 | /** 33 | * Check if a point is within the browser window 34 | */ 35 | function isOnScreen(v) { 36 | if(v.x>0 && v.x < window.innerWidth) 37 | { 38 | if(v.y>0 && v.y < window.innerHeight) 39 | { 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | /** 47 | * Get a ranom intiger within a range 48 | */ 49 | function getRandomInt (min, max) { 50 | return Math.floor(Math.random() * (max - min + 1)) + min; 51 | } 52 | 53 | /** 54 | * Calculate the angle between two cordinates TODO: incorrect results, fix it 55 | */ 56 | function angle_to_target(pos1, pos2) { 57 | dx = pos1.x - pos2.x; 58 | dy = pos1.y - pos2.y; 59 | theta = Math.atan2(dy, dx); 60 | theta *= 180 / Math.PI // rads to degs 61 | if(theta < 0) { 62 | theta = 360 + theta 63 | } 64 | return theta 65 | } 66 | 67 | /** 68 | * Convert HSV to RGB 69 | */ 70 | 71 | function hsv_to_rgb(color) { 72 | h = color[0] / 255 73 | s = color[1] / 255 74 | v = color[2] / 255 75 | //console.log(h,s,v); 76 | h_i = parseInt(h*6) 77 | f = h*6 - h_i 78 | p = v * (1 - s) 79 | q = v * (1 - f*s) 80 | t = v * (1 - (1 - f) * s) 81 | //console.log(h_i); 82 | if (h_i == 0) { 83 | r = v 84 | g = t 85 | b = p 86 | } 87 | if (h_i == 1) { 88 | r = q 89 | g = v 90 | b = p 91 | } 92 | if (h_i == 2) { 93 | r = p 94 | g = v 95 | b = t 96 | } 97 | if (h_i == 3) { 98 | r = p 99 | g = q 100 | b = v 101 | } 102 | if (h_i == 4) { 103 | r = t 104 | g = p 105 | b = v 106 | } 107 | if (h_i == 5) { 108 | r = v 109 | g = p 110 | b = q 111 | } 112 | 113 | rgb = [parseInt(r*256), parseInt(g*256), parseInt(b*256)] 114 | //console.log(rgb) 115 | return rgb 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Endless Void 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |

Keys

18 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /objects.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * A vector object representing xyz coordinates, with methods for common operations 4 | */ 5 | function Vector(x, y, z) { 6 | this.x = typeof x !== 'undefined' ? x : 0; 7 | this.y = typeof y !== 'undefined' ? y : 0; 8 | this.z = typeof z !== 'undefined' ? z : 0; 9 | } 10 | Vector.prototype.set = function(v) { 11 | this.x = v.x 12 | this.y = v.y 13 | this.z = v.z 14 | } 15 | Vector.prototype.copy = function() { 16 | v = new Vector(this.x, this.y, this.z) 17 | return v; 18 | } 19 | Vector.prototype.add = function(v) { 20 | this.x = this.x + v.x 21 | this.y = this.y + v.y 22 | this.z = this.z + v.z 23 | return this 24 | } 25 | Vector.prototype.sub = function(v) { 26 | this.x = this.x - v.x 27 | this.y = this.y - v.y 28 | this.z = this.z - v.z 29 | return this 30 | } 31 | Vector.prototype.mul = function(m) { 32 | this.x = this.x * m 33 | this.y = this.y * m 34 | this.z = this.z * m 35 | return this 36 | } 37 | Vector.prototype.combine = function() { 38 | return Math.abs(this.x)+Math.abs(this.y) 39 | } 40 | Vector.prototype.dist = function(v) { 41 | xd = this.x - v.x 42 | yd = this.y - v.y 43 | return Math.sqrt( Math.pow(Math.abs(xd),2) + Math.pow(Math.abs(yd),2) ); 44 | } 45 | Vector.prototype.invert = function() { 46 | this.x = this.x * -1 47 | this.y = this.y * -1 48 | this.z = this.z * -1 49 | return this 50 | } 51 | Vector.prototype.random = function(max,negative) { 52 | max = typeof max !== 'undefined' ? max : new Vector(1,1,1); 53 | negative = typeof negative !== 'undefined' ? negative : true; 54 | this.x = getRandomInt(0,max.x) 55 | this.y = getRandomInt(0,max.y) 56 | this.z = getRandomInt(0,max.z) 57 | 58 | if(negative) { 59 | this.x = this.x-(max.x/2); 60 | this.y = this.y-(max.y/2); 61 | this.z = this.z-(max.z/2); 62 | } 63 | return this 64 | } 65 | 66 | /** 67 | * A particle object with a position and speed. Used as a foundation for things in space 68 | */ 69 | function Particle() { 70 | this.max_x = 9999 71 | this.max_y = 9999 72 | this.speed = new Vector(0, 0); 73 | } 74 | Particle.prototype = new Vector( ); 75 | Particle.prototype.update = function() { 76 | this.x = this.x + this.speed.x; 77 | this.y = this.y + this.speed.y; 78 | } 79 | 80 | 81 | /** 82 | * A small star used in the background star field 83 | */ 84 | function BgStar() { 85 | this.twinkle = false; 86 | this.color = [255, 255, 255]; 87 | } 88 | BgStar.prototype = new Particle( ); 89 | BgStar.prototype.warp = function() { 90 | this.twinkle = false; 91 | this.color = [255, 255, 255]; 92 | if(getRandomInt(0, 10) == 5) { 93 | this.twinkle = true; 94 | } else { 95 | this.z = getRandomInt(0, 99); 96 | } 97 | if(getRandomInt(0, 150) > 149) { 98 | this.color = hsv_to_rgb( [getRandomInt(0,255), 205, 250] ) 99 | this.z = getRandomInt(0, 50) + 49; 100 | } 101 | } 102 | BgStar.prototype.move = function(add) { 103 | this.add(add) 104 | if(this.x < 0) { 105 | this.x = window.innerWidth 106 | this.y = getRandomInt(0,window.innerHeight) 107 | this.warp() 108 | } 109 | if(this.x > window.innerWidth) { 110 | this.x = 0 111 | this.y = getRandomInt(0,window.innerHeight) 112 | this.warp() 113 | } 114 | if(this.y < 0) { 115 | this.y = window.innerHeight 116 | this.x = getRandomInt(0,window.innerWidth) 117 | this.warp() 118 | } 119 | if(this.y > window.innerHeight) { 120 | this.y = 0 121 | this.x = getRandomInt(0,window.innerWidth) 122 | this.warp() 123 | } 124 | } 125 | 126 | BgStar.prototype.update = function() { 127 | if(this.twinkle) { 128 | this.z = getRandomInt(0,99) 129 | } 130 | } 131 | 132 | 133 | /** 134 | * A planet object that has gravity 135 | */ 136 | function Planet(main) { 137 | this.main = main 138 | this.radius = 240; 139 | this.mass = this.radius*this.radius; 140 | this.max_dist = 40000; 141 | this.max_gravity_dist = 3600; 142 | } 143 | Planet.prototype = new Particle( ); 144 | 145 | Planet.prototype.set_radius = function(radius) { 146 | this.radius = radius; 147 | this.mass = this.radius*this.radius; 148 | } 149 | Planet.prototype.warp = function() { 150 | p = new Vector(); 151 | p.random(new Vector(window.innerWidth*(50,window.innerHeight*50,99))); 152 | angle = angle_to_target(this.main.space_ship.speed,{x:0,y:0}) 153 | distance = rotate_point(this.max_dist/2,0,0,0,angle) 154 | // p.add(this.main.space_ship).add(distance) //.add(this.main.space_ship.speed.copy().mul(102)) // Planets vanish at high speeds 155 | p.add(this.main.space_ship).add(distance).add(this.main.space_ship.speed.copy().mul(102)) // Planets vanish at high speeds 156 | this.set(p) 157 | } 158 | 159 | Planet.prototype.update = function() { 160 | if(this.main.space_ship.dist(this)>this.max_dist) { 161 | this.warp() 162 | } 163 | if(this.main.space_ship.landed) {return} 164 | dist = this.dist(this.main.space_ship) 165 | 166 | // Handle Gravity 167 | if(dist < this.max_gravity_dist) { 168 | if(dist2.4){ 170 | this.main.space_ship.explode() 171 | } 172 | else { 173 | this.main.space_ship.land(); 174 | } 175 | } 176 | P2 = this.main.space_ship 177 | P = this 178 | XDiff = P.x - P2.x 179 | YDiff = P.y - P2.y 180 | Distance = Math.sqrt((Math.pow(XDiff, 2))+(Math.pow(YDiff, 2))) 181 | if (Distance < 2) {Distance = 2} 182 | a = 0.125 183 | Force = a*(P.mass*P2.mass)/Math.pow(Distance,2)*4 184 | Acceleration = Force / (P2.mass*2) 185 | XComponent = XDiff/Distance 186 | YComponent = YDiff/Distance 187 | v = new Vector(Acceleration * XComponent,Acceleration * YComponent) 188 | this.main.space_ship.speed.add(v) 189 | } 190 | } 191 | Planet.prototype.random = function(max,negative) { 192 | max = typeof max !== 'undefined' ? max : new Vector(1,1,1); 193 | negative = typeof negative !== 'undefined' ? negative : true; 194 | this.x = getRandomInt(0,max.x) 195 | this.y = getRandomInt(0,max.y) 196 | this.radius = 150 + getRandomInt(0,250) 197 | this.mass = this.radius*this.radius; 198 | 199 | if(negative) { 200 | this.x = this.x-(max.x/2); 201 | this.y = this.y-(max.y/2); 202 | this.z = this.z-(max.z/2); 203 | } 204 | } 205 | 206 | 207 | 208 | /** 209 | * A star object that has orbiting planets 210 | */ 211 | function Sun(main) { 212 | this.main = main 213 | this.radius = 540; 214 | this.mass = this.radius*this.radius; 215 | this.max_dist = 40000; 216 | this.max_gravity_dist = 3600; 217 | 218 | this.min_planets = 1 219 | this.max_planets = 4 220 | 221 | this.planets = [] 222 | this.generate_planets(); 223 | } 224 | 225 | Sun.prototype = new Particle( ); 226 | 227 | Sun.prototype.rand_int = function(min, max) { 228 | return Math.random() * (max - min) + min; 229 | } 230 | 231 | Sun.prototype.generate_planets = function() { 232 | amount = this.rand_int(this.min_planets, this.max_planets); 233 | i = 0; 234 | while (i < amount) { 235 | p = new Planet(this); 236 | // From endlessvoid.js 237 | p.random(new Vector(window.innerWidth*40, window.innerHeight*40, 99)); 238 | planet = null; 239 | //p = new Vector(); 240 | //p.random(new Vector(window.innerWidth*(50,window.innerHeight*50,99))); 241 | //angle = angle_to_target(this.main.space_ship.speed,{x:0,y:0}) 242 | //distance = rotate_point(this.max_dist/2,0,0,0,angle) 243 | //// p.add(this.main.space_ship).add(distance) //.add(this.main.space_ship.speed.copy().mul(102)) // Planets vanish at high speeds 244 | //p.add(this.main.space_ship).add(distance).add(this.main.space_ship.speed.copy().mul(102)) // Planets vanish at high speeds 245 | //this.set(p) 246 | i += 1; 247 | } 248 | } 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | /** 263 | * A bullet that the spaceship can fire 264 | */ 265 | function Bullet(x, y, z, radius) { 266 | this.x = typeof x !== 'undefined' ? x : 0; 267 | this.y = typeof y !== 'undefined' ? y : 0; 268 | this.z = typeof z !== 'undefined' ? z : 0; 269 | this.radius = typeof radius !== 'undefined' ? radius : 1; 270 | } 271 | Bullet.prototype = new Particle( ); 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | /** 280 | * The space ship 281 | */ 282 | function SpaceShip(main) { 283 | this.main = main 284 | this.z = 0; 285 | 286 | this.color = "red"; // TODO: implement this 287 | this.angle = 180; 288 | this.angle = 0; 289 | this.turn_amount = 6.2; 290 | this.turn_amount = 8.2; 291 | this.turn_amount = 9.9; 292 | this.mass = 13*13; 293 | this.radius = 10; 294 | 295 | this.landed = false; 296 | this.acceleration = 0.35; 297 | this.acceleration = 0.45; 298 | this.accelerating = false; 299 | this.turbo_on = false; 300 | this.turbo_thrust = 2.45; 301 | this.speed_v = 0; 302 | this.speed_max = 50; 303 | 304 | this.weapons = []; 305 | this.weapons.push( new Minigun(this.main, this) ); 306 | this.weapons.push( new Spreadgun(this.main, this) ); 307 | this.weapons.push( new Cannon(this.main, this) ); 308 | this.weapons.push( new Ring(this.main, this) ); 309 | this.weapon_index = 0; 310 | this.weapon = this.weapons[0]; 311 | 312 | 313 | this.shape = [ 314 | {x:10, y:0}, 315 | {x:-6, y:7}, 316 | {x:-2, y:0}, 317 | {x:-6, y:-7} 318 | ] 319 | 320 | //this.thrust = [{x:-3, y:-4}, {x:0, y:-11}, {x:3, y:-4}] 321 | this.thrust = [{x:-4, y:-3}, {x:-11, y:0}, {x:-4, y:3}] 322 | this.shape_rot = self.shape; 323 | this.thrust_rot = self.thrust; 324 | } 325 | 326 | SpaceShip.prototype = new Particle( ); 327 | 328 | SpaceShip.prototype.update = function() { 329 | if(this.landed) {return;} 330 | this.x = this.x + this.speed.x; 331 | this.y = this.y + this.speed.y; 332 | screen_pos = this.main.bgTranslate(this.copy()) 333 | 334 | if(screen_pos.x < this.main.edge_threshhold.x) { 335 | add = new Vector(this.main.edge_threshhold.x-screen_pos.x,0) 336 | this.main.origin.add(add) 337 | this.main.updateStars(add) 338 | } 339 | if(screen_pos.y < this.main.edge_threshhold.y) { 340 | add = new Vector(0,this.main.edge_threshhold.y-screen_pos.y) 341 | this.main.origin.add(add) 342 | this.main.updateStars(add) 343 | } 344 | if(screen_pos.x > window.innerWidth-this.main.edge_threshhold.x) { 345 | add = new Vector(window.innerWidth-this.main.edge_threshhold.x-screen_pos.x,0) 346 | this.main.origin.add(add) 347 | this.main.updateStars(add) 348 | } 349 | if(screen_pos.y > window.innerHeight-this.main.edge_threshhold.y) { 350 | add = new Vector(0,window.innerHeight-this.main.edge_threshhold.y-screen_pos.y) 351 | this.main.origin.add(add) 352 | this.main.updateStars(add) 353 | } 354 | } 355 | SpaceShip.prototype.switch_weapon = function() { 356 | this.weapon_index += 1; 357 | if(this.weapon_index >= this.weapons.length) { 358 | this.weapon_index = 0 359 | } 360 | this.weapon = this.weapons[this.weapon_index] 361 | } 362 | SpaceShip.prototype.shoot = function() { 363 | this.weapon._fire() 364 | } 365 | SpaceShip.prototype.explode = function() { 366 | for (var i = 0; i < 20; i++) { 367 | bullet = new Bullet(this.x, this.y); 368 | bullet.speed = new Vector() 369 | bullet.speed.add(rotate_point(2.5, 0, 0, 0, getRandomInt(0, 360))) 370 | this.main.bullets.push(bullet) 371 | }; 372 | this.sub(this.speed.copy().mul(20)) 373 | this.speed = new Vector() 374 | this.landed = false; 375 | this.main.resetStars() 376 | } 377 | SpaceShip.prototype.turn = function(amount) { 378 | this.angle = this.angle + (this.turn_amount*amount); 379 | if(this.angle > 360) 380 | { 381 | this.angle = this.angle-360; 382 | } 383 | if(this.angle < 0) 384 | { 385 | this.angle = 360-Math.abs(this.angle) 386 | } 387 | this.rotateShapes() 388 | this.landed = false; 389 | } 390 | SpaceShip.prototype.rotateShapes = function() { 391 | this.shape_rot = rotate_points(this.shape,new Vector(),this.angle) 392 | this.thrust_rot = rotate_points(this.thrust,new Vector(),this.angle) 393 | } 394 | SpaceShip.prototype.accelerate = function(amount) { 395 | this.accelerating = true; 396 | v = (this.acceleration*amount) 397 | p = rotate_point(v, 0, 0, 0, this.angle) 398 | this.speed.x = this.speed.x + p.x 399 | this.speed.y = this.speed.y + p.y 400 | this.landed = false; 401 | } 402 | SpaceShip.prototype.turbo = function() { 403 | this.turbo_on = true; 404 | v = this.turbo_thrust 405 | rot = rotate_point(v, 0, 0, 0, this.angle) 406 | p = new Vector( rot.x, rot.y ) 407 | this.speed.add(p) 408 | } 409 | SpaceShip.prototype.maxSpeed = function() { 410 | return this.speed.x+this.speed.y>this.speed_max 411 | } 412 | SpaceShip.prototype.land = function() { 413 | this.landed = true; 414 | this.speed.x = 0 415 | this.speed.y = 0 416 | } 417 | 418 | SpaceShip.prototype.render = function(ctx) { 419 | ctx.strokeStyle = "rgba(156,232,255,0.9)"; 420 | ctx.beginPath(); 421 | 422 | p = this.main.bgTranslate(this.copy().add(this.shape_rot[0])) 423 | ctx.lineTo(p.x,p.y) 424 | for (var i = this.shape_rot.length - 1; i >= 0; i--) { 425 | point = this.shape_rot[i] 426 | p = this.main.bgTranslate(this.copy().add(point)) 427 | ctx.lineTo(p.x,p.y) 428 | }; 429 | ctx.stroke(); 430 | if(this.accelerating || this.turbo_on) { 431 | ctx.strokeStyle = "rgba(255,100,100, 0.9)"; 432 | if(this.turbo_on) { 433 | ctx.strokeStyle = "rgba(100,100,255, 0.9)"; 434 | } 435 | ctx.beginPath(); 436 | p = this.main.bgTranslate(this.copy().add(this.thrust_rot[0])) 437 | ctx.lineTo(p.x,p.y) 438 | for (var i = this.thrust_rot.length - 1; i >= 0; i--) { 439 | p = this.main.bgTranslate(this.copy().add(this.thrust_rot[i])) 440 | ctx.lineTo(p.x,p.y) 441 | }; 442 | ctx.stroke(); 443 | } 444 | 445 | this.accelerating = false; 446 | this.turbo_on = false; 447 | } -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000; 3 | color: #fff; 4 | font-weight: 600; 5 | font-family: "Ruda", sans-serif; 6 | } 7 | .full-screen { 8 | position: absolute; 9 | top:0; 10 | left:0; 11 | right:0; 12 | bottom:0; 13 | } 14 | #background { 15 | } 16 | #bg-canvas { 17 | } 18 | #foreground { 19 | z-index:100; 20 | font-size:1vw; 21 | letter-spacing:.05em; 22 | } 23 | #foreground .help { 24 | background-color: rgba(0,0,0,0.5); 25 | box-shadow:inset 0 0 15px 10px rgba(20,20,20,0.5); 26 | color: rgba(255,255,255,0.8); 27 | position:absolute; 28 | top:20px; right:20px; 29 | padding: 5px 25px; 30 | border-radius: 8px; 31 | opacity:.9; 32 | } 33 | #foreground .help ul { 34 | list-style:none; 35 | } 36 | #foreground .help ul li span { 37 | display:inline-block; 38 | min-width:4vw; 39 | margin-right:.3em; 40 | text-align:right; 41 | color:#fff; 42 | } -------------------------------------------------------------------------------- /weapons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Weapons 3 | */ 4 | 5 | function Weapon(main, ship) { 6 | this.main = main; 7 | this.ship = ship; 8 | 9 | this.name = ""; 10 | this.fire_time = 0; 11 | this.fire_delay = 0 // Minimum time in ms to wait between shots 12 | } 13 | Weapon.prototype._can_fire = function() { 14 | now = new Date; 15 | if(now - this.fire_time > this.fire_delay) { 16 | return true; 17 | } 18 | } 19 | Weapon.prototype._fire = function() { 20 | if(this._can_fire()) { 21 | this.fire(); 22 | this.fire_time = new Date; 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | Weapon.prototype.fire = function() { 29 | return; 30 | } 31 | Weapon.prototype.add_bullet = function(angle, speed, radius) { 32 | if(typeof(angle)==='undefined') angle = 0; 33 | if(typeof(speed)==='undefined') speed = 30; 34 | if(typeof(radius)==='undefined') radius = 1; 35 | bullet = new Bullet(this.ship.x, this.ship.y); 36 | bullet.speed = this.ship.speed.copy(); 37 | bullet.radius = radius 38 | bullet.speed.add(rotate_point(speed, 0, 0, 0, this.ship.angle + angle)); 39 | this.main.bullets.push(bullet); 40 | } 41 | 42 | 43 | function Minigun(main, ship) { 44 | this.main = main; 45 | this.ship = ship; 46 | this.name = "Minigun"; 47 | this.fire_delay = 28; 48 | } 49 | Minigun.prototype = new Weapon( ); 50 | Minigun.prototype.fire = function() { 51 | this.add_bullet(0, 20) 52 | } 53 | 54 | 55 | 56 | function Spreadgun(main, ship) { 57 | this.main = main; 58 | this.ship = ship; 59 | this.name = "Spreadgun"; 60 | this.fire_delay = 100; 61 | } 62 | Spreadgun.prototype = new Weapon( ); 63 | Spreadgun.prototype.fire = function() { 64 | this.add_bullet(0); 65 | this.add_bullet(10); 66 | this.add_bullet(-10); 67 | } 68 | 69 | 70 | 71 | 72 | function Cannon(main, ship) { 73 | this.main = main; 74 | this.ship = ship; 75 | this.name = "Cannon"; 76 | this.fire_delay = 333; 77 | this.fire_time = 0; 78 | } 79 | Cannon.prototype = new Weapon( ); 80 | Cannon.prototype.fire = function() { 81 | this.add_bullet(0, 20, 2.5); 82 | } 83 | 84 | 85 | 86 | 87 | function Ring(main, ship) { 88 | this.main = main; 89 | this.ship = ship; 90 | this.name = "Ring"; 91 | this.fire_delay = 1000; 92 | this.fire_time = 0; 93 | } 94 | Ring.prototype = new Weapon( ); 95 | Ring.prototype.fire = function() { 96 | ang = 0 97 | while (ang < 360) { 98 | this.add_bullet(ang); 99 | ang += 5 100 | } 101 | } 102 | 103 | --------------------------------------------------------------------------------