1. 程式人生 > >3d學習筆記(二)——遊戲物件的運動

3d學習筆記(二)——遊戲物件的運動

簡答題

簡答並用程式驗證

  • 遊戲物件運動的本質是什麼?
  • 請用三種方法以上方法,實現物體的拋物線運動。(如,修改Transform屬性,使用向量Vector3的方法…)
  • 寫一個程式,實現一個完整的太陽系, 其他星球圍繞太陽的轉速必須不一樣,且不在一個法平面上。
遊戲物件運動的本質
  • 遊戲物件運動的本質,其實是遊戲物件跟隨每一幀的變化,空間地變化。這裡的空間變化包括了遊戲物件的transform屬性中的position跟rotation兩個屬性。一個是絕對或者相對位置的改變,一個是所處位置的角度的旋轉變化。
實現物體的拋物線運動
  • 第一種方法是利用position的改變來實現拋物線運動,水平方向的移動是勻速進行,豎直方向是有一定的加速度變化的,按照物理的規律來看,兩個方向的運動向量相加即可實現拋物線運動,程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move1 : MonoBehaviour {

    public float speed = 1;
	// Use this for initialization
	void Start () {
        Debug.Log("start!");
	}
	
	// Update is called once per frame
	void Update () {

        this.transform.position += Vector3.down * Time.deltaTime * (speed/10);
        this.transform.position += Vector3.right * Time.deltaTime * 5;
        speed++;
	}
}
  • 第二種方法是直接宣告建立一個Vector3變數,同時定義該變數的值,也是豎直方向上是一個均勻增加的數值,水平方向是一個保持不變的數值,然後將遊戲物件原本的position屬性與該向量相加即可實現拋物線運動,程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move2 : MonoBehaviour {

    public float speed = 1;
	// Use this for initialization
	void Start () {
        Debug.Log("start!");
	}
	
	// Update is called once per frame
	void Update () {

        Vector3 change = new Vector3( Time.deltaTime*5, -Time.deltaTime*(speed/10), 0);
        ;
        this.transform.position += change;
        speed++;
	}
}
  • 第三種方法其實與第二種方法類似,區別在於第二種方法直接是利用Vector3的向量相加,而第三種方法則是利用transform中的translate函式來進行改變position,傳入引數也需要是一個Vector3向量,才可以實現position的改變,程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class move3 : MonoBehaviour {

    public float speed = 1;
	// Use this for initialization
	void Start () {
        Debug.Log("start!");
	}
	
	// Update is called once per frame
	void Update () {

        Vector3 change = new Vector3(Time.deltaTime * 5, -Time.deltaTime * (speed / 10), 0);

        transform.Translate(change);
        speed++;
    }
}
完整太陽系的設計
  • 首先是遊戲物件的安排,如下圖所示:


  • 其次按照一定的大小以及距離,將八大行星的位置大小依次安排,修改它們的transform屬性即可,同時將圖片素材直接拉到該物件上面即可新增物件

  • 將八大行星的運動寫進同個腳本里面,掛在Sun物件上即可。首先利用GameObject.Find函式找到該遊戲物件,再通過其transform函式中的RotationAround跟Rotation函式分別來實現公轉跟自轉。
  • RotationAround需要三個引數,第一個引數是旋轉的中心,這個八大行星都是以太陽中心為旋轉中心;第二個引數是旋轉軸,也就是一個Vector3變數,通過改變旋轉軸的屬性。
  • Rotation函式則可以只需要一個引數,即旋轉時的方向及速度,用Vector3.up代表該物體是沿著自己的Y軸進行旋轉的,後面的引數則是代表旋轉的角速度,因此即可實現自轉。
  • 掛在sun上的行為程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Sun : MonoBehaviour {

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        GameObject.Find("Earth").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 30 * Time.deltaTime);
        GameObject.Find("Earth").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Mercury").transform.RotateAround(Vector3.zero, new Vector3(1, 1, 0), 25 * Time.deltaTime);
        GameObject.Find("Mercury").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Venus").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 1), 20 * Time.deltaTime);
        GameObject.Find("Venus").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Mars").transform.RotateAround(Vector3.zero, new Vector3(2, 1, 0), 45 * Time.deltaTime);
        GameObject.Find("Mars").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Jupiter").transform.RotateAround(Vector3.zero, new Vector3(1, 2, 0), 35 * Time.deltaTime);
        GameObject.Find("Jupiter").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Saturn").transform.RotateAround(Vector3.zero, new Vector3(0, 1, 2), 40 * Time.deltaTime);
        GameObject.Find("Saturn").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Uranus").transform.RotateAround(Vector3.zero, new Vector3(0, 2, 1), 45 * Time.deltaTime);
        GameObject.Find("Uranus").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
        GameObject.Find("Neptune").transform.RotateAround(Vector3.zero, new Vector3(1, 1, 1), 50 * Time.deltaTime);
        GameObject.Find("Neptune").transform.Rotate(Vector3.up * Time.deltaTime * 10000);
    }
}
  • 旋轉運動過程中的效果圖如下:

月球圍繞地球旋轉的實現
  • 假如地球沒有設定成自轉的話,可以將月球設定成地球的子物件,再通過RotationAround中的旋轉點改成地球的位置,即可實現月球圍繞著地球轉(該部分程式碼不給出)
  • 但是一般來說地球是會自轉的,這勢必帶來地球自轉會影響到月球圍繞著地球轉的問題,那如何解決這個問題呢?
  • 首先我們另行建立一個空物件,將該空物件的位置設定成地球的位置,同時旋轉的條件也跟地球一樣,就可以建立一個不顯示不自轉的地球的影子,再將月球設定成該空物件的子物件,即可實現月球圍繞著地球轉的操作了,具體的程式碼實現如下:

空物件的程式碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InitBeh : MonoBehaviour {

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {

        this.transform.RotateAround(Vector3.zero, new Vector3(0, 1, 0), 30 * Time.deltaTime);
    }
}

掛在月球的指令碼程式碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class moon : MonoBehaviour {

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        
        Vector3 position = this.transform.parent.position;
        this.transform.RotateAround(position, Vector3.up, 360 * Time.deltaTime); 
	}
}
  • 附上八大行星的中英文翻譯,方便檢視
中文英文
太陽Sun
水星Mercury
金星Venus
地球Earth
火星Mars
木星Jupiter
土星Saturn
天王星Uranus
海王星Neptune

程式設計實踐

  • 閱讀以下游戲指令碼

Priests and Devils

Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

程式需要滿足的要求:

  • play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
  • 列出遊戲中提及的事物(Objects)
  • 用表格列出玩家動作表(規則表),注意,動作越少越好
  • 請將遊戲中物件做成預製
  • 在 GenGameObjects 中建立 長方形、正方形、球 及其色彩代表遊戲中的物件。
  • 使用 C# 集合型別 有效組織物件
  • 整個遊戲僅 主攝像機 和 一個 Empty 物件, 其他物件必須程式碼動態生成!!! 。 整個遊戲不許出現 Find 遊戲物件, SendMessage 這類突破程式結構的 通訊耦合 語句。 違背本條準則,不給分
  • 請使用課件架構圖程式設計,不接受非 MVC 結構程式
  • 注意細節,例如:船未靠岸,牧師與魔鬼上下船運動中,均不能接受使用者事件!
問答題
  • 遊戲中的物件有:牧師、魔鬼、小船、開始岸、結束岸
  • 玩家動作表(規則表)如下:
事件條件
開船船在開始岸,船在結束岸,船上至少有一個人
船的左方下船船靠岸且船左方有人
船的右方下船船靠岸且船右方有人
開始岸的牧師上船船在開始岸,船有空位,開始岸有牧師
開始岸的魔鬼上船船在開始岸,船有空位,開始岸有魔鬼
結束岸的牧師上船船在結束岸,船有空位,結束岸有牧師
結束岸的魔鬼上船船在結束岸,船有空位,結束岸有魔鬼

實踐題

  • 題目中要求必須採用標準的mvc結構,同時在初始的場景當中,只能有攝像機跟空物件兩個物件,其他的必須時預製
  • 首先是完成相關的預製,一共是四個,分別是代表魔鬼的球體,代表牧師的正方體,代表小船的小型褐色長方體,代表河岸的綠色大型長方體。這四個預製統一放在Resources中的Prefabs資料夾中
  • 之後根據mvc結構,分別建立三個指令碼Model,VIew,Controller。View指令碼掛在空物件下,其餘兩個指令碼都掛在攝像頭下

具體的遊戲物件在執行前後的對比:



檔案管理介面如下:


遊戲初始的介面如下:


遊戲獲勝的介面如下:


  • 遊戲程式碼如下:
  • Controller檔案:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using mygame;

namespace mygame
{
    public enum State { Start, SE_move, ES_move, End, Win, Lose };

    public interface UserAction {
        void priestSOnB();
        void priestEOnB();
        void devilSOnB();
        void devilEOnB();
        void moveShip();
        void offShipL();
        void offShipR();
        void reset();
    }

    public class SSDirector : System.Object, UserAction
    {
        private static SSDirector _instance;

        public Controller currentScenceController;
        public State state = State.Start;
        private Model game_obj;

        public static SSDirector GetInstance()
        {
            if(_instance == null)
            {
                _instance = new SSDirector();
            }
            return _instance;
        }

        public Model getModel()
        {
            return game_obj;
        }
        
        internal void setModel(Model someone)
        {
            if(game_obj == null)
            {
                game_obj = someone;
            }
        }

