3D游戏学习 游戏对象与图形基础

1. 基本操作演练

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

__游戏对象__是 Unity 中的基础对象,表示角色、道具和景物。它们本身并没有取得多大作为,但它们充当__组件__的容器,而组件可实现真正的功能。(例如,通过将光源组件附加到游戏对象来创建光源对象。)

游戏对象是许多不同组件的容器。默认情况下,所有游戏对象都自动拥有__变换__(Transform)组件。这是因为变换组件可指示游戏对象的位置以及是如何旋转和缩放的。如果没有变换组件,游戏对象将在世界中没有位置。

__组件__是游戏中对象和行为的基本要素。它们是每个__游戏对象__的功能单元。可以将任意数量的组件或组件的组合附加到单个游戏对象。某些组件与其他组件结合使用效果最佳。例如,刚体可与任何碰撞体 (Collider) 配合使用。刚体通过 NVIDIA PhysX 物理引擎来控制变换组件,而碰撞体允许刚体与其他碰撞体碰撞和交互。

创建脚本并将脚本附加到__游戏对象__时,脚本将显示在__游戏对象__的 Inspector 中,就像内置__组件__一样。这是因为,将脚本保存到项目后,脚本也变成了__组件__。

2. 编程实践——牧师与魔鬼 动作分离版

2.1 设计说明

相较于上一版本,该版本主要做了如下改动:

  • 把每个需要移动的游戏对象的移动方法提取出来,建立一个动作管理器来管理不同的移动方法。
  • 在上一个版本中,每一个可移动的游戏对象都有相应的移动脚本,当游戏对象需要移动时候,游戏对象自己调用相应脚本中的方法让自己移动。而该版本则剥夺了游戏对象自己调用动作的能力,建立一个动作管理器,通过场景控制器把需要移动的游戏对象传递给动作管理器,让动作管理器去移动游戏对象。
  • 增加裁判类,使用裁判类中的方法判断游戏是否结束,当游戏结束时通知场景控制器。

改进后的动作管理部分UML类图如下:

UML类图(部分)

2.2 动作管理类实现

2.2.1 动作事件接口 ISSActionCallback

动作、__动作管理器__的接口,当动作完成时,动作会调用这个接口的方法,通知其管理者该动作已完成(便于动作管理器处理下一动作)。

public interface ISSActionCallback {
    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null);
}
2.2.2 动作管理器基类 SSActionManager

SSActionManager 对场景中动作的执行进行管理,可以通过给 SSActionManager 传递游戏对象,使对象执行一定的动作或组合动作,并且控制着动作的切换。SSActionManager 继承了 ISSActionCallback 接口,通过这个接口,当动作或组合动作完成时,动作类会稿纸通知 SSActionManager,然后 SSActionManager 去决定如何执行下一个动作。

public class SSActionManager : MonoBehaviour, ISSActionCallback                      //action管理器
{

    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合,int为key,SSAction为value
    private List<SSAction> waitingAdd = new List<SSAction>();                       //等待去执行的动作列表
    private List<int> waitingDelete = new List<int>();                              //等待删除的动作的key          

    protected void Update() {
        foreach(SSAction ac in waitingAdd) {
            actions[ac.GetInstanceID()] = ac;                                      //获取动作实例的ID作为key
        }
        waitingAdd.Clear();

        foreach(KeyValuePair<int, SSAction> kv in actions) {
            SSAction ac = kv.Value;
            if(ac.destroy) {
                waitingDelete.Add(ac.GetInstanceID());
            }
            else if(ac.enable) {
                ac.Update();
            }
        }

        foreach(int key in waitingDelete) {
            SSAction ac = actions[key];
            actions.Remove(key);
            Object.Destroy(ac);
        }
        waitingDelete.Clear();
    }

    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null) {
        //牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空
    }
}
2.2.3 场景动作管理类 MySceneActionManager

该类继承动作管理器基类 SSActionManager,定义且实现了牧师与魔鬼游戏场景中角色上下船、移动船两个动作的实现,其中角色上下船为组合动作,而船的移动为普通动作。对不同的游戏对象可以调用不同的方法,使游戏对象完成其相应的移动。

public class MySceneActionManager : SSActionManager  //本游戏管理器
{

    private SSMoveToAction moveBoatToEndOrStart;     //移动船到结束岸,移动船到开始岸
    private SequenceAction moveRoleToLandorBoat;     //移动角色到陆地,移动角色到船上

    public Controller sceneController;

    protected void Start() {
        sceneController = (Controller)SSDirector.GetInstance().CurrentScenceController;
        sceneController.actionManager = this;
    }
    public void moveBoat(GameObject boat, Vector3 target, float speed) {
        moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);
        this.RunAction(boat, moveBoatToEndOrStart, this);
    }

    public void moveRole(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed) {
        SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);
        SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);
        moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });
        this.RunAction(role, moveRoleToLandorBoat, this);
    }
}
2.2.3 动作基类 SSAction

SSAction 是所有动作的基类。SSAction 继承了 ScriptableObject,代表 SSAction 不需要绑定 GameObject 对象,且受Unity引擎场景管理。在该类中定义了动作的基本数据成员和虚方法。

public class SSAction : ScriptableObject            //动作
{

    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁

    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //回调函数

    protected SSAction() { }                        //保证SSAction不会被new

    public virtual void Start()                    //子类可以使用这两个函数
    {
        throw new System.NotImplementedException();
    }

    public virtual void Update() {
        throw new System.NotImplementedException();
    }
}
2.2.4 移动动作类 SSMoveToAction

该类是动作基类 SSAction 的子类,定义了移动动作的实现,能够使游戏对象从当前位置移动到所设置的目的地处,无论是船的移动,还是牧师、魔鬼上下船的动作,都是基于该类的方法完成的。

