Unity3D开发体验之Unity3D 脚本开发

 

 

目录

概述

一、准备工作

二、开始创建步骤

1、创建“Player”

说明:“Player”为游戏场景中的一个对象,在本游戏中就是一个有C#脚本控制的具有智慧的球体。

2、创建“Player” 移动脚本

3、创建相机脚本

4、创建敌人

5、使用预制体

6、发射弹丸

7、生成更多敌人

8、实现游戏控制器

总结


 


概述

通过创建经典的竞技场射击游戏来学习 Unity 中脚本的基本知识。本示例是Georgi Ivanov发表的英文文章的翻译,是学习Unity 应用C#脚本的很好学习资料,作为初学者请依该文方法练习一遍,保证您对Unity脚本编程已经掌握。

Unity3D开发体验之Unity3D 脚本开发_第1张图片 最终效果图

Unity 的很多力量都来自其丰富的脚本语言C#。您可以使用它来处理用户输入、操作场景中的对象、检测碰撞、生成新的 GameObject 和在场景周围投射方向光线,以帮助处理游戏逻辑。这听起来可能令人生畏,但 Unity 公开了记录良好的 API,这些 API 使这些任务变得轻而易举 — 即使对于新手开发人员!在本教程中,您将创建一个自上而下的射击游戏,该游戏使用 Unity 脚本来处理敌人的生成、玩家控制、发射弹丸和游戏的其他重要方面。

注意:本教程是为 Unity 5.3 或更高版本编写的。您可以在此处下载最新版本的Unity。虽然 Unity 也支持 UnityScript 和 Boo,但 C# 是大多数开发人员倾向于使用的编程语言,并且有充分的理由。C# 被全球数百万开发人员用于应用程序、Web 和游戏开发,并且有大量的信息和教程可以帮助您。


 

一、准备工作

 

下载GameSample初学者项目,解压缩,并在 Unity 中打开创建的文件夹,您应该看到:

Unity3D开发体验之Unity3D 脚本开发_第2张图片

"Scene view"视图中有一个小竞技场,这将是游戏的战场,一个相机和一个光源。如果您的布局与屏幕截图中不同,请选择右上拉菜单,然后更改为Default。

Unity3D开发体验之Unity3D 脚本开发_第3张图片

 

二、开始创建步骤


 

1、创建“Player”

 在"Hierarchy"中,单击"Create"按钮,然后从 3D 部分中选择" Sphere"。将 Sphere定位在 (X:0, Y:0.5, Z:0),并命名“Player”:

Unity3D开发体验之Unity3D 脚本开发_第4张图片

Unity 使用entity-component来构建其游戏对象。这意味着所有 GameObject 都是组件的容器,可以附加这些容器来赋予其行为和属性。下面是 Unity 内置的组件的一些示例:

Tranform每个游戏对象都附带这个组件。它持有游戏对象的位置、旋转和比例。

Box Collider立方体形状的碰撞器,可用于检测碰撞。

Mesh Filter:用于显示 3D 模型的网格数据。

Unity3D开发体验之Unity3D 脚本开发_第5张图片

“Player”游戏对象将需要响应与场景中其他对象的碰撞。为此,请在"Hierarchy"窗口中选择“Player”,然后在"Inspector "窗口中的单击"Add Component"按钮,在弹出的菜单中选择"Physics > Rigidbody",这将向“Player”添加一个刚体组件,以便它可以使用 Unity 的物理引擎。调整 Rigidody 的值,像这样:将拖动设置为 1,将角拖动设置 0,并选中"Freeze Position"旁边的 Y复选框。这将确保“Player”无法上下移动,并且在旋转时没有添加阻尼。

Unity3D开发体验之Unity3D 脚本开发_第6张图片

 

2、创建“Player” 移动脚本

现在,“Player” 已经准备好了,是时候创建脚本,从键盘开始输入并移动播放机了。在"Project"窗口中,单击" Create"按钮并选择" Folder"。命名新文件夹Scripts并在其中创建一个名为“Player” 的子文件夹。在“Player” 文件夹中,单击"Create"按钮并选择"C#Scripts"。命名您的新脚本PlayerMovement。步骤如下所示:

Unity3D开发体验之Unity3D 脚本开发_第7张图片

