games104学习笔记

本文最后更新于:2025年1月14日 晚上

动画

面临的挑战

玩家行为是不可控的,要和gameplay互动;实时性;真实性(Motion Matching)

2D动画

精灵动画(Sprite):一帧一帧循环播放

Live2D

把每个图元都分隔,然后各种拉伸的时候可以做出各种动作

每一个部位生成一个图元来控制,非常直观的动画系统,简单又生动

3D动画分类

基于层次的刚体动画,是一个树形结构,但是可能会穿帮,因为只操纵骨骼会互相穿插有可能。

顶点动画,存offerset的texture和法向的texture,然后根据帧数来计算,可以做一些布料或者流体什么的

蒙皮动画

基于物理的动画:衣料、布娃娃系统、IK(反向动力学)

蒙皮动画

蒙皮动画步骤

坐标系的转换,要理解怎么变化的,乘旋转和平移矩阵,每一个骨骼都有自己的Local Space,并且会是一个一直传递相乘的矩阵(正向运动学),比如手指要一直乘各个手臂

骨骼树的起点是胯部(脊椎的最后一块骨头)

可能会加入一些额外的骨骼,比如什么眼球啊,翅膀啊,斗篷,武器,绑定节点(人骑在马上)这些都是用骨骼来驱动的

为了更好地计算移速、离地高度,会加一个root

四足动物的位置如下:

3D旋转数学基础

2d旋转

3D旋转

可以证明任何旋转都可以用xyz旋转等价,也就是欧拉角

但是欧拉角是有很多问题的,首先他顺序乘的不同会导致结果不同

其次欧拉角会有锁死的情况,比如y轴转了90度,这时x轴和z轴是同轴了,那么就两个的旋转等于只有一个旋转了,造成退化的情况。

非常难插值

传递的时候不能直接相乘

并且很难绕着一个任意的轴来旋转

四元数

这时候就要用到四元数(Quaternion)了

没太听懂,到时候仔细研究一下四元数吧

四元数的可视化

关节与蒙皮

MJm,是从root上一步一步累计的transform矩阵,记住要乘一个自己绑定的时候的逆,然后可能还要乘一个骨骼到世界坐标的transform,一般编程的时候会把这个逆直接存储下来。

因为动画的帧数不够,所以需要插值,NLERP,然后可能要满足最短路径,这样才能看起来比较自然一点,夹角小的时候用NLERP,大的时候用SLEPR

动画压缩

动画一个帧就三十个pose,然后几十个骨骼,每个都要存旋转位移scale,这种时候就需要压缩,因为很多时候他们的数据是不变的

首先不变的值直接干掉,然后可以用keyframe插值,如果插值造成的误差没超过阈值,那么就可以接受。

直接线性插值会对旋转矩阵会比较差,可以用catmull-rom spline

如果浮点数的范围是有限的,可以转成定点数,归一数压缩一下

如果避免error累积,防止出现动画飘来飘去的情况?要更换计算error的方式,直接比较矩阵函数和插值之后的差值其实不太直观,最好的是visual error,但是又不可能真的直接算,所以会在每个joint拿两个垂直的点,然后来计算这个前后的差值。敏感的可以给大一点。

动画制作流程(DCC)

先用低模做,但是在关节地方会多加一点mesh

骨骼绑定

Skinning

一般自动计算权重,但是也可能要手动调整一下

动画制作

Exporting

要注意跳跃的时候root可能会变化,

动画混合

两个动作之间插值,比如走到跑,weight根据速度来变化,要保证两个动作的时间是一样的,这样才能确定是哪一帧

混合空间(Blending Space)

2d空间中,动作不是一维的,所以有可能有些地方动作密(因为觉得这里需要更细腻的表现),然后插值的时候,不可能把全部动作都插值,这时候就需要用到delaunay三角化,其实也就是把每三个点都连成三角形,然后你插值的时候判断是在哪个三角形里面,只插值这最近的三角形的动作。

