“鼠标打飞碟”(Hit UFO)游戏——改进版

本文详细描述了一个3D游戏中的飞碟挑战,涉及游戏规则、工厂模式管理飞碟、单例模式应用、MVC架构、Adapter模式实现物理运动以及四种游戏模式的设计。代码展示了如何使用Unity引擎实现对象池、飞碟对象的创建和回收,以及用户交互与游戏逻辑的分离。
摘要由CSDN通过智能技术生成

一、游戏介绍

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

针对上面的游戏要求,我给游戏设计了4个模式,规则如下:

  1. 正常模式共有五轮飞碟,飞碟数列遵循斐波那契数列,当所有飞碟发射完毕时游戏结束。
  2. 无限模式拥有无限轮数
  3. 红色飞碟为4分,黄色飞碟为3分,绿色飞碟为2分,蓝色飞碟为1分。
  4. 物理学模式下飞碟会互相碰撞

二、游戏准备

1、UML图

将工厂方法 + 单实例 + 对象池的UML图完成对飞碟的创建和回收,再用Adapter模式来给飞碟增加物理学运动,同时还需要保留之前的MVC架构,所以最后形成的UML图如下:

对UML图的理解:

  1. 游戏由导演、场记、运动管理师、演员构成。
  2. 新游戏中,场记请了记分员、飞碟管理员
  3. 飞碟管理员管理飞碟的发放与回收,自己有个小仓库管理这些飞碟
  4. 记分员按飞碟的数据计分,记分员拥有计分规则
  5. 场记只需要管理出飞碟规则与管理碰撞就可以了
  6. 新建 PhysisActionManager来形成Adapter模式形成物理学运动
2、伪代码
对象池的实现:
  • DiskFactory 类是一个单实例类,用前面场景单实例创建
  • DiskFactory 类有工厂方法 GetDisk 产生飞碟,有回收方法 Free(Disk)
  • DiskFactory 使用模板模式根据预制和规则制作飞碟
  • 对象模板包括飞碟对象与飞碟数据

伪代码如下:

getDisk(ruler) 
BEGIN
	IF (free list has disk) THEN
	a_disk = remove one from list
	ELSE
	a_disk = clone from Prefabs
	ENDIF
	Set DiskData of a_disk with the ruler
	Add a_disk to used list
	Return a_disk
END
FreeDisk(disk)
BEGIN
	Find disk in used list
	IF (not found) THEN THROW exception
	Move disk from used to free list
END

三、游戏代码

为了在游戏中使用MVC结构并采用面向对象的变成,所以SSDirector类、SSAction类、SSActionManager类、ISceneController类和ISSActionCallback类均保持不变,其他类的代码都需要改变,改变的代码如下:

Singleton

运用模板,可以为每个 MonoBehaviour子类 创建一个对象的实例。在任意位置使用代码Singleton<YourMonoType>.Instance 获得该对象。

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{//运用模板,可以为每个 MonoBehaviour子类 创建一个对象的实例。
    protected static T instance;

	public static T Instance {  //在任意位置使用代码 Singleton<YourMonoType>.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;  
		}  
	}
}
Disk

完成飞碟对象的创建

public class Disk : MonoBehaviour
{
    public GameObject disk;
    //public DiskData data;
    public Disk(string name){//四种飞碟
        if(name == "disk1"){
                disk = GameObject.Instantiate(Resources.Load<GameObject>("disk1"), Vector3.zero, Quaternion.identity) as GameObject;
                disk.AddComponent<DiskData>();
                //data=disk.GetComponent<DiskData>();
                disk.GetComponent<DiskData>().points=1;
                disk.GetComponent<DiskData>().direction=new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<DiskData>().speed=1.0f;
            }
            else if(name == "disk2"){
                disk = GameObject.Instantiate(Resources.Load<GameObject>("disk2"), Vector3.zero, Quaternion.identity) as GameObject;
                disk.AddComponent<DiskData>();
                //data=disk.GetComponent<DiskData>();
                disk.GetComponent<DiskData>().points=2;
                disk.GetComponent<DiskData>().direction=new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<DiskData>().speed=2.0f;
            }
            else if(name == "disk3"){
                disk = GameObject.Instantiate(Resources.Load<GameObject>("disk3"), Vector3.zero, Quaternion.identity) as GameObject;
                disk.AddComponent<DiskData>();
                //data=disk.GetComponent<DiskData>();
                disk.GetComponent<DiskData>().points=3;
                disk.GetComponent<DiskData>().direction=new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<DiskData>().speed=3.0f;
            }
            else if(name == "disk4"){
                disk = GameObject.Instantiate(Resources.Load<GameObject>("disk4"), Vector3.zero, Quaternion.identity) as GameObject;
                disk.AddComponent<DiskData>();
                disk.GetComponent<DiskData>().points=4;
                disk.GetComponent<DiskData>().direction=new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<DiskData>().speed=4.0f;
            }
            //return disk;
        }
    public GameObject getGameObject(){return disk;}
}
DiskData

