猫史档案馆


关于神岛写敌对NPC的那些事

用户:迷失冷光迷失冷光查看:6 回复:13 评论:6 创建时间:2023-08-13T16:48:30


想一想神岛做怪物的发展史怎么样?)可能遗漏

最开始官方出的年兽教程算随机移动的

然后出了个僵尸追杀玩家()

所以现在大部分地图的怪物啊都是类似于这种僵尸的()

缺点很明显()

会被玩家卡墙壁输出。

然后就是动作很奇怪。

对于这个问题有很多可优化的地方。

作为一个两年半的擅长做怪物的代码师(现在不是)

有几点建议

————————————————————

首先啊

我们知道神岛的一个特性()

模型的旋转是会影响模型的碰撞箱的(这个特性同样影响cameraEntity下的玩家驾驶模型载具)

不知道可以做个试验

在地图里放个模型,点一下模型看看模型边框大小

再旋转一定角度看大小

旋转了碰撞箱会变大

那么就会出现个问题

假设有一个碰撞箱大小为1*2*1的怪

然后玩家把这只怪物引到一个一个宽两个高的动力,再转个弯

这时候怪物也会转一定角度

那么怪物碰撞箱会膨胀一些

就有可能卡到地下

所以就出现了一个解决方法。

创建怪物的时候先创建一个怪物的碰撞箱(可碰撞,可推动)

然后再创建怪物模型(不可碰撞,可推动)

每帧把怪物模型的位置设为碰撞箱位置

具体做法:

//设bounds为碰撞箱模型
//entity为怪物实体模型

//以下代码每帧执行:
if(bounds.position.distance(entity.position) > 0.1/*校正距离*/){
    entity.position.copy(bounds.position);
}
entity.velocity.copy(bounds.velocity);

这样我们就解决了第一个碰撞箱问题。

————————————————————

第二,一个模型的动作会十分僵硬

如果在旧版神岛

可以用多个模型做怪物

参考帖子:https://shequ.codemao.cn/community/535888

如果在新版神岛

那必须用motion

可以把怪物的默认动作设为走路或者待机

然后攻击时专门调用攻击动作

不会的看API:https://box3.yuque.com/org-wiki-box3-ev7rl4/guide/ghyi06of3nb89tyq

————————————————————

第三,就是转向问题

比如怪锁定了面前的一个玩家

然后又锁定了后面的一个玩家

那么就会180°转过去(你是水滴吗)

那么,我们要加一个缓转

首先在代码加上函数

const PI2 = Math.PI * 2;
function rotate(entity){
    let angle1 = entity.direction;
    if(angle1 < 0){
        angle1 = PI2 + angle1;
    }
    if(entity.angle < 0){
        entity.angle = PI2 + entity.angle;
    }    
    entity.angle %= PI2;
    const angle2 = entity.angle;
    if(Math.min(Math.abs(angle1 - angle2),Math.abs(PI2 - angle1 + angle2)) < 0.1){
        entity.angle = entity.direction;
        return;
    }else{
        if(angle1 > angle2){
            if(angle1 - angle2 < Math.PI){
                entity.angle += 0.1;
                return 0;
            }else{
                entity.angle -= 0.1;
                return 1;
            }
        }else{
            if(angle1 + (PI2 - angle2) < Math.PI){
                entity.angle += 0.1;
                return 0;
            }else{
                entity.angle -= 0.1;
                return 1;
            }
        }
    }
}

然后,在怪物初始化的时候定义点变量:

//entity为怪物(或者碰撞箱)
entity.direction = 0;//初始角度
entity.angle = 0;//初始角度

最后,怪物每帧执行:

//entity为怪物(碰撞箱)
entity.direction = 追踪对象的角度(这个必须根据自己代码上下文写)
rotate(entity);
//这里我们会得到entity.angle就是怪物应该面向的角度

————————————————————

第四,怪物不能被玩家放风筝,所以大部分怪物要有远程攻击技能

这个远程特效一大堆在这就不详细解释了

专门近战怪忽略这一条

————————————————————

第五,智慧的怪物要学会避障,不被玩家卡墙

所以需要寻路算法

不要以为寻路算法很卡()

写得好就不卡()写的不好才卡)

