1. 程式人生 > >3D程式設計遊戲2--太陽系--牧師與惡魔

3D程式設計遊戲2--太陽系--牧師與惡魔

1、簡答並用程式驗證

遊戲物件運動的本質是什麼?

遊戲物件位置和狀態的改變

請用三種方法以上方法,實現物體的拋物線運動。

第一種

以(0,0,0)為原點,分別在(0,0,0)和(2,4,0)放兩個cube作為起點和終點,模擬出y=x2的效果,方法就是利用速度控制x方向的值,然後再用x的值推出y的值。用的是Transform的方法。

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

public class Move1 : MonoBehaviour {

    public float xspeed = 1.0
f; public Transform begin; public Transform end; // Use this for initialization void Start () { } // Update is called once per frame void Update () { begin.transform.position += Vector3.right * xspeed * Time.deltaTime; begin.transform.position = Vector3.right
* begin.transform.position.x + Vector3.up * begin.transform.position.x * begin.transform.position.x; } }

第二種

用rigidbody,(2,4,0)的cube作為起點,(0,0,0)的cube作為終點,給上面的cube一個向左初速度1,那麼cube下落的路線會非常接近下面的cube。

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

public class Move2 : MonoBehaviour
{
    public
Rigidbody begin; // Use this for initialization void Start() { //begin = GetComponent<Rigidbody>(); begin.velocity = new Vector3(-1, 0, 0); } // Update is called once per frame void Update() { } }

第三種

用Transform.Translate方法,(2,4,0)為起點,給向左的一個固定速度V0,由於向下的速度要為at,所以設定一個變數t,每幀增加0.3,作為一個正比例函式,兩個方向上合成路線就是拋物線。

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

public class Move3 : MonoBehaviour {

    public Transform begin;
    public float t = 1.0f;
    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        begin.transform.Translate(Vector3.left * Time.deltaTime);
        begin.transform.Translate(Vector3.down * Time.deltaTime * t, Space.World);
        t += 0.3f;
    }
}

寫一個程式,實現一個完整的太陽系, 其他星球圍繞太陽的轉速必須不一樣,且不在一個法平面上。

第一步 建立10個球體

加上月亮和九大行星,一共十個球體
十個球體

第二步 製作material

在網上找到相應行星的3d貼圖,然後做成一個material,製作成下面的樣子,拖到球體上
貼圖

第三步 寫指令碼

以太陽為中心,用RotateAround函式,其中的角度隨機生成,用Vector3控制,因為要保證接近Y軸,所以寫x,z的值相對較小,y的值相對較大。將指令碼拖到相應的球體中,然後將太陽,在拖到該球體的Transfrom中,設定一下speed即可。月球對地球的繫結也是直接拖拽,無需重新寫程式碼,只要把月球指令碼中的Transform換成地球就可以了。

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

public class Sun : MonoBehaviour {

    public Transform sun;
    public float speed;
    private float rx;
    private float ry;
    private float rz;
    // Use this for initialization
    void Start () {
        rx = Random.Range(10, 30);
        ry = Random.Range(40, 60);
        rz = Random.Range(10, 30);
    }

    // Update is called once per frame
    void Update () {
        Vector3 axis = new Vector3(rx, ry, rz);
        this.transform.RotateAround(sun.position, axis, speed * Time.deltaTime);
    }
}

第四步 自由視角

主要實現從各個角度觀看太陽系,用wasd或方向鍵控制上下左右,按住滑鼠右鍵,可以用滑鼠控制鏡頭。

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

public class view : MonoBehaviour {
    public float sensitivityMouse = 2f;
    public float sensitivetyKeyBoard = 0.1f;
    public float sensitivetyMouseWheel = 10f;

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {
        //按著滑鼠右鍵實現視角轉動  
        if (Input.GetMouseButton(1))
        {
            transform.Rotate(-Input.GetAxis("Mouse Y") * sensitivityMouse, Input.GetAxis("Mouse X") * sensitivityMouse, 0);
        }

        //鍵盤按鈕←/a和→/d實現視角水平移動,鍵盤按鈕↑/w和↓/s實現視角水平旋轉  
        if (Input.GetAxis("Horizontal") != 0)
        {
            transform.Translate(Input.GetAxis("Horizontal") * sensitivetyKeyBoard, 0, 0);
        }
        if (Input.GetAxis("Vertical") != 0)
        {
            transform.Translate(0, Input.GetAxis("Vertical") * sensitivetyKeyBoard, 0);
        }
    }
}

程式設計實踐

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!

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

