游戏作业5-打飞碟

1: 任务概述

本次编程实践需要设计一个鼠标打地鼠的游戏:

  • 将游戏设置为多个round,每个round有n个trail。每个 trail 的飞碟的色彩,大小;
  • 发射位置,速度,角度,每次发射飞碟数量不一;。
  • 鼠标击中得分,得分按色彩、大小、速度不同计算,计分规则自由定

2:项目结构:

本次编程依旧使用MVC结构,游戏由导演、场记、运动管理员和演员等部分构成。为了管理飞碟的创建于销毁,以及发放和使用这些飞碟,使用了工厂方法。具体的结构如下:

在这里插入图片描述

  • 记分员按飞碟的数据计分,记分员拥有计分规则

  • 场记只需要管理出飞碟规则与管理碰撞就可以了

  • DiskFactory 类是一个单实例类,用前面场景单实例创建

  • DiskFactory 类有工厂方法 GetDisk 产生飞碟,有回收方法 Free(Disk)

  • DiskFactory 使用模板模式根据预制和规则制作飞碟

  • 对象模板包括飞碟对象与飞碟数据

3:项目代码:

3.1:管理飞碟信息DiskData.cs:

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

public class DiskData : MonoBehaviour{
    public int score = 1;                               //射击此飞碟得分
    public Color color = Color.white;                   //飞碟颜色
    public Vector3 direction;                           //飞碟初始的位置
    public Vector3 scale = new Vector3( 1 ,0.25f, 1);   //飞碟大小
}

3.2 导演:Director

用于实例化一个导演类

public class SSDirector : System.Object{
    private static SSDirector _instance;                  //导演类的实例
    public ISceneController CurrentScenceController { get; set; }
    public static SSDirector GetInstance(){
        if (_instance == null) _instance = new SSDirector();
        return _instance;
    }
}

3.3 工厂模式: DiskFactors

本次实验要求使用带缓存的工厂模式来管理飞碟的发放与回收利用。如果需要使用一个飞碟示例,但是如果仓库中没有该飞碟实例,就需要创建一个新的实例,在飞碟使用过后,工厂不会销毁该飞碟实例,而是将飞碟回收到空闲队列,等待下一次的调用 。
同时工厂需要使用单例模式。
工厂使用两个链表来储存飞碟。分别代表使用的飞碟队列和空闲的飞碟队列。

    public GameObject disk_prefab = null;                 //飞碟预制体
    private List<DiskData> used = new List<DiskData>();   //正在被使用的飞碟列表
    private List<DiskData> free = new List<DiskData>();   //空闲的飞碟列表

工厂主要有两个方法,分别用于获取空闲的实例和让使用中的实例返回到空闲的状态。

1:获取空闲飞碟实例,参数是当前的round值,round 1:只会获取disk1 类型
round 2: 可以获取disk1 和 disk2 两种类型
round 3: 可以获取disk1 和 disk2 和disk 3三种类型值。

随机选择一个类型,并从队列中寻找该类型的飞碟,如果找到一个实例,那么就从空闲队列中选出该实例,如果没有该类型的实例,就从预制中创建一个新实例。

最后需要对飞碟的位置等属性进行设置。

    public GameObject GetDisk(int round){
        int choice = 0;
        int scope1 = 1, scope2 = 4, scope3 = 7;           //随机的范围
        float start_y = -10f;                             //刚实例化时的飞碟的竖直位置
        string name;
        disk_prefab = null;

        //根据回合,随机选择要飞出的飞碟
        if (round == 1) choice = Random.Range(0, scope1);
        else if(round == 2) choice = Random.Range(0, scope2);
        else choice = Random.Range(0, scope3);
        
        //将要选择的飞碟的name
        if(choice <= scope1) name = "disk1";
        else if(choice <= scope2 && choice > scope1) name = "disk2";
        else name = "disk3";

        //寻找相同name的空闲飞碟
        for(int i=0;i<free.Count;i++) if(free[i].name == name){
            disk_prefab = free[i].gameObject;
            free.Remove(free[i]);
            break;
        }
        //如果空闲列表中没有,则重新实例化飞碟
        if(disk_prefab == null){
            if (name == "disk1") disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
            else if (name == "disk2") disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
            else disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
            
            //给新实例化的飞碟赋予其他属性
            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<DiskData>().color;
            disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
            disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
        }
        //添加到使用列表中
        used.Add(disk_prefab.GetComponent<DiskData>());
        return disk_prefab;
    }