以前我尝试过求最优解(异界之岛旧版本巡逻机甲(反正你们不知道))

不过有(fei)点(chang)慢

现在推荐一下适合神岛怪物体质的寻路算法(A*)

这个代码是冷光的后花园里抄出来的

搜索一栋楼不要0.1秒(当然也不会这么搜)

这里写的是1*2*1碰撞箱的怪物

其他体型的把empty函数改一下就行了

我们先假设这个怪物可以上一格台阶

可以下一格台阶

可以前后左右走

也可以沿方块对角线走

(当然与玩家之间没有障碍物想怎么走就怎么走)

就水出了这个代码

const direction1 = [
    [1,0,0,10/*权*/],
    [0,0,1,10],
    [-1,0,0,10],
    [0,0,-1,10],
]
const direction2 = [
    [1,0,1,14],
    [1,0,-1,14],
    [-1,0,1,14],
    [-1,0,-1,14],
]
const direction3 = [
    [1,-1,0,80/*讨厌上楼梯*/],//权可以自己改
    [0,-1,1,80],
    [-1,-1,0,80],
    [0,-1,-1,80],
    [1,1,0,80],
    [0,1,1,80],
    [-1,1,0,80],
    [0,-1,1,80],
]
function PriorityQueue() {
    function QueueElement(element, priority) {
        this.element = element;
        this.priority = priority;
    }

    //属性
    this.items = [];

    //方法
    //1.实现插入方法
    PriorityQueue.prototype.put = function (element, priority) {
        //创建PriorityQueue对象
        var priorityQueue = new QueueElement(element, priority)

        //判断队列是否为空
        if (this.items.length == 0) { 
            this.items.push(priorityQueue)
        } else {
            var added = false
            for (var i = 0; i < this.items.length; i++) {
                if (priorityQueue.priority < this.items[i].priority) {
                    this.items.splice(i, 0, priorityQueue)
                    added = true
                    break
                }
            }
            if (!added) {
                this.items.push(priorityQueue)
            }
        }
    }

    PriorityQueue.prototype.pop = function () {
        return (this.items.pop()).element;
    }

    //2.删除元素
    PriorityQueue.prototype.shift = function () {
        return (this.items.shift()).element;
    }

    //3.查看元素
    PriorityQueue.prototype.front = function () {
        return this.items[0];
    }

    //4.查看是否为空
    PriorityQueue.prototype.isEmpty = function () {
        return this.items.length == 0
    }

    //5.查看元素的个数
    PriorityQueue.prototype.size = function () {
        return this.items.length
    }

    //6.toSting方法
    PriorityQueue.prototype.toString = function () {
        let str = ""
        for (let i = 0; i < this.items.length; i++) {
            str += this.items[i].element + ":" + this.items[i].priority + '\xa0\xa0\xa0'
        }
        return str
    }
}//懒得写,csdn找的

