一、作业要求
1、编写一个简单的鼠标打飞碟(Hit UFO)游戏。
游戏内容要求:

1、游戏有 n 个 round,每个 round 都包括10 次 trial;
2、每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
3、每个 trial 的飞碟有随机性,总体难度随 round 上升;
4、鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。

游戏的要求:

1、使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类;
2、近可能使用前面 MVC 结构实现人机交互与游戏模型分离;
3、按 adapter模式 设计图修改飞碟游戏,使它同时支持物理运动与运动学(变换)运动。

三、项目演示
视频演示

bf9c746eedff3840a51c7777930ad68.png
dcebd8ca822bb09fa58b5ecf223a45f.png

项目下载

四、动作管理器
并且在本次实现中,有用到动作管理器:
c27061745de2696eea1a4c1e4c1992d.png

适配器
适配器将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器的优点:
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。适配器还增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。

五、实现过程和方法(算法)
运动学和物理学运动方式,主要的区别在于,物理学执行动作的时候不需要我们使用transform.Translate()。
并且通过前几次作业的结合,可以简单画出本次作业程序的结构:

149927dac6e191fd07726eed87caba6.png
Director
Director类和上次的编写一模一样,也是利用单例模式和懒汉模式。

SceneController
SceneController也是一个接口,但这一次的游戏并不需要预先加载角色(上次要加载牧师、恶魔、船之类的对象,但这次没有),所以loadResources在FirstController的实现为空。

public interface ISceneControl
{
    void loadResources();
}

UserAction
UserAction是门面模式,与用户交互相关的,与玩家动作有关。

public enum GameState { ROUND_START, ROUND_FINISH, RUNNING, PAUSE, START, FUNISH }

public interface IUserAction
{
    GameState getGameState();//获取游戏状态
    void setGameState(GameState gameState);//设置游戏状态
    int getScore();//获取当前积分
    void hit(Vector3 pos);//
    bool getActionMode();//获取模式状态(运动学还是动力学)
    void setActionMode(bool mode);//设置模式
}

FristSceneControl
FristSceneControl就要实现上面(SceneController、UserAction)两个类的函数。

1、 这次作业需要实现让运动学和动力学两种模式的共存,因此就需要利用适配器。对于FirstSceneActionManager,有两个类,一个是CCActionManager,负责运动学的行为;另一个是ModifiedActionManager,负责物理学的行为。
· ScoreRecorder类是专门负责记录成绩的

        public ActionManagerAdapter actionManager { set; get; }//适配器
        public ScoreRecorder scoreRecorder { set; get; }       //成绩记录器
        public Queue<GameObject> diskQueue = new Queue<GameObject>();//飞碟对象队列
        private int diskNumber = 0;//飞碟的数量,主要用于设定每一轮抛出的飞碟数量
        private int currentRound = -1;//当前进行到第几局
        private float time = 0;//时间控制,用来做抛飞碟的时候用到
        private GameState gameState = GameState.START;//游戏状态
        UserGUI userGUI;//挂载GUI
        private bool isPhysical = false;//用于判断是否使用物理引擎

2、对于项目的初始化(Awake):

        void Awake()
        {
            Director director = Director.getInstance();//获取导演
            director.current = this;
            diskNumber = 10;//初始化每轮抛出的飞碟数
            this.gameObject.AddComponent<ScoreRecorder>();//挂载ScoreRecorder
            this.gameObject.AddComponent<DiskFactory>();//挂载DiskFactory
            scoreRecorder = Singleton<ScoreRecorder>.Instance;
            userGUI = gameObject.AddComponent <UserGUI>() as UserGUI;//挂载UserGUI

            director.current.loadResources();
        }

· scoreRecorder = Singleton<ScoreRecorder>.Instance;使得我们获取ScoreRecorder的单例对象。Singleton类的写法就是老师给出的模板类。
3、SceneController中的loadResources实现,本次为空。

public void loadResources()
        {
        }

4、UserAction的getGameState(),目的是将变量gameState进行return
5、UserAction的setGameState(GameState gameStateIn),设置变量gameStat,将gameStat设置为传入的参数即可
6、UserAction的getScore(),返回变量scoreRecorder.score
7、UserAction的hit(Vector3 pos)主要利用光标拾取多个物体的程序:

        public void hit(Vector3 pos)
        {
            RaycastHit[] hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(pos));
            for (int i = 0; i < hits.Length; i++)
            {
                RaycastHit hit = hits[i];
                if (hit.collider.gameObject.GetComponent<DiskData>() != null)
                {
                    scoreRecorder.record(hit.collider.gameObject);
                    hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
                }
            }
        }

8、UserAction的getActionMode(),返回变量isPhysical即可。
9、UserAction的setActionMode(bool mode),设置变量isPhysical为传入参数。

ActionManagerAdapter
因为FristSceneContoller要调用ActionManager的函数,并且这次由于有两种模式的存在,所以FristSceneContoller会通过ActionManagerAdapter(适配器)来调用相关模式下的函数。
适配器本质上就是一个接口,而继承适配器的类就要实现这些接口,也通过适配器进行了统一。
然后就是运动学CCActionManager和物理学ModifiedActionManager各自对这个函数进行实现。