鼓掌可以有一个mask,某些动作只应用到上半身

Additive Blending

混合之后再叠加一个blend,比如旋转,可以做一直朝着一个地方点头

动画状态机(Action State Machine)

每一个状态也可以是一个动画状态空间

一般还可以是多层状态机,分开独立控制

动画混合树

定义控制变量,暴露给gameplay系统,让他们来改变,然后通过这些变量来选择当前树该选择哪个节点

也可以通过事件来控制,比如切枪(从步枪变成火箭筒)

UE骨架动画文档

Inverse Kinematics(IK反向动力学)

ik可能是多解的,所以这时候需要一个指定朝向,相当于再多加一个平面

如果不止两个,有多个关节,该怎么解?(解有无穷多种可能)

首先要判断解空间,考虑最大和最小,并且还有考虑关节的约束度

CCD,不断地翻滚骨头,循环到目标

FABRIK

把末端直接放在终点,这时候那个骨头会突出去,然后把这个作为下一个的起点,最后迭代过去会导致root也有偏差,这时候再backward一遍,把root拉过去,然后一直这样forward和backward迭代,设置一个error误差。

多目标约束怎么办?共同的节点发生变化,可以用雅可比矩阵+逼近的方式。

一些其他的IK技术

面部动画

Facical Action Code System

46个基础表情

存的是顶点动画,每一个node相对于自然状态的delta,morph target animation

也有些使用骨骼动画,捏脸,眼球,这些需要。

2d的话也可以直接用uv texture

动画重定向

Source Character->Target Character

Source Animation->Target Animation

重定向的时候存的是相对旋转、scale和位移

要根据腰线的高度等比例放缩一下

如果还不行就得用IK

有些时候,如果骨骼数量不一样怎么办?简单的办法就是把数量不一样的地方,按照数量的差异,做一个插值,4->3就是0.3,0.7,然后把插值找到的位置直接认为是少的那个骨骼的终点位置。

也存在一些问题,比如自身穿模,或者没达到效果,因为有些动作是有语义的,比如鼓掌没合手,可以加一些约束

物理

Gameplay框架

事件机制

一般是用观察者模式,需要定义以下三个东西

Event Definition

有一个enum和parament,要做到可拓展,能实时修改

Callback Registration

要考虑事件和回调的线程安全性问题,比如有可能你invoke的时候那个对象已经被销毁了

可以用强引用,保证invoke的的对象一定不被销毁;或这软引用,使用之前先判断是否已经被销毁了。

Event Dispatching

派发,如果来一个事件就直接派发,可能会产生堵塞、或者可能发生链式反应,事件一直会有。

一般会实现一个消息队列,把一帧到来的事件全部收集好,然后下一帧统一执行。

要实现序列化和反序列化,以及Ring buffer,这样就不用重复申请和释放内存,好处是不会崩,顶多是那一瞬间卡一下

ringbuffer

不同的类型要分批分发

也会有问题,比如有些事件就需要即时发生;以及delay会一直累积

游戏逻辑和脚本系统

热更新所以得用脚本语言?

GO的管理靠谁来管理?一般是脚本,因为很多跟逻辑相关,一般是靠GC

如果本身对象没有那么多,比如单机游戏,更加注重ai动画,那么就直接引擎来管理

解释性语言可能会比较慢,可以用JIT(一边执行一边编译)

可视化脚本

面向设计师,让他们免于管理生命周期。可视化脚本也要实现编程语言的那些共同的要素。

变量:类型和作用域(管脚不一样,同颜色对同颜色就可以了)

语句和表达式

Control Flow

函数:input,body和return

:蓝图本身就是一个类

debug:直接可视化一步一步走

3C

Character

各种复杂的状态

Control

处理各种输入设备

细节很多,比如会自动锁定,反馈,多态的输入

Camera

不能穿墙

Camera Effect

Cmmera Manager:会有很多不同视角的相机

寻路(Navigation)

地图的表达;寻路;path smoothing

表达的不同形式

路点网络

