如何用Unity制作3D第一人称射击游戏?
在本教程中,我们详细学习如何制作一个简单的第一人称射击游戏(FPS)。它将介绍3D游戏编程的一些基本概念以及如何像游戏程序员一样思考的一些技巧。
先决条件
本教程假设您熟悉Unity软件的基本操作,并掌握基本的脚本概念。
创建新项目
下载压缩文件FPS_Tutorial.zip,解压,在Unity中打开项目文件。
从Unity安装目录导入标准资产资源包。
导入项目后,您将在Unity项目面板的“标准资源”文件夹下看到这些资源的内容。当我们导入新资源时,最好按照功能分组,比如火箭、爆炸、音频等。
设置游戏环境
导入资源后,您会注意到项目面板中有许多文件夹。
在“项目”面板中,从文件夹对象/mainLevelMesh中选择mainLevelMesh。
在参数面板中,在FBXImporter选项中,你会找到“生成碰撞器”选项,并选中这个选项。如果不做这一步,玩家会穿越地面,直接坠入深渊(其实就是开启了“碰撞”,产生了互动)。
将“mainLevelMesh”拖放到场景中。
场景不需要添加灯光,所有场景都已经应用了光照贴图。整个场景中的所有灯光都是通过光照贴图来渲染的,并且使用了“预烤阴影”。光线贴图对显示效果很有帮助,尤其是在复杂的光照环境下。
现在,您可以向场景中添加一个角色。
添加主要角色
让我们在场景中添加一个可控制的角色对象。Unity预置了很多第一人称射击游戏的内置控制器,在工程面板标准资产->中;;在预制屋下面。
添加一个第一人称控制器,点击工程面板中标准资产旁边的小三角形,就会弹出资源列表。找到预置文件夹,点击小三角形弹出资源列表。将“第一人称控制器”拖到场景中。
此时,场景中会出现一个代表玩家的圆柱体,三个大箭头代表物体在3D空间中的位置(如果没有看到箭头,选择物体,按“W”键),白色表面代表物体当前的视角。现在FPS控制器处于默认视角位置,您可以通过移动它来更改游戏视图。将角色移动到游戏环境关卡地面以上的位置。
主摄像头现在没用了,可以删除了。
点击“播放”键,现在你应该可以使用鼠标和键盘(光标或“W,A,S,D”)在这一关的地形上移动了。
现在我们已经创建了一个非常简单的FSP,让我们给角色添加武器。
增加武器
接下来我们会给游戏角色一个类似手榴弹的物体,可以在游戏中发射。为了实现这个功能,你需要创建一些脚本语言来告诉这个武器如何统一行动。
那么我们到底想达到什么目的呢?我们想让游戏角色在摄像机的任何位置开火。不过,还是先想想游戏人物和武器吧。在游戏角色游戏中,是第一人称视角,所以摄像头的位置是和眼睛平行的。如果玩家用武器射击,武器应该射向角色的手的位置,而不是眼睛的位置。这样我们就需要添加一个“游戏对象”来代表榴弹发射器,同时将它放置在游戏角色手持武器的地方。这样就保证了起火位置没有问题。
创建一个武器发射器
首先,创建一个“游戏对象”来代表榴弹发射器。游戏对象是3D世界中的任意对象(人物、关卡、声音),零件是游戏对象的属性。所以我们还需要给游戏对象添加部件:
选择游戏对象& gt从主菜单栏。巨大的空白,在层级面板中命名为“启动器”。请注意,空对象在场景中是不可见的,它仅用于放置导弹发射器。
现在将视野推近场景中的FPS控制器,方便我们放置武器发射器。
在“层次”面板中选择FPS控制器,确保鼠标在场景视图中,然后按“F”键。将窗口置于当前所选对象的中心。
在层级面板中选择发射器,然后在主菜单栏中选择游戏对象>。移动到视图.注意发射器如何靠近FPS控制器。然后使用手柄将发射器移动到角色手的位置。
注意:你可以通过设置这个物体的位置来设置游戏角色是左撇子还是右撇子,不需要写代码。
使统一窗口模式成为“2×3”模式(window >;布局& gt2乘3),单击播放按钮。确保在层次面板中选择发射器,移动角色,同时观察场景窗口。你会发现启动器不会随着角色移动(现在再次点击播放按钮停止运行游戏)
让我们来解决这个问题。在层次面板中,将发射器拖放到FPS控制器下的主摄像机上。在弹出的对话框中单击“是”。再次运行游戏,观察场景窗口。发射器与角色的移动是一致的。所以我们把发射器和摄像机联系起来。
制造导弹
让我们创建一个当玩家点击发射按钮时可以发射的导弹。
让我们用一个简单的物体球来代替导弹。Unity主菜单栏点击资产& gtCreat & gt;预设创建一个预设对象,并将其命名为“导弹”。
创建一个球体(游戏对象>;创建对象& gt球体)
在层次面板中,将球体拖放到导弹的预制对象上,预制对象的图标会发生变化。可以从“层次”面板中删除球体。
提示:游戏操作过程中产生的任何游戏物体都应该是一个预置。
编写导弹发射器脚本
FPS控制器是一个预制的对象,包含几个游戏对象和组件。FPS控制器本身就是一个圆柱体,只能沿Y轴旋转。所以如果我们直接把发射器脚本给FPS控制器,是不可能上下火的。所以我们把脚本交给控制器里可以旋转的主摄像头。
让我们编写描述发送方行为的第一个JavaScript代码。
单击资产& gtGreate & gtJavaScript,创建一个空的JavaScript文档。一个名为“NewBehaviourScript”的资源将出现在“项目”面板中,它将被重命名为“MissileLauncher”。
提示:通过Unity & gt;首选项单击外部脚本编辑器来自定义外部脚本编辑器。
在项目面板中创建一个“武器脚本”文件夹来放置我们所有的武器脚本。将导弹发射器脚本和导弹预设拖到这个文件中。
让我们来看看导弹发射器的完整JavaScript脚本。
进一步想,我们想达到什么效果?我们需要检查玩家是否按下了发射按钮,然后生成一枚导弹,然后沿着玩家面对的方向以一定的速度发射出去。让我们仔细分析一下这个脚本:
var弹丸:Rigibody
var速度= 20;
功能更新( )
{
这是脚本的开始,它定义了一些属性并打开了“更新”功能
如果(输入。GetButtonDown("Fire1 "))
首先,我们需要检查玩家是否按下了开火键。“Fire 1”映射到鼠标左键和当前配置的键盘上的按钮(可以点击主菜单栏中的Editor & gt;项目设置& gt输入设置)
{
var instantiated projectile:rigid body = Instantiate(
投射,变换.位置,变换.旋转);
我们使用变量来定义生成的对象。变量的类型是Rigibody,因为导弹有物理属性。
Unity中用来生成新对象的函数是Instantiate,它有三个参数,即生成的对象、生成的对象的3D空间位置和对象的旋转。它还有另外一个语法结构,参考API手册,我们这里只使用这个结构。
第一个参数Project表示我们想要创建的对象。那么到底推出的是什么?具体生成的对象可以手动设置。实现方法:将Project定义为函数的外部变量,这样就可以在参数面板中显示出来。启动的对象也可以用代码创建,但是如果你想让一个变量可调,就用上面的方法。
第二个参数transform.position使生成的对象与发射器的空间位置一致。为什么是发射器?因为如果导弹的位置没有问题的话,脚本一定是和发射装置有关联的。(transform读取的变换数据是给定脚本的游戏对象变换数据)
第三个参数transform.rotation类似于第二个参数,只是它的值与发射器的旋转值相同。
代码的下一部分使导弹移动。为了实现运动,我们要给导弹一个速度,但是这个速度会产生在哪个方向(x,y,z)?在场景中,单击FPS控制器,会出现运动箭头(如果没有出现,按“W”键),其中一个是红色,一个是绿色,一个是蓝色。红色代表X轴,绿色代表Y轴,蓝色代表Z轴。因为蓝色和玩家面对的方向一致,所以我们要给导弹一个Z轴上的速度。
(速度)速度是实例化项目的一个属性。我们怎么知道?因为instantiatedProjectile是一种刚体,如果我们看一下API手册就会知道速度是刚体的属性之一。还要看刚体的其他性质。要设置速度,我们必须设置每个轴的值。但是有一个小问题。三维空间中的物体一般使用两种坐标模型:局部坐标系和世界坐标系。在局部坐标系中,物体的轴向只与物体本身有关。在世界坐标系中,轴向是绝对的,比如向上,向上的方向对所有物体都是一样的。
刚体。刚体物体的速度必须使用世界坐标系。因此,在定义速度时,需要将局部坐标系中的Z轴(正向)变换到世界坐标系中的相应方向。您可以使用函数转换。TransformDirection,它有三个向量作为独立变量。变量speed也应该定义为一个外部变量,这样以后就可以直接在编辑器中调整这个值。
最后要关掉导弹和游戏角色的碰撞。如果不这样做,导弹在制作的时候可能会和角色发生碰撞。您可以在API手册IgnoreCollision中查看详细信息。
MissileLauncher.js的完整代码如下:
将脚本MissileLauncher交给FPS控制器中的发射器。在“层次”面板中单击“发射器”,并检查“参数”面板下是否显示了MissileLauncher脚本。
之前创建的导弹的预制对象还没有与脚本中的变量项目关联,所以我们需要在编辑器中创建它。可变抛射体只能与一个刚体关联。所以首先要给导弹一个刚体。
在项目面板中单击missiles,然后选择components >;;物理学& gt刚体.这将赋予我们要发射的导弹一个刚体属性。我们必须确保我们想要在游戏中启动的对象的类型与脚本中外部变量所需的类型相同。
在脚本中创建导弹和变量项目之间的链接。首先,单击层次面板中的发射器,然后从工程面板中拖动导弹的预制对象,并将其放置在发射器参数面板中的MissileLauncher脚本部分。
如果你运行游戏,你会发现点击开火按钮可以发出一个受重力影响的小球。
导弹爆炸
接下来,当导弹与其他物体碰撞时,会添加爆炸效果。为了达到这个效果,我们需要写一个新的脚本,然后交给导弹。
创建一个新脚本,并将其命名为Project。拖放到项目面板中的WeaponScripts文件夹。
那么我们希望剧本项目达到什么效果呢?我们需要检测导弹是否碰撞,然后在碰撞点产生爆炸效果。代码如下:
OnCollisionEnter中程序代码的作用是计算给定脚本的对象是否与其他对象发生碰撞。
在OnCollisionEnter函数中,我们主要是想在3D空间中的导弹碰撞点实现一次新的爆炸。那么碰撞发生在哪里呢?OnCollisionEnter函数具有记录该信息的功能。碰撞点的信息存储在变量ContactPoint中。
这里我们使用函数Instantiate来创建一个爆炸。我们已经知道函数instatiate有三个参数:(1)生成的对象;(2)对象的3D空间位置。
(3)物体的旋转。
第一个参数,我们稍后会用粒子系统分配给一个游戏对象。同时我们也想通过编辑器来实现这个功能,所以我们将变量设置为外部变量。
第二个参数,爆炸发生点的位置,就是碰撞发生的位置。
第三个参数,爆炸旋转的设定,需要说明一下。我们需要爆炸体的y轴方向与导弹与其他物体碰撞的表面法线方向一致。也就是说,如果是墙壁,爆炸会朝外,如果是地板,爆炸会朝上。所以实际上我们是想让爆炸体在局部坐标系中的Y轴与导弹与之碰撞物体的表面法线方向(世界坐标系)一致。
最后,我们想让导弹在碰撞后从游戏中消失,这是通过函数Destroy()实现的,它的参数是gameObject(gameObject代表给定这个脚本的对象)。
弹丸. js的完整代码如下:
给导弹预制对象赋予脚本。
接下来,我们将创建一个爆炸效果对象,它是由导弹碰撞时的爆炸产生的。
首先,创建一个新的预制对象(名为Explosion)来存储爆炸效果资源。
标准资产里有一个很好的爆炸预制体,粒子系统和灯光都设置好了。将该爆炸预制对象(在标准资源/粒子/爆炸中)拖放到“层次”面板。
调整此爆炸效果的参数,直到您满意为止,然后将其从“层次”面板拖动到“工程”面板中的爆炸预制对象。
现在配置导弹的爆炸:
单击导弹预制对象,将工程面板中的爆炸拖放到参数面板的爆炸变量列上。
定义爆炸的行为
接下来,我们将创建另一个脚本来定义爆炸本身的特征。
创建一个新的脚本-Disclosure,把它放在武器文件夹中,双击脚本进行编辑。
脚本中另一个常用的函数叫做Start()。函数Start()中的代码只在游戏中生成它所赋的对象时执行一次。我们要实现的是在一定时间后删除游戏中的爆炸。我们使用Destroy()函数的第二个参数来定义删除前的时间长度。
变量explosionTime设置为外部变量,便于调整。
当新脚本插入上面的代码时,函数Update()应该被删除。
把剧本爆炸给爆炸预制体。
音响效果
现在的游戏世界太安静了。让我们给爆炸效果添加一些音效。
首先在爆炸预制(Prefab)中加入一段音频。
在给爆炸添加音效之前,我们要先添加一个音源,在主菜单中点击组件-音频-音源。您会发现音频组件具有音频剪辑属性。
将“RocketLauncherImpact”的音效添加到爆炸预型体的AudioClip的外部变量中。Unity支持多种音频格式。
当你运行游戏并发射导弹时,会有声音!
添加图形界面
我们来添加一个GUI,有点像头显设备(HUD)。我们要做的GUI很简单,就是一个瞄准镜。
添加一个景点:
在项目栏中创建一个GUI文件夹。
创建一个新的脚本,将其命名为Crosshair,并将其拖到GUI文件夹中。
用十字准线写下以下脚本:
首先,我们设置两个变量。第一个变量是定义我们将以另一种方式选择图形纹理。第二个变量定义一个正方形间隔,它是图形纹理在屏幕上的位置范围。
在start()中,该函数用于设置图形纹理在屏幕上的位置。函数中有四个参数,用于定义正方形区域的大小和位置。第一个参数定义正方形区域的左边界,第二个参数定义底部边界,第三个和第四个参数定义宽度和高度。
在OnGUI()函数中,GUI程序用于在屏幕上显示图形。DrawTexture()函数的参数position和crosshairTexture将使瞄准器出现在屏幕的中央。
保存脚本。
创建一个新的空对象,并将其命名为“GUI”。
将脚本“十字准线”分配给GUI对象。
点击GUI对象,将要在Texturelaim文件夹下使用的图形拖放到参数面板变量Crosshair Texture中。
运行游戏,屏幕上会出现一个景象。
物理特效:
现在,我们希望游戏中的物体效果尽可能真实,这是通过添加物理特效来实现的。在本节中,我们将在环境中添加一些物体,这些物体在被导弹击中后可以做出相应的反应。首先,有几个新概念需要解释。
更新(更新)
之前,我们在函数Update()中编写代码,这样代码就可以在每一帧中执行。一个例子是检测玩家点击开火按钮。帧率不是一个固定值,它取决于场景的复杂程度等因素。帧之间的时间差会导致不稳定的对象反应。因此,如果要添加物理对象(刚体等。)到场景中,代码应该写在函数FixedUpdate()中。Unity中的deltaTime值用于测量渲染两个连续帧所用的时间。
一般来说,函数Update和FixedUpdate的区别如下:
Update()-里面的代码通常用于角色行为、游戏逻辑等。此函数中的deltaTime值不是固定的。
FixedUpdate()-其中的代码通常用于刚性对象(物理属性的行为)。函数中deltaTime的值通常是固定的。
调用FixedUpdate函数的频率由主菜单中编辑-项目设置-时间的FixedTimestep属性决定,也可以更改。第二个属性Time Scale是每秒的帧速率和相应的倒数值。
提示:在定义FixedTimestep值时,要注意很好的平衡:值越小,物理效果越真实,但会影响游戏的运行速度。既要保证游戏的运行速度,又要保证物理效果的真实性。
最后说说yield,相当于暂停当前正在执行的函数。
回到游戏,我们想要达到的目标:
使玩家能够发射导弹(已经实现)。
如果导弹与其他刚性物体发生碰撞,检测其range类中是否存在其他被赋予刚性属性的物体。
对于爆炸冲击力范围内的每一个刚体,都给予一个向上的力,使其对导弹产生反作用力。
我们来看看修改后的爆炸Javascript。
先检查导弹弹着点周围是否有带对撞机的物体。函数物理学。overlapping sphere()有两个参数:3D位置和半径值,然后返回半径内检测到的碰撞体的数组。
一旦获得这些阵列,就会有一个特定方向的力施加到碰撞器对应的每个刚性物体上。
然后我们在导弹的爆炸点增加一个向上的爆炸力。但是爆炸效果随着距离的增大而减小,在整个半径范围内受力不可能相同。刚体在圆周位置的受力应该小于爆炸点中心的受力。函数考虑了这种影响。通过调整外部变量“爆炸功率”和“爆炸半径”的值,可以很容易地获得所需的效果。