Unity3D开发教程:愤怒的小鸟

一、前言

“愤怒的小鸟”在2009年12月发布,由于它的高度上瘾的游戏,它很快成为有史以来最成功的移动游戏。

在本教程中,我们将在“Unity”中实现“愤怒的小鸟”翻版。游戏中最复杂的部分是物理系统,但是多亏了Unity,我们就不用担心太多了。

像往常一样,一切都会尽可能简单地解释,这样每个人都能理解它。

以下是项目的预览:

二、源码

UI资源:
https://wwr.lanzoui.com/iENnJop2n9i
密码:bnj2

源代码:
https://wwr.lanzoui.com/i9xPAop2naj
密码:7rox

三、正文 项目版本

Unity5.0.0f4

1.设置相机

点击Main Cameras,在Hierarchy面板设置背景色以友好的蓝色色调(红色=187, 绿色=238, 蓝色=255)并调整大小而位置如下图所示:

2.地面设置

地面贴图设置
为了防止版权问题,我们不能在本教程中使用原“愤怒的小鸟”图形。相反,我们将画我们自己的Sprite,使他们看起来像原来的游戏。

让我们从用我们选择的绘图工具开始:

将其保存到我们的项目中后,我们可以在项目区可以看到:

然后在Inspector修改导入设置:

注:Pixels Per Unit像素转到单位价值16这意味着16x16像素将适合在游戏世界的一个单位。我们将使用这个值作为我们所有的纹理。我们选择16,因为鸟的大小将有一个16x16像素后,我们希望在游戏世界它有一个单位的大小。

好了,现在我们可以将图片从项目区拖入到场景中:

让我们看看Inspector把地面定位在(0, -2),所以作为不为y=0的都不是地面的一部分:

地面物体设置
现在地面只是一幅图像,仅此而已。它不是物理世界的一部分,事物不会与它相撞,也不会站在它上面。我们需要添加一个Collider让它成为物理世界的一部分,这意味着事物将能够站在它的顶端,而不是掉进它的正中。

添加BoxCollider2D组件:

3.边界设置

创建空对象,命名为borders

位置归零:

现在,我们将在我们的水平的左边、右边和顶部添加某种不可见的区域。每当有东西进入那个区域,它就应该被摧毁。此类行为可以通过Trigger,这几乎只是一个Trigger它接收到碰撞信息,但不会与任何东西发生冲突。

添加碰撞器:

勾选

Is Trigger

之后,我们可以为级别的右侧和顶部再添加两个triggers :

如果我们看看场景然后,我们可以看到触发器是如何与我们的背景很好地对齐的:

现在我们仍然必须确保任何进入边界的东西都会立即被销毁。此类行为可以通过脚本Borders:

创建脚本Borders.cs:

将其添加到边界对象物体上面:

让我们也将脚本移动到一个新的Scripts文件夹,只是为了保持清洁:

编辑Borders.cs脚本:

using UnityEngine;
using System.Collections;

public class Borders : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
我们不需要启动或者更新函数,所以让我们移除它们。相反,我们将使用OnTriggerEnter2D函数,每当有东西进入其中一个边界触发器时,统一将自动调用该函数:
using UnityEngine;
using System.Collections;

public class Borders : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D co) {

    }
}
在这个函数中,无论什么东西进入Triggers,我们都将Destroy这个物体:
using UnityEngine;
using System.Collections;

public class Borders : MonoBehaviour {

    void OnTriggerEnter2D(Collider2D co) {
        Destroy(co.gameObject);
    }
}
保存脚本后,我们的边界就完成了。我们稍后会看到,如果我们试图将一只鸟射出水平之外,它就会消失。

4.云彩设置

我们将花几分钟额外添加云到背景,以使水平看起来更好。像往常一样,我们首先画一个:

让我们在项目区,然后在Inspector修改云的导入设置:

现在我们要做的就是把它从项目区进入场景几次,将每一片云放置在我们想要的位置:

注意:只要使用一些重复的模式和一些非常颜色,我们可以使水平看起来相当好,无需付出很大的努力。

5.弹弓设计

弹弓图片

一个飞弹将产生新的鸟类,并允许用户发射到水平。和往常一样,我们将从画Sprites开始:
这里是导入设置:

稍后,我们将创建一个脚本,在弹弓的位置生成一只新的鸟,或者确切地说是在弹弓的Pivot位置生成一只鸟。
我们想要在弹弓顶部而不是中间处出现,这就是为什么我们要在“导入设置”中设置Pivot在顶部。

下面的图像显示了中心和顶:

*注意:如果我们将数据透视设置为中心然后变换位置是弹弓中心的点。如果我们把Pivot 设为顶,然后变换位置是弹弓顶端的点。

好了,现在我们可以将弹弓拖到场景中去了(-22, 3):

