Skip to content

Latest commit

 

History

History
281 lines (175 loc) · 11.2 KB

07.shooting(2.2).md

File metadata and controls

281 lines (175 loc) · 11.2 KB

< 前一篇 后一篇 >

射击(2/2)

我们的雄伟的飞船正在射击无辜的飞章鱼。

它不能一直这样,它们需要有回应,向我们射击,为了自…而战斗,对不起。

和我们上一章做的一样,我们将修改enemy的行为,让它也能射击子弹。

敌人的子弹

我们将制作一个新的子弹,用下面的精灵:

Poulpi shot Sprite

(右键点击保存图片)

如果你像我一样懒,复制"PlayerShot" prefab,重命名为"EnemyShot1",修改精灵为上面的家伙。

关于复制,你可以拖放到场景里来创建实例,重命名这个创建的游戏对象,再将它保存为Prefab

或者你可以简单直接的在目录使用cmd + D (OS X)或者ctrl + D 来 复制Prefab

如果你喜欢这样做,你同样创建一整个新的精灵,刚体,碰撞体等。

正确的缩放是(0.35, 0.35, 1)

就像下面这样子:

Poulpi shot configuration

如果你点击"Play",子弹会移动,还会摧毁enemy,这是因为"ShotScript"的属性 (默认是对章鱼造成伤害)。

不用改变它们,记得"WeaponScript"的最后一部分吗?可以设置它的值。

我们有一个"EnemyShot1" 预制件,从场景里将实例移除,如果有的话。

开火

你们为player做的那样,我们需要为Enemy添加武器,让它们调用Attack()来发射子弹。

新的脚本和设置

  1. 为enemy添加"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);
    }
  }
}

将这个脚本添加给我们的enemy。

就像下面这样 (注意轻微的增加shooting rate到0.75)

Poulpi configuration with weapon

备注:如果你在场景里修改了游戏对象,记得将所有的修改保存到Prefab里,使用"Apply"按钮,在右侧的"Inspector"顶部

试着运行,观察!

Poulpi is shooting

好的,它有点工作了,武器后右边在开火,因为这是我们叫它这样做的。

如果你旋转enemy,你可以让它往左边开火,但是,恩…这个精灵也同样倒了过来,这不是我们想要的。

Upside down Poulpi

所以呢?明显的,我们的这个错误是有原因的。

往任意方向射击

"WeaponScript"使用了一个流行的方法:你可以简单的通过旋转它附着的游戏对象来选择方向。前面当我们旋转enemy时已经看到了。

窍门就是创建一个空游戏对象,作为enemyPrefab的子对象。

我们需要:

  1. 创建一个"Empty Game Object",命名为"Weapon"
  2. 删除附加在你enemy预制件上的"WeaponScript"
  3. 将"WeaponScript"添加到"Weapon"对象上,并像之前一样设置射击预制件的属性
  4. 旋转"Weapon"到(0, 0, 180)

如果你对游戏对象做了处理 (包括不是Prefab的),别忘了点击保存变化。

现在应该像这样:

Enemy with a new object

然而,你还需要对"EnemyScript"脚本做一点点调整。

在当前的情形里,"EnemyScript"调用GetComponent<WeaponScript>()会返回null,的确,"WeaponScript"不再附加到游戏对象上。

幸运的是,Unity提供了一个方法,它可以找到游戏对象的子对象:GetComponentInChldren<Type>()方法

:像GetComponent<>()GetComponentInChildren<>()都存在一个复制方法:GetComponentsInChildren<Type>()。注意s在"Component"的后面。这个方法返回一个数组而不是第一个相关的组件

实事上,如果纯娱乐的话,我们还有另外的方法去管理多个武器。我们刚才操作数组而不是一个实例组件。

看看"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"的属性来修改射击速度,它应该移动得比章鱼快一些:

Super dangerous Poulpi

非常好,我们现在有了一个非常危险的章鱼了。

奖励:朝两个不同的方向开火

朝两个方向开火只需要在编辑器里做一些点击和复制就可以了,它不用修改任何代码:

  1. 添加另一把武器给enemy (通过复制第一个"Weapon")
  2. 改变第二个"Weapon"的方向

现在,enemy将向两个方向射击了。

可能的效果:

A "super super" dangerous Poulpi with two guns

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

伤害玩家

我们的章鱼是危险的,对吧?啊,并不,虽然它们可以射击,但并不能对玩家角色造成伤害。

我们仍然不怕,这里没有挑战。

简单的为player添加"HealthScript",确保没有选中"IsEnemy"选项

Script configuration for player health

运行游戏,注意不同:

Player hit by an enemy projectile

奖励

我们将给你一些提示,以便你可以更好的理解游戏的射击。我可以跳过这一部分,如果你对更多详细的shmup思想不感兴趣的话。

PLAYER-ENEMY碰撞

来看看,我们要如何驾驭player和enemy之前的碰撞,如果看到它们撞到一起,却没有什么反应,那是当然不爽的...

碰撞是两个非触发Colliders 2D对象相交的结果。

我们需要简单的处理PlayerScript脚本里的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脚本组件对player和enemy同时造成伤害,这样做,它们都和health/damage建立了联系。

子弹池

就像你在玩的时候注意到的,在"Hierarchy"里,游戏对象被创建,20秒后被删除 (除非它们击中player或一个enemy)。

如果你打算做一款需要子弹非常多的游戏, danmaku 弹幕游戏,这(上面的处理方法)不再以一个可行的技术了。

处理大量子弹的一种解决方案是使用,主要来讲,你可以使用一个有限长度的子弹数组,当数组满了,删除较早的对象,用新的替换它。

我们不会在这里实现,但它真的很简单。我们使用the same technique on a painting script

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

注意:请记住,使用Instantiate方法会产生大量的消耗,你应该小心的使用它

子弹行为

一个好的枪手有他值得纪念的战斗。

有一些库,像BulletML,可以让你们很容易的定义一些复杂和壮观的子弹模式。

BulletML for Unity

如果你对制作一款Shoot'Em Up game非常的有兴趣,可以看看我们的BulletML for Unity插件。

延迟射击

添加一些武装的敌人到场景里,然后运行游戏,你应该看到所有敌人是如此同步。

我们可以为武器简单的增加延时,冷却时间初始化为大于0的数值,你可以使用一个算法或是简单的用一个随机数替代。

敌人的速度也可以用一个随机数替代。

再一次,看你,它完全取决于你想如何实现。

下一步

你已经学会了如果给我们的enemies添加武器,同时,我们看到如何重用一些脚本来增加玩法。

我们有一个几乎完整的射手!一个非常基础,核心的东西,不可否认。

Result

不要犹豫添加enemies,武器,并去尝试调整它们的属性。

在下一章,我们将学习如何加强背景和场景去创建一个更大的关卡。

< 前一篇 后一篇 >