人人都能写游戏系列(一)Unity简单跳一跳游戏开发

人人都能写游戏系列(一)

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

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

创建项目

首先,我们打开Unity工程,建立一个简单的3d项目,起名叫JumpJump(当然了,这里起名是任意的)
创建项目
为了体现远近大小一致,我们这里采用正交相机
在这里插入图片描述
然后我们就该布置场景了

布置场景

添加物体

我们需要一个柱子,让用户能在上面落脚,我们还需要一个玩家,能让用户看到自己的位置,我们的背景也很丑,我们需要一个遮羞布把他挡起来。

为了简单,我这里柱子使用了伸长了的cube,玩家也是一个简单的小cube,遮羞布?那就更简单了,只是个旋转了的plane而已。
在场景中右键选择3D Object 下面的cube和plane我们就创建了如图所示的物体。

在这里插入图片描述

调整物体

然后我们调整一下物体的命名和位置,(这里就不上图了)调整后的结果,相机的位置是(1.12,0,-3)。cube的位置是(0,0,0),缩放是(0.6,1,1),并改名为seat。cube(1)的位置是(0,1,0)缩放是(0.2,0.2,0.5),并改名为player。plane的位置是(0,0,0)旋转是(270,0,0)缩放是(2,1,2),如此一来,我们的场景就布置好了。

在这里插入图片描述
注意:在这里检查你的层级关系,这里是没有嵌套的(跟上图一致就对了)

添加材质球

我们发现,我们的plane和方块都是白色,很难看出来个数,所以我们想到,给我们的遮羞布,换个颜色。要想生活过的去,总得头上带点那啥 ,咳咳,那我们就用酷酷的绿色好了。
在下面的assets中右键创建一个材质球在这里插入图片描述
并命名为bg(背景background的缩写)当然了,这里的起名也是随意的。
选中材质球,然后选择颜色,这里颜色当然是任意的了,我就随便选了一个我觉得酷酷的绿色。
在这里插入图片描述
选择颜色后,左键选中材质球,并拖拽到plane上,这样我们的plane就是酷酷的绿色了。
在这里插入图片描述
我们的小玩家,就是我们的player还没有颜色呢,我们重复上面的材质球步骤,给我们的小玩家,也添上一个颜色。这里我就用了帅帅的黄色。
在这里插入图片描述
好了,到这里,我们的布置场景就算结束了。

制作预制体

预制体是什么