        public void priestSOnB()
        {
            game_obj.priS();
        }
        public void priestEOnB()
        {
            game_obj.priE();
        }
        public void devilSOnB()
        {
            game_obj.delS();
        }
        public void devilEOnB()
        {
            game_obj.delE();
        }
        public void moveShip()
        {
            game_obj.moveShip();
        }
        public void offShipL()
        {
            game_obj.getOffTheShip(0);
        }
        public void offShipR()
        {
            game_obj.getOffTheShip(1);
        }
        public void reset()
        {
            //暫時不寫,待會回來進行定義
            //_instance = null;
            state = State.Start;
            game_obj.Reset_all();

        }
}

        
}

public class Controller : MonoBehaviour {

    // Use this for initialization

    void Start()
    {
        SSDirector one = SSDirector.GetInstance();
    }

}
  • Model檔案的程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using mygame;

public class Model : MonoBehaviour {

    Stack<GameObject> start_priests = new Stack<GameObject>();
    Stack<GameObject> end_priests = new Stack<GameObject>();
    Stack<GameObject> start_devils = new Stack<GameObject>();
    Stack<GameObject> end_devils = new Stack<GameObject>();

    GameObject[] ship = new GameObject[2];
    GameObject ship_obj;
    public float speed = 100f;

    SSDirector one;

    //以上的向量都是遊戲物件的初始位置,以後移動後的位置
    Vector3 shipStartPos = new Vector3(-2f, -1f, 0);
    Vector3 shipEndPos = new Vector3(6f, -1f, 0);
    Vector3 bankStartPos = new Vector3(-10f, -5f, 0);
    Vector3 bankEndPos = new Vector3(14f, -5f, 0);

    float gap = 1.5f;
    Vector3 priestsStartPos = new Vector3(-10.5f, 0.5f, 0);
    Vector3 priestsEndPos = new Vector3(13.5f, 0.5f, 0);
    Vector3 devilsStartPos = new Vector3(-6f, 0.5f, 0);
    Vector3 devilsEndPos = new Vector3(18f, 0.5f, 0);
    

    // Use this for initialization
    void Start () {
		one = SSDirector.GetInstance();
        one.setModel(this);
        loadSrc();
	}
	
	// Update is called once per frame
	void Update () {
        setposition(start_priests, priestsStartPos);
        setposition(end_priests, priestsEndPos);
        setposition(start_devils, devilsStartPos);
        setposition(end_devils, devilsEndPos);

        if(one.state == State.SE_move)
        {
            ship_obj.transform.position = Vector3.MoveTowards(ship_obj.transform.position, shipEndPos, Time.deltaTime * speed);
            if(ship_obj.transform.position == shipEndPos)
            {
                one.state = State.End;
            }
        }
        else if(one.state == State.ES_move)
        {
            ship_obj.transform.position = Vector3.MoveTowards(ship_obj.transform.position, shipStartPos, Time.deltaTime * speed);
            if(ship_obj.transform.position == shipStartPos)
            {
                one.state = State.Start;
            }
        }
        else
        {
            check();
        }
	}

    //載入遊戲物件
    void loadSrc()
    {   
        //bank
        Instantiate(Resources.Load("Prefabs/bank"), bankStartPos, Quaternion.identity);
        Instantiate(Resources.Load("Prefabs/bank"), bankEndPos, Quaternion.identity);

        //ship
        ship_obj = Instantiate(Resources.Load("Prefabs/ship"), shipStartPos, Quaternion.identity) as GameObject;
        
        //prisets and devils
        for(int i = 0; i < 3; i++)
        {
            start_priests.Push(Instantiate(Resources.Load("Prefabs/Priest")) as GameObject);
            start_devils.Push(Instantiate(Resources.Load("Prefabs/Devil")) as GameObject);
        }
    }

    void setposition(Stack<GameObject> aaa, Vector3 pos)
    {
        GameObject[] temp = aaa.ToArray();
        for(int i = 0; i < aaa.Count; i++)
        {
            temp[i].transform.position = pos + new Vector3(-gap * i, 0, 0);
        }
    }

    //上船的操作
    void getOnTheShip(GameObject obj)
    {
        obj.transform.parent = ship_obj.transform;
        if(shipNum() != 0)
        {
            if(ship[0] == null)
            {
                ship[0] = obj;
                obj.transform.localPosition = new Vector3(-0.4f, 1, 0);
            }
            else
            {
                ship[1] = obj;
                obj.transform.localPosition = new Vector3(0.4f, 1, 0);
            }
        }
    }
    //判斷船上是否有空位,以及上船的物件將坐再哪個位置上
    int shipNum()
    {
        int num = 0;
        for(int i = 0; i < 2; i++)
        {
            if(ship[i] == null)
            {
                num++;
            }
        }
        return num;
    }

