中山大学3D游戏设计读书笔记 unity3D Note2

作业内容

本文主要针对unity的游戏对象的移动做出介绍,并通过几个简易的代码实现抛物线运动,运用物体的旋转完成简单的太阳系模拟构造。最后,根据MVC模式实现牧师与恶魔小游戏。


1、简答并用程序验证

A. 游戏对象运动的本质是什么?

  游戏对象运动的本质就是坐标的变换,包括世界坐标(绝对坐标)的变换和对象坐标(相对坐标)的变换。每个游戏对象必须有一个Transform组件,我们可以通过改变该组件的属性来让游戏对象运动起来。


B. 请用三种方法以上方法,实现物体的抛物线运动。(如,修改Transform属性,使用向量Vector3的方法…)

  • 利用transform中的position属性方法直接改变位置
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public int a = 2;
    private void Update()
    {
        this.transform.position += Vector3.right * Time.deltaTime * 1f;
        this.transform.position += Vector3.down * Time.deltaTime * Time.deltaTime * a;
    }
}
  • 利用Vector3的MoveTowards方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class moveup : MonoBehaviour {
    //The target marker
    Vector3 target1 = Vector3.right * 5;
    Vector3 target2 = Vector3.down * 5;
    float speed1 = 1;
    float speed2 = 4;
    // Update is called once per frame
    void Update(){
        float step1 = speed1 * Time.deltaTime;
        float step2 = speed2 * Time.deltaTime * Time.deltaTime;
        transform.position = Vector3.MoveTowards(transform.position, target1, step1);
        transform.position = Vector3.MoveTowards(transform.position, target2, step2);
    }
}
  • 使用Vector3.Lerp()方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public float Speed = 0.5f;
    Vector3 Target1 = new Vector3(-6, -3, 8);
    //控制物体向Target移动
    void Update()
    {
        gameObject.transform.localPosition = Vector3.Lerp(transform.position, Target1, Speed * Time.deltaTime);
    }
}
  • 利用transform.Translate()方法
    function Translate (translation : Vector3, relativeTo : Space = Space.Self) : void
    物体以relativeTo为参照系,沿着translation运动|translation|的距离。如果relativeTo缺省将以Space.Self为默认值。

C.写一个程序,实现一个完整的太阳系, 其他星球围绕太阳的转速必须不一样,且不在一个法平面上。

效果

//对于八大行星围绕太阳公转
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript1 : MonoBehaviour {
    public Transform sun;
    public float speed = 15;
    private float random_x, random_y;
    //由于题目需要行星要在不同的法平面旋转,故我们随机行星旋转的轴位置
    private void Start()
    {
        random_x = Random.Range(1, 10);
        random_y = Random.Range(10, 60);
    }
    void Update()
    {
        Vector3 axis = new Vector3(random_x, random_y, 0); //围绕哪一条轴旋转
        this.transform.RotateAround(sun.position, axis, speed * Time.deltaTime);
    }
}
//对于地球的自转
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateSelf : MonoBehaviour {
    // Update is called once per frame
    void Update () {
        this.transform.RotateAround(this.transform.position, Vector3.up, 2);
    }
}

2、编程实践

A. 阅读以下游戏脚本

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!

B. 程序需要满足的要求:

规则名称 条件
牧师或魔鬼上左岸 船已靠左岸,船上有牧师或魔鬼
牧师或魔鬼上右岸 船已靠右岸,船上有牧师或魔鬼
魔鬼上船 岸上有魔鬼,船未载满两人
牧师上船 岸上有牧师,船未载满两人
船只开动 船上有人
击杀规则 一边的牧师数量少于魔鬼的数量
游戏胜利规则 所有角色从左岸到达右岸,且全部存活
游戏失败规则 牧师被击杀
  • 请将游戏中对象做成预制

Perfabs

  • 在 GenGameObjects 中创建 长方形、正方形、球 及其色彩代表游戏中的对象。

  白正方形表示牧师,黑球代表魔鬼,棕色长方体代表船,绿色长方体左右岸,蓝色长方体代表河流。

  • 使用 C# 集合类型 有效组织对象
  • 整个游戏仅 主摄像机 和 一个 Empty 对象, 其他对象必须代码动态生成!整个游戏不许出现 Find 游戏对象,SendMessage 这类突破程序结构的通讯耦合语句。
  • 请使用课件架构图编程,不接受非 MVC 结构程序
    mvc
  • 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

