2 次代码提交

作者 SHA1 备注 提交日期
  Pabloader cd44bbe7a8 Title screen 2 年前
  Pabloader 4bbf215d09 Sound 2 年前
共有 10 个文件被更改,包括 111 次插入55 次删除
  1. 15
    1
      README.md
  2. 12
    0
      css/style.css
  3. 1
    0
      index.html
  4. 8
    0
      js/cloud.js
  5. 58
    42
      js/game.js
  6. 17
    1
      js/shooting_pad.js
  7. 二进制
      snd/click.ogg
  8. 二进制
      snd/hit.ogg
  9. 二进制
      snd/shoot.ogg
  10. 0
    11
      todo.md

+ 15
- 1
README.md 查看文件

@@ -1,3 +1,17 @@
1 1
 # About
2 2
 
3
-This game is written for [OLC Code Jam 2022](https://itch.io/jam/olc-codejam-2022)
3
+This game is written for [OLC Code Jam 2022](https://itch.io/jam/olc-codejam-2022)
4
+
5
+# TODO
6
+
7
+- [x] lives
8
+- [x] shooting platform sprite
9
+- [ ] clouds should strike lightnings sometimes
10
+- [x] bg rain effect
11
+- [ ] animated gif for clouds
12
+- [ ] boss?
13
+- [ ] umbrella shields
14
+- [ ] music
15
+- [x] sounds
16
+- [x] title screen
17
+- [x] settings

+ 12
- 0
css/style.css 查看文件

@@ -63,6 +63,18 @@ html, body {
63 63
     font-size: 32px;
64 64
 }
65 65
 
66
+#sound {
67
+    position: absolute;
68
+    display: block;
69
+    top: 15px;
70
+    right: 15px;
71
+    font-size: 32px;
72
+    border: none;
73
+    background: transparent;
74
+    outline: none;
75
+    cursor: pointer;
76
+}
77
+
66 78
 #lives:before {
67 79
     content: '❤ ';
68 80
     color: red;

+ 1
- 0
index.html 查看文件

@@ -9,6 +9,7 @@
9 9
         <canvas id="canvas" width="1280" height="720"></canvas>
10 10
         <div id="root">
11 11
             <section id="lives"></section>
12
+            <section id="sound"></section>
12 13
         </div>
13 14
         <script src="https://cdn.jsdelivr.net/gh/josephg/noisejs/perlin.js"></script>
14 15
         <script src="js/utils.js"></script>

+ 8
- 0
js/cloud.js 查看文件

@@ -21,6 +21,9 @@ class Cloud extends Entity {
21 21
 
22 22
             this.particles.add(cloudParticle);
23 23
         }
24
+
25
+        this.sound = new Audio();
26
+        this.sound.src = 'snd/click.ogg';
24 27
     }
25 28
 
26 29
     draw() {
@@ -67,6 +70,11 @@ class Cloud extends Entity {
67 70
             if (Math.random() < Cloud.shootProbability) {
68 71
                 const raindrop = new RainDrop(this.x, this.y + 0.009);
69 72
                 bullets.add(raindrop);
73
+
74
+                if (soundEnabled) {
75
+                    this.sound.currentTime = 0;
76
+                    this.sound.play().catch(() => { });
77
+                }
70 78
             }
71 79
         }
72 80
     }

+ 58
- 42
js/game.js 查看文件

@@ -1,8 +1,12 @@
1 1
 const $ = (s) => document.querySelector(s);
2
-const $$ = (s) => Array.from(document.querySelectorAll(s));
2
+
3 3
 const ROOT = $('#root');
4
+const LIVES_LABEL = $('#lives');
5
+const SOUND_BUTTON = $('#sound');
4 6
 const CANVAS = $('#canvas');
5 7
 const CTX = CANVAS.getContext('2d');
8
+
9
+
6 10
 const X_BOUND = 0.05;
7 11
 const GAME_OVER_BOUND = 0.9;
8 12
 const MAX_RAINDROPS = 100;
@@ -11,8 +15,9 @@ const clouds = new Set();
11 15
 const bullets = new Set();
12 16
 const raindrops = new Set();
13 17
 let shootingPad = new ShootingPad();
14
-let running = true;
18
+let running = false;
15 19
 let direction = 1;
20
+let soundEnabled = localStorage.soundEnabled !== 'false';
16 21
 