public class SSMoveToAction : SSAction                        //移动
{
    public Vector3 target;        //移动到的目的地
    public float speed;           //移动的速度

    private SSMoveToAction() { }
    public static SSMoveToAction GetSSAction(Vector3 target, float speed) {
        SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();//让unity自己创建一个MoveToAction实例,并自己回收
        action.target = target;
        action.speed = speed;
        return action;
    }

    public override void Update() {
        this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
        if(this.transform.position == target) {
            this.destroy = true;
            this.callback.SSActionEvent(this);      //告诉动作管理或动作组合这个动作已完成
        }
    }

    public override void Start() {
        //移动动作建立时候不做任何事情
    }
}
2.2.5 组合动作类 SequenceAction

组合动作类 SequenceAction 是动作基类 SSAction 的子类,并且实现了 ISSActionCallback 接口。该类既是一个动作,也是一个动作管理器,因为它既可以表示组合动作这一整体,也管理着组成这一组动作的诸多小动作的执行与切换。

public class SequenceAction : SSAction, ISSActionCallback {
    public List<SSAction> sequence;    //动作的列表
    public int repeat = -1;            //-1就是无限循环做组合中的动作
    public int start = 0;              //当前做的动作的索引

    public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence) {
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例
        action.repeat = repeat;
        action.sequence = sequence;
        action.start = start;
        return action;
    }

    public override void Update() {
        if(sequence.Count == 0)
            return;
        if(start < sequence.Count) {
            sequence[start].Update();     //一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现
        }
    }
    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null) {
        source.destroy = false;          //先保留这个动作,如果是无限循环动作组合之后还需要使用
        this.start++;
        if(this.start >= sequence.Count) {
            this.start = 0;
            if(repeat > 0)
                repeat--;
            if(repeat == 0) {
                this.destroy = true;               //整个组合动作删除
                this.callback.SSActionEvent(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() {
        //如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
    }
}
2.2.6 场景控制器 Controllor

当 Controllor 接收到移动游戏对象的信息的时候,只要将相应的参数和游戏对象传给 MySceneActionManager 就可以进行令游戏对象进行移动。

public class Controller : MonoBehaviour, ISceneController, IUserAction {
    public LandModel start_land;            //开始陆地
    public LandModel end_land;              //结束陆地
    public BoatModel boat;                  //船
    private RoleModel[] roles;              //角色
    UserGUI user_gui;

    public MySceneActionManager actionManager;   //动作管理
    private Referee referee;                    //裁判

    void Start() {
        ...
    }

    public void LoadResources()              //创建水,陆地,角色,船
    {
        ...
    }

    public void MoveBoat()                  //移动船
    {
        if(boat.IsEmpty() || user_gui.sign != 0)
            return;
        actionManager.moveBoat(boat.getGameObject(), boat.BoatMoveToPosition(), boat.move_speed);   
        user_gui.sign = referee.Check(boat, (start_land.GetRoleNum())[0], (start_land.GetRoleNum())[1],
            (end_land.GetRoleNum())[0], (end_land.GetRoleNum())[1]);
    }

    public void MoveRole(RoleModel role)    //移动角色
    {
        if(user_gui.sign != 0)
            return;
        if(role.IsOnBoat()) {
            LandModel land;
            if(boat.GetBoatSign() == -1)
                land = end_land;
            else
                land = start_land;
            boat.DeleteRoleByName(role.GetName());

            Vector3 end_pos = land.GetEmptyPosition();                                         
            Vector3 middle_pos = new Vector3(role.getGameObject().transform.position.x, end_pos.y, end_pos.z);  
            actionManager.moveRole(role.getGameObject(), middle_pos, end_pos, role.move_speed); 

            role.GoLand(land);
            land.AddRole(role);
        }
        else {
            LandModel land = role.GetLandModel();
            if(boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign())
                return;   //船没有空位,也不是船停靠的陆地,就不上船

            land.DeleteRoleByName(role.GetName());

            Vector3 end_pos = boat.GetEmptyPosition();                                             
            Vector3 middle_pos = new Vector3(end_pos.x, role.getGameObject().transform.position.y, end_pos.z); 
            actionManager.moveRole(role.getGameObject(), middle_pos, end_pos, role.move_speed);  

            role.GoBoat(boat);
            boat.AddRole(role);
        }
        user_gui.sign = referee.Check(boat, (start_land.GetRoleNum())[0], (start_land.GetRoleNum())[1],
            (end_land.GetRoleNum())[0], (end_land.GetRoleNum())[1]);
    }

    public void Restart() {
        ...
    }
}
2.2.7 裁判类 Referee

裁判类用于判断游戏是否达到结束条件,当游戏结束时,将结束信息通知场景控制器,场景控制器将结束游戏。

public class Referee {

    public int Check(BoatModel boat, int start_priest, int start_devil, int end_priest, int end_devil) {
        if(end_priest + end_devil == 6)     //获胜
            return 2;

        int[] boat_role_num = boat.GetRoleNumber();
        if(boat.GetBoatSign() == 1)         //在开始岸和船上的角色
        {
            start_priest += boat_role_num[0];
            start_devil += boat_role_num[1];
        }
        else                                  //在结束岸和船上的角色
        {
            end_priest += boat_role_num[0];
            end_devil += boat_role_num[1];
        }
        if(start_priest > 0 && start_priest < start_devil) //失败
        {
            return 1;
        }
        if(end_priest > 0 && end_priest < end_devil)        //失败
        {
            return 1;
        }
        return 0;                                             //未完成
    }
}

2.3 游戏展示

3.1 游戏开始

游戏开始

3.2 游戏胜利

游戏胜利

3.3 游戏失败

游戏失败

2.4 游戏项目地址

牧师与魔鬼 动作分离版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值