MVC模式

所謂mvc模式,模型,檢視,控制分離。
我的檔案結構
檔案結構

  • Boat,Coast,Character,Water,作為Model
  • Click,move,作為控制
  • simplGUI,作為介面
  • other,main,分別作為介面和實現介面已經程式的入口

    類的結構

    Character類

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

public class Character {
    private GameObject character;
    public int TypeOfCharacter;//1代表魔鬼,0代表牧師
    private move Cmove;
    public int characterStatus;//0代表在岸上,1代表在船上
    Coast coast;
    Click click;

    public Character(int input)
    {
        if(input == 0)
        {
            character = Object.Instantiate(Resources.Load("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            TypeOfCharacter = 0;
        }
        else
        {
            character = Object.Instantiate(Resources.Load("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            TypeOfCharacter = 1;
        }

        Cmove = character.AddComponent(typeof(move)) as move;
        click = character.AddComponent(typeof(Click)) as Click;
        click.setGameObj(this);
    }

    public string getName()
    {
        return character.name;
    }

    public void setName(string input)
    {
        character.name = input;
    }

    public void setPosition(Vector3 input)
    {
        character.transform.position = input;
    }

    public void getOnBoat(Boat input)
    {
        character.transform.parent = input.getGameobj().transform;
        characterStatus = 1;
    }

    public void getOnCoast(Coast input)
    {
        character.transform.parent = null;
        characterStatus = 0;
        coast = input;
    }

    public void reset()
    {
        Cmove.reset();
        coast = (Director.getInstance().currentSceneController as main).RightCoast;
        getOnCoast(coast);
        setPosition(coast.getEmptyPosition());
        coast.getOnCoast(this);
    }

    public void moveToPosition(Vector3 input)
    {
        Cmove.setDestination(input);
    }

    public Coast getCoast()
    {
        return coast;
    }
}

Character類的解釋

成員變數

  • private GameObject character;必須的成員,在遊戲中用cube代替
  • public int TypeOfCharacter;作為牧師和惡魔的標識,牧師是0,惡魔是1,因為沒有把牧師和惡魔作為分開的類,在後面計算牧師和惡魔數量的時候需要用到。
  • private move Cmove; Character需要移動,因為牧師和惡魔的動作基本一致,所以也沒有分開寫成兩個動作。
  • public int characterStatus; 表示Character的狀態是在岸上還是在船上,0在岸上,1在船上。
  • Coast coast; 什麼Character類會需要coast類,因為需要判斷當前的人物是站在左邊的coast上還是右邊的coast上,已經上下船的時候,確定所在的coast。
  • Click click; 物體的點選事件。

    成員函式,只說明重要的部分

  • 第一個是掛載move和Click,
    Cmove = character.AddComponent(typeof(move)) as move;
    click = character.AddComponent(typeof(Click)) as Click;

  • public void getOnBoat(Boat input),在上船的時候,將Character變成船的子節點,便於船統計Character的數量。同樣public void getOnCoast(Coast input) 在上岸的時候解除該關係,並確定上的是哪個河岸。

  • public void moveToPosition(Vector3 input) 這個利用的是move裡面的方法。

Coast類

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

public class Coast {

    private GameObject coast;
    public int TypeOfCoast;//-1代表左邊,1代表右邊
    Vector3 LeftPos = new Vector3(-9,1,0);
    Vector3 RightPos = new Vector3(9, 1, 0);
    Vector3[] positions;
    Character[] charactersOnCoast;
    public Coast(int input)
    {
        positions = new Vector3[] {new Vector3(6.5F,2.25F,0), new Vector3(7.5F,2.25F,0), new Vector3(8.5F,2.25F,0),
                new Vector3(9.5F,2.25F,0), new Vector3(10.5F,2.25F,0), new Vector3(11.5F,2.25F,0)};
        charactersOnCoast = new Character[6];
        if (input == -1)
        {
            coast = Object.Instantiate(Resources.Load("Perfabs/Stone", typeof(GameObject)),LeftPos, Quaternion.identity, null) as GameObject;
            TypeOfCoast = -1;
        }
        else
        {
            coast = Object.Instantiate(Resources.Load("Perfabs/Stone", typeof(GameObject)), RightPos, Quaternion.identity, null) as GameObject;
            TypeOfCoast = 1;
        }
    }

    public int getEmptyIndex()
    {
        for (int i = 0; i < charactersOnCoast.Length; i++)
        {
            if (charactersOnCoast[i] == null)
            {
                return i;
            }
        }
        return -1;
    }

    public Vector3 getEmptyPosition()
    {
        Vector3 pos = positions[getEmptyIndex()];
        pos.x *= TypeOfCoast;
        return pos;
    }

    public void getOnCoast(Character input)
    {
        int index = getEmptyIndex();
        charactersOnCoast[index] = input;
    }

    public Character getOffCoast(string input)
    {   
        for (int i = 0; i < charactersOnCoast.Length; i++)
        {
            if (charactersOnCoast[i] != null && charactersOnCoast[i].getName() == input)
            {
                Character charactorLeaveCoast = charactersOnCoast[i];
                charactersOnCoast[i] = null;
                return charactorLeaveCoast;
            }
        }
        return null;
    }

    public void reset()
    {
        charactersOnCoast = new Character[6];
    }

    public int[] getCharacterNum()
    {
        int[] count = { 0, 0 };
        for (int i = 0; i < charactersOnCoast.Length; i++)
        {
            if (charactersOnCoast[i] == null)
                continue;
            if (charactersOnCoast[i].TypeOfCharacter == 0)
            {   
                count[0]++;
            }
            else
            {
                count[1]++;
            }
        }
        return count;
    }
}

Coast類的解釋

成員變數 只說不重複的

  • public int TypeOfCoast; 標識是左河岸還是右河岸,-1代表左河岸,1代表是右河岸,為什麼用-1,不用0,因為左右河岸對稱,乘以1,或者-1,正好取到兩邊的位置。
  • Vector3 LeftPos = new Vector3(-9,1,0);Vector3 RightPos = new Vector3(9, 1, 0); 便於初始化。
  • Vector3[] positions; 初始化成6個位置,分別給6個Character;
  • Character[] charactersOnCoast; 記錄在該岸上的Character

    成員函式

  • public Character getOffCoast(string input) 在下岸的時候,將岸上的那個Character置為null。

    Boat類

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

public class Boat {

    private GameObject boat;
    Vector3 LeftPos = new Vector3(-5, 1, 0);
    Vector3 RightPos = new Vector3(5, 1, 0);
    Vector3[] characterOnLeftPos;
    Vector3[] characterOnRightPos;
    move Bmove;
    public int BoatPosStatus;//-1在左邊,1在右邊
    Character[] characterOnBoat;
    Click click;

    public Boat()
    {
        characterOnBoat = new Character[2];
        characterOnRightPos = new Vector3[] { new Vector3(4.5F, 1.5F, 0), new Vector3(5.5F, 1.5F, 0) };
        characterOnLeftPos = new Vector3[] { new Vector3(-5.5F, 1.5F, 0), new Vector3(-4.5F, 1.5F, 0) };
        boat = Object.Instantiate(Resources.Load("Perfabs/Boat", typeof(GameObject)),RightPos , Quaternion.identity, null) as GameObject;
        boat.name = "boat";
        Bmove = boat.AddComponent(typeof(move)) as move;
        BoatPosStatus = 1;
        click = boat.AddComponent(typeof(Click)) as Click;
    }

    public void Move()
    {
        Debug.Log("Move");
        if (BoatPosStatus == -1)
        {
            Bmove.setDestination(RightPos);
            BoatPosStatus = 1;
        }
        else
        {
            Bmove.setDestination(LeftPos);
            BoatPosStatus = -1;
        }
    }

    public int getEmptyIndex()
    {
        for (int i = 0; i < characterOnBoat.Length; i++)
        {
            if (characterOnBoat[i] == null)
            {
                return i;
            }
        }
        return -1;
    }

    public bool isEmpty()
    {
        for (int i = 0; i < characterOnBoat.Length; i++)
        {
            if (characterOnBoat[i] != null)
            {
                return false;
            }
        }
        return true;
    }

    public Vector3 getEmptyPosition()
    {
        Vector3 pos;
        int emptyIndex = getEmptyIndex();
        if (BoatPosStatus == -1)
        {
            pos = characterOnLeftPos[emptyIndex];
        }
        else
        {
            pos = characterOnRightPos[emptyIndex];
        }
        return pos;
    }

    public void GetOnBoat(Character input)
    {
        int index = getEmptyIndex();
        characterOnBoat[index] = input;
    }

    public Character GetOffBoat(string input)
    {
        for (int i = 0; i < characterOnBoat.Length; i++)
        {
            if (characterOnBoat[i] != null && characterOnBoat[i].getName() == input)
            {
                Character character = characterOnBoat[i];
                characterOnBoat[i] = null;
                return character;
            }
        }
        return null;
    }

    public void reset()
    {
        if ( BoatPosStatus == -1)
        {
            Move();
        }
        characterOnBoat = new Character[2];
    }

    public GameObject getGameobj()
    {
        return boat;
    }

    public int[] getCharacterNum()
    {
        int[] count = { 0, 0 };
        for (int i = 0; i < characterOnBoat.Length; i++)
        {
            if (characterOnBoat[i] == null)
                continue;
            if (characterOnBoat[i].TypeOfCharacter == 0)
            {
                count[0]++;
            }
            else
            {
                count[1]++;
            }
        }
        return count;
    }
}

Boat類的解釋

和之前Character和Coast類似。

Water類

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

public class Water {

    private GameObject water;
    Vector3 Position = new Vector3(0, 0.5f, 0);

    public Water()
    {
        water = Object.Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), Position, Quaternion.identity, null) as GameObject;
    }
}

Water類的解釋

Water類只是作為一個靜物,沒有任何動作和聯絡,但是為什麼單獨成類,因為方便以後功能的新增,比如想讓water有波紋之類的。

Click類

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

public class Click : MonoBehaviour {

    ClickAction action;
    Character character;

    public void setGameObj(Character input)
    {
        character = input;
    }

    void Start()
    {
        action = Director.getInstance().currentSceneController as ClickAction;
    }

    void OnMouseDown()
    {
        Debug.Log("OnMouseDown");
        if (gameObject.name == "boat")
        {
            action.ClickBoat();
        }
        else
        {
            action.ClickCharacter(character);
        }
    }
}

Click類的解釋

成員變數

  • ClickAction action; ClickAction是由main實現的關於點選的介面
  • Character character; 有Character類,沒有Boat類,因為Boat只有一個,點選的時候並不需要判斷是哪一個Boat,但似乎Character需要判斷。

    成員函式

  • void OnMouseDown() 通過滑鼠點選的物件,傳給action,做出相應的動作。

    move類

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

public class move : MonoBehaviour {

    private float moveSpeed = 20;

    int moveStatus;//0靜止,1移動到中間,2移動到終點
    Vector3 Destination;
    Vector3 Middle;

    private void Update()
    {
        if(moveStatus == 1)
        {
            transform.position = Vector3.MoveTowards(transform.position, Middle, moveSpeed * Time.deltaTime);
            if (transform.position == Middle)
            {
                moveStatus = 2;
            }
        }
        else if(moveStatus == 2)
        {
            transform.position = Vector3.MoveTowards(transform.position, Destination, moveSpeed * Time.deltaTime);
            if(transform.position == Destination)
            {
                moveStatus = 0;
            }
        }


    }

    public void setDestination(Vector3 input)
    {
        Middle = input;
        Destination = input;
        if(transform.position.y == input.y)
        {
            moveStatus = 2;
        }
        else if(transform.position.y > input.y)
        {
            Middle.y = transform.position.y;
            moveStatus = 1;
        }
        else
        {
            Middle.x = transform.position.x;
            moveStatus = 1;
        }
    }

    public void reset()
    {
        moveStatus = 0;
    }
}

move類的解釋

成員變數

  • int moveStatus; 用來判讀移動的狀態,0靜止,1移動到中間,2移動到終點。
  • Vector3 Destination;Vector3 Middle; 有Middle的原因,防止,Character直線穿牆上船,視覺效果不好。

    成員函式

  • public void setDestination(Vector3 input) 這個函式的實現,通過Boat和Character的高度,判斷是移動船還是移動人,而且通過Destination確定Middle的位置。

    other

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

public class Director : System.Object
{
    private static Director _instance;
    public SceneController currentSceneController { get; set; }

    public static Director getInstance()
    {
        if (_instance == null)
        {
            _instance = new Director();
        }
        return _instance;
    }
}

public interface SceneController
{
    void loadResources();
}

public interface ClickAction
{
    void ClickBoat();
    void ClickCharacter(Character input);
    void ClickReset();
}

other的解釋

不知道該怎麼劃分,就放到other裡面了,主要用了單例模式,和聲明瞭場景控制以及點選事件的介面。

simpleGUI

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

public class simpleGUI : MonoBehaviour {

    private ClickAction action;
    public int status = 0;
    GUIStyle style;
    GUIStyle buttonStyle;

    void Start()
    {
        action = Director.getInstance().currentSceneController as ClickAction;

        style = new GUIStyle();
        style.fontSize = 40;
        style.alignment = TextAnchor.MiddleCenter;

        buttonStyle = new GUIStyle("button");
        buttonStyle.fontSize = 30;
    }
    void OnGUI()
    {
        if (status == 1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "Gameover!", style);
            if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle))
            {
                status = 0;
                action.ClickReset();
            }
        }
        else if (status == 2)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 85, 100, 50), "You win!", style);
            if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 140, 70), "Restart", buttonStyle))
            {
                status = 0;
                action.ClickReset();
            }
        }
    }
}

