【Unity3D开发小游戏】植物大战僵尸

博客分类: Unity3D-Game 阅读次数: comments

【Unity3D开发小游戏】植物大战僵尸

@[TOC]

一、前言

今天我们要用Unity3D做一个植物大战僵尸的仿制版本。为了保持简单,我们将忽略所有花哨的东西,如菜单,多层关卡或过场动画,并专注于在后院与僵尸作战。

效果图:

在这里插入图片描述

二、源码

UI资源和源代码请搜索QQ群:1040082875下载

三、正文

版本

==Unity5.0.1f1==

1.主摄像机设置

如果我们选择主照相机在层次性然后我们可以设置背景色若要黑色,请调整大小而位置如下图所示: 在这里插入图片描述

2.创造草地

在这里插入图片描述 注意:右击图像,选择另存为。保存到项目的资产文件夹并将其保存到新的Sprites文件夹。

让我们在项目区选择它: 在这里插入图片描述 然后修改导入设置在==Inspector==: [Inspector]:自定义设置面板 在这里插入图片描述 *注:Pixels to Unit 值设成32这意味着32 x 32像素将适合在游戏世界的一个单位。我们将使用这个值作为我们所有的纹理。

之后,我们可以将图片从项目区拖入到场景中: 在这里插入图片描述 我们会把它定位在(0, 0)所以它在我们游戏的左下角: 在这里插入图片描述 添加排序层 我们正在制作一个2D游戏,所以我们不能用三维作为深度效果,也不能轻易区分背景和前景。相反,我们将使用Sorting Layer告诉统一它应该先画哪些元素。例如,我们应该先画背景图,然后再画植物。(因此在背景之上).

我们可以换草Sorting Layer如果我们看看Inspector中的Sprite Renderer组件:

在这里插入图片描述 让我们选择添加排序层.。从分选层列表,添加Background层,并将其移动到顶部,如下所示: 在这里插入图片描述 之后,我们再次选择草,并分配先前创建的Background分类层: 在这里插入图片描述 ==注意:Unity从上到下画层,因此背景中的任何内容都将位于列表的顶部。==

现在,草将永远被植物和僵尸所吸引。

3.草皮音效

稍后,在处理“构建”菜单时,我们需要一种方法来确定是否单击了草瓦。在United中有各种不同的方法来实现这一点,但最简单的方法是只使用OnMouseUpAsButton函数,如果单击了草,则由United自动调用该函数。

现在这里有一件事要记住:只有当游戏对象有对撞机时,Unity才能做到这一点。所以让我们选择添加组件->物理二维->Box Collider 2D在Inspector并启用Is Trigger 选项:

复制草 现在我们的游戏中只有一片草瓦。让我们在Hierarchy,选择Duplicate然后把它放在(1, 0)..然后我们将再次复制它,并将其放置在(2, 0)..我们会继续复制,直到我们有10 * 5左下角瓷砖位于(0, 0)右上方的瓷砖在(9, 4):

在这里插入图片描述 ==注意:重要的是所有的瓷砖都有圆形的位置(2, 3)从来没有(2.01, 3.023).==

4.生命值脚本

僵尸应该能够攻击植物,并且开火的植物也可以攻击讲师。

我们需要创建一个Health.cs

我们可以通过右键单击项目区然后选择Create->C#脚本: 在这里插入图片描述 我们给它起个名字Health.cs然后拖入到新的Scripts文件夹: 在这里插入图片描述 我们可以通过双击脚本打开并修改脚本:

using UnityEngine;
using System.Collections;

public class Health : MonoBehaviour {

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

我们不需要Start或者Update函数,所以让我们移除这两个函数。 相反,我们将添加一个INT变量cur,该变量跟踪当前的生命值,并添加一个减少该变量的函数。 如果当前的生命值低于0然后,应摧毁该实体:

using UnityEngine;
using System.Collections;

public class Health : MonoBehaviour {
    // Current Health
    [SerializeField]
    int cur = 5;

