Unity快速上手系列之番外篇:《2D橫版跑酷》

作者:四五二十
寫在前面
未嘗試做過跑酷游戲,大約相當(dāng)于沒學(xué)過游戲開發(fā)。

玩笑至此,想傳達(dá)的意思大家想必也清楚。跑酷游戲普遍非常簡單,但簡單并不代表簡陋,跑酷是最能體現(xiàn)“麻雀雖小五臟俱全”的游戲類型。在當(dāng)中開發(fā)者可以充分展示自己的設(shè)計(jì)才能,無論是奇思妙想的關(guān)卡設(shè)計(jì)還是靈光一現(xiàn)的功能創(chuàng)意。這樣必然能獲取到充足而高頻的正反饋——而這一切都是建立在這樣一個(gè)門檻較低的游戲體量上的。
因此對(duì)于初學(xué)者來說,沒有比它更合適練手的類型了,這也是我專門寫這樣一篇番外篇的目的。
閑話少敘,直接開干。
創(chuàng)建地形
地面
標(biāo)配的地面和天花板自不必說,跟著自己的喜好設(shè)計(jì)即可。
而移動(dòng)地形則能稍微增加一點(diǎn)變數(shù)。以垂直升降為例,在自己設(shè)定的移動(dòng)范圍內(nèi),地形最低位置就向上移動(dòng),反之亦然。把腳本掛載到對(duì)應(yīng)地形上即可:
void Update()
{
if(transform.position.y > 0)
{
IsChange = false;
}
if(transform.position.y < -6)
{
IsChange = true;
}
if(IsChange == false)
{
//在世界坐標(biāo)向下移動(dòng)
transform.Translate(0, -4 * Time.deltaTime, 0, Space.World);
}
else
{
//在世界坐標(biāo)向上移動(dòng)
transform.Translate(0, 4 * Time.deltaTime, 0, Space.World);
}
}
水平移動(dòng)同理。
背景
以下圖為例,一張普通的磚墻背景:

為了展示出向前跑的效果,需要讓圖片每一幀向后移動(dòng)。
transform.position = new Vector3(transform.position.x-0.03f, transform.position.y, transform.position.z);
順便說一句,在Unity2018版本中可以使用紋理偏移的方法來達(dá)到類似的效果,這樣就不用單獨(dú)移動(dòng)圖片了:
public float scrollSpeed = 0.5f;
public Renderer = rend;
//Use this for initialization
void Start()
{
rend = GetComponent<Renderer>();
}
//Update is called once per frame
void Update()
{
float offset = Time.time * scrollSpeed;
rend.material.mainTextureOffset = new Vector2(offset, 0); //紋理偏移
}
與之對(duì)應(yīng)的就不再是創(chuàng)建一個(gè)精靈,而是創(chuàng)建Quad:

角色
角色圖片
把圖片素材放在精靈上,并加上剛體和碰撞盒子:

強(qiáng)調(diào)一下,網(wǎng)上的圖片素材一定只能供自己學(xué)習(xí),一定不能用作商業(yè)用途!
角色動(dòng)作
1、移動(dòng)
我們給角色一個(gè)速度,使得游戲一開始角色就向右移動(dòng)。
myRigidbody.transform.Translate(speed * Time.deltaTime, 0, 0);
2、跳躍
按下空格鍵給它一個(gè)向上的力,使得角色可以向上跳躍。有兩個(gè)判斷:只有當(dāng)檢測到玩家在地面上,或者在天花板上時(shí),才能跳躍。在空中不能跳躍。當(dāng)然,通過稍微的邏輯修改,還可以讓角色實(shí)現(xiàn)二段跳功能。
//按下空格鍵可以使方塊跳躍
if (Input.GetKeyDown(KeyCode.Space))
{
if (Physics2D.Raycast(transform.position, Vector2.down,hight, LayerMask.GetMask("ground")))
{
myRigidbody.AddForce(Vector3.up * upspeed, ForceMode2D.Impulse); //給它一個(gè)向上的力
}
if (Physics2D.Raycast(transform.position, Vector2.down, hight, LayerMask.GetMask("ceiling")))
{
myRigidbody.AddForce(Vector3.up * upspeed, ForceMode2D.Impulse); //給它一個(gè)向上的力
}
}
3、放縮
在這個(gè)示例里,我給予了玩家一個(gè)放縮的特性,通過靈活地改變體型的大小來通過障礙物。根據(jù)體型不同,跳躍能力、奔跑速度會(huì)發(fā)生相應(yīng)的變化。
if (transform.localScale.x>0.3)
{
//按下A鍵可以縮小方塊
if (Input.GetKey(KeyCode.A))
{
transform.localScale = new Vector3(transform.localScale.x - 0.01f, transform.localScale.y - 0.01f, transform.localScale.z - 0.01f);
speed = speed + 0.05f;
upspeed = upspeed + 0.05f;
}
}
if(transform.localScale.x <=1)
{
//按下D鍵可以變大方塊
if (Input.GetKey(KeyCode.D))
{
//檢測到方塊上面是天花板則不能變大
if (Physics2D.Raycast(transform.position, Vector2.up, 0.5f*hight, LayerMask.GetMask("ceiling")))
{
}
else
{
transform.localScale = new Vector3(transform.localScale.x + 0.01f, transform.localScale.y + 0.01f, transform.localScale.z + 0.01f);
speed = speed - 0.05f;
upspeed = upspeed - 0.05f;
}
}
效果如圖:

攝像機(jī)
這一部分的處理較為簡單,只需要讓鏡頭跟隨玩家移動(dòng)即可。
//讓相機(jī)跟著玩家移動(dòng)
transform.position = new Vector3(player.transform.position.x + 5, 0 , transform.position.z);
金幣
金幣是場景中散落的收集要素,就跟《超級(jí)馬里奧》中的金幣一樣。
圖片資源隨便用一個(gè):

我們讓金幣帶上一個(gè)旋轉(zhuǎn)的樣式:
transform.Rotate(Vector3.up * 4, Space.World); //原地旋轉(zhuǎn)
效果如下圖所示:

根據(jù)收集邏輯,場景中的金幣一旦被玩家所觸碰到,就會(huì)自動(dòng)移動(dòng)至UI中金幣數(shù)量位置。
金幣的移動(dòng)函數(shù)如下:
//金幣向目標(biāo)點(diǎn)移動(dòng)
public void CoinMove()
{
//UI坐標(biāo)轉(zhuǎn)換成世界坐標(biāo)
UIcoin = Camera.main.ScreenToWorldPoint(AllCoin.transform.position);
//當(dāng)前物體向這某一個(gè)物體移動(dòng)
transform.position = Vector3.MoveTowards(transform.position, UIcoin + Vector3.forward, 25 * Time.deltaTime);
//兩個(gè)坐標(biāo)相減是方向,用sqrMagnitude獲取方向的距離
if ((transform.position - (UIcoin + Vector3.forward)).sqrMagnitude < 0.1f)
{
player.GetComponent<PlayerMove>().money++;
player.GetComponent<PlayerMove>().SetMoney();
Destroy(gameObject);
}
}
當(dāng)然,要觸發(fā)這個(gè)移動(dòng)函數(shù),還需要增加一個(gè)檢測碰撞的過程,在過程中調(diào)用移動(dòng)函數(shù)。不要忘了給金幣添加碰撞盒子。
void Update ()
{
transform.Rotate(Vector3.up * 4, Space.World); //原地旋轉(zhuǎn)
if (IsRun == true)
{
CoinMove();
}
}
public void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player"))
{
IsRun = true;
}
}
在角色腳本中獲取該數(shù)量text。
void Start ()
{
myRigidbody = this.GetComponent<Rigidbody2D>();
money = 0;
SetMoney();
Hp.gameObject.SetActive(false);
}
public void SetMoney() //改變金幣數(shù)量
{
MoneyText.text = money.ToString();
}
玩家碰到金幣時(shí),金幣在移動(dòng)至相應(yīng)位置的同時(shí),數(shù)量+1,并銷毀自己。
player.GetComponent<PlayerMove>().money++;
player.GetComponent<PlayerMove>().SetMoney();
Destroy(gameObject);
機(jī)關(guān)
角色一旦碰到場景中的各種機(jī)關(guān),就宣告game over。機(jī)關(guān)分為固定機(jī)關(guān)和移動(dòng)機(jī)關(guān)兩種。
移動(dòng)機(jī)關(guān)當(dāng)檢測到角色靠近時(shí),就自動(dòng)向下掉落,碰到地板后停止運(yùn)動(dòng),在掉落過程中如果碰到玩家則game over。
void Update ()
{
if (isRun==true)
{
//玩家靠近絕對(duì)值距離小于4時(shí)火焰下落
if (Mathf.Abs(player.transform.position.x-transform.position.x)<4)
{
transform.Translate(0, -9 * Time.deltaTime, 0, Space.World);
}
}
}
public void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("ground"))
{
isRun = false;
}
磁鐵功能
很多跑酷游戲里都會(huì)有“磁鐵”這樣的powerup道具,吃到后,會(huì)在一定時(shí)間內(nèi)讓場景中的金幣自動(dòng)被吸附到玩家身上。
碰撞盒
要實(shí)現(xiàn)這樣的功能,我們首先需要給磁鐵加一個(gè)可任意調(diào)整的碰撞盒。

