人人都能写游戏系列(二)Unity Flappybird游戏开发

引言

本系列中,我会在0美术的情况下,教大家开发几款简单的小游戏。适合Unity的初学者。
本系列其他游戏开发

今天要开发的游戏是仿造Flappybird类型的跳跃游戏。仅有一个场景,简单易学。

创建项目

首先,我们打开Unity工程,建立一个简单的3d项目,起名叫Flappybird(当然了,这里起名是任意的)
在这里插入图片描述
为了便于讲解,有些跟人人都能写游戏系列(一)Unity简单跳一跳游戏开发的内容在此不在重复叙述。
为了体现远近大小一致,我们这里采用正交相机(也可在创建项目时选择2D)。
接下来,就是布置场景了。如果你对上文很熟悉,那么这里的布置场景,就对你来讲是很简单的事了。

布置场景

右键删除场景的光照,并添加一个plane将scale X,Y,Z设置为2,这样可以铺满整个屏幕,并添加一个如图所示的cube,并在cube上挂上一个绿色材质(颜色也是任意啦)
在这里插入图片描述
注意这里将cube的z位置z设置为-2,是为了在plane的前面,没有更改z轴缩放,是为了有合理的碰撞厚度
接着,我们选中cube,右键选择Duplicate,这个功能是用来copy一个跟当前物体一样的出来。在Flappybird这个游戏中,柱子是分上下的,只有中间可以通过,然后我们调整两个柱子的距离,让中间留个合适的缝隙。这里为了观察,我将plane变成了蓝色。
在这里插入图片描述
接着,我们新建一个空的gameobject,让这俩个cube挂载在空的gameobject下,变成他的子物体。
在这里插入图片描述
我们整理一下我们的命名,将plane更名为bg(背景的缩写),将gameobject更名为pillar(柱子的含义)并拖拽到下面的面板中,变成prefab。
在这里插入图片描述
接着,我们添加我们的主角,就是小鸟,当然了,我们没有美术,这里就用一个cube代替了,为了方便,我们直接添加上柱子的材质。并调整到适合的大小。
在这里插入图片描述
为了让我们的小鸟可以像真实世界一样因为重力掉落,我们需要对他添加一个名叫Rigidbody的组件。
关于Rigidbody,以后有时间,我会开一个单独的博文来讲解。
在这里插入图片描述
此时,如果我们运行游戏,小鸟就会无限向下落。证明我们的组件是生效的。
接下来,我们就要编写脚本了。

编写脚本

在写脚本之前的思考

首先,我们要明确我们的目标,要运行这个游戏,我们需要哪几个部分?

  • 结束游戏的判断
    一个游戏要能健康的运行,必须要有失败或者成功的判断,我们这里,小鸟gg的可能性就两种:
    1.超出屏幕外(往上飞的太高/落的太深)。
    2.碰到了柱子。
    ok,这里懂了。

  • 乐趣所在
    一个游戏要做到吸引人玩,我们得有乐趣所在,这里我们直接采用无脑循环的方式来让用户一遍接一遍的玩。就是死了立刻没任何延迟的重新开始。

  • 敌人或者障碍
    这里当然就是高低不同的柱子了,我们通过随机数,随机出不同距离,不同位置的柱子就可以了。

  • 玩法
    小鸟已恒速前进,我们只能控制是否向上飞起。

  • 摄像机的跟随
    摄像机的跟随,我们不希望小鸟在动,摄像机不跟着动。

  • 背景的跟随
    类似摄像机跟随,我们不希望露馅儿。

ok,思考了这么多,我们开始编写我们的脚本。

开始编写

我们新建一个名为叫Move(名称任意)的脚本,我们可能会需要多次调用小球的Rigidbody和Transform,所以,我们最好使用变量把他们存下来。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
	void Start () {
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
	}
	
	
	void Update () {
		
	}
}

我们可能还需要不断的调整弹跳力的大小,为了方便调试,我们应该能允许他在检视器面板中调整。
我们可能还需要不断的调整小鸟前进的速度,为了方便调试,我们应该能允许他在检视器面板中调整。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    //弹跳力
    public float force=200;
    //前进速度
    public float speed = 2;
	void Start () {
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
	}
	
	
	void Update () {
		
	}
}

接着,我们要控制我们的小鸟跳起来,如何跳起来呢?
很简单,我们只需对小鸟施加一个向上的力就可以了。
我们要写在update方法中,确保每次我们按下空格,小鸟都会跳起。
我们不希望小鸟在按住空格的时候一直跳,所以检测按键中我们使用Input.GetKeyDown而不是Input.GetKey。
然后我们需要小鸟已恒速前进,这个也简单。
注意,在游戏开发中,我们通常将按键检测写在update中而不是fixedupdate,写在fixedupdate中会导致手感较肉

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    public float force=200;
    public float speed = 2;
	void Start () {
       
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
        rigid.velocity = Vector3.right * speed;
	}
	
	
	void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        rigid.AddForce(Vector3.up*force,ForceMode.Force);
	}
}