    public void doDamage(int n) {
        // Subtract damage from current health
        cur -= n;

        // Destroy if died
        if (cur <= 0)
            Destroy(gameObject);
    }
}

==注:我们使用[SerializeField]属性让Unity知道我们希望能够在Inspector面板中修改cur变量。这通常这是通过public访问权限,但在这种情况下,我们不希望其他脚本能够访问cur变量。相反,他们应该使用doDamage函数。 该脚本稍后将添加到所有植物和僵尸。==

5.创建向日葵植物

向日快图片

由于我们正在开发一个2D游戏,动画是非常容易的。

对于我们的向日葵,我们只需要一个闲置的动画,它的头部轻微移动: 在这里插入图片描述 注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。

让我们在项目区然后在Inspector面板中修改导入设置

在这里插入图片描述 这个Filter Mode和Format影响图片的分辨率。 设置Sprite Mode为Multiple告诉Unity,这一张照片可以分割成几个向日葵。

切片

我们可以打开Inspector面板中按Sprite Editor按钮

之后,我们可以在Sprite Editor中看到我们的向日葵:在这里插入图片描述 我们要做的就是打开Slice菜单,将类型设置为格网像素大小为32 x 32..之后,我们按下Slice按钮: 在这里插入图片描述 让我们按Apply然后关闭 Sprite Editor

如果我们看看项目区然后我们可以看到我们的向日葵现在有两个子节点: 在这里插入图片描述

6.向日葵动画

从这两个slices创建一个Unity动画是非常容易的。我们所要做的就是在项目区然后把他们拖到场景中: 在这里插入图片描述 一旦我们把它拖到场景中,Unity会问我们在哪里保存动画。 我们可以在我们的项目区创造一个新的SunflowerAnimation文件夹,然后将其保存为idle.anim.

Unity会为我们创建两个文件: 在这里插入图片描述 这就是我们的动画,如果我们按下Play: 在这里插入图片描述 ==注意:我们可以在SunflowerAnimation文件夹中通过双击sunflower_0,选择idle状态,然后可以在Inspector面板更改speed==

7.添加物理、标签与生命值

我们的植物应该是物理世界的一部分,这意味着我们必须分配一个Collider给它。 一个Collider确保物体会与植物发生碰撞,而植物也会与其他物体发生碰撞。

让我们选择植物,然后在Inspector面板按下添加组件->Physics 2D->Box Collider 2D: 在这里插入图片描述 我们还需要找出某个游戏对象是一个植物,一个僵尸,还是完全不同的东西。 这就需要Unity标签系统了。 如果我们看看Inspector,我们可以看到当前标记是Untagged: 在这里插入图片描述 我们选择Plant标签。 在标记列表中,然后添加Plant标记到可用标签列表, 再次选择plant,然后从标记列表中分配标记: 在这里插入图片描述 最后在Inspector那里添加组件->Scripts->Health 僵尸可以在稍后对草地plant造成伤害:

在这里插入图片描述 注:我们的向日葵刚刚被随意放置在现场的某个地方。我们将把它保存在那里,下一步我们将生成阳光

5.生成阳光

画阳光 向日葵植物每隔几秒钟就会孕育出一个新的阳光,所以让我们先画一个: 在这里插入图片描述 ==注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。==

我们将使用以下方法导入设置: 在这里插入图片描述 Foreground Layer前景层 我们希望太阳被画在背景前面和植物前面。让我们点击添加排序层。 再一次创造另一个分选层,说出来前景并将其移至列表的底部: 在这里插入图片描述 之后,我们可以再次选择sun并分配Foreground排序层: 在这里插入图片描述 创建预制件

让我们把阳光拖到Hierarchy一次,创建一个游戏对象: 在这里插入图片描述 然后按下添加组件->Physics 2D>Circle Collider 2D 碰撞器可以让阳光可以接收碰撞信息: 在这里插入图片描述 ==注意:在实际的植物大战僵尸游戏中,阳光并没有真正地与植物或僵尸发生碰撞。我们可以通过在Circle Collider 2D中勾选Is Trigger。这意味着阳光接收到碰撞信息,但从未真正与任何物体发生碰撞。僵尸将能够穿过它,而不是与它相撞。==

之后,我们创建一个新的文件Prefabs,将sun拖到Prefabs文件中创建一个预制体: 在这里插入图片描述 现在我们可以将sun从场景中删除。

注意:稍后将通过使用实例化功能生成阳光。

8.创建生成脚本

我们希望向日葵每隔几秒钟产生一个新的阳光。这种行为可以通过脚本实现。让我们选择向日葵,然后点击添加组件->New Script: 在这里插入图片描述 我们给它起个名字SunSpawn,选择C#作为语言 然后将其移动到项目的Scrips文件夹中。之后,我们将打开它:

using UnityEngine;
using System.Collections;

public class SunSpawn : MonoBehaviour {

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

我们将添加一个公共GameObject变量,允许我们稍后在Inspector中指定预制体:

public class SunSpawn : MonoBehaviour {
    public GameObject prefab;
    ...

这个实例化函数允许我们将预置体加载到场景中。 这个InvokeRepeting函数允许我们从某个时候开始,重复调用某个函数。 例如,如果我们想在1秒内第一次生成,然后每2秒重复一次,我们将使用 InvokeRepeting(“Spawn”,1,2) 我们希望在10秒内产生第一个阳光,然后每10秒继续生成更多的阳光,下面是我们的代码:

using UnityEngine;
using System.Collections;

public class SunSpawn : MonoBehaviour {
    public GameObject prefab;