隨意改變一下形狀,能碰到前方的金幣即可。

拾取金幣
public void OnTriggerEnter2D(Collider2D coll)
{
if(coll.gameObject.CompareTag("coin"))
{
coll.GetComponent<Coin>().IsRun = true;
}
}
碰撞盒碰到金幣的盒子時(shí),把金幣的IsRun改為True,此時(shí)金幣就會(huì)自動(dòng)跑向目標(biāo)點(diǎn),達(dá)到磁鐵吸金的效果。
跟隨角色
角色碰到磁鐵后,為了讓吸金效果跟隨角色,需要讓磁鐵本身也跟隨角色一起移動(dòng)。
if (coll.gameObject.CompareTag("Player"))
{
IsFollow = true;
}
void Update ()
{
if (IsFollow == true)
{
//跟著玩家移動(dòng)
transform.position = new Vector3(player.transform.position.x - 0.5f, player.transform.position.y + 1, transform.position.z);
}
}
時(shí)間條
我們首先創(chuàng)建兩張圖片,分別為白色和綠色的,綠色圖片在白色圖片之上,兩張大小一樣。

找到面板上的綠色時(shí)間條,然后以幀為單位逐漸縮小尺寸。在減少為0的時(shí)候銷毀磁鐵和時(shí)間條,意味著磁鐵失效。
//找到玩家
public GameObject player;
//找到血量條
public GameObject MagnetHp;
//找到HP圖片
public Image HpPhoto;
//HP數(shù)值
public float Hp;
//是否移動(dòng)
public bool IsFollow = false;
void Update ()
{
if (IsFollow==true)
{
//改變Rotation
transform.eulerAngles = new Vector3(0,0,0);
transform.localScale = new Vector3(0.17f, 0.17f, transform.localScale.z);
transform.position = new Vector3(player.transform.position.x-0.5f, player.transform.position.y+1, transform.position.z);
player.GetComponent<PlayerMove>().Hp.gameObject.SetActive(true);
Hp = Hp - 0.002f;
if(Hp<0)
{
Hp = 0;
Destroy(transform.parent.gameObject); //銷毀磁鐵
Destroy(MagnetHp); //銷毀血條
}
//實(shí)時(shí)更新面板上的HP數(shù)值
HpPhoto.rectTransform.localScale = new Vector3(Hp, HpPhoto.rectTransform.localScale.y, HpPhoto.rectTransform.localScale.z);
}
}
切換屬性
這是在本篇示例中另外添加的一個(gè)特性:屬性切換。
場景中有時(shí)會(huì)出現(xiàn)類似于水墻的障礙,玩家需要切換角色的屬性后才能通過。
水屬性圖片:

當(dāng)角色觸碰到上圖時(shí),切換角色外形為如下:

player.GetComponent<SpriteRenderer>().sprite = tupian;
當(dāng)檢測到玩家屬性已切換時(shí),將水墻的碰撞盒變?yōu)榭纱┰郊纯伞?/strong>
rainbowwall.GetComponent<BoxCollider2D>().isTrigger = true;
游戲結(jié)束
goal標(biāo)志
當(dāng)玩家到達(dá)goal標(biāo)志時(shí),判定玩家闖關(guān)成功。

反之,玩家碰到機(jī)關(guān)、摔下懸崖或是未能穿過水墻時(shí),判定游戲失敗。

That's it。實(shí)際運(yùn)行的效果如下:

完整的工程如下:https://github.com/wushupei/RunGame
這樣一個(gè)簡單的游戲卻作用多多,不光可以應(yīng)對(duì)類似于課程設(shè)計(jì)之類的學(xué)習(xí)任務(wù),還足以作為開發(fā)者的入門第一次實(shí)踐。希望能藉由此文,讓盡量多的盆友們踏上游戲開發(fā)令人激動(dòng)的旅程中去。
最后想系統(tǒng)學(xué)習(xí)游戲開發(fā)的童鞋,歡迎訪問 http://levelpp.com/
游戲開發(fā)攪基QQ群:869551769
微信公眾號(hào):皮皮關(guān)