Unity实现打飞碟游戏

前言

在打飞碟游戏中,每一轮会产生若干飞碟随机飞入,不同的飞碟带有不同的分数,玩家需要用鼠标点击飞行中的飞碟来实现打飞碟的行为,打中即可得分,总共有四轮,四轮后游戏结束,玩家需要得到尽可能高分。

游戏效果视频

游戏对象(飞碟)的预制制作

创建一个Cylinder类型对象,设置高度y=0.1,添加组件Rigidbody2D和Capsule Collider 2D

对象池

对象池(Object Pool)是一种设计模式,用于管理和重用一组预先创建的对象,而不是在每次需要时都创建和销毁它们。这种模式的主要目的是减少创建和销毁对象的开销,特别是在频繁创建和销毁大量临时对象的情况下,可以显著提高性能和资源利用率。

对象池的基本概念

  1. 预创建对象:在程序启动时或某个初始化阶段,预先创建一批对象,并将它们存储在一个集合中(通常是列表或队列)。这些对象处于未使用状态,等待被分配。

  2. 对象借用:当需要使用一个对象时,不是直接创建新的对象,而是从对象池中“借”一个已存在的对象。这通常涉及到从集合中取出一个对象,并将其标记为正在使用。

  3. 对象归还:当对象不再需要时,不是直接销毁它,而是将其“归还”到对象池中。这通常涉及将对象重置到初始状态,并将其放回集合中,以便下次再被借用。

  4. 对象管理:对象池还需要处理一些管理任务,例如:

    • 对象数量控制:确保对象池中对象的数量在一定范围内,防止过多对象占用内存。
    • 对象状态检查:确保归还的对象处于有效状态,可以被再次借用。
    • 对象销毁:在必要时销毁不再需要的对象,释放资源。
// 定义一个对象池类
public class ObjectPool
{
    private List<GameObject> pool; // 对象池列表
    private GameObject prefab; // 对象的预制体
    private int poolSize; // 对象池的大小
 
    // 构造函数,初始化对象池
    public ObjectPool(GameObject prefab, int poolSize)
    {
        this.prefab = prefab;
        this.poolSize = poolSize;
        pool = new List<GameObject>();
 
        // 在对象池中创建指定数量的对象
        for (int i = 0; i < poolSize; i++)
        {
            GameObject obj = Instantiate(prefab);
            obj.SetActive(false);
            pool.Add(obj);
        }
    }
 
    // 从对象池中获取一个对象
    public GameObject GetObject()
    {
        // 遍历对象池,找到一个未激活的对象并返回
        foreach (GameObject obj in pool)
        {
            if (!obj.activeInHierarchy)
            {
                obj.SetActive(true);
                return obj;
            }
        }
 
        // 如果没有未激活的对象,创建一个新的对象并返回
        GameObject newObj = Instantiate(prefab);
        newObj.SetActive(true);
        pool.Add(newObj);
        return newObj;
    }
 
    // 将对象放回对象池
    public void ReturnObject(GameObject obj)
    {
        obj.SetActive(false);
    }
}
 
// 使用对象池
public class ObjectPoolExample : MonoBehaviour
{
    public GameObject prefab; // 预制体
    public int poolSize; // 对象池的大小
    private ObjectPool objectPool; // 对象池实例
 
    private void Start()
    {
        objectPool = new ObjectPool(prefab, poolSize);
    }
 
    private void Update()
    {
        // 按下空格键从对象池中获取一个对象,并将其放置在鼠标点击的位置
        if (Input.GetKeyDown(KeyCode.Space))
        {
            GameObject obj = objectPool.GetObject();
            obj.transform.position = Input.mousePosition;
        }
    }
}

UML设计图

代码实现

设计本游戏时,我采用了MVC架构,MVC(Model-View-Controller)架构是一种软件设计模式,广泛应用于用户界面(UI)的设计和开发中。MVC 架构将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。

一、Action动作和动作管理器

SSActionManager类

Update()方法:

添加动作:将watinglist列表的动作添加到actions字典

更新动作:检查每个action的destory和enable状态,进行摧毁或更新

删除动作:遍历watinglist列表,从actions字典移除并销毁