    // Use this for initialization
    void Start() {
        // Spawn first Sun in 10 seconds, repeat every 10 seconds
        InvokeRepeating("Spawn", 10, 10);
    }
    
    void Spawn() {
        // Load prefab into the Scene
        // -> transform.position means current position
        // -> Quaternion.identity means default rotation
        Instantiate(prefab,
                    transform.position,
                    Quaternion.identity);
    }
}

现在我们可以保存脚本并再一次查看Inspector面板。 脚本有一个预制体插槽,因为我们的预制体变量是公开的。 让我们拖着我们的SunPrefab从项目区到预制件脚本的插槽: 在这里插入图片描述 如果我们按下Play然后我们每10秒就能看到一个新的太阳生成。

阳光移动 阳光生成后,它应该慢慢地消失在屏幕的顶端。我们将使用 Rigidbody 2D。 刚体通常被用于物理世界中的任何东西,而这些东西应该是在移动的。

让我们在Inspector那里单击添加组件->Physics2D->Rigidbody2D。我们将为它分配以下属性:

在这里插入图片描述 现在我们要做的就是给刚体一些速度(这是movement direction * speed)..以下图像显示某些运动方向所需的不同矢量: 在这里插入图片描述 我们将创建另一个C#脚本,命名为DefaultVelocity然后一直用它来设定速度:

using UnityEngine;
using System.Collections;

public class DefaultVelocity : MonoBehaviour {
    // The Velocity (can be set from Inspector)
    public Vector2 velocity;

    void FixedUpdate () {
        GetComponent<Rigidbody2D>().velocity = velocity;
    }
}

注意:我们在每个FixedUpdate调用此函数,以便无论发生什么情况,该脚本所附加的内容都将尝试继续移动。这对于僵尸来说是很有用的,当他们跑进一个植物时,可能无法继续移动,但是我们的脚本将确保他们在植物被摧毁后再次开始移动。

之后,我们再次选择Sun预置,然后单击添加组件->Scripts->Default Velocity 我们指定一个速度向上 (进入y方向):

在这里插入图片描述 如果我们按下Play然后,我们现在可以看到太阳阳光生成,然后向上移动: 在这里插入图片描述 收集阳光 当我们谈到阳光的时候,我们必须做的最后一件事就是让Player收集它。 我们将需要一个全局变量得分,以跟踪多少阳光被收集,每当玩家点击阳光,我们将增加这个数的大小。

Unity有Onmousedown函数,该函数确定我们是否单击了GameObject。 所以让我们创建一个新的C#脚本SunCollect然后使用Onmousedown函数:

using UnityEngine;
using System.Collections;

public class SunCollect : MonoBehaviour {
    // Global score
    public static int score = 100;

