Skip to content

Latest commit

 

History

History
368 lines (259 loc) · 11.6 KB

06.shooting(1.2).md

File metadata and controls

368 lines (259 loc) · 11.6 KB

< 前一篇 后一篇 >

射击(1/2)

我们的飞船面对的是可怕的章鱼,但却束手无策...

让我们授予它一把武器和一些弹药!这将包含一些脚本,但请相信,它是值得的。

发射物

首先,在让player射击前,我们需要定义一个游戏对象,用来描绘我们要用的发射物。

如下的精灵:

Shot Sprite

(右键点击保存)

发射物是我们要用得非常多的对象:当player射击时,在屏幕上将会出现很多的实例。

我们将如何处理这事儿呢?当然是Prefab了!

准备Prefab

你现在可以按如下步骤:

  1. 导入图片
  2. 在场景里创建一个新的Sprite
  3. 将图片设置到sprite里
  4. 将"Sprite Layer"设置为"Bullets"
  5. 添加一个"Rigidbody 2D",将"Gravity Scale"和"Fixed Angles"置为0
  6. 添加一个"Box Collider 2D"

将绽放(scale)设置为(0.5, 0.5, 1),会让我们看着更舒服些。

然后,这次,我们需要在"Inspector"里设置一个新的参数:

  1. 在"Box Collider 2D"里,选中"Is Trigger"属性

一个trigger碰撞体,当碰撞发生时,同样会有事件,但它不会进行物理模拟。

意思是射击会在接触对象后穿过它 — 将不会再有"真实"的相互作用。同时,另一个碰撞体同样有"OnTriggerEnter2D"事件发生。

乖乖,你有了射击的东西,该写行为脚本了。

创建一个新的脚本,命名为"ShotScript":

using UnityEngine;

/// <summary>
/// Projectile behavior
/// </summary>
public class ShotScript : MonoBehaviour
{
  // 1 - Designer variables

  /// <summary>
  /// Damage inflicted
  /// </summary>
  public int damage = 1;

  /// <summary>
  /// Projectile damage player or enemies?
  /// </summary>
  public bool isEnemyShot = false;

  void Start()
  {
    // 2 - Limited time to live to avoid any leak
    Destroy(gameObject, 20); // 20sec
  }
}
  1. 一些变量做为速度,方向和伤害
  2. 为了避免一些问题,我们将在20秒后摧毁对象

将"ShotScript"附加到精灵上,同时添加"MoveScript",这样你的射击才会移动。

拖动"Project"框里的shot游戏对象,用以创建一个Prefab,我们将在一些步骤里里需要它。

你的设置如下:

Shot configuration 1

如果你使用"Play"按钮来运行游戏,可以看到射击已经在移动。

碰撞和伤害

然后,射击并没有摧毁任何东西。

不要惊讶,我们没有制造伤害,我们需要一个新的脚本,"HealthScript":

using UnityEngine;

/// <summary>
/// Handle hitpoints and damages
/// </summary>
public class HealthScript : MonoBehaviour
{
  /// <summary>
  /// Total hitpoints
  /// </summary>
  public int hp = 1;

  /// <summary>
  /// Enemy or player?
  /// </summary>
  public bool isEnemy = true;

  /// <summary>
  /// Inflicts damage and check if the object should be destroyed
  /// </summary>
  /// <param name="damageCount"></param>
  public void Damage(int damageCount)
  {
    hp -= damageCount;

    if (hp <= 0)
    {
      // Dead!
      Destroy(gameObject);
    }
  }

  void OnTriggerEnter2D(Collider2D otherCollider)
  {
    // Is this a shot?
    ShotScript shot = otherCollider.gameObject.GetComponent<ShotScript>();
    if (shot != null)
    {
      // Avoid friendly fire
      if (shot.isEnemyShot != isEnemy)
      {
        Damage(shot.damage);

        // Destroy the shot
        Destroy(shot.gameObject); // Remember to always target the game object, otherwise you will just remove the script
      }
    }
  }
}

将"HealthScript"附加到章鱼Prefab上。

注意:直接对Prefab做设置是比较好的。

这样做,在场景里的每个enemy实例,都会因为Prefab而得到修改。这里是非常重要的,因为我们在场景里有非常多的enemies。

如果你只是有一个游戏对象上做设置,而不是Prefab,不要担心,你可以点击"Inspector"顶部的"Apply"按钮将改变保存到Prefab里。

确保子弹和章鱼在同一条线上,测试一下。

:2D物理引擎,Box2D,不会用到z坐标,Colliders 2D将总是在同一平面,除非你的游戏对象不在。

现在运行游戏,观察:

Enemy is shot

如果enemy的生命值大于射击的伤害值,它不会死。试着改变hp的值,在enemy的"HealthScript":

Enemy is shot but has more HP

开火

将射击物从场景里删除,我们已经完成了它,它已经不需要了。

我们需要一个新的脚本用来开火,创建一个,命名为"WeaponScript"。

这个脚本将在到处被重复使用(playes, enemies等),它的作用是在在附着的游戏对象的前面实例化一个射击物。

这里是完整的代码,比平常多一点,解释在下面:

using UnityEngine;

