目錄
人人都能寫遊戲系列(一)
本系列中,我會在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;
}
}
注意:腳本完成後,需要在檢視面板中綁定主相機和遮羞布
完成後如圖所示
現在,遊戲已經完成了,快讓我們運行吧!
支持我
您的支持,就是我創作的最大動力