Unity 3D ——空间与运动

目录

  • 简答并用程序验证
    • 游戏对象运动的本质是什么?
    • 实现抛物运动
  • 太阳系模拟
  • 编程实践
    • 程序要求和实现:
    • 游戏设计
        • MVC架构
        • 实现过程和MVC框架设计分析
    • 游戏效果
        • 演示视频

简答并用程序验证

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

  • 游戏对象运动的本质就是经过矩阵变换(平移、旋转、缩放)而引起的游戏对象空间属性的改变,空间属性包括Transform中的Position(位置参数)和Rotation(旋转参数)。

实现抛物运动

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

  1. 添加重力特性

    
    public class useGravity : MonoBehaviour {
        void Start()
        {
            //给物体添加刚体属性,使之具有重力
            gameObject.AddComponent<Rigidbody>();
            //设置物体的初速度
            Vector3 _velocity = new Vector3(5, 0, 0);
            gameObject.GetComponent<Rigidbody>().velocity = _velocity;
        } 
    }
    
    
  2. 直接修改transform属性

    	private float speedX = 5.0f;
        private float speedY = -5.0f;
        private float g = 10.0f;
        void Update()
        {
            this.transform.position += Vector3.right * speedX * Time.deltaTime + Vector3.down * speedY * Time.deltaTime;
            speedY += g * Time.deltaTime;
        }
    
  3. 使用向量

    	private float speedX = 5.0f;
        private float speedY = 5.0f;
        private float g = -10.0f;
        private float X, Y;
        private float t = 0.0f;
        void Update()
        {
            X = speedX * Time.deltaTime;
            Y = (float)(speedY * (t + Time.deltaTime) + 0.5 * g * (t + Time.deltaTime) * (t + Time.deltaTime)) - (float)(speedY * t + 0.5 * g * t * t);
            t += Time.deltaTime;
            this.transform.position += new Vector3(X, Y, 0);
        }
    
  4. 使用Transform中的translate方法

    	private float speedX = 5.0f;
        private float speedY = 5.0f;
        private float g = -10.0f;
        private float X, Y;
        private float t = 0.0f;
        void Update()
        {
            X = speedX * Time.deltaTime;
            Y = (float)(speedY * (t + Time.deltaTime) + 0.5 * g * (t + Time.deltaTime) * (t + Time.deltaTime)) - (float)(speedY * t + 0.5 * g * t * t);
            t += Time.deltaTime;
            transform.Translate(new Vector3(X, Y, 0), Space.World);
        }
    

太阳系模拟

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

  1. 设置各个星体的初始位置,设置地球自转和地球影子(empty对象)的初始欧拉角

    void Start()
        {
            sun.position = Vector3.zero;
            water.position = new Vector3(5, 0, 0);
            gold.position = new Vector3(7, 0, 0);
            earth.position = new Vector3(9, 0, 0);
            fire.position = new Vector3(10, 0, 0);
            wood.position = new Vector3(12, 0, 0);
            flor.position = new Vector3(14, 0, 0);
            sky.position = new Vector3(16, 0, 0);
    
            empty.position = new Vector3(9, 0, 0);
            moon.position = new Vector3(8, 0, 0);
            earth.transform.rotation = Quaternion.AngleAxis(30, Vector3.up);
            empty.transform.rotation = Quaternion.AngleAxis(30, Vector3.up);
        }
    
  2. 使用RotateAround(中心点,方向轴,速度)函数来设置星体绕太阳旋转,使用Quaternion.AngleAxis(速度,方向轴)来设置地球和地球影子自传,将月球放在地球影子下,月球将和地球影子自传

    void Update()
        {
            Vector3 axis1 = new Vector3(5, 80, 0);
            Vector3 axis2 = new Vector3(10, 70, 0);
            Vector3 axis3 = new Vector3(15, 45, 0);
            Vector3 axis4 = new Vector3(20, 61, 0);
            Vector3 axis5 = new Vector3(25, 66, 0);
            Vector3 axis6 = new Vector3(22, 55, 0);
    
            earth.RotateAround(sun.position, Vector3.up, speed * Time.deltaTime);
            water.RotateAround(sun.position, axis1, (float)1.8 * speed * Time.deltaTime);
            gold.RotateAround(sun.position, axis2, (float)1.5 * speed * Time.deltaTime);
            fire.RotateAround(sun.position, axis3, (float)1.2 * speed * Time.deltaTime);
            wood.RotateAround(sun.position, axis4, (float) 1.05 * speed * Time.deltaTime);
            flor.RotateAround(sun.position, axis5, (float)1.01 * speed * Time.deltaTime);
            sky.RotateAround(sun.position, axis6, (float) 0.6 * speed * Time.deltaTime);
            earth.transform.rotation *= Quaternion.AngleAxis(30 * Time.deltaTime, Vector3.up);
            empty.transform.rotation *= Quaternion.AngleAxis(300 * Time.deltaTime, Vector3.up);
        }
    
  3. 将脚本挂载到Main Camera上,创建各个星球,并放置在脚本相应的位置
    Unity 3D ——空间与运动_第1张图片

  4. 下载行星贴图创建Material,添加到行星上,设置Trail Rendeder显示轨迹,添加Skybox进行细节优化

    实验效果如下,行星在不同的法平面以不同的速度运动
    Unity 3D ——空间与运动_第2张图片