    void OnMouseDown() {
        // Increase Score
        score += 20;

        // Destroy Sun
        Destroy(gameObject);
    }
}

注:我们必须使用静态以使分数变量全局化。这意味着其他脚本可以使用SunCollection.score获取到数值。最初的分数是100,所以玩家有一些阳光来开始建造植物。

一旦我们将脚本添加到SunPrefab中,我们就可以按Play,等待太阳生成阳光,然后点击它收集它。

清理层级 现在向日葵还在场景中,但我们不希望它从一开始就在那里。 游戏应该在启动后自动生成它。 因此,让我们从它拖到Prefabs文件夹: 在这里插入图片描述

6.植物开火

植物图片 好吧,让我们再创造一种植物,这种植物能够向僵尸射击。和往常一样,我们将从绘制所有动画开始。顶部的两个切片是空闲动画,底部的两个切片是开火动画: 在这里插入图片描述 ==注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。==

导入设置 在这里插入图片描述 点击Sprite Editor,然后使用以下设置对其进行切片: 在这里插入图片描述

7.植物动画

创造动画 这一次,我们有两个动画在我们的形象。前两个切片是空闲动画,最后两个切片是开火动画。

将前两个切片拖入到场景中然后创建空闲动画: 在这里插入图片描述 命名它idle.anim并将其保存在一个新FiringplantAnimation文件夹中

射击动画的操作方式也是一样的: 在这里插入图片描述 如果我们按下Play然后我们可以在游戏中看到我们的两个动画: 在这里插入图片描述 注意:它们仍然是两个不同的游戏对象,但是我们很快就会处理好的。

清理对象 让我们在层级面板上删除firingplant_2,然后去项目区删除firingplant_2动画控制器,删完以后的样子: 在这里插入图片描述 在这里插入图片描述 注意:这么做只是为了整洁,不删除也没有影响

修改动画控制器 在Project区,双击firingplant_0动画控制器,打开动画控制器,可以看到当前状态机firingplant_0在项目区: 在这里插入图片描述 现在我们看到的就是闲散状态。如果我们想让它在某个时候播放射击动画,那么我们将再创建一个状态,我们将firing.anim动画也拖入到动画控制器: 在这里插入图片描述 我们需要某种参数来帮助Unity确定何时进入射击状态。 我们将选择参数选项卡,然后在右边单击+,添加一个Trigger类型的参数给它起个名字IsFiring:

在这里插入图片描述 注意:触发器是只触发一次的参数。稍后,我们将在脚本中设置此参数。

当IsFiring参数被触发,我们将切换到射击状态。让我们右键单击idle状态,选择Make Transition然后将白色箭头拖到firing状态: 在这里插入图片描述 之后,点击白色箭头,我们将禁用Has Exit Time属性并设置条件IsFiring: 在这里插入图片描述 注意:禁用Has Exit Time,是因为转换不应该在一段时间之后自动发生

现在,我们再从firing到idle。这一次我们将开启Has Exit Time 因为我们希望动画状态机从firing到idle在下一秒自动执行。 我们还将设置Exit Time为1: 在这里插入图片描述 这是我们最后的状态机: 在这里插入图片描述 注意:状态机现在可以根据设置的IsFiring参数自动切换idle和firing状态

8.植物的物理和Tag

添加碰撞器 在这里插入图片描述 设置Tag为Plant: 在这里插入图片描述 添加Health.cs脚本给植物,以便僵尸稍后能够对植物造成伤害: 在这里插入图片描述

9.植物的子弹

画个子弹 我们将使用16x16px纹理作为子弹:

在这里插入图片描述 ==注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。==

导入设置

在这里插入图片描述 子弹预制体 我们将把子弹纹理拖到场景中,然后将其拖回项目区,它创建了一个Prefab: 在这里插入图片描述 之后,我们可以再次从层次面板中删除它。

子弹物体 添加碰撞器和刚体: 在这里插入图片描述 注意:我们启用了Is Trigger所以子弹会穿过植物而不是与它们相撞。

发射子弹 子弹一被实例化就应该飞向右边。 我们已经有了一个脚本,通过使用刚体的velocity。 所以让我们选择添加DefaultVelocity.cs脚本,指定一个速度让它向右飞: 在这里插入图片描述 注意:这是基于组件的开发的美妙之处。我们可以写一次脚本,然后重复使用它在我们的游戏中的各种实体。

碰撞造成伤害

为了检查碰撞,我们将使用Unity的OnTriggerEnter2D函数 一旦子弹飞进另一个GameObject,就会调用该函数 在这种情况下,我们将检查它是否是僵尸(通过比较标签)然后降低它的生命值。

让我们创建一个新的C#脚本,命名为BulletDamage.cs然后双击打开,不需要Stat函数和Update函数删除,然后添加一个OnTriggerEnter2D函数:

using UnityEngine;
using System.Collections;

public class BulletDamage : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D co) {
        // Zombie?
        if (co.tag == "Zombie") {
            // Deal Damage, destroy Bullet
            co.GetComponent<Health>().doDamage(1);
            Destroy(gameObject);
        }
    }
}

注意:我们使用GetComponent找到僵尸上面的组件Health,然后我们通过调用doDamage功能。之后,我们通过以下方法将子弹从游戏中销毁

我们可以通过选择Add Component->Scripts->Bullet Damage: 在这里插入图片描述

10.植物发射子弹

既然我们有了子弹,我们就可以让我们的开火植物一看到僵尸就射它。 我们新添加一个脚本Firing.cs,然后双击打开它:

using UnityEngine;
using System.Collections;

public class Firing : MonoBehaviour {

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

首先,我们得找出植物右边是否有僵尸。 我们可以通过使用RaycastAll功能。一个RaycastAll射出一条射线穿过这个世界,然后发现它是否击中了任何东西。 所以,如果我们从屏幕的最右边向植物发射射线,如果它与标有“僵尸”标签的物体相撞,那么植物就应该在发射子弹。

这是我们的功能:

bool zombieInFront() {
    // Raycast from the right of the game to the plant
    Vector2 origin = new Vector2(9.5f, transform.position.y);
    RaycastHit2D[] hits = Physics2D.RaycastAll(origin, -Vector2.right);

    // Find out if any Zombie was hit
    foreach (RaycastHit2D hit in hits) {
        if (hit.collider != null &&
            hit.collider.gameObject.tag == "Zombie")
            return true;
    }
    return false;
}

注意:RaycastAll函数返回所有点击,而不像Raycast函数那样只返回第一个点击。我们也可以从植物向屏幕右侧射出一束光线。但是从右向植物发射更好,因为如果我们给植物增加一个碰撞器,我们就会遇到麻烦,在这种情况下,射线只会击中植物本身。我们使用-Vector2.right。因为它是向左。我们使用向量(9.5f, pos.y)作为光线投射的位置,因为这是草的右边界,还有植物的高度。

我们还需要一个公共的Prefab变量,这个变量将被设置为子弹预制体:

public class Firing : MonoBehaviour {
    // The Bullet Prefab
    public GameObject bulletPrefab;    
    ...
}

现在我们可以写我们的Shoot函数,实例化生成子弹,并设置IsFiring参数在我们的动画状态机中, 每当它看到僵尸时:

void Shoot() {
    if (zombieInFront()) {
        // Animation
        GetComponent<Animator>().SetTrigger("IsFiring");

        // Instantiate Bullet
        Instantiate(bulletPrefab, transform.position, Quaternion.identity);
    }
}

如果我们把它们放在一起的话,情况是这样的:

using UnityEngine;
using System.Collections;

public class Firing : MonoBehaviour {
    // The Bullet Prefab
    public GameObject bulletPrefab;
    