CCActionManager与ModifiedActionManager
这两个类在这次的作业中其实代码差不多,主要区别是Update中一个要使用刚体一个不用。
对于CCActionManager:

    protected void Start()
    {
        sceneControl = (FirstSceneControl)Director.getInstance().current;
        sceneControl.actionManager = this;
        flys.Add(CCFlyAction.getCCFlyAction());
        base.flag = true;//这个flag如果是true表示使用运动学,false表示使用物理学
    }

    private new void Update()
    {
        if (sceneControl.getGameState() == GameState.RUNNING)
            base.Update();
    }

而ModifiedActionManager:

    protected void Start()
    {
        sceneControl = (FirstSceneControl)Director.getInstance().current;
        sceneControl.actionManager = this;
        flys.Add(CCFlyAction.getCCFlyAction());
        base.flag = false;//使用物理学
    }

    private new void Update()                                     
    {
        if (sceneControl.getGameState() == GameState.RUNNING)
        {
            base.Update();
            base.startRigidbodyAction();//启动刚体
        } 
        else
        {
            base.stopRigidbodyAction();//停止刚体
        }
    }

SSActionManager
ActionManager跟上次作业的基本相似,多了要判断哪一种运动方式用不同的update

protected void Update()
    {
        //......

        foreach (KeyValuePair<int, SSAction> i in actions)
        {
            SSAction value = i.Value;
            if (value.destroy)
            {
                waitingDelete.Add(value.GetInstanceID());
            }
            else if (value.enable && flag)//flag就是对应上面说使用哪种运动方式的变量
            {
                value.Update();
            }
            else if(value.enable && !flag)
            {
                value.FixedUpdate();
            }
        }
    }

SSAction
SSAction也实际上是一个接口,在本次作业的程序中,类CCFlyAction就负责实现这个最底部被继承的动作类。

CCFlyAction
CCFlyAction是负责实现飞碟飞行动作的类,它负责设置了大部分的变量参数。

    float acceleration;//加速度
    float horizontalSpeed;//水平速度
    Vector3 direction;
    float time;
    bool flag=false;//记录游戏是否经过暂停状态
    Vector3 temp;//记录游戏暂停时刚体游戏对象的速度矢量
    Rigidbody rigidbody; //物理运动,添加刚体

    //变量的初始化
    public override void Start()
    {
        enable = true;
        acceleration = 9.8f;
        time = 0;
        horizontalSpeed = gameObject.GetComponent<DiskData>().getSpeed();
        direction = gameObject.GetComponent<DiskData>().getDirection();
        //执行行为的对象如果有刚体性质,则需要设置刚体的速度属性
        rigidbody = gameObject.GetComponent<Rigidbody>();
        if (rigidbody)
        {
            rigidbody.velocity = horizontalSpeed * direction;
            temp = rigidbody.velocity;
        }
    }

DiskFactory
DiskFactory给我感觉有点像线程池,预先创建了一些飞碟,当需要用的时候才会取出。

对于一个飞碟,有以下的一些参数:

    private Vector3 size;//大小
    private Color color;//颜色
    private float speed;//运动速度
    private Vector3 direction;//位置

而工厂应该要有一个空闲队列和使用队列,用来记录哪些飞碟目前还没被使用,哪些已经被Active:

    public List<DiskData> used = new List<DiskData>();
    public List<DiskData> free = new List<DiskData>();

工厂的Awake()函数是加载飞碟对象的预建,并且设置它非Active:

    private void Awake()
    {
        diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
        diskPrefab.SetActive(false);
    }

此时的飞碟的参数都并没有被设置,也就是大小颜色那些都没有设定。

之后是从工厂种取出飞碟的getDisk()函数,传入的参数是当前第几轮(用以判断可以出什么颜色的飞碟)、是否使用刚体。getDisk()的工作主要有:

1、判断空闲队列还有没有飞碟,有的话直接取出空闲队列中的一个飞碟,并且把它从空闲队列移除。
2、如果空闲队列没有飞碟,则重新创建一个飞碟对象直接使用。
3、判断是否利用刚体,如果是的话,给刚刚所取出的飞碟套上刚体属性。
4、根据当前所进行到的回合数(约往后给出的飞碟会变小并且速度更快),设定飞碟的大小、颜色、速度、位置。
5、return这个飞碟对象
当一个飞碟使用完毕进行回收时,可以利用freeDisk(GameObject disk)函数。

1、要把它设置成非Active
2、把这个飞碟放到空闲队列,并且从使用队列中移除

ScoreRecorder
ScoreRecorder就是拿来记录不同颜色的飞碟对应的分数,以及当玩家击中飞碟时计算总得分。
因此它的初始化是设置总分为0、设置各种颜色飞碟对应多少分:

        void Start()                                        /* 初始化各种颜色的分数值 */
        {
            score = 0;
            scoreTable.Add(Color.white, 1);
            scoreTable.Add(Color.gray, 2);
            scoreTable.Add(Color.black, 4);
        }

当击中某个飞碟时,就可以调用record(GameObject disk)函数,把飞碟对象传入,让这个函数去计算得分:

        public void record(GameObject disk)
        {
            score += scoreTable[disk.GetComponent<DiskData>().getColor()];
        }

具体代码可以查看gitee链接

六、参考资料
1、Unity3d-learning 物理碰撞打飞碟小游戏
2、Unity3D游戏编程-鼠标打飞碟


Kim_Yang
1 声望1 粉丝