simpleGUI的解釋

原本是叫GUI,但是具體寫程式的時候凡是遇到GUI. 這樣的都沒法執行,所以改成了simpleGUI。

mian

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


public class main : MonoBehaviour,SceneController,ClickAction {

    private Coast LeftCoast;
    public Coast RightCoast;
    private Boat boat;
    private Character[] characters;
    private Water water;
    public simpleGUI simplegui;
    void Awake()
    {
        Director director = Director.getInstance();
        director.currentSceneController = this;
        characters = new Character[6];
        loadResources();
        simplegui = gameObject.AddComponent<simpleGUI>() as simpleGUI   ;
    }
    public void loadResources()
    {
        LeftCoast = new Coast(-1);
        RightCoast = new Coast(1);
        boat = new Boat();
        water = new Water();
        for (int i = 0; i < 3; i++)
        {
            Debug.Log("ok");
            Character newCharacter = new Character(0);
            newCharacter.setName("priest" + i);
            newCharacter.setPosition(RightCoast.getEmptyPosition());
            newCharacter.getOnCoast(RightCoast);
            RightCoast.getOnCoast(newCharacter);

            characters[i] = newCharacter;
        }
        for (int i = 0; i < 3; i++)
        {
            Character newCharacter = new Character(1);
            newCharacter.setName("devil" + i);
            newCharacter.setPosition(RightCoast.getEmptyPosition());
            newCharacter.getOnCoast(RightCoast);
            RightCoast.getOnCoast(newCharacter);

            characters[i+3] = newCharacter;
        }
    }

