用户:
LWKevin0wvf小号查看:4 回复:9 评论:4 创建时间:2024-05-06T22:18:36
射线检测判断更合理,不会穿墙看到玩家,能够自动跳跃(Demo版自动跳跃,有时检测不灵)
开源协议:MPL-2.0 https://opensource.org/license/mpl-2-0 https://www.mozilla.org/en-US/MPL/2.0/
请遵循开源协议,严禁侵权。
对源代码进行修改后的代码必须以同样或相似的开源协议开源。
monsters.js文件(文件名改为monsters)
const MeshScale = new GameVector3(1 / 16, 1 / 16, 1 / 16);
const Quat = new GameQuaternion(0, 0, 0, 1);
module.exports = {
normalZombie: {
/**
* @description 生成
* @param {number} x x坐标
* @param {number} y y坐标
* @param {number} z z坐标
* @param {GameVector3} scale 大小
*/
spawn(x, y, z, scale = MeshScale) {
var e = world.createEntity({
id: "normalZombie",
position: new GameVector3(x, y, z),
collides: true,
gravity: true,
mesh: "mesh/normalZombie.vb",
meshScale: scale
});
e.addTag("monster");
Object.assign(e, {
sightingDistance: 32,//视野距离
speed: 0.5,//速度
jumpPower: 1,//跳跃高度
funcs: [],
//每隔500毫秒(0.5秒)执行函数
interval: setInterval(async () => {
for (var func of e.funcs) func(e);//执行函数传入丧尸实体参数e
}, 100),
onInterval(...funcs) {
for (var func of funcs) this.funcs.push(func);
}
});
e.onDie(() => {
clearInterval(e.interval);//结束间隔执行函数
e.destroy();
});
//用setInterval仅仅是因为我不想用onTick)))
e.onInterval((e) => {
//寻找target(不能穿墙看到玩家)
this.findTarget(e);//寻找目标玩家实体
if (e.isPlayer) console.log(e.player.name)
if (e.target && !e.target.destroyed && e.target.hp) {
this.move(e, e.target.position);//移动一步(?)
//如果靠近玩家进行攻击(播放攻击动画、扣防御值/血机制,玩家受伤后玩家要执行的代码写在index里的entity.onTakeDamage里)
}
});
return e;
},
/**
* @description 移动,调用一次只移动一步
* @param {GameEntity} e 丧尸实体
* @param {GameVector3} targetPosition 目标位置
*/
move(e, targetPosition) {
var direction = targetPosition.sub(e.position);
var dist = direction.mag();
e.velocity.x = direction.x * e.speed / dist;
e.velocity.z = direction.z * e.speed / dist;
//自动上楼梯机制(有时不灵敏)
//先用射线判断前面1格距离里有没有方块,再判断上一格有没有固体方块,如果能跳上去就跳跃。(跳跃高度默认设定1格,这里有点乱,jumpPower是跳跃力度,不是跳跃高度,但是我没有跳跃高度计算公式。)
//onVoxelContact不好用
//发射一条起点在腿位置的射线
//new GameVector3(e.velocity.x, 0, e.velocity.y)
var raycast = world.raycast(e.position.add(new GameVector3(0, -0.5, 0)), getVectorByOrientation(e.meshOrientation), {
maxDistance: 2,//最大距离
ignoreFluid: true,//忽略液体
ignoreSelector: ".monster"//忽略怪物
});
//如果击中方块 或 (击中实体 且 实体不是玩家 且 实体开碰撞了 且 实体能跳上去)
if (raycast.hitVoxel ||
(raycast.hitEntity && !raycast.hitEntity.isPlayer && raycast.hitEntity.collides && (
(raycast.hitEntity.position.y + (raycast.hitEntity.bounds.y / 2)
- e.position.y) <= e.jumpPower))) {
console.log(1);
//再发射一条起点在眼睛位置的射线
var raycast2 = world.raycast(e.position.add(new GameVector3(0, 0.5, 0)), getVectorByOrientation(e.meshOrientation), {
maxDistance: 2,//最大距离
ignoreFluid: true,//忽略液体
ignoreSelector: "*"//忽略怪物
});
//如果没击中方块 或 (击中的实体 是玩家 或 没开碰撞),就可以跳跃
if (!raycast2.hitVoxel ||
(raycast2.hitEntity && (raycast2.hitEntity.isPlayer || !raycast2.hitEntity.collides)))
e.velocity.y = e.jumpPower;
}
var orientation = Quat.rotateY(Math.atan2(e.velocity.z, e.velocity.x));
e.meshOrientation.copy(orientation);
},
/**
* @description 寻找目标
* @param {GameEntity} e 丧尸实体
* @returns {GamePlayerEntity} 目标玩家实体
*/
findTarget(e) {
/*
1.不能穿墙看到玩家(能穿墙就直接找distance最小,不用射线)
2.超过视线距离就看不到
如果找不到就把e.target设为undefined
功能:给e.target赋值为目标玩家实体并返回e.target
*/
//擂台法找最近且能看到
e.target = undefined;//先赋值为undefined,防止找不到时返回之前的已经看不到的目标。
for (var player of world.querySelectorAll("player")) {
var distance = player.position.distance(e.position);
//如果丧尸还没有目标实体 或 距离比喵来目标实体的距离更近
if ((!e.target || distance < e.target.position.distance(e.position))) {
//判断能否看到(先求出方向,再发喵线)
var direction = player.position.sub(e.position);
//射线起点要稍高于丧尸位置才是丧尸眼睛位置
var raycast = world.raycast(e.position.add(new GameVector3(0, 0.5, 0)), direction, {
maxDistance: e.sightingDistance,//视野距离
ignoreFluid: true,//忽略液体
ignoreSelector: ".monster"//忽略其他怪物
});
if (raycast.hitEntity === player) e.target = player;//找到目标!!
}
}
return e.target;
}
}
}
/**
* @description 把四元数转成旋转向量
* @param {GameQuaternion} orientation 四元数
* @returns {GameVector3} 旋转向量
* 四元数与旋转向量的转化 见https://www.cnblogs.com/narjaja/p/16358263.html
*/
function getVectorByOrientation(orientation) {
var theta = 2 * Math.acos(orientation.w);
return new GameVector3(
orientation.x / Math.sin(theta * 0.5) * theta,
orientation.y / Math.sin(theta * 0.5) * theta,
orientation.z / Math.sin(theta * 0.5) * theta);
}
index.js文件
"use strict";
console.clear();
const { normalZombie } = require("./monsters.js");
normalZombie.spawn(0,11,0);//在坐标{x:0,y:11,z:0}的地方生成普通僵尸