第五次作业 与游戏世界交互

第五次作业 与游戏世界交互

1. 编写一个简单的鼠标打飞碟(Hit UFO)游戏

  1. 游戏内容要求
  1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
  2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该round 的 ruler 控制;
  3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  1. 游戏的要求
  1. 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  2. 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
  1. 实现

游戏规则

  1. 游戏不限round的次数,每个roung有10次trial;
  2. 飞碟的颜色有三种(红、绿、蓝),每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同;
  3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  4. 鼠标点中得分,绿色记1分,红色记2分,蓝色记3分;
    游戏看开始有20条生命,击不中掉一条命,掉为0时游戏结束。

(1) 资源目录树
在这里插入图片描述
由三部分(Scenes除外)组成:
Materials:存放背景图
Resources:存放预制(飞碟)
Scripts:存放代码脚本

Resources
在这里插入图片描述
Scripts
在这里插入图片描述

(2) 代码实现
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, 1, 1);        //飞碟大小
}

DiskFactory.cs

单实例类,用场景单实例创建,使用模板模式根据预制和规则制作飞碟,对象模板包括飞碟对象和飞碟数据。有工厂方法GetDisk产生飞碟,回收方法FreeDisk回收飞碟

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

	public class DiskFactory : MonoBehaviour
	{
	    public GameObject UFO_instace = null;
	    private List<DiskData> used = new List<DiskData>();
	    private List<DiskData> free = new List<DiskData>();

	    public GameObject GetDisk(int round)
	    {
	        int random = 0;
	        int range1 = 1, range2 = 4, range3 = 7;
	        float start_y = -10f;
	        string tag;
	        UFO_instace = null;
	
	        //根据回合,随机选择要飞出的飞碟
	        if (round == 1)
	        {
	            random = Random.Range(0, range1);
	        }
	        else if (round == 2)
	        {
	            random = Random.Range(0, range2);
	        }
	        else
	        {
	            random = Random.Range(0, range3);
	        }
	        //将要选择的飞碟的tag
	        if (random <= range1)
	        {
	            tag = "disk1";
	        }
	        else if (random <= range2 && random > range1)
	        {
	            tag = "disk2";
	        }
	        else
	        {
	            tag = "disk3";
	        }
	        //寻找相同tag的空闲飞碟
	        for (int i = 0; i < free.Count; i++)
	        {
	            if (free[i].tag == tag)
	            {
	                UFO_instace = free[i].gameObject;
	                free.Remove(free[i]);
	                break;
	            }
	        }
	        //如果空闲列表中没有,则重新实例化飞碟
	        if (UFO_instace == null)
	        {
	            if (tag == "disk1")
	            {
	                UFO_instace = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);
	            }
	            else if (tag == "disk2")
	            {
	                UFO_instace = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
	            }
	            else
	            {
	                UFO_instace = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
	            }
	            //给新实例化的飞碟赋予其他属性
	            float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
	            UFO_instace.GetComponent<Renderer>().material.color = UFO_instace.GetComponent<DiskData>().color;
	            UFO_instace.GetComponent<DiskData>().direction = new Vector3(ran_x, start_y, 0);
	            UFO_instace.transform.localScale = UFO_instace.GetComponent<DiskData>().scale;
	        }
	        //添加到使用列表中
	        used.Add(UFO_instace.GetComponent<DiskData>());
	        return UFO_instace;
	    }
	
	    //回收飞碟
	    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;
	            }
	        }
	    }
	}

FirstController.cs

场景控制器,负责加载资源

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	public class FirstController : MonoBehaviour, ISceneController, IUserAction
	{
	    public FlyActionManager action_manager;
	    public DiskFactory UFO_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 = 1;                                                   //回合
	    private float speed = 1.5f;                                              //发射一个飞碟的时间间隔
	    private bool playing_game = false;                                       //游戏中
	    private bool game_over = false;                                          //游戏结束
	    private bool game_start = false;                                         //游戏开始
	    private int score_round2 = 10;                                           //去到第二回合所需分数
	    private int score_round3 = 25;                                           //去到第三回合所需分数
	
	    void Start()
	    {
	        SSDirector director = SSDirector.GetInstance();
	        director.CurrentScenceController = this;
	        UFO_factory = Singleton<DiskFactory>.Instance;
	        score_recorder = Singleton<ScoreRecorder>.Instance;
	        action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
	        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
	    }
	
	    void Update()
	    {
	        if (game_start)
	        {
	            //游戏结束,取消定时发送飞碟
	            if (game_over)
	            {
	                CancelInvoke("LoadResources");
	            }
	            //设定一个定时器,发送飞碟,游戏开始
	            if (!playing_game)
	            {
	                user_gui.round = round;
	                InvokeRepeating("LoadResources", 1f, speed);
	                playing_game = true;
	            }
	            //发送飞碟
	            SendDisk();
	            //回合升级
	            if (score_recorder.score >= score_round2 && round == 1)
	            {
	                round = 2;
	                user_gui.round = round;
	                speed = speed - 0.6f;
	                CancelInvoke("LoadResources");
	                playing_game = false;
	            }
	            else if (score_recorder.score >= score_round3 && round == 2)
	            {
	                round = 3;
	                user_gui.round = round;
	                speed = speed - 0.3f;
	                CancelInvoke("LoadResources");
	                playing_game = false;
	            }
	        }
	    }
	
	    public void LoadResources()
	    {
	        disk_queue.Enqueue(UFO_factory.GetDisk(round));
	    }
	
	    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(10f, 15f);
	            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)
	            {
	                UFO_factory.FreeDisk(disk_notshot[i]);
	                disk_notshot.Remove(disk_notshot[i]);
	                user_gui.ReduceBlood();
	            }
	        }
	    }
	
	    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);
	                StartCoroutine(WaitingParticle(0.08f, hit, UFO_factory, hit.collider.gameObject));
	                break;
	            }
	        }
	    }
	
	    public int GetScore()
	    {
	        return score_recorder.score;
	    }
	
	    public void ReStart()
	    {
	        game_over = false;
	        playing_game = false;
	        score_recorder.score = 0;
	        round = 1;
	        speed = 2f;
	    }
	
	    public void GameOver()
	    {
	        game_over = true;
	    }
	
	    //回收飞碟
	    IEnumerator WaitingParticle(float wait_time, RaycastHit hit, DiskFactory disk_factory, GameObject obj)
	    {
	        yield return new WaitForSeconds(wait_time);
	        hit.collider.gameObject.transform.position = new Vector3(0, -9, 0);
	        disk_factory.FreeDisk(obj);
	    }
	    public void BeginGame()
	    {
	        game_start = true;
	    }
	}

