1. 程式人生 > >【Unity3D實戰】零基礎一步一步教你製作跑酷類遊戲(填坑完整版)

【Unity3D實戰】零基礎一步一步教你製作跑酷類遊戲(填坑完整版)

在兩個月前曾寫了一篇《【Unity3D實戰】零基礎一步一步教你製作跑酷類遊戲(1)》,裡面一步一步演示了製作跑酷類遊戲,然而由於時間原因,只寫到了讓角色往前移動為止。這個坑一直沒有時間去填,(雖然也沒多少人看啦),今天剛好有時間完成了一個跑酷類遊戲的Demo。放上來給有興趣的朋友看看。

Demo原始碼及對應素材下載:連結: https://pan.baidu.com/s/1smnQFt7 密碼: ptcw

遊戲簡要說明

遊戲型別:跑酷類遊戲(Demo,非完整遊戲)

操作方式:左右方向鍵(可自己移植到手機端)

遊戲要素:

1.遊戲角色會自動向前跑,玩家可通過左右方向鍵讓其左右移動

2.遊戲中存在障礙物,玩家需避開這些障礙物,否則會因為被障礙物阻擋的原因無法前進

3.當遊戲角色因為被阻擋而消失在視野中時,視為失敗

4.當遊戲角色因為被阻擋而處於偏後方時,會提高移動速度直到回到原本所處的螢幕位置

遊戲場景搭建

使用準備好的素材(路面、人物、障礙物),將這些素材製作成Prefab,然後根據自己喜好搭建好場景(如何搭建請看上一篇教程:《【Unity3D實戰】零基礎一步一步教你製作跑酷類遊戲(1)》)。如下圖:

遊戲指令碼編寫

遊戲角色控制器moveController: 新建一個C#檔案,命名為moveController,然後將其開啟。 由於角色需要向前、左、右三個方向移動,所以我們需要有其在前進方向上的速度左右方向上的速度,分別命名為:moveVSpeed、moveHSpeed, 同時由於玩家在落後的情況下需要加速,所以我們宣告兩個變數:前進方向上的最小移動速度minVSpeed
前進方向上的最大移動速度maxVSpeed。 於是我們可以得到以下指令碼:
// 前進移動速度
float moveVSpeed;
// 水平移動速度
public float moveHSpeed = 5.0f;
// 最大速度
public float maxVSpeed = 10.0f;
// 最小速度
public float minVSpeed = 5.0f;
其中moveHSpeed、maxVSpeed、minVSpeed宣告為public,方便在面板上修改。 錯誤修改:感謝 jewis123 朋友提出的,這裡漏了jumpHeight與m_jumpState的定義,前者代表最大高度,後者代表當前是向上跳躍,還是從高處落下,詳細可檢視原始碼。
接下來,在Start()函式中定義moveVSpeed的初始值:
        moveVSpeed = minVSpeed;
在Update()中使人物能移動起來:
        float h = Input.GetAxis("Horizontal");
        Vector3 vSpeed = new Vector3(this.transform.forward.x, this.transform.forward.y, this.transform.forward.z) * moveVSpeed ;
        Vector3 hSpeed = new Vector3(this.transform.right.x, this.transform.right.y, this.transform.right.z) * moveHSpeed * h;
        Vector3 jumpSpeed = new Vector3(this.transform.up.x, this.transform.up.y, this.transform.up.z) * jumpHeight * m_jumpState;
        this.transform.position += (vSpeed + hSpeed + jumpSpeed) * Time.deltaTime;
儲存一下cs檔案,切換到Unity,將該指令碼掛載在角色物件的身上,保留預設值或手動設定: 運行遊戲,看看是否能成功跑起來,並且能通過左右鍵控制人物左右移動。 看著人物越跑越遠越跑越遠,最後消失在遠方......誒!教練,這和說好的不一樣啊!人物咋不見了? 咳咳,這是因為我們沒有讓攝像機跟隨它的原因,接下來,我們讓攝像機與人物一起移動~ 開啟剛才的C#檔案,宣告一個public的變數

    // 攝像機位置
   public Transform cameraTransform;
在Update()函式中,新增以下程式碼: // 設定攝像機移動速度 Vector3 vCameraSpeed = new Vector3(this.transform.forward.x, this.transform.forward.y, this.transform.forward.z) * minVSpeed; // 讓攝像機跑起來 cameraTransform.position += (vCameraSpeed) * Time.deltaTime; 注意到沒,這裡我們所定義的攝像機的移動速度與人物移動速度有點差別: 1.攝像機沒有左右移動 2.攝像機的速度恆定為minVSpeed,也就是我們所定義的人物的最小移動速度(當然這個時候人物也一直是以這個速度在移動) 轉到Unity,檢視人物身上的Move Controller元件,現在這裡應該多了一個變數等你設定: 我們將攝像機拖動到camera Transform處,再運行遊戲。這時候你應該能看到人物在不斷往前走,但在螢幕上的位置是沒有變化的,因為攝像機一起移動了。 人物走著走著 哎呀 前面怎麼沒路了?別急,讓我們來讓路無限延長起來~ 首先我們將道路的GameObject複製幾個,我這裡是總共有3個道路的GameObject,分別命名為Road1,Road2,Road3 然後在每一個Road下,新增一個Cube,將Cube的Mesh Renderer關閉,並將其Box Collider的Is Trigger勾上。命名為ArrivePos。(我才不會告訴你們這一步應該在上一行之前做呢!) 多條道路拼好,連成一條筆直的公路。 然後新建一個空物體,命名為GameManager,為其新建C#Script  GameManager.cs,然後開啟該指令碼。 宣告一下多個變數:(注意引用名稱空間using System.Collections.Generic;
    // 生成障礙物點列表
    public List<Transform> bornPosList = new List<Transform>();
    // 道路列表
    public List<Transform> roadList = new List<Transform>();
    // 抵達點列表
    public List<Transform> arrivePosList = new List<Transform>();
    // 障礙物列表
    public List<GameObject> objPrefabList = new List<GameObject>();
    // 目前的障礙物
    Dictionary<string, List<GameObject>> objDict = new Dictionary<string, List<GameObject>>();
    // 道路間隔距離
    public int roadDistance;
