CocosCreator塔防

塔防(TowerDefence)

场景搭建

设置CocosCreator布局为经典布局

规范项目资源目录结构

目录描述
scene场景目录
script脚本目录
texture纹理目录
anim动画片段

保存当前场景到scene目录下并命名为game.fire

将游戏资源拖拽到texture目录下

  • 搭建游戏场景
  • 游戏场景中创建路径导航

将地图纹理拖拽到Canvas渲染节点上释放后会在画布节点下生成新的地图节点,修改画布节点的设计分辨率为地图自身的宽高。

创建地图

设置主角hero节点,在其下创建单色渲染精灵节点,并命名为blood血条,调整血条的背景色为红色。设置blood`血条的宽高尺寸为30x3。

在blodd节点下创建子节点,即选择创建节点中的创建渲染下选中Sprite单色节点,同时命名为value。设置value节点中Sprite属性中Type为FIELD,同时设置Fill Range为1。

制作敌人

制作敌人与血条

制作角色与血条.gif

制作路径

使用动画编辑器创建补间动画完成路径的绘制

制作路径.gif
补间动画

使用自定义的组件类从补间动画中获取所有节点的坐标

贝塞尔曲线

贝塞尔曲线是应用于2D图形的数学曲线,该曲线的定义由4个点组成,分别是起始点p0,终止点p3(又称为锚点),以及两个相互分离的中间点控制点p1和p2。

  • p0 起始点
  • p1 中间控制点1
  • p2 中间控制点2
  • p3 终止点
贝塞尔曲线

创建path.js用户脚本组件,用于将路径动画片段中的贝塞尔曲线转化为坐标点。

$ vim /assets/script/path.js
cc.Class({
    extends: cc.Component,
    properties: {
        debug:true
    },
    onLoad () {
        //获取当前节点上的动画组件
        this.anim = this.node.getComponent(cc.Animation);
        //获取动画片段
        const clips = this.anim.getClips();
        //获取当前片段
        const clip = clips[0];

        //获取贝塞尔曲线路径
        this.pathdata = [];
        const paths = clip.curveData.paths;
        for(let k in paths){
            const frames = paths[k].props.position;
            this.genPathData(frames);
        }

        //根据调试模式绘制节点
        if(this.debug){
            //添加绘图组件
            this.graphics = this.node.addComponent(cc.Graphics);
            this.graphics.fillColor = cc.color(255, 0, 0, 255);
            this.graphic();
        }
    },
    graphic(){
        this.graphics.clear();
        for(let i=0; i<this.pathdata.length; i++){
            const path = this.pathdata[i];
            for(let j=0; j<path.length; j++){
                const node = path[j];
                this.graphics.moveTo(node.x - 1, node.y + 1);
                this.graphics.lineTo(node.x -1, node.y - 1);
                this.graphics.lineTo(node.x + 1, node.y - 1);
                this.graphics.lineTo(node.x + 1, node.y + 1);
                this.graphics.close();//封闭路径
            }
        }
        this.graphics.fill();//填充
    },
    genPathData(frames){
        let bezier = [];
        //贝塞尔曲线定义四个点:起点、终点、两个相互分离中间控制点点 [begin, ctrl1, ctrl2, end]
        let begin = null, ctrl1 = null, ctrl2 = null, end = null;
        //遍历关键帧获取贝塞尔曲线
        for(let i=0; i<frames.length; i++){
            const frame = frames[i];
            //获取控制起点
            if(ctrl1 !== null){
                bezier.push([begin, ctrl1, ctrl1, cc.v2(frame.value[0], frame.value[1])]);
            }
            //获取起点坐标
            begin = cc.v2(frame.value[0], frame.value[1]);
            //获取关键帧的运动路径
            const motionPath = frame.motionPath;
            //遍历关键帧运动路径获取贝塞尔曲线的四个点
            for(let j=0; j<motionPath.length; j++){
                const item = motionPath[j];
                //获取终点
                end = cc.v2(item[0], item[1]);
                //获取控制终点
                ctrl2 = cc.v2(item[2], item[3]);
                //判断控制起点是否为空
                if(ctrl1 === null){
                    ctrl1 = ctrl2;
                }
                //添加贝塞尔曲线中各点坐标
                bezier.push([begin, ctrl1, ctrl2, end]);
                //获取控制起点
                ctrl1 = cc.v2(item[4], item[5]);
                //设置下一个节点的起点为上一个节点的终点
                begin = end;
            }
        }
        //将贝塞尔曲线转换为坐标点
        const first = bezier[0][0];//获取起始点坐标
        let data = [first];
        for(let k=0; k<bezier.length; k++){
            const item = bezier[k];
            begin = item[0];
            ctrl1 = item[1];
            ctrl2 = item[2];
            end = item[3];
            //使用微元法获取曲线长度
            const length = this.getBezierLength(begin, ctrl1, ctrl2, end);
            //将每一段贝塞尔曲线转化为16个坐标点
            const count = Math.floor(length / 16);
            const delta = 1 / count;
            let t = delta;
            for(let m=0; m<count; m++){
                const x = begin.x * (1 - t) * (1 - t) * (1 - t) + 3 * ctrl1.x * t * (1 - t) * (1 - t) + 3 * ctrl2.x * t * t * (1 - t) + end.x * t * t * t;
                const y = begin.y * (1 - t) * (1 - t) * (1 - t) + 3 * ctrl1.y * t * (1 - t) * (1 - t) + 3 * ctrl2.y * t * t * (1 - t) + end.y * t * t * t;
                data.push(cc.v2(x, y));
                t += delta;
            }
        }
        this.pathdata.push(data);
    },
    //获取贝塞尔曲线节点 t [0, 1] t 分成20等分 1 / 20 = 0.05
    getBezierLength(begin, ctrl1, ctrl2, end) {
        const count = 20;
        const delta = 1 / count;
        //贝塞尔插值
        let length = 0;
        let prev = begin;
        let t = delta;
        for (let i = 0; i < count; i++) {
            let x = begin.x * (1 - t) * (1 - t) * (1 - t) + 3 * ctrl1.x * t * (1 - t) * (1 - t) + 3 * ctrl2.x * t * t * (1 - t) + end.x * t * t * t;
            let y = begin.y * (1 - t) * (1 - t) * (1 - t) + 3 * ctrl1.y * t * (1 - t) * (1 - t) + 3 * ctrl2.y * t * t * (1 - t) + end.y * t * t * t;
            let point = cc.v2(x, y);
            
            let dir = point.sub(prev);
            prev = point;
            length += dir.mag();
            
            t += delta;
        }
        
        return length;
    },
    getPathData(){
        return this.pathdata;
    }
});

path路径节点添加自定义的用户脚本组件path.js

设置脚本组件

预览观察调试模式下路径

路径

敌人导航

控制敌人沿着路径移动

$ vim assts/script/enemy.js
/**控制敌人按照路径导航移动 */
const Path = require("./path");
cc.Class({
    extends: cc.Component,
    properties: {
        path:{type:Path, default:null, tooltip:"路径组件"},
    },
    pause(){
        this.walking = false;
    },
    reset(){
        this.walking = true;
    },
    //读取配置文件获取初始化变量
    initConfig(config){
        this.config = config;
        this.speed = config.speed;
        this.index = config.index;//路径索引
    },
    start(){
        this.walking = false;
        //读取配置初始化参数
        this.initConfig({speed:200, min_blood:100, max_blood:1000, index:0});
        //生成路径节点坐标
        this.getPath(this.index);
        //设置敌人验证路径节点移动
        this.walk();
        //行走中的暂停,5秒内走2秒暂停3秒
        this.scheduleOnce(this.pause.bind(this), 3);//3秒后暂停
        this.scheduleOnce(this.reset.bind(this), 5);//移动5秒
    },
    //生成路径节点
    getPath(index){
        //获取目标路径节点的集合
        const pathdata = this.path.getPathData();
        if(index < 0 || index >= pathdata.length){
            return;
        }
        this.pathnode = pathdata[index];
        //cc.log("pathnode", this.pathnode);
    },
    //设置敌人验证路径节点移动
    walk(){
        if(this.pathnode.length < 2){
            return;
        }
        //设置敌人移动到起点位置
        this.node.setPosition(this.pathnode[0].x, this.pathnode[0].y);
        //记录下一步
        this.next = 1;
        //从当前点移动到下一点
        this.walkToNext();
    },
    //从当前点移动到下一步
    walkToNext(){
        //是否走到最后一点
        if(this.next > this.pathnode.length){
            this.walking = false;
            return;
        }
        //获取起点和终点
        const src = this.node.getPosition();
        const dst = this.pathnode[this.next];
        if(dst == undefined){
            return;
        }
        //获取起始点和终止点之间的方向向量
        const dir = dst.sub(src);
        //计算向量长度,取模
        const distance = dir.mag();
        //判断长度是否超出
        if(distance <= 0){
            this.next ++;
            this.walkToNext();//跳入下一步
            return;
        }
        //将速度分解
        this.vx = this.speed * dir.x / distance;
        this.vy = this.speed * dir.y / distance;
        //计算从起点移动到终点所需要耗费的时间长度
        this.walktime = distance / this.speed;

        //定义定时器用于判断行走过程中的累加时间是否大于总消耗时间
        this.timer = 0;

        //设置行走状态
        this.walking = true;
    },
    //敌人移动 dt是每次更新的时间间隔
    update(dt){
        if(!this.walking){
            return;
        }
        //累加时间
        this.timer += dt;
        //重新设置当前节点的坐标
        if(this.timer >= this.walktime){
            dt -= (this.timer - this.walktime);
        }
        this.node.x += this.vx * dt;
        this.node.y += this.vy * dt;
        //判断计时器时间进入下一步
        if(this.timer >= this.walktime){
            this.next ++;
            this.walkToNext();
        }
    }
});
添加脚本组件
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值