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