「Unity2D」使用Unity创建一个2D游戏系列-5

本文由泰然教程组成员 HelloWorld 翻译,原文请参阅「Shooting (2/2)」

射击(2/2)

我们的华丽飞车现在在射杀毫无反抗的章鱼。

不能一直这样,它们需要反击,为自由而战。哎呀,不好意思,有点激动!

使用上一章节我们学习的,我们将要修改敌人的行为来使它们也可以发射弹药。

敌人的弹药

我们将要使用这个精灵创造一个新的弹药:

shot_poulpi

(右键点击来保存这个图片)

如果你和我一样懒,复制“PlayerShot”预设(prefab),将其重命名为“EnemyShot1”,并且使用上面所给的图片改变这个精灵。

你可以通过在场景中拖拽创建的实例来进行复制,重命名这个游戏对象最后再将其保存为一个预设。

或者你可以简单的直接在文件夹中通过cmd+D(OS X)或者Ctrl+D(Windows)快捷键复制Preafb。如果你想复杂一些的话,你可以重新创造一个新的精灵,刚体,新的碰撞触发器等等。

合适的缩放比例为(0.35,0.35,1)

你会看到如下图所示场景:

shot_config2

如果你点击“Play”,子弹将会移动并且可能会摧毁敌人。这是因为”ShotScript“脚本的属性,默认是对Poupli有伤害的。

不要改变它们。还记得上一章的”WeaponScript“脚本么?它将会正确的设置这些值。

我们有了”EnemyShot1“预设。移除场景中它的实例如果有的话。

发射

正如我们为玩家所做的一样,我们需要给敌人添加一个武器并且让它调用Attack(),从而创建弹药。

新的脚本和任务

  1. 给敌人添加一个”WeaponScript"。
  2. 将”EnemyShot1“预设拖拽到脚本中的”Shot Prefab”变量中。
  3. 创建一个叫做“EnemyScript”的新脚本。它将会在每一帧触发这个武器。实现自动发射。
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
  private WeaponScript weapon;

  void Awake()
  {
    // Retrieve the weapon only once
    weapon = GetComponent<WeaponScript>();
  }

  void Update()
  {
    // Auto-fire
    if (weapon != null && weapon.CanAttack)
    {
      weapon.Attack(true);
    }
  }
}

将这个脚本绑定到我们的敌人。

你应该能看到这个(观察把子弹的旋转率改为0.75):

enemy_config

备注:如果你是在场景中修改了游戏对象,记住通过“Inspector”右上方的“Apply”按钮来保存所有的变化到Prefab。

试着运行并观察!

shoot_right

好吧!有些作用了。这个武器向右边开火是因为我们之前是这样设置的。

如果你旋转敌人,你可以使它向左开火,但是,嗯...这个精灵就会朝下方了。那不是我们想要的。

gizmo2

那又怎样!显然,我们犯这个错误是有原因的。

向任何方向射击

“WeaponScript”有它特定的方式:你可以通过旋转绑定的游戏对象来选择它的方向。在我们旋转敌人精灵的时候我们已经看过这个效果了。

技巧在于创建一个基于敌人预设的空游戏对象。

我们需要:

  1. 创造一个“空的游戏对象”,将他命名为“WeaponObject”。
  2. 删除绑定在敌人预设上的“WeaponScript”脚本。
  3. 将“WeaponScript”添加到“WeaponObject”,并且像之前一样设置子弹的预设的参数。
  4. 旋转“WeaponObject”到(0,0,180)

如果你已经在游戏对象中这样做了(不是预设),那么不要忘记“Apply”这个改变。

看起来应该是这个样子:

enemy_full_config

然而,我们在“EnemyScript”脚本上有一个小变化。

在其当前状态下,“EnemyScript”调用GetComponent()方法将会返回null。事实上,“WeaponScript”没有连接到相同游戏对象上了。

幸运的是,Unity提供了一个方法,也可以在访问游戏对象的孩子层级:调用 GetComponentInChildren() 方法。

注:和GetComponent<>()方法一样,GetComponentInChildren<>()同样存在一个复数的形式:GetComponentsInChildren()。注意到在“Component”后面的S。这个方法返回一个列表而不是第一个相应的组件。

事实上,只是为了好玩,我们还添加了一个方法来管理多个武器。我们仅仅是在操纵一个列表而不是单一的组件实例。