注:使用这样的文件夹可以方便地按角色组织所有内容,并减少混乱。您将制作多个脚本供“Player”使用,因此向它提供自己的文件夹是有意义的。

击PlayerMovement.cs脚本。这将打开加载脚本时的首选代码编辑器。Unity 附带了在所有程序上预安装的 MonoDevelop,Windows 用户可以选择安装 Visual Studio来取代它,并在运行安装程序时使用。本教程假定您使用的是 MonoDevelop,但 Visual Studio 用户应该能够毫无问题。一旦您选择的编辑器打开,您将看到:

Unity3D开发体验之Unity3D 脚本开发_第8张图片

这是 Unity 在新脚本中生成的默认的类。它继承了 MonoBehaviour 基类,这样脚本才能够在游戏中运行,同时还有一些特殊的方法对特定事件作出响应。如果你是一个 iOS 开发者,这个类就好比 UIViewCotnroller。Unity 会在运行脚本时以特定顺序调用多个方法。最常见的几个方法包括:

  • Start():在脚本进行第一次更新之前,将调用此方法一次。
  • Update():当游戏正在运行并启用脚本时,此方法将触发每帧。
  • OnDestroy():在游戏对象附加到此脚本被销毁之前,将调用此方法。
  • OnCollisionEnter():当对撞机或刚体连接到其他碰撞器或刚体时,将调用此方法。

在 Start() 方法前,添加两行代码:

public float acceleration;
public float maxSpeed;

这是它应该的格式:

Unity3D开发体验之Unity3D 脚本开发_第9张图片

这意味着这两个变量能够在检视器中看到并修改,而无需在脚本和编辑器中来回切换。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;
    }
  }
}

这里有几件重要的事情:

  1. FixedUpdate()速率与帧速率无关,它和刚体一起使用。和以尽量快的速度运行不同,这个方法会以固定的间隔执行。
  2. 此循环检查是否按下了有效键。
  3. 获取按下键的方向,将其乘以加速度和完成最后一帧的秒数。这将生成用于移动对象的方向矢量(X、Y 和 Z 轴上的速度)。就可以用它来移动 Player 对象了。

如果您是游戏编程的新朋友,您可能会问自己为什么必须乘以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);

Unity3D开发体验之Unity3D 脚本开发_第10张图片

完美!保存此脚本并返回到 Unity 编辑器。在"Project"窗口中,将"PlayerMovement"脚本拖动到层次结构内的 Player上。将脚本添加到 GameObject 将创建一个组件的实例,这意味着您附加的 GameObject 执行所有代码。使用检查将 Acceleration 设置为 625,将Max Speed 设置为 4375

Unity3D开发体验之Unity3D 脚本开发_第11张图片

运行场景,使用 WASD 键移动 Player:

Unity3D开发体验之Unity3D 脚本开发_第12张图片

对于只有几行代码来说, 这是一个相当不错的结果!然而,有一个明显的问题——Player迅速移动到视线外,这使得它很难打到敌人。

3、创建相机脚本

在脚本编辑器中,新建脚本名为 CameraRig,然后将它添加到 MainCamera。

public float moveSpeed;
public GameObject target;

private Transform rigTransform;

你可能想到了,moveSpeed 是相机跟随目标——任何场景内部游戏对象——进行移动的速度。在 Start() 方法中,添加:

rigTransform = this.transform.parent;

这句引用了父对象 Camera 在场景树中的 transform 组件。每个在场景中的对象都会有一个 Transform 组件,它描述了对象的位置、角度和比例。

Unity3D开发体验之Unity3D 脚本开发_第13张图片

在同一脚本中,添加以下方法:

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:

Unity3D开发体验之Unity3D 脚本开发_第14张图片

运行游戏,并在现场移动;摄像机无论走到哪里,都应平稳地跟随目标变换。

Unity3D开发体验之Unity3D 脚本开发_第15张图片

4、创建敌人

没有敌人的射击游戏很容易被击败, 但有点无聊。通过单击顶部菜单中的GameObject+3D 对象+立方体来创建敌人立方体。将多维数据集重命名为"敌人"并添加刚体组件。在检查器中,首先将多维数据集的变换设置为(0, 0.5,4)。在刚零部件的"约束"部分中,选中"冻结位置"类别中的"Y"复选框。

Unity3D开发体验之Unity3D 脚本开发_第16张图片