RunAction()方法:

启用一个新动作,设置动作属性,添加到等待列表并启用。

RemainActionCoun()方法:

返回当前actions字典的动作数量

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

public class SSActionManager : MonoBehaviour {

	private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();
	private List <SSAction> waitingAdd = new List<SSAction> (); 
	private List<int> waitingDelete = new List<int> ();

	// Update is called once per frame
	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.destory) { 
				waitingDelete.Add(ac.GetInstanceID()); // release action
			} else if (ac.enable) { 
				ac.Update (); // update action
			}
		}

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


	// Use this for initialization
	protected void Start () {
	}

	public int RemainActionCount()
	{
		return actions.Count;
	}
}

CCActionManager类

SSActionEvent()方法:

当action完成时,调用DiskFactory的FreeDisk回收飞碟

MoveDisk()方法:

创建一个CCFlyAction示例,传入飞碟速度,使用RunAction方法启动该动作

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

public class CCActionManager : SSActionManager, ISSActionCallback {
	
	private FirstController sceneController;
	//private CCMoveToAction moveToA , moveToB, moveToC, moveToD;
	public CCFlyAction action;
	public DiskFactory factory;
	protected new void Start() {
		sceneController = (FirstController)SSDirector.getInstance().currentSceneController;
		sceneController.actionManager = this;
		factory = Singleton<DiskFactory>.Instance;
	}

	// Update is called once per frame
	// protected new void Update ()
	// {
	// 	base.Update ();
	// }
		
	#region ISSActionCallback implementation
	public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
	{
		factory.FreeDisk(source.transform.gameObject);
	}
	#endregion

	public void MoveDisk(GameObject disk){
		action = CCFlyAction.GetSSAction(disk.GetComponent<DiskAttributes>().speedX,disk.GetComponent<DiskAttributes>().speedY);
		RunAction(disk,action,this);
	}
}

CCFlyAction类

GetSSAction()方法:

创建一个CCFlyAction实例,并设置速度然后返回该实例

Start()方法

在动作开始时调用,配置飞碟的物理属性和初始速度

Update()方法:

检查飞碟的状态并处理出界和销毁的情况。

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

public class CCFlyAction : SSAction
{
    public float speedX;
    public float speedY;
    public static CCFlyAction GetSSAction(float x, float y){
        CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction> ();
        action.speedX = x;
        action.speedY = y;
        return action;
    }
    // Start is called before the first frame update
    public override void Start()
    {
        Rigidbody2D rb = this.transform.gameObject.GetComponent<Rigidbody2D>();
        rb.velocity = new Vector2(speedX, speedY);
        rb.gravityScale = 0f;
        Vector2 force = new Vector2(0, -8);
        rb.AddForce(force, ForceMode2D.Impulse);
    }

    // Update is called once per frame
    public override void Update()
    {
        if(this.transform.gameObject.activeSelf == false){
            //  如果飞碟已经被销毁
            Debug.Log("飞碟已经销毁");
            this.destory = true;
            this.callback.SSActionEvent(this);
            return;
        }

        Vector3 vec3 = Camera.main.WorldToScreenPoint(this.transform.position);
        // 飞碟出界
        if(vec3.x <-100 || vec3.x>Camera.main.pixelWidth+100 || vec3.y<-100 || vec3.y>Camera.main.pixelHeight+100){
            Debug.Log("飞碟出界");
            this.destory= true;
            this.callback.SSActionEvent(this);
            return;
        }
    }
    
}

二、Model类

Singleton类

该类用于单实例化飞碟工厂,使用了对象池的思想,通过使用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("instance null");
                }
            }
            return instance;
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

DiskFactory类

该类用于管理飞碟对象,通过建立used和free两个列表来存储飞碟对象进行管理

GetDisk()方法:

获取一个飞碟对象,用于在游戏中生成新的飞碟

如果free列表中有可用的飞碟对象,从free列表中取出一个,如果free列表为空,使用Instantiate方法创建一个新的飞碟对象,并为其添加DiskAttributes组件,设置飞碟的随机旋转角度与DiskAttributes属性,生成随机分数,按照分数设定速度、颜色、大小,生成随机飞入方向,启用飞碟对象。

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

