3D游戏编程与设计_HW4

 

1、基本操作演练【建议做】

  • 下载 Fantasy Skybox FREE, 构建自己的游戏场景
  • 写一个简单的总结,总结游戏对象的使用

   构建自己的游戏场景:

游戏对象的使用:

在3DUnity 中,游戏对象通过手动添加到场景中,然后可以配置好它的大小旋转角度等属性,通过添加Material 等组件来增加游戏对象的属性。之后可以编写脚本,然后绑定到该有对象上,使其完成一定的动作或事件的处理。

之后可以在将制作好的对象存为预制,这样有助于处理多个相同的对象。在脚本中通过实例化预制好的对象,可以快捷方便的的生成多个相似的对象,同时可以很方便的同时配置多个对象。

同时多个对象可以通过组合模式,把多个对象组合成一个对象,形成树状结构。同时当组成复合对象的多个简单对象实现了相同的接口时,就可以把复合对象当做一个简单对象来处理了。

当多个对象可能会执行相同或者类似的动作时,可以把动作分离出来单独做一个类。然后编写调用动作的接口,然后让对象来调用这些动作,这样就可以不用为每个对象编写动作代码了。

 

2、编程实践

  • 牧师与魔鬼 动作分离版
    • 【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束

在动作分离版,把牧师和魔鬼的归为一种游戏对象,因为他们的动作一致。之后牧师或魔鬼(统称为物体)的移动关系到,岸,船两种对象,所以要实现岸和船的控制器。物体离岸上船离船上岸的动作,需要得到岸和船的空位,以及船的位置。所以物体的实现代码如下:

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

public class FirstController : MonoBehaviour, ISceneController, UserAction
{
    InteracteGUI UserGUI;
    public CoastController fromCoast;
    public CoastController toCoast;
    public BoatController boat;
    private GameObjects[] GameObjects;

    public Referee referee_=new Referee();

    private FirstSceneActionManager FCActionManager;

    void Start()
    {
        FCActionManager = GetComponent<FirstSceneActionManager>();
    }

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentScenceController = this;
        UserGUI = gameObject.AddComponent<InteracteGUI>() as InteracteGUI;
        GameObjects = new GameObjects[6];
        LoadResources();
    }

    public void LoadResources()
    {
        fromCoast = new CoastController("from");
        toCoast = new CoastController("to");
        boat = new BoatController();
        GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), new Vector3(0, 0.5F, 0), Quaternion.identity, null) as GameObject;
        water.name = "water";
        for (int i = 0; i < 3; i++)
        {
            GameObjects s = new GameObjects("priest");
            s.setName("priest" + i);
            s.setPosition(fromCoast.getEmptyPosition());
            s.getOnCoast(fromCoast);
            fromCoast.getOnCoast(s);
            GameObjects[i] = s;
        }

        for (int i = 0; i < 3; i++)
        {
            GameObjects s = new GameObjects("devil");
            s.setName("devil" + i);
            s.setPosition(fromCoast.getEmptyPosition());
            s.getOnCoast(fromCoast);
            fromCoast.getOnCoast(s);
            GameObjects[i + 3] = s;
        }
    }

    public void ObjectIsClicked(GameObjects Objects)//物体的点击事件
    {
        if (FCActionManager.Complete == SSActionEventType.Started) return;
        if (Objects.isOnBoat())//物体在船上时
        {
            CoastController whichCoast;
            if (boat.get_State() == -1)//得到船的位置
            { // to->-1; from->1
                whichCoast = toCoast;
            }
            else
            {
                whichCoast = fromCoast;
            }

            boat.GetOffBoat(Objects.getName());//下船,
            FCActionManager.GameObjectsMove(Objects,whichCoast.getEmptyPosition());//移动
            Objects.getOnCoast(whichCoast);//决定上哪个岸,就去拿到岸的控制器
            whichCoast.getOnCoast(Objects);//上岸

        }
        else//在岸上
        {
            Debug.Log("On Coast!");
            CoastController whichCoast = Objects.getCoastController(); // 得到岸的控制器

            if (boat.getEmptyIndex() == -1)
            {      
                return;
            }

            if (whichCoast.get_State() != boat.get_State())   // 船不在岸边
                return;

            whichCoast.getOffCoast(Objects.getName());//离岸
            FCActionManager.GameObjectsMove(Objects, boat.getEmptyPosition());//移动
            Objects.getOnBoat(boat);//拿到穿的控制权
            boat.GetOnBoat(Objects);//上船
        }
        //UserGUI.SetState = Check();
    }

    public void MoveBoat()//船的点击事件
    {   UserGUI.SetState = referee_.Check(fromCoast, toCoast, boat);
        if (FCActionManager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
        FCActionManager.BoatMove(boat);

    }



    public void Restart()
    {
        fromCoast.reset();
        toCoast.reset();
        foreach (GameObjects gameobject in GameObjects)
        {
            gameobject.reset();
        }
        boat.reset();
    }
}

