每次写类的时候都会觉得发明面向对象的人真是个天才。 今天晚上又稍微写了点,增加了碰撞伤害,现在玩家碰到怪物会回扣怪物.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>