動態

詳情 返回 返回

20251107 - 動態 詳情

每次寫類的時候都會覺得發明面向對象的人真是個天才。

今天晚上又稍微寫了點,增加了碰撞傷害,現在玩家碰到怪物會回扣怪物.at血量並進入一秒的無敵狀態,並且自身顏色變為白色,也是第一次體會到異步的方便之處吧。

更新後代碼如下:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 
  4 <head>
  5   <meta charset="UTF-8">
  6   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7   <title>Document</title>
  8 </head>
  9 <style>
 10   body {
 11     margin: 0;
 12     overflow: hidden;
 13   }
 14 </style>
 15 
 16 <body>
 17   <canvas id="Canvas" style="border:1px solid #000000;"></canvas>
 18 </body>
 19 <script>
 20   //初始化畫布
 21   const canvas = document.getElementById('Canvas');
 22   canvas.width = window.innerWidth;
 23   canvas.height = window.innerHeight;
 24   canvas.style.backgroundColor = '#000000';
 25   const ctx = canvas.getContext('2d');
 26   //定義遊戲對象數組
 27   const grounds = [];
 28   const monsters = [];
 29   //定義玩家類
 30   class Player {
 31     //基礎屬性
 32     hp = 10;
 33     x = Math.round(canvas.width / 2);
 34     y = Math.round(canvas.height / 2);
 35     width = 30;
 36     height = 30;
 37     color = '#FF0000';
 38     invincibleColor = 'white';
 39     speedX = 0;
 40     speedY = 0;
 41     a = 0.05;
 42     g = 0.1;
 43     maxSpeedX = 3;
 44     maxSpeedY = 3;
 45 
 46     lastAttackedTime = Date.now();
 47 
 48     status = {
 49       up: false,
 50       down: false,
 51       left: false,
 52       right: false,
 53 
 54       landing: false,
 55       toward: 'right',
 56       attacking: false,
 57       invincible: false,
 58     }
 59 
 60     damage = {
 61       at: 1,
 62       width: 80,
 63       height: 40,
 64     }
 65 
 66     //方法
 67 
 68     //跳躍方法
 69     jump() {
 70       this.speedY = -5;
 71       this.status.landing = false;
 72     }
 73     //碰撞檢測方法
 74     crush(ground) {
 75       if (ground.y - (this.y + this.height) <= 0 && ground.y - (this.y + this.height) >= -this.speedY)
 76         return true;
 77       else
 78         return false;
 79     }
 80     //玩家運動
 81     move() {
 82       this.x += this.speedX;
 83       this.y += this.speedY;
 84     }
 85     //碰撞監測
 86     checkCrash() {
 87       grounds.forEach(Ground => {
 88         if (this.crush(Ground)) {
 89           this.y = Ground.y - this.height;
 90           this.status.landing = true;
 91         }
 92       });
 93     }
 94     //重力作用
 95     applyGravity() {
 96       if (this.status.landing == false) {
 97         this.speedY += this.g;
 98         if (this.speedY > this.maxSpeedY)
 99           this.speedY = this.maxSpeedY;
100       } else {
101         this.speedY = 0;
102       }
103     }
104     //水平無操作時水平減速
105     velocityDecay() {
106       if ((this.status.left == false && this.status.right == false) || (this.status.left == true && this.status.right == true)) {
107         if (this.speedX > 0) {
108           this.speedX -= this.a;
109           if (this.speedX < 0) this.speedX = 0;
110         } else if (this.speedX < 0) {
111           this.speedX += this.a;
112           if (this.speedX > 0) this.speedX = 0;
113         }
114       }
115     }
116     //水平加速度操作速度
117     controlSpeed() {
118       if (this.status.left) {
119         this.speedX -= this.a;
120         if (this.speedX < -this.maxSpeedX) this.speedX = -this.maxSpeedX;
121       }
122       if (this.status.right) {
123         this.speedX += this.a;
124         if (this.speedX > this.maxSpeedX) this.speedX = this.maxSpeedX;
125       }
126     }
127     //繪製玩家
128     draw() {
129       if (this.status.invincible)
130         ctx.fillStyle = this.invincibleColor;
131       else
132         ctx.fillStyle = this.color;
133       ctx.fillRect(this.x, this.y, this.width, this.height);
134     }
135     //展示血量數字
136     showHp() {
137       ctx.fillStyle = 'white';
138       ctx.font = '12px Arial';
139       ctx.fillText(this.hp, this.x + this.width / 2 - 6, this.y - 2);
140     }
141     //攻擊方法
142     attack(m) {
143       m.hp -= this.damage.at;
144       console.log("攻擊命中!怪物剩餘血量:" + m.hp);
145       if (m.hp <= 0) {
146         const index = monsters.indexOf(m);
147         if (index > -1) {
148           monsters.splice(index, 1);
149           console.log("怪物已被擊敗!");
150         }
151       }
152     }
153     //繪製攻擊範圍與攻擊判定
154     drawAttackRange() {
155       //繪製範圍
156       if (this.status.attacking) {
157         ctx.fillStyle = '#FFFF00';
158         if (this.status.toward == 'right') {
159           ctx.fillRect(this.x + this.width, this.y + (this.height - this.damage.height) / 2, this.damage.width, this.damage.height);
160         } else if (this.status.toward == 'left') {
161           ctx.fillRect(this.x - this.damage.width, this.y + (this.height - this.damage.height) / 2, this.damage.width, this.damage.height);
162         }
163         //攻擊判定
164         monsters.forEach(m => {
165           if (this.status.toward == 'right' &&
166             m.x < this.x + this.width + this.damage.width &&
167             m.x + m.width > this.x + this.width &&
168             m.y < this.y + (this.height + this.damage.height) / 2 &&
169             m.y + m.height > this.y + (this.height - this.damage.height) / 2
170           ) {
171             this.attack(m);
172           }
173           else if (
174             this.status.toward == 'left' &&
175             m.x + m.width > this.x - this.damage.width &&
176             m.x < this.x &&
177             m.y < this.y + (this.height + this.damage.height) / 2 &&
178             m.y + m.height > this.y + (this.height - this.damage.height) / 2
179           ) {
180             this.attack(m);
181           }
182         })
183         this.status.attacking = false;
184       }
185     }
186     //受擊方法
187     attacked() {
188       monsters.forEach(m => {
189         if (
190           m.x < this.x + this.width &&
191           m.x + m.width > this.x &&
192           m.y < this.y + this.height &&
193           m.y + m.height > this.y
194         ) {
195           this.reduceHp(m.at);
196         }
197       });
198     }
199     //常規血量減少
200     reduceHp(at) {
201       const currentTime = Date.now();
202       if (currentTime - this.lastAttackedTime > 1000) {
203         this.hp -= at;
204         this.status.invincible = true;
205         this.lastAttackedTime = currentTime;
206         //異步延遲
207         setTimeout(() => {
208           this.status.invincible = false;
209         }, 1000);
210       }
211     }
212   }
213   //定義地面類
214   class Ground {
215     x = 0;
216     y = 0;
217     width = 0;
218     height = 0;
219     color = '#654321';
220 
221     constructor(x, y, width, height) {
222       this.x = x;
223       this.y = y;
224       this.width = width;
225       this.height = height;
226     }
227   }
228   //定義怪物類
229   class Monster {
230     hp = 5;
231     at = 1;
232     x = 0;
233     y = 0;
234     width = 30;
235     height = 30;
236     color = '#00FF00';
237 
238     constructor(x, y, width, height) {
239       this.x = x;
240       this.y = y;
241       this.width = width;
242       this.height = height;
243     }
244     //繪製怪物
245     draw() {
246       ctx.fillStyle = this.color;
247       ctx.fillRect(this.x, this.y, this.width, this.height);
248     }
249     //展示血量數字
250     showHp() {
251       ctx.fillStyle = 'white';
252       ctx.font = '12px Arial';
253       ctx.fillText(this.hp, this.x + this.width / 2 - 6, this.y - 2);
254     }
255   }
256 
257   //創建初始測試 玩家對象 地面對象 怪物對象
258   const p = new Player();
259   const ground1 = new Ground(0, Math.round(canvas.height - 100), Math.round(canvas.width), 100);
260   grounds.push(ground1);
261   const monster1 = new Monster(200, ground1.y - 30, 30, 30);
262   monsters.push(monster1);
263 
264   //鍵盤事件監聽
265 
266   //1.按下按鍵
267   document.addEventListener('keydown', function (event) {
268     switch (event.key) {
269       case 'ArrowUp':
270         if (p.status.landing == true)
271           p.jump();
272         break;
273       case 'ArrowDown':
274         p.status.down = true;
275         break;
276       case 'ArrowLeft':
277         p.status.left = true;
278         p.status.toward = 'left';
279         break;
280       case 'ArrowRight':
281         p.status.right = true;
282         p.status.toward = 'right';
283         break;
284       case 'z':
285         p.status.attacking = true;
286         break;
287       case 'Z':
288         p.status.attacking = true;
289         break;
290     }
291   });
292   //2.鬆開按鍵
293   document.addEventListener('keyup', function (event) {
294     switch (event.key) {
295       case 'ArrowUp':
296         break;
297       case 'ArrowDown':
298         p.status.down = false;
299         break;
300       case 'ArrowLeft':
301         p.status.left = false;
302         break;
303       case 'ArrowRight':
304         p.status.right = false;
305         break;
306     }
307   });
308   //3.c鍵查看玩家狀態
309   document.addEventListener('keydown', function (event) {
310     if (event.key === 'c' || event.key === 'C') {
311       console.log("玩家狀態:", p);
312     }
313 
314   });
315   //動畫循環
316   function animate() {
317     ctx.clearRect(0, 0, canvas.width, canvas.height);
318 
319     //繪製陸地
320     grounds.forEach(Ground => {
321       ctx.fillStyle = Ground.color;
322       ctx.fillRect(Ground.x, Ground.y, Ground.width, Ground.height);
323     });
324 
325     //玩家
326     {
327       //玩家運動
328       p.move();
329       //碰撞監測
330       p.checkCrash();
331       //重力作用
332       p.applyGravity();
333       //水平無操作時水平減速
334       p.velocityDecay();
335       //水平加速度操作速度
336       p.controlSpeed();
337       //受到傷害方法
338       p.attacked()
339       //繪製玩家
340       p.draw();
341       //展示血量
342       p.showHp();
343       //繪製攻擊範圍
344       p.drawAttackRange();
345     }
346 
347     //怪物
348     {
349       monsters.forEach(m => {
350         //繪製怪物
351         m.draw();
352         //展示血量
353         m.showHp();
354       });
355 
356     }
357     requestAnimationFrame(animate);
358   }
359   //啓動!!
360   animate();
361 </script>
362 
363 </html>

 

Add a new 評論

Some HTML is okay.