|
原文:Introduction To UFPS: Unity FPS Tutorial
作者:Anthony Uccello
用一支散弹枪轰杀大片凶恶的敌人或者在战场上小心翼翼地狙杀你的对手是一种爽到极点的游戏体验。以动作+射击为主的游戏被称作第一人称射击游戏(FPS)。它有一个类似的种类,即第三人称射击游戏——区别在于你看到的画面是角色的后背还是枪管的下方。
在编写 FPS 游戏时,毫无疑问需要做大量工作。但是,我们没有必要从 0 开始,你可以使用一个叫做 UFPS(终极FPS)的框架!
在本文中,我们将介绍 UFPS 是什么以及如何用它创建一个基本的 Unity FPS “沙盒”游戏——“沙盒”一词意指玩家被限制在一个有限的空间内。
读完本文,你将学会:
用简单的 UFPS 脚本让玩家移动和开火使用 vp_FP 组件当子弹击到平面时显示弹孔为玩家添加内置的 FPS 动作添加新的枪具并配置属性添加带瞄准镜的大狙(最好的枪)
预备知识
你应当熟悉 C# 脚本并关联组件到 GameObject 对象。如果你没接触过 Unity 或者需要重温一下,请参考我们的 getting started in Unity 教程。
你还需要一份 Opsive 公司的 “UFPS: Ultimate FPS” 许可协议,这份协议你可以从Unity Asset 商店获得。这篇教程是用 UFPS 1.7编写的。
最后,你需要使用 Unity 5.5.0 或以上的版本。
开始
UFPS 是一个 Unity 插件,包含用于支持典型 FPS 游戏的核心逻辑。不需要自己为每个场景编写脚本,你可以通过修改预置脚本来处理常见的任务比如:
装备枪支枪的移动和开火管理弹药和库存玩家的移动管理相机视角跟踪相机管理事件,比如被激光枪击中管理平面,比如滑坡和水道尸体及重力物理
好的游戏不可能一蹴而就。做游戏的最好方式是把注意力集中在基本功能上,比如核心玩法,然后以此为基础逐步完善。UFPS 能够帮你打好这个基础并且还提供一个更加庞大的框架给你使用。
下载开始项目:ReadyAimFire_Unity3D_starter。
在 Unity 中打开项目,双击 Main 场景加载游戏场景。
注意:我们不能再次分发 UFPS 的源文件,因此这个教程中不会有一个最终项目,它们也不包含在开始项目中。你需要自己完成整个游戏:]
在 Unity Asset 商店中找到 UFPS,找到下载栏,点击下载按钮。下载完成后,点击 Import 按钮,稍等一会显示出包的内容,然后点 OK。包很大,请耐心一点。
注意:你可能会看到一个关于“importing a complete project”的警告:
这是因为 asset 中包含了一些项目设置,这些设置会覆盖你当前 open project 设置。在这里,只有 Graphics 和 Tag 设置会被覆盖,因此你只需要点击 Import 按钮继续导入 UFPS 即可。
导入完成后,你可能会看到几个警告。这些警告来自于 UFPS,直接忽略即可。
从 File 菜单选择 Save Scenes,保存修改。点击 Play 按钮,感受一下全新的项目。
重要概念
你肯定非常想射击(或者编写脚本),但理解 UFPS 框架的一些关键概念非常重要。
FP 组件
FP 包含了一个组件,它属于一个脚本族,这个脚本族用于管理真实的第一人称射击对象——也就是玩家。这些组件都以 vp_FP 开头。其中最重要的组件是 vp_FPController 脚本,在玩家对象被创建的时候就关联了这个脚本。
状态
状态是一个容易混淆的概念。在许多 vp_FP 逐渐中,你都会找到另一个名为 State 的属性,例如:
某个 vp_FP 脚本用一个 state 字段来记录一些本地变量的值,这些变量用于表示组件在指定时间点的状态。设想一下:当你放大并使用狙击步枪的瞄准镜时,你可以处于一种“Zoom”状态—— 这个状态下的预设值应当变大,同时改变枪的位置和角度以对准中心位置。当你 Zoom 的时候,它也会让武器的移动呈现出动画。
事件
事件是对象之间的信号,用于表示某些重要的事情发生了,其它对象必须做出响应。例如,当某样东西被射中时。“被射中”就是一种事件——也可以说一个信号。根据你的游戏设定,其它对象会对此做出响应。这篇教程不会过多介绍事件。
好了,伙计,干得不错。
创建玩家预制件
在项目浏览器的搜索栏中输入 SimplePlayer,然后将 SimplePlayer 预制件拖到场景中,放到靶子前面。
在结构视图中选中 SimplePlayer 这个 GameObject,设置它的 Position 为 (X = -26.6, Y = 1.3, Z = -21.3) ,Rotation 为 (X = 0, Y = 0, Z = 0)。
从结构视图中删除 Camera 这个 GameObject。我们不需要它,因为在 SimplePlayer 上的镜头会给你一个第一人称视角。
点击 Play,你创建了一个 FPS 游戏并且可以运行了!
在开枪或使用瞄准镜的的时候按下 play,会将光标锁定到屏幕上。按下 Escape 又会重新获得光标。
使用 WASD 键移动或者按下鼠标左键开枪,右键打开瞄准镜——你可以在打开瞄准镜的同时开枪。还可以用 Shift 键跑,用空格键跳。请打到靶子上:]
注意:在编写本教程的时候,UFPS 有一个 Bug,当你第一个试玩一个场景时,不能看到枪口焰。解决办法是点击鼠标右键打开瞄准镜一次——后面就正常了。
后面有许多逻辑,但 UFPS 好的地方就是它会为你完成许多工作。它让你把注意力放在更重要的事情上,比如为武器添加自定义的行为。你会惊喜地发现根本不需要为玩家的移动和武器的开火创建任何动画。你看见的动作使用了物理引擎。这也是由 UFPS 完成的!
UFPS 开始只有一把小手枪,但开始项目中还包含散弹枪和狙击步枪的预制件。你可以简单地拖入这两个预制件并修改 UFPS 设置以完善动画效果。
尽管不需要管枪支的动画,但你需要自己实现手的动画。这篇教程不会设计手的动画,UFPS 也不会包含手的动画。但是,如果你要创建一个正式的 FPS 游戏,可能需要考虑实现手的动画。
最后,你可以为枪支模型添加复杂动画。这样,你就需要在模型中包含手。
添加弹孔
你会听到枪声并看到弹壳跳出!在枪口还会有很酷的枪焰喷出!UFPS 为你实现了这些,它有一个特性运允许你轻易地添加和配置新枪。
尝试向某些东西开火,看看到底还差了点什么?这个枪难道是射到了空气中吗?为什么没有弹孔?
当你射到某样物体时,你会留下一些痕迹。终止游戏。在结构视图中,点击 Eviroment GameObject 左边的下拉箭头,选择子 GameObject Wall。
在检视器中,点击 Add Component,在搜索栏中输入 vp_SurfaceIdentifier。点击 vp_SuerfaceIdentifier——唯一的脚本——然后添加它。点击 Surface Type 字段右边的小圆点,在弹出菜单中,选择 Assets 标签,然后选择 Metal。
在结构视图中选中 Ground 这个 GameObject,重复上述步骤,但在最后一步选择 Wood。
枪靶需要一个木制的表面痕迹,因此在结构视图中选中 Target 和 BackTarget GameObject,点击旁边的下拉箭头,选择它们的子 GameObject,然后添加 vp_SurfaceIdentifier 脚本组件,表面类型设置为 Wood。
点击 Play 然后分别向标靶、墙和地板射击,现在你可以检查你的手艺如何了 :]
如何使用 UFPS 手枪
仅仅用小手枪是无法应付一堆恶心的怪物的,你觉得呢?过一会你会添加散弹枪,但我们先来理解如何使用手枪会更简单一点。
在结构视图中,展开 SimplePlayer,然后展开 FPSCamera GameObject。选择 1Pistol GameObject。
武器前面的编号 1 表示玩家在游戏期间可以按这个数字来装备手枪。当你添加新武器的时候,你可以向上增加这个数字,UFPS会自动处理换枪的问题。
1Pistol 有几个重要组件:
AudioSource 让枪开火时发出 bang 的声音。vp_FPWeapon 将该 GameObject 识别为 UFPS 框架中的一种武器并决定了和这个武器所对应的外观。vp_FPWeaponShooter 负责枪焰、弹壳弹跳和子弹的效果。vp_FPWeaponReloader 负责装弹时的声音和动效。
注意: 弹药和装弹不属于本文内容,但它们也属于创建游戏时需要考虑的内容。在 UFPS 文档中关于这些 vp_FP 组件的完整介绍包含了许多章节,因此当你遇到问题时,请首先参考这个文档。
vp_FPWeapon
在检视器中,展开 vp_FPWeapon 脚本组件的 Position Springs 一节。这个组件用于设置枪在屏幕上的位置,它是根据状态改变的——例如,当手枪在 Zoom 状态下移动的时候。Offset 是枪在屏幕上的位置。
点击 Play,通过修改 Offset 值随意调整枪的位置。通过这种方式,可以让你在试玩模式下不会搞乱游戏的真实值。
在试玩模式下修改设置是一种测试和调试的好方法——唯有一点,如果你真的想改变这些设置,请不要在试玩模式下。这是非常容易忘记的事情,忘记自己仍然处于试玩模式是,这往往会导致额外的工作量并让你爆出粗口。
从这个组件往下看,找到 Spring2 Stiffn 和 Spring2 Damp — 这是枪的后坐力设置。Spring2 Stiffn 即 ‘弹性’, 或者开火后枪如何弹回它原来的位置。Spring2 Damp 是弹回的速度有多快。最简单的办法是通过实际操作来体验:]
点击 Play 然后拖动 Spring2 Stiffn 和 Spring2 Damp 值,感受不同的效果。
要使最终的动画满意,可能需要做一些实验并付出点代价,因此一点也不要奇怪,头几次很可能枪就像一只弹簧弹来弹去。
你已经知道 Position 了,接下来我们看一下 Rotation。在检视器中,展开 vp_FPWeapon 的 RotationSprings 节。找到 Offset 值,它和你在 PositionSrping 节中看到的是一样的。所不同的是这些值决定了枪的角度。
点击 Play 并修改一下 Offse 值试试。
试想一下当你开枪的时候会发生什么。UFPS 通过插值的方式在两个状态间实时创建动画。
你所需要的仅仅是在 vp_FPWeapon 组件中设置各个状态值——不需要创建或导入导致这种效果的动画。你会在添加散弹枪和狙击步枪的时候亲身体会这一点。
停止游戏。
进行一点深入讨论。在 vp_FPWeapon 的检视器中展开 States 节,看一下已有的状态及与之关联的数据文件。数据文件在状态改变时 vp_FPweapon 中的什么记录值会发生改变。
以 Zoom 状态为例。双击 WeaponPistolZoom,看一下脚本文件——Unity 默认的代码编辑器会打开。它是一个简单数据文件:
ComponentType vp_FPWeapon
ShakeSpeed 0.05
ShakeAmplitude 0.5 0 0
PositionOffset 0 -0.197 0.17
RotationOffset 0.4917541 0.015994 0
PositionSpringStiffness 0.055
PositionSpringDamping 0.45
RotationSpringStiffness 0.025
RotationSpringDamping 0.35
RenderingFieldOfView 20
RenderingZoomDamping 0.2
BobAmplitude 0.5 0.4 0.2 0.005
BobRate 0.8 -0.4 0.4 0.4
BobInputVelocityScale 15
PositionWalkSlide 0.2 0.5 0.2
LookDownActive false
当 vp_FPWeapon 进入 Zoom 状态时肯定会有某些变化发生。 看起来有点吓人,但其实非常简单——你可以在试玩期间改变枪的位置,然后保存状态文件。
vp_FPWeaponShooter
在检视器中展开 vp_FPWeaponShooter 下面的 Projectile 节。这个决定了枪的开火方式。
Firing Rate 是多长时间触发一次动作。 Tap Firing 允许玩家不需要等待开火的动画完成直接射击——它修改了开火动画。Prefab 是子弹。子弹的脚本位于 vp_BulletFX 下,要了解更多请参考这个文档。
展开 Muzzle Flash 小节。你可以设置这个美工预制件,它的大小、位置以在玩家开枪后显示完美的枪焰效果。
展开 Shell 小结,看一下这些设置,它们负责完成当弹壳从枪管跳出后的动作。
最后,展开 Sound 小节。这些设置负责开枪时和空枪时的声效。
在这里还会有一个 States 节。它和 vp_WPWeapon 节的状态栏类似。如果你不知道它们的用途,你可以查看源代码,里面包含了一个高级主题 Refelection——在本文最后有一个链接,你可以参考它。
添加散弹枪
编写一个不让主角拥有短程毁灭性武器的 FPS 游戏有什么意义?那还不如不要写。没有这种武器的只可能是一个非常小、非常小的 FPS 游戏。
回到 UFPS 上来,你将不废吹灰之力就添加一个枪械模型。
在项目浏览器中,输入 2Shotgun 然后将 2Shotgun 预制件拖到结构视图的 1Pistol GameObjecdt 下面。
你会发现 vp_WeaponShooter 没有任何选项,那是因为预制件被禁用了。将 2Shotgun 左边的选项框勾上。
你可能也想到了,你只需要运行游戏然后按 2 就可以装备上散弹枪了?差不多吧!但是 SimplePlayer 预制件只配置了手枪。
为了让新枪能够生效,你需要添加了一个新组件。在结构视图中点击 SimplePlayer GameObject,然后在检视器中点 Add Component。输入 vp_FPWeaponHandler,选中这个脚本。这个脚本允许更换武器。
将 Start Weapon 设置为 1,即手枪。现在,你可以点击 Playe 然后按 2 了,你就可以从手枪切换到散弹枪。
等等……有点不对劲——枪的位置不对!在结构视图中选中 2Shotgun,在检视器中展开 vp_FPWeapon 的 PositionSprings 节,将 Offset 设为 (X = -0.04, Y = -0.46, Z = 4.89)。 展开 RotationSprings 节,将 offset 设置为 (X = -1.09, Y = -93.3, z = 2.5)。
散弹枪会发射一片子弹。一次只射出一颗子弹就有问题了。
展开 vp_FPWeaponShooter 下面的 Projectdile,将 Count 设置为 10,Spread 设置为 2。
展开 MuzzleFlash 将预制件设置为 MuzzleFlashShotgun。
将 Position 设为 (X = -0.08, Y = -0.24, Z = 8.88) ,Scale 为 (X = 1, Y = 1, Z = 1)。如果你想修改这些值,可以在试玩过程中编辑 MuzzleFlash Position,然后停止游戏后再修改 Position——这样枪焰就会精确地出现在你指定的位置。
展开 Sound 小节,将 Fire 设置为 ShotgunFire。
点击 Play 并按下数字 2,在靶心上射出一些弹孔。
Zoom 是一种特殊的状态,在 UFPS 中它被预设为 RMB(鼠标右键)来触发。点击右键后,会将所有组件设置为 Zoom 状态,这些组件必须监听这种状态。
你可以添加自己的 Zoom 状态。来试一下,点击 Play,按下 2 换成散弹枪。修改 vp_FPWeapon 的 Position Springs 和 Rotation Springs Offset 值,让镜头指向枪管下面。点击位于 vp_FPWeapon 底部的 Save 按钮,将文件名命名为 ShotgunZoom 然后点击 Save。
注意:如果你无法让 Zoom 显示正常,或者你使用的 Mac 上无法看到 vp_FileDialog 窗口中的 Save 按钮,请下载这个文件 ShotgunZoom并将它拖到 Unity 的 Assets 文件夹。
设置完 Zoom 之后,散弹枪并不知道怎样去用它。停止游戏,点击结构视图中的 2Shotgun GameObjecdt,在检视器中展开 vp_FPWeapon 的 States 小节。将 ShotgunZoom 拖到 Zoom 字段。
UPFPS 利用开始值和终止值创建动画。点击 Play,当你换上散弹枪并切换到 Zoom 模式下时,它会以动画方式移动到你所保存的 Zoom 状态的位置。
添加狙击枪
一个好的 FPS 游戏都会提供一系列武器以便玩家能够在各种条件下消灭敌人。这次你需要在一个安全距离下一个接一个地搞定,没有什么比一直大狙更好的武器了!
在项目浏览器中搜索 3Sniper 并将 3Sniper 预制件拖到结构视图的 2Shotgun GameObject 下面。在检视器中,和前面一样,添加 vp_FPWeapon 和 vp_WeaponShooter 脚本。
在项目浏览器中展开 Assets/Prefabs 文件夹,将狙击步枪的模型添加到 vp_FPWeapon 的 Rendering 节。
在结构视图中,点击 3Sniper 这个 GameObject,在检视器中展开 vp_FPWeapon 组件。展开 Rendering 节,找到 1st Person Weapon(Prefab) 字段。将 m24 预制件从项目浏览器中拖到 1st Person Weapon(Prefab) 字段。
在 vp_FPWeapon 下面,将 Position Springs 设置为 (X = 1.4, Y = -0.81, Z = 4.88) ,Rotation Springs 设为 (X = -2.7, Y = 79.62, Z = -5.01)。展开 vp_FPWeaponShooter 的 Sound 小节,将 Fire 设置为 SniperShot。
然后就可以使用大狙了。点击 Play,按数字 3 试一试。别忘了开枪试试 :]
你是不是觉得“可以用” 是有点夸张了?在你瞄准的时候没有瞄准镜!完这个游戏的手,添加一个瞄准镜是非常有用的。
接下来添加一个 Sniper 组件,用于瞄准。这是一个特殊脚本,为了能够正常工作,它需要监听玩家事件。
打开 Scripts 文件夹,在任何地方右键,使用弹出菜单创建一个新的脚本,名为 Sniper。双击这个文件打开,将内容编辑为:
using UnityEngine;using System.Collections;publicclass Sniper : MonoBehaviour{ //1public vp_FPPlayerEventHandler playerEventHandler; public Camera mainCamera; privatebool isZooming = false; privatebool hasZoomed = false; //2void Update() { if (playerEventHandler.Zoom.Active && !isZooming) { isZooming = true; StartCoroutine("ZoomSniper"); } elseif (!playerEventHandler.Zoom.Active) { isZooming = false; hasZoomed = false; GetComponent<vp_FPWeapon>().WeaponModel.SetActive(true); } if (hasZoomed) { mainCamera.fieldOfView = 6; } } //3 IEnumerator ZoomSniper() { yieldreturnnew WaitForSeconds(0.40f); GetComponent<vp_FPWeapon>().WeaponModel.SetActive(false); hasZoomed = true; }}代码解释如下:
这些变量用于保存 PlayerEventHandler 和镜头的引用。两个布尔变量用于保存 zoom 状态。这段代码判断 zoom 状态是否启用。当枪处于 zoom 状态时,它会将视图的这个字段设置为 6,这就出发了 zoom 状态。这个子程序判断什么时候 zoom 动画才完成——这里大约需要 0.4 秒左右——然后隐藏枪支模型。这会在切换视野之前给玩家一个缓冲时间。
保存文件,返回 Unity。将 Sniper 基本拖到 3Sniper 这个 GameObject。
将 SimplePlayer 预制件拖到 Sniper 组件的 PlayerEventHandler 上。然后,将 FPSCamera 拖到 Simper 组件的 Main Camera 字段。
在结构视图,你会发现有一个名为 UI 的 GameObject,它是用一个 SniperZoom 的贴图创建的。
进入 Scripts 文件夹,创建一个新的 C# 脚本。命名为为 GameUI。在结构视图中选中 UI 对象,将 GameUI 脚本从项目浏览器中拖到检视器中。
打开 GameUI 脚本,编辑如下内容:
using UnityEngine;using UnityEngine.UI;publicclass GameUI : MonoBehaviour{ public GameObject sniperZoom; public vp_PlayerEventHandler playerEventHandler; publicvoidShowSniperZoom() { sniperZoom.SetActive(true); sniperZoom.GetComponent<Image>().enabled = true; } publicvoidHideSniperZoom() { sniperZoom.SetActive(false); sniperZoom.GetComponent<Image>().enabled = false; }}两个公共方法分别用于显示和隐藏 sniper scope 贴图——这里保存了一个 scope 纹理的引用和事件处理器的引用。Sniper 脚本经过配置后会调用这两个方法。保存脚本,返回 Unity。
在结构视图中,展开 UI 对象。将 SniperZoom 拖到检视器中的 SinperZoom 字段,然后将 SimplePlayer 拖进 PlayerEventHandler 字段。
打开 Sniper 脚本。在头部添加变量:现在,这个脚本将引用 GameUI。
在 else if (!playerEventHandler.Zoom.Active) 语句下面,加入:修改之前是 :
elseif (!playerEventHandler.Zoom.Active){ isZooming = false; hasZoomed = false; GetComponent<vp_FPWeapon>().WeaponModel.SetActive(true);}修改之后变成了:
elseif (!playerEventHandler.Zoom.Active){ gameUI.HideSniperZoom(); isZooming = false; hasZoomed = false; GetComponent<vp_FPWeapon>().WeaponModel.SetActive(true);}当处于“非-zoom”状态时,sniper zoom 图片将隐藏。
在 GetComponent().WeaponModel.SetActive(false); 后面添加:修改之前的代码是:
IEnumerator ZoomSniper(){ yieldreturnnew WaitForSeconds(0.40f); GetComponent<vp_FPWeapon>().WeaponModel.SetActive(false); hasZoomed = true;}修改之后变成了:
IEnumerator ZoomSniper(){ yieldreturnnew WaitForSeconds(0.40f); GetComponent<vp_FPWeapon>().WeaponModel.SetActive(false); gameUI.ShowSniperZoom(); hasZoomed = true;}这句在狙击步枪进入 zoom 状态之后显示 sniper scope 图片。保存,返回 Unity。选择 3Sniper 对象,将它拖到 UI 对象的 GameUI 字段。
所有的工作是为了在玩家进入瞄准状态时把枪隐藏,同时显示瞄准器视野。运行游戏,按下数字键 3,换成大狙,点击 RMB (鼠标右键)切换到 zoom 状态。
起作用了,但还不完善。你可以通过一些设置进行改进,让玩家在瞄准的时候能够看一眼瞄准镜。
点击 Play,按 3 切到大狙。展开 3Sniper 的 Position Spring 和 Rotation Springs,调整位置和角度的 offset 值,直到镜头前有瞄准镜显示出来。
点击 vp_FPWeapon 组件底部的 Save 按钮。文件命名为 SniperZoom 然后点 Save。
我们将大狙进入瞄准状态时的状态保存到了文件里。这里,你将枪上移,进入镜头前面显示出来,就像玩家在低头看瞄准镜一样,SniperZoom 中刚好保存了这个状态。
注意:如果你无法创建或保存你的设置,请下载此SniperZoom文件。
现在,你已经设好了状态,但还需要创建要用到的状态。选中 3Sniper,展开 vp_FPWeapon,展开 State 一节。
点击 Add State 按钮,将 Untitled with Zoom 替换成 new State 字段。
从 Assets 文件夹将 SniperZoom.txt 文件拖到 3Sniper 的 vp_FPWeapon 组件的 Text Asset 字段。
吃枪子去吧!
在你能够狙击猎物之前,还有几个工作要做。在枪里面装入子弹是个不错的注意——当然,你可以跳过这一步,但这就不是狙杀了。前面的手枪和散弹枪都创建过子弹。我们从头开始创建大狙的子弹,因此你需要设置子弹所用的预制件。
仍然是在 3Sniper 中,展开 vp_FPWeaponShooter,再展开 Projectile 一节。点击 Prefab 右边的小圆圈按钮。找到并选择 PistolBullet 预制件。
点击 Play,瞄准距离最远的枪靶——你可能需要转个方向。按下 RMB 瞄准、开枪!
现在,在进入 zoom 状态之前,你会看见狙击镜显示。干得不错!
结束
噢,又到说再见的时候了。从何说起呢?UFPS 是一个强大的框架,这篇教程仅仅论及皮毛。
接下来的步骤应该是添加一只突击步枪和令人满意的动画。试着修改 vp_FPWeapon 中的滑竿和值直到感觉满意。记住,它的射速要快,每次射击后会产生快而小的后弹动作。
你学会了基本的概念,但在 UFPS 文档中还有大量的东西需要学习。比如,你还不知道什么是 vp_FXBullet。在 FPS 游戏中有一个关键特性就是库存和弹药管理,它设计到 UI 的使用。
你也没有体验到许多 UFPS 的超酷的特性——因此请继续学习它们。
关于 Unity 的 UI 系统,你可以参考以下教程:
Unity UI Part 1
Unity UI Part 2
Unity UI Part 3
或者购买Unity Games by Tutorials一书,学习如何编写棒棒的游戏。
正如我之前所说的,reflection 是一个高级技术,它在你的编码工具箱中是个很好的工具,因此请你有机会一定要阅读它。
通过这个官方 Unity 事件教程(https://www.youtube.com/watch?v=3NBYqPAA5Es),你可以更深入地学习事件系统。
有任何问题、建议或需要更多的 UFPS 教程,请在论坛中留言。你的评价将决定我们下一个主题会介绍什么——请告诉我!
致谢
特别感谢 Wojciech Klimas 提供了枪械模型,以及其他资源:
网站
散弹枪的 Asset
狙击步枪的 Asset |
|