游戏中所用到的主要概念:

1.首先我们要理解MVC模式

模型(Model)– GameObject 及其关系
控制(Controller)– MonoBehavior
视图(View)– Camera

2.创建 SSDirector 对象,其职责大致如下:

  • 获取当前游戏的场景
  • 控制场景运行、切换、入栈与出栈
  • 暂停、恢复、退出
  • 管理游戏全局状态
  • 设定游戏的配置
  • 设定游戏全局视图

3. SCENECONTROLLER(场记)

  • 管理本次场景所有的游戏对象
  • 协调游戏对象(预制件级别)之间的通信
  • 响应外部输入事件
  • 管理本场次的规则(裁判)
  • 杂务

4.接口(INTERFACE)

  • 一种数据类型,表示对象的对外行为类型
  • 每个场景都有自己的场记,导演需要与不同场景打交道
  • 导演只知道场记的加载资源、暂停、恢复等行为,但并不知道实现细节,如:暂停前要保存哪些状态等
  • 导演也不希望知道细节,而是规定场记们必须会做什么,怎么做自由发挥。这个规定就是接口

5. 接口与门面(FASÀDE)模式

  • 外部与一个子系统的通信必须通过一个统一的门面
    (Facade)对象进行。

解释代码

1.Director为整个游戏的导演,要使用单例模式。它掌控着场景的加载、切换等,也可以控制游戏暂停、结束等等。

 //Director 控制唯一一个实例
    public class Director : System.Object
    {
        private static Director D_instance;
        public SceneController sceneController { get; set; }

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

2.场景接口

//场景控制器,被FirstController类所继承来加载资源预设
    public interface SceneController
    {
        void load();
    }

3.门面模式 ,FirstController实现该接口来与用户交互。

    public interface UserAction
    {
        void moveBoat();
        void characterIsClicked(MyCharacterController c_controller);
        void restart();
        bool stop();
    }

4.生成角色的类

 //角色的类
    public class MyCharacterController
    {
        //只读数据,不希望通过Inspector中改变角色
        readonly GameObject character;
        readonly Moveable moveable;
        readonly ClickGUI clickGUI;
        readonly bool is_priest;
        //可改变
        bool is_onboat;
        CoastController coastController;
        //通过字符串构造角色函数
        public MyCharacterController(string c_str)
        {
            if(c_str == "priest")
            {
                is_priest = true;
                character = Object.Instantiate(Resources.Load("Perfabs/priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            }
            else if(c_str == "devil")
            {
                is_priest = false;
                character = Object.Instantiate(Resources.Load("Perfabs/devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
            }
            moveable = character.AddComponent(typeof(Moveable)) as Moveable;
            clickGUI = character.AddComponent(typeof(ClickGUI)) as ClickGUI;
            clickGUI.setController(this);
        }
        //角色上船函数
        public void Onboat(BoatController boatController)
        {
            coastController = null; //离开岸边
            character.transform.parent = boatController.getBoat().transform;
            is_onboat = true;
        }
        //角色上岸函数
        public void Oncoast(CoastController temp)
        {
            coastController = temp;
            character.transform.parent = null;
            is_onboat = false;
        }
        //重置函数,恢复现场
        public void reset()
        {
            moveable.reset();
            coastController = (Director.getInstance().sceneController as FirstController).leftCoast;
            Oncoast(coastController);
            setPosition(coastController.getEmptyPosition());
            coastController.getOnCoast(this);
        }
        //各种get,set函数
        public void setName(string name)
        {
            character.name = name;
        }
        public string getName()
        {
            return character.name;
        }
        public void setPosition(Vector3 position)
        {
            character.transform.position = position;
        }
        public Vector3 getPosition()
        {
            return character.transform.position;
        }
        public void movePosition(Vector3 position)
        {
            moveable.setDestination(position);
        }
        public bool getType() //true -> priest; false -> devil
        {
            return is_priest;
        }
        public bool getis_onboat()
        {
            return is_onboat;
        }
        public CoastController getcoastController()
        {
            return coastController;
        }
    }

4.生成船的类

 //船的类
    public class BoatController
    {
        //只读数据,不希望通过Inspector中改变船的位置
        readonly GameObject boat;
        readonly Moveable moveable;
        readonly Vector3 right_pos = new Vector3(4, 1, 0);
        readonly Vector3 left_pos = new Vector3(-4, 1, 0);
        readonly Vector3[] start_pos;
        readonly Vector3[] end_pos;
        //判断船是否向左岸走
        bool is_left;
        //船上的角色最多只有两个
        MyCharacterController[] passenger = new MyCharacterController[2];
        //船的构造函数
        public BoatController()
        {
            is_left = true;
            end_pos =  new Vector3[] { new Vector3(3F, 2F, 0), new Vector3(4.5F, 2F, 0) };
            start_pos = new Vector3[] { new Vector3(-4.5F, 2F, 0), new Vector3(-3F, 2F, 0) };
            boat = Object.Instantiate(Resources.Load("Perfabs/boat", typeof(GameObject)), left_pos, Quaternion.identity, null) as GameObject;
            boat.name = "boat";
            moveable = boat.AddComponent(typeof(Moveable)) as Moveable;
            boat.AddComponent(typeof(ClickGUI));
        }
        //判断船是否为空
        public bool isEmpty()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                    return false;
            }
            return true;
        }
        //查找船上的空位
        public int getEmptyIndex()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null) return i;
            }
            return -1;
        }
        //查找船上空位的位置
        public Vector3 getEmptyPos()
        {
            int index = getEmptyIndex();
            if (is_left)
                return start_pos[index];
            else
                return end_pos[index];
        }
        //船的移动函数,通过调用moveable中的setDestination函数
        public void boat_move()
        {
            if (is_left)
            {
                is_left = false;
                moveable.setDestination(right_pos);
            }
            else
            {
                is_left = true;
                moveable.setDestination(left_pos);
            }
        }
        //上船函数
        public void GetOnBoat(MyCharacterController charactercontroller)
        {
            int index = getEmptyIndex();
            if(index != -1)
                passenger[index] = charactercontroller;
        }
        //上岸函数
        public MyCharacterController GetOffBoat(string name)
        {
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null && passenger[i].getName() == name)
                {
                    MyCharacterController mycharacter = passenger[i];
                    passenger[i] = null;
                    return mycharacter;
                }
            }
            return null;
        }
        //重置函数
        public void reset()
        {
            moveable.reset();
            if(is_left == false)
            {
                boat_move();
            }
            passenger = new MyCharacterController[2];
        }
        //各种get和set函数
        public bool get_is_left()
        {
            return is_left;
        }
        public GameObject getBoat()
        {
            return boat;
        }
        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                {
                    if(passenger[i].getType() == true)
                    {
                        count[0]++;
                    }
                    else
                    {
                        count[1]++;
                    }
                }
            }
            return count;
        }
    }

