没错,你没看错,这次的教程不是说好的搭飞机,而是DotH!
Defence of the home
这次的教程依旧是三部曲
- 设置基本要素
- 添加互动界面
- 优化和发布
概述
好的那么在开始之前罗列一下塔防游戏的要素
- 行走路径
- 自动生成的怪物
- 防御塔和防御塔的安置地点
- 生成防御塔的按钮
- 需要守卫的目标(终点)
好的,现在开始进入正片环节。
设置场景
地面
打开Unity创建一个工程,由于要给炮塔做子弹,所以我们默认导入粒子包。
和RollBall一样,在工程开始前,先给我们的组件们创建好分类:场景,脚本,材质和贴图,还有预制件。
在Hierarchy中简历一个Plane对象,命名为Ground,作为我们的地面,当然地面只是长成这个样子有一点难看的,我们给他
贴上材质贴图。
在Project视窗中右键Material->create->Material
换到Inspector窗口中选择纹理,然后把材质拖到Component(这里是Plane)上去
当然我们可不是要做星战类游戏,所以还是换一个材质的好。)笑
设置完groud之后在Hierarchy中创建一个灯光(directional lights)并调整。
将Camera设置到一个你认为合适的角度,之后你会拥有下图
路径
再创建一个plane作为我们的道路,重命名为Path,将plane的scale值调整为(0.8,1,0.1);给出的数据是为了得到事例图中的效果,可以按照自己喜好编辑
然后拖拽到一个合适的地方
可能你的工程和上图不太一样,这是因为这个新的Plane和原有的plane在Y轴上重合(可以将PosY设为0.01)
为了让他美观一点我们重复绑定材质的步骤:建立一个材质球-》设定纹理-》将材质球绑定到path上
好像不太美观= =因为我的素材太小了,被严重的拉伸。好的为了让他变的美观,我要做一些额外的工作。
将Path的材质选择为None。
新建一个Plane,并且将它拖入Path中作为子对象。
调整plane的大小使其符合纹理图片的大小,并且移动到合适的位置,如下图。
复制这个子对象,用它把Path铺满(就好像真的在用砖块将路径铺满一样)
可以看见= =实际好像是并不美观,因为虽然没有材质,但是还是存在这一个空材质,就是那个紫红的区域。将材质的Size改为0就好了。
----》
有人会说了,那为什么不一开始就把Size改为0呢。那是因为一旦你把Material的Size改成0,你就看不见Plane了,操作上会十分困难
既然建好了一个Path,另一个就直接复制就好了,复制Path,将它绕着Y旋转90度得到一个垂直的Path2,移动到合适的地点
然后新建一个Plane命名为Corner,绑定拐弯的贴图。(重复以上步骤即可)
这样我们的场景就初步建立好了(当然很粗糙,请自行添加目的地,更改路径复杂度)
创建怪物
设定怪物生成点
MonoBehivor.InvokeRepeating(string methodname,float time,float repeatRate);
在time秒内,重复执行methodname,repeatRate次;
Instantiate(gameObject,position,rotation)
克隆原始物体,位置设置在position,设置旋转在rotation,返回的是克隆后的物体。这实际上在Unity和使用复制(ctrl+D)命令是一样的,并移动到指定的位置。如果一个游戏物体,组件或脚本实例被传入,实例将克隆整个游戏物体层次,以及所有子对象也会被克隆。所有游戏物体被激活。
public class CreateMonster:MonoBehavior
{
public GameObject Monster;
public Transform startp;
private GameObject tempMonster;
void Start(){
InvokeRepeating("MonsterFactory",2,3.0f);
void Update(){}
void MonsterFactory(){
tempMonster = Instantiate(Monster,startp.transform.postion,Quaternion.identity) as GameObject;
}
}
创建一个怪物模板
有了生成点,我们还需要一个怪物模板(预制件)。然后将Monster和BirthPoint分别挂载到脚本的Monster和 startp下。
有了怪物我们应该让他会移动。再给预制件添加一个脚本叫做MoveMonster
这个脚本应该具备改变怪物行走方向的功能。
那么如何改变怪物的方向呢?
我们不妨列举一下可能的方案
1,给Path标上tag ,实时监测Monster脚下的对象,一旦不与path接触返回上一帧,遍历其余三个方向。
伪代码可以表示成一个递归函数
void MoveMonster(dir,speed)
if(monster.checkbelow()==tag("path"))
moster.pos += speed*dir*time;
else
{
monster.dir->rotate(90,0,1,0);
MoveMonster(dir,speed);
}
2,给所有要转向的路口设立一个触发器,当物体进入时改变他的方向伪代码可以表示为
if(thisTrigger.getObject == Monster)
Monster.dir = Vec3(nextTrigger.pos - thisTrigger.pos)
很明显,后者适用于地形简单,路口数量可控的状况。而前者更适合于复杂的地图中Ai自动寻路(Minecraft的僵尸啊..)
所以我们采用第二种策略,首先创建wayPoint组件。直接复制BirthPoint将复制体移动到拐角处和终点,命名为wayPoint0,1
然后我们在脚本中添加如下代码(不懂请留言)
public class MoveMonster : MonoBehaviour {
public Transform []wayPoint;
public float speed = 200;
Vector3 currentWayPoint;
CharacterController mPlayer;
Vector3 dir;
int wayPointSub = 1;
// Use this for initialization
void Start ()
{
mPlayer = gameObject.GetComponent<CharacterController>();
currentWayPoint = wayPoint[0].position;
//获取当前的角色控制器
}
// Update is called once per frame
void Update ()
{
mPlayer.SimpleMove(dir * speed * Time.deltaTime);
if (Vector3.Distance(currentWayPoint, transform.position) < 0.1f)
{
SwitchWay();//到了wayPoint就拐弯
}
else
{
dir = (currentWayPoint -transform.position).normalized;//没到就朝着它走
}
if (Vector3.Distance(wayPoint[1].position, transform.position) < 1.0f)
{
Destroy(gameObject);//到了终点就销毁
}
}
void SwitchWay()//将wayPoint换为下一个
{
currentWayPoint = wayPoint[wayPointSub].position;
wayPointSub++;
}
然后将两个wayPoint分别挂载到脚本的公开数组中(你得先把他们变成prefab才能挂载到prefab的脚本选项里)
接下来我们就可以按下播放键查看我们的游戏了
为了美观你可以把Hierarchy中的wayPoint删掉或者关闭显示(因为transform信息已经被保存在了预制件中,所以实例并不重要(只需参数即可)
创建炮台
有了场景和怪物,接下来的就应该是防御塔了。
继承我们一贯偷懒的原则,
新建一个圆柱体当做炮台,给她装饰点材质。
这个脚本该具备的功能应该如下:
锁定场景中所有的Monster位置
生成粒子
将粒子沿着dir发射
而粒子发射器能够完成功能2,剩下的两个功能可以简化为让发射器朝向Monster
建立粒子发射器
打开之前导入的粒子包选择你喜欢的粒子(Standard Asssets-》Particles)拖拽到Tower中,作为他的子物体。
调整粒子参数(这个我们会在稍后的教程中仔细讲解)。
挂载脚本和发射器
接下来为炮台添加一个用来发射子弹的脚本(请仔细阅读注释)
public class Fire : MonoBehaviour {
public ParticleEmitter emitter;
//为了让粒子挂载到脚本中,我们需要新建一个公有变量
List<Collider> Monsters = new List<Collider>();
//为了存储场景中的Monster我们还需要建立一个数组
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
for (int i = 0; i < Monsters.Count; i++)
{
if (Monsters[i] == null)
Monsters.Remove(Monsters[i]);
}//如果Monster被销毁了就将他从数组中扔掉
}
void OnTriggerEnter(Collider m)
{
Monsters.Add(m);
}
void OnTriggerStay(Collider m)
{
Collider monster = Monsters[0];
if (monster != null)
{
MoveMonster temp = monster.GetComponent<MoveMonster>();
transform.LookAt(monster.transform);
//让炮塔面向进入碰撞器的Monster
}
}
void OnTriggerExit(Collider m)
{
Monsters.Remove(m);//离开Trigger范围之后就不再进行运算,直接移除
}
}
上面脚本中涉及的OnTrigger系列函数如果有不明白请参考之前的Roll a Ball3
然后将之前的粒子发射器挂载到public变量中。
点击运行,你就能看到我们的炮台朝着Monster开火啦!
什么,你说我骗人= =?根本看不到炮台旋转?
你没仔细看Roll a Ball吧 ......你要让炮台响应脚本,首先他得具备一个Trigger,在Inspector选项卡中,
我们选中Capsule Collider
将他的半径调大一点(我们又不是吃豆人= =)然后勾选is Trigger。
好了,那么今天的教程就到此为止了,我是妖哲,微博@卷毛的呈秀波
下期见~