- 浏览: 101965 次
- 性别:
- 来自: 大连
文章分类
最新评论
第一部分介绍
这个教程将详细介绍如何制作一个简单的第一人称射击(FPS)游戏,它将介绍基本的3D游戏程序概念并且给出一个游戏程序员如何进行思考的提示
安装游戏环境
源文件下载地址:
http://www.otee.dk/tutorials/fps_assets.zip
一旦资源被导入,你将注意到项目面板上有很多文件夹
在项目面板上,从Objects/mainLevelMesh/mainLevelMesh中选择mainLevelMesh,在检查面板上,有一个FBXImporter选项,你将找到“产生碰撞器”选项,勾选它,如果我们不这样做,游戏者落下时将穿过层面,
拖拽mainLevelMesh到场景中
这里不需要给场景增加灯光,这个层面上已经提供了充分的光影,这个导入层使用了允许我们使用rebaked Shadows预焙阴影。尤其是你想建立一个复杂的灯光设置的时候光影执行起来非常好。你现在准备要给环境中增加一个角色。
增加主要角色
我们现在将给游戏者增加一个控制器来对角色进行控制,Unity有一个内建在特定预控制物体中的第一人称控制器,它能在项目面板的Standard Assets>Prefabs下面。
增加第一人称控制器
单击项目面板上的Standard Assets,所有的资源将被列出,发现有许多可被调用的预制物体,单击预制物体左手边的箭头,你将看到第一人称控制器资源,拖拽它到场景面板中
你能看到一个代表游戏者的圆柱体对象,3个大的箭头用来改变对象在3维空间中的位置(如果你不明白三个箭头,也可以按“W”键),并且有一个白色的网格(mesh)显示在对象的视窗中(就是当前看到的),FPS控制器现在是个缺省的照相机,当你在游戏视窗中改变当前的视窗时会随着这个对象移动,你也能注意到FPS控制器顶部有一个照相机图标,它的移动特性是用来在环境中环绕场地的。因为主照相机我们不再需要它了,所以你可以删除它。按下“Play”,你可以使用鼠标和键盘围绕场地移动(鼠标键移动和按“W”、“A”、“S”、“D”)。你现在建立了一个非常简单的FPS,让我们给游戏者一把武器。
增加一件武器
我们现在将给游戏者一个能在场景中发射的手榴弹类型的对象,为了做到这一点,你将需要建立一些Javascript脚本告诉Unity这个武器的特征。
那么我们该怎么做呢?我们想让游戏者射击,无论照相机被定位到哪个点上了,然而,让我们首先想到的是游戏的角色和他们的武器。我们游戏的角色是第一人称视角,照相机被定为在游戏者的眼睛处。如果游戏者手里有个武器,那么武器是可以被发射的,无论它们是否被握在手里,不是从眼睛处发射。这意味着我们要增加一个游戏对象以描绘一个发射手榴弹者并且当他们手里握着武器的时候要定位他的位置,这些能确保对象朝正确的目标开火。
建立一个武器发射器
首先,让我们增加一个游戏对象以表示发射手榴弹者。一个游戏对象是3D世界中的任何一个事项(游戏者、层面、声音),组件式游戏对象的属性。因此,你可以在游戏对象上应用组件。
从主菜单上选择GameObject >Create Empty,并且在层次面板上重命名对象为“Launcher”(发射器),注意,这些对象作为空对象是不可见的,然而它恰好为我们的导弹发射者提供一个站位符。现在让我们关闭FPS控制器以便我们能看到武器发射者的位置。在层次面板上选择FPS控制器并且确保你的光标在场景视窗中,按下“F”(视窗框被选中的情况下),这时焦点会移到当前选择的项上
现在在层次面板上选择发射者并且选择Game Object >Move to view from the main menu,注意发射者对象是怎么被定为到靠近FPS控制器的。我们现在能使用手柄大概定位发射者到手柄使用的地方。
(注意 注意你可以通过改变这个对象的向左或向右的特性以改变对象的定位而没有必要改变任何代码。)
确保你的Unity窗口布局是2 by 3模式(Window >Layouts >2 by 3),并且按下运行按钮。确保发射者在层次面板被选择并且在场景视窗中能被观察到,在周围使用移动特性。你能注意到我们的发射者对象不恩能够随我们移动(你能在运行时按下运行按钮停止游戏运行)
为了解决这个问题拖拽发射者对象到主照相机对象上,主照相机属于层次面板上的FPS控制器。它也可能正好打断预制物体的连接。
建立一个导弹对象
眼下我们将使用一些简单物体,一个球(sphere),建立一个预制物体对象在Unity菜单条上单击Assets >Create >Prefab,并且重命名它为Missile。现在建立一个球(GameObject >Create Other >Sphere)
现在从层次面板上拖拽球游戏对象到项目面板上的missile预制物体(prefab)中,预制物体的图标将被改变。你可以从层次面板上删除球对象。
(提示 任何你知道的游戏物体在作为与预制物体时在运行时你都需要初始化它。)
写导弹发射者代码
FPS控制器是有一些游戏对象和组件组成的一个预制物体,FPS控制器自身是一个围绕Y轴旋转而成的圆柱状的网格,所以我们将发射者代码附加到它身上,这时我们还不能向上或向下射击,因此我们将我们的发射者代码附着到了FPS控制器内的主照相机上,作为照相机可以看任何方向。让我们建立我们的第一个用来描述发射者行为的Javascript代码。
选择Assets >Create >JavaScript,用来建立一个空白的Javascript文档,一个新的名为newBehaviourScript资源显示在项目面板上,重命名这个脚本为MissileLauncher。
(提示 你能指定你想在Unity中使用的扩展的代码编辑器,通过单击Unity >Preferences然后选择the External Script Editor box)
在你的项目视图中建立一个新的目录,叫做WeaponScripts,我们将我们的武器脚本都放在这,把你的MissileLauncher javascript脚本迁移到这里,并且导弹预制物体也这样移动。
让我么会看一看完成的关于导弹发射器的Javascript代码,你可以输入这段代码。
从高的层次思考这个问题,我们为什么这么做?好吧,我们认为当用户单击了开火按钮时,程序能初始化一个导弹,并且朝着一个正确的方向,即用户瞄准的方向能快速发射,让我们检查这段代码的细节:
var projectile : Rigidbody;
var speed = 20;
function Update()
{
这是脚本的开始部分,在这里定义了一些属性和一个叫Update的函数
if( Input.GetButtonDown( "Fire1" ) )
首先我们想检查当用户单击了开火按钮时的代码。“Fire1”被绑定到鼠标左键和当前的键盘配置上(这可以通过菜单Editor >Project Settings >Input编辑)
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation );
我们为初始化对象声明一个控制参照变量,这个类型是Rigidbody(刚体类型),这时因为这个导弹有一些物理特性和行为。
在Unity中初始化一个对象我们使用带三个参数的Instantiate函数,这个对象被初始化后,用这个函数初始化它的3D位置,并且转动这个对象。这里也有另外一个构造器,参考检查API指南,但我们现在将用这个。第一个参数projectile是我们想建立的一个对象,那么什么是projectile呢?projectile是一个在你的编辑器内的插座,为了允许它产生我们会在任何一个函数外头声明projectile变量。这样可以使它成为公共的并暴露在Unity编辑器下。Projectile对象能被用代码产生,不管你是否想编辑或调整一个变量,最好是在Unity编辑器下。第二个参数transform.position在3D空间中建立projectile的位置,即发射者的位置,为什么是发射者?好吧,我们将建立的这个脚本将被附着到发射者上以便获得当前我们的3D位置,transform.position将这些返给我们(transform键盘存取在脚本里被附着的游戏对象处的Transform)。第三个参数是transform.rotation与第二个参数相似,除了建立一个与发射者相同的转动属性的导弹。
instantiatedProjectile.velocity =transform.TransformDirection( Vector3( 0, 0, speed ) );
下面的代码是的导弹能够移动。为了做到这一点,我们将给导弹一个速度,但是朝哪个方向呢(x,y,或者z)?在场景视图中,单击FPS控制器,移动箭头显示(按“W”键,如果箭头不显示),一个是红色的,一个是绿色的,并且一个是蓝色的,红色的指示X轴,绿色的指示Y轴,蓝色的指示Z轴,蓝色的方向是游戏者面对的方向,我们想给我们的导弹在Z轴方向一个速度。速度是instantiatedProjectile的一个属性,我们怎么知道的?好吧instantiatedProjectile是一个刚体类型,如果我们查看API就会明白速度是一个属性,也可以查看刚体的其他一些属性,为了设置速度我们需要给每一个坐标轴指定速度,在3D中对象能被两种坐标系模型指定,本地的和世界的,在本地空间,坐标是相对于你正在使用的对象,在世界空间,坐标轴是绝对的,举例来说,所有物体向上的方向都是指向同一个方向,所以Rigidbody.velocity必须被在世界空间中指定,当你需要在Z轴方向转换一个分配的在本地空间中速度(向前的方向)到它相对于世界空间方向的速度时,你可以使用一个将Vector3作为一个参数的transform.TransformDirection函数,变量speed也将会被暴露在外,所以我们能在编辑器中调整它。
Physics.IgnoreCollision( instantiatedProjectile. collider,
transform.root.collider );
}
}
最后,我们在导弹和游戏者之间关闭碰撞器,如果我们不这样做,那么这个导弹将和游戏者发生碰撞,当导弹被初始化时。查看API下的IgnoreCollision。
这里是MissileLauncher.js的全部代码
var projectile : Rigidbody;
var speed = 20;
function Update()
{
if( Input.GetButtonDown( "Fire1" ) )
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation );
instantiatedProjectile.velocity =transform.TransformDirection( Vector3( 0, 0, speed ) );
Physics.IgnoreCollision( instantiatedProjectile. collider,transform.root.collider );
}
}
插入这些被说明的代码到MissileLauncher Javascript脚本中并且保存它。
在FPS控制器中将MissileLauncher脚本附着到发射者上,你能确认MissileLauncher脚本现在被附着到发射者上了,选择在层次视窗中的发射者,在检查面板中能看到MissileLauncher脚本。在我们的脚本中我们先前创建的预制物体还没有被分配到变量projectile上,所以我们必须在编辑器下做这个,projectile变量是刚体类型,所以首先我们必须确保我们的导弹有一个附着的刚体组件。单击项目面板上的 Missile,然后再主菜单下选择Components >Physics >Rigidbody,将会增加一个刚体组件到我们用来开火的 Missile 对象上,我们想拿来开火的我们做的这个对象类型必须与脚本中暴露的变量类型相匹配。给脚本中的projectile变量分配missile。首先,在层次面板中单击发射者,注意在检查面板上的MissileLauncher 脚本中的Projectile变量,现在从项目面板上拖拽Missile预制物体到Projectile变量上。现在你能运行游戏,你将能射出一个小的有重力效果的球体
导弹爆炸
现在我们将增加一个爆炸,当导弹跟另一个物体发生碰撞的时候,做这些我们需要给导弹增加一个行为,例如建立一个新的脚本并且增加到导弹对象上。建立一个新的空白脚本,选择Assets >Create >Javascript并且重命名这个新的脚本为Projectile,拖拽这个新的脚本到项目面板上的WeaponScripts目录下。
那么我们想让Projectile怎么做呢?当导弹发生了一个碰撞时我们想检测到它并且在那个点上建立一个爆炸效果让我们看一下这个代码:
var explosion : GameObject;
function OnCollisionEnter( collision : Collision )
{
当脚本附着的对象与另一个对象发生碰撞时我们写到OnCollisionEnter函数中的任何代码都会被执行
var contact : ContactPoint = collision.contacts[0];
这里的主要任务是在导弹发生碰撞的3D空间中我们想在OnCollisionEnter函数内初始化一个新的爆炸效果。那么碰撞在哪里发生?OnCollisionEnter函数是通过一个包含了这些信息的Collision类得到的碰撞在哪里发生的,碰撞发生的地点存储在ContactPoint变量中。
var rotation = Quaternion.FromToRotation( Vector3.up, contact.normal );
var instantiatedExplosion : GameObject = Instantiate(
explosion, contact.point, rotation );
这里我们使用Instantiate函数建立一个爆炸,我们知道Instantiate带了三个参数,(1)对象实例;2)3D位置;3)对象的旋转角度。 我们稍后将分配一个例子系统到这个游戏对象上,我们也想使用编辑器来分配,所以我们将使变量成为公共(在函数的外面任何地方)来暴露这个变量。
第二个参数,在用来初始化话爆炸效果的3D点,是被碰撞类的第2点来决定的。
第三个参数,设置爆炸的旋转角度,需要一些说明,我们想旋转爆炸的角度以便爆炸的Y轴
沿着正常的导弹碰撞的表面,这意味着要面对墙壁向外爆炸,它的底层将面临向上爆炸,所以在被碰撞对象的正常表面上我们想做的这些在本地空间被转换到了Y轴上(Y轴是向上的)。本质上旋转是说“使对象点的Y轴与它碰撞的正常表面同一个方向”
Destroy( gameObject );
}
最后,在游戏里我们想使导弹消失在它发生碰撞的地方,在此处调用带一个gameObject 参数的Destroy()函数(gameObject指在脚本中被分配的对象)。这里是Projectile.js脚本的全部代码:
var explosion : GameObject;
function OnCollisionEnter( collision : Collision )
{
var contact : ContactPoint = collision.contacts[0];
var rotation = Quaternion.FromToRotation( Vector3.up, contact.normal );
var instantiatedExplosion : GameObject = Instantiate(
explosion, contact.point, rotation );
Destroy( gameObject );
}
添加这些代码到Projectile Javascript脚本中并保存它。选择Component >Scripts >Projectile附着Projectile Javascript脚本到Missile预制物体上.接下来建立一个爆炸效果,在发生导弹碰撞时使用的
首先,建立一个新的预制物体(调用它会产生爆炸),我们将它存储到asset资源中。标准的资源中包含一个好的带一个例子效果和爆炸周围有灯光的预制爆炸物体,在层次视窗中Standard Assets/Particles/explosion拖拽爆炸预制物体。调整爆炸设置直到你希望看到的效果,这时从层次视窗中拖拽爆炸到项目面板上的爆炸预制物体中。现在我们能分配爆炸预制物到导弹上,确保投射预制物体被选择,在项目面板上通过拖拽填充Explosion对象到层次面板上的Explosion变量中
定义爆破行为
现在我们定义更多的脚本来定义爆破自身的行为。
建立一个新的脚本,让Explosion调用它,并且将它放到Weapons目录下,在Explosion脚本上双击进行编辑。
另一个脚本中共同的可以被调用的函数是Start()。代码被定位为在它所属的对象被初始化时仅被执行一次,所有的我们现在想做的就是当正确的时间计数完了以后从游戏中删除爆破,为了这样做我们使用带第二参数的Destroy()函数,指定在删除前等待多少时间。
var explosionTime = 1.0;
function Start()
{
Destroy( gameObject, explosionTime );
}
explosionTime变量被暴露在Unity编辑器重以便我们能容易地调整。
插入上面的代码到Explosion脚本中,在进程中删除Update()函数。在Explosion预制物体上首先单击,将Explosion脚本附着到Explosion预制物体上然后选择Components >Scripts >Explosion
(注意如果可能的话我们尝试归整行为,被包含在脚本中关联到爆破的代码被附着在Explosion预制物体上。)
运行游戏并且无论你在环境中怎么导弹,你都能在期望的时间看到爆炸的粒子效果。
Sound effects 声音效果
到目前为止我们的游戏世界有一点安静,让我们增加一个声音效果给我们的爆炸效果,首先让我们分配一个声音文件到Explosion预制物体。为了允许Explosion对象能够接受一个声音片段,我们从主菜单增加声音源组件到它身上(Component >Audio>Audio Source)。你将注意到组件里有一个属性是Audio Clip。把“RocketLauncherImpact”拖给Explosion预制物体的Audio Clip这个暴露的变量,Unity能播放许多不同的声音格式。
再一次运行游戏在每次导弹碰撞时都能听到爆破的声音。
Adding a GUI 增加一个GUI
现在让我们增加一个图形用户界面,更通常的熟知的是在游戏里抬头显示(HUD),我们简单的图形用户界面仅仅将包含一个十字形瞄准线。
增加一个十字形瞄准线
在你的项目视窗中建立一个叫做GUI的目录,接下来建立一个新的脚本,十字形瞄准线将调用它并将这个脚本拖拽到GUI目录中。
输入随后的代码到Crosshair.js脚本中。
var crosshairTexture : Texture2D;//这里放置图像。
var position : Rect;//矩形材质在屏幕上的位置。
function Start()
{
position = Rect( ( Screen.width - crosshairTexture.width ) / 2, ( Screen.height -
crosshairTexture.height ) / 2, crosshairTexture.width, crosshairTexture.height );
}
function OnGUI()
{
GUI.DrawTexture( position, crosshairTexture );//在屏幕上画出材质。
}
首先我们建立两个变量。第一个变量是定位放我们将要放置用我们的图像,我们将要使用的材质。第二个变量是个矩形即材质在屏幕上的位置。
在Start()函数中我们计算将要绘制的材质的位置,在构造器中我们有四个参数据确定了矩形的位置和它大小。第一个参数告诉我们矩形的左边在哪里,第二个参数设置矩形的底,第三个和第四个参数据定了矩形的宽和高
在OnGUI()函数中我们使用GUI类的功能将项目画在屏幕上,在这种情况下我们调用DrawTexture()并且传递它的位置和十字形瞄准线材质。保存脚本。建立一个新的空的游戏对象并且重命名它为“GUI”,拖拽你的十字瞄准线脚本到GUI对象上。选择GUI游戏对象并且拖拽在项目视窗中的Textures/aim下的瞄准材质到检查视窗的Crosshair Texture变量上。按下运行按钮在游戏屏幕的中心会出现十字瞄准线。
Physics 物理
现在我们想让对象在我们的环境中看起来更自然一些,通过增加物理特性使它更完美。在这一节我们将增加对象到环境中,可以用我们的导弹打击它。首先有一些新的术语要解释。
Update()更新函数
以前我们在Update函数中键入代码,以便我们能在画面的每一帧都执行它,一个例子是我们检测用户是否单击了开火按钮,然而,帧的速率是不规则的值,它依赖场景等的复杂程度,这个介于多帧之间的不规则变化的时间可能导致不稳定的物理特性,因此,无论何时你想更新你的需要物理特性(刚体)的游戏中的对象时,代码一定要写到FixedUpdate函数中,在Unity中的deltaTime的值被用来决定渲染两个连续帧之间所花的时间。在Update和FixedUpdate函数之间的一个普通的区别像随后的一样:
在Update()函数内输入更新游戏者的行为、游戏逻辑等的代码,当在函数中使用Update()时deltaTime的值是不一致的。
在FixeUpdate()函数内键入刚体随时间的变化(物理行为)。当调用一个FixedUpdate 函数时deltaTime总是返回一个相同的值。
FixedUpdate被调用的频率是由在菜单选项Edit >Project Settings >Time中的固定时间间隔所指定的并且你也能改变它。这个属性包含了几秒钟的时间得到的帧数从而获得的每帧之间的值并且采用了它的倒数( F = 1/T 即物理公式:频率=1/周期)
(提示当编辑固定时间间隔(Fixed Timestep等效于每帧之间的时间)时要小心取得平衡;一个小的时间间隔将会给出更多的物理稳定性(更精确),然而游戏的执行性能将变差。要确保游戏运行流畅并且物理特性看起来逼真。)
在本节最后解释的术语是yield。这可以看作是一个暂停执行当前函数的声明。
所以让我们返回我们的游戏,看我们到底想做什么:
允许用户发射导弹(已经做了)
如果导弹与另一个刚体发生碰撞,要确定是否有在附近还附着有任意其它的刚体对象。每个刚体内都有爆炸效果影响,给他们一个向上的力,以使它们对导弹做出反应。
让我看看这段爆炸的JS脚本:
var explosionTime = 1.0;//爆炸时间
var explosionRadius = 5.0;//影响半径
var explosionPower = 2000.0;//爆炸威力
function Start()
{
//Destroy the explosion in x seconds,
//在X秒后销毁爆炸物体
//this will give the particle system and audio enough time to finish playing
//将给出足够的时间完成运行粒子系统和播放声音
Destroy( gameObject, explosionTime );
//Find all nearby colliders
//找出所有附近的碰撞体
var colliders : Collider[] = Physics.OverlapSphere( transform.position,explosionRadius );
让我们看这段更新了的Explosion Javascript脚本代码:
首先我们确定如果这里有其它任何带有碰撞器的对象在导弹降落地方的附近。这个类函数Physics.OverlapSphere()带了两个参数:一个3D位置和半径并且返回碰撞器数组,被包含在已定义的sphere球体内
//Apply a force to all surrounding rigid bodies.
for( var hit in colliders )
{
// 一旦获得数组,每个刚体上附着的碰撞器将被施加一个固定的方向上的影响
if( hit.rigidbody )
{
hit.rigidbody.AddExplosionForce( explosionPower,
transform.position, explosionRadius );
}
}
我们然后在导弹着地的地方沿Y轴方向施加一个爆炸的影响(explosionPower)。然而,作为距离爆炸效应是递减的,影响的半径不是恒定的,在爆炸中心点的刚体越靠近圆周边界影响也越弱,这个影响被考虑到这个函数中。explosionPower和explosionRadius着两个变量能被很容易的调整以建立一个恰当的效果。
//If we have a particle emitter attached, emit particles for .5 seconds
//如果我们有一个附着的粒子发射器,让粒子发射器发射0.5秒
if( particleEmitter )
{
particleEmitter.emit = true;
yield WaitForSeconds( 0.5 );
particleEmitter.emit = false;
}
}
如果一个颗粒的发射器被附加到我们的Explosion预制物体上那么就会开始发射颗粒,等待0.5后,然后停止发射。
这里是Explosion.js中的全部的代码:
var explosionTime = 1.0;
var explosionRadius = 5.0;
var explosionPower = 2000.0;
function Start()
{
//Destroy the explosion in x seconds,
//在X秒后销毁爆炸物体
//this will give the particle system and audio enough time to finish playing
//将给出足够的时间完成运行粒子系统和播放声音
Destroy( gameObject, explosionTime );
//Find all nearby colliders
//找出所有附近的碰撞体
var colliders : Collider[] = Physics.OverlapSphere( transform.position,explosionRadius );
//Apply a force to all surrounding rigid bodies.
//将影响施加到周围所有的刚体上
for( var hit in colliders )
{
if( hit.rigidbody )
{
hit.rigidbody.AddExplosionForce( explosionPower,
transform.position, explosionRadius );
}
}
//If we have a particle emitter attached, emit particles for .5 seconds
//如果我们有一个附着的粒子发射器,让粒子发射器发射0.5秒
if( particleEmitter )
{
particleEmitter.emit = true;
yield WaitForSeconds( 0.5 );
particleEmitter.emit = false;
}
}
用新的代码更新Explosion Javascript脚本代码并且保存脚本。
让我们建立射击对象,在场景中建立一个Cube并且给它附加一个刚体组件,重复创建Cube多次并且将它们散布在周围。
现在再次试一下游戏并且射击那些附着着刚体的对象。它们现在会起反应向上,另外,试一下不直接射击对象,但靠近它们,它们将清晰地展示向上爆炸的影响被应用到一个半径范围内的所有物体上而不是每次都要直接射击它们。
保存场景
这个教程将详细介绍如何制作一个简单的第一人称射击(FPS)游戏,它将介绍基本的3D游戏程序概念并且给出一个游戏程序员如何进行思考的提示
安装游戏环境
源文件下载地址:
http://www.otee.dk/tutorials/fps_assets.zip
一旦资源被导入,你将注意到项目面板上有很多文件夹
在项目面板上,从Objects/mainLevelMesh/mainLevelMesh中选择mainLevelMesh,在检查面板上,有一个FBXImporter选项,你将找到“产生碰撞器”选项,勾选它,如果我们不这样做,游戏者落下时将穿过层面,
拖拽mainLevelMesh到场景中
这里不需要给场景增加灯光,这个层面上已经提供了充分的光影,这个导入层使用了允许我们使用rebaked Shadows预焙阴影。尤其是你想建立一个复杂的灯光设置的时候光影执行起来非常好。你现在准备要给环境中增加一个角色。
增加主要角色
我们现在将给游戏者增加一个控制器来对角色进行控制,Unity有一个内建在特定预控制物体中的第一人称控制器,它能在项目面板的Standard Assets>Prefabs下面。
增加第一人称控制器
单击项目面板上的Standard Assets,所有的资源将被列出,发现有许多可被调用的预制物体,单击预制物体左手边的箭头,你将看到第一人称控制器资源,拖拽它到场景面板中
你能看到一个代表游戏者的圆柱体对象,3个大的箭头用来改变对象在3维空间中的位置(如果你不明白三个箭头,也可以按“W”键),并且有一个白色的网格(mesh)显示在对象的视窗中(就是当前看到的),FPS控制器现在是个缺省的照相机,当你在游戏视窗中改变当前的视窗时会随着这个对象移动,你也能注意到FPS控制器顶部有一个照相机图标,它的移动特性是用来在环境中环绕场地的。因为主照相机我们不再需要它了,所以你可以删除它。按下“Play”,你可以使用鼠标和键盘围绕场地移动(鼠标键移动和按“W”、“A”、“S”、“D”)。你现在建立了一个非常简单的FPS,让我们给游戏者一把武器。
增加一件武器
我们现在将给游戏者一个能在场景中发射的手榴弹类型的对象,为了做到这一点,你将需要建立一些Javascript脚本告诉Unity这个武器的特征。
那么我们该怎么做呢?我们想让游戏者射击,无论照相机被定位到哪个点上了,然而,让我们首先想到的是游戏的角色和他们的武器。我们游戏的角色是第一人称视角,照相机被定为在游戏者的眼睛处。如果游戏者手里有个武器,那么武器是可以被发射的,无论它们是否被握在手里,不是从眼睛处发射。这意味着我们要增加一个游戏对象以描绘一个发射手榴弹者并且当他们手里握着武器的时候要定位他的位置,这些能确保对象朝正确的目标开火。
建立一个武器发射器
首先,让我们增加一个游戏对象以表示发射手榴弹者。一个游戏对象是3D世界中的任何一个事项(游戏者、层面、声音),组件式游戏对象的属性。因此,你可以在游戏对象上应用组件。
从主菜单上选择GameObject >Create Empty,并且在层次面板上重命名对象为“Launcher”(发射器),注意,这些对象作为空对象是不可见的,然而它恰好为我们的导弹发射者提供一个站位符。现在让我们关闭FPS控制器以便我们能看到武器发射者的位置。在层次面板上选择FPS控制器并且确保你的光标在场景视窗中,按下“F”(视窗框被选中的情况下),这时焦点会移到当前选择的项上
现在在层次面板上选择发射者并且选择Game Object >Move to view from the main menu,注意发射者对象是怎么被定为到靠近FPS控制器的。我们现在能使用手柄大概定位发射者到手柄使用的地方。
(注意 注意你可以通过改变这个对象的向左或向右的特性以改变对象的定位而没有必要改变任何代码。)
确保你的Unity窗口布局是2 by 3模式(Window >Layouts >2 by 3),并且按下运行按钮。确保发射者在层次面板被选择并且在场景视窗中能被观察到,在周围使用移动特性。你能注意到我们的发射者对象不恩能够随我们移动(你能在运行时按下运行按钮停止游戏运行)
为了解决这个问题拖拽发射者对象到主照相机对象上,主照相机属于层次面板上的FPS控制器。它也可能正好打断预制物体的连接。
建立一个导弹对象
眼下我们将使用一些简单物体,一个球(sphere),建立一个预制物体对象在Unity菜单条上单击Assets >Create >Prefab,并且重命名它为Missile。现在建立一个球(GameObject >Create Other >Sphere)
现在从层次面板上拖拽球游戏对象到项目面板上的missile预制物体(prefab)中,预制物体的图标将被改变。你可以从层次面板上删除球对象。
(提示 任何你知道的游戏物体在作为与预制物体时在运行时你都需要初始化它。)
写导弹发射者代码
FPS控制器是有一些游戏对象和组件组成的一个预制物体,FPS控制器自身是一个围绕Y轴旋转而成的圆柱状的网格,所以我们将发射者代码附加到它身上,这时我们还不能向上或向下射击,因此我们将我们的发射者代码附着到了FPS控制器内的主照相机上,作为照相机可以看任何方向。让我们建立我们的第一个用来描述发射者行为的Javascript代码。
选择Assets >Create >JavaScript,用来建立一个空白的Javascript文档,一个新的名为newBehaviourScript资源显示在项目面板上,重命名这个脚本为MissileLauncher。
(提示 你能指定你想在Unity中使用的扩展的代码编辑器,通过单击Unity >Preferences然后选择the External Script Editor box)
在你的项目视图中建立一个新的目录,叫做WeaponScripts,我们将我们的武器脚本都放在这,把你的MissileLauncher javascript脚本迁移到这里,并且导弹预制物体也这样移动。
让我么会看一看完成的关于导弹发射器的Javascript代码,你可以输入这段代码。
从高的层次思考这个问题,我们为什么这么做?好吧,我们认为当用户单击了开火按钮时,程序能初始化一个导弹,并且朝着一个正确的方向,即用户瞄准的方向能快速发射,让我们检查这段代码的细节:
var projectile : Rigidbody;
var speed = 20;
function Update()
{
这是脚本的开始部分,在这里定义了一些属性和一个叫Update的函数
if( Input.GetButtonDown( "Fire1" ) )
首先我们想检查当用户单击了开火按钮时的代码。“Fire1”被绑定到鼠标左键和当前的键盘配置上(这可以通过菜单Editor >Project Settings >Input编辑)
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation );
我们为初始化对象声明一个控制参照变量,这个类型是Rigidbody(刚体类型),这时因为这个导弹有一些物理特性和行为。
在Unity中初始化一个对象我们使用带三个参数的Instantiate函数,这个对象被初始化后,用这个函数初始化它的3D位置,并且转动这个对象。这里也有另外一个构造器,参考检查API指南,但我们现在将用这个。第一个参数projectile是我们想建立的一个对象,那么什么是projectile呢?projectile是一个在你的编辑器内的插座,为了允许它产生我们会在任何一个函数外头声明projectile变量。这样可以使它成为公共的并暴露在Unity编辑器下。Projectile对象能被用代码产生,不管你是否想编辑或调整一个变量,最好是在Unity编辑器下。第二个参数transform.position在3D空间中建立projectile的位置,即发射者的位置,为什么是发射者?好吧,我们将建立的这个脚本将被附着到发射者上以便获得当前我们的3D位置,transform.position将这些返给我们(transform键盘存取在脚本里被附着的游戏对象处的Transform)。第三个参数是transform.rotation与第二个参数相似,除了建立一个与发射者相同的转动属性的导弹。
instantiatedProjectile.velocity =transform.TransformDirection( Vector3( 0, 0, speed ) );
下面的代码是的导弹能够移动。为了做到这一点,我们将给导弹一个速度,但是朝哪个方向呢(x,y,或者z)?在场景视图中,单击FPS控制器,移动箭头显示(按“W”键,如果箭头不显示),一个是红色的,一个是绿色的,并且一个是蓝色的,红色的指示X轴,绿色的指示Y轴,蓝色的指示Z轴,蓝色的方向是游戏者面对的方向,我们想给我们的导弹在Z轴方向一个速度。速度是instantiatedProjectile的一个属性,我们怎么知道的?好吧instantiatedProjectile是一个刚体类型,如果我们查看API就会明白速度是一个属性,也可以查看刚体的其他一些属性,为了设置速度我们需要给每一个坐标轴指定速度,在3D中对象能被两种坐标系模型指定,本地的和世界的,在本地空间,坐标是相对于你正在使用的对象,在世界空间,坐标轴是绝对的,举例来说,所有物体向上的方向都是指向同一个方向,所以Rigidbody.velocity必须被在世界空间中指定,当你需要在Z轴方向转换一个分配的在本地空间中速度(向前的方向)到它相对于世界空间方向的速度时,你可以使用一个将Vector3作为一个参数的transform.TransformDirection函数,变量speed也将会被暴露在外,所以我们能在编辑器中调整它。
Physics.IgnoreCollision( instantiatedProjectile. collider,
transform.root.collider );
}
}
最后,我们在导弹和游戏者之间关闭碰撞器,如果我们不这样做,那么这个导弹将和游戏者发生碰撞,当导弹被初始化时。查看API下的IgnoreCollision。
这里是MissileLauncher.js的全部代码
var projectile : Rigidbody;
var speed = 20;
function Update()
{
if( Input.GetButtonDown( "Fire1" ) )
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position, transform.rotation );
instantiatedProjectile.velocity =transform.TransformDirection( Vector3( 0, 0, speed ) );
Physics.IgnoreCollision( instantiatedProjectile. collider,transform.root.collider );
}
}
插入这些被说明的代码到MissileLauncher Javascript脚本中并且保存它。
在FPS控制器中将MissileLauncher脚本附着到发射者上,你能确认MissileLauncher脚本现在被附着到发射者上了,选择在层次视窗中的发射者,在检查面板中能看到MissileLauncher脚本。在我们的脚本中我们先前创建的预制物体还没有被分配到变量projectile上,所以我们必须在编辑器下做这个,projectile变量是刚体类型,所以首先我们必须确保我们的导弹有一个附着的刚体组件。单击项目面板上的 Missile,然后再主菜单下选择Components >Physics >Rigidbody,将会增加一个刚体组件到我们用来开火的 Missile 对象上,我们想拿来开火的我们做的这个对象类型必须与脚本中暴露的变量类型相匹配。给脚本中的projectile变量分配missile。首先,在层次面板中单击发射者,注意在检查面板上的MissileLauncher 脚本中的Projectile变量,现在从项目面板上拖拽Missile预制物体到Projectile变量上。现在你能运行游戏,你将能射出一个小的有重力效果的球体
导弹爆炸
现在我们将增加一个爆炸,当导弹跟另一个物体发生碰撞的时候,做这些我们需要给导弹增加一个行为,例如建立一个新的脚本并且增加到导弹对象上。建立一个新的空白脚本,选择Assets >Create >Javascript并且重命名这个新的脚本为Projectile,拖拽这个新的脚本到项目面板上的WeaponScripts目录下。
那么我们想让Projectile怎么做呢?当导弹发生了一个碰撞时我们想检测到它并且在那个点上建立一个爆炸效果让我们看一下这个代码:
var explosion : GameObject;
function OnCollisionEnter( collision : Collision )
{
当脚本附着的对象与另一个对象发生碰撞时我们写到OnCollisionEnter函数中的任何代码都会被执行
var contact : ContactPoint = collision.contacts[0];
这里的主要任务是在导弹发生碰撞的3D空间中我们想在OnCollisionEnter函数内初始化一个新的爆炸效果。那么碰撞在哪里发生?OnCollisionEnter函数是通过一个包含了这些信息的Collision类得到的碰撞在哪里发生的,碰撞发生的地点存储在ContactPoint变量中。
var rotation = Quaternion.FromToRotation( Vector3.up, contact.normal );
var instantiatedExplosion : GameObject = Instantiate(
explosion, contact.point, rotation );
这里我们使用Instantiate函数建立一个爆炸,我们知道Instantiate带了三个参数,(1)对象实例;2)3D位置;3)对象的旋转角度。 我们稍后将分配一个例子系统到这个游戏对象上,我们也想使用编辑器来分配,所以我们将使变量成为公共(在函数的外面任何地方)来暴露这个变量。
第二个参数,在用来初始化话爆炸效果的3D点,是被碰撞类的第2点来决定的。
第三个参数,设置爆炸的旋转角度,需要一些说明,我们想旋转爆炸的角度以便爆炸的Y轴
沿着正常的导弹碰撞的表面,这意味着要面对墙壁向外爆炸,它的底层将面临向上爆炸,所以在被碰撞对象的正常表面上我们想做的这些在本地空间被转换到了Y轴上(Y轴是向上的)。本质上旋转是说“使对象点的Y轴与它碰撞的正常表面同一个方向”
Destroy( gameObject );
}
最后,在游戏里我们想使导弹消失在它发生碰撞的地方,在此处调用带一个gameObject 参数的Destroy()函数(gameObject指在脚本中被分配的对象)。这里是Projectile.js脚本的全部代码:
var explosion : GameObject;
function OnCollisionEnter( collision : Collision )
{
var contact : ContactPoint = collision.contacts[0];
var rotation = Quaternion.FromToRotation( Vector3.up, contact.normal );
var instantiatedExplosion : GameObject = Instantiate(
explosion, contact.point, rotation );
Destroy( gameObject );
}
添加这些代码到Projectile Javascript脚本中并保存它。选择Component >Scripts >Projectile附着Projectile Javascript脚本到Missile预制物体上.接下来建立一个爆炸效果,在发生导弹碰撞时使用的
首先,建立一个新的预制物体(调用它会产生爆炸),我们将它存储到asset资源中。标准的资源中包含一个好的带一个例子效果和爆炸周围有灯光的预制爆炸物体,在层次视窗中Standard Assets/Particles/explosion拖拽爆炸预制物体。调整爆炸设置直到你希望看到的效果,这时从层次视窗中拖拽爆炸到项目面板上的爆炸预制物体中。现在我们能分配爆炸预制物到导弹上,确保投射预制物体被选择,在项目面板上通过拖拽填充Explosion对象到层次面板上的Explosion变量中
定义爆破行为
现在我们定义更多的脚本来定义爆破自身的行为。
建立一个新的脚本,让Explosion调用它,并且将它放到Weapons目录下,在Explosion脚本上双击进行编辑。
另一个脚本中共同的可以被调用的函数是Start()。代码被定位为在它所属的对象被初始化时仅被执行一次,所有的我们现在想做的就是当正确的时间计数完了以后从游戏中删除爆破,为了这样做我们使用带第二参数的Destroy()函数,指定在删除前等待多少时间。
var explosionTime = 1.0;
function Start()
{
Destroy( gameObject, explosionTime );
}
explosionTime变量被暴露在Unity编辑器重以便我们能容易地调整。
插入上面的代码到Explosion脚本中,在进程中删除Update()函数。在Explosion预制物体上首先单击,将Explosion脚本附着到Explosion预制物体上然后选择Components >Scripts >Explosion
(注意如果可能的话我们尝试归整行为,被包含在脚本中关联到爆破的代码被附着在Explosion预制物体上。)
运行游戏并且无论你在环境中怎么导弹,你都能在期望的时间看到爆炸的粒子效果。
Sound effects 声音效果
到目前为止我们的游戏世界有一点安静,让我们增加一个声音效果给我们的爆炸效果,首先让我们分配一个声音文件到Explosion预制物体。为了允许Explosion对象能够接受一个声音片段,我们从主菜单增加声音源组件到它身上(Component >Audio>Audio Source)。你将注意到组件里有一个属性是Audio Clip。把“RocketLauncherImpact”拖给Explosion预制物体的Audio Clip这个暴露的变量,Unity能播放许多不同的声音格式。
再一次运行游戏在每次导弹碰撞时都能听到爆破的声音。
Adding a GUI 增加一个GUI
现在让我们增加一个图形用户界面,更通常的熟知的是在游戏里抬头显示(HUD),我们简单的图形用户界面仅仅将包含一个十字形瞄准线。
增加一个十字形瞄准线
在你的项目视窗中建立一个叫做GUI的目录,接下来建立一个新的脚本,十字形瞄准线将调用它并将这个脚本拖拽到GUI目录中。
输入随后的代码到Crosshair.js脚本中。
var crosshairTexture : Texture2D;//这里放置图像。
var position : Rect;//矩形材质在屏幕上的位置。
function Start()
{
position = Rect( ( Screen.width - crosshairTexture.width ) / 2, ( Screen.height -
crosshairTexture.height ) / 2, crosshairTexture.width, crosshairTexture.height );
}
function OnGUI()
{
GUI.DrawTexture( position, crosshairTexture );//在屏幕上画出材质。
}
首先我们建立两个变量。第一个变量是定位放我们将要放置用我们的图像,我们将要使用的材质。第二个变量是个矩形即材质在屏幕上的位置。
在Start()函数中我们计算将要绘制的材质的位置,在构造器中我们有四个参数据确定了矩形的位置和它大小。第一个参数告诉我们矩形的左边在哪里,第二个参数设置矩形的底,第三个和第四个参数据定了矩形的宽和高
在OnGUI()函数中我们使用GUI类的功能将项目画在屏幕上,在这种情况下我们调用DrawTexture()并且传递它的位置和十字形瞄准线材质。保存脚本。建立一个新的空的游戏对象并且重命名它为“GUI”,拖拽你的十字瞄准线脚本到GUI对象上。选择GUI游戏对象并且拖拽在项目视窗中的Textures/aim下的瞄准材质到检查视窗的Crosshair Texture变量上。按下运行按钮在游戏屏幕的中心会出现十字瞄准线。
Physics 物理
现在我们想让对象在我们的环境中看起来更自然一些,通过增加物理特性使它更完美。在这一节我们将增加对象到环境中,可以用我们的导弹打击它。首先有一些新的术语要解释。
Update()更新函数
以前我们在Update函数中键入代码,以便我们能在画面的每一帧都执行它,一个例子是我们检测用户是否单击了开火按钮,然而,帧的速率是不规则的值,它依赖场景等的复杂程度,这个介于多帧之间的不规则变化的时间可能导致不稳定的物理特性,因此,无论何时你想更新你的需要物理特性(刚体)的游戏中的对象时,代码一定要写到FixedUpdate函数中,在Unity中的deltaTime的值被用来决定渲染两个连续帧之间所花的时间。在Update和FixedUpdate函数之间的一个普通的区别像随后的一样:
在Update()函数内输入更新游戏者的行为、游戏逻辑等的代码,当在函数中使用Update()时deltaTime的值是不一致的。
在FixeUpdate()函数内键入刚体随时间的变化(物理行为)。当调用一个FixedUpdate 函数时deltaTime总是返回一个相同的值。
FixedUpdate被调用的频率是由在菜单选项Edit >Project Settings >Time中的固定时间间隔所指定的并且你也能改变它。这个属性包含了几秒钟的时间得到的帧数从而获得的每帧之间的值并且采用了它的倒数( F = 1/T 即物理公式:频率=1/周期)
(提示当编辑固定时间间隔(Fixed Timestep等效于每帧之间的时间)时要小心取得平衡;一个小的时间间隔将会给出更多的物理稳定性(更精确),然而游戏的执行性能将变差。要确保游戏运行流畅并且物理特性看起来逼真。)
在本节最后解释的术语是yield。这可以看作是一个暂停执行当前函数的声明。
所以让我们返回我们的游戏,看我们到底想做什么:
允许用户发射导弹(已经做了)
如果导弹与另一个刚体发生碰撞,要确定是否有在附近还附着有任意其它的刚体对象。每个刚体内都有爆炸效果影响,给他们一个向上的力,以使它们对导弹做出反应。
让我看看这段爆炸的JS脚本:
var explosionTime = 1.0;//爆炸时间
var explosionRadius = 5.0;//影响半径
var explosionPower = 2000.0;//爆炸威力
function Start()
{
//Destroy the explosion in x seconds,
//在X秒后销毁爆炸物体
//this will give the particle system and audio enough time to finish playing
//将给出足够的时间完成运行粒子系统和播放声音
Destroy( gameObject, explosionTime );
//Find all nearby colliders
//找出所有附近的碰撞体
var colliders : Collider[] = Physics.OverlapSphere( transform.position,explosionRadius );
让我们看这段更新了的Explosion Javascript脚本代码:
首先我们确定如果这里有其它任何带有碰撞器的对象在导弹降落地方的附近。这个类函数Physics.OverlapSphere()带了两个参数:一个3D位置和半径并且返回碰撞器数组,被包含在已定义的sphere球体内
//Apply a force to all surrounding rigid bodies.
for( var hit in colliders )
{
// 一旦获得数组,每个刚体上附着的碰撞器将被施加一个固定的方向上的影响
if( hit.rigidbody )
{
hit.rigidbody.AddExplosionForce( explosionPower,
transform.position, explosionRadius );
}
}
我们然后在导弹着地的地方沿Y轴方向施加一个爆炸的影响(explosionPower)。然而,作为距离爆炸效应是递减的,影响的半径不是恒定的,在爆炸中心点的刚体越靠近圆周边界影响也越弱,这个影响被考虑到这个函数中。explosionPower和explosionRadius着两个变量能被很容易的调整以建立一个恰当的效果。
//If we have a particle emitter attached, emit particles for .5 seconds
//如果我们有一个附着的粒子发射器,让粒子发射器发射0.5秒
if( particleEmitter )
{
particleEmitter.emit = true;
yield WaitForSeconds( 0.5 );
particleEmitter.emit = false;
}
}
如果一个颗粒的发射器被附加到我们的Explosion预制物体上那么就会开始发射颗粒,等待0.5后,然后停止发射。
这里是Explosion.js中的全部的代码:
var explosionTime = 1.0;
var explosionRadius = 5.0;
var explosionPower = 2000.0;
function Start()
{
//Destroy the explosion in x seconds,
//在X秒后销毁爆炸物体
//this will give the particle system and audio enough time to finish playing
//将给出足够的时间完成运行粒子系统和播放声音
Destroy( gameObject, explosionTime );
//Find all nearby colliders
//找出所有附近的碰撞体
var colliders : Collider[] = Physics.OverlapSphere( transform.position,explosionRadius );
//Apply a force to all surrounding rigid bodies.
//将影响施加到周围所有的刚体上
for( var hit in colliders )
{
if( hit.rigidbody )
{
hit.rigidbody.AddExplosionForce( explosionPower,
transform.position, explosionRadius );
}
}
//If we have a particle emitter attached, emit particles for .5 seconds
//如果我们有一个附着的粒子发射器,让粒子发射器发射0.5秒
if( particleEmitter )
{
particleEmitter.emit = true;
yield WaitForSeconds( 0.5 );
particleEmitter.emit = false;
}
}
用新的代码更新Explosion Javascript脚本代码并且保存脚本。
让我们建立射击对象,在场景中建立一个Cube并且给它附加一个刚体组件,重复创建Cube多次并且将它们散布在周围。
现在再次试一下游戏并且射击那些附着着刚体的对象。它们现在会起反应向上,另外,试一下不直接射击对象,但靠近它们,它们将清晰地展示向上爆炸的影响被应用到一个半径范围内的所有物体上而不是每次都要直接射击它们。
保存场景
发表评论
-
fps 翻译 第二部分
2012-08-15 10:48 1191第二部分:进阶 这部分是中级教程,介绍游戏中类似多武器切换 ... -
Unity3d 模型导入的若干问题
2012-08-10 10:44 1008Unity3d导入3dMax模型会产 ... -
如何使用unity3d 自带的MonoDevelop工具进行Debug调试
2012-08-03 15:11 12621.首先建立一个新的Unity3d工程(当然有自己的工程了可以 ... -
Unity3d的js编辑器SciTEG永久支持中文
2012-08-03 14:35 855假如你的代码中有中文这个设置是很爽的。以后注释也可以中文了。 ...
相关推荐
在本项目中,我们关注的是一个大学毕业生使用Visual C++进行的毕业设计——一款仿制Counter-Strike(CS)的第一人称射击游戏(FPS)。这个实训项目不仅展示了开发者对C++编程语言的理解,还体现了他们对游戏开发核心...
在推理阶段,它定义为局部的单样本检测框架,跟踪框在第一帧确定后就作为模板。通过这种方式,模板分支可以被看作是预测检测核参数的元学习器,与检测分支一起,仅受RPN的监督进行端到端训练。 实验结果表明,Siam...
这允许将跟踪任务表达为局部单样本检测框架,其中第一帧中的边界框作为唯一的示例。通过这种方式,模型能够将目标跟踪表述为一种快速的、一次性的检测任务。 该论文的主要贡献可以归纳为以下三点: 1. 提出的...
这款游戏旨在提供与全球顶级第三人称射击(TPS)和第一人称射击(FPS)游戏相媲美的体验,同时引入创新元素。 在开发过程中,制作团队采取了跨国合作的方式,邀请了海外作家和工作室参与,以适应全球玩家的不同口味。...
在电子竞技领域,尤其是经典的第一人称射击游戏Counter-Strike(CS)中,"刷新率锁定"是一个重要的概念。刷新率是指显示器每秒钟能够显示的画面数量,通常以Hz(赫兹)为单位。一个较高的刷新率能提供更流畅的游戏...
- 文档的“Contents”部分透露了它包含了至少两章的内容,第1章介绍了文档的目的是为了技术帮助,第二章则是关于摄像头前端的信息,包括捕获日志信息、监控性能指标(KPI Perf)、故障排除等。 - 故障排除部分涉及到...
老枯认为其意思是通过对整个视频源进行2次处理使编码效率最高:第一遍判断何处为复杂场景和简单场景,第二遍根据码率的上下限,把码 率重新分配更多给复杂场景。可以在实验中看出,tmpgenc在进行这种编码时进度指示...
而在3D游戏开发部分,读者则会深入了解如何使用SceneKit构建更加复杂的3D场景和游戏,例如角色扮演游戏(RPG)、模拟游戏和第一人称射击游戏(FPS)等。 本书的内容还包括了如何利用iOS设备的各种硬件特性,比如...
1. 第一人称射击游戏(FPS)的设计与开发 2. 虚幻引擎(Unreal Engine)的使用,特别是Unreal Engine 4(UE4) 3. 游戏性能优化和资源管理 4. C++编程在游戏开发中的应用 5. 虚幻引擎的蓝图系统和可视化编程 6. 游戏...