生成鸟脚本
如前所述,我们的弹弓应该是生成鸟。确切地说,它应该在一开始就生成一个,然后等待用户启动它,然后在所有的物理计算完成之后再生成另一个。(当什么都不动的时候).

我们可以通过脚本来实现这样的行为。
添加脚本Spawn.cs:

我们可以双击脚本来打开它:

using UnityEngine;
using System.Collections;

public class Spawn : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
这个启动函数在开始游戏时由Unity自动调用。这个更新函数被一次又一次地自动调用,大约每秒60次。我们不需要它们中的任何一个,这样我们就可以从脚本中删除它们。

还有另一种类型的更新函数,它被称为FixedUpdate…它也被一次又一次的调用,但是是在单位物理完全相同的时间间隔内计算的,所以在做物理工作的时候使用FixedUpdate是一个好主意(我们很快就会这么做).

下面是修改后的脚本FixedUpdate脚本:

using UnityEngine;
using System.Collections;

public class Spawn : MonoBehaviour {

    void FixedUpdate() {

    }
}
好的,让我们添加一个变量,允许我们稍后指定BirdPrefab(我们想生的鸟):
using UnityEngine;
using System.Collections;

public class Spawn : MonoBehaviour {
    // Bird Prefab that will be spawned
    public GameObject birdPrefab;

    void FixedUpdate() {

    }
}
以下是我们如何生成它的方法:
void spawnNext() {
    // Spawn a Bird at current position with default rotation
    Instantiate(birdPrefab,transform.position,Quaternion.identity);
}
生成的触发区域

现在我们不能只生一只又一只鸟。相反,我们将不得不生成一个,然后等待它被发射。有几种方法可以实现这一点,但最简单的方法是使用Triggers.

Trigger是一个简单的Collider ,接收碰撞信息,但实际上并不是物理世界的一部分。所以如果我们添加一个Trigger然后,每当有东西进入Trigger、留在Trigger中或离开Trigger时,我们都会收到通知。然而,由于它只是一个Trigger,事情不会像普通Collider 那样与它相撞(这很快就更有意义了).

我们可以将Trigger添加到弹弓中,方法是在Hierarchy面板中,然后点击添加组件Circle Collider 2D,给它一个合适的半径和中心然后启用触发:

Is Trigger

我们还可以在场景中查看:

在添加触发器之后,每当有东西进入时,我们都会收到通知。(OnTriggerEnter2D),停留(OnTriggerStay2D)或离开(OnTriggerExit2D)上面那个绿色的圆圈。

现在,我们可以通过创建一个使用中变量,然后将其设置为bool值,当生下一只鸟的时候为false,当它离开触发器时为true:

using UnityEngine;
using System.Collections;

public class Spawn : MonoBehaviour {
    // Bird Prefab that will be spawned
    public GameObject birdPrefab;

    // Is there a Bird in the Trigger Area?
    bool occupied = false;

    void FixedUpdate() {

    }

    void spawnNext() {
        // Spawn a Bird at current position with default rotation
        Instantiate(birdPrefab, transform.position, Quaternion.identity);
        occupied = true;
    }

    void OnTriggerExit2D(Collider2D co) {
        // Bird left the Spawn
        occupied = false;
    }
}
之后我们可以修改我们的FixedUpdate函数,因此每当触发区域不再被占用时,它总是生成一只鸟:
void FixedUpdate() {
    // Bird not in Trigger Area anymore?
    if (!occupied)
        spawnNext();
}
注:!occupied意思是还没有被占用…我们也可以用if(occupied==false).

我们的生成脚本现在可以正常工作了,但是让我们再添加一个特性。在射杀一只鸟之后,会有很多东西相互碰撞,坠落,滚来滚去,甚至爆炸。在最初的“愤怒的小鸟”游戏中,只有在水平上的所有东西停止移动之后,才会产生一只新的鸟。

我们可以很容易地创建一个sceneMoving函数,该函数查找场景中是否有任何对象仍在移动,而不仅仅是一点点:

bool sceneMoving() {
    // Find all Rigidbodies, see if any is still moving a lot
    Rigidbody2D[] bodies = FindObjectsOfType(typeof(Rigidbody2D)) as Rigidbody2D[];
    foreach (Rigidbody2D rb in bodies)
        if (rb.velocity.sqrMagnitude > 5)
            return true;
    return false;
}
注意:我们使用了FindObjectsOfType找到所有带有刚体的物体,之后我们会检查每个物体的velocity,如果这个刚体的sqrMagnitude大于5,说明这个刚体还在移动就返回True,没有就返回false

使用这个整洁的小脚本,我们可以轻松地修改FixedUpdate功能,因此只有在没有任何移动的情况下才会产生新的鸟:

void FixedUpdate() {
    // Bird not in Trigger Area anymore? And nothing is moving?
    if (!occupied && !sceneMoving())
        spawnNext();
}
现在我们已经完成了生成鸟的脚本,我们可以在Inspector面板看到弹弓上面挂载的脚本:

注意:我们还不能在没有鸟的情况下测试生成鸟脚本,但是它确实工作得很好,我们将在创建鸟之后看到这一点。

6.鸟的设置

鸟的图片
让我们开始更有趣的事情:鸟。我们首先画一个16 x 16一只大圆身躯和一些小小的翅膀和眼睛的鸟的像素图像:

我们将使用以下方法导入设置为此:

让我们从项目区进入场景若要从其中创建游戏对象,请执行以下操作:

鸟的物理

让我们为鸟添加碰撞器Circe Collider 2D:

现在有一个Physics Material 2D对撞机的缝隙,让我们可以给鸟一些特殊的物理特性。在“Unity愤怒的小鸟”教程中,物理材料将是非常有用的,因为现在,如果这只鸟掉在地上,它看起来会是这样的:

看起来有点不自然。相反,我们想让这只鸟从下面的东西中跳出来:

要在第二张图片中创建弹跳效果,我们所要做的就是在项目区并选择Create>Physics2D Material,说出来鸟类材料把它变成一个新的物理材料文件夹:

一旦被选中,我们就可以修改Inspector:

注:Bounciness值越大,鸟就越会反弹。

最后,我们可以再次选择鸟,然后拖动鸟类材料从项目区进入Collider Material插槽:

这只鸟也应该四处走动。刚体负责物体的重力、速度和其他使物体运动的力。根据经验法则,在物理世界里,所有应该移动的东西都需要一个刚体.:

注意:我们设置了Gravity Scale到4因为它能让鸟飞得更快。

如果我们按下Play现在我们可以看到鸟从地上掉下来并弹跳起来:

我们的鸟类物理已经完成了,但是有一个小的调整是我们必须在这里进行的。现在,如果我们在弹弓中生成的话,由于它的刚体引力,它会立即坠落到地面。我们只希望用户一开火,鸟就会受到重力的影响,所以让我们现在启用Is Kinematic,然后在脚本中禁用它:

现在,刚体是运动学的,这意味着它不受重力或速度的影响,因此不会立即坠落。

注意:为了更清楚地说明这一点,任何像英雄、汽车或鸟之类的东西都应该有一个刚体,它是运动学的。我们只使能只要鸟还在弹弓里就能运动。

鸟预制体
如前所述,这只鸟从一开始就不应该出现在场景中。相反,弹弓应该在需要的时候生成出一只新的鸟。为了使弹弓能够生成鸟,我们必须创建一个预制件 (换句话说,我们必须在我们的项目区有鸟的资源).

要创建预制件,我们所要做的就是在hierarchy面板,将物体拖入到项目区的Prefabs文件夹中:

现在,我们可以在任何时候将鸟加载到场景中,这意味着我们现在也可以从Hierarchy中删除这个对象:

生成鸟
让我们将预制体bird拖到到我们的Spawn.cs的脚本中的BirdPrefab插槽中:

如果我们按下Play现在我们可以看到弹弓是如何生出一只鸟的:

拉and释放脚本
用户应该能够把鸟在弹弓周围,然后释放它,以便把它射向所希望的方向。

我们将创造一个新的C#脚本给它起个名字PullAndRelease :

using UnityEngine;
using System.Collections;

public class PullAndRelease : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
用户将能够拖动鸟绕一个圆圈。每个圆都需要一个中心,在我们的例子中,它是鸟的生成位置,所以让我们确保将它保存在一个变量中:
using UnityEngine;
using System.Collections;

public class PullAndRelease : MonoBehaviour {
    // The default Position
    Vector2 startPos;

    // Use this for initialization
    void Start () {
        startPos = transform.position;
    }
}
*注意:我们还删除了更新函数因为我们不需要它。

好的,为了让用户把鸟拖曳成一个圆圈,我们必须找出这只鸟是否被点击了。(确切地说:拖着)…我们还需要知道用户是否释放了鼠标,在这种情况下,我们必须在我们目标方向发射鸟。

当然,如果没有这方面的功能,那就不是Unity了。Unity自动调用Onmouseup和OnmouseDrag函数,当我们用鼠标拖动游戏对象或随后释放鼠标时:

*注:鼠标拖动,意思是用户在GameObject上按住鼠标按钮,然后移动鼠标。

移动鸟真的很容易。我们所要做的就是将当前的鼠标位置转换到游戏世界的某个点,然后将鸟移到那里。当然,只有在一定半径内:

void OnMouseDrag() {    // Convert mouse position to world position    Vector2 p= Camera.main.ScreenToWorldPoint(Input.mousePosition);    // Keep it in a certain radius    float radius = 1.8f;    Vector2 dir = p - startPos;    if (dir.sqrMagnitude > radius)        dir = dir.normalized * radius;    // Set the Position    transform.position = startPos + dir;}

*注意:我们可以使用ScreenToWorldPoint获取到手指点击的位置,但是这个位置是不固定的,在找到手指点击的位置p之后,我们只需要计算从startPos到p的距离,如果这个距离太长dir.sqrMagnitude > radius,就让这个位置等于一个最大值dir = dir.normalized * radius

如果我们按下Play然后我们可以把鸟绕个圈:

把鸟射向一个方向也同样容易。我们可以用我们的Onmouseup函数来知道鼠标何时释放。然后,我们将计算出从鸟到startPos然后使用rigidbody的AddForce在那里启动它的功能:

// The Force added upon releasepublic float force = 1300;void OnMouseUp() {    // Disable isKinematic    GetComponent<Rigidbody2D>().isKinematic = false;    // Add the Force    Vector2 dir = startPos - (Vector2)transform.position;    GetComponent<Rigidbody2D>().AddForce(dir * force);    // Remove the Script (not the gameObject)    Destroy(this);}

*注意:如前所述,我们也将禁用运动学等使刚体再次受到重力和速度的影响。我们只需将当前位置减去startPos…最后,我们删除这个对象,这样它就不能再被发射了。

如果我们按下Play然后我们就可以拉着这只鸟开火了:

Feather Particle Effect羽毛的粒子效果
让我们通过增加鸟的碰撞效果来使游戏更加流畅。一旦它第一次落地,它就应该像这样在自己周围随意地长出羽毛:

当我们需要随机粒子产生、旋转和向某个方向移动时,就会使用粒子系统。粒子系统的一个简单的例子是烟雾,它产生灰色纹理,然后以锥状向上移动。

我们将修改我们的粒子系统,使其不是使粒子向上飞,而是使它们飞向四面八方。我们还将修改一些更具体的东西,如大小,速度和旋转。我们的羽毛没有正确或错误的粒子系统,所以你可以随意使用它,直到它看起来像你想让它看起来那样。以下是我们得出的结论:

这是我们用来做这件事的图像:

*注意:右击图像,选择另存为.。并将其保存在项目的Assets/Sprites文件夹。

导入设置:

之后,我们可以从项目区进入我们粒子系统所以它使用图像对所有的粒子。

现在我们可以将场景中的羽毛对象拖入到我们项目区的Prefabs文件夹,做成一个预制体:

然后我们可以在Hierarchy中删除羽毛游戏对象

最后一件事是给我们的鸟添加一个脚本,这样羽毛粒子系统就会在发生碰撞时产生。让我们在项目区然后创建新脚本…我们给它起个名字CollisionSpawnOnce。我们也会把它移到我们的Sprits文件夹,然后双击它以打开它:

using UnityEngine;
using System.Collections;

public class CollisionSpawnOnce : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
我们不需要启动或更新函数。相反,我们将使用OnCollisionEnter2D函数和effect公共游戏对象应生成的预制件的变量:
using UnityEngine;
using System.Collections;

public class CollisionSpawnOnce : MonoBehaviour {
    // Whatever should be spawned (Particles etc.)
    public GameObject effect;

    void OnCollisionEnter2D(Collision2D coll) {
        // Spawn Effect, then remove Script
        Instantiate(effect,transform.position,Quaternion.identity);
        Destroy(this);
    }
}
*注意:为了确保只产生一次效果,我们将从Destroy(this)(这只会关闭脚本,而不是整只鸟)。

保存脚本后,我们可以看到Effect插槽…现在我们可以拖着羽毛粒子系统预制件项目区进入Effect插槽:

如果我们按下Play然后把这只鸟发射到地上,然后我们就可以看到它周围的羽毛在生成:

路径
我们还会给我们的鸟添加另一个效果,让它看起来更流畅:一条白点的轨迹,显示鸟的轨迹:

首先,我们需要一些大小不同的跟踪图像:

我们会用同样的导入设置对于每一幅图像:

我们希望能够在我们想要的任何时候产生轨迹部分,这意味着我们将需要三个预制件。因此,让我们选择三个图像并将其拖到场景中,然后拖回到Prefabs文件夹。直到我们有三个预制体:

现在我们只需要一个脚本来生成一个又一个的TRAIL元素,大约每秒钟一次。让我们创建一个新的C#脚本给它起个名字Trail :

using UnityEngine;
using System.Collections;

public class Trail : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
我们可以移除更新因为我们不需要它。让我们添加一个public GameObject[]保存所有跟踪元素的变量。我们将使用Array,这意味着它不仅仅是一个GameObject:
using UnityEngine;
using System.Collections;

public class Trail : MonoBehaviour {

    // Trail Prefabs
    public GameObject[] trails;

    // Use this for initialization
    void Start () {

    }
}
我们还需要一个函数来生成下一条线索。例如,它应该产生第一个TRAIL元素,然后下一次应该生成第二个,然后是第三个,然后是第一个。这可以通过使用实例化有一个额外的计数器变量:
using UnityEngine;
using System.Collections;

public class Trail : MonoBehaviour {

    // Trail Prefabs
    public GameObject[] trails;
    int next = 0;

    // Use this for initialization
    void Start () {

    }

    void spawnTrail() {
        Instantiate(trails[next], transform.position, Quaternion.identity);
        next = (next+1) % trails.Length;
    }
}
我们将其设置为0,这意味着trails spawnTrail中的第一个元素被调用。然后使用next+1来增加next。为了保持它在trails数组的范围内,我们还将使用% trails.Length,它使用模(%)运算。对于那些不了解模的人,这里有一个更明显的版本:
void spawnTrail() {
    Instantiate(trails[next], transform.position, Quaternion.identity);
    next = next + 1;
    if (next == trails.Length) next = 0;
}
现在我们有了一个生成轨迹函数,我们可以使用它生成一个新的trail 元素通过使用InvokeRepeting函数100 ms生成一个:
using UnityEngine;
using System.Collections;

public class Trail : MonoBehaviour {

    // Trail Prefabs
    public GameObject[] trails;
    int next = 0;

    // Use this for initialization
    void Start () {
        // Spawn a new Trail every 100 ms
        InvokeRepeating("spawnTrail", 0.1f, 0.1f);
    }

    void spawnTrail() {
        Instantiate(trails[next], transform.position, Quaternion.identity);
        next = (next+1) % trails.Length;
    }
}
现在,小径元素会一直生成,甚至当鸟不飞的时候也是如此。让我们添加一个小小的修改,只在鸟飞得足够快的情况下才会产生轨迹:
void spawnTrail() {
    // Spawn Trail if moving fast enough
    if (GetComponent<Rigidbody2D>().velocity.sqrMagnitude > 25) {
        Instantiate(trails[next], transform.position, Quaternion.identity);
        next = (next+1) % trails.Length;
    }
}
好的,让我们保存脚本。在这里,我们将从我们的三条小径预制体中拖到插槽中:

如果我们按下Play然后我们就可以看到这只鸟射击后的踪迹:

7.木片

让我们添加一些结构,如石头,冰和木材,我们的Unity2D愤怒的小鸟游戏更加丰富。

我们先画一块木片:

注意:右击图像,选择另存为。并将其保存在项目的Assetes/Sprits文件夹。

这里是导入设置:

现在我们可以把它拖到场景把它放在地上的某个地方:

木片应该是物理世界的一部分,所以我们将一如既往地在添加Box Collider 2D组件:

木片也应该能够四处移动。现在它不会自己移动,但是如果鸟飞进它,它就会移动。它也应该受到重力的影响,所以我们需要的是刚体…我们可以通过选择添加组件->物理二维->Rigidbody 2D…我们也会增加Mass到4所以它更重了一点:

现在我们有一块木头,它是物理世界的一部分!

对于这个略有不同的木片,我们将重复相同的工作流程:

这是我们的游戏如何看待添加第二块木材和旋转第一个90°:

8.石头

为了在我们的游戏中有几种不同的结构,我们还将添加两种不同类型的石头:

操作流程和以前一样,这次我们将Mass设置为10:

下面是我们的游戏中有一些石头的样子:

注:我们再次实现了一些体面的外观与基本的形状,只有少数颜色和抖动。

9.冰

冰的图片
我们将为我们的游戏增加一个结构:冰。不过,这一次会更有趣一些。

像往常一样,我们首先画一块冰:

这个导入设置与以往相同:

冰物理
添加 Boxcollider2D组件 刚体组件:

冰应该很滑,所以让我们右击项目区并选择创造->物理二维材料给它起个名字冰IceMaterial:

设置参数:

之后,我们可以在Hierarchy面板中选择冰然后将IceMaterial从项目区拖入到BoxCollider2D>Material插槽:

撞击时摧毁冰
如果被足够的力量撞击,我们也希望冰层被摧毁,因为这就是冰的自然作用。我们添加脚本BreakOnImpact.cs脚本:

using UnityEngine;
using System.Collections;

public class BreakOnImpact : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
我们不需要启动或者更新函数,所以让我们移除这两个函数。我们需要一种方法来估计碰撞的力量。我们将保持简单,并将速度与质量相乘:
float collisionForce(Collision2D coll) {
    // Estimate a collision's force (speed * mass)
    float speed = coll.relativeVelocity.sqrMagnitude;
    if (coll.collider.GetComponent<Rigidbody2D>())
        return speed * coll.collider.GetComponent<Rigidbody2D>().mass;
    return speed;
}
*注:Collision2D继承OnCollisionEnter2D功能,我们将获取一个估计碰撞的力量,它包含方向与速度相乘。如果我们只关心速度,那么我们可以使用coll.relativeVelocity.sqrMagnitude…现在,如果造成碰撞的对象有刚体然后我们把速度乘以刚体的质量…否则我们只返回速度。

好了,现在我们可以用OnCollisionEnter2D函数以获得有关碰撞的通知。然后,我们将比较碰撞的力量和一个可配置的变量。如果它比力大,那么冰就会破裂:

using UnityEngine;
using System.Collections;

public class BreakOnImpact : MonoBehaviour {
    public float forceNeeded = 1000;

    float collisionForce(Collision2D coll) {
        // Estimate a collision's force (speed * mass)
        float speed = coll.relativeVelocity.sqrMagnitude;
        if (coll.collider.GetComponent<Rigidbody2D>())
            return speed * coll.collider.GetComponent<Rigidbody2D>().mass;
        return speed;
    }

    void OnCollisionEnter2D(Collision2D coll) {
        if (collisionForce(coll) >= forceNeeded)
            Destroy(gameObject);
    }
}
如果我们保存脚本,请按Play把鸟碰撞冰层,然后它就会破裂:

现在,我们可以花几分钟来复制这些结构,并将它们放在一起,这样我们就可以在它们之间添加猪了:

10.绿猪

鸟儿想要消灭所有的猪,所以让我们把一些猪加入到我们的游戏中,这样鸟儿就不会觉得无聊了。

我们首先画一个:

导入设置:

添加碰撞器和刚体:

如果有足够大的力量袭击猪,猪就会死。幸运的是,我们已经有了一个脚本。给我们的pig物体添加脚本BreakOnImpact.cs,并且设置Force Needed的值:

*注意:能够重用这样的脚本是基于组件的开发。

现在我们可以复制这头猪并把它移到一些结构之间:

如果我们按下Play然后我们就可以试着消灭猪了:

11.橡胶

我们将为我们的游戏添加最后一个功能:弹弓橡胶,所以拖拽和释放鸟看起来要好得多:

我们首先画一半的橡胶,这几乎只是一条粗线:

这里是导入设置:

*注意:这次我们设置了Pivot到右(边),正确的让一些旋转变得更容易。

现在我们可以从项目区进入场景两次,说出其中一个左橡胶,另一个右橡胶把它们放在弹弓的上部:

我们要确保左边的那个总是画出来的。后面弹弓和右弹弓总是被拉进去的。前面其中的一部分。我们可以再加两个分类层对于我们的游戏,但我们将保持简单,只需更改层序到-1左边的橡胶和1右边的橡胶:

现在橡胶看起来就像在外弹弓:

在开始编写脚本之前,让我们在Hierarchy然后将这两个对象设置为slingshot的子对象:

*注意:每当我们移动弹弓时,橡胶部件就会随之移动。

让我们创建一个新的C#脚本给它起个名字Rubber:

using UnityEngine;
using System.Collections;

public class Rubber : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}
这个脚本的目的是让两个橡胶部分跟随鸟,直到它离开生成鸟的触发圈。