类似地铁,但是每次地图变化也改变路网的图,而且npc很容易往中间走

Grid

障碍物可以自动更新,类似于光栅化的方式;

存储空间可能比较浪费;而且很难表达3d的层叠结构(桥上桥下)

动态更新

寻路网格(Navigation Mesh)

需要用凸三多边形

寻路比较快,内存占用比较低。

生成比较复杂,且不支持3d空间

八叉树

可以表达3d空间

寻路算法

其实不管什么表达方式连接点都可以变成一张图

dfs

bfs

Dijkstra算法

Astar

navmesh里面直接用多边形相交边的中点的欧拉距离

Path Smoothing

funnel算法

从几何理解比较简单

  1. 创建沿 A* 路径的portal(多边形交线)列表。确保每个portal的点相对于起点以相同的方式存储。您需要知道某个点是在起点的左侧还是右侧。
  2. 创建一个由三个点组成的“漏斗”:角色的起始位置(顶点)、portal的右侧和portal的左侧。
  3. 交替更新funnel的左右两侧,每次都使其变窄
  4. 当funnel的两侧交叉时,请注意不要更新新漏斗的顶点,并将其存储为平滑路径的一部分。

Funnel-Algorithm

假如创建的funnel里面直接包含了终点,那么直接连过去就行

voxelization(体素化)

先体素化,然后标记出能通行的区域(通过高度差距),然后用洪水算法,从一个最深的点开始判断哪些是可达的,然后再根据这个生成多边形

还可以给navmesh添加flag,比如水陆地,来实现复杂的寻路算法,习性不同的怪物会走不同的路,一般是直接在地图上放flag然后映射到navmesh上面。

tile:大的meshmp可以分隔成多个小的tilie,因为实际游戏中map可能会变

转向系统

需要更自然,先加速再匀速然后减速

群体模拟

微观的方法,定义每个个体的规则

鱼群效果:远离、接近和跟随

宏观

两者结合,比如说蜂群,多个结合成一个小团体,有一个大趋势,然后团体内部每个个体有自己的规则

如何避免碰撞?

直接加斥力or方向场

RVO

计算别人的速度向量是否在自己的路径上,如果在路径上就各自调整速度向量

感知

自身信息

位置、HP

地形信息:寻路、战术信息、可交互物体、掩护点

动态数据:影响力图,动态寻路数据、视线图、游戏中的object(它身上的各种信息)

视觉感知、听觉感知

AI

状态机

状态太多就不好处理,可以分层,但是不太好飞线

行为树

Sequence

所有子节点依次执行,每个都要执行

Selector

一直试,直到某个节点成功完成

Parallel

同时执行所有节点

比如一边走向目标一边开枪

Decorator

有各种判断条件,以及loop,time这种

Blackboard

行为树的各个节点直接互相通信,存储一些变量

没有目标,只是遇到情况就来判断做什么

层次任务网络(HTN)

首先要定义task:primitive task(原子性任务),要有前置条件,动作和产生影响(effect)

解毒

复合任务:会有多个条件,根据优先级哪个满足做哪个,类似行为树的selector,method也可以是复合行为,类似行为树的sequence。

两种选择

HTN Domain

简单小怪逻辑

Planning

根据world state的各种propety,来选择做哪个method,计划的时候根据当前的world state来做假设,拷贝一份,但是会假设能做的action都能成功,这里是有可能会有问题的。

Replan

计划失败了需要计划

会更快,plan会有长期影响

行为是不可预测的,会对本身world state有状态变化

目标导向行为规划(GOAP)

多了一个目标集,HTN里面只是写在了注释里面

goal会有precondition和目标state

Action加了一个cost,比如路径的远近,风险,这个需要策划凭借经验来填

state满足的情况下,cost要尽可能地小

网络

面向数据编程和任务系统

Lumen

Nanite


games104学习笔记
https://rorschachandbat.github.io/游戏杂货铺/games104学习笔记/
作者
R
发布于
2025年1月10日
许可协议