public class DiskAttributes : MonoBehaviour
{
    public int score;
    public float speedX;
    public float speedY;
}
public class DiskFactory : MonoBehaviour
{
    List<GameObject> used;
    List<GameObject> free;
    System.Random rand;
    
    // Start is called before the first frame update
    void Start()
    {
        used = new List<GameObject>();
        free = new List<GameObject>();
        rand = new System.Random();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public GameObject GetDisk(int round){
        GameObject disk;
        if(free.Count != 0){
            disk = free[0];
            free.Remove(disk);
        }
        else{
            // 调用Instantiate方法来生成一个disk的复制
            disk = GameObject.Instantiate(Resources.Load("Prefabs/disk",typeof(GameObject))) as GameObject;
            disk.AddComponent<DiskAttributes>();
        }
        // 设置一个随机方向
        disk.transform.localEulerAngles = new Vector3(-rand.Next(20,40),0,0);
        // GetComponent方法是获取一个指定的组件,例如Rigidbody组件
        DiskAttributes attri = disk.GetComponent<DiskAttributes>();
        // 该飞碟的分数随机1-4
        attri.score = rand.Next(1,4);
        // 分数决定飞碟的速度颜色大小
        attri.speedX = (rand.Next(20,30) + attri.score + round)*0.2f;
        attri.speedY = (rand.Next(5,10) + attri.score + round)*0.2f;

        if(attri.score == 1){
            disk.GetComponent<Renderer>().material.color = Color.blue;
        }
        else if(attri.score == 2){
            disk.GetComponent<Renderer>().material.color = Color.green;
            disk.transform.localScale += new Vector3(-0.2f,0,-0.2f);
        }
        else if(attri.score == 3){
            disk.GetComponent<Renderer>().material.color = Color.red;
            disk.transform.localScale += new Vector3(-0.5f,0,-0.5f);
        }

        // 飞碟的飞入方向
        int direction = rand.Next(1,5);

        // 左上,左下,右上,右下
        if(direction == 1){
            disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight * 1.5f, 8)));
            attri.speedY *= 0;
        }
        else if (direction == 2) {
            disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight * 0f, 8)));
            attri.speedY =rand.Next(10,15);
        }
        else if (direction == 3) {
            disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight * 1.5f, 8)));
            attri.speedX *= -1;
            attri.speedY =0 ;
        }
        else if (direction == 4) {
            disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight * 0f, 8)));
            attri.speedX *= -1;
            attri.speedY =rand.Next(10,15);
        }
        used.Add(disk);
        disk.SetActive(true);
        return disk;
    }

    public void FreeDisk(GameObject disk){
        disk.SetActive(false);
        // 速度与大小恢复到预制
        disk.transform.position = new Vector3(0,0,0);
        disk.transform.localScale = new Vector3(2f,0.1f,2f);
        used.Remove(disk);
        free.Add(disk);
    }
}

三、Controller控制类

ScoreController裁判类

ScoreController是一个控制计算得分的类

Record()方法:

记录飞碟的得分并更新UI界面

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

public class ScoreController : MonoBehaviour
{
    int score;
    public FirstController firstController;
    public UserGUI userGUI;
    // Start is called before the first frame update
    void Start()
    {
        firstController = (FirstController)SSDirector.getInstance().currentSceneController;
        firstController.scoreController = this;
        userGUI = this.gameObject.GetComponent<UserGUI>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void Record(GameObject disk){
        score += disk.GetComponent<DiskAttributes>().score;
        userGUI.score = score;
    }
}

FirstController类

Awake()方法:

为当前游戏对象添加UserGUI,CCActionManager, ScoreController和DiskFactory组件。

GameOver()方法:

判断游戏是否结束,返回布尔值

Update()方法:

调用Gethit方法处理玩家射击,如果游戏结束,直接返回,否则,减少 timer,如果 timer 小于等于 0 且没有剩余动作,生成 20 个飞碟并增加轮次,更新 userGUI 的轮次显示,重置 timer

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

public class FirstController : MonoBehaviour, ISceneController, IUserAction {

