人人都能寫遊戲系列(一)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;

    }
}

注意:腳本完成後,需要在檢視面板中綁定主相機和遮羞布
完成後如圖所示
在這裏插入圖片描述
現在,遊戲已經完成了,快讓我們運行吧!
在這裏插入圖片描述

支持我

您的支持,就是我創作的最大動力

完整項目下載

點我下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章