Pabloader 2 年之前
當前提交
1db470c7bc
共有 9 個文件被更改,包括 360 次插入0 次删除
  1. 41
    0
      bullet.js
  2. 45
    0
      cloud.js
  3. 36
    0
      entity.js
  4. 93
    0
      game.js
  5. 17
    0
      index.html
  6. 36
    0
      shooting_pad.js
  7. 81
    0
      style.css
  8. 10
    0
      todo.md
  9. 1
    0
      utils.js

+ 41
- 0
bullet.js 查看文件

@@ -0,0 +1,41 @@
1
+class Bullet extends Entity {
2
+    constructor(x, y) {
3
+        super(x, y, 0.001, 0.02);
4
+        this.element.className = 'bullet';
5
+    }
6
+
7
+    update() {
8
+        if (this.alive) {
9
+            this.y -= 0.01;
10
+            if (this.y < -0.1) {
11
+                this.destroy();
12
+            }
13
+
14
+            for (let cloud of clouds) {
15
+                if (this.isOverlapped(cloud)) {
16
+                    cloud.destroy();
17
+                    this.destroy();
18
+                }
19
+            }
20
+        }
21
+    }
22
+}
23
+
24
+class RainDrop extends Bullet {
25
+    constructor(x, y) {
26
+        super(x, y);
27
+        this.element.className = 'raindrop';
28
+    }
29
+
30
+    update() {
31
+        if (this.alive) {
32
+            this.y += 0.01;
33
+            if (this.y > 1.1) {
34
+                this.destroy();
35
+            } else if (this.isOverlapped(shootingPad)) {
36
+                gameOver('Game over');
37
+                this.destroy();
38
+            }
39
+        }
40
+    }
41
+}

+ 45
- 0
cloud.js 查看文件

@@ -0,0 +1,45 @@
1
+const VELOCITY = [0.002, 0.0001];
2
+const SHOOT_PROBABILITY = 0.0007;
3
+
4
+class Cloud extends Entity {
5
+    static shootProbability = SHOOT_PROBABILITY;
6
+
7
+    constructor(...args) {
8
+        super(...args);
9
+        this.element.className = 'cloud';
10
+
11
+        for (let i = 0; i < 10; i++) {
12
+            const cloudParticle = document.createElement('div');
13
+            cloudParticle.className = 'cloud-particle';
14
+            cloudParticle.style.width = `${randRange(50, 80)}%`;
15
+            cloudParticle.style.height = `${randRange(60, 100)}%`;
16
+            cloudParticle.style.left = `${randRange(2, 98)}%`;
17
+            cloudParticle.style.top = `${randRange(2, 98)}%`;
18
+            cloudParticle.style.animationDelay = `${randRange(-10, 0)}s`;
19
+            cloudParticle.style.animationDuration = `${randRange(0.5, 3)}s`;
20
+
21
+            this.element.appendChild(cloudParticle);
22
+        }
23
+    }
24
+
25
+    static reset() {
26
+        this.shootProbability = SHOOT_PROBABILITY;
27
+    }
28
+
29
+    update(direction) {
30
+        if (this.alive) {
31
+            this.x += VELOCITY[0] * direction;
32
+            this.y += VELOCITY[1];
33
+
34
+            if (Math.random() < Cloud.shootProbability) {
35
+                const raindrop = new RainDrop(this.x, this.y + 0.009);
36
+                bullets.add(raindrop);
37
+            }
38
+        }
39
+    }
40
+
41
+    destroy() {
42
+        super.destroy();
43
+        Cloud.shootProbability += 0.0001;
44
+    }
45
+}

+ 36
- 0
entity.js 查看文件

