1. 第一人称射击
游戏开发对地图框架的性能是一种考验,也是最好的测试。游戏具有极高的实时性,任何卡顿在游戏中将会暴露无遗。
three-tile的地图支持全球海量地形,所以它不能像常规游戏那样在启动时进行数据预加载和预处理,所有地图数据都是实时加载的。在鼠标的移动中,地图视角会以极快速度切换,大量地图瓦片实时下载、解析、渲染,十分考验框架性能,如果游戏能流畅地在地图中运行,那框架性能就足以应付绝大多数应用了。编写
three-tile最初的目标其实是开发游戏,地图只能算是three-tile的一个扩展,但目前地图的应用场景远多于游戏,所以现在框架的重点已经偏向于地图开发了。
1. 更换控制器
第一人称 FPS 游戏以玩家视角渲染地图,模拟玩家看到的真实世界,与常规地图相比,仅是使用的控制器不同。
为了方便用户使用,three-tile 对第一人称场景进行了封装:PLViewer,使用PLViewer初始化场景即可。
three-tile 的地图不依赖 camera、controls,所以可以任意更换控制器,第一人称视角,只需要将控制器更换为 threejs 的 PointerLockControls:
https://threejs.org/docs/?q=controls#examples/zh/controls/PointerLockControls
修改 GLViewer 将控制器换成 PointerLockControls,可参考:
https://threejs.org/examples/?q=controls#misc_controls_pointerlock
TIP
计划下个版本增加使用 PointerLockControls 控制器的 Viewer (V0.11.4 已内置)。
2. 射击
使用 PointerLockControls 控制器,就能有一个第一人称视角的三维地图,只需要在鼠标单击时创建一个球飞出去就行了,不到 100 行代码。
监听地图容器的 click 事件,当鼠标单击地图时:
1. 创建一个球体作为炮弹加入地图
2. 球的起点为摄像机坐标
3. 球的终点为屏幕中点(准星处)的地面坐标
4. 调用 Tween 动画,将球从起点动画移动到终点
/**
* 第一人称射击初始化
* @param viewer
* @param map
* @param onfire 点击开火回调
*/
export const fireInit = (viewer: PLViewer, map: tt.TileMap, onfire: () => number) => {
const { camera, controls, renderer } = viewer;
const bombGroup = new Group();
map.add(bombGroup);
// 创建一个球
const ball = new Mesh(
new SphereGeometry(500),
new MeshPhongMaterial({
map: new TextureLoader().load("./test.jpg"),
shininess: 3,
transparent: true,
})
);
// 监听click
renderer.domElement.addEventListener("click", (evt) => {
if (!controls.isLocked) {
controls.lock();
return;
} else if (evt.button === 2) {
controls.unlock();
return;
}
onfire();
// clone一个球
const aBall = ball.clone();
bombGroup.add(aBall);
// 球起点坐标:从摄像机位置开始
const startPosition = map.worldToLocal(camera.getWorldPosition(new Vector3()));
aBall.position.copy(startPosition);
// 取得视线与地面交点
const crossPoint = map.getLocalInfoFromScreen(camera, new Vector2(0, 0));
// 球终点坐标:视线与地面有交点则取交点,无交点则取摄像机前方100km
const endPosition = crossPoint
? map.worldToLocal(crossPoint.point)
: map.worldToLocal(camera.localToWorld(new Vector3(0, 0, -100 * 1000)));
// 球飞行时间
const delay = 800;
// 球直线飞行动画
new Tween(aBall.position).to(endPosition, delay).easing(Easing.Quadratic.Out).start();
// 球减速下落动画(模拟重力作用)
new Tween(aBall.position)
.to({ z: endPosition.z }, delay)
.easing(crossPoint ? Easing.Quadratic.In : Easing.Quartic.Out)
.start()
.onComplete(() => {
// 动画完成延迟销毁球
const dispose = () => {
aBall.removeFromParent();
aBall.geometry.dispose();
aBall.material.dispose();
};
if (crossPoint) {
setTimeout(dispose, 10 * delay);
} else {
dispose();
}
});
});
};