function getRoad(a,b){
    const startTime = Number(new Date());
    const pA = [Math.floor(a.x),Math.floor(a.y),Math.floor(a.z)];
    const pB = [Math.floor(b.x),Math.floor(b.y),Math.floor(b.z)];
    for(let y=pA[1];y>0;y--){
        if(blocked(pA[0],y - 1,pA[2])){
            pA[1] = y;
            break;
        }
    }
    for(let y=pB[1];y>0;y--){
        if(blocked(pB[0],y - 1,pB[2])){
            pB[1] = y;
            break;
        }
    }
    const edge = [
        [0,0,0],
        [62,128,62],
    ]//边界
    const frontier = new PriorityQueue();
    frontier.put(pA,0);
    const came_from = {};
    const cost_so_far = {};
    came_from[getId(pA)] = null;
    cost_so_far[getId(pA)] = 0;
    var success = false;
    while(!frontier.isEmpty()){
        const current = frontier.shift();
        if(current[0] == pB[0] && current[1] == pB[1] && current[2] == pB[2]){
            success = true;
            break;
        }
        const direction = [...direction1];
        const floor = blocked(current[0],current[1] - 1,current[2]);
        if(floor){
            for(const e of direction2){
                if(empty(current[0] + e[0],current[1],current[2]) && empty(current[0],current[1],current[2] + e[1]) && blocked(current[0] + e[0],current[1] - 1,current[2]) && blocked(current[0],current[1] - 1,current[2] + e[1])){
                    direction.push(e);
                }
            }
            for(const e of direction3){
                if(e[1] == -1 || blocked(current[0] + e[0],current[1],current[2] + e[2])){
                    direction.push(e);
                }
            }
        }
        for(const e of direction){
            const next = [current[0] + e[0],current[1] + e[1],current[2] + e[2]];
            const new_cost = cost_so_far[getId(current)] + e[3];
            const id = getId(next);
            if(!empty(...next) || !blocked(next[0],next[1] - 1,next[2]) || edge[0][0] == next[0] || edge[1][0] == next[0] || edge[0][1] == next[1] || edge[1][1] == next[1] || edge[0][2] == next[2] || edge[1][2] == next[2])continue;
            const cost = cost_so_far[id];
            if(cost === undefined || new_cost < cost){
                cost_so_far[id] = new_cost;
                const h = Math.abs(pB[0] - next[0]) * 10 + Math.abs(pB[1] - next[1]) * 200 + Math.abs(pB[2] - next[2]) * 10;
                frontier.put(next,new_cost + h);
                came_from[id] = current;
            }
        }
    }
    const result = [];
    function print(p){
        if(!p)return;
        result.unshift(p);
        print(came_from[getId(p)]);
    }
    console.log('寻路用时:' + (Number(new Date()) - startTime) + 'ms,结果:' + (success? '成功':'失败'));
    if(!success)return;
    print(pB);
    return result;
}
function getId(p){
    return `${p[0]},${p[1]},${p[2]}`;
}
function empty(x,y,z){
    for(let i=0;i<2;i++){
        if(blocked(x,y + i,z))return;
    }
    return true;
}
function blocked(x,y,z){
    return voxels.getVoxel(x,y,z);//如果怪能涉水把这个改掉
}

(上面的那个edge是边界自己改)

这个函数返回从a位置到b位置的路径

然后下面的可以当成伪代码()

//entity为怪物或碰撞箱,target为追击的实体

//(初始化)
entity.followState = 0;//0为直线追击,1为寻路
entity.path = [];

//(每帧执行)
if(无阻隔){
    entity.followState = 0;
}else{
    entity.followState = 1;
}
if(entity.followState){
    //用不着我多讲了,懂得都懂
}else{
    if(!entity.path.length || 太久没寻路){
        entity.path = getRoad(entity.position,target.position);
    }
    这里把实体经过的path都shift掉
    //(不会有人不会Array.shift()吧)
    const targetPosition = new GameVector3/*或者Box3Vector3*/(...entity.path[0]).add({x: 0.5,y: 0.5,z: 0.5});//方块与模型的差距就是三个0.5()
    这里实体追击targetPosition
}

这里跳跃我没写,要写的自己写啊()

这样寻路也差不多了

————————————————————

最后说一下,上面的代码全是没测试过的(确信)

有bug评论区说,自己改掉


回复

上一页1 页 / 共 1下一页
迷失冷光迷失冷光

幸好,排版还在()

点赞0


评论


145a145a

w,nb

点赞0


评论


囧仙_official囧仙_official

ah that's good

最短路建议跑SPFA(确信

(注:这是开玩笑,网格图跑spfa是最慢的)

点赞1


评论


xwstsrhxwstsrh

顶上去让吉看看

点赞0


评论


yee剑走天下yee剑走天下

看不懂()但是很nb()

点赞0


评论


全能代码师全能代码师

()()@迷失 混沌星海pro做不做了()还有武器pro能带我个吗()还有你那个异界之岛怎么玩()

点赞1


评论


神奇代码岛func王清林神奇代码岛func王清林

沙发

点赞1


评论


无敌版滑稽战神无敌版滑稽战神

泰裤辣()

点赞0


评论


Dreamland_AuroraDreamland_Aurora

看不懂但是大受震撼()

点赞0


评论


PoiresPoires

这代码,让我看的瑟瑟发抖

点赞0


评论


屑怪盗屑怪盗

收藏

点赞0


评论


韵律源点Arcaea韵律源点Arcaea

笑点解析:从csdn找的

点赞1


评论


Dark_forestDark_forest

冷光的破鸣做不做了()

点赞0


评论