我们需要两个变量leftRubber 和rightRubber,让我们在以后指定橡胶。我们不需要启动或者更新函数,让我们删除掉它们:

using UnityEngine;
using System.Collections;

public class Rubber : MonoBehaviour {
    // The Rubber objects
    public Transform leftRubber;
    public Transform rightRubber;
}
现在,有一些稍微复杂一些的功能。我们将需要一个功能,定位橡胶在弹弓和鸟的位置,我们必须先将橡胶旋转到鸟的方向,然后根据鸟的距离使橡胶变成或变短:
void adjustRubber(Transform bird, Transform rubber) {
    // Rotation
    Vector2 dir = rubber.position - bird.position;
    float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
    rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward);

    // Length
    float dist = Vector3.Distance(bird.position, rubber.position);
    dist += bird.GetComponent<Collider2D>().bounds.extents.x;
    rubber.localScale = new Vector2(dist, 1);
}
注意:首先我们计算从鸟到橡胶的距离,然后计算这个方向的角度。然后我们,通过Quaternion.AngleAxis(angle, Vector3.forward)将橡胶旋转到这个角度。最后,我们计算从鸟到橡胶的距离,之后,我们将橡胶的缩放设置为这个距离加上碰撞器的x的长度既dist += bird.GetComponent().bounds.extents.x