之后就要去实现岸的控制器,在逻辑上要记录物体的是否在岸上,在岸上的第几个位置,通过一个数组来记录。这样就可以得到有几个物体在岸上,以及得到一个空位可以使物体上岸。代码如下:

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

public class CoastController
{
    readonly GameObject coast;
    readonly Vector3 from_pos = new Vector3(9, 1, 0);
    readonly Vector3 to_pos = new Vector3(-9, 1, 0);
    readonly Vector3[] positions;
    readonly int State;    // to->-1, from->1

    GameObjects[] passengerPlaner;

    public CoastController(string _State)
    {
        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)};

        passengerPlaner = new GameObjects[6];

        if (_State == "from")
        {
            coast = Object.Instantiate(Resources.Load("Perfabs/Ston", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
            coast.name = "from";
            State = 1;
        }
        else
        {
            coast = Object.Instantiate(Resources.Load("Perfabs/Ston", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;
            coast.name = "to";
            State = -1;
        }
    }

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

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

    public void getOnCoast(GameObjects ObjectControl)
    {
        int index = getEmptyIndex();
        passengerPlaner[index] = ObjectControl;
    }

    public GameObjects getOffCoast(string passenger_name)
    {   // 0->priest, 1->devil
        for (int i = 0; i < passengerPlaner.Length; i++)
        {
            if (passengerPlaner[i] != null && passengerPlaner[i].getName() == passenger_name)
            {
                GameObjects charactorCtrl = passengerPlaner[i];
                passengerPlaner[i] = null;
                return charactorCtrl;
            }
        }
        Debug.Log("cant find passenger on coast: " + passenger_name);
        return null;
    }

    public int get_State()
    {
        return State;
    }

    public int[] GetobjectsNumber()
    {
        int[] count = { 0, 0 };
        for (int i = 0; i < passengerPlaner.Length; i++)
        {
            if (passengerPlaner[i] == null)
                continue;
            if (passengerPlaner[i].getType() == 0)
            {   // 0->priest, 1->devil
                count[0]++;
            }
            else
            {
                count[1]++;
            }
        }
        return count;
    }

    public void reset()
    {
        passengerPlaner = new GameObjects[6];
    }

}

同样的实现船的控制器,同样在逻辑上采用一个数组来记录船上是否有空位,是否有物体在船上等等,实现代码如下:

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

public class BoatController
{
    readonly GameObject boat;
    readonly Vector3 fromPosition = new Vector3(5, 1, 0);
    readonly Vector3 toPosition = new Vector3(-5, 1, 0);
    readonly Vector3[] from_positions;
    readonly Vector3[] to_positions;

    int State; // to->-1; from->1
    GameObjects[] passenger = new GameObjects[2];
    int Speed = 10;
    int MovingState = -1; // Move = 1;Not Move = -1;

    public BoatController()
    {
        State = 1;
        MovingState = -1;
        from_positions = new Vector3[] { new Vector3(4.5F, 1.5F, 0), new Vector3(5.5F, 1.5F, 0) };
        to_positions = 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)), fromPosition, Quaternion.identity, null) as GameObject;
        boat.name = "boat";
        boat.AddComponent(typeof(ClickGUI));
    }

    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 (State == -1)
        {
            pos = to_positions[emptyIndex];
        }
        else
        {
            pos = from_positions[emptyIndex];
        }
        return pos;
    }

    public void GetOnBoat(GameObjects ObjectControl)
    {
        int index = getEmptyIndex();
        passenger[index] = ObjectControl;
    }

    public GameObjects GetOffBoat(string passenger_name)
    {
        for (int i = 0; i < passenger.Length; i++)
        {
            if (passenger[i] != null && passenger[i].getName() == passenger_name)
            {
                GameObjects charactorCtrl = passenger[i];
                passenger[i] = null;
                return charactorCtrl;
            }
        }
        return null;
    }

    public GameObject GetGameObject()
    {
        return boat;
    }

    public void ChangeState()
    {
        State = -State;
    }

    public int get_State()
    { // to->-1; from->1
        return State;
    }

    public int[] GetobjectsNumber()
    {
        int[] count = { 0, 0 };// [0]->priest, [1]->devil
        for (int i = 0; i < passenger.Length; i++)
        {
            if (passenger[i] == null)
                continue;
            if (passenger[i].getType() == 0)
            {
                count[0]++;
            }
            else
            {
                count[1]++;
            }
        }
        return count;
    }

    public Vector3 GetDestination()
    {
        if (State == 1) return toPosition;
        else return fromPosition;
    }

    public int GetMoveSpeed()
    {
        return Speed;
    }

    public void reset()
    {
        State = 1;
        boat.transform.position = fromPosition;
        passenger = new GameObjects[2];
        MovingState = -1;
    }

    public int GetMovingState()
    {
        return MovingState;
    }

    public void ChangeMovingstate()
    {
        MovingState = -MovingState;
    }
}