/// <summary>
/// Launch projectile
/// </summary>
public class WeaponScript : MonoBehaviour
{
  //--------------------------------
  // 1 - Designer variables
  //--------------------------------

  /// <summary>
  /// Projectile prefab for shooting
  /// </summary>
  public Transform shotPrefab;

  /// <summary>
  /// Cooldown in seconds between two shots
  /// </summary>
  public float shootingRate = 0.25f;

  //--------------------------------
  // 2 - Cooldown
  //--------------------------------

  private float shootCooldown;

  void Start()
  {
    shootCooldown = 0f;
  }

  void Update()
  {
    if (shootCooldown > 0)
    {
      shootCooldown -= Time.deltaTime;
    }
  }

  //--------------------------------
  // 3 - Shooting from another script
  //--------------------------------

  /// <summary>
  /// Create a new projectile if possible
  /// </summary>
  public void Attack(bool isEnemy)
  {
    if (CanAttack)
    {
      shootCooldown = shootingRate;

      // Create a new shot
      var shotTransform = Instantiate(shotPrefab) as Transform;

      // Assign position
      shotTransform.position = transform.position;

      // The is enemy property
      ShotScript shot = shotTransform.gameObject.GetComponent<ShotScript>();
      if (shot != null)
      {
        shot.isEnemyShot = isEnemy;
      }

      // Make the weapon shot always towards it
      MoveScript move = shotTransform.gameObject.GetComponent<MoveScript>();
      if (move != null)
      {
        move.direction = this.transform.right; // towards in 2D space is the right of the sprite
      }
    }
  }

  /// <summary>
  /// Is the weapon ready to create a new projectile?
  /// </summary>
  public bool CanAttack
  {
    get
    {
      return shootCooldown <= 0f;
    }
  }
}

将此脚本附加到player上。

此脚本分为3个部分:

1. 在"Inspector"面板里可见的变量

我们有两个成员:shotPrefabshootingRate

第一个需要设置为我们要在武器里使用的射击物对象。

在"Hierarchy"里选择player,在"WeaponScript"组件里,你可以看到属性"Shot Prefab"是没有值的。

拖动"Shot" prefab到它上面:

Using a prefab

Unity会自动使用此信息填充脚本,是不是很简单?

ShootingRate变量在代码里有默认值,这次我们将不改变它。但你可以运行游戏,试着做一些测试。

小心:在Unity"Inspector"里改变变量的值,并不会改变它在脚本里的默认值,如果你将脚本添加给另外的对象,使用的仍然是脚本里的默认值

这是合理的,但你需要小心,如果你想确保调整的值,你需要在自己去代码编辑器里移植。

2.冷却时间

枪有发射频率,如果没有,在每一帧,你可能会创建成吨的射发物。

我们有一个简单的冷却原理,使用记数器,每帧减1,如果它大于0,我们将不能射击。

3. 公有攻击方法

这个脚本有一个主要的目的:被另外的脚本调用。这就是为什么要用公有方法来创建发射物的原因。

一但发射物实例化,we retrieve the scripts of the shot object and override some variables.

GetComponent<TypeOfComponent>()方法可以准确的获取组件对象 (同样适用于脚本,因为脚本也是一个组件),范型用于指定你想要使用的组件。

GetComponents<TypeOfComponent>()方法会获取一组对象,而不是第一个。

使用武器

在这个时候,如果你运行游戏,并没有任何的改变,我们创建一个一武器,但并没有使用。

的确,即使把"WeaponScript"附到一个实体上,但Attack(bool)方法并没有被调用。

让我们回到"PlayerScript"。

Update()方法里,写上如下代码:

void Update()
  {
    // ...

    // 5 - Shooting
    bool shoot = Input.GetButtonDown("Fire1");
    shoot |= Input.GetButtonDown("Fire2");
    // Careful: For Mac users, ctrl + arrow is a bad idea

    if (shoot)
    {
      WeaponScript weapon = GetComponent<WeaponScript>();
      if (weapon != null)
      {
        // false because the player is not an enemy
        weapon.Attack(false);
      }
    }

    // ...
  }

在这里,如果你把它放到移动的前面或后面,都不会有什么问题。

我们要做什么?

  1. 我们从开火键获取输入值 (默认是clickctrl)
  2. 我们加载武器脚本
  3. 调用Attack(false)

按下按钮:你可能注意到,我们使用GetButtonDown()方法来获取输入,最后的"Down"允许我们在按钮被按下一次且只有一次时获取其输入,GetButton()在每一帧都返回true,直到按钮被释放。在我们的这里,我们显然想要GetButtonDown()`方法的行为。

试着使用GetButton()来代替,观察它们的不同

运行游戏:

Working shooting

子弹太慢了?尝试找到"Shot" prefab的设置项,并设置为你喜欢的值。

红利:好玩,给player增加一个倾斜度,像(0, 0, 45),射击将会有一个45度的移动,但射击物的旋转角度并不正确,因为我们也没有改变它。

Shooting rotation

下一步

我们有了射手!一个非常基础的版本,但毕竟是射手。你学会了如何创建武器,并可以开火摧毁目标,试着添加更多的敌人。:)

但这一部分还没有结束!我们想enemies也可以射击,休息一下,接下来,我们主要是利用刚才我们做的内容。

< 前一篇 后一篇 >