现在让敌人以一种吓人的方式移动吧。在 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 的属性:

  1. Move Speed: 5
  2. Health: 2
  3. Damage: 1
  4. Target Transform: Player

现在你应该自己尝试着修改这些值。自己动手,然后和下面的 Gif 动画进行比较:

Unity3D开发体验之Unity3D 脚本开发_第17张图片

 在这个游戏中,当敌人和玩家发生碰撞,就会构成一次攻击。用 Unity 的物理引擎来检测碰撞不过是小菜一碟。最终,将 Player 脚本绑定到结构视图中的 Player 上。运行游戏,注意查看控制台:

Unity3D开发体验之Unity3D 脚本开发_第18张图片

当敌人碰上玩家,它会进行攻击并扣减玩家的生命值为 2。但控制台会抛出一个 NullReferenceException 错误,指向了 Player 脚本的这一行:

Unity3D开发体验之Unity3D 脚本开发_第19张图片

哎呀——玩家不仅仅和敌人发生了碰撞,也和游戏中的其它对象发生了碰撞,比如竞技场。因为这个对象没有 Enemy 脚本,因此 GetComponent() 返回了 null。打开 Player.cs。在 OnCollisionEnter() 方法中,用一个 if 语句将 collidedWithEnemy() 方法包裹起来:

if(enemy) {
  collidedWithEnemy(enemy);
}

不再有空号!

5、使用预制体

简单地跑来跑去,避免敌人是一个相当片面的游戏。是时候武装玩家战斗了。

单击层次结构中的"创建"按钮并选择3D 对象/胶囊。命名它 Projectile并赋予它以下转换值:

  1. 位置: (0, 0, 0)
  2. 旋转: (90, 0, 0)
  3. 比例: (0.075, 0.246, 0.075)

Unity3D开发体验之Unity3D 脚本开发_第20张图片

每次玩家拍摄时,它将发射 的实例。为此,您需要创建 一个 。与场景中已有的对象不同,预制件是按游戏逻辑按需创建的。ProjectilePrefab

在"资产"下创建一个名为"预制件"的新文件夹。现在"弹丸"对象拖动到此文件夹中。就是这样: 你有一个预制件!

Unity3D开发体验之Unity3D 脚本开发_第21张图片

您的预制件需要一点脚本。在名为 Projectile 的脚本文件夹中创建一个新脚本,并将其添加到其中的类变量:

public float speed;
public int damage;

Vector3 shootDirection;

就像本教程中到目前为止的任何移动对象一样,这个对象也将具有速度和伤害变量,因为它是战斗逻辑的一部分。矢量确定 将去哪里。shootDirectionProjectile

通过将该向量在类中实现以下方法,请将该向量用于工作:

// 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);
}

以下是上述代码中正在执行的:

  1. 移动不同于这个游戏中其他一切。它没有目标,或随着时间的推移施加到它一些力量;相反,它在整个生命周期中朝着预定的方向发展。Projectile
  2. 在这里,您可以设置预制件的起始位置和方向。这个论点似乎很神秘,但你很快就会知道它是如何计算的。Ray
  3. 如果一个弹丸与敌人相撞,它会调用并摧毁自己。TakeDamage()

在场景层次结构中,将弹射脚本附加到弹射游戏对象。将"速度"设置为 0.2,"伤害"设置为1,然后单击位于检查器顶部附近的"应用"按钮。这将应用您刚才所做的更改到此预制件的所有实例。

Unity3D开发体验之Unity3D 脚本开发_第22张图片

场景层次结构中删除"弹射"对象 - 不再需要它。

6、发射弹丸

现在,您有一个可以移动和应用伤害的预制件,您就可以开始拍摄了。

在"播放器"文件夹中,创建一个名为"播放器拍摄"的新脚本并将其附加到场景中的播放机。在类中,声明以下变量:

public Projectile projectilePrefab;
public LayerMask mask;

第一个变量将包含对之前创建的弹射预制件的引用。每次播放机发射弹丸时,您都会从此预制件创建新实例。该变量用于筛选游戏对象。mask

等等, 铸造光线?这是什么巫术?

不, 没有黑魔法在进行 - 有时在你的游戏中, 当你需要知道, 如果碰撞器存在于一个特定的方向。为此,Unity 可以从特定点投射到指定方向的不可见光线。您可能会遇到许多与光线相交的 Game 对象,因此使用掩码可以过滤掉任何不需要的对象。