编程实践

  • 阅读以下游戏脚本

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!

程序要求和实现:

  1. play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )

  2. 列出游戏中提及的事物(Objects)
    河岸Coast,船Boat,河流River,魔鬼Devils,牧师Priests

  3. 用表格列出玩家动作表(规则表),注意,动作越少越好
    玩家动作为点击不同对象,来控制游戏对象

    条件 结果
    鬼/牧师 对象在岸上,船在本河岸,并且船上有空位 点中的游戏对象上船
    鬼/牧师 对象在船中,船靠岸 游戏对象上岸
    船中至少有一个对象 船载着游戏对象移到对岸
  4. 在 GenGameObjects 中创建长方形、正方形、球及其色彩代表游戏中的对象,将游戏中对象做成预制
    Unity 3D ——空间与运动_第3张图片

  5. 使用 C# 集合类型 有效组织对象

  6. 整个游戏仅 主摄像机 和 一个Empty对象, 其他对象必须代码动态生成!!!

  7. 请使用课件架构图编程,不接受非 MVC 结构程序
    MVC架构是人机交互程序设计的一种架构模式。它把程序分为三个部分:

    • 模型(Model):数据对象及关系

      游戏对象、空间关系

    • 界面(View):显示模型,将人机交互事件交给控制器处理

      处收Input事件
      渲染GUI,接收事件

    • 控制(Controller):接受用户事件,控制模型的变化

      一个场景一个主控制器
      至少实现与玩家交互的接口(IPlayerAction)
      实现或管理运动

  8. 注意细节,例如:船未靠岸,牧师与魔鬼上下船运动中,均不能接受用户事件!

游戏设计

MVC架构

Unity 3D ——空间与运动_第4张图片

MVC结构在游戏中的使用:

  • Model:场景中的游戏对象都是model,包括牧师、魔鬼、船、河岸,在程序中它们被包含在不同的controller类,并收到相应控制器的控制。具有初始化和控制方法。
  • View:在游戏中包括了UserGUI和ClickGUI,提供游戏界面和用户交互的渠道(可点击暂停按钮和物体)
  • Controller:不同对象对应不同的控制器,例如牧师和魔鬼受到MyCharacterController类的控制,船受到BoatController类的控制,河岸受到CoastController类的控制。另外还有更高层次的控制器,FirstSceneController(场记)管理本次场景所有的游戏对象,协调游戏对象(预制件级别)之间的通讯,响应外部输入事件,管理本场次的规则(裁判)以及各种杂务。最高层次的控制器SSDirector类则控制场记的创建、切换、销毁、游戏暂停、恢复、退出等最高层次的功能。