	int round = 0;
	int max_round = 4;
	float timer = 0.5f;
	GameObject disk;
	DiskFactory diskFactory;
	public CCActionManager actionManager;
	//public GameObject move1,move2;
	public ScoreController scoreController;
	public UserGUI userGUI;
	// the first scripts
	void Awake () {
		SSDirector director = SSDirector.getInstance();
		director.currentSceneController = this;
		director.currentSceneController.LoadResources();
		gameObject.AddComponent<UserGUI>();
		gameObject.AddComponent<CCActionManager>();
        gameObject.AddComponent<ScoreController>();
        gameObject.AddComponent<DiskFactory>();
        diskFactory = Singleton<DiskFactory>.Instance;
        userGUI = gameObject.GetComponent<UserGUI>();
	}
	 
	// loading resources for first scence
	public void LoadResources () {
		;
	}

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

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

	#region IUserAction implementation
	public bool GameOver(){
		if(round >= max_round && actionManager.RemainActionCount()==0){
			userGUI.gameMessage = "Game Over";
			return true;
		}
		return false;
	}
	#endregion


	// Use this for initialization
	void Start () {
		//give advice first
	}
	
	// Update is called once per frame
	void Update () {
		//give advice first
		if (userGUI.mode == 0) return;
		GetHit ();
		if(GameOver()){
			return;
		}
		timer -= Time.deltaTime;
		if (timer <= 0 && actionManager.RemainActionCount()==0){
			for(int i=0; i<20; i++){
				disk = diskFactory.GetDisk(round);
				actionManager.MoveDisk(disk);
			}
			round += 1;
			if(round <= max_round){
				userGUI.round = round;
			}
			timer=4.0f;
		}
	}



	public void GetHit(){
		if(Input.GetButtonDown("Fire1")){
			Camera ca = Camera.main;
			// 把鼠标左键的点击转换为一条从摄像机视角射出的射线
			Ray ray = ca.ScreenPointToRay(Input.mousePosition);
			// 判断射线碰撞到的物体
			RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction);
			if(hit.collider != null){
				scoreController.Record(hit.transform.gameObject);
				// 将命中的物体销毁
				hit.transform.gameObject.SetActive(false);
			}
		}
	}

}

四、View

为游戏绘制了基本页面,mode=0时,是游戏开始前的界面,玩家可以点击开始游戏按钮开始,mode=1时,是游戏界面,会有计分标签等部件

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

public class UserGUI : MonoBehaviour
{
    public int mode;
    public int score;
    public int round;
    public string gameMessage;
    public GUIStyle bigStyle, smallStyle;
    private int menu_width = Screen.width / 5, menu_height = Screen.width/10; 
    // Start is called before the first frame update
    void Start()
    {
        mode = 0;
        gameMessage = "";
        bigStyle = new GUIStyle();
        bigStyle.normal.textColor = Color.white;
        bigStyle.normal.background = null;
        bigStyle.alignment = TextAnchor.MiddleCenter;
        bigStyle.fontSize = 50;

        smallStyle = new GUIStyle();
        smallStyle.normal.textColor = Color.white;
        smallStyle.alignment = TextAnchor.MiddleCenter;
        smallStyle.fontSize = 30;
        smallStyle.normal.background = null;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnGUI(){
        GUI.skin.button.fontSize = 35;
        switch(mode){
            case 0:
                mainMenu();
                break;
            case 1:
                GameStart();
                break;
        }

    }

    void mainMenu(){
        GUI.Label(new Rect(Screen.width / 2 - menu_width * 0.5f, Screen.height * 0.1f, menu_width, menu_height), "打飞碟", bigStyle);
        bool button = GUI.Button(new Rect(Screen.width/2 - menu_width*0.5f, Screen.height*0.4f, menu_width, menu_height),"开始游戏");
        if(button){
            mode = 1;
        }
    }

    void GameStart(){
        GUI.Label(new Rect(300, 60, 50, 200), gameMessage,bigStyle);
        GUI.Label(new Rect(0, 0, 100, 50), "Score: " + score, smallStyle);
        GUI.Label(new Rect(560, 0, 100, 50), "Round: " + round, smallStyle);
    }
}

结语

代码地址

感谢学长/学姐博客参考:参考博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值