    // Shooting Interval
    public float interval = 0.5f;

    // Use this for initialization
    void Start () {
        // Try to shoot every few seconds
        InvokeRepeating("Shoot", 0, interval);
    }

    bool zombieInFront() {
        // Raycast from the right of the game to the plant
        Vector2 origin = new Vector2(9.5f, transform.position.y);
        RaycastHit2D[] hits = Physics2D.RaycastAll(origin, -Vector2.right);

        // Find out if any Zombie was hit
        foreach (RaycastHit2D hit in hits) {
            if (hit.collider != null &&
                hit.collider.gameObject.tag == "Zombie")
                return true;
        }
        return false;
    }
    
    void Shoot() {
        if (zombieInFront()) {
            // Animation
            GetComponent<Animator>().SetTrigger("IsFiring");

            // Instantiate Bullet
            Instantiate(bulletPrefab, transform.position, Quaternion.identity);
        }
    }
}

然后我们点击植物,将我们的预制体子弹拖入到预制体插槽中: 在这里插入图片描述 注意:一旦我们加入僵尸,我们就可以尝试一下射击了。

然后将植物firingplant拖入到prefab文件夹,做成一个预制体。

11.生成菜单

在这里插入图片描述 现在我们创建菜单,并实现需要的功能。

Build Info组件 我们的建造菜单将需要每个植物的价格和一个小预览图像。 现在这种数据必须存储在某个地方。 要做到这一点,最好的方法是将其存储在每个植物的预制体中。

让我们选择我们的两个植物预制体,然后点击添加组件,新脚本,命名为BuildInfo,然后双击打开:

using UnityEngine;
using System.Collections;

public class BuildInfo : MonoBehaviour {
    public Texture previewImage;
    public int price;
}

我们将使用以下预览图像: 在这里插入图片描述在这里插入图片描述 ==注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。==

导入设置选中两幅图片: 在这里插入图片描述 然后我们分别选择我们的Build Info组件,分配以下属性: 在这里插入图片描述 向日葵 在这里插入图片描述 寒冰射手

12.绘制菜单

为了显示菜单名,我们将使用OnGUI函数:

让我们先梳理一下步骤:

注意:GUILayout.Button只能显示一个图像或者一个文本,不能两个都显示,我们希望显示预览图和价格,我们将使用GUIContent

我们点击Main Camera,选择添加组件,新建一个脚本,命名为BuildMenu.cs

这是我们的BuildMenu脚本:

using UnityEngine;
using System.Collections;

public class BuildMenu : MonoBehaviour {
    // Sun Image
    public Texture sunImage;