射线广播非常有用,可用于各种用途。它们通常用于测试其他玩家是否被弹丸击中,但您也可以使用它们来测试鼠标指针下方是否有任何几何体。要了解有关 Raycasts 的信息,请查看 Unity网站上的此 Unity实时培训视频。

下图显示了从立方体到圆锥体的光线投射。由于光线上有一个图标球蒙版,因此它忽略游戏对象并报告圆锥体上的命中:

Unity3D开发体验之Unity3D 脚本开发_第23张图片

现在是你发射自己的光线的时候。

将以下方法添加到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);
}

以下是上述代码所起的用:

  1. 实例化弹丸预制件并获取其组件,以便可以初始化它。Projectile
  2. 这一点总是看起来像 。X 和 Z 是地板上的坐标,从鼠标单击位置投射光线。这种计算很重要,因为弹丸必须平行于地面,否则你会向下射击,只有业余爱好者才能向地面射击。:](x, 0.5, z)
  3. 计算从玩家游戏对象到 的方向。pointAboveFloor
  4. 创建一条新光线,按其原点和方向描述弹丸轨迹。
  5. 此行告诉 Unity 的物理引擎忽略 和 之间的碰撞。否则在将调用之前,它有机会飞走。Player colliderProjectile colliderOnCollisionEnter()Projectile script
  6. 最后,它为弹丸设定了轨迹。

注:当光线投射是无价的时,它可以帮助您可视化光线的外观和击中什么。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();  
  }
}

将每个编号注释重新进行:

  1. 此方法将光线从相机投射到鼠标单击的点。然后检查此光线是否与给定的层蒙版游戏对象相交。
  2. 每次更新时,脚本都会检查鼠标左键按下。如果它找到一个,它调用 。raycastOnMouseClick()

返回到 Unity 并在检查器中设置以下变量:

  • 弹射预制件:从预制件文件夹中引用弹丸
  • 掩码:地板

Unity3D开发体验之Unity3D 脚本开发_第24张图片

注:Unity 附带有限的预定义图层,您可以在其中创建蒙版。

您可以通过单击"游戏对象"的"图层"下拉列表并选择"添加图层"来创建自己的图层:

Unity3D开发体验之Unity3D 脚本开发_第25张图片

要将图层分配给游戏对象,请从"图层"下拉列表中选择它:

Unity3D开发体验之Unity3D 脚本开发_第26张图片

有关图层的详细信息,请查看 Unity的图层文档。

运行项目,并着火的要从!弹丸是朝所需方向发射的,但有些东西似乎有点脱落,不是吗?

Unity3D开发体验之Unity3D 脚本开发_第27张图片

如果弹丸指向行驶方向,会凉快得多。若要解决此问题,请打开Projectile.cs并添加以下方法:

void rotateInShootDirection() {
  Vector3 newRotation = Vector3.RotateTowards(transform.forward, shootDirection, 0.01f, 0.0f);
  transform.rotation = Quaternion.LookRotation(newRotation);
}

注意:与 非常相似,但它将矢量视为方向而不是位置。此外,您不需要随着时间的推移更改旋转,因此使用接近零的步骤就足够了。Unity 中的变换旋转使用四元数表示,这些四元数超出了本教程的范围。本教程中,您需要了解的是,在进行涉及 3D 旋转的计算时,它们比矢量具有优势。RotateTowardsMoveTowards

有兴趣了解更多有关四元纪元以及为什么它们有用?看看这篇优秀的文章: 我是如何学会停止担心和爱四重奏的

在 的末尾向 添加调用。 现在应如下所示:FireProjectile()rotateInShootDirection()FireProjectile()

public void FireProjectile(Ray shootRay) {
  this.shootDirection = shootRay.direction;
  this.transform.position = shootRay.origin;
  rotateInShootDirection();
}

再次运行游戏,在几个不同的方向开火;这一次,弹丸将指向他们拍摄的方向:

Unity3D开发体验之Unity3D 脚本开发_第28张图片

删除呼叫,因为您不需要进一步呼叫。Debug.DrawRay

7、生成更多敌人

只有一个敌人并不是非常具有挑战性。但现在你知道了预制件,你可以生成所有你想要的对手!:]