当控制器要做的事都定义实现好了,之后,要实现一个场景的控制器来控制所有的控制器,加载场景资源,并实例化控制器,来实现控制器的调用逻辑,代码如下:

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

public class FirstController : MonoBehaviour, ISceneController, UserAction
{
    InteracteGUI UserGUI;
    public CoastController fromCoast;
    public CoastController toCoast;
    public BoatController boat;
    private GameObjects[] GameObjects;

    public Referee referee_=new Referee();

    private FirstSceneActionManager FCActionManager;

    void Start()
    {
        FCActionManager = GetComponent<FirstSceneActionManager>();
    }

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentScenceController = this;
        UserGUI = gameObject.AddComponent<InteracteGUI>() as InteracteGUI;
        GameObjects = new GameObjects[6];
        LoadResources();
    }

    public void LoadResources()
    {
        fromCoast = new CoastController("from");
        toCoast = new CoastController("to");
        boat = new BoatController();
        GameObject water = Instantiate(Resources.Load("Perfabs/Water", typeof(GameObject)), new Vector3(0, 0.5F, 0), Quaternion.identity, null) as GameObject;
        water.name = "water";
        for (int i = 0; i < 3; i++)
        {
            GameObjects s = new GameObjects("priest");
            s.setName("priest" + i);
            s.setPosition(fromCoast.getEmptyPosition());
            s.getOnCoast(fromCoast);
            fromCoast.getOnCoast(s);
            GameObjects[i] = s;
        }

        for (int i = 0; i < 3; i++)
        {
            GameObjects s = new GameObjects("devil");
            s.setName("devil" + i);
            s.setPosition(fromCoast.getEmptyPosition());
            s.getOnCoast(fromCoast);
            fromCoast.getOnCoast(s);
            GameObjects[i + 3] = s;
        }
    }

    public void ObjectIsClicked(GameObjects Objects)//物体的点击事件
    {
        if (FCActionManager.Complete == SSActionEventType.Started) return;
        if (Objects.isOnBoat())//物体在船上时
        {
            CoastController whichCoast;
            if (boat.get_State() == -1)//得到船的位置
            { // to->-1; from->1
                whichCoast = toCoast;
            }
            else
            {
                whichCoast = fromCoast;
            }

            boat.GetOffBoat(Objects.getName());//下船,
            FCActionManager.GameObjectsMove(Objects,whichCoast.getEmptyPosition());//移动
            Objects.getOnCoast(whichCoast);//决定上哪个岸,就去拿到岸的控制器
            whichCoast.getOnCoast(Objects);//上岸

        }
        else//在岸上
        {
            Debug.Log("On Coast!");
            CoastController whichCoast = Objects.getCoastController(); // 得到岸的控制器

            if (boat.getEmptyIndex() == -1)
            {      
                return;
            }

            if (whichCoast.get_State() != boat.get_State())   // 船不在岸边
                return;

            whichCoast.getOffCoast(Objects.getName());//离岸
            FCActionManager.GameObjectsMove(Objects, boat.getEmptyPosition());//移动
            Objects.getOnBoat(boat);//拿到穿的控制权
            boat.GetOnBoat(Objects);//上船
        }
        //UserGUI.SetState = Check();
    }

    public void MoveBoat()//船的点击事件
    {   UserGUI.SetState = referee_.Check(fromCoast, toCoast, boat);
        if (FCActionManager.Complete == SSActionEventType.Started || boat.isEmpty()) return;
        FCActionManager.BoatMove(boat);

    }



    public void Restart()
    {
        fromCoast.reset();
        toCoast.reset();
        foreach (GameObjects gameobject in GameObjects)
        {
            gameobject.reset();
        }
        boat.reset();
    }
}