    // Plant Prefabs
    public BuildInfo[] plants;

    void OnGUI() {
        // Begin Gui
        GUILayout.BeginArea(new Rect(Screen.width/2 - 100, -7, 200, 100));
        GUILayout.BeginHorizontal("box");

        // Draw the Sun
        GUILayout.Box(new GUIContent(SunCollect.score.ToString(), sunImage));

        // Draw each Plant's BuildInfo
        foreach (BuildInfo bi in plants) {
            GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage));
        }

        // End Gui
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }
}

注意:植物预制板是一个数组(因此[ ])。这意味着这不仅仅是一个而是一组。

保存脚本后,我们将把我们的太阳图像和植物预制板从我们的项目区进入脚本的插槽: 在这里插入图片描述 如果我们按下Play然后我们就可以看到我们的菜单了: 在这里插入图片描述 启用和禁用按钮 现在有更多的东西要在这里实现。首先,每个按钮应该启用,只有当玩家收集到足够的太阳支付它。这可以很容易地通过使用GUI.enabled:

// Draw each Plant's BuildInfo
foreach (BuildInfo bi in plants) {
    GUI.enabled = SunCollect.score >= bi.price;
    GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage));
}

注意:这会将一个bool值(真或假)分配给GUI.enabled。bool值是通过判断SunCollect.score是否植物的价格要高来判断是否为真。

如果我们按下Play,我们就可以看到我们的按钮被禁用,除非我们有足够的阳光:

13.生成植物

让我们来处理最重要的部分:生成植物。 玩家应首先单击“生成”菜单中的“植物”按钮,然后单击“草地”。之后,植物应该建在那块草地上。

因此,首先,我们需要跟踪玩家想要建造的植物:

// Currently building...
public static BuildInfo cur;

注:public static因为我们希望以后能够从另一个脚本访问它。

一旦玩家点击一个按钮,我们就会设置cur变量为玩家想要创建的植物:

// Draw each Plant's BuildInfo
foreach (BuildInfo bi in plants) {
    GUI.enabled = SunCollect.score >= bi.price;
    if (GUILayout.Button(new GUIContent(bi.price.ToString(), bi.previewImage)))
        cur = bi;
}

14.草地的脚本

现在,我们将不得不等待玩家点击草地。 如果他点击了一个,那么我们想要构建BuildInfo.cur 植物。 让我们选择场景中的所有草块,然后单击Add Component->New Script。 我们给它起个名字Grass,再一次将它移到我们的Scripts 文件夹中,然后打开它。 我们将使用OnMouseUpAsButton函数以确定是否单击了草地:

using UnityEngine;
using System.Collections;

public class Grass : MonoBehaviour {

    void OnMouseUpAsButton() {
        // Build Stuff
    }
}

现在我们将找出是否有什么东西要建,如果有,我们将建造它,并让玩家支付阳光:

using UnityEngine;
using System.Collections;

public class Grass : MonoBehaviour {

    void OnMouseUpAsButton() {
        // Is there something to build?
        if (BuildMenu.cur != null) {
            // Build it
            Instantiate(BuildMenu.cur.gameObject, transform.position, Quaternion.identity);
            SunCollect.score -= BuildMenu.cur.price;
            BuildMenu.cur = null;
        }
    }
}

注意:我们可以从任何位置访问BuildMenu的cur变量。这起作用是因为public static。我们用实例化使用草地的位置(transform.position),默认的旋转(Quaternion.identity)。之后我们清除cur变量,因为我们已经完成了创建。