17 22
 const initCloudField = () => {
18 23
     for (let y = 0; y < 6; y++) {
@@ -36,6 +41,11 @@ const initRainEffect = () => {
36 41
     }
37 42
 }
38 43
 
44
+const updateSoundButton = () => {
45
+    SOUND_BUTTON.textContent = soundEnabled ? '🔊' : '🔈';
46
+    SOUND_BUTTON.title = `Sound ${soundEnabled ? 'enabled' : 'disabled'}`;
47
+}
48
+
39 49
 const start = () => {
40 50
     $('.result')?.remove();
41 51
 
@@ -51,7 +61,6 @@ const start = () => {
51 61
     initCloudField();
52 62
 
53 63
     running = true;
54
-    loop();
55 64
 }
56 65
 
57 66
 const gameOver = (result) => {
@@ -63,58 +72,63 @@ const gameOver = (result) => {
63 72
 }
64 73
 
65 74
 const loop = () => {
66
-    if (!running) return;
67 75
     document.title = `${fps()} FPS | CloudInvaders | OLC Codejam 2022`;
68 76
     CTX.clearRect(0, 0, CANVAS.width, CANVAS.height);
69 77
 
70
-    let directionUpdated = false;
71
-    let isOver = false;
72
-    Array.from(clouds).forEach(c => {
73
-        c.update(direction);
74
-        c.draw();
75
-        if (c.x < X_BOUND || c.x > 1 - X_BOUND) {
76
-            directionUpdated = true;
77
-        }
78
-        if (c.y >= GAME_OVER_BOUND) {
79
-            isOver = true;
78
+    raindrops.forEach(d => {
79
+        d.update();
80
+        d.draw();
81
+    });
82
+    if (running) {
83
+        let directionUpdated = false;
84
+        let isOver = false;
85
+        Array.from(clouds).forEach(c => {
86
+            c.update(direction);
87
+            c.draw();
88
+            if (c.x < X_BOUND || c.x > 1 - X_BOUND) {
89
+                directionUpdated = true;
90
+            }
91
+            if (c.y >= GAME_OVER_BOUND) {
92
+                isOver = true;
93
+            }
94
+            if (!c.alive) {
95
+                clouds.delete(c);
96
+            }
97
+        });
98
+
99
+        if (directionUpdated) {
100
+            direction = -direction;
80 101
         }
81
-        if (!c.alive) {
82
-            clouds.delete(c);
102
+
103
+        if (isOver) {
104
+            gameOver('Game over');
83 105
         }
84
-    });
85 106
 
86
-    if (directionUpdated) {
87
-        direction = -direction;
88
-    }
107
+        Array.from(bullets).forEach(b => {
108
+            b.update();
109
+            b.draw();
89 110
 
90
-    if (isOver) {
91
-        gameOver('Game over');
92
-    }
111
+            if (!b.alive) {
112
+                bullets.delete(b);
113
+            }
114
+        });
93 115
 
94
-    Array.from(bullets).forEach(b => {
95
-        b.update();
96
-        b.draw();
116
+        shootingPad.update();
117
+        shootingPad.draw();
97 118
 
98
-        if (!b.alive) {
99
-            bullets.delete(b);
119
+        if (clouds.size === 0) {
120
+            gameOver('WIN');
100 121
         }
101
-    });
102
-
103
-    raindrops.forEach(d => {
104
-        d.update();
105
-        d.draw();
106
-    });
107
-
108
-    shootingPad.update();
109
-    shootingPad.draw();
110
-
111
-    if (clouds.size === 0) {
112
-        gameOver('WIN');
113 122
     }
114
-
115 123
     requestAnimationFrame(loop);
116 124
 };
117 125
 
126
+SOUND_BUTTON.addEventListener('click', () => {
127
+    soundEnabled = !soundEnabled;
128
+    localStorage.soundEnabled = soundEnabled;
129
+    updateSoundButton();
130
+});
131
+
118 132
 document.addEventListener('keypress', () => {
119 133
     if (!running) {
120 134
         start();
@@ -122,4 +136,6 @@ document.addEventListener('keypress', () => {
122 136
 });
123 137
 
124 138
 initRainEffect();
125
-start();
139
+updateSoundButton();
140
+loop();
141
+gameOver('🌩 Cloud Invaders 🌩');

+ 17
- 1
js/shooting_pad.js 查看文件

@@ -21,6 +21,12 @@ class ShootingPad extends Entity {
21 21
         image.src = 'img/shooting-pad.svg';
22 22
         image.onload = () => this.image = image;
23 23
 
24
+        this.shootSound = new Audio();
25
+        this.shootSound.src = 'snd/shoot.ogg';
26
+
27
+        this.hitSound = new Audio();
28
+        this.hitSound.src = 'snd/hit.ogg';
29
+
24 30
         this.lastShootTime = Date.now();
25 31
         this.invulnerable = false;
26 32
         this.lives = MAX_LIVES;
@@ -44,6 +50,11 @@ class ShootingPad extends Entity {
44 50
             this.lastShootTime = Date.now();
45 51
             const bullet = new Bullet(this.x, this.y - 0.009);
46 52
             bullets.add(bullet);
53
+
54
+            if (soundEnabled) {
55
+                this.shootSound.currentTime = 0;
56
+                this.shootSound.play().catch(() => { });
57
+            }
47 58
         }
48 59
     }
49 60
 
@@ -54,7 +65,7 @@ class ShootingPad extends Entity {
54 65
     }
55 66
 
56 67
     initLives() {
57
-        $('#lives').textContent = `× ${this.lives}`;
68
+        LIVES_LABEL.textContent = `× ${this.lives}`;
58 69
     }
59 70
 
60 71
     lifeOver() {
@@ -63,6 +74,11 @@ class ShootingPad extends Entity {
63 74
         this.lives--;
64 75
         this.initLives();
65 76
 
77
+        if (soundEnabled) {
78
+            this.hitSound.currentTime = 0;
79
+            this.hitSound.play().catch(() => { });
80
+        }
81
+
66 82
         if (this.lives === 0) {
67 83
             return gameOver('Game over');
68 84
         } else {

二进制
snd/click.ogg 查看文件


二进制
snd/hit.ogg 查看文件


二进制
snd/shoot.ogg 查看文件


+ 0
- 11
todo.md 查看文件

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

正在加载...
取消
保存