目录
概述
一、准备工作
二、开始创建步骤
1、创建“Player”
说明:“Player”为游戏场景中的一个对象,在本游戏中就是一个有C#脚本控制的具有智慧的球体。
2、创建“Player” 移动脚本
3、创建相机脚本
4、创建敌人
5、使用预制体
6、发射弹丸
7、生成更多敌人
8、实现游戏控制器
总结
通过创建经典的竞技场射击游戏来学习 Unity 中脚本的基本知识。本示例是Georgi Ivanov发表的英文文章的翻译,是学习Unity 应用C#脚本的很好学习资料,作为初学者请依该文方法练习一遍,保证您对Unity脚本编程已经掌握。
Unity 的很多力量都来自其丰富的脚本语言C#。您可以使用它来处理用户输入、操作场景中的对象、检测碰撞、生成新的 GameObject 和在场景周围投射方向光线,以帮助处理游戏逻辑。这听起来可能令人生畏,但 Unity 公开了记录良好的 API,这些 API 使这些任务变得轻而易举 — 即使对于新手开发人员!在本教程中,您将创建一个自上而下的射击游戏,该游戏使用 Unity 脚本来处理敌人的生成、玩家控制、发射弹丸和游戏的其他重要方面。
注意:本教程是为 Unity 5.3 或更高版本编写的。您可以在此处下载最新版本的Unity。虽然 Unity 也支持 UnityScript 和 Boo,但 C# 是大多数开发人员倾向于使用的编程语言,并且有充分的理由。C# 被全球数百万开发人员用于应用程序、Web 和游戏开发,并且有大量的信息和教程可以帮助您。
下载GameSample初学者项目,解压缩,并在 Unity 中打开创建的文件夹,您应该看到:
在"Scene view"视图中有一个小竞技场,这将是游戏的战场,一个相机和一个光源。如果您的布局与屏幕截图中不同,请选择右上拉菜单,然后更改为Default。
在"Hierarchy"中,单击"Create"按钮,然后从 3D 部分中选择" Sphere"。将 Sphere定位在 (X:0, Y:0.5, Z:0),并命名“Player”:
Unity 使用entity-component来构建其游戏对象。这意味着所有 GameObject 都是组件的容器,可以附加这些容器来赋予其行为和属性。下面是 Unity 内置的组件的一些示例:
Tranform:每个游戏对象都附带这个组件。它持有游戏对象的位置、旋转和比例。
Box Collider:立方体形状的碰撞器,可用于检测碰撞。
Mesh Filter:用于显示 3D 模型的网格数据。
“Player”游戏对象将需要响应与场景中其他对象的碰撞。为此,请在"Hierarchy"窗口中选择“Player”,然后在"Inspector "窗口中的单击"Add Component"按钮,在弹出的菜单中选择"Physics > Rigidbody",这将向“Player”添加一个刚体组件,以便它可以使用 Unity 的物理引擎。调整 Rigidody 的值,像这样:将拖动设置为 1,将角拖动设置 0,并选中"Freeze Position"旁边的 Y复选框。这将确保“Player”无法上下移动,并且在旋转时没有添加阻尼。
现在,“Player” 已经准备好了,是时候创建脚本,从键盘开始输入并移动播放机了。在"Project"窗口中,单击" Create"按钮并选择" Folder"。命名新文件夹Scripts并在其中创建一个名为“Player” 的子文件夹。在“Player” 文件夹中,单击"Create"按钮并选择"C#Scripts"。命名您的新脚本PlayerMovement。步骤如下所示:
注:使用这样的文件夹可以方便地按角色组织所有内容,并减少混乱。您将制作多个脚本供“Player”使用,因此向它提供自己的文件夹是有意义的。
双击PlayerMovement.cs脚本。这将打开加载脚本时的首选代码编辑器。Unity 附带了在所有程序上预安装的 MonoDevelop,Windows 用户可以选择安装 Visual Studio来取代它,并在运行安装程序时使用。本教程假定您使用的是 MonoDevelop,但 Visual Studio 用户应该能够毫无问题。一旦您选择的编辑器打开,您将看到:
这是 Unity 在新脚本中生成的默认的类。它继承了 MonoBehaviour 基类,这样脚本才能够在游戏中运行,同时还有一些特殊的方法对特定事件作出响应。如果你是一个 iOS 开发者,这个类就好比 UIViewCotnroller。Unity 会在运行脚本时以特定顺序调用多个方法。最常见的几个方法包括:
Start()
:在脚本进行第一次更新之前,将调用此方法一次。Update()
:当游戏正在运行并启用脚本时,此方法将触发每帧。OnDestroy()
:在游戏对象附加到此脚本被销毁之前,将调用此方法。OnCollisionEnter()
:当对撞机或刚体连接到其他碰撞器或刚体时,将调用此方法。在 Start() 方法前,添加两行代码:
public float acceleration;
public float maxSpeed;
这是它应该的格式:
这意味着这两个变量能够在检视器中看到并修改,而无需在脚本和编辑器中来回切换。acceleration 表示玩家的速度随着时间递增。maxSpeed 则表示速度的上限。在它们后面声明这几个变量:
private Rigidbody rigidBody;
private KeyCode[] inputKeys;
private Vector3[] directionsForKeys;
不能通过检查器设置私有变量,开发人员有责任在适当的时间初始化它们。rigidBody 用于保存一个对刚体组件的引用,即附着在 Player GameObject 上的刚体组件。
inputKeys 是一个键盘码的数组,用于检查输入。directionsForKeys 用于保存一个 Vector3 变量数组,这些变量表示方向数据。将 Start() 方法修改为:
void Start () {
inputKeys = new KeyCode[] { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
directionsForKeys = new Vector3[] { Vector3.forward, Vector3.left, Vector3.back, Vector3.right };
rigidBody = GetComponent();
}
此代码链接每个键的相应方向,例如按W将对象向前移动。最后一行获得了一个对所附着的刚体组件的引用,将它保存到 rigidBody 变量以便使用。要真正移动玩家的角色,还需要处理键盘输入。将 Update() 修改为 FixedUpdate() 并加入以下代码:
// 1
void FixedUpdate () {
for (int i = 0; i < inputKeys.Length; i++){
var key = inputKeys[i];
// 2
if(Input.GetKey(key)) {
// 3
Vector3 movement = directionsForKeys[i] * acceleration * Time.deltaTime;
}
}
}
这里有几件重要的事情:
FixedUpdate()
速率与帧速率无关,它和刚体一起使用。和以尽量快的速度运行不同,这个方法会以固定的间隔执行。如果您是游戏编程的新朋友,您可能会问自己为什么必须乘以Time.detalTime。虽然游戏运行帧速率(或帧每秒)会因硬件和承受的压力而异,但这可能会导致在功能强大的计算机上发生太快,在较弱的机器上速度过慢,这可能会导致不希望发生的行为。通常的办法是,当需要按每帧执行一个动作时,都乘上 Time.deltaTimeTime.
在 FixedUpdate() 方法后添加:
void movePlayer(Vector3 movement) {
if(rigidBody.velocity.magnitude * acceleration > maxSpeed) {
rigidBody.AddForce(movement * -1);
} else {
rigidBody.AddForce(movement);
}
}
这个方法向刚体施加一个力,驱使它移动。乳沟当前速度超过 maxSpeed,这个力会转成反方向,让玩家减速,将速度有效地限制在最大速度下。在 FixedUpdate() 方法中,在 if 语句右括号结束之前,添加:
movePlayer(movement);
完美!保存此脚本并返回到 Unity 编辑器。在"Project"窗口中,将"PlayerMovement"脚本拖动到层次结构内的 Player上。将脚本添加到 GameObject 将创建一个组件的实例,这意味着您附加的 GameObject 执行所有代码。使用检查器将 Acceleration 设置为 625,将Max Speed 设置为 4375:
运行场景,使用 WASD 键移动 Player:
对于只有几行代码来说, 这是一个相当不错的结果!然而,有一个明显的问题——Player迅速移动到视线外,这使得它很难打到敌人。
在脚本编辑器中,新建脚本名为 CameraRig,然后将它添加到 MainCamera。
public float moveSpeed;
public GameObject target;
private Transform rigTransform;
你可能想到了,moveSpeed 是相机跟随目标——任何场景内部游戏对象——进行移动的速度。在 Start() 方法中,添加:
rigTransform = this.transform.parent;
这句引用了父对象 Camera 在场景树中的 transform 组件。每个在场景中的对象都会有一个 Transform 组件,它描述了对象的位置、角度和比例。
在同一脚本中,添加以下方法:
void FixedUpdate () {
if(target == null){
return;
}
rigTransform.position = Vector3.Lerp(rigTransform.position, target.transform.position,
Time.deltaTime * moveSpeed);
}
CameraRig 的移动代码比 PlayerMovement 要简单。这是因为你不需要刚体,在 rigTransform 和 target 的位置之间做插值运算即可。Vector3.Lerp() 以两个点和一个0-1 之间的小数做参数,这个小数表示两个端点之间的一个位置。左端点为 0,右端点为 1。0.5 则返回两点之间的终点。以渐慢方式让 rigTransform 靠近 target 的位置。也就是说——相机会跟随玩家角色。回到 Unity。在结构视图选中 Main Camera。在检视器中,设置 Move Speed 为 8 ,Target 为 Player:
运行游戏,并在现场移动;摄像机无论走到哪里,都应平稳地跟随目标变换。
没有敌人的射击游戏很容易被击败, 但有点无聊。通过单击顶部菜单中的GameObject+3D 对象+立方体来创建敌人立方体。将多维数据集重命名为"敌人"并添加刚体组件。在检查器中,首先将多维数据集的变换设置为(0, 0.5,4)。在刚体零部件的"约束"部分中,选中"冻结位置"类别中的"Y"复选框。
现在让敌人以一种吓人的方式移动吧。在 Scripts 目录下新建脚本 Enemy。这个步骤你应该很熟悉了,如果忘记了,请参考前面的描述过的步骤。然后,在类中声明变量:
public float moveSpeed;
public int health;
public int damage;
public Transform targetTransform;
这些变量的作用并不难猜。moveSpeed 先前在相机中也用到过,这里是同样的作用。health 和 damage 用于决定敌人什么时候死,以及它们对玩家造成的伤害。targetTransform 引用了玩家的 transform。对于 Player 来说,你需要一个类描述玩家的所有属性,这一切恰好是敌人想摧毁的。在项目浏览器中,选中 Player 文件夹并新建脚本 Player,这个脚本用于对碰撞进行处理,并保存玩家的生命值。双击脚本,打开它。添加一个公共变量用于保存玩家的生命值:
public int health = 3;
这为health提供了默认值,但也可以在检查器中修改。要处理冲突,请添加以下方法:
void collidedWithEnemy(Enemy enemy) {
// Enemy attack code
if(health <= 0) {
// Todo
}
}
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent();
collidedWithEnemy(enemy);
}
OnCollisionEnter() 方法会在两个带有碰撞体的刚体发生碰撞时触发。Collision 参数包含了交点和碰撞速度等信息。在这里,你只对 Collision 中的 Enemy 组件感兴趣,因此调用 collidedWithEnemy() 并执行攻击逻辑——这个在后面添加。回到 Enemy.cs,添加下列方法:
void FixedUpdate () {
if(targetTransform != null) {
this.transform.position = Vector3.MoveTowards(this.transform.position, targetTransform.transform.position, Time.deltaTime * moveSpeed);
}
}
public void TakeDamage(int damage) {
health -= damage;
if(health <= 0) {
Destroy(this.gameObject);
}
}
public void Attack(Player player) {
player.health -= this.damage;
Destroy(this.gameObject);
}
FixedUpdate() 方法你应该很熟悉了,略有不同的地方是,你用 MoveToward() 替代了 Lerp() 方法。这是因为敌人始终以同样的速度进行移动,当它到达目标后不需要减速。当敌人被子弹击中,TakeDamage() 方法被调用;当敌人的生命值变为 0,它将被销毁。Attack() 是类似的——它将伤害施加到 Player,然后敌人自动销毁。回到 Player.cs,在 collidedWithEnemy() 方法中,将注释“Enemy attack code”替换成:
enemy.Attack(this);
在这个过程中,玩家被减血,敌人自毁。
返回 Unity。将 Enemy 脚本绑定到 Enemy 对象,在检视器中,修改 Enemy 的属性:
现在你应该自己尝试着修改这些值。自己动手,然后和下面的 Gif 动画进行比较:
在这个游戏中,当敌人和玩家发生碰撞,就会构成一次攻击。用 Unity 的物理引擎来检测碰撞不过是小菜一碟。最终,将 Player 脚本绑定到结构视图中的 Player 上。运行游戏,注意查看控制台:
当敌人碰上玩家,它会进行攻击并扣减玩家的生命值为 2。但控制台会抛出一个 NullReferenceException 错误,指向了 Player 脚本的这一行:
哎呀——玩家不仅仅和敌人发生了碰撞,也和游戏中的其它对象发生了碰撞,比如竞技场。因为这个对象没有 Enemy 脚本,因此 GetComponent() 返回了 null。打开 Player.cs。在 OnCollisionEnter() 方法中,用一个 if 语句将 collidedWithEnemy() 方法包裹起来:
if(enemy) {
collidedWithEnemy(enemy);
}
不再有空号!
简单地跑来跑去,避免敌人是一个相当片面的游戏。是时候武装玩家战斗了。
单击层次结构中的"创建"按钮并选择3D 对象/胶囊。命名它 Projectile并赋予它以下转换值:
每次玩家拍摄时,它将发射 的实例。为此,您需要创建 一个 。与场景中已有的对象不同,预制件是按游戏逻辑按需创建的。Projectile
Prefab
在"资产"下创建一个名为"预制件"的新文件夹。现在将"弹丸"对象拖动到此文件夹中。就是这样: 你有一个预制件!
您的预制件需要一点脚本。在名为 Projectile 的脚本文件夹中创建一个新脚本,并将其添加到其中的类变量:
public float speed;
public int damage;
Vector3 shootDirection;
就像本教程中到目前为止的任何移动对象一样,这个对象也将具有速度和伤害变量,因为它是战斗逻辑的一部分。矢量确定 将去哪里。shootDirection
Projectile
通过将该向量在类中实现以下方法,请将该向量用于工作:
// 1
void FixedUpdate () {
this.transform.Translate(shootDirection * speed, Space.World);
}
// 2
public void FireProjectile(Ray shootRay) {
this.shootDirection = shootRay.direction;
this.transform.position = shootRay.origin;
}
// 3
void OnCollisionEnter (Collision col) {
Enemy enemy = col.collider.gameObject.GetComponent();
if(enemy) {
enemy.TakeDamage(damage);
}
Destroy(this.gameObject);
}
以下是上述代码中正在执行的:
Projectile
Ray
TakeDamage()
在场景层次结构中,将弹射脚本附加到弹射游戏对象。将"速度"设置为 0.2,将"伤害"设置为1,然后单击位于检查器顶部附近的"应用"按钮。这将应用您刚才所做的更改到此预制件的所有实例。
从场景层次结构中删除"弹射"对象 - 不再需要它。
现在,您有一个可以移动和应用伤害的预制件,您就可以开始拍摄了。
在"播放器"文件夹中,创建一个名为"播放器拍摄"的新脚本并将其附加到场景中的播放机。在类中,声明以下变量:
public Projectile projectilePrefab;
public LayerMask mask;
第一个变量将包含对之前创建的弹射预制件的引用。每次播放机发射弹丸时,您都会从此预制件创建新实例。该变量用于筛选游戏对象。mask
等等, 铸造光线?这是什么巫术?
不, 没有黑魔法在进行 - 有时在你的游戏中, 当你需要知道, 如果碰撞器存在于一个特定的方向。为此,Unity 可以从特定点投射到指定方向的不可见光线。您可能会遇到许多与光线相交的 Game 对象,因此使用掩码可以过滤掉任何不需要的对象。
射线广播非常有用,可用于各种用途。它们通常用于测试其他玩家是否被弹丸击中,但您也可以使用它们来测试鼠标指针下方是否有任何几何体。要了解有关 Raycasts 的信息,请查看 Unity网站上的此 Unity实时培训视频。
下图显示了从立方体到圆锥体的光线投射。由于光线上有一个图标球蒙版,因此它忽略游戏对象并报告圆锥体上的命中:
现在是你发射自己的光线的时候。
将以下方法添加到PlayerShooting.cs:
void shoot(RaycastHit hit){
// 1
var projectile = Instantiate(projectilePrefab).GetComponent();
// 2
var pointAboveFloor = hit.point + new Vector3(0, this.transform.position.y, 0);
// 3
var direction = pointAboveFloor - transform.position;
// 4
var shootRay = new Ray(this.transform.position, direction);
Debug.DrawRay(shootRay.origin, shootRay.direction * 100.1f, Color.green, 2);
// 5
Physics.IgnoreCollision(GetComponent(), projectile.GetComponent());
// 6
projectile.FireProjectile(shootRay);
}
以下是上述代码所起的用:
Projectile
(x, 0.5, z)
pointAboveFloor
Player collider
Projectile collider
OnCollisionEnter()
Projectile script
注:当光线投射是无价的时,它可以帮助您可视化光线的外观和击中什么。Debug.DrawRay()
在启动逻辑到位后,添加以下方法,让玩家实际扣动扳机:
// 1
void raycastOnMouseClick () {
RaycastHit hit;
Ray rayToFloor = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(rayToFloor.origin, rayToFloor.direction * 100.1f, Color.red, 2);
if(Physics.Raycast(rayToFloor, out hit, 100.0f, mask, QueryTriggerInteraction.Collide)) {
shoot(hit);
}
}
// 2
void Update () {
bool mouseButtonDown = Input.GetMouseButtonDown(0);
if(mouseButtonDown) {
raycastOnMouseClick();
}
}
将每个编号注释重新进行:
raycastOnMouseClick()
返回到 Unity 并在检查器中设置以下变量:
注:Unity 附带有限的预定义图层,您可以在其中创建蒙版。
您可以通过单击"游戏对象"的"图层"下拉列表并选择"添加图层"来创建自己的图层:
要将图层分配给游戏对象,请从"图层"下拉列表中选择它:
有关图层的详细信息,请查看 Unity的图层文档。
运行项目,并着火的要从!弹丸是朝所需方向发射的,但有些东西似乎有点脱落,不是吗?
如果弹丸指向行驶方向,会凉快得多。若要解决此问题,请打开Projectile.cs并添加以下方法:
void rotateInShootDirection() {
Vector3 newRotation = Vector3.RotateTowards(transform.forward, shootDirection, 0.01f, 0.0f);
transform.rotation = Quaternion.LookRotation(newRotation);
}
注意:与 非常相似,但它将矢量视为方向而不是位置。此外,您不需要随着时间的推移更改旋转,因此使用接近零的步骤就足够了。Unity 中的变换旋转使用四元数表示,这些四元数超出了本教程的范围。本教程中,您需要了解的是,在进行涉及 3D 旋转的计算时,它们比矢量具有优势。RotateTowards
MoveTowards
有兴趣了解更多有关四元纪元以及为什么它们有用?看看这篇优秀的文章: 我是如何学会停止担心和爱四重奏的
在 的末尾向 添加调用。 现在应如下所示:FireProjectile()
rotateInShootDirection()
FireProjectile()
public void FireProjectile(Ray shootRay) {
this.shootDirection = shootRay.direction;
this.transform.position = shootRay.origin;
rotateInShootDirection();
}
再次运行游戏,在几个不同的方向开火;这一次,弹丸将指向他们拍摄的方向:
删除呼叫,因为您不需要进一步呼叫。Debug.DrawRay
只有一个敌人并不是非常具有挑战性。但现在你知道了预制件,你可以生成所有你想要的对手!:]
为了保持玩家猜测,您可以随机化每个敌人的运行状况、速度和位置。
创建空游戏对象 -游戏对象 =创建空。将其命名为"敌人"生产器并添加盒式对撞器组件。在检查器中设置值,如下所示:
您附加的对撞机定义竞技场内的特定 3D 空间。若要查看此内容,请在层次结构中选择"游戏对象"并查看场景视图内部:Enemy Producer
绿色导线轮廓表示对撞机。
您将要编写一个脚本,该脚本沿 和 轴在此空间中选择随机位置,并实例化敌人预制件。X
Z
创建一个名为"敌人制作者"的新脚本,并将其附加到敌人制作者游戏对象。在新设置的类中,添加以下实例成员:
public bool shouldSpawn;
public Enemy[] enemyPrefabs;
public float[] moveSpeedRange;
public int[] healthRange;
private Bounds spawnArea;
private GameObject player;
第一个变量启用并禁用生成。该脚本将从中选取一个随机的敌人预制件并实例化它。接下来的两个数组将指定速度和运行状况的最小和最大值。生成区域是您在"场景"视图中看到的绿色框。最后,您需要对玩家的引用,并作为目标传递给坏人。enemyPrefabs
在脚本中,定义以下方法:
public void SpawnEnemies(bool shouldSpawn) {
if(shouldSpawn) {
player = GameObject.FindGameObjectWithTag("Player");
}
this.shouldSpawn = shouldSpawn;
}
void Start () {
spawnArea = this.GetComponent().bounds;
SpawnEnemies(shouldSpawn);
InvokeRepeating("spawnEnemy", 0.5f, 1.0f);
}
SpawnEnemies()
获取具有标记玩家的游戏对象的引用,并确定敌人是否应生成。
Start()
初始化生成区域,并在游戏开始后 0.5 秒计划调用方法。它会每隔一秒被反复调用。除了充当设置器方法外,还可以获取具有 标记的游戏对象的引用。SpawnEnemies()
Player
玩家游戏对象尚未标记 - 您现在将这样做。从"层次结构"中选择"播放者"对象,然后在"检查器"选项卡中选择"播放者",从"标记"下拉菜单中选择"播放者":
现在,您需要为单个敌人编写实际的生成代码。
打开"敌人"脚本并添加以下方法:
public void Initialize(Transform target, float moveSpeed, int health) {
this.targetTransform = target;
this.moveSpeed = moveSpeed;
this.health = health;
}
这仅充当创建对象的设置器。下一步: 代码生成你的敌人联盟。打开EnemyProducer.cs并添加以下方法:
Vector3 randomSpawnPosition() {
float x = Random.Range(spawnArea.min.x, spawnArea.max.x);
float z = Random.Range(spawnArea.min.z, spawnArea.max.z);
float y = 0.5f;
return new Vector3(x, y, z);
}
void spawnEnemy() {
if(shouldSpawn == false || player == null) {
return;
}
int index = Random.Range(0, enemyPrefabs.Length);
var newEnemy = Instantiate(enemyPrefabs[index], randomSpawnPosition(), Quaternion.identity) as Enemy;
newEnemy.Initialize(player.transform,
Random.Range(moveSpeedRange[0], moveSpeedRange[1]),
Random.Range(healthRange[0], healthRange[1]));
}
所有操作就是选择一个随机的敌人预制件,在随机位置实例化它,并初始化敌人脚本公共变量。spawnEnemy()
EnemyProducer.cs差不多准备好了!
回到团结。通过将"敌人"对象从层次结构拖动到"预制件"文件夹来创建"敌人"预制件。从场景中移除敌人对象 - 您不再需要它了。接下来设置脚本公共变量,如:Enemy Producer
运行游戏,并检查出来 - 一无尽的坏人流!
好吧,那些立方体看起来并不可怕。是时候把事情调味了。
在场景中创建3D圆柱体和胶囊。分别命名他们敌人2和敌人3。正如您之前对第一个敌人所做的一样,向它们添加一个刚体组件和"敌人"脚本。选择"敌人2"并在检查器中更改其配置,请按以下操作进行:
现在对敌人3也做同样的工作,但将其比例设置为0.7:
接下来,将它们变成预制件,就像您使用原始敌人一样,并在《敌人生产者》中引用所有这些。检查器中的值应看起来像:
运行游戏;你会看到不同的预制件在竞技场内生成。
过没多久就意识到自己无敌了!虽然很棒, 但你需要提高一点竞争环境。
现在,你有射击,运动和敌人到位,你将实现一个基本的游戏控制器。一旦 "死", 它将重新启动游戏。但首先,您必须创建一个机制,通知任何感兴趣的各方,玩家已达到 0 运行状况。Player
打开播放机脚本并在类声明上方添加以下内容:
using System;
在类中添加以下新的公共事件:
public event Action onPlayerDeath;
事件是一个 C# 语言功能,允许您向任何侦听器广播对象中的更改。要了解如何使用活动,请查看 Unity的活动现场培训。 .
编辑可查看以下代码:collidedWithEnemy()
void collidedWithEnemy(Enemy enemy) {
enemy.Attack(this);
if(health <= 0) {
if(onPlayerDeath != null) {
onPlayerDeath(this);
}
}
}
事件为对象提供了一种整洁的方式,用于发出它们之间的状态变化信号。游戏控制器会对上面声明的事件非常感兴趣。在文件夹中,创建一个名为"游戏控制器"的新脚本。双击文件进行编辑,并添加以下变量:Scripts
public EnemyProducer enemyProducer;
public GameObject playerPrefab;
脚本将需要对敌人的生产有一些控制,因为一旦玩家灭亡,生成敌人就没有意义。此外,重新启动游戏意味着你将不得不重新创建播放器,这意味着...没错, 它会成为预制件。
添加以下方法:
void Start () {
var player = GameObject.FindGameObjectWithTag("Player").GetComponent();
player.onPlayerDeath += onPlayerDeath;
}
void onPlayerDeath(Player player) {
enemyProducer.SpawnEnemies(false);
Destroy(player.gameObject);
Invoke("restartGame", 3);
}
在 中,脚本获取对脚本的引用,并订阅您之前创建的事件。一旦玩家的运行状况达到 0 将被调用,停止敌人生产,从场景中删除播放机对象并在 3 秒后调用方法。Start()
Player
onPlayerDeath()
restartGame()
最后,添加重新启动游戏操作的实现:
void restartGame() {
var enemies = GameObject.FindGameObjectsWithTag("Enemy");
foreach (var enemy in enemies)
{
Destroy(enemy);
}
var playerObject = Instantiate(playerPrefab, new Vector3(0, 0.5f, 0), Quaternion.identity) as GameObject;
var cameraRig = Camera.main.GetComponent();
cameraRig.target = playerObject;
enemyProducer.SpawnEnemies(true);
playerObject.GetComponent().onPlayerDeath += onPlayerDeath;
}
在这里,你正在做一些清理:你摧毁场景的所有敌人,并创建一个新的播放器对象。然后,您将摄像机设备的目标重新分配给此实例,恢复敌人生产,并订阅玩家死亡事件。Game Controller
现在返回到 Unity,打开预制件文件夹,并更改所有敌人预制件的标签到敌人。接下来,通过将玩家游戏对象拖动到"预制件"文件夹中,将其制作成预制件。创建一个空的游戏对象,将其命名为 GameController并附加您刚刚创建的脚本。将检查器中所有必需的引用挂钩。
现在,你非常熟悉这种模式。尝试自己放置参考资料,然后根据下面隐藏的插图检查结果:
再次运行游戏以查看游戏控制器在运行。
就是这样 - 你已经脚本了你的第一个 Unity 游戏!祝贺!
该处使用的url网络请求的数据。
您可以在此处下载已完成的项目。现在,你应该掌握了如何构建一个简单的动作游戏。游戏制作不是简单工作;要完成整个游戏,大量的工作和脚本绝对只是其中一部分。为了添加更多的亮点,你必须在游戏中添加动画和 UI。