飞碟的数据,携带飞碟的飞行速度、得分、以及飞行方向。

public class DiskData: MonoBehaviour
{
    public float speed;//飞行速度
    public Vector3 direction;//初始飞行方向
    public int points;//得分
}
DiskFactory

负责生产和释放飞碟

public class DiskFactory : MonoBehaviour
{
    // Start is called before the first frame update
    List<DiskData> used;
    List<DiskData> free;
    public void Start(){
        used = new List<DiskData>();
        free = new List<DiskData>();
    }
    public GameObject GetDisk(){
        Debug.Log("get...\n");
        Disk disk=null; 
        GameObject d;
        int rand=Random.Range(0,99);//生成一个随机数
        if(free.Count>0){//若还有空闲的飞碟,就将其加入
            Debug.Log("free...\n");
            d=free[0].gameObject;
            //disk.getGameObject().SetActive(true);
            free.Remove(free[0]);
        }
        else{
            Debug.Log("new...\n");
            if(rand%4==0){
                disk=new Disk("disk1");
            }
            else if(rand%4==1){
                disk=new Disk("disk2");
            }
            else if(rand%4==2){
                disk=new Disk("disk3");
            }
            else{
                disk=new Disk("disk4");
            }
            d=disk.getGameObject();
        }
        used.Add(d.GetComponent<DiskData>());
        return d;
    }
    public void FreeDisk(GameObject disk){
        for(int i=0;i<used.Count;i++){
            if(disk.GetInstanceID() == used[i].gameObject.GetInstanceID()){
                //used[i].getGameObject().SetActive(false);
                disk.SetActive(false);
                used.Remove(used[i]);
                free.Add(used[i]);
                break;
            }
        } 
    }
}
IUserAction

包含对运动学和物理学的飞行模式的切换,是否将游戏设置为无穷模式,控制飞碟的点击得分。

public interface IUserAction
{
	void setFlyMode(bool isPhysis);//设置飞行模式
    void Hit(Vector3 position);//点击
    void Restart();//重置
    void SetMode(bool isInfinite);//选择模式
    bool Check();
    int GetPoints();
}
FirstController

对接口的函数的实现

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

public class FirstController : MonoBehaviour, ISceneController, IUserAction {

	DiskFactory diskFactory;
    RoundController roundController;
    //UserGUI userGUI;
    public IActionManager actionManager;
	public int points;
	void Awake () {
		SSDirector director = SSDirector.getInstance ();
		director.setFPS (60);
        points=0;
		director.currentSceneController = this;
        LoadResources ();
    }
	// loading resources for first scence
	public void LoadResources () {
		UnityEngine.Debug.Log("load...\n");
        //获得对象
        diskFactory=Singleton<DiskFactory>.Instance;
        roundController=Singleton<RoundController>.Instance;
	}

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

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

    public void FreeDisk(GameObject disk){
        diskFactory.FreeDisk(disk);
    }

	#region IUserAction implementation
    public bool Check(){//检查比赛是否结束
        return roundController.isEnd;
    }
	public void Hit(Vector3 position){//光标拾取多个物体
        Camera ca = Camera.main;;
		Ray ray = ca.ScreenPointToRay(position);
        //将飞碟移至底端,触发动作回调
        //移动位置
        //积分
           //更新GUI数据,更新得分
			RaycastHit hit;
			if (Physics.Raycast(ray, out hit)) {
				print (hit.transform.gameObject.name);
				if (hit.collider.gameObject.GetComponent<DiskData>() != null) { //plane tag
					Debug.Log ("hit " + hit.collider.gameObject.name +"!" ); 
                    //将飞碟移至底端,触发动作回调
                    //移动位置
                    hit.collider.gameObject.transform.position=new Vector3(0,-7,0);
                    //积分
                    roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());
                    points=roundController.GetPoints();
				}

			}
        
    }
	public int GetPoints(){
        return points;
    }
    public void Restart()//游戏重新开始
    {
        //userGUI.SetMessage(" ");
        //清零得分
        //userGUI.SetPoints(0);
        roundController.Reset();
        points=0;
        
    }

    public void SetMode(bool isInfinite){
        roundController.SetMode(isInfinite);
    }
    
    public void setFlyMode(bool isPhysis){
        roundController.setFlyMode(isPhysis);
    }
    #endregion
    void Update(){

    }
}
IActionManager

对飞行动作的实现

public interface IActionManager 
{
    void Fly(GameObject disk,float speed,Vector3 direction);
}
CCFlyAction

