乐趣区

关于javascript:3D-沙盒游戏之人物的点击行走移动

前言

在 3D 游戏中,都会有一个主人公。咱们能够通过点击游戏中的其余地位,使游戏主人公向点击处挪动。

那当咱们想要实现一个“点击高空,人物挪动到点击处”的性能,须要什么前置条件,并且具体怎么实现呢?本文带大家一步步实现人物行走挪动,同时进行状态扭转的性能。

一、骨骼动画

骨骼动画(Skeleton animation 又称骨架动画,是一种计算机动画技术,它将三维模型分为两局部:用于绘制模型的蒙皮(Skin),以及用于管制动作的骨架。

个别在 3D 游戏中的主人公,它的跑步、走路、站立的动作,都是模型文件的自带骨骼动画。

骨骼动画权重
扭转骨骼动画的权重,能够使得动画间的过渡更为天然。比方体测时,当你达到起点后,会逐步减慢速度,跑步动作的幅度越来越小,而后变成走路,最初进行。

让咱们看看一个俩动作权重突变的例子:

这个例子中,从休闲变到走路,休闲动画的权重从 1 到 0 递加,同时走路动画的权重从 0 到 1 递增。能够的点击👉 这个网站中 > Crossfading > from idle to walk 体验一下。

在本次 3D 沙盒游戏中,人物状态扭转,次要是鼠标点击高空后,人物从休闲状态转为跑步状态,当人物达到目的地后,又变为休闲状态。咱们先来看看这些状态扭转是如何实现的。

首先,咱们须要设计师提供一个领有骨骼动画的模型,它有两个骨骼动画,一个为休闲(idle)状态,一个为跑步(run)状态。

1.1 思路

1.2 动画初始化

先让咱们将骨骼动画、动画名称、权重放到一个对象中存储起来,

idleAnimConfig = {
  name: string;
  anim: AnimationGroup;
  weight: number;
}

那么如何判断是否正在行走呢?就须要一个以后动画的 flag,初始化时将 idle 设为以后动画

currentAnimConfig = idleAnimConfig

1.3 动画权重扭转

如图,咱们在人物状态扭转时,须要将以后状态的动画权重递增,另一状态的动画权重递加(留神,权重值须要限度在[0, 1])。让咱们看下伪代码,假如 deltaWeight 为负数

changeAnimWeight() {
  // 以后动画 -> 递增
  if (currentAnimConfig) {setAnimationWeight(currentAnimConfig, deltaWeight)
  }
  // 其余动画 -> 递加,如站立动作切换到走路
  if (currentAnimConfig !== idleAnimConfig) {setAnimationWeight(idleAnimConfig, -deltaWeight)
  }
  // 其余动画 -> 递加,如走路动作切换到站立
  if (currentAnimConfig !== runAnimConfig) {setAnimationWeight(runAnimConfig, -deltaWeight)
  }
}

而后在 render 的时候,进行状态切换

onRender() {if (筹备达到目的地) {setCurAnimation(runAnimConfig)
  } else {setCurAnimation(idleAnimConfig)
  }
  changeAnimWeight()}

1.4 短少动画

如果 animationGroup 里只有一个 run 动画怎么办呢?
答案还是一样的,只有将 idle 动画的骨骼动画设为 null 即可,像这样:

idleAnimConfig = {
  name: string;
  anim: null;
  weight: number;
}

这么做即便起初更换了具备两个动画的人物模型,也能复用。

动画状态切换实现成果

二、行走挪动

当咱们平时写动画时,会用到 rAF 并递归调用渲染函数,实现一个逐帧渲染动画。当人物行走在平地上时,也能够利用逐帧挪动,来实现一个位移的动画。例如 Babylon 曾经封装好了 render 的事件 API,只有咱们将渲染动画绑定 render 事件,就能够应用了。

让咱们看看具体思路:

2.1 挪动

由下面的思路能够看出,咱们挪动的时候须要用到几个变量:

  • 距起点的间隔(distance)
  • 挪动的方向(direction)

那么就须要在点击的时候,获取到这些变量。distance 能够利用矩阵对应坐标相加减计算,direction 就是指标地位减初始地位的法向量

directToPath() {
  // 将人物的地位设为初始地位
  initVec = this.player.position
  // 计算初始地位与起点的间隔
  distance = Distance(targetVec, initVec)
  // 将起点地位与初始地位相减
  targetVec = targetVec.subtract(initVec)
  // 应用法向量计算出与起点的朝向
  direction = Normalize(targetVec)
  player.lookAt(targetVec)
}
onClick() {
  // ...
  directToPath()}

在 render 的时候进行位移

onRender() {if (distance > READY_ARRIVE) {
    distance -= SPEED
    // 人物朝 direction 方向挪动 SPEED 间隔
    player.translate(direction, SPEED, Space.WORLD)
  }
}

位移实现成果

2.2 联合动画

当咱们的挪动联合模型的骨骼动画

让咱们看看伪代码:

onRender() {if (distance > READY_ARRIVE) {
    distance -= SPEED
    // 人物朝 direction 方向挪动 SPEED 间隔
    player.translate(direction, SPEED, Space.WORLD)
    setCurAnimation(runAnimConfig)
  } else {setCurAnimation(idleAnimConfig)
  }
  changeAnimWeight()}

位移及状态变动实现成果

三、人物避障

3.1 思路

人物行走避障,实际上就是从终点到起点,在这之中增加了 两头点。如图

所以咱们只有记录下以后终点到起点这个门路数组,每次都朝数组的第 N 个点行走,就能做到转向。上面咱们来依据思路及伪代码进行步骤细化。

(1) 记录门路和初始化以后的门路索引

path = getPath(targetVec)
prePathIdx = 0

(2) 当达到以后两头点时,切换到下一个两头点。当走到最初一个,则进行

onRender() {if (distance > READY_ARRIVE) {// ... 挪动及动画权重切换...} else {switchPath()
    // ...
  }
  // ...
}
switchPath() {
  prePathIdx += 1
  directToPath()}
directToPath() {const curPath = path[prePathIdx]
  if (!curPath) return
  // ... 人物挪动及转向...
}

3.2 接入理论避障算法

3.1 得悉,人物的行走挪动要接入避障算法,须要利用到该算法提供的门路布局数组。理论利用中,咱们只须要把,伪代码里的 getPath() 办法,换成算法计算路线的办法即可。

3.2.1 RecastJSPlugin

上面咱们应用 Babylon 自带的 Recast 插件,来具体阐明一下如何接入避障算法。

办法 1

在 recast 中,能够通过 computePath 获取门路:

const closestPoint = this.navigationPlugin.getClosestPoint(pickedPoint)
const path = this.navigationPlugin.computePath(this._crowd.getAgentPosition(0),
  closestPoint
)

而后利用 3.1 的思路,通过门路索引切换进行挪动。

办法 2

recast 首先会创立一个导航网格,而后通过增加 agent 让它们束缚在这个导航网格中,而这些 agent 的汇合,称为 crowd

并且 recast 自带了挪动的 API —— agentGoto,此时能够不须要再去计算间隔和方向,并且也不须要手动切换挪动门路,让咱们看看具体是怎么做的。

(1) 初始化插件,并设置 Web Worker 来获取网格数据以优化性能

initNav() {navigationPlugin = new RecastJSPlugin()
  // 设置 Web Worker,在外面获取网格数据
  navigationPlugin.setWorkerURL(WORKER_URL)
  // 创立导航 mesh
  navigationPlugin.createNavMesh([
    ground,
    ...obstacleList, // 障碍物列表 mesh
  ], NAV_MESH_CONFIG, (navMeshData) => {navigationPlugin.buildFromNavmeshData(navMeshData)
  }
  this.navigationPlugin = navigationPlugin
}

(2) 初始化 crowd(crowd:束缚在导航网格中 agent 的汇合)

initCrowd() {this.crowd = this.navigationPlugin.createCrowd(1, MAX_AGENT_RADIUS, this.scene)
  const transform = new TransformNode('playerTrans')
  this.crowd.addAgent(this.player.position, AGENTS_CONFIG, transform)
}

(3) 点击时利用 agentGoto API 进行挪动,pickedPoint 为点击点的三维坐标,因为 crowd 里只有一个对象,所以索引是 0

const closestPoint = this.navigationPlugin.getClosestPoint(pickedPoint)
this.crowd.agentGoto(0, closestPoint)

(4) 判断是否进行,未进行则扭转人物朝向

那么如何扭转人物的朝向呢,咱们须要下一个两头点的地位,让人物看向它即可。
所以回到之前初始化的中央,创立一个 navigator

initCrowd() {
  // ...
  const navigator = MeshBuilder.CreateBox('navBall', {
    size: 0.1,
    height: 0.1,
  }, this.scene)
  navigator.isVisible = false
  this.navigator = navigator
  // ...
}

在 render 的时候,人物是否进行,能够通过以后 agent 的挪动速度来进行判断。而改变方向,则是通过将 navigator 挪动到下一个 path 的两头点,让人物看向它。

onRender () {
  // 第一个 agent 对象的挪动速度
  const velocity = this.crowd.getAgentVelocity(0)
  // 挪动人物到 agent 的地位
  this.player.position = this.crowd.getAgentPosition(0)
  // 将 navigator 的地位移到下一个点
  this.crowd.getAgentNextTargetPathToRef(0, this.navigator.position)
  if (velocity.length() > 0) {this.player.lookAt(this.navigator.position)
    // ...
  } else {// ...}
 // ...
}

4. 避障实现成果

让咱们看看最初的成果

5. 遇到的问题

整个开发过程中,其实也不是十分顺利,总结了一些遇到的问题,能够给大家参考一下。

(1) 年兽挪动时,有时会“无奈刹车”,导致在起点时重复来回停不下来;
这是因为在这一帧里,因为年兽的加速度较小,无奈使得短时间内将速度降为 0。所以只能“走过头”再“走回来”直到速度降为 0 之后,进行在起点。
此时,只须要 hack 一下,将 agent 的 maxAcceleration 设为极大,让其有种匀速行走并立马停下的感觉。

export const AGENTS_CONFIG: IAgentParameters = {
  maxAcceleration: 1000
  // ...
}

(2) 障碍物的动静增加与移除

如果障碍物在该场景初始化后,地位产生了扭转,此时再去销毁创立一次 navMesh 是很耗费性能的。

于是咱们通过查找文档,看到还有动静增加障碍物的 API。再立马调了下文档中的 Playground,发现是能够用的。然而当咱们把障碍物放大了之后,穿模了👉 看看这里。

于是在 Babylon 的论坛上提了这个问题,20 分钟后就失去了 reply,这个速度👍。

原来是须要调整 NavMeshParametersch / cs / tileSize 参数,对我的项目做适配。

那如果想要本人实现避障,创立更快的 navMesh,咱们应该怎么做呢?能够看看这篇文章:3D 沙盒游戏之避障踩坑和实现之旅

总结

这篇文章,咱们从骨骼动画的介绍及应用、模型的挪动及状态扭转、门路布局的适配三个方面,解说了 3D 沙盒游戏中实现人物行走挪动并进行状态扭转的思路及步骤,心愿新人浏览完结之后,能更快上手这个性能。

当然,本篇文章介绍的实现形式还仍有不足之处,比方挪动能够加上加速度,让动作与挪动速度匹配得更天然等。

如果还有什么适合的倡议,也欢送大家踊跃留言交换。

参考资料

  1. 骨骼动画 – 维基百科,自在的百科全书
  2. Grouping Animations | Babylon.js Documentation
  3. Advanced Animation Methods | Babylon.js Documentation
  4. Vector3 | Babylon.js Documentation
  5. Crowd Navigation System | Babylon.js Documentation
  6. Web Workers API
  7. Make crowd agent move at constant speed – Questions – Babylon.js

    欢送关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章。

退出移动版