用户:
迷失冷光查看: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评论区说,自己改掉