这个OnTriggerStay2D功能将通知我们,每当鸟改变它的位置时,它还在弹弓。我们可以使用这个函数来调整左右橡皮筋:

using UnityEngine;
using System.Collections;

public class Rubber : MonoBehaviour {
    // The Rubber objects
    public Transform leftRubber;
    public Transform rightRubber;

    void adjustRubber(Transform bird, Transform rubber) {
        // Rotation
        Vector2 dir = rubber.position - bird.position;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
        rubber.rotation = Quaternion.AngleAxis(angle, Vector3.forward);

        // Length
        float dist = Vector3.Distance(bird.position, rubber.position);
        dist += bird.GetComponent<Collider2D>().bounds.extents.x;
        rubber.localScale = new Vector2(dist, 1);
    }

    void OnTriggerStay2D(Collider2D coll) {
        // Stretch the Rubber between bird and slingshot
        adjustRubber(coll.transform, leftRubber);
        adjustRubber(coll.transform, rightRubber);
    }
}
快好了。我们将再增加一个离开时候触发的事件,使橡皮筋在发射后变短:
void OnTriggerExit2D(Collider2D coll) {
    // Make the Rubber shorter
    leftRubber.localScale = new Vector2(0, 1);
    rightRubber.localScale = new Vector2(0, 1);
}
现在,我们可以通过首先选择弹弓对象中的游戏对象。层次性然后点击添加组件->Scitps->Rubber…我们亦会把这两个橡胶拖到相应的槽内:

如果我们按下Play现在我们可以看到小鸟和弹弓之间的橡皮筋:

当然,现在我们也可以玩一轮愤怒的小鸟了:

总结

到这里本篇文章就结束了,我们刚刚创建了一个小游戏愤怒的小鸟翻版,使用简单的形状与颜色来达到一个良好的效果,广泛的使用了Unity的2D物理引擎,添加了许多的效果

(0)

相关推荐

  • Unity查找游戏物体的六种方式详解

    一篇小白也能看懂的查找游戏物体的方式解析 – Unity 之 查找物体的几种方式.本文通过实际测试得出使用结论,大家进行简单记录,在使用时想不起来可以再来看看,多用几次基本就没有问题了. 一,Object.Find() Object.Find():根据名称找到游戏对象并返回它. void ObjectFind() { // 找父级 GameObject parent = GameObject.Find("GameObject"); Debug.Log("找父级物体,是否找到:

  • Unity UI实现拖拽旋转

    本文实例为大家分享了Unity UI实现拖拽旋转的具体代码,供大家参考,具体内容如下 跟随鼠标旋转 第一种效果是跟随鼠标旋转,原理是计算下鼠标位置与拖拽物体的相对位移 旋转方向即可 注意转换对应空间坐标 新建脚本mono类继承 IBeginDragHandler, IDragHandler, IEndDragHandler 接口 [SerializeField] private Canvas m_Canvas; private Vector3? CalculateWorldToScreenPos

  • 详解Unity安卓共享纹理

    概述 本文的目的是实现以下的流程: Android/iOS native app 操作摄像头 -> 获取视频流数据 -> 人脸检测或美颜 -> 传输给 Unity 渲染 -> Unity做出更多的效果(滤镜/粒子) 简单通信 在之前的博客里已经说到,Unity 和安卓通信最简单的方法是用 UnitySendMessage 等 API 实现. Android调用Unity: //向unity发消息 UnityPlayer.UnitySendMessage("Main Cam

  • Unity实现切割图集工具

    本文实例为大家分享了Unity实现切割图集工具的具体代码,供大家参考,具体内容如下 操作步骤 先将脚本拖入Editor 1.选中要切割的图片,texture type 选为default,并勾选Advanced下的read/Write Enabled 2.texture type改为sprite(2D and UI),Sprite mode 选为Multiple,apply一下 3.Sprite Editor 先选其他的切一下,在选第一个切一下,切割成小图,apply 4.选中图集右键,imag

  • 详解Unity入门之GameObject

    GameObject和Component GameObject是游戏场景中真实存在的,而且有位置的一个物件 Component附属于GameObject,控制GameObject的各种属性 GameObject是由Component组合成的,Component的生命周期和GameObject息息相关.调用此GameObject的Destroy方法,它的子对象和对应的所有Component都会被销毁,但也可以一次只销毁一个Component 常见的Component: Component 作用 R

  • Unity3D开发教程:愤怒的小鸟

    一.前言 "愤怒的小鸟"在2009年12月发布,由于它的高度上瘾的游戏,它很快成为有史以来最成功的移动游戏. 在本教程中,我们将在"Unity"中实现"愤怒的小鸟"翻版.游戏中最复杂的部分是物理系统,但是多亏了Unity,我们就不用担心太多了. 像往常一样,一切都会尽可能简单地解释,这样每个人都能理解它. 以下是项目的预览: 二.源码 UI资源: https://wwr.lanzoui.com/iENnJop2n9i 密码:bnj2 源代码: h

  • Unity3D开发实战之五子棋游戏

    前言 经过前面<Unity3D入门教程>系列讲解,再加上我们自己的探索,相信大家已经掌握了Unity3D的相关知识和基本方法.本文将使用前面学到的知识,开发一款简单的五子棋程序.本文用到的东西其实不多,非常简单.在最后我们会把完整工程的源代码发布出来,以供初学者参考.先展示一下最后的运行效果吧. 1 准备工作 (1)开发环境:Win10 + Unity5.4.1 (2)图片素材准备: 黑棋子和白棋子 棋盘 获胜提示图片 2 开发流程 上文提到的素材可以直接下载我们给出的这些图,也可以自己制作.

  • AngularJS开发教程之控制器之间的通信方法分析

    本文实例讲述了AngularJS开发教程之控制器之间的通信方法.分享给大家供大家参考,具体如下: 一.指令与控制器之间通信,无非是以下几种方法: 基于scope继承的方式 基于event传播的方式 service的方式(单例模式) 二.基于scope继承的方式: 最简单的让控制器之间进行通信的方法是通过scope的继承.假设有两个控制器Parent.Child,Child 在 Parent 内,那Child 可以称为控制器Parent的子控制器,它将继承父控制器Parent的scope.这样,C

  • 微信小程序应用号开发教程详解

    微信应用号(微信公众平台小程序,「应用号」的新称呼)终于来了!开源中国社区的博卡君通宵吐血赶稿写出的微信公众平台应用号开发教程!大家赶紧来学习一下吧 微信公众平台小程序目前还处于内测阶段,微信只邀请了部分企业参与封测.想必大家都关心应用号的最终形态到底是什么样子?怎样将一个「服务号」改造成为「小程序」? 我们暂时以一款简单的第三方工具的实例,来演示一下开发过程吧.(公司的项目保密还不能分享代码和截图.博卡君是边加班边偷偷给大家写教程.感谢「名片盒」团队提供他们的服务号来动这个手术,所以博卡君的教

  • iOS10最新实现远程通知的开发教程详解

    一.iOS推送通知简介 众所周知苹果的推送通知从iOS3开始出现, 每一年都会更新一些新的用法. 譬如iOS7出现的Silent remote notifications(远程静默推送), iOS8出现的Category(分类, 也可称之为快捷回复), iOS9出现的Text Input action(文本框快捷回复). 而在iOS10, 苹果可谓是大刀阔斧般的, 对远程通知和本地通知进行了大范围的更新. iOS10推出了全新的UserNotifications框架(iOS10之前从属于UIKi

  • 微信公众帐号开发教程之图文消息全攻略

    引言及内容概要 已经有几位读者抱怨"柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用",好吧,我错了,原本以为把基础API封装完.框架搭建好,再给出一个文本消息的使用示例,大家就能够照猫画虎的,或许是因为我的绘画功底太差,画出的那只猫本来就不像猫吧-- 本篇主要介绍微信公众帐号开发中图文消息的使用,以及图文消息的几种表现形式.标题取名为"图文消息全攻略",这绝对不是标题党,是想借此机会把大家对图文消息相关的问题.疑虑.障碍全部清除掉. 图文消

  • 微信公众平台开发教程(五)详解自定义菜单

    一.概述: 如果只有输入框,可能太简单,感觉像命令行.自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯.在一个小小的微信对话页面,可以实现更多的功能.菜单直观明了,不仅能提供事件响应,还支持URL跳转,如果需要的功能比较复杂,我们大可以使用URL跳转,跳转至我们的网页即可. 注意:自定义菜单,只有服务号才有此功能 接着我们详细介绍,如何实现自定义菜单? 二.详细步骤: 1.首先获取access_token access_token是公众号的全局唯一票据,公众号调用各接口时都需使用acc

  • Node.js开发教程之基于OnceIO框架实现文件上传和验证功能

    OnceIO 是 OnceDoc 企业内容(网盘)的底层Web框架,它可以实现模板文件.静态文件的全缓存,运行起来完全不需要I/O操作,并且支持客户端缓存优化,GZIP压缩等(只在第一次压缩),拥有非常好的性能,为您节约服务器成本.它的模块化功能,可以让你的Web进行分布式存储,即一个扩展包里即包含前端.后端和数据库定义,只需通过添加/删除目录的方式就可实现功能删减,实现真正的模块化扩展.这里是介绍如何使用OnceIO的一系列文章. 在这一章节中,我们将为大家演示如何使用 OnceIO 实现文件

  • 微信公众平台开发教程(四) 实例入门:机器人回复(附源码)

    上一篇文章,写了基本框架,可能很多人会觉得晕头转向,这里提供一个简单的例子来予以说明,希望能帮你解开谜团. 一.功能介绍 通过微信公众平台实现在线客服机器人功能.主要的功能包括:简单对话.查询天气等服务. 这里只是提供比较简单的功能,重在通过此实例来说明公众平台的具体研发过程.只是一个简单DEMO,如果需要的话可以在此基础上进行扩展. 当然后续我们还会推出比较复杂的应用实例. 二.具体实现 1.提供访问接口 这里不再赘述,参照上一章,微信公众账号开发教程(二) 基础框架搭建 http://www

  • PHP扩展开发教程(总结)

    PHP是一种解释型的语言,对于用户而言,我们精心的控制内存意味着easier prototyping和更少的崩溃!当我们深入到内核之后,所有的安全防线都已经被越过,最终还是要依赖于真正有责任心的软件工程师来保证系统的稳定运行. 1.线程安全宏定义 在TSRM/TSRM.h文件中有如下定义 #define TSRMLS_FETCH()       void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL) #define TSRMLS_FETCH_FR

随机推荐