U3D DotH教程

没错,你没看错,这次的教程不是说好的搭飞机,而是DotH!

Defence of the home

这次的教程依旧是三部曲

  1. 设置基本要素
  2. 添加互动界面
  3. 优化和发布

概述

好的那么在开始之前罗列一下塔防游戏的要素

  1. 行走路径
  2. 自动生成的怪物
  3. 防御塔和防御塔的安置地点
  4. 生成防御塔的按钮
  5. 需要守卫的目标(终点)

好的,现在开始进入正片环节。


设置场景

地面

打开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,绑定拐弯的贴图。(重复以上步骤即可)

这样我们的场景就初步建立好了(当然很粗糙,请自行添加目的地,更改路径复杂度)



创建怪物

有了地图,那我们就来创建一些会沿着路径行走的怪物。

设定怪物生成点

创建一个Cube,拖拽到左下角的Path入口出,重命名为BirthPoint。给它添加一个脚本“CreateMonster”。
作为怪物生成点,我们的脚本需要具备一个功能,批量复制moster对象
这里我们需要学习两个函数来完成这个功能。
MonoBehivor.InvokeRepeating(string methodname,float time,float repeatRate);
在time秒内,重复执行methodname,repeatRate次;

Instantiate(gameObject,position,rotation)
 
 克隆原始物体,位置设置在position,设置旋转在rotation,返回的是克隆后的物体。这实际上在Unity和使用复制(ctrl+D)命令是一样的,并移动到指定的位置。如果一个游戏物体,组件或脚本实例被传入,实例将克隆整个游戏物体层次,以及所有子对象也会被克隆。所有游戏物体被激活。 
看了这两个函数之后,我们很容易想到,使用InvokeRepeating重复调用Instantiate就可以满足我们的需求。
所以我们的脚本如下
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;
    }
}

创建一个怪物模板

有了生成点,我们还需要一个怪物模板(预制件)。
在这里我们直接使用Sphere来偷懒,给它加一个纹理充当怪物,命名为Monster。把他拖到Project下的Prefab中。
然后将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。

好了,那么今天的教程就到此为止了,我是妖哲,微博@卷毛的呈秀波

下期见~







(1)编GtTt)现算文“testtxt”该文本文件中的内容如下所示: Good now, sit down, and tell me, he that knows. Why this same strict and most observant watch So nightly toils the subject of the land, And why such daily cast of brazen cannon, And foreign mart for implements of war, Why such impress of shipwrights, whose sore task Does not divide the Sunday from the week; 第5部分编程测试题 81 What might be toward, that this sweaty haste Doth make the night joint-labourer with the day: Who is't that can inform me? (2)将读取的文本进行分词后放入列表lw中(注意处理大小写和除标点符)并按厅16个单词的格式输出结果,如图 5-2 所示。 调信的结果为: sod nou sit domm and tell me he that knows why this sime strict and mast lstrvant watch so nightlz toils the subiect of the land and sto maoh aily caat of nzen cannen and foreign micrt for implements of war why ruch iwprest of shipnrigits whecs shr it does not divide the sunday from the weok what might be torand that this iweety doth make the nlght oint-lab urer viih the day wno itt that ca inlers me 图5-2输出结果1 (3)根据列表lw的内容生成字典dic(键为单词,值为单词个数)。 (4)根据字典dic,分别输出单词of、and、such、why的个数,按图 5-3所示的格式输出结果。 列表 海结更为: 买,有4个 and有4个 auch有2个 he有3个 图5-3输出结果2 (5)将写程文件名“zhpy”保存算M盘根目录。 请写出代码并说明代码的意思
最新发布
05-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值