我们的雄伟的飞船正在射击无辜的飞章鱼。
它不能一直这样,它们需要有回应,向我们射击,为了自…而战斗,对不起。
和我们上一章做的一样,我们将修改enemy的行为,让它也能射击子弹。
我们将制作一个新的子弹,用下面的精灵:
(右键点击保存图片)
如果你像我一样懒,复制"PlayerShot" prefab,重命名为"EnemyShot1",修改精灵为上面的家伙。
关于复制,你可以拖放到场景里来创建实例,重命名这个创建的游戏对象,再将它保存为Prefab
。
或者你可以简单直接的在目录使用
cmd + D
(OS X)或者ctrl + D
来 复制Prefab
。如果你喜欢这样做,你同样创建一整个新的精灵,刚体,碰撞体等。
正确的缩放是(0.35, 0.35, 1)
。
就像下面这样子:
如果你点击"Play",子弹会移动,还会摧毁enemy,这是因为"ShotScript"的属性 (默认是对章鱼造成伤害)。
不用改变它们,记得"WeaponScript"的最后一部分吗?可以设置它的值。
我们有一个"EnemyShot1" 预制件,从场景里将实例移除,如果有的话。
你们为player做的那样,我们需要为Enemy添加武器,让它们调用Attack()
来发射子弹。
- 为enemy添加"WeaponScript"
- 将"EnemyShot1"预制件拖放到脚本的"Shot Prefab"变量上
- 创建一个新的脚本,命名为"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)
备注:如果你在场景里修改了游戏对象,记得将所有的修改保存到
Prefab
里,使用"Apply"按钮,在右侧的"Inspector"顶部
试着运行,观察!
好的,它有点工作了,武器后右边在开火,因为这是我们叫它这样做的。
如果你旋转enemy,你可以让它往左边开火,但是,恩…这个精灵也同样倒了过来,这不是我们想要的。
所以呢?明显的,我们的这个错误是有原因的。
"WeaponScript"使用了一个流行的方法:你可以简单的通过旋转它附着的游戏对象来选择方向。前面当我们旋转enemy时已经看到了。
窍门就是创建一个空游戏对象,作为enemyPrefab
的子对象。
我们需要:
- 创建一个"Empty Game Object",命名为"Weapon"
- 删除附加在你enemy预制件上的"WeaponScript"
- 将"WeaponScript"添加到"Weapon"对象上,并像之前一样设置射击预制件的属性
- 旋转"Weapon"到
(0, 0, 180)
如果你对游戏对象做了处理 (包括不是Prefab
的),别忘了点击保存变化。
现在应该像这样:
然而,你还需要对"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"的属性来修改射击速度,它应该移动得比章鱼快一些:
非常好,我们现在有了一个非常危险的章鱼了。
朝两个方向开火只需要在编辑器里做一些点击和复制就可以了,它不用修改任何代码:
- 添加另一把武器给enemy (通过复制第一个"Weapon")
- 改变第二个"Weapon"的方向
现在,enemy将向两个方向射击了。
可能的效果:
这是一个正确使用Unity的一个很好的例子:像这样创建独立的脚本,设置一些有用的公有变量,你可以大幅的减少代码量,越少的代码意味着越少的错误。
我们的章鱼是危险的,对吧?啊,并不,虽然它们可以射击,但并不能对玩家角色造成伤害。
我们仍然不怕,这里没有挑战。
简单的为player添加"HealthScript",确保没有选中"IsEnemy"选项。
运行游戏,注意不同:
我们将给你一些提示,以便你可以更好的理解游戏的射击。我可以跳过这一部分,如果你对更多详细的shmup
思想不感兴趣的话。
来看看,我们要如何驾驭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,可以让你们很容易的定义一些复杂和壮观的子弹模式。
如果你对制作一款Shoot'Em Up game非常的有兴趣,可以看看我们的BulletML for Unity插件。
添加一些武装的敌人到场景里,然后运行游戏,你应该看到所有敌人是如此同步。
我们可以为武器简单的增加延时,冷却时间初始化为大于0的数值,你可以使用一个算法或是简单的用一个随机数替代。
敌人的速度也可以用一个随机数替代。
再一次,看你,它完全取决于你想如何实现。
你已经学会了如果给我们的enemies添加武器,同时,我们看到如何重用一些脚本来增加玩法。
我们有一个几乎完整的射手!一个非常基础,核心的东西,不可否认。
不要犹豫添加enemies,武器,并去尝试调整它们的属性。
在下一章,我们将学习如何加强背景和场景去创建一个更大的关卡。