我们的华丽飞车现在在射杀毫无反抗的章鱼。
不能一直这样,它们需要反击,为自由而战。哎呀,不好意思,有点激动!
使用上一章节我们学习的,我们将要修改敌人的行为来使它们也可以发射弹药。
我们将要使用这个精灵创造一个新的弹药:
(右键点击来保存这个图片)
如果你和我一样懒,复制“PlayerShot”预设(prefab),将其重命名为“EnemyShot1”,并且使用上面所给的图片改变这个精灵。
你可以通过在场景中拖拽创建的实例来进行复制,重命名这个游戏对象最后再将其保存为一个预设。
或者你可以简单的直接在文件夹中通过cmd+D(OS X)或者Ctrl+D(Windows)快捷键复制Preafb。如果你想复杂一些的话,你可以重新创造一个新的精灵,刚体,新的碰撞触发器等等。
合适的缩放比例为(0.35,0.35,1)
你会看到如下图所示场景:
如果你点击“Play”,子弹将会移动并且可能会摧毁敌人。这是因为”ShotScript“脚本的属性,默认是对Poupli有伤害的。
不要改变它们。还记得上一章的”WeaponScript“脚本么?它将会正确的设置这些值。
我们有了”EnemyShot1“预设。移除场景中它的实例如果有的话。
正如我们为玩家所做的一样,我们需要给敌人添加一个武器并且让它调用Attack(),从而创建弹药。
using UnityEngine;
///
/// Enemy generic behavior
///
public class EnemyScript : MonoBehaviour
{
private WeaponScript weapon;
void Awake()
{
// Retrieve the weapon only once
weapon = GetComponent();
}
void Update()
{
// Auto-fire
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
将这个脚本绑定到我们的敌人。
你应该能看到这个(观察把子弹的旋转率改为0.75):
备注:如果你是在场景中修改了游戏对象,记住通过“Inspector”右上方的“Apply”按钮来保存所有的变化到Prefab。
试着运行并观察!
好吧!有些作用了。这个武器向右边开火是因为我们之前是这样设置的。
如果你旋转敌人,你可以使它向左开火,但是,嗯...这个精灵就会朝下方了。那不是我们想要的。
那又怎样!显然,我们犯这个错误是有原因的。
“WeaponScript”有它特定的方式:你可以通过旋转绑定的游戏对象来选择它的方向。在我们旋转敌人精灵的时候我们已经看过这个效果了。
技巧在于创建一个基于敌人预设的空游戏对象。
我们需要:
如果你已经在游戏对象中这样做了(不是预设),那么不要忘记“Apply”这个改变。
看起来应该是这个样子:
然而,我们在“EnemyScript”脚本上有一个小变化。
在其当前状态下,“EnemyScript”调用GetComponent()方法将会返回null。事实上,“WeaponScript”没有连接到相同游戏对象上了。
幸运的是,Unity提供了一个方法,也可以在访问游戏对象的孩子层级:调用 GetComponentInChildren() 方法。
注:和GetComponent<>()方法一样,GetComponentInChildren<>()同样存在一个复数的形式:GetComponentsInChildren()。注意到在“Component”后面的S。这个方法返回一个列表而不是第一个相应的组件。
事实上,只是为了好玩,我们还添加了一个方法来管理多个武器。我们仅仅是在操纵一个列表而不是单一的组件实例。
看下整个“EnemyScript”:
using System.Collections.Generic;
using UnityEngine;
///
/// Enemy generic behavior
///
public class EnemyScript : MonoBehaviour
{
private WeaponScript[] weapons;
void Awake()
{
// Retrieve the weapon only once
weapons = GetComponentsInChildren();
}
void Update()
{
foreach (WeaponScript weapon in weapons)
{
// Auto-fire
if (weapon != null && weapon.CanAttack)
{
weapon.Attack(true);
}
}
}
}
最后,通过调整“EnemyShot1”预设的“MoveScript”脚本的公共属性值来更新子弹发射的速度。子弹的速度应该比Poupli的速度要快。
好!我们现在有了一个超级危险的Poulpi。
向两个方向发射只需要在编辑器中点击几点并且复制就可以了。它不涉及任何的脚本:
敌人现在应该向两个方向发射了。
一个可能的结果是:
这是正确使用Unity的一个很好的例子:像这样通过创建独立的脚本和一些公共的有用的变量,这样你可以大大减少代码量。更少的代码意味着更少的错误。
我们的Poulpi现在是危险的。对吗?恩,并不是这样的。即使他们可以射击,但是还不能伤害到主角。
我们仍然是不可战胜的。毫无挑战。
给玩家简单的添加一个“HealthScript”。一定要取消勾选“isEnemy”选项。
运行这个游戏并且观察不同之处:
我们将会给你一些小提示来让你在你的游戏射击方面得到帮助。你可以跳过这一部分,如果你对更具体的shmup思想没有兴趣的话。
让我们来看看如何处理玩家和敌人的碰撞,因为这是非常令人沮丧的,当它们互相阻挡但不产生任何的后果...
碰撞是两个非触发的Colliders 2D交叉的结果。我们需要在我们的PlayScript掌中处理OnCollisionEnter2D事件:
//PlayerScript.cs
//....
void OnCollisionEnter2D(Collision2D collision)
{
bool damagePlayer = false;
// Collision with enemy
EnemyScript enemy = collision.gameObject.GetComponent();
if (enemy != null)
{
// Kill the enemy
HealthScript enemyHealth = enemy.GetComponent();
if (enemyHealth != null) enemyHealth.Damage(enemyHealth.hp);
damagePlayer = true;
}
// Damage the player
if (damagePlayer)
{
HealthScript playerHealth = this.GetComponent();
if (playerHealth != null) playerHealth.Damage(1);
}
}
关于碰撞,我们就通过HealthScript组件来对玩家和敌人造成伤害。通过这样,任何和health/damage相关的行为都将和它相连接。
当你运行的时候,你可以在"Hierachy"中观察到游戏对象被创建并且在20s后删除(除非它们打中了一个玩家或者敌人)。
如果你需要作出弹幕的效果的话,那么你就会需要大量的弹药,这不是一个可行的方法。
使用pool是控制大量子弹的方法之一。基本上,你可以使用一个有大小限制的子弹数组。当这个数组已经满了的时候,你删掉最早的对象并且创建一个新的对象来代替。
因为比较简单就不在这实现它了。我们使用了相同的技术在绘画脚本上。
你也可以减少子弹的存活时间来使它消失的更快。
注意:请记住大量的使用实例化方法是有成本的。你需要小心的使用它。
一个好的射手应该有难忘的战斗。
一些库,像BulletML,允许你轻松定义一些复杂和壮观的子弹样式。
如果你有兴趣做一个完整的设计游戏的话,可以看一下我们的[BulletML for Unity](BulletML for Unity)插件。
添加一些有武器的敌人在场景中,并且运行这个游戏。你应该看到了所有的敌人都是同步的。
我们应该简单添加一些延迟给武器:初始化冷却时间为非0的数。你也可以一个算法或者简单的使用一个随机数来替代。
敌人的速度也可以通过随机数来改动。
同样的,这些都取决于你。这完全取决于你希望你的游戏达到什么样的效果。
我们已经学会了如何为敌人配置武器。我们也学会了如何重用一些脚本来改进游戏。
我们有了一个几乎完整的射击手了。无可否认,一个最核心和基本的。
还在等什么,尽情地添加敌人、武器,然后测试它们的属性吧。
在下一章中,我们将学习如何扩展背景和场景来创建更大的关卡。