实现过程和MVC框架设计分析

  • 游戏资源制作
    Unity 3D ——空间与运动_第5张图片
    场景启动对象和控制器:仅在Hierarchy页面创建一个empty对象,并挂载FirstController代码,从而可以控制游戏加载行为,其他对象由代码动态生成。
    Unity 3D ——空间与运动_第6张图片

  • MVC核心代码分析
    1. Controller
    从最高层次的控制器开始设计,首先是创建导演类

     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;
            }
        }
    

    导演类SSDirector负责:

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

    导演类继承至 C# 根对象,不会受 Unity 引擎管理,也不需要加载。采用单例模式,在游戏中只有一个,在本游戏中,导演可以在任何时候都接受到信息,负责获取当前场景(它通过内部的场记控制器ISceneController实现加载资源),并控制游戏暂停和恢复等

    接口
    再设计其他控制器之前,根据MVC结构我们需要接口来定义一组操作,功能调用和实现的细节分开,松弛控制器的关系

    SceneController定义加载资源(游戏对象)的方法。同时在导演类Director中包含有SceneController接口,可以使用接口调用加载资源方法,而不需要具体实现的细节。具体实现在接口的继承类中实现。

    public interface SceneController
        {
            void loadResources();
        }
    

    玩家并不关心游戏程序得到结果的过程,而仅关注能做什么。通过UserAction定义一个了用户动作接口,实现了用户行为与游戏系统规则计算的分离,它采用了门面模式(Fasade)。

    public interface UserAction
        {
            void moveBoat();
            void characterIsClicked(MyCharacterController characterCtrl);
            void restart();
        }
    

    场记FirstController:根据MVC架构图,FirstController需要继承ISceneController,和IUserAction,并实现接口中的定义的函数,包括加载场景和游戏对象,控制船只移动、控制牧师或魔鬼上船、上岸来响应外部输入。

    public class FirstController : MonoBehaviour, SceneController, UserAction
    {
    
        readonly Vector3 water_pos = new Vector3(0, 0.5F, 0);
    
    
        UserGUI userGUI;
    
        public CoastController fromCoast;
        public CoastController toCoast;
        public BoatController boat;
        private MyCharacterController[] characters;
        public GameObject camera;
        void Awake()
        {
            Director director = Director.getInstance();
            director.currentSceneController = this;
            userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
            characters = new MyCharacterController[6];
    
            camera = GameObject.Find("Main Camera");
            camera.transform.position = new Vector3(0,3,-15);
            loadResources();
        }
    
        public void loadResources()
        {
            GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
            water.name = "water";
    
            fromCoast = new CoastController("from");
            toCoast = new CoastController("to");
            boat = new BoatController();
    
            loadCharacter();
        }
    
        private void loadCharacter()
        {
            for (int i = 0; i < 3; i++)
            {
                MyCharacterController cha = new MyCharacterController("priest");
                cha.setName("priest" + i);
                cha.setPosition(fromCoast.getEmptyPosition());
                cha.getOnCoast(fromCoast);
                fromCoast.getOnCoast(cha);
    
                characters[i] = cha;
            }
    
            for (int i = 0; i < 3; i++)
            {
                MyCharacterController cha = new MyCharacterController("devil");
                cha.setName("devil" + i);
                cha.setPosition(fromCoast.getEmptyPosition());
                cha.getOnCoast(fromCoast);
                fromCoast.getOnCoast(cha);
    
                characters[i + 3] = cha;
            }
        }
    
    
        public void moveBoat()
        {
            if (boat.isEmpty())
                return;
            boat.Move();
            userGUI.status = check_game_over();
        }
    
        public void characterIsClicked(MyCharacterController characterCtrl)
        {
            if (characterCtrl.isOnBoat())
            {
                CoastController whichCoast;
                if (boat.get_to_or_from() == -1)
                { // to->-1; from->1
                    whichCoast = toCoast;
                }
                else
                {
                    whichCoast = fromCoast;
                }
    
                boat.GetOffBoat(characterCtrl.getName());
                characterCtrl.moveToPosition(whichCoast.getEmptyPosition());
                characterCtrl.getOnCoast(whichCoast);
                whichCoast.getOnCoast(characterCtrl);
    
            }
            else
            {                                   // character on coast
                CoastController whichCoast = characterCtrl.getCoastController();
    
                if (boat.getEmptyIndex() == -1)
                {       // boat is full
                    return;
                }
    
                if (whichCoast.get_to_or_from() != boat.get_to_or_from())   // boat is not on the side of character
                    return;
    
                whichCoast.getOffCoast(characterCtrl.getName());
                characterCtrl.moveToPosition(boat.getEmptyPosition());
                characterCtrl.getOnBoat(boat);
                boat.GetOnBoat(characterCtrl);
            }
            userGUI.status = check_game_over();
        }
    
        int check_game_over()
        {   // 0->not finish, 1->lose, 2->win
            int from_priest = 0;
            int from_devil = 0;
            int to_priest = 0;
            int to_devil = 0;
    
            int[] fromCount = fromCoast.getCharacterNum();
            from_priest += fromCount[0];
            from_devil += fromCount[1];
    
            int[] toCount = toCoast.getCharacterNum();
            to_priest += toCount[0];
            to_devil += toCount[1];
    
            if (to_priest + to_devil == 6)      // win
                return 2;
    
            int[] boatCount = boat.getCharacterNum();
            if (boat.get_to_or_from() == -1)
            {   // boat at toCoast
                to_priest += boatCount[0];
                to_devil += boatCount[1];
            }
            else
            {   // boat at fromCoast
                from_priest += boatCount[0];
                from_devil += boatCount[1];
            }
            if (from_priest < from_devil && from_priest > 0)
            {       // lose
                return 1;
            }
            if (to_priest < to_devil && to_priest > 0)
            {
                return 1;
            }
            return 0;           // not finish
        }
    
        public void restart()
        {
            boat.reset();
            fromCoast.reset();
            toCoast.reset();
            for (int i = 0; i < characters.Length; i++)
            {
                characters[i].reset();
            }
        }
    }
    

    游戏对象控制器:CoastController,BoatController和MyCharacterController实现方法类似。控制器被封装为类,内部包含了对象的实例化方法以及供SceneController获取信息、控制的方法,例如BoatController能够控制船的运动、角色的上下船绑定,以及为SceneController提供船上空位数量和空位号的查询方法。

     public class BoatController
        {
            readonly GameObject boat;
            readonly Moveable moveableScript;
            readonly Vector3 fromPosition = new Vector3(-5, 1, 0);
            readonly Vector3 toPosition = new Vector3(5, 1, 0);
            readonly Vector3[] from_positions;
            readonly Vector3[] to_positions;
    
            // change frequently
            int to_or_from; // to->-1; from->1
            MyCharacterController[] passenger = new MyCharacterController[2];
    
            public BoatController()
            {
                to_or_from = 1;
    
                from_positions = new Vector3[] { new Vector3(-5.5F, 1.65F, 0), new Vector3(-4.5F, 1.65F, 0) };
                to_positions = new Vector3[] { new Vector3(4.5F, 1.65F, 0), new Vector3(5.5F, 1.65F, 0) };
               
                boat = Object.Instantiate(Resources.Load("Perfabs/Boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
                boat.name = "boat";
    
                moveableScript = boat.AddComponent(typeof(Moveable)) as Moveable;
                boat.AddComponent(typeof(ClickGUI));
            }
    
    
            public void Move()
            {
                if (to_or_from == -1)
                {
                    moveableScript.setDestination(fromPosition);
                    to_or_from = 1;
                }
                else
                {
                    moveableScript.setDestination(toPosition);
                    to_or_from = -1;
                }
            }
    
            public int getEmptyIndex()
            {
                for (int i = 0; i < passenger.Length; i++)
                {
                    if (passenger[i] == null)
                    {
                        return i;
                    }
                }
                return -1;
            }
    
            public bool isEmpty()
            {
                for (int i = 0; i < passenger.Length; i++)
                {
                    if (passenger[i] != null)
                    {
                        return false;
                    }
                }
                return true;
            }
    
            public Vector3 getEmptyPosition()
            {
                Vector3 pos;
                int emptyIndex = getEmptyIndex();
                if (to_or_from == -1)
                {
                    pos = to_positions[emptyIndex];
                }
                else
                {
                    pos = from_positions[emptyIndex];
                }
                return pos;
            }
    
            public void GetOnBoat(MyCharacterController characterCtrl)
            {
                int index = getEmptyIndex();
                passenger[index] = characterCtrl;
            }
    
            public MyCharacterController GetOffBoat(string passenger_name)
            {
                for (int i = 0; i < passenger.Length; i++)
                {
                    if (passenger[i] != null && passenger[i].getName() == passenger_name)
                    {
                        MyCharacterController charactorCtrl = passenger[i];
                        passenger[i] = null;
                        return charactorCtrl;
                    }
                }
                Debug.Log("Cant find passenger in boat: " + passenger_name);
                return null;
            }
    
            public GameObject getGameobj()
            {
                return boat;
            }
    
            public int get_to_or_from()
            { // to->-1; from->1
                return to_or_from;
            }
    
            public int[] getCharacterNum()
            {
                int[] count = { 0, 0 };
                for (int i = 0; i < passenger.Length; i++)
                {
                    if (passenger[i] == null)
                        continue;
                    if (passenger[i].getType() == 0)
                    {   // 0->priest, 1->devil
                        count[0]++;
                    }
                    else
                    {
                        count[1]++;
                    }
                }
                return count;
            }
    

    CoastController实现河岸实例化,游戏对象上下岸状态切换,并为SceneController提供河岸空位号、空位位置、岸上物体移动方向查询的方法
    MyCharacterController实现6个角色的实例化,控制位置移动(上岸、上船),位置重置,并提供相关状态查询函数等

    2. Model
    物体对象包含在相关控制器中,使用代码生成,并进行参数初始化

    if (which_character == "priest")
                {
                    character = Object.Instantiate(Resources.Load("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                    characterType = 0;
                }
                else
                {
                    character = Object.Instantiate(Resources.Load("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                    characterType = 1;
                }
                
     coast = Object.Instantiate(Resources.Load("Perfabs/LeftCoast", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
     
     boat = Object.Instantiate(Resources.Load("Perfabs/Boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
    

    3. View
    UserGUI:定义用户看到的图形界面,包括reStart按钮,游戏结果。还可以通过UserAction接口提供用户交互

    public class UserGUI : MonoBehaviour
    {
        private UserAction action;
        public int status = 0;
        GUIStyle style;
        GUIStyle buttonStyle;
    
        void Start()
        {
            action = Director.getInstance().currentSceneController as UserAction;
    
            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.restart();
                }
            }
            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.restart();
                }
            }
        }
    }
    

    ClickGUI:通过UserAction接口,提供点击玩家点击物体时的响应

    public class ClickGUI : MonoBehaviour
    {
        UserAction action;
        MyCharacterController characterController;
    
        public void setController(MyCharacterController characterCtrl)
        {
            characterController = characterCtrl;
        }
    
        void Start()
        {
            action = Director.getInstance().currentSceneController as UserAction;
        }
    
        void OnMouseDown()
        {
            if (gameObject.name == "boat")
            {
                action.moveBoat();
            }
            else
            {
                action.characterIsClicked(characterController);
            }
        }
    }
    

游戏效果

Unity 3D ——空间与运动_第7张图片
Unity 3D ——空间与运动_第8张图片
Unity 3D ——空间与运动_第9张图片

演示视频

视频

你可能感兴趣的:(游戏开发,unity3d)