並定義函式:
    // 切出新的道路
    public void changeRoad(Transform arrivePos)
    {
        int index = arrivePosList.IndexOf(arrivePos);
        if(index >= 0)
        {
            int lastIndex = index - 1;
            if (lastIndex < 0)
                lastIndex = roadList.Count - 1;
            // 移動道路
            roadList[index].position = roadList[lastIndex].position + new Vector3(roadDistance, 0, 0);

            initRoad(index);
        }
        else
        {
            Debug.LogError("arrivePos index is error");
            return;
        }
    }

    void initRoad(int index)
    {
        
        string roadName = roadList[index].name;
        // 清空已有障礙物
        foreach(GameObject obj in objDict[roadName])
        {
            Destroy(obj);
        }
        objDict[roadName].Clear();

        // 新增障礙物
        foreach(Transform pos in bornPosList[index])
        {
            GameObject prefab = objPrefabList[Random.Range(0, objPrefabList.Count)];
            Vector3 eulerAngle = new Vector3(0, Random.Range(0, 360), 0);
            GameObject obj = Instantiate(prefab, pos.position, Quaternion.EulerAngles(eulerAngle)) as GameObject;
            obj.tag = "Obstacle";
            objDict[roadName].Add(obj);
        }
    }

在Start()中:
    void Start () {
        foreach(Transform road in roadList)
        {
            List<GameObject> objList = new List<GameObject>();
            objDict.Add(road.name, objList);
        }
        initRoad(0);
        initRoad(1);
    }
然後開啟之前的moveController.cs,宣告變數:
    // 遊戲管理器
    public GameManager gameManager; 定義函式:
    void OnTriggerEnter(Collider other)
    {
        // 如果是抵達點
        if (other.name.Equals("ArrivePos"))
        {
            gameManager.changeRoad(other.transform);
        }
        // 如果是透明牆
        else if (other.tag.Equals("AlphaWall"))
        {
            // 沒啥事情
        }
        // 如果是障礙物
        else if(other.tag.Equals("Obstacle"))
        {

        }
    }
 呼,一大串程式碼,大家敲的累不累,什麼!你是copy過去的?太過分了!我要拿刀子了! 嗯,切換回Unity中,點選GameManager這個物體,設定其GameManager元件的值:
這裡的BornPos指的是障礙物出生點,以下圖所示為每一條道路定義一個或多個出生點,每條路的出生點用一個BornPos的空物體進行管理: 然後將出生點按其所處道路的序號一一拖入(先設定size的值,3條就設定為3) RoadList也是一樣,將道路按序號一一拖入。 這裡的ArrivePosList要注意一下,並不是直接按道路序號拖入,而是往後一位,即: road2 road3 ........ roadn road1
這樣的順序將其對應的ArrivePos拖入列表 然後將需要生成的障礙物的Prefab檔案拖入ObjPrefabList 設定道路的間隔距離(即一條道路的中心點到接下來一條道路中心點的距離 distance = road1.length / 2 + road2.length / 2 大概這麼計算) 到這一步為止,GameManager的設定基本完成。點選人物的GameObject,設定moveController,將GameManager的遊戲物件拖入到指定位置: 對了,還有一步非常重要的設定: 為人物新增Collider與RightBody,為所有障礙物和路面新增Collider(注意不要勾上Is Trigger) 然後運行遊戲。 呼,這時候沒有問題的話應該是能看到有障礙物出現了,人物走到障礙物處會被擋住,並且道路會自動拼接移動,無止境的走下去、走下去、走下去。。。 這個Demo也基本進入尾聲了,接下來,做最後的遊戲失敗判斷和讓角色趕回正常位置。 開啟GameManager.cs,宣告變數:
    public bool isEnd = false; 開啟moveController.cs 宣告變數:
    // 攝像機距離人物的距離
    public float cameraDistance;
在Update()函式中 新增以下程式碼:
// 當人物與攝像機距離小於cameraDistance時 讓其加速
        if(this.transform.position.x - cameraTransform.position.x < cameraDistance)
        {
            moveVSpeed += 0.1f;
            if (moveVSpeed > maxVSpeed)
            {
                moveVSpeed = maxVSpeed;
            }
        }
        // 超過時 讓攝像機趕上
        else if(this.transform.position.x - cameraTransform.position.x > cameraDistance)
        {
            moveVSpeed = minVSpeed;
            cameraTransform.position = new Vector3(this.transform.position.x - cameraDistance, cameraTransform.position.y, cameraTransform.position.z);
        }
        // 攝像機超過人物
        if(cameraTransform.position.x - this.transform.position.x > 0.0001f)
        {
            Debug.Log("你輸啦!!!!!!!!!!");
            gameManager.isEnd = true;
        }
定義OnGUI()函式:
void OnGUI()
    {
        if (gameManager.isEnd)
        {
            GUIStyle style = new GUIStyle();

            style.alignment = TextAnchor.MiddleCenter;
            style.fontSize = 40;
            style.normal.textColor = Color.red;
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 50, 200, 100), "你輸了~", style);

        }
    }

到這一步,Demo編寫完成。(由於現在是凌晨4點 實在太疲憊,所以本篇基本都是直接貼程式碼,如果有朋友有什麼問題的話 可以直接留言哈~)