Hold-ON!复盘:物理材质
什么是物理材质
物理材质定义了刚体表面的弹性和摩擦力,包括以下几项:
Dynamic Friction(动摩擦)
在移动时使用的摩擦力,动摩擦将尝试在与另一个对象接触时减慢对象的速度。Dynamic Friction 通常为 0 到 1 之间的值。
值为 0 时表面就像冰一样滑
值为 1 将使对象迅速静止(除非用很大的力或重力推动对象)。
Static Friction(静摩擦)
当对象静止在表面上时使用的摩擦力,静摩擦力会阻止对象开始移动,如果向对象施加足够大的力,对象才会开始移动。Static Friction 通常为 0 到 1 之间的值。
值为 0 时表面就像冰一样滑
值为 1 时物体将很难移动。
Bounciness(弹性)
表面的弹性如何?值为 0 将不会反弹。值为 1 将在反弹时不产生任何能量损失,预计会有一些近似值,但可能只会给模拟增加少量能量。
除此之外,还有一个 Friction Combine 和 Bounce Combine。这两个“组合”表示了两个碰撞对象之间摩擦力/弹性的组合方式,Unity 将根据所选择的模式对两个物体施加相同 ...
Hold-ON!复盘:物理关节
我们经常在游戏中看到下面的 gif 里所展示的“可交互场景对象”。这样的对象的存在增添了场景的可交互性,让场景不那么死板,显得更有生气和灵性。除了下图展示的游戏 HUE 之外,GRIS、空洞骑士等游戏也大量使用了可交换场景对象。
这种可交互的场景对象是怎么实现的呢?这就要引出文章的主题:Joints(关节)了。
什么是关节
Joints(关节)是一种用于将两个刚体连接在一起的物理组件,通常用于模拟现实世界中的连接、约束或机械行为。例如,你可以用关节连接一个物体到另一个物体、固定到某个位置,或者模拟摆锤、弹簧等效果。
固定关节/Fixed
固定关节(Fixed Joint)将两个对象固定在相对位置,适合用来模拟焊接、粘合等固定效果。例如将一个箱子固定在某个位置、或者连接两个不会相对移动的物体(例如把粘土炸弹固定到某个移动对象上)。
可使用固定关节面板上的 Break Force 和 Break Torque 属性来设置关节强度的限制。如果这些值小于无穷大,并对该对象施加大于这些限制的力/扭矩,则其固定关节将被破坏并将摆脱其约束的束缚。
另外,固定关节的刚体类型也会影响到展示的效果, ...
Hold-ON!复盘:Unity中的物理碰撞
我们参加的 48 小时 jam,第三届《益·游未尽》的比赛作品——《Hold-ON!》在比赛截至前平安上线 itch 平台了,欢迎大家前来试玩!
Hold on- h5 version by Guinyの时光
在这次的项目中,我和另一位程序大量使用到了 Unity 的物理系统,其中包括通过力驱使角色移动、玩家之间的碰撞、触发器检测等,我想借此机会在复盘文章中好好聊一聊 Unity 的物理系统,以及物理系统在实际项目中的一些应用。
Unity 中的物理模拟包括碰撞检测、力学模拟、刚体运动和关节(铰链等)等等。Unity 的物理引擎按照对象类型分为两种实现:
对于 3D 对象的物理系统是基于 NVIDIA 的实时开源物理引擎 PhysX 实现的。
对于 2D 对象的物理系统是基于开源的物理引擎 Box2D 实现的。
如果是 Unity6 的面向数据(DOTS,即 Unity 的 ECS 框架)方案,则采用 Unity Physics 包,在本文中不多做讨论。
刚体物理
在 Unity 3D 中,物理系统有两个很基本的组件:Collider(碰撞体)和 Rigidbody(刚体)。希望产 ...
UnityUI程序设计与UI性能优化(五):UGUI性能优化指南
UGUI是一个很强大的系统,通过它我们可以拼出多姿多彩的UI界面,让玩家更好的与游戏UI进行交互。但作为开发者,我们也需要注意UGUI中潜在的性能优化问题,防止游戏因为 drawcall 过多、频繁 rebatch / rebuild 而造成发热和卡顿(特别是对于低端机型来说)。
性能瓶颈
硬件影响
典型场景
CPU耗时参考
过度绘制
GPU负载
半透明元素叠加
每层增加0.5-1ms
批次破碎
DrawCall激增
动态列表滚动
100元素≈30DC
布局重建
主线程卡顿
LayoutGroup嵌套使用
嵌套3层≈15ms
那么该怎么对 UI 进行优化呢?我们优化的主要目的就是减少绘制调用(drawcall)和布局重建的次数。绘制调用发生在 CPU 与 GPU 中间,当 CPU 打包好渲染数据(例如顶点、纹理、材质等)之后,就会发起一次绘制调用,将这些信息打包到显存以通知 GPU 进行绘制。也就是说,有多少个绘制调用,CPU 就需要向 GPU 发送多少次通知。drawcall 太高,意味着有频繁地改变渲染状态,这是很慢的操作(相对于GPU执行其内部的渲染流 ...
UnityUI程序设计与UI性能优化(四):UGUI的点击事件
在 Unity 的 UI 系统中,Button 是最常见的可交互组件之一(除此之外还有 Slider、Toggle、Input Field、Scroll Bar、Drop Down),其点击事件不仅是开发者构建用户交互的基础,更是一个复杂而精巧的功能模块。本文将从 UGUI 的源码层面出发,全面剖析 Button 点击事件的实现原理,理解其设计思路与内部逻辑。
按钮点击的触发流程
相信每个介绍 UGUI 的新手视频都会讲到 Button 的点击事件。在 Unity 中,如果我们想为一个按钮添加事件有两种实现方式,一种是直接在检查器上为该按钮绑定事件:
另一种是通过代码为按钮的 onClick 加上方法监听:
123GetComponent<Button>().onClick.AddListener(() => { ...//按钮点击的处理逻辑});
这两者是等价的。让我们沿着 onClick 的调用链一步一步看看是怎么回事吧。
Button:Press -> OnPointerClick
我们来看看 Button 的源码吧!在 ...
UnityUI程序设计与UI性能优化(三):UGUI的渲染流程
UGUI的渲染过程
Unity中渲染的物体都是由网格(Mesh)构成的,而网格的绘制单元是图元(点、线、三角面)。在 Unity 中渲染一个 2D 或 3D 对象时,需要由 CPU 提起一个 draw call(绘制调用),经过一个完整的渲染管线流程,经由 GPU 处理最后输出像素到屏幕上。那么对于 UGUI 元素(例如Image)来说是不是也是一样的呢?
绘制一个 UI,简而言之就是绘制 UI 的形状与图元:
生成 mesh:mesh 由顶点和三角形组成,顶点里包括 UV 坐标、顶点颜色。
渲染图元:将纹理渲染到 mesh 上。
我们将以绘制一张 Image 为例,学习 UGUI 的渲染过程。
可以看到,新建一个 Image 对象,默认挂载了Rect Transform、Canvas Renderer 和 Image 三个组件。Image 的源码位于 Runtime/UI/Core/Image.cs,这个文件有 1000 多行,初见感觉挺吓人的,但其实大部分都是注释。Image 继承自 MaskableGraphic(同样继承自此抽象类的还有 RawImage 和 Text, ...
UnityUI程序设计与UI性能优化(二):UI系统的程序设计
聊完了什么是 UGUI,现在可以看看作为开发者的我们应该如何设计 UI 系统的整体框架了。我在大二的时候曾经基于栈写过一个 UIManager ,原理很简单:一层一层打开的 UI 面板天然符合栈的数据结构思想,所以通过一个栈来维护打开的面板就可以了。这个模块由于和 hhl 学长的 UI 模块重复了所以没有合并进仓库,当然我的想法也并没有考虑到太多商业化场景中的复杂情况,因此在这里就不详细展开让大家见笑了。
本篇文章将聊聊 hhl 学长的 UI 模块和更商业化项目中的 UI 模块的设计思路。
ProjectBase-UI分析
学长的代码在 github 仓库里,我对其中的UI部分拆开做了一张思维导图:
整个 UI 管理器是一个单例的管理器,可以通过 UIManager 开启或关闭面板,也可以通过 UIManager 查找指定的开启的面板并调用其中 BasePanel 的方法。单例很好用,但它也存在增加耦合的风险,具体可以看我之前写的关于单例模式的文章。
这个模块用起来很方便,但问题也很明显,比如说 UIManager 中隐藏面板的地方:
1234567public void HideP ...
UnityUI程序设计与UI性能优化(一):基本概念
常见UI框架
NGUI、UGUI、FGUI,这些“GUI”都是什么?GUI 通常指 graphic user interface(图形用户接口),是指采用图形方式显示的计算机操作用户界面。在这篇文章中我将简单介绍一下这几个常见名词,而在之后的章节中,笔者将从 UGUI 源码剖析、UI 框架的程序设计以及 UGUI 性能优化三个方面详细展开。
UGUI (Unity Graphic User Interface)
这是我们最常听说的 GUI,也是 Unity 内置的用户界面系统,从 Unity 4.6 版本开始推出,可以在Github上找到它的开源仓库。UGUI 以 Canvas 为核心,提供了丰富的 UI 组件(比如我们常用的button、slicer、text等)和可视化编辑工具,操作简单直观。
优点
Unity 原生的 GUI 系统,和 Unity 的其他模块(例如事件、渲染、动画)深度集成,并且由官方更新和维护。
社区中存在大量的教程和文档。
缺点
由于 Canvas 的重绘机制(特别是复杂或动态的 UI 元素)可能导致性能瓶颈,这部分会在 UI 性能优化的章节详细 ...
两个经典的容器:C++ map与C# Dictionary
为什么要单独说说map和Dictionary呢?因为这两个容器非常经典也很常使用到。对于一个开发者来说,了解它们如何实现是有必要的,官方文档中对二者的定义如下:
map用于存储和检索集合中的数据,此集合中的每个元素均为包含数据值和排序键的元素对,大小可变。
键的值是唯一的,用于自动排序数据。可以直接更改映射中的元素值。键值是常量,不能更改。
必须先删除与旧元素关联的键值,才能为新元素插入新键值。
map是可逆的,因为提供了双向迭代器来访问其元素。
12345template <class Key, class Type, class Traits = less<Key>, class Allocator=allocator<pair <const Key, Type>>>class map;
其中Traits代表了自动排序的规则(默认为less),Allocator则是一种表示存储的分配器对象的类型(默认对,pair)。
dictionary表示键和值的集合,是一个泛型集合,它以不特定的顺序存储键值对。
字典不能包含重 ...
寻星之路复盘——A*寻路算法及其优化
论文答辩、送审等七七八八的事情终于结束了,相关评审也告一段落。感谢王宝土老师和学校电脑店的大力相助,毕设《寻星之路》demo终于是有惊无险地完成了它的使命。说实话我对这个作品可能不是太满意,因为它比我最开始设想的“三个章节、25个关卡、精妙绝伦的故事”相差甚远,仅两个月的独立开发时间,构思就浪费了小半个月,可叹。
在毕业答辩的技术部分中,占比相当大的就是接下来要介绍的 A* 了(另一部分是行为树,可以看看我的上一篇文章)。作为毕设的复盘,我将在这篇文章里聊聊我是怎么实现A*的,以及A*可以有哪方面的一些优化。
什么是A*?
A*是一种启发式搜索算法,是一种在平面上有多个节点的路径,求出最低通过成本的算法。该算法综合了最良优先搜索和 Dijkstra 算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条最优路径。
Dijkstra算法
Dijkstra算法使用类似广度优先搜索的方法解决赋权图(只能用在权重为正的图中)中的单源最短路径问题,其思想如下:
从源节点(起点)出发,寻找它与图中所有其它节点之间的最短路径。
记录当前已知的最短路径,并在寻找到更短的路径时更新。
一旦 ...
游戏AI-状态机与行为树
什么是游戏AI?
游戏中,能模拟玩家行为、和玩家进行交互的游戏对象就可以被称作为游戏 AI。AI 可以观察环境、做出反应,并达到相应的目的。需要实现一个 AI,我们通常需要借助游戏引擎中的动画、物理、渲染,以及 AI 系统中的环境感知、寻路系统和 gameplay 来实现。常见的 AI 有 NPC、敌人、队友等,游戏 AI 存在的目的在于提升玩家的游戏代入感、增强玩家的心流体验。
动画:动画(如骨骼动画、帧动画、程序动画、morph动画、curve动画、顶点动画等)可以让 AI 看起来更真实,不那么生硬死板。
物理:提供真实世界的感知和反馈,为游戏 AI 增强效果,让 AI 有更丰富的感知和交互表现。物理引擎可以用于碰撞检测/事件触发、仿真(头发模拟、流体模拟等)、AI感知、场景查询等。
渲染:经过渲染引擎的渲染,游戏 AI 的形象才能最终显示在屏幕上。次世代渲染通过应用阶段、几何阶段(模型变换、视图变换、投影变换)、光栅化阶段实现最终效果。
环境感知:AI 能感受周围环境并根据环境做出相应的决策。环境感知主要是通过数据查询(例如 UE 中的 EQS,即环境场景查询)来实现的。我们可 ...
Cinemachine基础学习
相机的控制与管理是游戏体验中相当重要的一部分,好的镜头语言能够让玩家更容易地进入心流,更好地体验游戏。相机属于游戏 3C 中的 Camera(另外两 C 分别是角色和控制),其地位不言而喻。在本文中,我将探索如何通过 Unity 的 Cinemachine 实现相机跟随的一系列功能。
Cinemachine 是 Unity 提供的高级相机系统,用于创建平滑、智能的摄像机跟随和过渡效果。Cinemachine 能够自动适应游戏场景,无需手动编写复杂的相机控制代码,让开发者专注于游戏玩法。要想在项目中使用 Cinemachine,需要从 Package Manager 中安装 Cinemachine 包。
导入 Cinemachine 包之后,在场景中创建一个虚拟相机,相机的视野立即产生了变化。观察视窗我们会发现游戏原本的主相机多出了一个图标,点开一看是一个名为“Cinemachine Brain”的组件造成的。自动添加了 Cinemachine Virtual Camera 脚本的虚拟相机和添加了 Cinemachine Brain 的主相机一起,组成了 Cinemachine 的基本 ...
C#进阶(四)-事件
什么是事件?
event 是C#的一个关键字,是对于委托的一种更安全的封装方式(只能作为成员存在于类/接口或结构体中),这使得被 event 关键字修饰的委托只能在声明它的类中被调用。
1234// 定义一个委托类型,用于事件处理程序public delegate void NotifyEventHandler(object sender, EventArgs e);// 用event修饰委托实例public event NotifyEventHandler _processCompleted;
你一定好奇这里 objrct 类型的 sender 和 EventArgs 类型的 e 分别是什么。其实从命名上也可以看出来,EventArgs 表示事件参数(EventArguments)的类的基类,它可以用来记录事件传递的额外信息(例如触发的时间、位置等等)。在实际使用中,开发者需要自行定义派生自 EventArgs 类的事件参数,例如:
123public class ButtonClickEventArgs : EventArgs { public string Tim ...
C#进阶(三)-委托
什么是委托?
委托(delegate)类似于C++中的函数指针,是类型化了的函数指针(一种可以存放特定参数/返回值类型方法的容器),可以作为参数一样被传递。
12345678910111213141516// 定义了一个返回值和参数为空的委托类型private delegate void MyDelegate();// 声明MyDelegate类型的一个委托private MyDelegate _myDelegate;public void Main(){ // 为委托赋值方法 _myDelegate = Test; // 调用委托 _myDelegate();}// 挂载到委托上的函数,其参数和返回值需与委托一致,否则不会被感知public void Test(){ Console.WriteLine("Test");}
这时有人就会有疑问了:这和直接在 Main 中调用 Test 方法有什么区别呢?
1234567public void Main(){ Test(); // 执 ...
C#进阶(二)-反射
什么是反射?
反射(Reflection)指程序可以访问、检测和修改它本身或其他程序集状态或行为的一种能力。开发者可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,还可以调用类型的方法或访问其字段和属性。
换句话说,反射指的是程序在运行时可以查看其他程序集或自身的元数据。一个运行的程序查看本身或者其他元数据的行为就叫做反射。函数在执行时,通过反射可以得到其他程序集或自己程序集代码的各种信息(即元数据信息,包括类、函数、变量、对象等等),可以实例化它们、执行它们或操作它们。
反射可以在程序编译后获得信息,提高了程序的拓展性和灵活性。
程序运行时得到所有元数据,包括元数据的特性
程序运行时实例化对象、操作对象
程序运行时创建新对象,用这些对象执行任务
程序集
程序集是由编译器编译得到的、供进一步编译执行的中间产物,在windows系统中一般表现为后缀为.dll(库文件)或.exe(可执行文件)的文件
元数据
描述数据的数据,例如程序中的类、类中的函数、变量等信息就是程序的元数据。元数据保存在程序集中
反射语法
Type
Type类是类的信息 ...
C#进阶(一)-闭包
什么是闭包?
C#中的闭包(Closure)指函数可以访问其定义作用域之外的变量(外部变量)。换句话说,一个闭包就是一个“捕获”或“携带”了其生成的环境中、所引用的自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。不止C#中存在闭包的特性,JS中也存在闭包。
C#闭包最常见的例子是在匿名函数(或Lambda表达式)中捕获自由变量,例如:
12345678910public static Action a; public static void Main(string[] args) { for (int i = 0; i < 5; i++) { a += () => { // 闭包捕获i Console.WriteLine(i); }; } a();}
为什么输出全是 5 而不是 1, 2, 3, 4, 5 呢?那是因为for ...
基于Gerstner Wave算法的海洋实现
介绍
Sin Wave的局限
在引入Gerstner Wave之前,人们更多使用正弦波来模拟海浪,因为正弦波天生具有类似海浪的形状。但它的局限也很明显,那就是波峰过于圆润,纵然有办法通过代数方法约束波峰的形状,但效果还是无法达到预期的尖锐。
什么是Gerstner Wave
Gerstner Wave是Sin Wave的后继者,到现在也是一种常用的用来模拟海洋波浪的算法。他的历史其实已经很古老了,可以追溯到1986年。相比于快速傅里叶变换(FFT),Gerstner Wave方法的开销更小,效果也很真实,因此被更多的应用与游戏领域(FFT更适合影视行业,因为它的效果更好但开销更大)。
Gerstner Wave的公式如下:
P(x,y,t)=(x+∑(QiAi∗Di.x∗cos(ωiDi(x,y)+φt))y+∑(QiAi∗Di.y∗cos(ωiDi(x,y)+φt))∑(Ai∗sin(ωiDi(x,y)+φt)))P(x,y,t) = \begin{pmatrix}x + \sum({Q_{i}A_{i} * D_{i}.x * \cos(\omega_{i} D_{i}(x ...
水面、波浪、白沫和焦散
介绍与准备
我最近打算开始做毕设项目的场景啦XD!这个项目的名字叫元素宇宙(Elemental Universe),是一个化学元素拟人世界观下的小宇宙的故事,目前只有我一个人在做,第一步是搭场景。主场景打算用polyBrush磨出来,其中有一些湖泊和海洋。海洋部分我打算用GerstnerWave的方法来做,湖泊部分采用本文所介绍的方法(当然,还有很多可以改进的地方。有改进的部分会在“更新说明”里提及)。
废话交代完了,现在正式开始吧:
首先,准备一个细分好的平面(因为我们需要一定数量的顶点)作为水面。在这个项目中我使用的是:
演示模型来自Sketchfab,使用遵守CC版权协议。如下预览所示,模型原本的水面只是一块普通的、smooth值几乎设置为1的水平面:
Little Pond & fish by Kenny Kwok. on Sketchfab
波浪
波浪的基本原理是顶点动画,通过修改水面顶点y值(高度)实现,这也是为什么前文提到我们需要一块顶点数比较多的面,越多的顶点意味着越高的波浪精细程度。
我们现在希望的事情是:水面能够上下波动形成波浪。对于湖泊这样较为波 ...
Lua实现面向对象
最近在实习,接到的第一个任务是用公司的自研引擎制作一款抖音特效《宅家舞蹈秀》并于五一期间上线抖音。目前特效已经上线了,不过数据不是很好看,希望之后能被推起来吧orz。
说到自研引擎,给我的感觉和 Unity 一类的游戏引擎很像,只不过有些功能开发的不是很成熟(或者说健全),会有一些偶发的诡异的bug(例如粒子播放失败)。虽然存在些许问题,但这款引擎的功能足够满足抖音小游戏/抖音特效这样轻量级的开发了。
自研引擎所使用的编程语言是Lua。Lua是一个灵活且轻量的脚本语言,在实际的游戏开发工作流中,可以用它结合xLua框架来实现游戏的热更新,也可以直接用Lua开发游戏(特别是一些早期的小游戏)。Lua本身并没有提供面向对象的特性,当游戏系统变得复杂时我们该怎么办呢?
在现代游戏编程中,我们会采用具有面向对象特性的语言(例如Java、C#、C++)来开发游戏。面向对象有三个特性,分别是:
封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节
继承:从已有的类中派生出新的类称为子类,子类继承父类的数据属性和行为,并能根据自己的需求扩展出新的行为,可以提高 ...
贴地雾效实现
接上篇文章画的饼,这回我打算试着实现一下雾效,我们最终的目标就是实现类似下图的效果。当然,纪念碑谷中的不怎么被雾影响的方块和浸泡在雾气中的方块使用的应该是两种shader,他们不会混在一起。但如果我们要实现的是一个可以移动的场景呢?使用我想在纪念碑谷的静态贴地雾效上加点东西,即:当摄像机移动时,雾的范围会随着摄像机的移动而移动,始终做到远处模糊、近处清晰的效果。
思路
其实我们实现纪念碑谷中的这种雾效的思路和全局渐变非常类似。总结一下,主要有以下两点需求:
离摄像机越远,雾气越浓,具体表现在物体整体与雾气的颜色相融
离摄像机越近,雾气越轻,具体表现在雾气很浅,几乎消失
即,离相机较远的物体如下左所示;离相机较近的物体如下右所示:
我们可以调整地平线(渐变中心位置)的值来实现这个效果。当一个物体离摄像机较近时,它的渐变中心也较低;反之则较高。
实现
变量
我们需要这么几个变量:
渐变纹理
主色调
雾的颜色
雾的浓度
12345678Properties{ _RampTex("渐变纹理", 2D) = "white&q ...