@@ -0,0 +1,36 @@
1
+class Entity {
2
+    constructor(x, y, w, h) {
3
+        this.x = x;
4
+        this.y = y;
5
+        this.w = w;
6
+        this.h = h;
7
+        this.element = document.createElement('div');
8
+        ROOT.appendChild(this.element);
9
+        this.draw();
10
+        this.alive = true;
11
+    }
12
+
13
+    draw() {
14
+        if (this.alive) {
15
+            this.element.style.left = `${this.x * 100}%`;
16
+            this.element.style.top = `${this.y * 100}%`;
17
+            this.element.style.width = `${this.w * 100}%`;
18
+            this.element.style.height = `${this.h * 100}%`;
19
+        }
20
+    }
21
+
22
+    destroy() {
23
+        this.element.remove();
24
+        this.alive = false;
25
+    }
26
+
27
+    isOverlapped(other) {
28
+        if (!this.alive || !other.alive) return false;
29
+        if (other.x + other.w / 2 < this.x - this.w / 2) return false;
30
+        if (other.x - other.w / 2 > this.x + this.w / 2) return false;
31
+        if (other.y + other.h / 2 < this.y - this.h / 2) return false;
32
+        if (other.y - other.h / 2 > this.y + this.h / 2) return false;
33
+
34
+        return true;
35
+    }
36
+}

+ 93
- 0
game.js 查看文件

@@ -0,0 +1,93 @@
1
+const $ = (s) => document.querySelector(s);
2
+const $$ = (s) => Array.from(document.querySelectorAll(s));
3
+const ROOT = $('#root');
4
+
5
+const clouds = new Set();
6
+const bullets = new Set();
7
+let shootingPad = new ShootingPad();
8
+const X_BOUND = 0.05;
9
+let running = true;
10
+let direction = 1;
11
+let lives = 3;
12
+
13
+const initCloudField = () => {
14
+    for (let y = 0; y < 6; y++) {
15
+        for (let x = 0; x < 11; x++) {
16
+            const cloud = new Cloud(X_BOUND + 0.06 * x, 0.04 + 0.04 * y, 0.03, 0.02);
17
+            clouds.add(cloud);
18
+        }
19
+    }
20
+}
21
+
22
+const start = () => {
23
+    $('.result')?.remove();
24
+
25
+    shootingPad?.destroy();
26
+    shootingPad = new ShootingPad();
27
+
28
+    bullets.forEach(c => c.destroy());
29
+    bullets.clear();
30
+
31
+    clouds.forEach(c => c.destroy());
32
+    clouds.clear();
33
+    Cloud.reset();
34
+    initCloudField();
35
+
36
+    running = true;
37
+    loop();
38
+}
39
+
40
+const gameOver = (result) => {
41
+    const resultLabel = document.createElement('div');
42
+    resultLabel.className = 'result';
43
+    resultLabel.textContent = result;
44
+    ROOT.appendChild(resultLabel);
45
+    running = false;
46
+}
47
+
48
+const loop = () => {
49
+    if (!running) return;
50
+
51
+    let directionUpdated = false;
52
+    Array.from(clouds).forEach(c => {
53
+        c.update(direction);
54
+        c.draw();
55
+        if (c.x < X_BOUND - 0.01 || c.x > 1.01 - X_BOUND) {
56
+            directionUpdated = true;
57
+        }
58
+        if (!c.alive) {
59
+            clouds.delete(c);
60
+        }
61
+    });
62
+
63
+    if (directionUpdated) {
64
+        direction = -direction;
65
+    }
66
+
67
+    Array.from(bullets).forEach(b => {
68
+        b.update();
69
+        b.draw();
70
+
71
+        if (!b.alive) {
72
+            bullets.delete(b);
73
+        }
74
+    });
75
+
76
+    shootingPad.update();
77
+    shootingPad.draw();
78
+
79
+    if (clouds.size === 0) {
80
+        gameOver('WIN');
81
+    }
82
+
83
+    requestAnimationFrame(loop);
84
+};
85
+
86
+
87
+document.addEventListener('keypress', () => {
88
+    if (!running) {
89
+        start();
90
+    }
91
+});
92
+
93
+start();

+ 17
- 0
index.html 查看文件

@@ -0,0 +1,17 @@
1
+<!DOCTYPE html>
2
+<html>
3
+    <head>
4
+        <title>OLC Codejam 2022</title>
5
+        <link rel="stylesheet" href="style.css">
6
+    </head>
7
+    <body>
8
+        <div id="root">
9
+        </div>
10
+        <script src="utils.js"></script>
11
+        <script src="entity.js"></script>
12
+        <script src="bullet.js"></script>
13
+        <script src="shooting_pad.js"></script>
14
+        <script src="cloud.js"></script>
15
+        <script src="game.js"></script>
16
+    </body>
17
+</html>