看下整个“EnemyScript”:

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
  private WeaponScript[] weapons;

  void Awake()
  {
    // Retrieve the weapon only once
    weapons = GetComponentsInChildren<WeaponScript>();
  }

  void Update()
  {
    foreach (WeaponScript weapon in weapons)
    {
      // Auto-fire
      if (weapon != null && weapon.CanAttack)
      {
        weapon.Attack(true);
      }
    }
  }
}

最后,通过调整“EnemyShot1”预设的“MoveScript”脚本的公共属性值来更新子弹发射的速度。子弹的速度应该比Poupli的速度要快。

shoot_ok

好!我们现在有了一个超级危险的Poulpi。

小贴士:向两个方向发射。

向两个方向发射只需要在编辑器中点击几点并且复制就可以了。它不涉及任何的脚本:

  1. 给敌人添加另一个武器(通过复制第一个“WeaponObject”)。
  2. 改变第二个“WeaponObject”的旋转角度。

敌人现在应该向两个方向发射了。

一个可能的结果是:

shoot_two_dir

这是正确使用Unity的一个很好的例子:像这样通过创建独立的脚本和一些公共的有用的变量,这样你可以大大减少代码量。更少的代码意味着更少的错误。

对玩家造成伤害

我们的Poulpi现在是危险的。对吗?恩,并不是这样的。即使他们可以射击,但是还不能伤害到主角。

我们仍然是不可战胜的。毫无挑战。

给玩家简单的添加一个“HealthScript”。一定要取消勾选“isEnemy”选项。

player_no_enemy

运行这个游戏并且观察不同之处:

player_die

小提示

我们将会给你一些小提示来让你在你的游戏射击方面得到帮助。你可以跳过这一部分,如果你对更具体的shmup思想没有兴趣的话。

玩家-敌人碰撞

让我们来看看如何处理玩家和敌人的碰撞,因为这是非常令人沮丧的,当它们互相阻挡但不产生任何的后果...

碰撞是两个非触发的Colliders 2D交叉的结果。我们需要在我们的PlayScript掌中处理OnCollisionEnter2D事件:

//PlayerScript.cs
//....

void OnCollisionEnter2D(Collision2D collision)
  {
    bool damagePlayer = false;

    // Collision with enemy
    EnemyScript enemy = collision.gameObject.GetComponent<EnemyScript>();
    if (enemy != null)
    {
      // Kill the enemy
      HealthScript enemyHealth = enemy.GetComponent<HealthScript>();
      if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);

      damagePlayer = true;
    }

    // Damage the player
    if (damagePlayer)
    {
      HealthScript playerHealth = this.GetComponent<HealthScript>();
      if (playerHealth != null) playerHealth.Damage(1);
    }
  }

关于碰撞,我们就通过HealthScript组件来对玩家和敌人造成伤害。通过这样,任何和health/damage相关的行为都将和它相连接。

弹药池

当你运行的时候,你可以在"Hierachy"中观察到游戏对象被创建并且在20s后删除(除非它们打中了一个玩家或者敌人)。

如果你需要作出弹幕的效果的话,那么你就会需要大量的弹药,这不是一个可行的方法。

使用pool是控制大量子弹的方法之一。基本上,你可以使用一个有大小限制的子弹数组。当这个数组已经满了的时候,你删掉最早的对象并且创建一个新的对象来代替。

因为比较简单就不在这实现它了。我们使用了相同的技术在绘画脚本上。

你也可以减少子弹的存活时间来使它消失的更快。

注意:请记住大量的使用实例化方法是有成本的。你需要小心的使用它。

子弹的行为

一个好的射手应该有难忘的战斗。

一些库,像BulletML,允许你轻松定义一些复杂和壮观的子弹样式。

screenshot2

如果你有兴趣做一个完整的设计游戏的话,可以看一下我们的[BulletML for Unity](BulletML for Unity)插件。

延迟射击

添加一些有武器的敌人在场景中,并且运行这个游戏。你应该看到了所有的敌人都是同步的。

我们应该简单添加一些延迟给武器:初始化冷却时间为非0的数。你也可以一个算法或者简单的使用一个随机数来替代。

敌人的速度也可以通过随机数来改动。

同样的,这些都取决于你。这完全取决于你希望你的游戏达到什么样的效果。

下一步

我们已经学会了如何为敌人配置武器。我们也学会了如何重用一些脚本来改进游戏。

我们有了一个几乎完整的射击手了。无可否认,一个最核心和基本的。

result

还在等什么,尽情地添加敌人、武器,然后测试它们的属性吧。

在下一章中,我们将学习如何扩展背景和场景来创建更大的关卡。

标签: none

?>