FlyActionManager.cs
飞碟动作管理器

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

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

SSAction.cs
动作基类

	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;
	    public ISSActionCallback callback;
	
	    protected SSAction() { }
	
	    public virtual void Start()
	    {
	        throw new System.NotImplementedException();
	    }
	    public virtual void Update()
	    {
	        throw new System.NotImplementedException();
	    }
	}

SSActionManager.cs

动作管理器

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	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);
	        }
	        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)
	    {
	    }
	}

SSDirector.cs

导演类

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

UFOFlyAction.cs

飞碟的飞行动作设计

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	public class UFOFlyAction : SSAction
	{
	    public float gravity = -2;
	    private Vector3 start_vector;
	    private Vector3 gravity_vector = Vector3.zero;
	    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()
	    {
	
	        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坐标小于-100,动作就做完了
	        if (this.transform.position.y < -100)
	        {
	            this.destroy = true;
	            this.callback.SSActionEvent(this);
	        }
	    }
	
	    public override void Start() { }
	}

ScoreRecorder.cs

记分规则

	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;
	        //Debug.Log(score);
	    }
	
	    public void Reset()
	    {
	        score = 0;
	    }
	}

SequenceAction.cs

由基本或组合动作组合的类

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	public class SequenceAction : SSAction, ISSActionCallback
	{
	
	    public List<SSAction> sequence;
	    public int repeat = -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()
	    {
	    }
	}

interface.cs

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	public interface ISceneController
	{
	    //加载场景
	    void LoadResources();
	}
	
	public interface IUserAction
	{
	    //用户点击游戏界面
	    void Hit(Vector3 pos);
	    //获得分数
	    int GetScore();
	    //游戏结束
	    void GameOver();
	    //游戏重新开始
	    void ReStart();
	    //游戏开始
	    void BeginGame();
	}
	public enum SSActionEventType : int { Started, Competeted }
	public interface ISSActionCallback
	{
	    void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
	       	int intParam = 0, string strParam = null, Object objectParam = null);
	}

UserGUI.cs

界面设计

	using System.Collections;
	using System.Collections.Generic;
	using UnityEngine;
	
	public class UserGUI : MonoBehaviour
	{
	    private IUserAction action;
	    public int round;
	    public int life = 20;                   //血量
	    //每个GUI的style
	    GUIStyle bold_style = new GUIStyle();
	    GUIStyle score_style = new GUIStyle();
	    GUIStyle text_style = new GUIStyle();
	    GUIStyle description_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()
	    {
	        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;
	        description_style.fontSize = 25;
	        description_style.normal.textColor = new Color(1, 0, 0);
	
	        if (game_start)
	        {
	            //用户射击
	            if (Input.GetButtonDown("Fire1"))
	            {
	                Vector3 pos = Input.mousePosition;
	                action.Hit(pos);
	            }
	            GUI.Label(new Rect(10, 5, 200, 30), "Round:", text_style);
	            GUI.Label(new Rect(65, 5, 200, 30), round.ToString(), score_style);
	            GUI.Label(new Rect(10, 45, 200, 30), "分数:", text_style);
	            GUI.Label(new Rect(55, 45, 200, 30), action.GetScore().ToString(), score_style);
	
	            GUI.Label(new Rect(10, 25, 200, 30), "生命:", text_style);
	            GUI.Label(new Rect(55, 25, 200, 30), life.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), "重新开始"))
	                {
	                    life = 20;
	                    action.ReStart();
	                    return;
	                }
	                action.GameOver();
	            }
	        }
	        else
	        {
	            GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 350, 100, 100), "Hit UFO!", over_style);
	            // GUI.Label(new Rect(Screen.width / 2 - 150, Screen.width / 2 - 220, 400, 100), "点击出现的UFO即可销毁,加油!", description_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--;
	    }
	}

Singleton.cs

本次作业限定了创建对象的模式是单例模式,即某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例。以下是单例模式的实现。

	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) 运行游戏
在完成上述步骤后,将DiskData.cs挂载到预制的三个飞碟中,分别修改Score, Color, Direction, Scale的值,然后将DiskFactory.cs,FirstController.cs,FlyActionManager.cs,SSActionManager.cs,Scorerecorder.csUserGUI.cs挂载到Main Camera中,并将DiskFactory.cs的UFO_instance改为Main Camera,点击运行,显示以下游戏画面:
在这里插入图片描述
点击游戏开始按钮即可开始游戏。

(4)项目代码

2. 编写一个简单的自定义component(选做)

在上面的鼠标打飞碟游戏中,其中 DiskData.cs就是一个简单的自定义component:

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, 1, 1);        //飞碟大小
}

挂载到飞碟上之后,可以在飞碟的Inspector里修改以下属性:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值