如果我们按下Play然后我们现在可以创建一些植物: 在这里插入图片描述

15.僵尸

画僵尸 现在我们一直在等待的是:僵尸: 和往常一样,我们将从绘制一个包含所有动画帧的图像开始。第一行包含行走动画,第二行包含攻击动画: 在这里插入图片描述 ==注意:右击图像,选择另存为。并将其保存在项目的Assets/Sprites文件夹。==

导入设置: 在这里插入图片描述 图像将被切片为32 x 32网格: 在这里插入图片描述

16.僵尸动画

让我们创建moving动画,通过选择前4个切片并将它们拖到场景中来创建动画: 在这里插入图片描述 我们会把它命名为moving.anim存放在一个新的ZombieAnimation文件夹

接下来的两片将是attacking动画: 在这里插入图片描述 我们会把它命名为attacking.anim存放在一个新的ZombieAnimation文件夹

在这里插入图片描述 清理 与往常一样,我们将在场景中删除zombie_4,在项目文件中删除zombie_4的动画状态机

17.僵尸动画状态机

我们前面已经创建了很多次状态机了,已经很熟悉了: 在这里插入图片描述 参数是:

这些Transitions 是:

这意味着只要我们说IsAttacking,动画状态机将播放进攻动画一秒钟。然后它会自动回到移动状态。

18.僵尸物理

我们的僵尸应该是物理世界的一部分,这意味着它需要一个BoxCollider2D。 它也应该四处移动,这意味着它需要一个Rigidbody 2D:

在这里插入图片描述

19.僵尸移动

僵尸应该向左走。当然,我们可以再次使用我们的DefaultVelocity脚本,选择Add Component->Scripts->Default Velocity然后分配一个速度,让它慢慢地向左走: 在这里插入图片描述

20.僵尸攻击脚本

一旦僵尸与植物相撞,它就应该每秒钟攻击一次。

添加一个新脚本ZombieAttacking.cs,然后双击打开,不需要Start函数和Update函数,删除,然后添加OnCollisionStay2D函数:

using UnityEngine;
using System.Collections;

public class ZombieAttacking : MonoBehaviour {

    void OnCollisionStay2D(Collision2D coll) {
        // Collided with a Plant?
        if (coll.gameObject.tag == "Plant") {
            // Do Stuff...
        }
    }
}

僵尸站在植物前应播放攻击动画:

void OnCollisionStay2D(Collision2D coll) {
    // Collided with a Plant?
    if (coll.gameObject.tag == "Plant") {
        // Play Attack Animation
        GetComponent<Animator>().SetTrigger("IsAttacking");
    }
}

它也应该对植物造成伤害,但每秒钟只能造成一次:

using UnityEngine;
using System.Collections;

public class ZombieAttacking : MonoBehaviour {
    // Last Attack Time
    float last = 0;

    void OnCollisionStay2D(Collision2D coll) {
        // Collided with a Plant?
        if (coll.gameObject.tag == "Plant") {
            // Play Attack Animation
            GetComponent<Animator>().SetTrigger("IsAttacking");
            // Deal damage once a second
            if (Time.time - last >= 1) {
                coll.gameObject.GetComponent<Health>().doDamage(1);
                last = Time.time;
            }
        }
    }
}

注意:我们只是用Time.time找出从那时起已经过去了多少时间最后的时间,然后进入植物 Health脚本,调用doDamage函数并重置最后的时间到了。

还记得我们是如何检查僵尸射击和子弹记录上的标签? 让我们添加僵尸Tag到我们的僵尸,以确保它可以被子弹射中: 在这里插入图片描述 我们还将添加一个Health脚本: 在这里插入图片描述 如果我们按下Play建造一个向日葵 然后我们可以看到僵尸将如何尝试攻击到它。 如果我们建了一个寒冰射手,那么我们就可以看到它是如何在几枪之后杀死僵尸的!

我们还将通过复制几个僵尸,他们将僵尸定位在水平的右边,这样他们在步行几秒钟后才能到达: 在这里插入图片描述 如果我们按下Play然后我们就可以看到游戏的行动: 在这里插入图片描述

21.摘要