预制体在Unity里面我们叫它Prefab。我们也可以这样理解:当制作好了游戏组件(场景中的任意一个gameobject我们希望将它制作成一个组件模版,用于批量的套用工作,例如说场景中本质上要重复使用的东西,比如:敌人、士兵、子弹或者一个砖块完全相同的墙体。这里说本质是因为默认生成的prefab其实和模版是一模一样的。

为什么要使用预制体

因为我们会有很多很多个落脚点,所以我们需要很多很多的柱子,也就是场景里的seat
我们希望通过预制体这种克隆方式,来生成很多很多的柱子。

制作预制体

选中场景中的seat,然后拖拽其到assets面板中,Unity会自动为我们生成预制体。
在这里插入图片描述
如图所示
在这里插入图片描述
有了预制体,我们就不需要原始的seat了,我们在场景中删除他
在这里插入图片描述
好了,我们的场景已经布置完毕了。接下来,我们只需要编写相应的脚本,我们的游戏就算完成了。

编写脚本

事先准备

首先,我们需要我们的目的,我们希望,可以让小人跳起来,那么在物理中,我们只需给小人一个向前和向上的力,他就会向前跳起来。小人跳起来会下落,那自然是因为重力的作用,所以为了使小人满足物理世界规律,我们要给小球添加Rigidbody组件。
在这里插入图片描述
选中小人,选择Add Component,找到Rigidbody,点击,即可完成添加,此时,小人已经默认有了重力作用。如果此时我们运行游戏,我们的player就会无限往下落。
在这里插入图片描述
为了防止我们的遮羞布对我们的游戏造成影响,我们要删除遮羞布的碰撞体
在这里插入图片描述

开始编写脚本

首先,我们在面板中创建一个c#的脚本
在这里插入图片描述
起名叫JumpJump(当然了,这里起名也是任意的),双击脚本,打开vs编辑器,如图所示
在这里插入图片描述
Unity的脚本很简单,Start是脚本启动的时候运行一次,Update是每帧都会运行,基本上有这俩个方法,我们就能实现一个游戏流程了。

Start方法

Start方法中,我们要进行一些初始化的工作,就是生成很多个柱子。

要生成很多个柱子,首先我们要获得我们创建的预制体。在Unity脚本中,public变量会显示在Unity的检视面板中,并能为其赋值。所以,我们这里声明我们的预制体变量

public GameObject seat;

然后我们像本文中叙述的挂载材质球一样,将这个脚本挂载在player上,并推拽seat到脚本框内,完成赋值。
在这里插入图片描述
完成后如图所示
在这里插入图片描述
接着,我们在脚本中编写生成柱子的代码。
我们首先要生成一个柱子,在player的正下方,他可以接住player,作为起始点。
后面的柱子我们希望他们的间隔距离随机的,而且宽度也是不一致的,这样才会有游戏性。很多个柱子我们希望可以动态回收和利用,有助于我们的游戏流畅性提高。
所以,我们需要声明一个变量,来保存我们随机的这些柱子。我这里采用了ArrayList
因为他的动态性比较好。
声明变量:

private ArrayList seats;

然后在Start中编写

        //需要new对象
        seats = new ArrayList();
        //添加第一个柱子在player下方。
        seats.Add(Instantiate(seat, new Vector3(0, 0, 0), Quaternion.identity));
        //循环添加20个柱子
        for (int i = 1; i < 20; i++)
        {
             //添加的柱子的间隔是随机的
            seats.Add(Instantiate(seat, new Vector3(Random.Range(1f, 2.28f) + ((GameObject)seats[i - 1]).transform.position.x, 0, 0), Quaternion.identity));
            //修改他们的宽度
            ((GameObject)seats[i]).transform.localScale = new Vector3(Random.Range(0.5f, 1f), ((GameObject)seats[i]).transform.localScale.y, ((GameObject)seats[i]).transform.localScale.z);
        }

Update方法

柱子有了,我们希望在小人蓄力阶段,柱子可以压缩,而且是缓慢非线形压缩,我们可以自己构建一个数学函数,来表达这个曲线的压缩过程,我们也可以使用Unity给我们提供的数学库中的方法。我这里使用的是Unity的平滑插值smoothstep,他的函数表达式是3x^2 -2x^3。因为压缩柱子是在用户按下屏幕/特定键才会触发松手即回弹,所以我们写在Update方法中,我们还希望我们的程序可以跑在pc和安卓中,所以我们需要检测在pc上按下了特定键,在安卓上是手指触碰了屏幕
(nowat,time和ondown应该声明在程序的最开始,是全局变量,nowat用于指示当前是在哪个柱子上)

 	if ((Input.GetKey(KeyCode.Space) || Input.touchCount > 0))
        {
            var y = Mathf.SmoothStep(1, endscalcey, time * 0.01f);
            nowat.transform.localScale = new Vector3(nowat.transform.localScale.x, y,nowat.transform.localScale.z);
            time += Time.timeScale;
            //ondown用来声明是不是已经按下
            ondown = true;
 	    //按下时间的最大值
            time = time > 100 ? 100 : time;
        }
        if ((Input.GetKeyUp(KeyCode.Space) || (onandriod && Input.touchCount == 0)) && ondown)
        {
            ondown = false;
  
            //将按下计时变成0
            time = 0;
            //回弹
            nowat.transform.localScale = new Vector3(nowat.transform.localScale.x, 1, nowat.transform.localScale.z);
        }

player

柱子的压缩我们已经完成了,接下来,我们需要让player可以跳起,因为第一次做游戏,我们并不知道多少的弹跳力合适,所以我们需要在检视面板中可以随时修改,由此,我们声明他为public

public float jump = 1;

添加起跳很简单,我们只需要在用户抬起的一瞬间,我们施加给他一个瞬时的向前和向上的力就可以了,为了简单,我这里假设起跳高度是永恒不变的,time改变的只是向前的力,我们在抬起的代码中添加一行即可

 GetComponent<Rigidbody>().AddForce(new Vector3(time * jump, 300, 0));

我们还需要判断用户是跳在了柱子上,还是掉了下去。也就是判断是否游戏结束。
这个也非常容易,我们只要检测player的y轴坐标是不是小于一个最小值,我们就知道了他是否掉了下去,掉了下去,我们就要重新开始游戏。
在脚本中导入UnityEngine.SceneManagement
然后在Update中编写死亡检测的代码:

        if (transform.position.y < 0.2f)
        {
            //这里要跟场景名称一致
            SceneManager.LoadScene("SampleScene");
        }

跳到哪了?

前面讲我们需要压缩柱子,所以我们需要知道压缩的是哪个柱子,我们不希望player可以在空中连续跳跃,所以也要有代码防止这个bug,所以我们使用柱子的碰撞体,撞到哪根柱子,我们就把哪根柱子作为nowat,只有碰到柱子,才可以起跳,离开柱子,不能起跳,所以我们要重写碰撞体的两个方法

 private void OnCollisionEnter(Collision collision)
    {
        nowat = collision.gameObject;
        canjump = true;
      
    }
    private void OnCollisionExit(Collision collision)
    {
        canjump = false;
       
    }

摄像机和plane的跟随

我们的代码基本完成了。但是我们希望摄像机和遮羞布一直跟着我们的player移动,而不是摄像机死叮在一个点上,跳跳,看不见我们的player了,这肯定是不和逻辑的。我们也希望回收一些不可见的柱子,提高游戏的流畅性。也要创建新柱子让用户可以接着跳。
为了减轻处理压力,我们一般把摄像机跟随,写在LateUpdate()中。LateUpdate()会在所有的Update方法调用完成后调用。

private void LateUpdate()
    {
        //player所在位置
        Vector3 playerpos = transform.position;
        //相机跟随
        maincamera.transform.position = new Vector3(playerpos.x + 1.12f, maincamera.transform.position.y, maincamera.transform.position.z);
        //遮羞布跟随
        plane.transform.position = new Vector3(playerpos.x + 1.12f, plane.transform.position.y, plane.transform.position.z);
        //回收柱子
        if (playerpos.x > ((GameObject)seats[0]).transform.position.x + 6)
        {
            ((GameObject)seats[0]).SetActive(false);
            Destroy(((GameObject)seats[0]));
            seats.Remove(seats[0]);
            seats.Add(Instantiate(seat, new Vector3(Random.Range(2f, 5f) + ((GameObject)seats[seats.Count - 1]).transform.position.x, Random.Range(-1.09f, 5.53f), -8.2f), Quaternion.identity));

        }

    }

完整代码

至此,我们的代码已经写完了,这里附上完整源码。

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

public class JumpJump : MonoBehaviour
{
    //预制件
    public GameObject seat;
    //最终压缩高度
    public float endscalcey = 0.5f;
    //很多的柱子
    private ArrayList seats;
    //主相机
    public Camera maincamera;
    //到哪个柱子了
    private GameObject nowat;
    //是否可以跳跃
    private bool canjump = false;
    //按下的时长
    private float time = 0;
    //指示是否按下
    private bool ondown = false;
    //弹跳力
    public float jump = 1;
    //遮羞布
    public GameObject plane;
    //是否运行在手机,如果运行手机,需要在检视面板中把他勾选上,然后再编译apk
    public bool onandriod = false;

    void Start()
    {
        seats = new ArrayList();
        seats.Add(Instantiate(seat, new Vector3(0, 0, 0), Quaternion.identity));

        for (int i = 1; i < 20; i++)
        {
            seats.Add(Instantiate(seat, new Vector3(Random.Range(1f, 2.28f) + ((GameObject)seats[i - 1]).transform.position.x, 0, 0), Quaternion.identity));
            ((GameObject)seats[i]).transform.localScale = new Vector3(Random.Range(0.5f, 1f), ((GameObject)seats[i]).transform.localScale.y, ((GameObject)seats[i]).transform.localScale.z);
        }
    }

    void Update()
    {

        if (canjump && (Input.GetKey(KeyCode.Space) || Input.touchCount > 0))
        {
            var y = Mathf.SmoothStep(1, endscalcey, time * 0.01f);
            nowat.transform.localScale = new Vector3(nowat.transform.localScale.x, y, nowat.transform.localScale.z);
            time += Time.timeScale;
            ondown = true;
            Debug.Log("asd");
            time = time > 100 ? 100 : time;
        }
        if (canjump && (Input.GetKeyUp(KeyCode.Space) || (onandriod && Input.touchCount == 0)) && ondown)
        {
            ondown = false;
            GetComponent<Rigidbody>().AddForce(new Vector3(time * jump, 300, 0));

            time = 0;
            nowat.transform.localScale = new Vector3(nowat.transform.localScale.x, 1, nowat.transform.localScale.z);
        }

        if (transform.position.y < 0.2f)
        {
            SceneManager.LoadScene("SampleScene");
        }
    }

    private void LateUpdate()
    {
        Vector3 playerpos = transform.position;
        maincamera.transform.position = new Vector3(playerpos.x + 1.12f, maincamera.transform.position.y, maincamera.transform.position.z);
        plane.transform.position = new Vector3(playerpos.x + 1.12f, plane.transform.position.y, plane.transform.position.z);
        if (playerpos.x > ((GameObject)seats[0]).transform.position.x + 6)
        {
            ((GameObject)seats[0]).SetActive(false);
            Destroy(((GameObject)seats[0]));
            seats.Remove(seats[0]);
            seats.Add(Instantiate(seat, new Vector3(Random.Range(2f, 5f) + ((GameObject)seats[seats.Count - 1]).transform.position.x, Random.Range(-1.09f, 5.53f), -8.2f), Quaternion.identity));

        }

    }


    private void OnCollisionEnter(Collision collision)
    {
        nowat = collision.gameObject;
        canjump = true;

    }
    private void OnCollisionExit(Collision collision)
    {
        canjump = false;

    }
}

注意:脚本完成后,需要在检视面板中绑定主相机和遮羞布
完成后如图所示
在这里插入图片描述
现在,游戏已经完成了,快让我们运行吧!
在这里插入图片描述

支持我

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

完整项目下载

点我下载

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

猜你喜欢

转载自blog.csdn.net/qwe25878/article/details/84955119
今日推荐