现在,让我们把脚本挂在我们的player上,运行游戏,看他有没有成功实现我们的功能。
在这里插入图片描述
他确实成功的运行了,我们的小鸟可以抬高,可以下落,有一个恒定的速度在前进。
接着我们进行下一步,游戏结束的判断。

游戏结束的判断

俩种情况会结束游戏
碰到柱子,我们可以用检测碰撞事件的发生。
在屏幕外,我们可以判断是否在摄像机外。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    public float force=200;
    public float speed = 2;
	void Start () {       
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
        rigid.velocity = Vector3.right * speed;
	}
	
	
	void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        rigid.AddForce(Vector3.up*force,ForceMode.Force);
	}

    void OnCollisionEnter(Collision collision)
    {
        Debug.Log("碰到柱子啦");
    }


    void OnBecameInvisible()
    {
        Debug.Log("看不见啦");
    }
}

保存脚本,让我们运行一下在这里插入图片描述
下方的控制台打出了我们的信息,说明代码没问题。
这里判断游戏结束,我们也可以使用一个y轴的范围,就是屏幕最上面的y坐标,屏幕最下面的y坐标,通过这个范围,检测我们的小鸟是否在屏幕范围内,代码也很简单,这里就不在多余的解释了。

秒开下一局

其实就是重新载入这个场景,Unity中使用SceneManager来管理场景。
我们这里使用SceneManager.LoadScene来加载场景。
SceneManager.LoadScene需要传入一个字符串,就是场景的名字,这里不要拼错,我们这个场景是默认的命名,就是SampleScene。
在检测游戏结束的代码中将打印log变成SceneManager.LoadScene就行了。
注意,这里要导入 UnityEngine.SceneManagement包

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    public float force=200;
    public float speed = 2;
	void Start () {       
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
        rigid.velocity = Vector3.right * speed;
	}
	
	
	void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        rigid.AddForce(Vector3.up*force,ForceMode.Force);
	}

    void OnCollisionEnter(Collision collision)
    {
        SceneManager.LoadScene("SampleScene");
    }


    void OnBecameInvisible()
    {
        SceneManager.LoadScene("SampleScene");
    }
}

这样,我们就完成了游戏结束和开始。

生成柱子

我们的场景中只有一个柱子,这不是一个游戏,我们还需要很多随机的柱子,这样才能算一个游戏。
而且,我们的柱子数量是无限的,不能用户玩玩后面就没柱子可以通过了。先让我们实现固定数目的柱子吧。
为了实现这个功能,我们需要先把prefab挂载进来。
我们还需要管理我们生成的柱子,那就自然要用数组啦。
这里我们发现,我们的柱子长度不够,所以更新下prefab让y=12,重新调整下空隙。
然后删除场景中的prefab,这样更方便管理一些。

 public GameObject pillar;
 public GameObject[] myPillar;

接着在start方法中生成一些柱子

        myPillar = new GameObject[20];
        myPillar[0]=Instantiate(pillar);
        myPillar[0].transform.position = new Vector3(0, 0, -2);
        for (int i = 1; i < 20;i++)
        {
            myPillar[i] = Instantiate(pillar);
            //设置随机位置
            myPillar[i].transform.position =   new Vector3(myPillar[i - 1].transform.position.x+Random.Range(2,4.02f),Random.Range(-2.0f, 2.04f),-2);
        }

将prefab推拽给脚本,我们就实现了随机柱子。
在这里插入图片描述
先不管回收柱子的事,我们先实现摄像机的跟随。

摄像机跟随

我们的摄像机只要在x轴上是和小鸟一致的就可以。
我们需要多次操作摄像机的位置,所以用一个变量来获得摄像机的Transform。

 public Transform cameraTrans;

一般在游戏中,我们会在LateUpdate中更新摄像机的位置

private void LateUpdate()
	{
        cameraTrans.position = new Vector3(trans.position.x, cameraTrans.position.y, cameraTrans.position.z);
	}

将摄像机在检视器面板赋值给脚本,我们运行游戏,发现确实跟随了。
在这里插入图片描述

背景的跟随