+ 36
- 0
shooting_pad.js 查看文件

@@ -0,0 +1,36 @@
1
+const keys = {};
2
+
3
+document.addEventListener('keydown', (e) => {
4
+    keys[e.key] = true;
5
+});
6
+document.addEventListener('keyup', (e) => {
7
+    keys[e.key] = false;
8
+});
9
+
10
+class ShootingPad extends Entity {
11
+    constructor() {
12
+        super(0.5, 0.95, 0.04, 0.03);
13
+        this.element.className = 'shooting-pad';
14
+        this.lastShootTime = Date.now();
15
+    }
16
+
17
+    update() {
18
+        if (keys['ArrowLeft']) {
19
+            this.x -= VELOCITY[0] * 2;
20
+        }
21
+        if (keys['ArrowRight']) {
22
+            this.x += VELOCITY[0] * 2;
23
+        }
24
+        if (this.x < X_BOUND) {
25
+            this.x = X_BOUND;
26
+        }
27
+        if (this.x > 1 - X_BOUND) {
28
+            this.x = 1 - X_BOUND;
29
+        }
30
+        if (keys[' '] && Date.now() - this.lastShootTime >= 300) {
31
+            this.lastShootTime = Date.now();
32
+            const bullet = new Bullet(this.x, this.y - 0.009);
33
+            bullets.add(bullet);
34
+        }
35
+    }
36
+}

+ 81
- 0
style.css 查看文件

@@ -0,0 +1,81 @@
1
+html, body {
2
+    margin: 0;
3
+    padding: 0;
4
+    width: 100%;
5
+    height: 100%;
6
+    display: flex;
7
+    justify-content: center;
8
+    align-items: center;
9
+    background: rgb(51, 51, 51);
10
+    overflow: hidden;
11
+    white-space: nowrap;
12
+}
13
+
14
+#root {
15
+    background: rgb(31, 31, 31);
16
+    position: relative;
17
+    margin: 0;
18
+    padding: 0;
19
+    width: 100%;
20
+    max-height: 100%;
21
+    aspect-ratio: 16/9;
22
+    overflow: hidden;
23
+}
24
+
25
+#root div {
26
+    transform: translate(-50%, -50%);
27
+    position: absolute;
28
+}
29
+
30
+.cloud-particle {
31
+    border-radius: 50%;
32
+    background: rgb(255, 255, 255);
33
+    animation-name: cloud;
34
+    animation-iteration-count: infinite;
35
+    animation-direction: alternate;
36
+}
37
+
38
+.shooting-pad {
39
+    background: red;
40
+}
41
+
42
+.bullet {
43
+    background: yellow;
44
+    width: 1px !important;
45
+}
46
+
47
+.raindrop {
48
+    background: cyan;
49
+    width: 1px !important;
50
+}
51
+
52
+.result {
53
+    left: 50%;
54
+    top: 50%;
55
+    font-size: 108px;
56
+    font-family: arial;
57
+    color: white;
58
+}
59
+
60
+.result:after {
61
+    content: 'Press any key to restart';
62
+    position: absolute;
63
+    font-size: 48px;
64
+    width: fit-content;
65
+    top: 85%;
66
+    left: 50%;
67
+    transform: translate(-50%);
68
+}
69
+
70
+
71
+@keyframes cloud {
72
+    from {
73
+        transform: translate(-50%) scale(1.2);
74
+        opacity: 0.7;
75
+    }
76
+
77
+    to {
78
+        transform: translate(-50%) scale(0.2);
79
+        opacity: 0.1;
80
+    }
81
+}

+ 10
- 0
todo.md 查看文件

@@ -0,0 +1,10 @@
1
+# TODO
2
+
3
+- lives
4
+- clouds should strike lightnings sometimes
5
+- bg rain effect
6
+- shooting platform sprite
7
+- boss?
8
+- umbrella shields
9
+- music & sounds
10
+

+ 1
- 0
utils.js 查看文件

@@ -0,0 +1 @@
1
+const randRange = (low, high) => Math.random() * (high - low) + low;

Loading…
取消
儲存