    //船移動時的操作
    public void moveShip()
    {
        if(shipNum() != 2)
        {
            if(one.state == State.Start)
            {
                one.state = State.SE_move;
            }
            else if(one.state == State.End)
            {
                one.state = State.ES_move;
            }
        }
    }

    //下船的操作
    public void getOffTheShip(int side)
    {
        if(ship[side] != null)
        {
            ship[side].transform.parent = null;
            if(one.state == State.Start)
            {
                if(ship[side].tag == "Priest")
                {
                    start_priests.Push(ship[side]);
                }
                else
                {
                    start_devils.Push(ship[side]);
                }
            }
            else if(one.state == State.End)
            {
                if(ship[side].tag == "Priest")
                {
                    end_priests.Push(ship[side]);
                }
                else
                {
                    end_devils.Push(ship[side]);
                }
            }
            ship[side] = null;
        }
    }

    void check()
    {   
        if(end_devils.Count == 3 && end_priests.Count == 3)
        {
            one.state = State.Win;
            return;
        }

        int bp = 0, bd = 0;
        for(int i = 0; i < 2; i++)
        {
            if(ship[i] != null && ship[i].tag == "Priest")
            {
                bp++;
            }
            else if(ship[i] != null && ship[i].tag == "Devil")
            {
                bd++;
            }
        }

        int sp = 0, sd = 0, ep = 0, ed = 0;
        if(one.state == State.Start)
        {
            sp = start_priests.Count + bp;
            ep = end_priests.Count;
            sd = start_devils.Count + bd;
            ed = end_devils.Count;
        }
        else if(one.state == State.End)
        {
            sp = start_priests.Count;
            ep = end_priests.Count + bp;
            sd = start_devils.Count;
            ed = end_devils.Count + bd;
        }

        if((sp != 0 && sp < sd) || (ep != 0 && ep < ed))
        {
            one.state = State.Lose;
        }
    }

    //定義遊戲物件從岸上到船上的變化
    public void priS()
    {
        if(start_priests.Count != 0 && shipNum() != 0 && one.state == State.Start)
        {
            getOnTheShip(start_priests.Pop());
        }
    }
    public void priE()
    {
        if(end_priests.Count != 0 && shipNum() != 0 && one.state == State.End)
        {
            getOnTheShip(end_priests.Pop());
        }
    }
    public void delS()
    {
        if(start_devils.Count != 0 && shipNum() != 0 && one.state == State.Start)
        {
            getOnTheShip(start_devils.Pop());
        }
    }
    public void delE()
    {
        if(end_devils.Count != 0 && shipNum() != 0 && one.state == State.End)
        {
            getOnTheShip(end_devils.Pop());
        }
    }

    //重置遊戲
    public void Reset_all()
    {
        ship_obj.transform.position = shipStartPos;

        int num1 = end_devils.Count, num2 = end_priests.Count;
        for(int i = 0; i < num1; i++)
        {
            Debug.Log(i);
            start_devils.Push(end_devils.Pop());
        }

        for (int i = 0; i < num2; i++)
        {
            start_priests.Push(end_priests.Pop());
        }

        

        getOffTheShip(0);
        getOffTheShip(1);

    }
}
  • View檔案的程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using mygame;

public class View : MonoBehaviour {

    SSDirector one;
    UserAction action;

	// Use this for initialization
	void Start () {
        one = SSDirector.GetInstance();
        action = SSDirector.GetInstance() as UserAction;
	}

    private void OnGUI()
    {
        GUI.skin.label.fontSize = 30;
        if (one.state == State.Win)
        {
            if(GUI.Button(new Rect(500, 200, 300, 100), "WIN\n(click here to reset)"))
            {
                action.reset();
            }
        }
        if (one.state == State.Lose)
        {
            if(GUI.Button(new Rect(500, 200, 300, 100), "LOSE\n(click here to reset)"))
            {
                action.reset();
            }
        }

        if(GUI.Button(new Rect(600, 80, 100, 50), "GO"))
        {
            action.moveShip();
        }
        if (GUI.Button(new Rect(500, 400, 75, 50), "OFF"))
        {
            action.offShipL();
        }
        if (GUI.Button(new Rect(700, 400, 75, 50), "OFF"))
        {
            action.offShipR();
        }
        if (GUI.Button(new Rect(220, 200, 75, 50), "ON"))
        {
            action.priestSOnB();
        }
        if (GUI.Button(new Rect(350, 200, 75, 50), "ON"))
        {
            action.devilSOnB();
        }
        if (GUI.Button(new Rect(980, 200, 75, 50), "ON"))
        {
            action.devilEOnB();
        }
        if (GUI.Button(new Rect(850, 200, 75, 50), "ON"))
        {
            action.priestEOnB();
        }
    }
}

以上都是個人在學習3d遊戲設計中的一些收穫跟感想,如果有想法,隨時歡迎跟博主進行討論