diff --git a/app.psgi b/app.psgi index 604c3ef..d2d8da8 100644 --- a/app.psgi +++ b/app.psgi @@ -17,10 +17,19 @@ use if DEBUG, 'Data::Dumper'; my %static_files = ( '/' => {fn => 'index.html', headers => ['Content-Type' => 'text/html; charset=utf-8']}, '/bunny.png' => 0, + '/carrot.png' => 0, + '/dead_bunny.png' => 0, '/circle.png' => 0, + '/turret.png' => 0, + '/turret_gun.png' => 0, + '/gradient.png' => 0, + '/ray.png' => 0, '/js/Character.js' => 0, '/js/Construction.js' => 0, '/js/Particle.js' => 0, + '/js/Enemy.js' => 0, + '/js/Game.js' => 0, + '/js/Dijkstras.js' => 0, ); my %html = ( # 'index._.html' => 'index.html', diff --git a/carrot.png b/carrot.png new file mode 100644 index 0000000..531aba3 Binary files /dev/null and b/carrot.png differ diff --git a/carrot2.png b/carrot2.png new file mode 100644 index 0000000..19297cb Binary files /dev/null and b/carrot2.png differ diff --git a/dead_bunny.png b/dead_bunny.png new file mode 100644 index 0000000..42c5b99 Binary files /dev/null and b/dead_bunny.png differ diff --git a/gradient.png b/gradient.png new file mode 100644 index 0000000..82fe343 Binary files /dev/null and b/gradient.png differ diff --git a/index.html b/index.html index 313820f..a26be78 100644 --- a/index.html +++ b/index.html @@ -1,39 +1,23 @@ - + webq2 - + + + + - + +
+ Grab the carrot! Don't get yourself killed! + Use WSAD to move, 1, 2, 3 to select weapons, mouse to aim and shoot. +
\ No newline at end of file diff --git a/js/Character.js b/js/Character.js index 03acc1c..39c3152 100644 --- a/js/Character.js +++ b/js/Character.js @@ -1,12 +1,19 @@ class Character { constructor() { - this.sprite = new PIXI.Sprite(PIXI.loader.resources.bunny.texture); + const self = this; - this.x = 20; - this.y = app.renderer.height - 50; + this.x = 100; + this.y = app.renderer.height - 75; this.dx = 0; this.dy = 0; this.ox = 0; + + this.dead = false; + + this.init(); + } + + init() { this.hw = 11; this.hh = 18; @@ -15,8 +22,14 @@ class Character { 2: new Weapon('grenade launcher'), 3: new Weapon('raygun'), }; - this.selected_weapon = 1; + Object.values(this.weapons).forEach(w => {w.owner = self}); + this.selected_weapon = 1; + this.target_x = this.x; + this.target_y = this.y; + } + draw() { + this.sprite = new PIXI.Sprite(PIXI.loader.resources.bunny.texture); this.sprite.anchor.x = 0.5; this.sprite.anchor.y = 0.5; @@ -68,6 +81,9 @@ class Character { this.dx = 0; } + this.x += this.dx * dt; + this.y += this.dy * dt; + if (this.stands_on) { if (this.jumps) { this.dy = -600; @@ -99,9 +115,6 @@ class Character { // if (this.dy > this. } - this.x += this.dx * dt; - this.y += this.dy * dt; - let limits_x = []; let limits_y = []; @@ -130,14 +143,14 @@ class Character { } const [intersection_y, edge] = floor.intersects(this); if (intersection_y !== false) { - if (!this.stands_on) { - if (this.dy > 0) { + // if (!this.stands_on) { + if (this.y > this.o_y) { limits_y.push([intersection_y, floor]); } - else if ( floor.type == FLOOR && this.dy < 0 ) { + else if ( floor.type == FLOOR && this.y < this.o_y ) { limits_y.push([intersection_y, floor]); } - } + // } } if (floor.type == FLOOR) { if (edge == 'left') { @@ -182,11 +195,11 @@ class Character { } }); - let limited_y; + let limited_y = undefined; limits_y.forEach(limit => { const [limit_y, limiter] = limit; - // console.log(limit_y, limiter, this.o_by); - if (this.dy > 0) { + // console.log(limit_y, limiter, this.b_y, this.o_by); + if (this.y > this.o_y) { if (limited_y !== undefined && limited_y < limit_y) { // console.log("have better already"); return; @@ -194,7 +207,7 @@ class Character { if ( this.o_by <= limiter.y_at(this.o_x) ) { // console.log("selected!"); limited_y = limit_y; - this.y = limit_y - this.hh - 1; + this.y = limiter.y_at(this.x) - this.hh - 1; this.stands_on = limiter; this.dropped_from = false; } @@ -202,36 +215,44 @@ class Character { // console.log("was not crossed"); } } - else if (this.dy < 0) { + else if (this.y < this.o_y) { if (limited_y !== undefined && limited_y > limit_y) { + // console.log("have better already"); return; } if ( this.o_ty >= limiter.y_at(this.o_x) ) { + // console.log("selected!"); limited_y = limit_y; this.y = limit_y + this.hh + 1; + this.dropped_from = this.stands_on; + this.stands_on = false; + } + else { + // console.log("was not crossed"); } } }); if (limited_y !== undefined) { this.dy = 0; + } if (!this.stands_on) { - if (this.b_y > app.renderer.height) { - this.stands_on = true; - this.dropped_from = false; - // console.log('stand on screen'); - this.y = app.renderer.height - this.hh; - } - else if (this.t_y < 0) { - this.y = this.hh; - this.dy = 0; - } - this.jumps = false; this.drops = false; } + + if (this.b_y > app.renderer.height) { + this.stands_on = true; + this.dropped_from = false; + // console.log('stand on screen'); + this.y = app.renderer.height - this.hh; + } + else if (this.t_y < 0) { + this.y = this.hh; + this.dy = 0; + } if (this.l_x < 0) { this.x = this.hw; } @@ -242,11 +263,58 @@ class Character { this.o_x = this.x; this.o_y = this.y; + this.weapons[this.selected_weapon].x = this.x; + this.weapons[this.selected_weapon].y = this.y; + this.weapons[this.selected_weapon].cool_down(dt); + if (this.firing) { + this.weapons[this.selected_weapon].r = Math.atan2(this.target_y - this.y, this.target_x - this.x); + this.weapons[this.selected_weapon].pull_trigger(); + } + this.sprite.x = this.x; this.sprite.y = this.y; } + sees(who) { + for (let wall of walls) { + if ( wall.crosses_line(this.x, this.y, who.x, who.y) ) { + return false; + } + } + for (let floor of floors) { + if (floor.type != FLOOR) { + continue; + } + if ( floor.crosses_line(this.x, this.y, who.x, who.y) ) { + return false; + } + } + + return true; + } + kill(angle) { + if (this.dead || this.invincible) { + return; + } + this.dead = true; + this.runs_left = false; + this.runs_right = false; + this.jumps = false; + this.drops = false; + this.firing = false; + + // this.sprite.texture = PIXI.loader.resources.dead_bunny.texture; + this.sprite.visible = false; + + add_particle('dead_bunny', this.x, this.y, angle); + + show_death_hint(); + } + handle_keydown(event) { + if (this.dead) { + return; + } switch (event.code) { case 'KeyA': this.runs_left = true; @@ -265,9 +333,19 @@ class Character { case 'Digit3': this.selected_weapon = event.code.substr(5, 1); break; + case 'KeyT': + this.x = this.o_x = this.target_x; + this.y = this.o_y = this.target_y; + break; + case 'KeyI': + this.invincible = true; + break; } } handle_keyup(event) { + if (this.dead) { + return; + } if (event.code == 'KeyA') { this.runs_left = false; } @@ -276,23 +354,81 @@ class Character { } } handle_click(event) { - add_particle( this.weapons[this.selected_weapon].projectile, this.x, this.y, Math.atan2(event.pageY - this.y, event.pageX - this.x) ); + if (this.dead) { + return; + } + this.firing = true; + } + handle_unclick(event) { + if (this.dead) { + return; + } + this.firing = false; + } + handle_mouse(event) { + if (this.dead) { + return; + } + this.target_x = event.pageX; + this.target_y = event.pageY; } } class Weapon { constructor(type) { + this.x = 0; + this.y = 0; + this.r = 0; this.type = type; + this.cool = 0; switch (this.type) { case 'pistol': this.projectile = 'bullet'; + this.cooldown = 0.5; break case 'grenade launcher': this.projectile = 'grenade'; + this.cooldown = 1; break; case 'raygun': this.projectile = 'ray'; + this.cooldown = 2; break; } } + + cool_down(dt) { + this.cool -= dt; + } + pull_trigger() { + if (this.cool <= 0) { + this.fire(); + } + } + fire() { + // console.log('fire', this.x, this.y); + add_particle(this.projectile, this.x, this.y, this.r).owner = this.owner; + this.cool = this.cooldown; + } +} + +class Carrot { + constructor() { + this.x = 1500; + this.y = 75; + + this.draw(); + } + draw() { + this.sprite = new PIXI.Sprite(PIXI.loader.resources.carrot.texture); + this.sprite.anchor.x = 0.5; + this.sprite.anchor.y = 0.5; + this.sprite.x = this.x; + this.sprite.y = this.y; + + app.stage.addChild(this.sprite); + } + + update() { + } } diff --git a/js/Construction.js b/js/Construction.js index bb36bc7..4372cdf 100644 --- a/js/Construction.js +++ b/js/Construction.js @@ -14,30 +14,7 @@ class Construction { this.normal = this.angle - Math.PI * 0.5; } crosses_line(x1, y1, x2, y2) { - const d = (x1 - x2) * (this.y1 - this.y2) - (y1 - y2) * (this.x1 - this.x2); - // If d is zero, there is no intersection - if (d == 0) return false; - - // Get the x and y - const pre = (x1 * y2 - y1 * x2); - const post = (this.x1 * this.y2 - this.y1 * this.x2); - const x = ( pre * (this.x1 - this.x2) - (x1 - x2) * post ) / d; - const y = ( pre * (this.y1 - this.y2) - (y1 - y2) * post ) / d; - // console.log(d, pre, post, x, y); - - // Check if the x and y coordinates are within both lines - if ( x < (Math.min(x1, x2) - 0.5) || x > (Math.max(x1, x2) + 0.5) || - x < (Math.min(this.x1, this.x2) - 0.5) || x > (Math.max(this.x1, this.x2) + 0.5) - ) { - return false; - } - if ( y < (Math.min(y1, y2) - 0.5) || y > (Math.max(y1, y2) + 0.5) || - y < (Math.min(this.y1, this.y2) - 0.5) || y > (Math.max(this.y1, this.y2) + 0.5) - ) { - return false; - } - - return [x, y]; + return lines_intersection(x1, y1, x2, y2, this.x1, this.y1, this.x2, this.y2) } reflected_angle(ray_angle) { return reflected_angle(this.normal, ray_angle); @@ -59,8 +36,6 @@ class Floor extends Construction { } this.type = FLOOR; - - this.draw(); } draw() { @@ -144,11 +119,12 @@ class Wall extends Construction { this.l_x = this.x2; this.r_x = this.x1; } - + } + draw() { this.graphics = new PIXI.Graphics(); this.graphics.lineStyle(LINE_WIDTH, 0xFFFFFF, 1); - this.graphics.moveTo(x1, y1); - this.graphics.lineTo(x2, y2); + this.graphics.moveTo(this.x1, this.y1); + this.graphics.lineTo(this.x2, this.y2); app.stage.addChild(this.graphics); } @@ -187,91 +163,181 @@ function reflected_angle(normal, ray_angle) { const incidence = ray_angle + Math.PI - normal; return normal - incidence; } +function lines_intersection(x1, y1, x2, y2, x3, y3, x4, y4) { + const d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + // If d is zero, there is no intersection + if (d == 0) return false; + + // Get the x and y + const pre = (x1 * y2 - y1 * x2); + const post = (x3 * y4 - y3 * x4); + const x = ( pre * (x3 - x4) - (x1 - x2) * post ) / d; + const y = ( pre * (y3 - y4) - (y1 - y2) * post ) / d; + // console.log(d, pre, post, x, y); + + // Check if the x and y coordinates are within both lines + if ( x < (Math.min(x1, x2) - 1) || x > (Math.max(x1, x2) + 1) || + x < (Math.min(x3, x4) - 1) || x > (Math.max(x4, x4) + 1) + ) { + return false; + } + if ( y < (Math.min(y1, y2) - 1) || y > (Math.max(y1, y2) + 1) || + y < (Math.min(y3, y4) - 1) || y > (Math.max(y3, y4) + 1) + ) { + return false; + } -const slots = { - 'right_wall,left_ceil,right_ceil,left_half,right_half': [ - [0, -150], - [400, -150], [800, -150], [1200, -150], - [-200, -300], [200, -300], [600, -300], [1000, -300], - [0, -450], [400, -450], [800, -450], [1200, -450], - [-200, -600], [200, -600], [600, -600], [1000, -600], - [0, -750], [400, -750], [800, -750], [1200, -750], - ], - // 'right_wall': [ - // [0, -150], - // ], + return [x, y]; +} +function distance_between(x1, y1, x2, y2) { + return Math.sqrt( (y1 - y2) * (y1 - y2) + (x1 - x2) * (x1 - x2) ); +} + +let graph_nodes; +const floors = []; +const walls = []; + +const parts_map = { + // 'normal': ['right_wall','left_ceil','right_ceil','left_half','right_half'], + 'left': ['ceil', 'block'], + 'right': ['wall', 'ceil', 'block'], + 'start': ['wall', 'ceil', 'start'], + 'end': ['end'], }; +const slots = [ + [0, 600, 'start'], [200, 600, 'left' ], [400, 600, 'right'], [600, 600, 'left' ], [800, 600, 'right'], [1000, 600, 'left' ], [1200, 600, 'right'], [1400, 600, 'left' ], + [0, 450, 'left' ], [200, 450, 'right'], [400, 450, 'left' ], [600, 450, 'right'], [800, 450, 'left' ], [1000, 450, 'right'], [1200, 450, 'left' ], [1400, 450, 'right'], + [0, 300, 'right'], [200, 300, 'left' ], [400, 300, 'right'], [600, 300, 'left' ], [800, 300, 'right'], [1000, 300, 'left' ], [1200, 300, 'right'], [1400, 300, 'left' ], + [0, 150, 'left' ], [200, 150, 'right'], [400, 150, 'left' ], [600, 150, 'right'], [800, 150, 'left' ], [1000, 150, 'right'], [1200, 150, 'left' ], [1400, 150, 'right'], + [0, 0, 'right'], [200, 0, 'left' ], [400, 0, 'right'], [600, 0, 'left' ], [800, 0, 'right'], [1000, 0, 'left' ], [1200, 0, 'right'], [1400, 0, 'end' ], +]; const blocks = { - 'left_wall': [ - 'w0,1,0,100', - 'w0,1,0,150 w1,1,1,150 f0,150,1,150', + 'wall': [ + // 'w200,1,200,100 cr', + 'w200,1,200,100 cr', + 'w200,1,200,150', ], - 'right_wall': [ - 'w400,1,400,100', - 'w400,1,400,100', - 'w400,1,400,149', - ], - 'left_ceil': [ - 'f0,0,74,0 s74,0,126,0 f126,0,200,0', - 'f0,0,74,0 f126,0,200,0', + 'ceil': [ + 'f0,0,74,0 s74,0,126,0 f126,0,200,0 cu', + 'f0,0,74,0 f126,0,200,0 cu', 'f0,0,200,0', ], - 'right_ceil': [ - 'f200,0,274,0 s274,0,325,0 f325,0,400,0', - 'f200,0,274,0 f326,0,400,0', - 'f200,0,400,0', - ], - 'half_block': [ - 's0,75,200,75', // 1 - 's25,120,125,50 s125,50,199,50', // 2 - 's1,50,75,50 s75,50,175,120', // 3 - 's0,75,73,75 w74,1,74,75 f125,75,199,75', // 4 - 'w25,75,25,150 s26,75,74,75 w75,75,75,150 w126,1,126,100 s127,100,174,100 w175,1,175,100', // 5 - 'f1,100,50,100 f75,50,125,50 f150,100,199,100', // 6 - 'f1,50,50,50 f75,100,125,100 f150,50,199,50', // 7 - 'f1,75,199,75', // 8 - 's1,50,199,50 s1,100,199,100', // 9 - 'w50,50,50,100 s51,50,99,50 w100,50,100,100 f100,50,100,50 s101,100,149,100 w150,50,150,100', // 10 - 'w25,50,25,100 s26,50,74,50 w75,50,75,100 s76,100,124,100 w125,50,125,100 s126,50,174,50 w175,50,175,100', // 11 + 'block': [ + 's1,75,199,75 t25,25', // 1 + 's25,120,125,50 s125,50,199,50 t175,125', // 2 + 's1,50,75,50 s75,50,175,120 t25,125', // 3 + 's0,75,73,75 w74,1,74,75 f125,75,199,75 t75,125', // 4 + 'w25,75,25,150 s26,75,73,75 w75,75,75,150 w126,1,126,100 s127,100,174,100 w175,1,175,100 t50,25', // 5 + 'f1,100,50,100 f75,50,125,50 f150,100,199,100 t125,75', // 6 + 'f25,50,75,50 f75,100,125,100 f125,50,175,50 t75,25', // 7 + 'f26,75,174,75 t75,125', // 8 + 's1,50,199,50 s1,100,199,100 t100,75', // 9 + 'w50,50,50,100 s51,50,99,50 w100,50,100,100 f100,50,100,50 s101,100,149,100 w150,50,150,100 t75,125', // 10 + 'w25,50,25,100 s26,50,74,50 w75,50,75,100 s76,100,124,100 w125,50,125,100 s126,50,174,50 w175,50,175,100 t150,125', // 11 + 's1,51,50,100 s1,149,50,100 f50,100,100,100 s100,100,150,50 s101,1,150,50 f150,50,199,50 t175,75', // 12 + 'f1,50,50,50 s50,50,99,1 s50,50,100,100 s51,149,100,100 f100,100,150,100 s150,100,199,51 t75,100', // 13 + 's1,99,50,50 f50,50,150,50 w125,1,125,49 t125,75', // 14 + 's0,50,50,75 f50,75,150,75 w75,1,75,74 s150,75,200,50 t100,50', // 15 + 's1,100,49,100 w50,50,50,149 s51,75,100,75 s100,125,149,125 w150,50,150,149 s151,100,199,100 t100,75', // 16 + 's1,100,199,100 w75,50,75,99 w125,50,125,99 t100,125', // 17 + 'f25,25,50,25 f100,25,125,25 f150,50,175,50 f50,75,75,75 f25,125,50,125 f126,125,150,125 t100,50', // 18 + 'f150,25,175,25 f25,50,50,50 f75,75,100,75 f175,75,199,75 f125,100,150,100 f25,125,50,125 t125,75', // 19 + 'f26,25,49,25 f26,50,49,50 w25,26,25,49 w50,26,50,49 f76,75,99,75 f76,100,99,100 w75,76,75,99 w100,76,100,99 f151,75,174,75 f151,100,174,100 w150,76,150,99 w175,75,175,99 f26,125,49,125 w25,126,25,149 w50,126,50,149 t175,50', // 20 + 'f2,25,24,25 f2,50,24,50 w1,25,1,50 w25,25,25,50 f76,25,99,25 f76,50,99,50 w75,25,75,50 w100,25,100,50 f151,25,174,25 f151,50,174,50 w150,25,150,50,50 w175,25,175,50 f76,100,99,100 s25,50,75,100 s100,100,150,50 t125,75', // 21 + 'f25,50,75,50 f125,50,175,50 f25,100,75,100 f125,100,175,100 t25,75', // 22* + // '', // 23 ], + 'start': ['w75,50,75,100 w125,50,125,100 f76,50,124,50 s50,100,150,100'], + 'end': ['w75,50,75,100 w125,50,125,100 f76,50,124,50 s76,100,124,100'], }; + function generate_level () { - Object.keys(slots).forEach(names => { - const parts = names.split(','); - slots[names].forEach(slot => { - parts.forEach(part => { - let name = part; - if (part == 'left_half' || part == 'right_half') { - name = 'half_block'; - } - let o_x = slot[0]; - let o_y = app.renderer.height + slot[1]; - if (part == 'right_half') { - o_x += 200; + walls.length = 0; + floors.length = 0; + enemies.length = 0; + graph_nodes = new Graph(); + + slots.forEach(slot => { + const parts = parts_map[ slot[2] ]; + parts.forEach(part => { + let name = part; + if (part == 'left_half' || part == 'right_half') { + name = 'block'; + } + let o_x = slot[0]; + let o_y = slot[1]; + if (part == 'right_half') { + o_x += 200; + } + + const node = o_x + ';' + o_y; + if (!graph_nodes.vertices[node]) { + graph_nodes.vertices[node] = {}; + } + if (slot[2] == 'right') { + const left_node = (o_x - 200) + ';' + o_y; + if (!graph_nodes.vertices[left_node]) { + graph_nodes.vertices[left_node] = {}; } + graph_nodes.vertices[node][left_node] = 1; + graph_nodes.vertices[left_node][node] = 1; + } + + const block_idx = Math.random() * blocks[name].length; + const block = blocks[name][Math.floor(block_idx)]; + const elements = typeof(block) == 'array' ? block : block.split(' '); - const block_idx = Math.random() * blocks[name].length; - const block = blocks[name][Math.floor(block_idx)]; - const elements = typeof(block) == 'array' ? block : block.split(' '); - - // console.log(slot, block_idx, elements); - elements.forEach(element => { - const t = element.substr(0, 1); - const [x1, y1, x2, y2] = element.substr(1).split(',').map(n => {return parseInt(n)}); - // console.log(t, slot[0], x1, slot[0] + x1); - switch (t) { - case 'w': - walls.push( new Wall(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); - break; - case 'f': - floors.push( new Floor(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); - break; - case 's': - floors.push( new Stair(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); - break; - } - }); + // console.log(slot, block_idx, elements); + elements.forEach(element => { + const t = element.substr(0, 1); + const [x1, y1, x2, y2] = element.substr(1).split(',').map(n => {return parseInt(n)}); + // console.log(t, slot[0], x1, slot[0] + x1); + switch (t) { + case 'w': + walls.push( new Wall(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); + break; + case 'f': + floors.push( new Floor(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); + break; + case 's': + floors.push( new Stair(o_x + x1, o_y + y1, o_x + x2, o_y + y2) ); + break; + case 't': + enemies.push( new Enemy('turret', o_x + x1, o_y + y1) ); + break; + case 'c': + const direction = element.substr(1); + let neighbour_node; + switch (direction) { + case 'u': + neighbour_node = (o_x) + ';' + (o_y - 150); + break; + case 'r': + neighbour_node = (o_x + 200) + ';' + o_y; + break; + } + if (!graph_nodes.vertices[neighbour_node]) { + graph_nodes.vertices[neighbour_node] = {}; + } + graph_nodes.vertices[node][neighbour_node] = 1; + graph_nodes.vertices[neighbour_node][node] = 1; + break; + } }); }); }); + + console.log(graph_nodes); +} + +function draw_level() { + for (let floor of floors) { + floor.draw(); + } + for (let wall of walls) { + wall.draw(); + } + for (let enemy of enemies) { + enemy.draw(); + } } diff --git a/js/Dijkstras.js b/js/Dijkstras.js new file mode 100644 index 0000000..42882ce --- /dev/null +++ b/js/Dijkstras.js @@ -0,0 +1,158 @@ +// taken from: https://github.com/mburst/dijkstras-algorithm and modified to work 2-100 times faster + +/** + * Basic priority queue implementation. If a better priority queue is wanted/needed, + * this code works with the implementation in google's closure library (https://code.google.com/p/closure-library/). + * Use goog.require('goog.structs.PriorityQueue'); and new goog.structs.PriorityQueue() + */ +function PriorityQueue () { + this._nodes = []; + + this.enqueue = function (priority, key) { + this._nodes.push({key: key, priority: priority }); + }; + this.dequeue = function () { + return this._nodes.shift().key; + }; + this.sort = function () { + this._nodes.sort(function (a, b) { + return a.priority - b.priority; + }); + }; + this.isEmpty = function () { + return !this._nodes.length; + }; +} + +/** + * Pathfinding starts here + */ +function Graph(){ + var INFINITY = 1/0; + this.vertices = {}; + + this.addVertex = function(name, edges){ + this.vertices[name] = edges; + }; + + // this.shortestPath = function (start, finish, max) { + this.isConnected = function (start, finish, max) { + var nodes = new PriorityQueue(), + distances = {}, + previous = {}, + smallest, vertex, neighbor, alt; + + var now = window.performance.now(); + for(vertex in this.vertices) { + if(vertex === start) { + distances[vertex] = 0; + nodes.enqueue(0, vertex); + } + else { + distances[vertex] = INFINITY; + nodes.enqueue(INFINITY, vertex); + } + + // previous[vertex] = null; + } + nodes.sort(); + // console.log('filling queue took', window.performance.now() - now, nodes._nodes, distances); + var now = window.performance.now(); + + + while(!nodes.isEmpty()) { + smallest = nodes.dequeue(); + + if (max != undefined && distances[smallest] > max) { + break; + } + + if(!smallest || distances[smallest] === INFINITY){ + continue; + } + + if (smallest == finish) { + return true; + var step = smallest; + var path = []; + + while(previous[step]) { + path.push(step); + step = previous[step]; + } + return path; + } + + for(neighbor in this.vertices[smallest]) { + alt = distances[smallest] + this.vertices[smallest][neighbor]; + + if(alt < distances[neighbor]) { + distances[neighbor] = alt; + previous[neighbor] = smallest; + + nodes.enqueue(alt, neighbor); + } + } + nodes.sort(); + } + + // console.log('pathfinding took', window.performance.now() - now, previous); + + return false; + }; + + this.nodesInRange = function(start, range) { + var nodes = new PriorityQueue(), + distances = {}, + in_range_map = {}, + smallest, vertex, neighbor, alt; + + // var now = window.performance.now(); + for(vertex in this.vertices) { + if(vertex === start) { + distances[vertex] = 0; + nodes.enqueue(0, vertex); + } + else { + distances[vertex] = INFINITY; + nodes.enqueue(INFINITY, vertex); + } + + // previous[vertex] = null; + } + nodes.sort(); + // console.log('filling queue took', window.performance.now() - now); + // var now = window.performance.now(); + + + while(!nodes.isEmpty()) { + smallest = nodes.dequeue(); + + if (range != undefined && distances[smallest] > range) { + break; + } + + if (!smallest || distances[smallest] === INFINITY){ + continue; + } + + in_range_map[smallest] = true; + + for (neighbor in this.vertices[smallest]) { + alt = distances[smallest] + this.vertices[smallest][neighbor]; + + if (alt < distances[neighbor]) { + distances[neighbor] = alt; + + nodes.enqueue(alt, neighbor); + } + } + nodes.sort(); + } + + // console.log('pathfinding took', window.performance.now() - now, start, finishes, paths); + + return in_range_map; + } +} + diff --git a/js/Enemy.js b/js/Enemy.js new file mode 100644 index 0000000..d4c47e5 --- /dev/null +++ b/js/Enemy.js @@ -0,0 +1,90 @@ +class Enemy extends Character { + constructor(type, x, y) { + super(); + this.type = type; + this.x = x; + this.y = y; + } + init() { + this.hw = 10; + this.hh = 10; + + this.r = (Math.random() - 0.5) * 2 * Math.PI; + this.desired_r = this.r; + this.turn_speed = 0.5 * level; + this.aggressive = false; + + this.weapon = new Weapon('pistol'); + this.weapon.x = this.x; + this.weapon.y = this.y; + this.weapon.owner = this; + } + draw() { + this.container = new PIXI.Container(); + app.stage.addChild(this.container); + + this.turret_graphics = new PIXI.Sprite(PIXI.loader.resources.turret.texture); + this.turret_graphics.anchor.set(0.5, 0.5); + this.container.addChild(this.turret_graphics); + + this.gun_graphics = new PIXI.Sprite(PIXI.loader.resources.turret_gun.texture); + this.gun_graphics.anchor.set(0.2, 0.5); + this.container.addChild(this.gun_graphics); + } + + update(dt) { + this.aggressive = false; + + if (!this.dead) { + if ( this.sees(bunny) && !bunny.dead ) { + this.aggressive = true; + if (this.r > Math.PI) { + this.r -= 2 * Math.PI; + } + if (this.r < -Math.PI) { + this.r += 2 * Math.PI; + } + + this.desired_r = Math.atan2(bunny.y - this.y, bunny.x - this.x); + } + + if ( Math.abs(this.desired_r - this.r) < this.turn_speed * dt ) { + this.r = this.desired_r; + } + else { + if (Math.abs(this.desired_r - this.r) > Math.PI) { + this.desired_r -= 2 * Math.PI * Math.sign(this.desired_r); + } + // console.log(desired_r, this.r, Math.abs(this.r - desired_r), this.turn_speed * dt); + this.r += this.turn_speed * dt * Math.sign(this.desired_r - this.r); + } + + this.weapon.x = this.x; + this.weapon.y = this.y; + this.weapon.r = this.r; + if (Math.abs(this.desired_r - this.r) < 0.1 && this.aggressive) { + // console.log(this.x, this.y); + this.weapon.pull_trigger(); + } + + this.weapon.cool_down(dt); + } + + this.gun_graphics.rotation = this.r; + this.container.x = this.x; + this.container.y = this.y; + } + + kill(angle) { + if (this.dead) { + return; + } + this.dead = true; + this.container.visible = false; + + add_particle('dead_turret', this.x, this.y, angle); + add_particle('dead_turret_gun', this.x, this.y, angle); + + // this.sprite.texture = PIXI.loader.resources.dead_bunny.texture; + } +} \ No newline at end of file diff --git a/js/Game.js b/js/Game.js new file mode 100644 index 0000000..9b7bdbe --- /dev/null +++ b/js/Game.js @@ -0,0 +1,100 @@ +let level = 0; +let bunny; +let carrot; +let hint_text; +let hint_text_ttl = 1.5; +let win = false; +let win_timer; +const enemies = []; +const particles = []; + +function start_level() { + level++; + win_timer = 3; + + particles.length = 0; + + do { + generate_level(); + // console.log( graph_nodes.shortestPath('0;600', '1400;0') ); + } while ( !graph_nodes.isConnected('0;600', '1400;0') ) + + app.stage.removeChildren(); + bunny = new Character(); + bunny.draw(); + carrot = new Carrot(); + carrot.draw(); + draw_level(); + init_particle_containers(); + + hint_text = new PIXI.Text( + 'Level ' + level + '! Grab the carrot!', + new PIXI.TextStyle({ + fontFamily: 'Impact', + fontSize: 100, + fill: ['#dddddd', '#ffffff', '#dddddd'], // gradient + stroke: '#111111', + strokeThickness: 5, + }) + ); + hint_text.x = app.renderer.width * 0.5; + hint_text.y = app.renderer.height * 0.5; + hint_text.anchor.set(0.5, 0.5); + app.stage.addChild(hint_text); + hint_text_ttl = 2; +} + +function update_game(dt) { + if (bunny.l_x < carrot.x && bunny.r_x > carrot.x && bunny.t_y < carrot.y && bunny.b_y > carrot.y) { + win = true; + show_win_hint(); + win_timer -= dt; + if (win_timer < 0) { + start_level(); + } + } + else { + bunny.update(dt); + carrot.update(dt); + enemies.forEach( char => { + char.update(dt); + }); + particles.forEach(particle => { + if (particle === undefined) { + return; + } + particle.update(dt); + }); + } + + update_hint(dt); +} + +function show_death_hint() { + hint_text_ttl = Infinity; + hint_text.alpha = 1; + hint_text.visible = true; + hint_text.style.fill = ['#dd0000', '#ff0000', '#dd0000']; + hint_text.text = 'You died!'; +} +function show_win_hint() { + hint_text_ttl = Infinity; + hint_text.alpha = 1; + hint_text.visible = true; + hint_text.style.fill = ['#ff5500', '#ff712b', '#ff5500']; + hint_text.text = 'Yummy!!'; +} + +function update_hint(dt) { + hint_text_ttl -= dt; + if (hint_text_ttl < 0.1 && hint_text_ttl > 0) { + hint_text.alpha = hint_text_ttl * 10; + } + else if (hint_text_ttl < 0) { + hint_text.visible = false; + } + else { + hint_text.visible = true; + hint_text.alpha = 1; + } +} diff --git a/js/Particle.js b/js/Particle.js index 95ad73f..f13e464 100644 --- a/js/Particle.js +++ b/js/Particle.js @@ -1,8 +1,8 @@ class Particle { - constructor(id, type, x, y, r) { + constructor(id, type, x, y, direction) { this.id = id; this.type = type; - this.r = r; + this.direction = direction; this.x = x; this.y = y; this.ox = x; @@ -11,35 +11,40 @@ class Particle { switch (type) { case 'bullet': this.speed = 1000; - this.dx = Math.cos(this.r) * this.speed; - this.dy = Math.sin(this.r) * this.speed; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; this.ttl = 2; this.breaks_by_walls = true; + this.is_projectile = true; + this.one_hit = true; + this.r = this.direction; break; case 'spark': this.speed = 300; - this.dx = Math.cos(this.r) * this.speed; - this.dy = Math.sin(this.r) * this.speed; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; this.ttl = 1; this.gravity = 2000; break; case 'grenade': this.ttl = 3; this.speed = 800; - this.dx = Math.cos(this.r) * this.speed; - this.dy = Math.sin(this.r) * this.speed; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; this.gravity = 2000; this.breakes_when_expires = true; this.bounces = true; + this.is_projectile = true; break; case 'frag': - this.ttl = 0.2 + Math.random() * 0.1; + this.ttl = 0.08 + Math.random() * 0.02; this.speed = 600; - this.dx = Math.cos(this.r) * this.speed; - this.dy = Math.sin(this.r) * this.speed; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; this.gravity = 2000; - this.spawns = ['fire', 'smoke']; + this.spawn_interval = {'fire': 0.003, 'smoke': 0.003}; this.spawn_time = {}; + this.bounces = true; break; case 'fire': this.ttl = 1 - Math.random() * 0.2; @@ -52,9 +57,11 @@ class Particle { this.dx = (Math.random() - 0.5) * 50; break; case 'ray': - this.ttl = 1; - this.ex = this.x + Math.cos(this.r) * app.renderer.width * 2; - this.ey = this.y + Math.sin(this.r) * app.renderer.width * 2; + this.ttl = 2; + this.ex = this.x + Math.cos(this.direction) * app.renderer.width * 2; + this.ey = this.y + Math.sin(this.direction) * app.renderer.width * 2; + this.r = this.direction; + this.is_projectile = true; let hit_what; let hit; for (let wall of walls) { @@ -66,21 +73,70 @@ class Particle { } } for (let floor of floors) { + if (floor.type != FLOOR) { + continue; + } let crossed = floor.crosses_line(this.x, this.y, this.ex, this.ey) ; // console.log(crossed); if (crossed) { - if (floor.type != FLOOR && crossed[1] < this.y) { - continue; - } [this.ex, this.ey] = crossed; hit = crossed; hit_what = floor; } } if (hit) { - this.reflected_at = [hit[0] - Math.cos(this.r) * 2, hit[1] - Math.sin(this.r) * 2]; + this.reflected_at = [hit[0] - Math.cos(this.direction) * 2, hit[1] - Math.sin(this.direction) * 2]; this.reflected_from = hit_what; } + + this.length = Math.sqrt( Math.pow( (this.ex - this.x), 2) + Math.pow( (this.ey - this.y), 2) ); + break; + case 'dead_bunny': + this.speed = 400; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; + this.gravity = 2000; + this.bounces = true; + if (this.direction < Math.PI * 0.5 && this.direction > -Math.PI * 0.5) { + this.dr = 10; + } + else { + this.dr = -10; + } + this.r = 0; + break; + case 'dead_turret': + this.speed = 400; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; + this.gravity = 2000; + this.bounces = true; + if (this.direction < Math.PI * 0.5 && this.direction > -Math.PI * 0.5) { + this.dr = 10; + } + else { + this.dr = -10; + } + this.r = 0; + this.ttl = 3; + break; + case 'dead_turret_gun': + this.direction += Math.random() - 0.5; + this.speed = 600; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; + this.gravity = 2000; + this.bounces = true; + this.spawn_interval = {'fire': 0.003, 'smoke': 0.003}; + this.spawn_time = {}; + if (this.direction < Math.PI * 0.5 && this.direction > -Math.PI * 0.5) { + this.dr = 10; + } + else { + this.dr = -10; + } + this.r = 0; + this.ttl = 3; break; } @@ -90,44 +146,65 @@ class Particle { draw() { switch (this.type) { case 'bullet': - this.graphics = new PIXI.Graphics(); + this.graphics = new PIXI.Sprite(PIXI.loader.resources.gradient.texture); + this.graphics.anchor.set(0.5, 0.5); this.graphics.blendMode = PIXI.BLEND_MODES.ADD; - this.graphics.lineStyle(2, 0xFFAA00, 1); - this.graphics.moveTo(-8, 0); - this.graphics.lineTo(8, 0); - this.graphics.rotation = this.r; + this.graphics.tint = 0xFFAA00; break; case 'spark': this.graphics = new PIXI.Sprite(PIXI.loader.resources.circle.texture); + this.graphics.anchor.set(0.5, 0.5); this.graphics.tint = 0xFFFF00; this.graphics.scale.set(0.03, 0.03); this.graphics.blendMode = PIXI.BLEND_MODES.ADD; break; case 'grenade': this.graphics = new PIXI.Sprite(PIXI.loader.resources.circle.texture); + this.graphics.anchor.set(0.5, 0.5); this.graphics.tint = 0x00AA00; this.graphics.scale.set(0.06, 0.06); break; case 'ray': - this.graphics = new PIXI.Graphics(); + this.graphics = new PIXI.Sprite(PIXI.loader.resources.ray.texture); this.graphics.blendMode = PIXI.BLEND_MODES.ADD; - this.graphics.lineStyle(3, 0xFF00FF, 1); - this.graphics.moveTo(0, 0); - this.graphics.lineTo(this.ex - this.x, this.ey - this.y); - break; - case 'frag': + this.graphics.tint = 0xFF00FF; + this.graphics.anchor.set(0, 0.5); + this.graphics.width = this.length; break; case 'fire': this.graphics = new PIXI.Sprite(PIXI.loader.resources.circle.texture); - this.graphics.tint = 0xFFFFFF; this.graphics.blendMode = PIXI.BLEND_MODES.ADD; + this.graphics.anchor.set(0.5, 0.5); + this.graphics.tint = 0xFFFFFF; this.graphics.scale.set(0.12, 0.12); break; case 'smoke': this.graphics = new PIXI.Sprite(PIXI.loader.resources.circle.texture); + this.graphics.anchor.set(0.5, 0.5); this.graphics.tint = 0x999999; this.graphics.scale.set(0.4, 0.4); break; + case 'frag': + break; + // case 'explosion': + // this.graphics = new PIXI.Sprite(PIXI.loader.resources.circle.texture); + // this.graphics.anchor.set(0.5, 0.5); + // this.graphics.tint = 0xFFFF00; + // this.graphics.blendMode = PIXI.BLEND_MODES.ADD; + // this.graphics.scale.set(1, 1); + break; + case 'dead_bunny': + this.graphics = new PIXI.Sprite(PIXI.loader.resources.dead_bunny.texture); + this.graphics.anchor.set(0.5, 0.5); + break; + case 'dead_turret': + this.graphics = new PIXI.Sprite(PIXI.loader.resources.turret.texture); + this.graphics.anchor.set(0.5, 0.5); + break; + case 'dead_turret_gun': + this.graphics = new PIXI.Sprite(PIXI.loader.resources.turret_gun.texture); + this.graphics.anchor.set(0.2, 0.5); + break; default: this.graphics = new PIXI.Text('?', new PIXI.TextStyle({fill: '#ffffff'})); break; @@ -148,6 +225,15 @@ class Particle { if (this.dy !== undefined) { this.y += this.dy * dt; } + if (this.dr !== undefined) { + this.r += this.dr * dt; + if (this.r > Math.PI) { + this.r -= 2 * Math.PI; + } + else if (this.r < -Math.PI) { + this.r += 2 * Math.PI; + } + } if (this.ttl !== undefined) { this.ttl -= dt; @@ -162,10 +248,13 @@ class Particle { } } - if (this.breaks_by_walls || this.bounces) { + if (this.breaks_by_walls || this.bounces || this.splats) { let hit; let hit_what; for (let wall of walls) { + if (this.splats) { + break; + } let crossed = wall.crosses_line(this.ox, this.oy, this.x, this.y) if (crossed) { // console.log(wall, crossed); @@ -176,6 +265,9 @@ class Particle { } if (!hit) { for (let floor of floors) { + if (this.splats && this.dy <= 0) { + break; + } if (floor.type != FLOOR) { if (this.breaks_by_walls) { continue; @@ -202,47 +294,56 @@ class Particle { else if (this.bounces) { this.bounce(hit_what); } + else if (this.splats) { + this.splat(); + } } } - if (this.bounces) { - if (this.x < 0) { - this.bounce('left'); - } - else if (this.x > app.renderer.width) { - this.bounce('right'); - } - else if (this.y < 0) { - this.bounce('top'); - } - else if (this.y > app.renderer.height) { + if (this.x < 0 && this.bounces) { + this.bounce('left'); + } + else if (this.x > app.renderer.width && this.bounces) { + this.bounce('right'); + } + else if (this.y < 0 && this.bounces) { + this.bounce('top'); + } + else if (this.y > app.renderer.height) { + if (this.bounces) { this.bounce('bottom'); } + else if (this.splats) { + this.splat(); + } } - if (this.type == 'ray' && this.reflected_from && this.ttl > 0.2) { + if (this.type == 'ray' && this.reflected_from && this.ttl > 1) { add_particle( - this.type, this.reflected_at[0], this.reflected_at[1], this.reflected_from.reflected_angle(this.r) - ).ttl = this.ttl - 0.2; + this.type, this.reflected_at[0], this.reflected_at[1], this.reflected_from.reflected_angle(this.direction) + ).ttl = this.ttl - 0.4; this.reflected_from = undefined; this.reflected_at = undefined; } - if (this.spawns) { - for (let spawn of this.spawns) { + if (this.spawn_interval) { + for ( let spawn of Object.keys(this.spawn_interval) ) { if (this.spawn_time[spawn] === undefined) { this.spawn_time[spawn] = 0; } this.spawn_time[spawn] += dt; - if (this.spawn_time[spawn] > 0.005) { - add_particle(spawn, this.x, this.y, 0); - this.spawn_time[spawn] = 0; + let x = this.x; + let y = this.y; + const step_x = (this.x - this.ox) * this.spawn_interval[spawn] / this.spawn_time[spawn]; + const step_y = (this.y - this.oy) * this.spawn_interval[spawn] / this.spawn_time[spawn]; + while (this.spawn_time[spawn] > this.spawn_interval[spawn]) { + add_particle(spawn, x, y, 0); + this.spawn_time[spawn] -= this.spawn_interval[spawn]; + x -= step_x; + y -= step_y; } } } - this.ox = this.x; - this.oy = this.y; - switch (this.type) { case 'spark': case 'ray': { @@ -268,11 +369,64 @@ class Particle { } this.graphics.tint = ( (0xFF * red) << 16 ) + ( (0xFF * green) << 8 ); } break; + case 'dead_turret': + case 'dead_turret_gun': + if (this.ttl < 1) { + this.graphics.alpha = this.ttl; + } + break; } + if (this.is_projectile) { + if (this.owner instanceof Enemy) { + if ( !bunny.dead && this.hits(bunny) ) { + this.strike_target(bunny, this.direction); + } + } + else { + for (let enemy of enemies) { + if (enemy.dead) { + continue; + } + if ( this.hits(enemy) ) { + if (this.type == 'grenade') { + this.break(); + return; + } + else { + this.strike_target(enemy, this.direction); + } + if (this.one_hit) { + this.destroy(); + return; + } + } + } + } + } + if (this.type == 'explosion') { + for (let char of enemies.concat([bunny])) { + if (char.dead) { + continue; + } + if ( distance_between(this.x, this.y, char.x, char.y) < 50 && char.sees(this) ) { + this.strike_target( char, Math.atan2(char.y - this.y, char.x - this.x) ); + } + } + this.destroy(); + return; + } + + this.ox = this.x; + this.oy = this.y; + if (this.graphics) { this.graphics.x = this.x; this.graphics.y = this.y; + if (this.r !== undefined) { + this.graphics.rotation = this.r; + } + this.graphics.visible = true; } // console.log(this); @@ -281,34 +435,84 @@ class Particle { bounce(what) { const current_angle = Math.atan2(this.dy, this.dx); if (typeof(what) == 'object') { - this.r = what.reflected_angle(current_angle); + this.direction = what.reflected_angle(current_angle); } else if (what == 'bottom' || what == 'top') { - this.r = reflected_angle(Math.PI * 0.5, current_angle); + this.direction = reflected_angle(Math.PI * 0.5, current_angle); } else if (what == 'right' || what == 'left') { - this.r = reflected_angle(0, current_angle); + this.direction = reflected_angle(0, current_angle); } - // console.log(current_angle *180/Math.PI, this.r *180/Math.PI, hit_what.normal *180/Math.PI); + // console.log(current_angle *180/Math.PI, this.direction *180/Math.PI, hit_what.normal *180/Math.PI); this.speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy) * 0.8; - this.dx = Math.cos(this.r) * this.speed; - this.dy = Math.sin(this.r) * this.speed; + this.dx = Math.cos(this.direction) * this.speed; + this.dy = Math.sin(this.direction) * this.speed; + if (this.dr !== undefined) { + this.dr *= 0.8; + + if (Math.abs(this.dr) < 1) { + this.dr = 0; + } + if ( (this.dx < 0 && this.dr > 0) || (this.dx > 0 && this.dr < 0) ) { + this.dr *= -1; + } + } this.x = this.ox; this.y = this.oy; } - + splat() { + console.log("splat"); + this.splats = false; + if (this.dx > 0) { + this.r = Math.PI * 0.5; + } + else { + this.r = -Math.PI * 0.5; + } + this.dr = 0; + this.dx = 0; + this.dy = 0; + this.gravity = 0; + } + hits(target) { + let x1, x2, y1, y2; + x1 = this.x; + y1 = this.y; + if (this.type == 'ray') { + x2 = this.ex; + y2 = this.ey; + } + else { + x2 = this.ox; + y2 = this.oy; + } + // console.log(x1, y1, x2, y2, target.l_x, target.t_y, target.r_x, target.b_y); + if ( + lines_intersection(x1, y1, x2, y2, target.l_x, target.t_y, target.l_x, target.b_y) || + lines_intersection(x1, y1, x2, y2, target.r_x, target.t_y, target.r_x, target.b_y) || + lines_intersection(x1, y1, x2, y2, target.l_x, target.t_y, target.r_x, target.t_y) || + lines_intersection(x1, y1, x2, y2, target.l_x, target.b_y, target.r_x, target.b_y) + ) { + return true; + } + } + strike_target(target, angle) { + target.kill(angle); + } break(source) { switch (this.type) { case 'bullet': - const angle = source ? source.reflected_angle(this.r) : this.r; + const angle = source ? source.reflected_angle(this.direction) : this.direction; for (let i = 0; i < Math.random() * 3 + 3; i++) { add_particle('spark', this.x, this.y, angle + Math.random() * 1 - 0.5); } break; case 'grenade': - for (let i = 0; i < Math.random() * 9 + 15; i++) { - add_particle('frag', this.x, this.y, -Math.random() * Math.PI); + const frag_num = 35; + for (let i = 0; i < frag_num; i++) { + add_particle('frag', this.x, this.y - 1, 2 * Math.PI * i / frag_num + Math.random() * 0.1); } + add_particle('explosion', this.x, this.y - 1); break; } this.destroy(); @@ -318,20 +522,51 @@ class Particle { } } +const containers = {}; +const container_blend_modes = {'fire': PIXI.BLEND_MODES.ADD}; +function init_particle_containers() { + for (let contained_type of []) { + containers[contained_type] = new PIXI.particles.ParticleContainer(10000, { + // scale: true, + // position: true, + // rotation: true, + // alpha: true, + // uvs: true, + scale: true, + position: true, + rotation: true, + uvs: true, + alpha: true, + tint: true, + }) + if (container_blend_modes[contained_type] !== undefined) { + containers[contained_type].blendMode = container_blend_modes[contained_type]; + } + app.stage.addChild( containers[contained_type] ); + } +} let free_particle_ids = []; function add_particle(type, x, y, r) { const new_id = free_particle_ids.length ? free_particle_ids.shift() : particles.length; particles[new_id] = new Particle(new_id, type, x, y, r); + let container = app.stage; + if (containers[type]) { + container = containers[type]; + } if (particles[new_id].graphics) { - particle_container.addChild(particles[new_id].graphics); + container.addChild(particles[new_id].graphics); } - // console.log("particle", new_id, particles[new_id]); + // console.log(type, new_id, particles[new_id]); return particles[new_id]; } function remove_particle(id) { free_particle_ids.push(id); if (particles[id].graphics) { - particle_container.removeChild(particles[id].graphics); + let container = app.stage; + if (containers[particles[id].type]) { + container = containers[particles[id].type]; + } + container.removeChild(particles[id].graphics); } particles[id] = undefined; } diff --git a/ray.png b/ray.png new file mode 100644 index 0000000..704c673 Binary files /dev/null and b/ray.png differ diff --git a/turret.png b/turret.png new file mode 100644 index 0000000..d2841a1 Binary files /dev/null and b/turret.png differ diff --git a/turret_gun.png b/turret_gun.png new file mode 100644 index 0000000..a107ec7 Binary files /dev/null and b/turret_gun.png differ