运动学的飞行动作

public class CCFlyAction : SSAction
{
	public Vector3 direction;
	public float speed;
	float gravity;
	float time;
    //public FirstController sceneController;

	public static CCFlyAction GetSSAction(Vector3 direction,float speed){
		CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction> ();
		action.direction=direction;
		action.speed=speed;
		action.gravity=9.8f;
		action.time=0;
		return action;
	}

	public override void Update ()
	{
		//获取上一帧渲染所消耗的时间
		time+=Time.deltaTime;
		//使物体沿着重力方向下落
		transform.Translate(Vector3.down*gravity*time*Time.deltaTime);
		//在每一帧更新物体的位置,使运动与帧率无关
		transform.Translate(direction*speed*Time.deltaTime);
		// this.transform.position = Vector3.MoveTowards (this.transform.position, target, speed * Time.deltaTime);
		if (this.transform.position.y < -10) {//如果飞碟到达底部,则动作结束,回调
			//waiting for destroy
			this.enable=false;
			this.destory = true;  
			this.callback.SSActionEvent (this);
		}
		
	}
	
	public override void Start () {
		//将对象设为刚体
		gameobject.GetComponent<Rigidbody>().isKinematic=true;
	}
}
CCActionManager

对运动学飞行动作的实现和控制

public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager{
	
	private FirstController sceneController;
	public CCFlyAction flyAction;

	protected new void Start() {
		sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
		sceneController.actionManager=this;
	}

	// Update is called once per frame
	public void Fly(GameObject disk,float speed,Vector3 direction){//实现飞行动作
		flyAction=CCFlyAction.GetSSAction(direction,speed);
		RunAction(disk,flyAction,this);
	}
		
	#region ISSActionCallback implementation
	public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
	{
		//飞碟结束飞行后回收飞碟
		sceneController.FreeDisk(source.gameobject);
	}
	#endregion
}
PhysisFlyAction

对物理学飞行动作的实现

public class PhysisFlyAction : SSAction
{//实现物体的物理学飞行,增加一个水平初速度即可
    float speed;//水平速度
    Vector3 direction;//飞行方向
    public static PhysisFlyAction GetSSAction(Vector3 direction,float speed){
		PhysisFlyAction action = ScriptableObject.CreateInstance<PhysisFlyAction> ();
		action.direction=direction;
		action.speed=speed;
		return action;
	}
    // Start is called before the first frame update
    public override void Start()
    {
        //增加一个水平初速度
        gameobject.GetComponent<Rigidbody>().velocity=speed*direction;
    }

    // Update is called once per frame
    public override void Update()
    {
        if (this.transform.position.y < -10) {//如果飞碟到达底部,则动作结束,回调
			//waiting for destroy
			this.enable=false;
			this.destory = true;  
			this.callback.SSActionEvent (this);
		}
    }
}
PhysisActionManager

对物理学飞行动作的控制

public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager{
	
	private FirstController sceneController;
	public PhysisFlyAction flyAction;

	protected new void Start() {
		sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
		sceneController.actionManager=this;
	}

	// Update is called once per frame
	public void Fly(GameObject disk,float speed,Vector3 direction){//实现飞行动作
		disk.GetComponent<Rigidbody>().isKinematic = false;
		disk.GetComponent<Rigidbody>().useGravity = true;
		flyAction=PhysisFlyAction.GetSSAction(direction,speed);
		RunAction(disk,flyAction,this);
	}
		
	#region ISSActionCallback implementation
	public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
	{
		//飞碟结束飞行后回收飞碟
		sceneController.FreeDisk(source.gameobject);
	}
	#endregion
}
ScoreRecorder

对得分的控制

public class ScoreRecorder
{
    static int points=0;//记录游戏当前比分
    public ScoreRecorder(){
        points=0;
    }
    public void Record(DiskData disk){
        //Debug.Log("1\n");
        points +=disk.points;
    }
    public int GetPoints(){
        return points;
    }
    public void Reset(){
        points=0;
    }
}
RoundController

对每回合的释放的飞碟数的控制

public class RoundController : MonoBehaviour
{
    IActionManager actionManager;
    DiskFactory diskFactory;
    ScoreRecorder scoreRecorder;
    int[] roundDisks;//对应回合的飞碟数
    bool isInfinite;//游戏当前模式
    int round;//游戏当前轮次
    int sendCnt;//当前已发送飞碟
    float sendTime;//发送时间
    public bool isEnd;//游戏是否结束
    // Start is called before the first frame update
    void Start()
    {
        actionManager=Singleton<CCActionManager>.Instance;
        diskFactory=Singleton<DiskFactory>.Instance;
        Debug.Log("diskFactory...\n");
        scoreRecorder=new ScoreRecorder();
        sendCnt=0;
        round=0;
        sendTime=0;
        isInfinite=false;
        isEnd=false;
        roundDisks=new int[]{3,5,8,13,21};
    }