这样,整个游戏的大致的逻辑便实现了,下面就是为物体和船添加动作了,船的动作简单,只有一种直线的移动方式,但是物体上船时的动作和上岸时的动作是要拐弯的,不能直线移动过去,所以每次移动要分成两次完成,而且上船和上岸的顺序是相反的,所以采用一个队列来把分成两次的动作按照应该的顺序存储起来,然后依次执行对应的动作即可,为节约空间资源,每个动作都是SSAction类的一个实例,执行完动作后要把实例销毁。代码如下:

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

public class SSAction : ScriptableObject {

    public bool enable = true;
    public bool destroy = false;

    public GameObject gameObject;
    public Transform transform;
    public SSActionCallback CallBack;

    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
}

public class CCMoveToAction : SSAction//Object移动的动作,从当前位置移动到目标位置
{
    public Vector3 target;
    public float speed;

    private CCMoveToAction() { }//
    public static CCMoveToAction getAction(Vector3 target, float speed)
    {
        CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
        action.target = target;
        action.speed = speed;
        return action;
    }

    public override void Update()//位置移动
    {
        this.transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
        if (transform.position == target)
        {
            destroy = true;
            CallBack.SSActionCallback(this);
        }
    }

    public override void Start()
    {

    }
}

public class CCSequenceAction : SSAction, SSActionCallback//对象被点击一次,会分成一次或两次动作来完成,记录动作的顺序
{
    public List<SSAction> sequence;
    public int repeat = 1; // 1->only do it for once, -1->repeat forever
    public int currentActionIndex = 0;

    public static CCSequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
    {
        CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
        action.sequence = sequence;
        action.repeat = repeat;
        action.currentActionIndex = currentActionIndex;
        return action;
    }

    public override void Update()
    {
        if (sequence.Count == 0) return;//动作队列为空
        if (currentActionIndex < sequence.Count)
        {
            sequence[currentActionIndex].Update();//执行动作队列里的动作
        }
    }

