2024GGJ🌏 - CluckCluck复盘——新输入系统与手柄控制
新输入系统(Input System)
Unity的新输入系统是一个基于事件的新系统,它在编辑器里提供了一个中间层(Input Action Asset),用户可以在其中自定义一系列玩家动作和其对应的操作。例如在CluckCluck中,我设定了移动(left joystick)、冲刺(B)、拾取道具(right trigger)三个控制:
完成操作设置之后,保存Input Action资产,在其检查器中可以点击生成对应的C#类,接下来就可以在代码中通过事件绑定的方法使用这些操作监听。
同屏双手柄(1p2p)
由于我们做的是一个比较欢脱的双人派对竞技游戏,这次jam还有一个重要的需求是同屏双手柄控制。我不想像懂哥一样用“PlayerController1.cs”和“PlayerController2.cs”来区分不同玩家(因为如果采取这种模式,如果玩家数量增多,需要维护的脚本就会变多),所以如何在一个脚本中为不同玩家绑定不同的控制器尤为重要。
首先是声明p1/p2,它们都用了同一套InputAction “Player2”(之所以是这个命名是因为Player1是键盘控制)。通过游戏对 ...
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 而造成发热和卡顿(特别是对于低端机型来说)。
那么,该怎么优化呢?我们优化的主要目的就是减少绘制调用(drawcall)。绘制调用发生在 CPU 与 GPU 中间,当 CPU 打包好渲染数据(例如顶点、纹理、材质等)之后,就会发起一次绘制调用,将这些信息打包到显存以通知 GPU 进行绘制。也就是说,有多少个绘制调用,CPU 就需要向 GPU 发送多少次通知。drawcall 太高,意味着有频繁地改变渲染状态,这是很慢的操作(相对于GPU执行其内部的渲染流水线来说),因此绘制调用的次数越多对性能影响越大。
动静分离
Canvas有两个重要的操作:重合批和重建。所有的 UI 元素都需要挂载在 canvas 下,而当一个 canvas 中包含的 mesh(只需要一个)发生改变时(例如SetActive、transform的改变、颜色改变、文本内容改变等等)就会 ...
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,即环境场景查询)来实现的。我们可 ...
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类是类的信息 ...