从零开始手把手教你使用javascript+canvas开发一个塔防游戏02敌人自动寻路

简介: 从零开始手把手教你使用javascript+canvas开发一个塔防游戏02敌人自动寻路

项目演示


image.png


项目演示地址:

体验一下

项目源码:

项目源码

代码结构


image.png


本节做完效果


image.png

Enemy.js

//敌人类
function Enemy(cxt,img,type,x,y,width,height){
    this.cxt = cxt;
    this.img = img;
    this.x = x;//55
    this.y = y;//0
    this.width = width;
    this.height = height;
    //敌人类型
    this.type = type;
    this.sp = 2;
    //移动的方向
    this.dir = null;
    //下个移动位置
    this.nextPosition = null;
    //记录已经走过的位置
    this.hadWalk = {};
}
Enemy.prototype = {
    //敌人在图片中对应的位置
    enemyMap : [{x:0,y:0},{x:40,y:0},{x:80,y:0},{x:120,y:0},{x:160,y:0},{x:200,y:0},{x:240,y:0},{x:280,y:0},{x:320,y:0},{x:360,y:0},
        {x:400,y:0},{x:440,y:0},{x:480,y:0},{x:520,y:0},{x:560,y:0},{x:600,y:0},{x:640,y:0},{x:680,y:0},{x:720,y:0},{x:760,y:0}],
    //画出敌人
    draw : function(){
        //冰冻中,画出冰冻图
        if(this.frozenTime > 0){
            Canvas.drawImg(this.cxt,this.img,this.enemyMap[this.type].x,this.enemyMap[this.type].y+40,this.width,this.height,this.x,this.y,this.width,this.height);
        }
        //画出正常图
        else Canvas.drawImg(this.cxt,this.img,this.enemyMap[this.type].x,this.enemyMap[this.type].y,this.width,this.height,this.x,this.y,this.width,this.height);
        //计算血量百分比
        var persen = Math.floor(this.life / this.maxLife * 100) / 2;
        //画出血量
        Canvas.fillRect(this.cxt,this.x-5,this.y-5,persen,3,"rgba(38,223,116,0.8)");
    },
    //更新敌人信息
    update : function(){
        //超出坐标
        if(this.x >= 500){
            return false;
        }
        var xIndex = parseInt(this.x / 50,10),//1
            yIndex = parseInt(this.y / 50,10);//0
        //判断是否有下个移动位置信息,或者下哥移动位置信息是否已经走到了
        if(!this.nextPosition ||
            ((this.x >= this.nextPosition.x - 5 && this.x <= this.nextPosition.x)
                && (this.y >= this.nextPosition.y - 5 && this.y <= this.nextPosition.y))
        ){
            //走到最右侧
            if(xIndex + 1 >= 10){
                xIndex = -1;
            }
            else{
                //判断往下能否走
                if(MapData[xIndex][yIndex+1] && !this.hadWalk[xIndex+"_"+(yIndex+1)]){
                    this.dir = "down";
                    yIndex += 1;
                }
                //判断往右能否走
                else if(MapData[xIndex+1][yIndex]  && !this.hadWalk[(xIndex+1)+"_"+yIndex]){
                    this.dir = "right";
                    xIndex += 1;
                }
                else if(MapData[xIndex][yIndex-1] && !this.hadWalk[xIndex+"_"+(yIndex-1)]){
                    this.dir = "up";
                    yIndex -= 1;
                }
                else if(MapData[xIndex-1][yIndex] && !this.hadWalk[(xIndex-1)+"_"+yIndex]){
                    this.dir = "left";
                    xIndex -= 1;
                }
            }
            //是否走到最右侧
            if(xIndex == -1){
                this.nextPosition = {x:500,y:yIndex*50+5};
            }
            //设置下个移动位置
            else {
                this.nextPosition = {x:xIndex*50+5,y:yIndex*50+5};
                //记录已经走过的位置
                this.hadWalk[xIndex+"_"+yIndex] = true;
            }
        }
        //移动
        switch(this.dir){
            case "down":
                this.y += this.sp;
                break;
            case "up":
                this.y -= this.sp;
                break;
            case "left":
                this.x -= this.sp;
                break;
            case "right":
                this.x += this.sp;
                break;
            default:
                break;
        }
    }
}
//更新所有敌人信息
function updateEnemy(){
    var enemy;
    for(var i=0,l=Game.enemyList.length;i<l;i++){
        enemy = Game.enemyList[i];
        if(!enemy)continue;
        enemy.update();
    }
}
//画出所有敌人
function drawEnemy(){
    var enemy;
    for(var i=0,l=Game.enemyList.length;i<l;i++){
        enemy = Game.enemyList[i];
        if(!enemy)continue;
        enemy.draw();
    }
}


game.js修改


image.png


每50次循环出一个敌人


image.png

image.png

tool.js新增


image.png


自动寻路算法解析


坐标系

向右为x轴

向下为y轴

整个地图为500*500

// 游戏对象
        var hero = {
            speed: 2, // 每秒移动的像素
            x: 55,
            y: 0,
            srcx:120,
            srcy:40,
           flood:50,
        //移动的方向
        dir:null,
        //下个移动位置
        nextPosition: null,
        //记录已经走过的位置
        hadWalk:{}
        };

英雄像素转换为网格(10*10)坐标