    public void Reset()
    {
        sendCnt=0;
        round=0;
        sendTime=0;
        isEnd=false;
        scoreRecorder.Reset();
        //userGUI.SetMessage("");
    }

    public void Record(DiskData disk){
        scoreRecorder.Record(disk);
    }

    public int GetPoints(){
        return scoreRecorder.GetPoints();
    }

    public void SetMode(bool isInfinite){
        this.isInfinite=isInfinite;
    }

    public void setFlyMode(bool isPhysis){
        actionManager=isPhysis? Singleton<PhysisActionManager>.Instance as IActionManager: Singleton<CCActionManager>.Instance as IActionManager; 
    }

    public void SendDisk(){//发送飞碟
        //从工厂生成一个飞碟
        GameObject disk=diskFactory.GetDisk();
        Debug.Log("getdisk...\n");
        //设置随机位置
        disk.transform.position=new Vector3(-disk.GetComponent<DiskData>().direction.x*7,UnityEngine.Random.Range(3f,8f),0);
        disk.SetActive(true);
        actionManager.Fly(disk,disk.GetComponent<DiskData>().speed,disk.GetComponent<DiskData>().direction);
    }
    // Update is called once per frame
    void Update()
    {
        sendTime+=Time.deltaTime;
        //每隔1s发送一次飞碟
        if(sendTime>1)
        {
            sendTime=0;
            //每次最多发送3个飞碟
            for(int i=0;i<2&&sendCnt<roundDisks[round];i++)
            {
                sendCnt++;
                this.SendDisk();
            }
        }
        //判断是否需要重置轮次
        if(sendCnt>=roundDisks[round]&&round==(roundDisks.Length-1))
        {//最后一轮结束
            if(isInfinite){
                round=0;
                sendCnt=0;
                isEnd=false;
            }
            else{
                isEnd=true;
            }
        }
        //更新轮次
        if(sendCnt>=roundDisks[round]&&round<(roundDisks.Length-1))
        {
            sendCnt=0;
            round++;
        }
    }
}
UserGUI

用户界面与游戏的对接,包括游戏界面的呈现

public class UserGUI : MonoBehaviour
{
    IUserAction action;
    public string gameMessage ;
    int points;

    void Start()
    {
        Debug.Log("UserGUI...\n");
        points=0;
        gameMessage=" ";
        action=SSDirector.getInstance().currentSceneController as IUserAction;
    }

    public void SetMessage(string gameMessage)
    {
        this.gameMessage=gameMessage;
    }
    public void SetPoints(int points)
    {
        this.points=points;
    }
    private void OnGUI()
    {
        //小字体
        GUIStyle fontStyle = new GUIStyle();
        fontStyle.fontSize = 30;
        fontStyle.normal.textColor = Color.white;
        //大字体
        GUIStyle bigStyle = new GUIStyle();
        bigStyle.fontSize = 50;
        bigStyle.normal.textColor = Color.white;
        
        if(action.Check()){
            SetMessage("GameOver!");
        }
        else{
            SetMessage("");
        }
        SetPoints(action.GetPoints());
        if (GUI.Button(new Rect(20, 50, 100, 40), "Restart"))
        {
            SetMessage("");
            //清零得分
            SetPoints(0);
            action.Restart();
        }
        if (GUI.Button(new Rect(20, 100, 100, 40), "Normal Mode"))
        {
            action.SetMode(false);
        }
        if (GUI.Button(new Rect(20, 150, 100, 40), "Infinite Mode"))
        {
            action.SetMode(true);
        }
        if (GUI.Button(new Rect(20, 200, 100, 40), "Kinematics"))
        {
            action.setFlyMode(false);
        }
        if (GUI.Button(new Rect(20, 250, 100, 40), "Physis"))
        {
            action.setFlyMode(true);
        }
        //返回鼠标点击的位置
        if(Input.GetButtonDown("Fire1"))//获取鼠标点击
        {
            Debug.Log ("Fired Pressed");
            action.Hit(Input.mousePosition);
        }
        GUI.Label(new Rect(300,30,50,200),"Hit UFO",bigStyle);
        GUI.Label(new Rect(20,0,100,50),"Points: "+points,fontStyle);
        Debug.Log("points:"+points+"\n");
        Debug.Log("message:"+gameMessage+"\n");
        GUI.Label(new Rect(310, 100, 50, 200), gameMessage, fontStyle);
    }
}

游戏完整代码位置:Hit UFO · 张菁/3D游戏编程与设计 - 码云 - 开源中国 (gitee.com)

游戏页面如下:

游戏开始时:

游戏结束时: 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值