2:从使用队列删除一个实例,并将该实例回收到空闲队列中:

    public void FreeDisk(GameObject disk){
        for(int i = 0;i < used.Count; i++) if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID()){
            used[i].gameObject.SetActive(false);
            free.Add(used[i]);
            used.Remove(used[i]);
            break;
        }
    }

3.4 单实例模式 Singleton

在该项目中,工厂和分数计数员使用的都是单实例模式。

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

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = (T)FindObjectOfType(typeof(T));
                if (instance == null)
                {
                    Debug.LogError("An instance of " + typeof(T)
                        + " is needed in the scene, but there is none.");
                }
            }
            return instance;
        }
    }
}

3.5 场记 FirstController

1:成员变量:

    public FlyActionManager action_manager;     //动作管理
    public DiskFactory disk_factory;           //飞碟工厂
    public UserGUI user_gui;                  //游戏交互界面
    public ScoreRecorder score_recorder;     //分数计算

    private Queue<GameObject> disk_queue = new Queue<GameObject>();          //游戏场景中的飞碟队列
    private List<GameObject> disk_notshot = new List<GameObject>();          //没有被打中的飞碟队列
    private int round = 0;                                                   //回合
    private float speed = 2f;                                                //发射一个飞碟的时间间隔
    private bool playing_game = false;                                       //游戏中
    private bool game_over = false;                                          //游戏结束
    private bool game_start = false;                                         //游戏开始

    private int[][] trial;                                 //记录分数轨迹                  
    private int[] score_round;                             //每一round的分数                  

2:初始化 Start

初始化分数变化数组trial[],以及获取工厂、导演等实例。

    void Start (){
        //初始化参数 一共3级 1级10层
        trial = new int[3][];                                                
        score_round = new int[3];                                           
        int tmp=0;
        for (int i=0;i<3;i++){
            trial[i]= new int[10];
            for (int j=0;j<10;j++){
                trial[i][j]=tmp;
                tmp+=2*(i+1);
            }
            score_round[i]=trial[i][0];
        }

        SSDirector director = SSDirector.GetInstance();     
        director.CurrentScenceController = this;             
        disk_factory = Singleton<DiskFactory>.Instance;
        score_recorder = Singleton<ScoreRecorder>.Instance;
        action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;

    }
	
3: Update

在一定的时间间隔会放飞飞碟,同时记录分数,依据当前的分数更新Round,以及飞碟发放的速度:


	void Update (){
        if(game_start){
            //游戏结束,取消定时发送飞碟
            if (game_over) CancelInvoke("LoadResources");

            //设定一个定时器,发送飞碟,游戏开始
            if (!playing_game){
                InvokeRepeating("LoadResources", 1f, speed);
                playing_game = true;
            }

            //发送飞碟
            SendDisk();
            
            //回合升级
            if (score_recorder.score >= score_round[1] && round == 0){
                round = 1;
                //缩小飞碟发送间隔
                speed = speed - 0.6f;
                CancelInvoke("LoadResources");
                playing_game = false;
            }
            else if (score_recorder.score >= score_round[2] && round == 1){
                round = 2;
                speed = speed - 0.5f;
                CancelInvoke("LoadResources");
                playing_game = false;
            }
        }
    }
4.发射飞碟 sendDisk:

从队列中获取飞碟,并设置为可见,在开始运动前,随机设置运动的力和运动的方向,判断飞行过程是否被击中,修改玩家血量。


    private void SendDisk(){
        float position_x = 16;                       
        if (disk_queue.Count != 0){
            GameObject disk = disk_queue.Dequeue();
            disk_notshot.Add(disk);
            disk.SetActive(true);
            //设置被隐藏了或是新建的飞碟的位置
            float ran_y = Random.Range(1f, 4f);
            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk.GetComponent<DiskData>().direction = new Vector3(ran_x, ran_y, 0);
            Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
            disk.transform.position = position;
            //设置飞碟初始所受的力和角度
            float power = Random.Range(5f, 10f);
            float angle = Random.Range(15f, 28f);
            action_manager.UFOFly(disk,angle,power);
        }

        for (int i = 0; i < disk_notshot.Count; i++){
            GameObject temp = disk_notshot[i];
            //飞碟飞出摄像机视野也没被打中
            if (temp.transform.position.y < -10 && temp.gameObject.activeSelf == true){
                disk_factory.FreeDisk(disk_notshot[i]);
                disk_notshot.Remove(disk_notshot[i]);
                //玩家血量-1
                user_gui.ReduceBlood();
            }
        }
    }
5 记录打击 Hit

判断飞碟是否被击中。记录分数记忆显示爆炸的效果,同时需要回收飞碟。

    public void Hit(Vector3 pos){
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        bool not_hit = false;
        for (int i = 0; i < hits.Length; i++){
            RaycastHit hit = hits[i];
            //射线打中物体
            if (hit.collider.gameObject.GetComponent<DiskData>() != null){
                //射中的物体要在没有打中的飞碟列表中
                for (int j = 0; j < disk_notshot.Count; j++)
                    if (hit.collider.gameObject.GetInstanceID() == disk_notshot[j].gameObject.GetInstanceID()) not_hit = true;

                if(!not_hit) return;
                
                disk_notshot.Remove(hit.collider.gameObject);
                //记分员记录分数
                score_recorder.Record(hit.collider.gameObject);
                //显示爆炸粒子效果
                Transform explode = hit.collider.gameObject.transform.GetChild(0);
                explode.GetComponent<ParticleSystem>().Play();
                //执行回收飞碟
                StartCoroutine(WaitingParticle(0.02f, hit, disk_factory, hit.collider.gameObject));
                break;
            }
        }
    }

3.6 分数记录 ScoreRecorder

分数记录也是一个单实例对象:记录当前玩家的得分:

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

public class ScoreRecorder : MonoBehaviour{
    public int score;                   //分数
    void Start (){
        score = 0;
    }
    //记录分数
    public void Record(GameObject disk){
        int temp = disk.GetComponent<DiskData>().score;
        score = temp + score;
    }
    //重置分数
    public void Reset(){
        score = 0;
    }
}

3.7 交互界面:UserGUI

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

public class UserGUI : MonoBehaviour{
    private IUserAction action;
    const int mxlife=10;
    public int life = mxlife;                   //血量
    //每个GUI的style
    GUIStyle bold_style = new GUIStyle();
    GUIStyle score_style = new GUIStyle();
    GUIStyle text_style = new GUIStyle();
    GUIStyle over_style = new GUIStyle();
    private int high_score = 0;            //最高分
    private bool game_start = false;       //游戏开始

    void Start (){
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
    }
	
	void OnGUI (){
        bold_style.normal.textColor = new Color(1, 0, 0);
        bold_style.fontSize = 16;
        text_style.normal.textColor = new Color(0,0,0, 1);
        text_style.fontSize = 16;
        score_style.normal.textColor = new Color(1,0,1,1);
        score_style.fontSize = 16;
        over_style.normal.textColor = new Color(1, 0, 0);
        over_style.fontSize = 25;

        if (game_start){
            //用户射击
            if (Input.GetButtonDown("Fire1")){
                Vector3 pos = Input.mousePosition;
                action.Hit(pos);
            }

            GUI.Label(new Rect(10, 5, 200, 50), "分数:", text_style);
            GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), score_style);