var xIndex = parseInt(hero.y / 50, 10),
 yIndex = parseInt(hero.x / 50, 10);

最开始时,

xIndex = 1

yIndex = 0

初始时满足!hero.nextPosition

if (!hero.nextPosition ||
                 ((hero.x >= hero.nextPosition.x - 5 && hero.x <= hero.nextPosition.x) && (hero.y >= hero.nextPosition.y - 5 && hero.y <= hero.nextPosition.y))
             )

进入后,满足往下走

else {
                //判断往下能否走
                if(MapData[xIndex][yIndex+1] && !this.hadWalk[xIndex+"_"+(yIndex+1)]){
                    this.dir = "down";
                    yIndex += 1;
                }

此时

xIndex = 1

yIndex = 1

然后,

//设置下个移动位置
                 else {
                     hero.nextPosition = {y: xIndex * 50 + 5, x: yIndex * 50 + 5};
                     //记录已经走过的位置
                     hero.hadWalk[xIndex + "_" + yIndex] = true;
                 }

hero.nextPosition = {

x:55,y:55

}

然后,

//移动
            switch(hero.dir){
                case "down":
                    hero.y += hero.speed;
                    break;
                case "up":
                    hero.y -= hero.speed;
                    break;
                case "left":
                    hero.x -= hero.speed;
                    break;
                case "right":
                    hero.x += hero.speed;
                    break;
                default:
                    break;
            }

此时,

hero = {
           speed: 2, // 每秒移动的像素
           x: 2,
           y: 55,

循环回去:

然后在hero.x未满足条件:

if (!hero.nextPosition ||
                ((hero.x >= hero.nextPosition.x - 5 && hero.x <= hero.nextPosition.x) && (hero.y >= hero.nextPosition.y - 5 && hero.y <= hero.nextPosition.y))
            )

时,有25次(50/2),跳过上面条件里的代码,直接执行以下代码:

//移动
            switch(hero.dir){
                case "down":
                    hero.y += hero.speed;
                    break;
                case "up":
                    hero.y -= hero.speed;
                    break;
                case "left":
                    hero.x -= hero.speed;
                    break;
                case "right":
                    hero.x += hero.speed;
                    break;
                default:
                    break;
            }

当再次hero.x满足条件:

if (!hero.nextPosition ||
                ((hero.x >= hero.nextPosition.x - 5 && hero.x <= hero.nextPosition.x) && (hero.y >= hero.nextPosition.y - 5 && hero.y <= hero.nextPosition.y))
            )


时,重复之前的逻辑。

如此循环。

such that


项目源码:


项目源码

目录
相关文章
|
7天前
|
JavaScript 前端开发 测试技术
探索现代JavaScript开发的最佳实践
本文探讨了现代JavaScript开发中的最佳实践,涵盖ES6+特性、现代框架使用、模块化与代码分割、测试驱动开发、代码质量与性能优化、异步编程、SPA与MPA架构选择、服务端渲染和静态站点生成等内容,旨在帮助开发者提升代码质量和开发效率。
|
10天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
【10月更文挑战第36天】本文将引导您探索Node.js的世界,通过实际案例揭示其背后的原理和实践方法。从基础的安装到高级的异步处理,我们将一起构建一个简单的后端服务,并讨论如何优化性能。无论您是新手还是有经验的开发者,这篇文章都将为您提供新的视角和深入的理解。
|
15天前
|
Web App开发 存储 JavaScript
深入浅出Node.js后端开发
【10月更文挑战第31天】本文将引导你进入Node.js的奇妙世界,探索其如何革新后端开发。通过浅显易懂的语言和实际代码示例,我们将一起学习Node.js的核心概念、搭建开发环境,以及实现一个简单但完整的Web应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇通往高效后端开发的大门。
|
12天前
|
运维 监控 JavaScript
鸿蒙next版开发:分析JS Crash(进程崩溃)
在HarmonyOS 5.0中,JS Crash指未处理的JavaScript异常导致应用意外退出。本文详细介绍如何分析JS Crash,包括异常捕获、日志分析和典型案例,帮助开发者定位问题、修复错误,提升应用稳定性。通过DevEco Studio收集日志,结合HiChecker工具,有效解决JS Crash问题。
29 4
|
13天前
|
Web App开发 JavaScript 前端开发
探索后端开发:Node.js与Express的完美结合
【10月更文挑战第33天】本文将带领读者深入了解Node.js和Express的强强联手,通过实际案例揭示它们如何简化后端开发流程,提升应用性能。我们将一起探索这两个技术的核心概念、优势以及它们如何共同作用于现代Web开发中。准备好,让我们一起开启这场技术之旅!
29 0
|
JavaScript 前端开发 测试技术
测试驱动javascript开发 -- 1.单元测试
  从今天开始,我将以读书笔记的方式向大家介绍《Test-Driven JavaScript Development》相关内容。我不太清楚这本书是否已经有了中文的译本,有兴趣的朋友可以去搜索下,或者直接读英文原版。
904 0
|
JavaScript 前端开发 测试技术
测试驱动javascript开发 -- 2.单元测试一例:学习断言、测试用例函数的编写
  本篇我们将通过对Date.strftime编写单元测试的例子,学会断言、测试用例函数的相关知识。   首先我们先来看Date.strftime的实现代码。 Date.prototype.strftime = (function () {   function strftime(form...
869 0
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
97 2
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
123 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
82 4