5.生成岸边的类

//岸的类
    public class CoastController
    {
        //只读数据,不希望通过Inspector中改变左右岸的位置
        readonly GameObject coast;
        readonly Vector3 right_pos = new Vector3(10, 1, 0);
        readonly Vector3 left_pos = new Vector3(-10, 1, 0);
        //角色在岸上的位置
        readonly Vector3[] positions;
        //岸是否在右边
        readonly bool is_right;

        MyCharacterController[] passenger;

        public CoastController(string pos)
        {
            positions = new Vector3[] {new Vector3(6.5F,2.6F,0), new Vector3(7.7F,2.6F,0), new Vector3(8.9F,2.6F,0),
                new Vector3(10.1F,2.6F,0), new Vector3(11.3F,2.6F,0), new Vector3(12.5F,2.6F,0)};
            passenger = new MyCharacterController[6];
            if (pos == "right")
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/coast", typeof(GameObject)), right_pos, Quaternion.identity, null) as GameObject;
                coast.name = "right";
                is_right = true;
            }
            else if (pos == "left")
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/coast", typeof(GameObject)), left_pos, Quaternion.identity, null) as GameObject;
                coast.name = "left";
                is_right = false;
            }
        }
        //获得空位函数
        public int getEmptyIndex()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null)
                {
                    return i;
                }
            }
            return -1;
        }
        //获得空位位置函数
        public Vector3 getEmptyPosition()
        {
            Vector3 pos = positions[getEmptyIndex()];
            if (is_right == false)
                pos.x *= -1;
            return pos;
        }
        //上岸函数
        public void getOnCoast (MyCharacterController mycharacter)
        {
            passenger[getEmptyIndex()] = mycharacter;
        }
        //离岸函数
        public MyCharacterController getOffCoast(string name)
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null && passenger[i].getName() == name)
                {
                    MyCharacterController mycharacter = passenger[i];
                    passenger[i] = null;
                    return mycharacter;
                }
            }
            return null;
        }
        //重置函数
        public void reset()
        {
            passenger = new MyCharacterController[6];
        }
        //各种get和set函数
        public bool get_is_right()
        {
            return is_right;
        }
        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for(int i = 0; i < passenger.Length; i++)
            {
                if(passenger[i] != null)
                {
                    if(passenger[i].getType() == true)
                    {
                        count[0]++;
                    }
                    else
                    {
                        count[1]++;
                    }
                }
            }
            return count;
        }
    }