我们刚刚创造了一个干净而简单的植物大战僵尸游戏。 该教程相当长,但仍然有许多功能和改进可以添加到游戏。 和往常一样,现在是你让游戏变得有趣的时候了!

我们将添加一个公共GameObject变量,允许我们稍后在检查器中指定Prefab:

public class SunSpawn : MonoBehaviour {
    public GameObject prefab;
    ...

这个实例化函数允许我们将预置文件加载到场景中。这个InvokeRepeting函数允许我们重复调用某个函数,从将来的某个时候开始。例如,如果我们想在1秒内第一次产卵,然后每2秒重复一次,我们将使用InvokeRepeting(“产卵”,1,2)..我们希望在10秒内产生第一个太阳,然后每10秒继续生成更多的太阳,下面是我们的代码:

using UnityEngine;
using System.Collections;

public class SunSpawn : MonoBehaviour {
    public GameObject prefab;

    // Use this for initialization
    void Start() {
        // Spawn first Sun in 10 seconds, repeat every 10 seconds
        InvokeRepeating("Spawn", 10, 10);
    }
    
    void Spawn() {
        // Load prefab into the Scene
        // -> transform.position means current position
        // -> Quaternion.identity means default rotation
        Instantiate(prefab,
                    transform.position,
                    Quaternion.identity);
    }
}

现在我们可以保存脚本并再一次查看Inspector面板。脚本有一个预制件因为我们的预制件变量是公开的。让我们拖着我们的SunPrefab从项目区到预制件脚本的插槽: 在这里插入图片描述 如果我们按下Play然后我们每10秒就能看到一个新的太阳生成。

太阳运动 太阳射出后,它应该慢慢地消失在屏幕的顶端。我们将使用刚体2D为了这个。刚体通常被用于物理世界中的任何东西,而这些东西应该是在周围移动的。

让我们在项目区然后在Inspector那里单击添加组件->Physics2D->Rigidbody2D。我们将为它分配以下属性:

在这里插入图片描述 现在我们要做的就是给刚体一些速度(这是movement direction * speed)..以下图像显示某些运动方向所需的不同矢量: 在这里插入图片描述 我们将创建另一个C#脚本,命名为DefaultVelocity然后一直用它来设定速度:

using UnityEngine;
using System.Collections;

public class DefaultVelocity : MonoBehaviour {
    // The Velocity (can be set from Inspector)
    public Vector2 velocity;

    void FixedUpdate () {
        GetComponent<Rigidbody2D>().velocity = velocity;
    }
}

注意:我们在每个FixedUpdate调用,调用此函数,以便无论发生什么情况,该脚本所附加的内容都将尝试继续移动。这对于僵尸来说是很有用的,当他们跑进一个植物时,可能无法继续移动,但是我们的脚本将确保他们在植物被摧毁后再次开始移动。

之后,我们再次选择Sun预置,然后单击添加组件->Scripts->Default Velocity..我们也会指定一个速度向上 (进入y方向):

在这里插入图片描述 如果我们按下Play然后,我们现在可以看到太阳阳光生成,然后向上移动: 在这里插入图片描述 收集阳光 当我们谈到太阳的时候,我们必须做的最后一件事就是让Player收集它。我们将需要一个全球得分,以跟踪多少太阳被收集,我们将需要增加,每当球员点击太阳。

Unity伴随着Onmousedown函数,该函数允许我们确定是否单击了GameObject。所以让我们创建一个新的C#脚本SunCollect然后使用Onmousedown职能:

using UnityEngine;
using System.Collections;

public class SunCollect : MonoBehaviour {
    // Global score
    public static int score = 100;

    void OnMouseDown() {
        // Increase Score
        score += 20;

        // Destroy Sun
        Destroy(gameObject);
    }
}

注:我们必须使用静态以使分数变量全局化。这意味着其他脚本可以使用SunCollection.得分随时都行。最初的分数是100,所以玩家有一些太阳开始建造工厂。

一旦我们将脚本添加到SunPrefab中,我们就可以按Play,等待太阳生成阳光,然后点击它收集它。

清理层级 现在向日葵还在等级体系中,但我们不希望它从一开始就在那里。玩家应该在稍后手动构建它。因此,让我们从它创建一个预置文件,方法是将它拖到项目区: 在这里插入图片描述

6.植物开火

植物图片 好吧,让我们再创造一种植物,这种植物能够向僵尸射击。

…未完待续