Unity3d_hw3_魔鬼与牧师v2

操作与总结

参考 Fantasy Skybox FREE 构建自己的游戏场景

  • 首先要去Assets store里找到对应的资源下载,在搜索框输入Fantasy Skybox FREE有如下结果,选择第一个下载,然后import导入到项目资源中,便可以使用这个资源包了。
  • Skybox顾名思义,天空盒,是用来模拟游戏场景中的天空效果的。由于scene和game的视角从某种意义上来说是固定的,所以要较好的模拟天空的效果,就需要把天空当成一个六面体的盒,在每个面上贴上对应的Texture,以此来模拟天空罩住游戏场景。
  • 下图是用了天空盒构建的场景图
  • 只有天空或许显得单调,我们可以再给它加个地形Terrain,地形的贴图以及后来的花草树木在新版本的unity里面是没有自带的,也需要自行到Assets store里面下载导入。
  • 以下是最终的效果图(water environment还没来得及做)

写一个简单的总结,总结游戏对象的使用

  • 游戏对象可以使用unity自带的正方体,圆柱体,球体等基础的资源构建
  • 游戏对象也可以通过基础游戏对象组合构建之后做成预制,以便后来重复使用
  • 游戏对象有许多属性,比较重要的有Transform属性,和纹理材质等
  • 游戏对象可以添加许多组件,来实现不同的特定功能,使效果更加炫酷
  • 游戏对象既可以在图形界面实例化,也可以通过代码实例化
  • 游戏对象可以通过添加对应的脚本来实现不同的行为
  • 等等

    编程实践—牧师与魔鬼

  • 本项目Github地址——传送门
  • 鉴于上次写的牧师与魔鬼v1版本没有来得及写博客,这次顺便补上,以下介绍从基础MVC版本到动作分离版的过渡

    牧师与魔鬼v1——基础MVC架构版

  • MVC架构,简单来说目的是使代码分层,解耦,使每个类尽可能地行使单一的职责,以便于之后维护和扩展;另外,模块化的架构还可以使刚开始编程时尽可能少的考虑细节,使代码编写趋于简单。

    MVC架构定义:MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。——百度百科

  • 游戏规则:很经典的游戏,应该大家以前都玩过。游戏初始界面是三个牧师和三个魔鬼都在同一侧河岸,准备过河,但只有一条船且只能容纳两个对象,船必须至少有一个对象才能开动。游戏目的是采取合适的策略将全部牧师和魔鬼安全送到对岸,如果途中出现某一岸魔鬼多于牧师的情况,则游戏失败。
  • 先贴一下此版本的效果图
    • 启动界面
    • 胜利界面
    • 失败界面
  • 本项目分离出来的MVC框架
    Model: Shore, Boat, Item
    View: UserGUI, ClickGUI
    Controller: BoatControl, ShoreControl, ItemControl, FirstControl, Director
  • 以下是各个类的简要作用,代码较多就不一一贴上来了,可前往Github查看具体代码
  • Model——模型(可预制)
    • Shore:岸的实例
    • Boat:船的实例
    • Item:对象(魔鬼或牧师)的实例
  • View——视图
    • UserGUI
      用户交互界面
    • ClickGUI
      响应点击事件
  • Controler——控制器

    • Director:导演,总指挥,单例模式

      public class Diretor : System.Object
      {
        private static Diretor _instance;
        public SceneControl sceneCtrl { get; set; }
      
        public static Diretor getInstance()
        {
            if (_instance == null) return _instance = new Diretor();
            else return _instance;
        }
      }
      
    • FirstControl:第一场景控制器,是唯一加到对象上的脚本
    • BoatControl:船的控制器,包括记录船上空余位置,船的属性和状态等
    • ShoreControl:岸的控制器,包括记录岸上空余位置,对象上岸离岸等
    • ItemControl:对象的控制器,包括对象的实例化,控制对象的移动
  • 一些接口
    • SceneControl:负责整个场景的实例化
      public interface SceneControl
      {
        void LoadPrefabs();
      }
      
    • UserAction:控制动作
      public interface UserAction
      {
        void BoatMove();
        void Restart();
        void ItemClick(ItemControl itemCtrl);
      }
      
  • 提一下主要的动作控制类——Moveable

      public class Moveable : MonoBehaviour
      {
          public float time = 3; //抛物时间
          public float g = -10; //重力加速度
          private Vector3 v0; //初速度
          private Vector3 Gravity; //由重力产生的垂直速度
          private float dTime = 0; //记录运动时间
          private float fixUpdateStep = 0.1f; //设置运动速度
    
          private static float speed = 20; //船的移速
          public int status; //运动状态
          Vector3 des; //终点
    
          void Update()
          {
    
              if (status == 1) // 实现对象抛物线运动
              {
                  Gravity.y = g * (dTime += fixUpdateStep);
                  transform.Translate(v0 * fixUpdateStep);
                  transform.Translate(Gravity * fixUpdateStep);
                  if (dTime >= time - fixUpdateStep)
                  {
                      transform.position = des;
                      status = 0;
                      dTime = 0;
                  }
              }
              else if (status == 2) // 实现船的直线运动
              {
                  transform.position = Vector3.MoveTowards(transform.position, des, speed * fixUpdateStep);
                  if (transform.position == des) status = 0;
              }
    
          }
          public void MoveTo(Vector3 target) // 设置运动的终点,并以此计算抛物线的初速度
          {
              des = target;
              status = 1;
              if (des.y == transform.position.y) status = 2;
              //Debug.Log("From:" + transform.position);
              v0 = new Vector3((des.x - transform.position.x) / time,
                  (des.y - transform.position.y) / time - 0.5f * g * time, (des.z - transform.position.z) / time);
              Gravity = Vector3.zero;
              //Debug.Log("v0:" + v0);
              //Debug.Log("Target:" + target);                       
          }
          public void Reset()
          {
              status = 0;
              dTime = 0;
          }
      }
    

    牧师与魔鬼v2——动作分离版

    最主要的改动——利用ActionManager的模板,实现Movable类的转移
    这里不再介绍MVC,直接开始介绍动作分离。

  • 先上效果图,改进了UI,现学现用加了一个天空盒


  • 添加的文件——Action,有关动作管理的类和实现全都转移到这个文件里,模板基本套用ppt上现成代码,略作修改
  • 动作的分离,以下改动主要是将v1遗留的耦合动作剔除

    • FirstControl里的改动

      private ActionManager MyActionManager; // add in v2
      
      // Use this for initialization
      void Start () {
        Diretor diretor = Diretor.getInstance();
        diretor.sceneCtrl = this;
        itemCtrls = new ItemControl[6];
        LoadPrefabs();
        user = gameObject.AddComponent<UserGUI>();
        user.Restart();
        MyActionManager = gameObject.AddComponent<ActionManager>() as ActionManager; // add in v2
      }
      
      public void BoatMove()
      {
        Debug.Log("BoatMove");
        if (boat.IsEmpty()) return;
        //boat.Move();
        MyActionManager.MoveBoat(boat); // add in v2
        user.step++;
        user.status = Check();
      }
      public void ItemClick(ItemControl itemCtrl)
      {
        Debug.Log("ItemClick");
        if (itemCtrl.isOnBoat)
        {
            ShoreControl side;
            if (boat.status == 1) side = fromShore;
            else side = toShore;
      
            Debug.Log(side.GetEmptyPosition());
            boat.GetOffBoat(itemCtrl);
            //itemCtrl.MoveTo(side.GetEmptyPosition());
            MyActionManager.MoveItem(itemCtrl, side.GetEmptyPosition()); // add in v2
            itemCtrl.GetOnShore(side);
            side.GetOnShore(itemCtrl);
            user.step++;
        }
        else
        {
            if (boat.IsFull()) return;
      
            Debug.Log(itemCtrl.item.name + " getting on boat");
            ShoreControl side = itemCtrl.shoreCtrl;
            if (side.status != boat.status) return;
      
            side.GetOffShore(itemCtrl.item.name);
            //Debug.Log(MyActionManager);
            //Debug.Log(itemCtrl);
            //Debug.Log(boat);
            //itemCtrl.MoveTo(boat.GetOnBoat(itemCtrl));
            MyActionManager.MoveItem(itemCtrl, boat.GetOnBoat(itemCtrl)); // add in v2
            itemCtrl.GetOnBoat(boat);
            user.step++;
        }
        user.status = Check();
      }
      
    • BoatControl的改动

      public class BoatControl
      {
        public GameObject boat;
        //public Moveable moveable; // remove in v2
        public Vector3 from = new Vector3(10, 2, 0);
        public Vector3 to = new Vector3(-10, 2, 0);
        public Vector3[] froms;
        public Vector3[] tos;
        public int status; // from = 1, to = 0
        public ItemControl leftSeat;
        public ItemControl rightSeat;
      
        public static float speed = 50; // add in v2
      
        public BoatControl()
        {
            status = 1;
            leftSeat = null;
            rightSeat = null;
      
            froms = new Vector3[] { new Vector3(9, 3, 0), new Vector3(11, 3, 0) };
            tos = new Vector3[] { new Vector3(-11, 3, 0), new Vector3(-9, 3, 0) };
      
            boat = (GameObject)Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), from, Quaternion.identity, null);           
            boat.name = "Boat";
      
            //moveable = boat.AddComponent(typeof(Moveable)) as Moveable; // remove in v2
            boat.AddComponent(typeof(ClickGUI)); // add click event
        }
        //public void Move() // remove in v2
        //{
        //    if (status == -1) moveable.MoveTo(from);
        //    else moveable.MoveTo(to);
        //    status = -status;
        //}
        public bool IsEmpty()
        {
            if (leftSeat == null && rightSeat == null) return true;
            else return false;
        }
        public bool IsFull()
        {
            if (leftSeat != null && rightSeat != null) return true;
            else return false;
        }
        public Vector3 GetOnBoat(ItemControl item)
        {
            if (leftSeat == null)
            {
                leftSeat = item;
                return status == -1 ? tos[0] : froms[0];
            }
            else if (rightSeat == null)
            {
                rightSeat = item;
                return status == -1 ? tos[1] : froms[1];
            }
            else return Vector3.zero;
        }
        public ItemControl GetOffBoat(ItemControl item)
        {
            // to decide which item to get off
            ItemControl temp = null;
            if (leftSeat == item)
            {
                temp = leftSeat;
                leftSeat = null;
            }
            else if (rightSeat == item)
            {
                temp = rightSeat;
                rightSeat = null;
            }
            return temp;
        }
        public int GetItemNum(int type) // get the num of item, priest or devil
        {
            int count = 0;
            if (leftSeat != null && leftSeat.itemType == type) count++;
            if (rightSeat != null && rightSeat.itemType == type) count++;
            return count;
        }
        public void Reset()
        {
            //moveable.Reset(); // remove in v2
            //if (status == -1) Move(); // remove in v2
            leftSeat = null;
            rightSeat = null;
        }
      }
      
    • ItemControl的改动

      public class ItemControl
      {
        public GameObject item { get; set; } // the instance
        public int itemType { get; set; } // the type of the item
        public ClickGUI clickGUI; // manage click event
        //public Moveable moveable; // remove in v2
        public bool isOnBoat;
        public ShoreControl shoreCtrl;
      
        public ItemControl(string type) //Instantiate
        {
            if (type == "Priest")
            {
                item = Object.Instantiate(Resources.Load("Prefabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                itemType = 0;
            }
            else
            {
                item = Object.Instantiate(Resources.Load("Prefabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                itemType = 1;
            }
            //moveable = item.AddComponent(typeof(Moveable)) as Moveable; // remove in v2
      
            clickGUI = item.AddComponent(typeof(ClickGUI)) as ClickGUI; // add click event
            clickGUI.itemCtrl = this;
        }
        //public void MoveTo(Vector3 des) // remove in v2
        //{
        //    moveable.MoveTo(des);
        //}
        public void GetOnBoat(BoatControl boatCtrl)
        {
            shoreCtrl = null;
            item.transform.parent = boatCtrl.boat.transform;
            isOnBoat = true;
        }
        public void GetOnShore(ShoreControl side)
        {
            shoreCtrl = side;
            item.transform.parent = null;
            isOnBoat = false;
        }
        public void Reset()
        {
            //moveable.Reset(); // remove in v2
            ShoreControl fromShore = ((FirstControl)Diretor.getInstance().sceneCtrl).fromShore;
            GetOnShore(fromShore);
            item.transform.position = fromShore.GetEmptyPosition();
            fromShore.GetOnShore(this);
        }
      }
      
  • Moveable的转移
    这里简要提一下抛物线从Moveable转移到ActionManager的处理,原本在Movable里面是将抛物线分成一小段一小段的直线位移,利用固定帧的刷新来实现抛物线的曲线效果,但是因为Moveable既管理船的动作又管理对象的动作,就避免不了要用状态来区分到底是要执行谁的动作,这样看起来代码就不是很美观,可读性也有所下降。而在ActionManager中,虽然也是同时管理船的动作和对象的动作,但是却可以用单运动组合运动来区分两者,单运动的处理就不用再赘述了,关于组合运动这里有个重要的方法就是SequenceAction,要知道把动作看成一小段一小段的直线运动的组合,就能实现几乎所有的曲线运动,也就是说,ActionManager完美解决了所有的曲线运动。在这里,虽然我也是将抛物线分成一小段一小段的直线运动,但并不是像Moveable里一样直接使用帧刷新来完成运动,而是通过将分段后的动作顺序放入一个Action的List中,交由SequenceAction来处理多个动作的连贯组合,就很好地避免了Moveable里面区分状态的尴尬局面。
    ` csharp
    public class ActionManager : SSActionManager
    {
    public void MoveBoat(BoatControl boatCtrl)
    {

      SSMoveToAction action;
      if (boatCtrl.status == -1) 
          action = SSMoveToAction.GetSSMoveToAction(boatCtrl.from, BoatControl.speed);
      else action = SSMoveToAction.GetSSMoveToAction(boatCtrl.to, BoatControl.speed);
      boatCtrl.status = -boatCtrl.status;
      AddAction(boatCtrl.boat, action, this);
    

    }

    public void MoveItem(ItemControl itemCtrl, Vector3 finalDes)
    {

      //Debug.Log("enter MoveItem!");
      float time = 3;
      float g = -10;
      Vector3 v0;
      float vy_ByGravity = 0;
      float stepTime = 0.1f;
      Vector3 currentDes = itemCtrl.item.transform.position;
    
      List<SSAction> divide = new List<SSAction>();
    
      // the des here is the final des
      v0 = new Vector3((finalDes.x - itemCtrl.item.transform.position.x) / time,
          (finalDes.y - itemCtrl.item.transform.position.y) / time - 0.5f * g * time, (finalDes.z - itemCtrl.item.transform.position.z) / time);
      //Debug.Log(v0);
      //Debug.Log(time / stepTime);
    
    // divide the curve to many parts
    for (int i = 0; i < time / stepTime - 1; i++)
    {
        //Debug.Log(divide[i]);
        //Debug.Log(currentDes);
        // change the vy
        vy_ByGravity += g * stepTime;
        // set current des
        currentDes += v0 * stepTime;
        currentDes.y += vy_ByGravity * stepTime;
        // get the current speed
        float currentSpeed = Mathf.Sqrt(v0.x * v0.x + (v0.y + vy_ByGravity) * (v0.y + vy_ByGravity));
        // add one of the movements
        SSAction temp = SSMoveToAction.GetSSMoveToAction(currentDes, currentSpeed * 10);
        divide.Add(temp);
    }
    SSAction seqAction = SequenceAction.GetSequenceAction(1, 0, divide);
    AddAction(itemCtrl.item, seqAction, this);
}

}
`