类似摄像机的跟随,我们也只需要移动背景的x轴就可以了。
附上到目前为止的完整代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    public float force=200;
    public float speed = 2;
    public GameObject pillar;
    public GameObject[] myPillar;
    public Transform cameraTrans;
    public Transform bgTrans;
	void Start () {       
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
        rigid.velocity = Vector3.right * speed;
        myPillar = new GameObject[20];
        myPillar[0]=Instantiate(pillar);
        myPillar[0].transform.position = new Vector3(0, 0, -2);
        for (int i = 1; i < 20;i++)
        {
            myPillar[i] = Instantiate(pillar);
            //设置随机位置
            myPillar[i].transform.position =   new Vector3(myPillar[i - 1].transform.position.x+Random.Range(2,4.02f),Random.Range(-2.0f, 2.04f),-2);
        }
	}
	
	
	void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        rigid.AddForce(Vector3.up*force,ForceMode.Force);
	}
	private void LateUpdate()
	{
        cameraTrans.position = new Vector3(trans.position.x, cameraTrans.position.y, cameraTrans.position.z);
        bgTrans.position=new Vector3(trans.position.x, bgTrans.position.y, bgTrans.position.z);
	}

	void OnCollisionEnter(Collision collision)
    {
        SceneManager.LoadScene("SampleScene");
    }


    void OnBecameInvisible()
    {
        SceneManager.LoadScene("SampleScene");
    }
}

我们可以将小鸟的z位置拉到-4,(这样小鸟和柱子不在一个平面上,不会发生碰撞)然后运行游戏,观看下柱子的生成情况。以及摄像机和背景的跟随情况,方便我们调整。
在这里插入图片描述
然后我们把小鸟调整回-2位置。在回到柱子这里。

回收柱子并生成新的柱子

我们刚才生成了20个柱子,但是用户玩过了20个之后要怎么办?
我们要考虑回收掉已经经过的柱子,然后在给用户生成新的柱子。
人人都能写游戏系列(一)Unity简单跳一跳游戏开发中,我使用了ArrayList来实现,这里,我们换一种方式,自己维护柱子。
先看代码

 int whichcan=0;
  int maxcan=19;
private void ReLoad()
    {
        if(myPillar[whichcan].transform.position.x<cameraTrans.position.x-10)
        {
            //销毁物体
            Destroy(myPillar[whichcan]);
            //重新生成物体
            myPillar[whichcan]=Instantiate(pillar);
            maxcan = whichcan == 0 ? 19 : whichcan - 1;
            myPillar[whichcan].transform.position = new Vector3(myPillar[maxcan].transform.position.x + Random.Range(2, 4.02f), Random.Range(-2.0f, 2.04f), -2);
            whichcan++;
            if (whichcan == 20)
                whichcan = 0;
        }
    }

看懂了么?
whichcan是队列的尾端,就是将要销毁的柱子
maxcan是队伍的头部,也就是最前方的柱子
我们新生成的柱子的x位置应该是最前方的柱子+随机量
这个问题就变成了销毁队尾的柱子生成比最前方的柱子还要前方的柱子。
嘿嘿,是不是很简单呢?

完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Move : MonoBehaviour {

    Transform trans;
    Rigidbody rigid;
    public float force=200;
    public float speed = 2;
    public GameObject pillar;
    public GameObject[] myPillar;
    public Transform cameraTrans;
    public Transform bgTrans;
    int whichcan=0;
    int maxcan=19;
	void Start () {       
        trans = this.transform;
        rigid = this.GetComponent<Rigidbody>();
        rigid.velocity = Vector3.right * speed;
        myPillar = new GameObject[20];
        myPillar[0]=Instantiate(pillar);
        myPillar[0].transform.position = new Vector3(0, 0, -2);
        for (int i = 1; i < 20;i++)
        {
            myPillar[i] = Instantiate(pillar);
            //设置随机位置
            myPillar[i].transform.position =   new Vector3(myPillar[i - 1].transform.position.x+Random.Range(2,4.02f),Random.Range(-2.0f, 2.04f),-2);
        }
	}
	
	
	void Update () {
        if(Input.GetKeyDown(KeyCode.Space))
        rigid.AddForce(Vector3.up*force,ForceMode.Force);
	}
	private void LateUpdate()
	{
        cameraTrans.position = new Vector3(trans.position.x, cameraTrans.position.y, cameraTrans.position.z);
        bgTrans.position=new Vector3(trans.position.x, bgTrans.position.y, bgTrans.position.z);
        ReLoad();
	}

	private void ReLoad()
    {
        if(myPillar[whichcan].transform.position.x<cameraTrans.position.x-10)
        {
            
            Destroy(myPillar[whichcan]);
            myPillar[whichcan]=Instantiate(pillar);
            maxcan = whichcan == 0 ? 19 : whichcan - 1;
            myPillar[whichcan].transform.position = new Vector3(myPillar[maxcan].transform.position.x + Random.Range(2, 4.02f), Random.Range(-2.0f, 2.04f), -2);
            whichcan++;
            if (whichcan == 20)
                whichcan = 0;
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        SceneManager.LoadScene("SampleScene");
    }


    void OnBecameInvisible()
    {
        SceneManager.LoadScene("SampleScene");
    }
}

运行截图

在这里插入图片描述

开始享受你的游戏之旅吧!

如果喜欢,请支持我哦!

支持我

您的支持,就是我创作的最大动力

完整项目下载

完整项目下载

发布了28 篇原创文章 · 获赞 49 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qwe25878/article/details/85328992