经过今天晚上近两个小时的努力,终于初具雏形了!!
添加功能如下:
1.增加了计量分数,和金币的功能。
2.增加了属性加点功能。
3.增加了怪物的增长曲线。
4.增加了界面跳转和回合重置。
也能应该够格算一个纯前端的简单的割草小游戏demo了吧。
等日后再去优化拓展其他功能。
html代码如下:
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 let running = false; 22 let score = 0; 23 let gold = 0; 24 let maxMonsterCount = 1; 25 let currentPlayerCount = 0; 26 let currentMonsterCount = 0; 27 let maxPlayerHp = 10; 28 let playerAt = 1; 29 let playerAttackwidth = 80; 30 let playerAttackheight = 40; 31 let maxMonsterHp = 5; 32 let monsterAt = 1; 33 //初始化画布 34 const canvas = document.getElementById('Canvas'); 35 canvas.width = window.innerWidth; 36 canvas.height = window.innerHeight; 37 canvas.style.backgroundColor = '#000000'; 38 const ctx = canvas.getContext('2d'); 39 //定义游戏对象数组 40 const players = []; 41 const grounds = []; 42 const monsters = []; 43 //定义玩家类 44 class Player { 45 //基础属性 46 maxHp = 10; 47 hp = 10; 48 x = Math.round(canvas.width / 2); 49 y = Math.round(canvas.height / 2); 50 width = 30; 51 height = 30; 52 color = '#FF0000'; 53 invincibleColor = 'white'; 54 speedX = 0; 55 speedY = 0; 56 a = 0.05; 57 g = 0.1; 58 maxSpeedX = 3; 59 maxSpeedY = 3; 60 61 lastAttackedTime = Date.now(); 62 63 status = { 64 up: false, 65 down: false, 66 left: false, 67 right: false, 68 69 landing: false, 70 toward: 'right', 71 attacking: false, 72 invincible: false, 73 } 74 75 damage = { 76 at: 1, 77 width: 80, 78 height: 40, 79 } 80 //构造函数 81 constructor() { 82 this.maxHp = maxPlayerHp; 83 this.hp = this.maxHp; 84 this.damage.at = playerAt; 85 this.damage.width = playerAttackwidth; 86 this.damage.height = playerAttackheight; 87 } 88 89 //方法 90 91 //跳跃方法 92 jump() { 93 this.speedY = -5; 94 this.status.landing = false; 95 } 96 //玩家运动 97 move() { 98 this.x += this.speedX; 99 this.y += this.speedY; 100 } 101 //碰撞监测 102 checkCrash() { 103 //陆地检测 104 grounds.forEach(Ground => { 105 //下落碰撞检测 106 if (Ground.y - (this.y + this.height) <= 0 && 107 Ground.y - (this.y + this.height) >= -this.speedY && 108 this.x + this.width > Ground.x && 109 this.x < Ground.x + Ground.width 110 ) { 111 this.y = Ground.y - this.height; 112 this.status.landing = true; 113 } 114 //左侧 115 if ( 116 this.x < Ground.x + Ground.width && 117 this.x > Ground.x + Ground.width + this.speedX && 118 this.y + this.height > Ground.y && 119 this.y < Ground.y + Ground.height 120 ) { 121 this.x = Ground.x + Ground.width; 122 this.speedX = 0; 123 this.status.left = false; 124 } 125 //右侧 126 if ( 127 this.x + this.width > Ground.x && 128 this.x + this.width < Ground.x + this.speedX && 129 this.y + this.height > Ground.y && 130 this.y < Ground.y + Ground.height 131 ) { 132 this.x = Ground.x - this.width; 133 this.speedX = 0; 134 this.status.right = false; 135 } 136 }); 137 //边界检测 138 if (this.x < 0) { 139 this.x = 0; 140 this.speedX = 0; 141 } 142 if (this.x + this.width > canvas.width) { 143 this.x = canvas.width - this.width; 144 this.speedX = 0; 145 } 146 if (this.y < 0) { 147 this.y = 0; 148 this.speedY = 0; 149 } 150 if (this.y + this.height > canvas.height) { 151 this.y = canvas.height - this.height; 152 this.status.landing = true; 153 } 154 } 155 //重力作用 156 applyGravity() { 157 if (this.status.landing == false) { 158 this.speedY += this.g; 159 if (this.speedY > this.maxSpeedY) 160 this.speedY = this.maxSpeedY; 161 } else { 162 this.speedY = 0; 163 } 164 } 165 //水平无操作时水平减速 166 velocityDecay() { 167 if ((this.status.left == false && this.status.right == false) || (this.status.left == true && this.status.right == true)) { 168 if (this.speedX > 0) { 169 this.speedX -= this.a; 170 if (this.speedX < 0) this.speedX = 0; 171 } else if (this.speedX < 0) { 172 this.speedX += this.a; 173 if (this.speedX > 0) this.speedX = 0; 174 } 175 } 176 } 177 //水平加速度操作速度 178 controlSpeed() { 179 if (this.status.left) { 180 this.speedX -= this.a; 181 if (this.speedX < -this.maxSpeedX) this.speedX = -this.maxSpeedX; 182 } 183 if (this.status.right) { 184 this.speedX += this.a; 185 if (this.speedX > this.maxSpeedX) this.speedX = this.maxSpeedX; 186 } 187 } 188 //绘制玩家 189 draw() { 190 if (this.status.invincible) 191 ctx.fillStyle = this.invincibleColor; 192 else 193 ctx.fillStyle = this.color; 194 ctx.fillRect(this.x, this.y, this.width, this.height); 195 } 196 //展示血量数字 197 showHp() { 198 ctx.fillStyle = 'white'; 199 ctx.font = '12px Arial'; 200 ctx.fillText(this.hp, this.x + this.width / 2 - 6, this.y - 2); 201 } 202 //攻击方法 203 attack(m) { 204 m.hp -= this.damage.at; 205 if (m.hp <= 0) { 206 const index = monsters.indexOf(m); 207 if (index > -1) { 208 monsters.splice(index, 1); 209 currentMonsterCount -= 1; 210 gold += 1; 211 score += m.maxHp; 212 } 213 } 214 } 215 //绘制攻击范围与攻击判定 216 drawAttackRange() { 217 //绘制范围 218 if (this.status.attacking) { 219 ctx.fillStyle = '#FFFF00'; 220 if (this.status.toward == 'right') { 221 ctx.fillRect(this.x + this.width, this.y + (this.height - this.damage.height) / 2, this.damage.width, this.damage.height); 222 } else if (this.status.toward == 'left') { 223 ctx.fillRect(this.x - this.damage.width, this.y + (this.height - this.damage.height) / 2, this.damage.width, this.damage.height); 224 } 225 //攻击判定 226 monsters.forEach(m => { 227 if (this.status.toward == 'right' && 228 m.x < this.x + this.width + this.damage.width && 229 m.x + m.width > this.x + this.width && 230 m.y < this.y + (this.height + this.damage.height) / 2 && 231 m.y + m.height > this.y + (this.height - this.damage.height) / 2 232 ) { 233 this.attack(m); 234 } 235 else if ( 236 this.status.toward == 'left' && 237 m.x + m.width > this.x - this.damage.width && 238 m.x < this.x && 239 m.y < this.y + (this.height + this.damage.height) / 2 && 240 m.y + m.height > this.y + (this.height - this.damage.height) / 2 241 ) { 242 this.attack(m); 243 } 244 }) 245 this.status.attacking = false; 246 } 247 } 248 //受击方法 249 attacked() { 250 monsters.forEach(m => { 251 if ( 252 m.x < this.x + this.width && 253 m.x + m.width > this.x && 254 m.y < this.y + this.height && 255 m.y + m.height > this.y 256 ) { 257 this.reduceHp(m.at); 258 if (this.hp <= 0) { 259 currentPlayerCount -= 1; 260 } 261 } 262 }); 263 } 264 //常规血量减少 265 reduceHp(at) { 266 const currentTime = Date.now(); 267 if (currentTime - this.lastAttackedTime > 1000) { 268 this.hp -= at; 269 this.status.invincible = true; 270 this.lastAttackedTime = currentTime; 271 //异步延迟 272 setTimeout(() => { 273 this.status.invincible = false; 274 }, 1000); 275 } 276 } 277 } 278 //定义地面类 279 class Ground { 280 x = 0; 281 y = 0; 282 width = 0; 283 height = 0; 284 color = '#654321'; 285 286 constructor(x, y, width, height) { 287 this.x = x; 288 this.y = y; 289 this.width = width; 290 this.height = height; 291 } 292 } 293 //定义怪物类 294 class Monster { 295 maxHp = 5; 296 hp = 5; 297 at = 1; 298 x = 0; 299 y = 0; 300 width = 30; 301 height = 30; 302 invincibleColor = 'white'; 303 speedX = 0; 304 speedY = 0; 305 a = 0.03; 306 g = 0.1; 307 maxSpeedX = 2; 308 maxSpeedY = 3; 309 color = '#00FF00'; 310 311 status = { 312 up: false, 313 down: false, 314 left: false, 315 right: false, 316 317 landing: false, 318 toward: 'right', 319 attacking: false, 320 invincible: false, 321 } 322 323 constructor(x, y, width, height) { 324 this.x = x; 325 this.y = y; 326 this.width = width; 327 this.height = height; 328 this.maxHp = maxMonsterHp; 329 this.hp = this.maxHp; 330 this.at = monsterAt; 331 } 332 //绘制怪物 333 draw() { 334 ctx.fillStyle = this.color; 335 ctx.fillRect(this.x, this.y, this.width, this.height); 336 } 337 //展示血量数字 338 showHp() { 339 ctx.fillStyle = 'white'; 340 ctx.font = '12px Arial'; 341 ctx.fillText(this.hp, this.x + this.width / 2 - 6, this.y - 2); 342 } 343 //怪物运动 344 move() { 345 this.x += this.speedX; 346 this.y += this.speedY; 347 } 348 //水平加速度操作速度 349 controlSpeed() { 350 if (this.status.left) { 351 this.speedX -= this.a; 352 if (this.speedX < -this.maxSpeedX) this.speedX = -this.maxSpeedX; 353 } 354 if (this.status.right) { 355 this.speedX += this.a; 356 if (this.speedX > this.maxSpeedX) this.speedX = this.maxSpeedX; 357 } 358 } 359 //判断玩家位置并移动 360 pursuePlayer(p) { 361 if (p.x < this.x) { 362 this.status.left = true; 363 this.status.right = false; 364 } else if (p.x > this.x) { 365 this.status.right = true; 366 this.status.left = false; 367 } else { 368 this.status.left = false; 369 this.status.right = false; 370 } 371 } 372 //重力作用 373 applyGravity() { 374 if (this.status.landing == false) { 375 this.speedY += this.g; 376 if (this.speedY > this.maxSpeedY) 377 this.speedY = this.maxSpeedY; 378 } else { 379 this.speedY = 0; 380 } 381 } 382 //碰撞监测 383 checkCrash() { 384 grounds.forEach(Ground => { 385 //下落碰撞检测 386 if (Ground.y - (this.y + this.height) <= 0 && 387 Ground.y - (this.y + this.height) >= -this.speedY && 388 this.x + this.width > Ground.x && 389 this.x < Ground.x + Ground.width) { 390 this.y = Ground.y - this.height; 391 this.status.landing = true; 392 } 393 //左侧 394 if ( 395 this.x < Ground.x + Ground.width && 396 this.x > Ground.x + Ground.width + this.speedX && 397 this.y + this.height > Ground.y && 398 this.y < Ground.y + Ground.height 399 ) { 400 this.x = Ground.x + Ground.width; 401 this.speedX = 0; 402 } 403 //右侧 404 if ( 405 this.x + this.width > Ground.x && 406 this.x + this.width < Ground.x + this.speedX && 407 this.y + this.height > Ground.y && 408 this.y < Ground.y + Ground.height 409 ) { 410 this.x = Ground.x - this.width; 411 this.speedX = 0; 412 } 413 }); 414 //边界检测 415 if (this.x < 0) { 416 this.x = 0; 417 this.speedX = 0; 418 } 419 if (this.x + this.width > canvas.width) { 420 this.x = canvas.width - this.width; 421 this.speedX = 0; 422 } 423 if (this.y < 0) { 424 this.y = 0; 425 this.speedY = 0; 426 } 427 if (this.y + this.height > canvas.height) { 428 this.y = canvas.height - this.height; 429 this.status.landing = true; 430 } 431 } 432 } 433 434 //辅助函数 435 { 436 //开始 437 function start() { 438 running = true; 439 createPlayer(); 440 createMonster(); 441 animate(); 442 } 443 //生成指定范围随机整数 444 function randomInt(min, max) { 445 min = Math.ceil(min); 446 max = Math.floor(max); 447 return Math.floor(Math.random() * (max - min + 1)) + min; 448 } 449 //生成玩家 450 function createPlayer() { 451 const p = new Player(); 452 players.push(p); 453 currentPlayerCount += 1; 454 } 455 //生成怪物 456 function createMonster() { 457 for (let i = monsters.length; i < maxMonsterCount; i++) { 458 const m = new Monster(randomInt(0, canvas.width - 30), 0, 30, 30); 459 monsters.push(m); 460 currentMonsterCount += 1; 461 } 462 } 463 //怪物升级 464 function monsterLevelUp() { 465 monsterAt += 1; 466 maxMonsterCount += 2; 467 maxMonsterHp += 5; 468 } 469 } 470 //ui界面 471 { 472 //绘制顶栏 473 function drawSection() { 474 ctx.fillStyle = 'white'; 475 ctx.font = '20px Arial Border'; 476 ctx.fillText('分数: ' + score, 20, 30); 477 ctx.fillText('金币: ' + gold, 20, 60) 478 } 479 //绘制加点界面 480 function drawPointAddUi() { 481 ctx.fillStyle = 'white'; 482 ctx.font = '30px Arial Border'; 483 ctx.fillText('当前金币: ' + gold, 20, 60) 484 ctx.fillText('按下对应按键选择你的加点,按空格继续游戏', canvas.width / 2 - 200, canvas.height / 4 + 40 * 0); 485 ctx.fillText('每次加点消耗金币x1', canvas.width / 2 - 200, canvas.height / 4 + 40 * 1); 486 ctx.fillText('1 - 生命+5,当前生命最大值: ' + maxPlayerHp, canvas.width / 2 - 200, canvas.height / 4 + 40 * 3); 487 ctx.fillText('2 - 伤害+1,当前伤害: ' + playerAt, canvas.width / 2 - 200, canvas.height / 4 + 40 * 4); 488 ctx.fillText('3 - 攻击宽度+10,当前攻击宽度: ' + playerAttackwidth, canvas.width / 2 - 200, canvas.height / 4 + 40 * 5); 489 ctx.fillText('4 - 攻击高度+10,当前攻击高度: ' + playerAttackheight, canvas.width / 2 - 200, canvas.height / 4 + 40 * 6); 490 } 491 } 492 493 //创建初始地面对象 494 const ground1 = new Ground(0, Math.round(canvas.height - 100), Math.round(canvas.width), 100); 495 grounds.push(ground1); 496 497 //键盘事件监听 498 499 //1.按下按键 500 document.addEventListener('keydown', function (event) { 501 switch (event.key) { 502 case 'ArrowUp': 503 if (players[0].status.landing == true) 504 players[0].jump(); 505 break; 506 case 'ArrowDown': 507 players[0].status.down = true; 508 break; 509 case 'ArrowLeft': 510 players[0].status.left = true; 511 players[0].status.toward = 'left'; 512 break; 513 case 'ArrowRight': 514 players[0].status.right = true; 515 players[0].status.toward = 'right'; 516 break; 517 case 'z': 518 players[0].status.attacking = true; 519 break; 520 case 'Z': 521 players[0].status.attacking = true; 522 break; 523 case '1': 524 if (!running && gold >= 1) { 525 ctx.clearRect(0, 0, canvas.width, canvas.height) 526 maxPlayerHp += 5; 527 gold--; 528 drawPointAddUi(); 529 } else { 530 alert('金币不足!') 531 } break; 532 case '2': 533 if (!running && gold >= 1) { 534 ctx.clearRect(0, 0, canvas.width, canvas.height) 535 playerAt += 1; 536 gold--; 537 drawPointAddUi(); 538 } else { 539 alert('金币不足!') 540 } break; 541 case '3': 542 if (!running && gold >= 1) { 543 ctx.clearRect(0, 0, canvas.width, canvas.height) 544 playerAttackwidth += 10; 545 gold--; 546 drawPointAddUi(); 547 } else { 548 alert('金币不足!') 549 } break; 550 case '4': 551 if (!running && gold >= 1) { 552 ctx.clearRect(0, 0, canvas.width, canvas.height) 553 playerAttackheight += 10; 554 gold--; 555 drawPointAddUi(); 556 } else { 557 alert('金币不足!') 558 } break; 559 case ' ': 560 if (!running) 561 start(); 562 break; 563 } 564 }); 565 //2.松开按键 566 document.addEventListener('keyup', function (event) { 567 switch (event.key) { 568 case 'ArrowUp': 569 break; 570 case 'ArrowDown': 571 players[0].status.down = false; 572 break; 573 case 'ArrowLeft': 574 players[0].status.left = false; 575 break; 576 case 'ArrowRight': 577 players[0].status.right = false; 578 break; 579 } 580 }); 581 //3.c键查看玩家状态 582 document.addEventListener('keydown', function (event) { 583 if (event.key === 'c' || event.key === 'C') { 584 console.log("玩家状态:", players[0]); 585 } 586 587 }); 588 //动画循环 589 function animate() { 590 ctx.clearRect(0, 0, canvas.width, canvas.height); 591 592 //绘制顶栏 593 drawSection(); 594 595 //绘制陆地 596 grounds.forEach(Ground => { 597 ctx.fillStyle = Ground.color; 598 ctx.fillRect(Ground.x, Ground.y, Ground.width, Ground.height); 599 }); 600 601 //玩家 602 { 603 //玩家运动 604 players[0].move(); 605 //碰撞监测 606 players[0].checkCrash(); 607 //重力作用 608 players[0].applyGravity(); 609 //水平无操作时水平减速 610 players[0].velocityDecay(); 611 //水平加速度操作速度 612 players[0].controlSpeed(); 613 //受到伤害方法 614 players[0].attacked() 615 //绘制玩家 616 players[0].draw(); 617 //展示血量 618 players[0].showHp(); 619 //绘制攻击范围 620 players[0].drawAttackRange(); 621 } 622 623 //怪物 624 { 625 monsters.forEach(m => { 626 //下落碰撞检测 627 m.checkCrash(); 628 //重力作用 629 m.applyGravity(); 630 //绘制怪物 631 m.draw(); 632 //展示血量 633 m.showHp(); 634 //怪物运动 635 m.pursuePlayer(players[0]) 636 //水平加速度操作速度 637 m.controlSpeed(); 638 //怪物移动 639 m.move(); 640 }); 641 642 } 643 //清图判定 644 if (currentMonsterCount == 0) { 645 running = false; 646 ctx.clearRect(0, 0, canvas.width, canvas.height); 647 648 monsters.length = 0; 649 currentMonsterCount = 0; 650 players.length = 0; 651 currentPlayerCount = 0; 652 653 monsterLevelUp(); 654 drawPointAddUi(); 655 return; 656 } 657 //玩家死亡判定 658 if (currentPlayerCount <= 0) { 659 running = false; 660 ctx.clearRect(0, 0, canvas.width, canvas.height); 661 662 monsters.length = 0; 663 currentMonsterCount = 0; 664 players.length = 0; 665 currentPlayerCount = 0; 666 667 ctx.fillStyle = 'white'; 668 ctx.font = '60px Arial Border'; 669 ctx.fillText('You Died', canvas.width / 2 - 100, canvas.height / 2-100) 670 ctx.font = '30px Arial Border'; 671 ctx.fillText('最终分数: ' + score, canvas.width / 2 - 100, canvas.height / 2) 672 return; 673 } 674 requestAnimationFrame(animate); 675 } 676 //启动!! 677 start(); 678 </script> 679 680 </html>