6.移动函数的类

public class Moveable : MonoBehaviour
    {
        public float speed = 20;
        int status;  // 0->not moving, 1->moving to boat, 2->moving to dest
        Vector3 dest;
        Vector3 boat;
        void Update()
        {
            if(status == 1)
            {
                transform.position = Vector3.MoveTowards(transform.position, boat, speed * Time.deltaTime);
                if(transform.position == boat)
                    status = 2;
            }
            else if(status == 2)
            {
                transform.position = Vector3.MoveTowards(transform.position, dest, speed * Time.deltaTime);
                if (transform.position == dest)
                    status = 0;
            }
        }
        //设置目的地
        public void setDestination(Vector3 pos)
        {
            dest = boat = pos;
            if (pos.y < transform.position.y)      
            {       
                boat.y = transform.position.y;
            }
            else if(pos.y > transform.position.y)
            {                               
                boat.x = transform.position.x;
            }
            status = 1;
        }
        public void reset()
        {
            status = 0;
        }
    }

7.ClickGUI函数,用户通过鼠标点击来交互,这就是MVC中的View部分

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class ClickGUI : MonoBehaviour {
    UserAction action;
    MyCharacterController character;
    // 得到唯一的实例
    void Start () {
        action = Director.getInstance().sceneController as UserAction;
    }
    //鼠标点击触发不同事件
    void OnMouseDown()
    {
        if (action.stop())
            return;
        if (gameObject.name == "boat")
        {
            action.moveBoat();
        }
        else
        {
            action.characterIsClicked(character);
        }
    }
    //设置角色控制器
    public void setController(MyCharacterController characterCtrl)
    {
        character = characterCtrl;
    }
}

8.UserGUI的函数,调整GUI界面样式,成功与失败的两种不同结果

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class UserGUI : MonoBehaviour {
    private UserAction action;
    public int status = 0; // -1表示失败,1表示成功
    GUIStyle style;
    GUIStyle style2;
    GUIStyle buttonStyle;
    public bool show = false;

    void Start()
    {
        action = Director.getInstance().sceneController as UserAction;
        style = new GUIStyle();
        style.fontSize = 15;
        style.alignment = TextAnchor.MiddleLeft;
        style2 = new GUIStyle();
        style2.fontSize = 30;
        style2.alignment = TextAnchor.MiddleCenter;
    }

    void OnGUI()
    {
        if (status == -1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 65, 100, 50), "Gameover!", style2);   
        }
        else if (status == 1)
        {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 65, 100, 50), "You win!", style2);  
        }
        buttonStyle = new GUIStyle("button");
        buttonStyle.fontSize = 15;
        if (GUI.Button(new Rect(Screen.width / 2 - 50, 20, 100, 50), "Restart", buttonStyle))
        {
            status = 0;
            action.restart();
        }
        if (GUI.Button(new Rect(Screen.width / 2 - 50, 80, 100, 50), "Rule", buttonStyle))
        {
            show = true;
        }
        if (show)
        {
            GUI.Label(new Rect(Screen.width / 2 + 70, 20, 100, 100), "游戏规则:\n白色正方体为牧师,黑色球体为魔鬼。\n" +
            "船只最多能容纳两人,有人在船上才可开船\n" +
            "当某一岸恶魔数量大于牧师数量,游戏失败!\n" +
            "牧师与恶魔全部渡过河流,游戏胜利!\n", style);
        }
    }
}

9.firstController利用前面所建立的类,实现场景和门面两个接口,生成游戏对象,将View与Model掌控在导演手上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Mygame;

public class FirstController : MonoBehaviour, SceneController, UserAction {
    Vector3 water_pos = new Vector3(0, 0.5f, 0);
    Vector3 bac_pos = new Vector3(0, 0, 10);