为了保持玩家猜测,您可以随机化每个敌人的运行状况、速度和位置。

创建空游戏对象 -游戏对象 =创建空。将其命名为"敌人"生产器并添加盒式对撞器组件。在检查器中设置值,如下所示:

  1. 位置: (0, 0, 0)
  2. 盒式碰撞器:
    • 是触发器:正真
    • 中心: (0, 0.5, 0)
    • 大小: (29, 1, 29)

Unity3D开发体验之Unity3D 脚本开发_第29张图片

您附加的对撞机定义竞技场内的特定 3D 空间。若要查看此内容,请在层次结构中选择"游戏对象"并查看场景视图内部:Enemy Producer

Unity3D开发体验之Unity3D 脚本开发_第30张图片

绿色导线轮廓表示对撞机。

您将要编写一个脚本,该脚本沿 和 轴在此空间中选择随机位置,并实例化敌人预制件。XZ

创建一个名为"敌人制作者"的新脚本,并将其附加到敌人制作者游戏对象。在新设置的类中,添加以下实例成员:

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

玩家游戏对象尚未标记 - 您现在将这样做。从"层次结构"中选择"播放者"对象,然后在"检查器"选项卡中选择"播放者",从"标记"下拉菜单中选择"播放者":

Unity3D开发体验之Unity3D 脚本开发_第31张图片

现在,您需要为单个敌人编写实际的生成代码。

打开"敌人"脚本并添加以下方法:

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

  1. 应生成:
  2. 敌人预制件:
    • 大小: 1
    • 元素 0:参考敌人的预制件
  3. 移动速度范围:
    • 大小: 2
    • 元素 0: 3
    • 元素 1: 8
  4. 健康范围:
    • 大小: 2
    • 元素 0: 2
    • 元素 1: 6

Unity3D开发体验之Unity3D 脚本开发_第32张图片

运行游戏,并检查出来 - 一无尽的坏人流!

好吧,那些立方体看起来并不可怕。是时候把事情调味了。

在场景中创建3D圆柱体和胶囊。分别命名他们敌人2和敌人3。正如您之前对第一个敌人所做的一样,向它们添加一个刚体组件和"敌人"脚本。选择"敌人2"并在检查器中更改其配置,请按以下操作进行:

  1. 比例: (0, 0.5, 0)
  2. 刚体:
    • 使用重力:
    • 冻结位置:Y
    • 冻结旋转:X、 Y、 Z
  3. 敌人组件:
    • 移动速度: 5
    • 健康: 2
    • 伤害: 1
    • 目标变换:没有

现在对敌人3也做同样的工作,但将其比例设置为0.7:

Unity3D开发体验之Unity3D 脚本开发_第33张图片

接下来,将它们变成预制件,就像您使用原始敌人一样,并在《敌人生产者》中引用所有这些。检查器中的值应看起来像:

 

  • 敌人预制件:
    • 大小: 3
    • 元素 0:敌人
    • 元素1:敌人2
    • 元素2:敌人3

EnemyPrefabs

运行游戏;你会看到不同的预制件在竞技场内生成。

过没多久就意识到自己无敌了!虽然很棒, 但你需要提高一点竞争环境。

8、实现游戏控制器

现在,你有射击,运动和敌人到位,你将实现一个基本的游戏控制器。一旦 "死", 它将重新启动游戏。但首先,您必须创建一个机制,通知任何感兴趣的各方,玩家已达到 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()PlayeronPlayerDeath()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并附加您刚刚创建的脚本。将检查器中所有必需的引用挂钩。

现在,你非常熟悉这种模式。尝试自己放置参考资料,然后根据下面隐藏的插图检查结果:

再次运行游戏以查看游戏控制器在运行。

Unity3D开发体验之Unity3D 脚本开发_第34张图片

就是这样 - 你已经脚本了你的第一个 Unity 游戏!祝贺!

该处使用的url网络请求的数据。


总结

您可以在此处下载已完成的项目。现在,你应该掌握了如何构建一个简单的动作游戏。游戏制作不是简单工作;要完成整个游戏,大量的工作和脚本绝对只是其中一部分。为了添加更多的亮点,你必须在游戏中添加动画和 UI。

你可能感兴趣的:(Unity3D游戏开发,unity3d,c#,脚本语言,游戏,游戏开发)