    public void SSActionCallback(SSAction source)//执行完队列尾的动作后,开始执行队列头的动作
    {
        source.destroy = false;
        this.currentActionIndex++;
        if (this.currentActionIndex >= sequence.Count)
        {
            this.currentActionIndex = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0)
            {
                this.destroy = true;
                this.CallBack.SSActionCallback(this);
            }
        }
    }

    public override void Start()
    {
        foreach (SSAction action in sequence)//动作绑定到对象上
        {
            action.gameObject = this.gameObject;
            action.transform = this.transform;
            action.CallBack = this;
            action.Start();
        }
    }

    void OnDestroy()
    {
        foreach (SSAction action in sequence)//销毁实例
        {
            DestroyObject(action);
        }
    }
}

public class SSActionManager : MonoBehaviour
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    private List<SSAction> waitingToAdd = new List<SSAction>();
    private List<int> watingToDelete = new List<int>();

    protected void Update()
    {
        foreach (SSAction ac in waitingToAdd)//在待加队列要加入字典的动作,加入进去
        {
            actions[ac.GetInstanceID()] = ac;
        }
        waitingToAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)//字典里要删除的动作加入到待删队列
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                watingToDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in watingToDelete)//删除待删队列里的实例,释放空间
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        watingToDelete.Clear();
    }

    public void addAction(GameObject gameObject, SSAction action, SSActionCallback ICallBack)//把动作加入到待加队列中区
    {
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.CallBack = ICallBack;
        waitingToAdd.Add(action);
        action.Start();
    }
}

之后在为动作添加一个管理类,来决定什么时候以什么顺序来执行动作。代码如下:
 

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

public class FirstSceneActionManager : SSActionManager, SSActionCallback//船和牧师魔鬼的动作执行函数
{
    public SSActionEventType Complete = SSActionEventType.Completed;

    public void BoatMove(BoatController Boat)
    {
        Complete = SSActionEventType.Started;
        CCMoveToAction action = CCMoveToAction.getAction(Boat.GetDestination(), Boat.GetMoveSpeed());
        addAction(Boat.GetGameObject(), action, this);
        Boat.ChangeState();
    }

    public void GameObjectsMove(GameObjects GameObject, Vector3 Destination)
    {
        Complete = SSActionEventType.Started;
        Vector3 CurrentPos = GameObject.GetPosition();
        Vector3 MiddlePos = CurrentPos;//移动时的需要转弯的位置
        if (Destination.y > CurrentPos.y)
        {
            MiddlePos.y = Destination.y;
        }
        else
        {
            MiddlePos.x = Destination.x;
        }
        SSAction action1 = CCMoveToAction.getAction(MiddlePos, GameObject.GetMoveSpeed());
        SSAction action2 = CCMoveToAction.getAction(Destination, GameObject.GetMoveSpeed());
        SSAction seqAction = CCSequenceAction.getAction(1, 0, new List<SSAction> { action1, action2 });//加入到动作序列里去,并执行动作
        this.addAction(GameObject.GetGameobject(), seqAction, this);
    }

    public void SSActionCallback(SSAction source)
    {
        Complete = SSActionEventType.Completed;
    }
}

最后使用了一个裁判类来决定当前游戏的状态,当每次船移动时,都调用该类的函数来判断游戏当前的状态,代码如下:

using UnityEngine;
using System.Collections;

public class Referee
{
	// Use this for initialization
    public int Check(CoastController fromCoast,CoastController toCoast,BoatController boat){
        int from_priest = 0;
        int from_devil = 0;
        int to_priest = 0;
        int to_devil = 0;

        int[] fromCount = fromCoast.GetobjectsNumber();
        from_priest += fromCount[0];
        from_devil += fromCount[1];

        int[] toCount = toCoast.GetobjectsNumber();
        to_priest += toCount[0];
        to_devil += toCount[1];

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

        int[] boatCount = boat.GetobjectsNumber();
        if (from_priest < from_devil && from_priest > 0)
        {       // lose
            return 1;
        }
        return 0;           
    }
}

剩余的就是用户交互的 GUI 的配置了,配置完成后,便完成了该游戏。

github 地址:

https://github.com/xutaiqi/3DUnity_HW3.git

项目传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值