博主前些日子和別的學院的同窗共同製做了一款小遊戲Jumper,如今把其開源出來,但願能夠給在Unity初學道路上的同窗一些幫助 :)markdown
咱們首先看一下游戲的最終截圖,效果完成度不高,可是其中代碼的基本邏輯是比較齊全的。dom
咱們看到的這隻小雞就是咱們的主角了!背景是一個大樓,右上角有一個溫度計,會隨着時間上升。咱們要跳上各類擋板,儘量地在那些窗戶上安裝空調,(不然同窗們會暴動的!)。提示,右下角有一個藥丸,吃了能夠大幅度提高跳躍能力,左下角是一個敵人的模型,別被黏黏的蝸牛觸碰到哦!ide
私覺得運用Unity,最重要就是場景的設計和遊戲主角的邏輯。只要這兩點明確下來,那麼附屬的物品等基本上就是輕車熟路了。包括閱讀別人的代碼,若是想從一個腳本中分辨出其十多個變量的意義,那麼無異於捨本逐末。只要理解了其最最基本的主角邏輯和場景邏輯,那麼再在其基礎上加上」藥物「、」物品「、」屬性「、」分數「這些東西,就會事半功倍。函數
即時是一個最簡單的遊戲,
讓咱們看一下主角的行爲,Character對象最後那個最最核心的Update()
爲了讓代碼更清晰易懂,我只保留了其中的一部分。ui
void Update()
{
//Vector3 dir = Vector3.zero;
//dir.x = -Input.acceleration.x * 2;
//dir *= Time.deltaTime;
if (Input.GetKey(KeyCode.LeftArrow))
{
if (!left) {/*主角轉向*/
transform.rotation = new Quaternion(0, 180, 0, 1);
left = true;
}
/*向左移動*/
transform.Translate(Vector3.right * -Time.deltaTime * moveSpeed, Space.World);
}
if (Input.GetKey(KeyCode.RightArrow))
{
/*和向左移動的邏輯相似*/
if (left) {
transform.rotation = new Quaternion(0, 0, 0, 1);
left = false;
}
transform.Translate(Vector3.right * Time.deltaTime * moveSpeed, Space.World);
}
/*跳躍*/
if (Input.GetKey(KeyCode.LeftControl))
{
Jump();
}
/*安裝空調*/
if (Input.GetKey(KeyCode.LeftShift))
{
OnWorking();
}
有些人喜歡將相似於Jump和OnWorking()這樣的函數直接在Update()中完成,我以爲這樣作很是不穩當。類中函數調用的開銷很是低,並且更有利於之後的重組、修改。並且總體邏輯看起來一目瞭然。spa
另外一個比較重要的和主角有關的邏輯是死亡斷定,我將這一部分寫入到了另外一個對象Score當中(它保有一個Character的引用)。由於主角的死亡緣由可能不光是由於」跳到釘子上面「,還有多是由於」超過了時間「等這樣的全局因素影響。設計
void Update()
{
if (player.transform.position.y > lastY)
{
score = (int)(player.transform.position.y * 200);
lastY = player.transform.position.y;
}
if (!isDead)
{
gameObject.GetComponent<GUIText>().text = "Score: " + score;
}
if (isDead && !isSet)
{
audioLose.Play();
int highscore = PlayerPrefs.GetInt("highscore");
if (highscore < score)
{
PlayerPrefs.SetInt("highscore", score);
}
GetComponent<GUIText>().anchor = TextAnchor.MiddleCenter;
transform.position = new Vector3(0.5f, 0.5f, 0);
gameObject.GetComponent<GUIText>().text = "You lost\nYour Score: " + score + "\nYour Highscore: " + highscore;
isSet = true;
player.GetComponent<Collider>().enabled = false;
}
}
另外一大要素場景的邏輯稍顯複雜一些。咱們除了背景固定外,各類障礙、跳板的位置是隨機的。並且種類不少,有普通板(x),破碎的板子(b),帶釘子的板子(s),須要安裝空調的窗戶(m)。
我寫了一個類來專門負責它們的安裝工做,它將從一個txt(相似於地圖)中讀取幾個預設,而後返回一個ArrayList來存儲相似於‘x’‘x’‘x’‘b’的字符。code
private ArrayList readPattern (string path)
{
TextAsset txt = (TextAsset)Resources.Load (path, typeof(TextAsset));
string content = txt.text;
ArrayList patternList = new ArrayList ();
string[] lines = content.Split ("\n" [0]);
char[,] singlePattern = new char[,] {
{'x','x','x','x','x'},
{'x','x','x','x','x'},
{'x','x','x','x','x'}
};
int lineCounter = 0;
foreach (string line in lines) {
if (lineCounter < 3) {
for (int i = 0; i < 5; i++) {
singlePattern [lineCounter, i] = line.ToCharArray () [i];
}
lineCounter++;
} else if (lineCounter == 3) {
patternList.Add (singlePattern);
singlePattern = new char[,] {
{'x','x','x','x','x'},
{'x','x','x','x','x'},
{'x','x','x','x','x'}
};
lineCounter = 0;
}
}
return patternList;
}
咱們已經獲得了隨機的地圖,下一步是有方法來調用他。
而且在合適的位置和合適的時機(在離主角足夠近時)安裝上這些擋板。orm
void Spawn ()
{
if (player.GetComponent<Rigidbody>().transform.position.y > 42) {
return;
}
float staticLastSpawnY = lastSpawnY;
while (j <= staticLastSpawnY) {
char[,] tempPattern = (char[,])easySpawnPattern [Random.Range (0, easySpawnPattern.Count)];
if (player.GetComponent<Rigidbody>().transform.position.y < 50) {
tempPattern = (char[,])easySpawnPattern [Random.Range (0, easySpawnPattern.Count)];
}
if (player.GetComponent<Rigidbody>().transform.position.y > 50 && player.GetComponent<Rigidbody>().transform.position.y < 100) {
tempPattern = (char[,])mediumSpawnPattern [Random.Range (0, mediumSpawnPattern.Count)];
}
if (player.GetComponent<Rigidbody>().transform.position.y > 100) {
tempPattern = (char[,])hardSpawnPattern [Random.Range (0, hardSpawnPattern.Count)];
}
for (int k = 2; k >= 0; k--) {
j += 5f;
for (int i = 0; i < 5; i++) {
if (tempPattern [k, i].ToString () == "-") {
Instantiate(Resources.Load("Normals"), new Vector3(getWorldCoordinates(i), Random.Range(0, 0.25f) + j, 0), Quaternion.identity);
}
if (tempPattern [k, i].ToString () == "s") {
Instantiate (Resources.Load ("Spikes"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j, 0), Quaternion.identity);
}
if (tempPattern [k,i].ToString () == "m") {
//這裏須要注意的是,因爲這個座標物體沒有正確的原點。因此在z軸上進行了偏移
Instantiate (Resources.Load ("Missions"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j,-0.3f), Quaternion.identity);
}
if (tempPattern [k,i].ToString () == "b") {
Instantiate (Resources.Load ("Brokens"), new Vector3 (getWorldCoordinates (i), Random.Range (0, 0.25f) + j, 0), Quaternion.identity);
}
}
}
Clouds (j, getWorldCoordinates (Random.Range (0, 5)) + Random.Range (-1, 1));
Award (j, getWorldCoordinates (Random.Range (0, 5)) + Random.Range (-1, 1));
}
lastSpawnY += 10;
}
這個源代碼中還有幾個部分,例如【板子的種類】【藥物】【主角屬性的變化】【計時操做】,這幾個部分咱們先將他們和C#文件一一對應起來。再大段粘貼代碼的話讀者已經很難看下去了~協程
【板子的種類】
共有MissionTile、SpikeTile、BrokenTile它們共同繼承於Tile(普通板)。MissionTile向Score」彙報「任務完成。(若是把這一工做交給了主角Character來作,明顯是打破了單一職責原則)
【藥物】
包括了Capsule,這個遊戲目前只有一種藥物。可是藥物的效果並無在裏面體現。只說明瞭這是」哪種「藥物和 OnTriggerEnter()用來觸發人物吃藥的效果。我之因此這樣設計是由於直接讓藥物這樣的類去操縱人物的屬性不符合里氏替換原則。(簡單地說是請面對接口!)
【主角屬性的變化】【計時操做】
主角屬性的變化通常都是有時間限制的,我看到過一些寫法,是在主角Character中完成這一計時。可是徹底有更好的辦法就是協程:
(我不得再也不粘點代碼上來。。)
IEnumerator AffectTimer()
{
yield return new WaitForSeconds(Constants.CAPSULE_TIME);
//todo 修改人物的屬性到基本狀體
Debug.Log("藥效完畢");
//恢復正常狀態
GoBackNormal();
yield return null;
}
這就先告一段落了,我上一次使用Unity寫的一個空調模擬系統這裏也一併貼出來佔一下空【請忽略下圖】