    public void ClickBoat()
    {
        if (boat.isEmpty())
        {
            return;
        }
        else
        {
            boat.Move();
        }
        simplegui.status = gameStatus();
    }

    public void ClickCharacter(Character input)
    {
        if (input.characterStatus == 1)//0在岸上,1在船上
        {
            Coast coast;
            Debug.Log(boat.BoatPosStatus);
            if (boat.BoatPosStatus == -1)
            {
                coast = LeftCoast;
            }
            else
            {
                coast = RightCoast;
            }
            boat.GetOffBoat(input.getName());
            Debug.Log(coast.getEmptyPosition());
            input.moveToPosition(coast.getEmptyPosition());
            input.getOnCoast(coast);
            coast.getOnCoast(input);

        }
        else
        {
            Coast coast = input.getCoast();

            if (boat.getEmptyIndex() == -1)
            {
                return;
            }
            if (coast.TypeOfCoast != boat.BoatPosStatus)
            {
                return;
            }
            coast.getOffCoast(input.getName());
            input.moveToPosition(boat.getEmptyPosition());
            input.getOnBoat(boat);
            boat.GetOnBoat(input);
        }
        Debug.Log(gameStatus());
        simplegui.status = gameStatus();
    }
    public void ClickReset()
    {
        boat.reset();
        LeftCoast.reset();
        RightCoast.reset();
        for (int i = 0; i < characters.Length; i++)
        {
            characters[i].reset();
        }
    }

    public int gameStatus()
    {
        int from_priest = 0;
        int from_devil = 0;
        int to_priest = 0;
        int to_devil = 0;

        int[] fromCount = RightCoast.getCharacterNum();
        from_priest += fromCount[0];
        from_devil += fromCount[1];
        int[] toCount = LeftCoast.getCharacterNum();
        to_priest += toCount[0];
        to_devil += toCount[1];

        if (to_priest + to_devil == 6)      
            return 2;

        int[] boatCount = boat.getCharacterNum();
        if (boat.BoatPosStatus == -1)
        {   
            to_priest += boatCount[0];
            to_devil += boatCount[1];
        }
        else
        {   
            from_priest += boatCount[0];
            from_devil += boatCount[1];
        }
        if (from_priest < from_devil && from_priest > 0)
        {
            return 1;
        }
        if (to_priest < to_devil && to_priest > 0)
        {
            return 1;
        }
        return 0;
    }
}

main的解釋

對介面類的實現,補充了判斷遊戲結束的函式。

資源