    public CoastController leftCoast;
    public CoastController rightCoast;
    public BoatController boat;

    private MyCharacterController[] characters = null;
    private UserGUI userGUI = null;
    public bool flag_stop = false;
    //得到唯一的实例
    void Awake()
    {
        Director director = Director.getInstance();
        director.sceneController = this;
        userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
        characters = new MyCharacterController[6];
        load();
        flag_stop = false;
    }
    //初始化游戏资源,如角色,船等等
    public void load()
    {
        GameObject water = Instantiate(Resources.Load("Perfabs/water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
        GameObject bac = Instantiate(Resources.Load("Perfabs/background", typeof(GameObject)), bac_pos, Quaternion.identity, null) as GameObject;
        bac.name = "background";
        water.name = "water";
        leftCoast = new CoastController("left");
        rightCoast = new CoastController("right");
        boat = new BoatController();
        for (int i = 0; i < 3; i++)
        {
            MyCharacterController character = new MyCharacterController("priest");
            character.setPosition(leftCoast.getEmptyPosition());
            character.Oncoast(leftCoast);
            leftCoast.getOnCoast(character);
            characters[i] = character;
            character.setName("priest" + i);
        }
        for (int i = 0; i < 3; i++)
        {
            MyCharacterController character = new MyCharacterController("devil");
            character.setPosition(leftCoast.getEmptyPosition());
            character.Oncoast(leftCoast);
            leftCoast.getOnCoast(character);
            characters[i + 3] = character;
            character.setName("devil" + i);
        }
    }
    public void moveBoat()
    {
        if (boat.isEmpty())
            return;
        boat.boat_move();
        userGUI.status = check_game_over();
    }
    //判断游戏胜负
    int check_game_over()
    {   
        int left_priest = 0, left_devil = 0, right_priest = 0, right_devil = 0;
        int[] fromCount = leftCoast.getCharacterNum();
        int[] toCount = rightCoast.getCharacterNum();
        left_priest += fromCount[0];
        left_devil += fromCount[1];
        right_priest += toCount[0];
        right_devil += toCount[1];
        //获胜条件
        if (right_priest + right_devil == 6)      
            return 1;
        int[] boatCount = boat.getCharacterNum();
        //统计左右两岸的牧师与恶魔的数量
        if (!boat.get_is_left())
        {   
            right_priest += boatCount[0];
            right_devil += boatCount[1];
        }
        else
        {        
            left_priest += boatCount[0];
            left_devil += boatCount[1];
        }
        //游戏失败条件
        if ((left_priest < left_devil && left_priest > 0)|| (right_priest < right_devil && right_priest > 0))
        {       
            return -1;
        }
        return 0;           //游戏继续
    }

    public void characterIsClicked(MyCharacterController character)
    {
        //角色要上岸
        if (character.getis_onboat())
        {
            CoastController coast;
            if (!boat.get_is_left())
            { 
                coast = rightCoast;
            }
            else
            {
                coast = leftCoast;
            }
            boat.GetOffBoat(character.getName());
            character.movePosition(coast.getEmptyPosition());
            character.Oncoast(coast);
            coast.getOnCoast(character);
        }
        // 角色要上船
        else
        {                                   
            CoastController coast = character.getcoastController();
            // 船上已有两人
            if (boat.getEmptyIndex() == -1)
            {      
                return;
            }
            // 船与角色并不在同一边岸
            if (coast.get_is_right() == boat.get_is_left())   
                return;
            coast.getOffCoast(character.getName());
            character.movePosition(boat.getEmptyPos());
            character.Onboat(boat);
            boat.GetOnBoat(character);
        }
        userGUI.status = check_game_over();
    }
    //重置函数
    public void restart()
    {
        boat.reset();
        leftCoast.reset();
        rightCoast.reset();
        for (int i = 0; i < characters.Length; i++)
        {
            characters[i].reset();
        }
    }
    //游戏结束后,不能再点击产生交互信息
    public bool stop()
    {
        if(check_game_over() != 0)
            return true;
        return false;
    }
}

最后将FirstController函数拖动进main的空对象中,就可以运行游戏。
效果图如下:
效果图


由于作者水平有限,如博客有任何错误,欢迎指出并讨论,谢谢
想了解更多代码详情或课程知识,可到本人github查询
Github地址:https://github.com/dick20/3d-learning

猜你喜欢

转载自blog.csdn.net/dickdick111/article/details/79808574