            GUI.Label(new Rect(10, 35, 200, 50), "Round:", text_style);
            GUI.Label(new Rect(65, 35, 200, 50), action.getRound().ToString(), score_style);

            GUI.Label(new Rect(400, 5, 50, 50), "生命:", text_style);
            //显示当前血量
            GUI.Label(new Rect(460, 5, 50, 50), getLife().ToString(), score_style);

            //游戏结束
            if (life == 0){
                high_score = high_score > action.GetScore() ? high_score : action.GetScore();
                GUI.Label(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 250, 100, 100), "游戏结束", over_style);
                GUI.Label(new Rect(Screen.width / 2 - 10, Screen.width / 2 - 200, 50, 50), "最高分:", text_style);
                GUI.Label(new Rect(Screen.width / 2 + 50, Screen.width / 2 - 200, 50, 50), high_score.ToString(), text_style);
                if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2 - 150, 100, 50), "重新开始")){
                    action.ReStart();
                    life = mxlife;
                    return;
                }
                action.GameOver();
            }
        }
        else{
            GUI.Label(new Rect(Screen.width / 2 - 5, Screen.width / 2 - 350, 100, 100), "UFO", over_style);
            if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.width / 2-150, 100, 50), "游戏开始")){
                game_start = true;
                action.BeginGame();
            }
        }
    }
    public void ReduceBlood(){
        if(life > 0) life--;
    }

    public int  getLife(){
         return life;
    }
}


3.8 飞碟动作:

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

public class SSAction : ScriptableObject{
    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁
    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //动作完成后的消息通知者

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

public class SSActionManager : MonoBehaviour, ISSActionCallback{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合
    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;                                    
        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);
         //   DestroyObject(ac);
            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){ }
}

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

    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(){ }
}

public class UFOFlyAction : SSAction{
    public float gravity = -2;                                 //向下的加速度
    private Vector3 start_vector;                              //初速度向量
    private Vector3 gravity_vector = Vector3.zero;             //加速度的向量,初始时为0
    private float time;                                        //已经过去的时间
    private Vector3 current_angle = Vector3.zero;               //当前时间的欧拉角

    private UFOFlyAction() { }
    public static UFOFlyAction GetSSAction(Vector3 direction, float angle, float power){
        //初始化物体将要运动的初速度向量
        UFOFlyAction action = CreateInstance<UFOFlyAction>();
        if (direction.x == -1) action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
        else action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        return action;
    }

    public override void Update(){
        //计算物体的向下的速度,v=at
        time += Time.fixedDeltaTime;
        gravity_vector.y = gravity * time;

        //位移模拟
        transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
        current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
        transform.eulerAngles = current_angle;

        //如果物体y坐标小于-10,动作就做完了
        if (this.transform.position.y < -10){
            this.destroy = true;
            this.callback.SSActionEvent(this);      
        }
    }

    public override void Start() { }
}

public class FlyActionManager : SSActionManager{

    public UFOFlyAction fly;                            //飞碟飞行的动作
    public FirstController scene_controller;             //当前场景的场景控制器

    protected void Start(){
        scene_controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        scene_controller.action_manager = this;     
    }
    //飞碟飞行
    public void UFOFly(GameObject disk, float angle, float power){
        fly = UFOFlyAction.GetSSAction(disk.GetComponent<DiskData>().direction, angle, power);
        this.RunAction(disk, fly, this);
    }
}


4:项目文件:

在这里插入图片描述

在这里插入图片描述

5:运行演示:

玩法介绍: 游戏会随机发射飞碟,如果鼠标击中分数就会叠加,随着分数的增加,飞碟发射的频率会逐渐增加,且分数到达某个值,round会上升,round每次增加,飞碟会增加一种类型,新的类型会更难点击,同时点击新类型的飞碟分数也会更高。一开始玩家的生命值为10。每次错失飞碟都会减少生命值,生命值小于0时,游戏结束:

Disk

6:项目地址

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值