打飞碟 Unity3D游戏设计

 游戏设计

游戏简介

        一个从左右发射飞碟让人点击的游戏。

规则

       规定回合数为3

        规定每回合发射次数为5

        每次发射的飞碟数为2或3

        飞碟有两种颜色,两种大小,其中大飞碟初始1分,小飞碟初始2分;蓝色加2分,红色加1分;除此之外,飞碟的速度大小分为三级,分别加分1、2、3分。

        飞碟会从左往右发射,或者从右往左发射,一旦落入云下就会消失(回收)。

游戏启动准备:

        脚本UserGUI挂到Main Camera上,FirstSceneController挂到一个空对象上。此外,Main Camera上可以加天空盒组件(Skybox)修饰一下背景。

玩法:

        运行后,玩家可以点击左上角的start按钮开始游戏,也可以用restart按钮刷新游戏为准备状态。游戏中可以点击飞碟得分。

游戏演示

打飞碟 - 游戏演示

上面视频中演示了两次游戏。(可能是录屏的原因,鼠标位置似乎偏上了,实际我鼠标是对准船了的)

游戏组成

预制体

代码文件

工厂对象设计

总UML图

飞碟工厂部分UML图

飞碟工厂对象实现为单例类,由回合控制器RoundController使用,工厂对象负责创建飞碟和回收飞碟,并根据回合控制器的要求改造飞碟属性,这样,只有当游戏同一时间需要的飞碟数量大于已有飞碟对象时,才需要创建新的飞碟对象。

游戏代码

场景控制器FirstController

加载一些场景预制体,实现用户动作接口的点击(射线)等方法和场景控制器接口方法。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
using UnityEngine;

/* 游戏状态,0为准备进行,1为正在进行游戏,2为结束 */
enum GameState
{
    Ready = 0, Playing = 1, GameOver = 2
};

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    private RoundController roundController; // 回合控制器
    private int gameState;

    private void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
    }
    void Start()
    {
        
        roundController = gameObject.AddComponent<RoundController>();

        gameState = (int)GameState.Ready;

    }

    public void LoadResources()
    {
        // 云
        GameObject plane = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Plane"), Vector3.zero, Quaternion.identity);
    }

    public void SetGameState(int state)
    {
        gameState = state;
    }

    public int GetGameState()
    {
        return gameState;
    }
    public void Restart()
    {
        gameState = (int)GameState.Ready;
        roundController.Reset();
    }
    public void start()
    {
        if(gameState == (int)GameState.Ready)
            gameState = (int)GameState.Playing;
    }

    public string check()
    {
        if(this.gameState == (int)GameState.GameOver)
        {
            return "GameOver!\nYour Score:"+roundController.GetScores();
        }
        return null;
    }

    public void shoot()
    {

        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.gameObject.GetComponent<DiskData>() != null)
                {
                    // 把击中的飞碟移出屏幕,触发回调释放
                    hit.collider.gameObject.transform.position = new Vector3(0, -6, 0);
                    // 记录飞碟得分
                    roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());
                }
            }
        }

    }
}

实现的接口:

using System;

public interface IUserAction
{
	void Restart();

	void start();

	void shoot();

	string check();
}
using System;

public interface ISceneController
{
	void LoadResources();
}

用户GUI界面UserGUI

调用IUserAction方法,实现点击功能、游戏结束提示

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

public class UserGUI : MonoBehaviour {
	private IUserAction action;
    private GUIStyle style;

	void Start () {
		action = SSDirector.getInstance ().currentSceneController as IUserAction;
	}
    void OnGUI()
    {
        float width = Screen.width / 8;
        float height = Screen.height / 12;
        if (GUI.Button(new Rect(0, 0, width, height), "Start"))
        {
            action.start();
        }
        if (GUI.Button(new Rect(width, 0, width, height), "ReStart"))
        {
            action.Restart();
        }
        if (action.check() != null )
        {
            GUI.Box(new Rect(width * 3.5f, height * 5, width*2, height*2), action.check());
        }

    }
    void Update()
    {
        if (((FirstController)SSDirector.getInstance().currentSceneController).GetGameState() == 1)
            action.shoot();
    }
}

导演类SSDirector

管理场景

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

public class SSDirector : System.Object
{
    private static SSDirector instance;
    public ISceneController currentSceneController { get; set; }
    public static SSDirector getInstance()
    {
        if (instance == null)
        {
            instance = new SSDirector();
        }
        return instance;
    }
}

单例模板类

用于工厂类的单实例化

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

飞碟数据

飞碟大小、速度大小和颜色。

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

public class DiskData : MonoBehaviour
{
    // Start is called before the first frame update

    public int size;

    public int speed;

    public Color color;
    
}

飞碟工厂对象

工厂对象中实现一个对象池,将飞碟对象分在被使用和没有使用两个队列,可以在飞碟使用完后回收,减少飞碟对象的重复创建。

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

public class DiskFactory : MonoBehaviour
{
    public GameObject diskPrefab; // 飞碟游戏对象,创建新的飞碟游戏对象的复制对象
    private List<DiskData> used; // 正在被游戏使用的飞碟对象
    private List<DiskData> free; // 没有被使用的空闲飞碟对象

    public void Start()
    {
        diskPrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
        diskPrefab.SetActive(false);
        used = new List<DiskData>();
        free = new List<DiskData>();
    }

    // 飞碟获取方法,根据ruler获取相应飞碟
    public GameObject GetDisk(Ruler ruler)
    {
        GameObject disk;
        // 从缓存中获取飞碟,没有则先创建
        int diskNum = free.Count;
        if (diskNum == 0)
        {
            disk = GameObject.Instantiate(diskPrefab, Vector3.zero, Quaternion.identity);
            disk.AddComponent(typeof(DiskData));
        }
        else
        {
            disk = free[diskNum - 1].gameObject;
            free.Remove(free[diskNum - 1]);
        }

        // 根据ruler设置disk的速度、颜色、大小、飞入方向
        disk.GetComponent<DiskData>().speed = ruler.speed;
        disk.GetComponent<DiskData>().color = ruler.color;
        disk.GetComponent<DiskData>().size = ruler.size;

        // 给飞碟上颜色
        disk.GetComponent<Renderer>().material.color = ruler.color;


        // 绘制飞碟大小
        disk.transform.localScale = new Vector3(1.5f * (float)ruler.size, 0.2f, 1.5f * (float)ruler.size);
        // 绘制碰撞器大小
        disk.GetComponent<BoxCollider>().size = disk.transform.localScale;
        // 选择飞碟飞入屏幕的起始位置
        disk.transform.position = ruler.beginPos;

        // 设置飞碟显示
        disk.SetActive(true);

        // 将飞碟加入使用队列
        used.Add(disk.GetComponent<DiskData>());

        return disk;
    }

    // 飞碟回收方法,将不使用的飞碟从使用队列放到空闲队列中
    public void FreeDisk(GameObject disk)
    {
        foreach (DiskData d in used)
        {
            if (d.gameObject.GetInstanceID() == disk.GetInstanceID())
            {
                disk.SetActive(false);
                used.Remove(d);
                free.Add(d);
                break;
            }

        }
    }
}

动作管理器部分

动作类(抽象类)SSAction

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

public class SSAction : ScriptableObject {

	public bool enable = true;
	public bool destory = false;

	public GameObject gameobject { get; set; }
	public Transform transform { get; set; }
	public ISSActionCallback callback { get; set; }

	protected SSAction () {}

	// Use this for initialization
	public virtual void Start () {
		throw new System.NotImplementedException ();
	}
	
	// Update is called once per frame
	public virtual void Update () {
		throw new System.NotImplementedException ();
	}
		
}

飞碟具体动作类FlyAction

GetSSAction方法在动作管理器CCActionManager中调用,获取动作信息(目标向量和速度)。FlyAction设置对应飞碟对象的初速度,在Update中进行动作结束判断,并由回调函数回收飞碟。

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

public class FlyAction : SSAction
{
    float speed; // 速度大小
    Vector3 direction; // 飞行方向

    public static FlyAction GetSSAction(Vector3 direction, float speed)
    {
        FlyAction action = ScriptableObject.CreateInstance<FlyAction>();
        action.speed = speed;
        action.direction = direction;
        return action;
    }

    public override void Start()
    {
        gameobject.GetComponent<Rigidbody>().isKinematic = false;

        gameobject.GetComponent<Rigidbody>().velocity = speed * direction;
    }

    public override void Update()
    {
        // 飞碟到达底部动作结束,回调
        if (this.transform.position.y < -1)
        {
            this.destory = true;
            this.enable = false;
            this.callback.SSActionEvent(this);
        }
    }
}

动作控制器基类SSActionManager

RunAction方法将待执行动作加入执行队列中,在Update中执行,执行过的动作加入销毁队列销毁。不过游戏要求同一时间只有一个动作,所以用了裁判类判断来限制了动作的生成和积累。

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

动作控制器类CCActionManager

实现动作管理器接口和回调函数。

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;


public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager
{
    FlyAction PlayDiskAction; // 飞碟空中动作

    public void PlayDisk(GameObject disk, float speed, Vector3 direction)
    {
        PlayDiskAction = FlyAction.GetSSAction(direction, speed);
        RunAction(disk, PlayDiskAction, this);
    }

    // 回调函数
    public void SSActionEvent(SSAction source,
    SSActionEventType events = SSActionEventType.Competed,
    int intParam = 0,
    string strParam = null,
    Object objectParam = null)
    {
        // 结束飞行后回收飞碟
        Singleton<RoundController>.Instance.FreeFactoryDisk(source.gameobject);
    }
}


实现的接口:

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

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


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

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

规则Ruler类

游戏中的回合数,每个回合的发射数,以及回合控制器调用飞碟工厂获取对应飞碟、设置动作时需要的飞碟数据。

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

// 飞碟获取规则
public struct Ruler
{
    public int trialNum; // 当前trial的编号
    public int roundNum; // 当前round的编号
    public int roundSum; // 一共round的总数目
    public int[] roundDisksNum; // 每一轮对于trial的飞碟数量
    public float sendTime; // 发射间隔时间

    public int size; // 飞碟大小
    public int speed; // 飞碟速度
    public Color color; // 飞碟颜色
    public Vector3 direction; // 飞碟飞入方向
    public Vector3 beginPos; // 飞碟飞入位置
};

回合控制器类

实现获取飞碟、设置动作等具体过程。

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class RoundController : MonoBehaviour
{
    private IActionManager actionManager; // 选择飞碟的运动类型
    private ScoreRecorder scoreRecorder; // 记分器
    private FirstController mainController;
    private Ruler ruler; // 飞碟获取规则

    void Start()
    {
        actionManager =gameObject.AddComponent<PhysisActionManager>();
        scoreRecorder = new ScoreRecorder();
        mainController = (FirstController)SSDirector.getInstance().currentSceneController;
        gameObject.AddComponent<DiskFactory>();
        InitRuler();
    }

    void InitRuler()
    {
        ruler.trialNum = 0;
        ruler.roundNum = 0;
        ruler.sendTime = 0;
        ruler.roundSum = 3;
        ruler.roundDisksNum = new int[10];
        generateRoundDisksNum();
    }

    // 生成每trial同时发出的飞碟数量的数组,同时发出飞碟个数不超过3
    public void generateRoundDisksNum()
    {
        for (int i = 0; i < 5; ++i)
        {
            ruler.roundDisksNum[i] = Random.Range(2, 4);
        }
    }

    public void Reset()
    {
        InitRuler();
        scoreRecorder.Reset();
        FreeAllFactoryDisk();
    }

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

    public int GetScores()
    {
        return scoreRecorder.score;
    }

    // 发射飞碟
    public void LaunchDisk()
    {
        // 使飞碟飞入位置尽可能分开,从不同位置飞入使用的数组

        for (int i = 0; i < ruler.roundDisksNum[ruler.trialNum]; ++i)
        {
            // 获取随机数
            int randomNum = Random.Range(0, 3) + 1;
            // 飞碟速度随回合数增加而变快,这样难度增加
            ruler.speed = randomNum;

            // 重新选取随机数,并根据随机数选择飞碟颜色
            randomNum = Random.Range(0, 2) + 1;
            if (randomNum == 1)
            {
                ruler.color = Color.red;
            }
            else
            {
                ruler.color = Color.blue;
            }

            // 重新选取随机数,并根据随机数选择飞碟的大小
            ruler.size = Random.Range(0, 2) + 1;
            // 从左到右 或者 从右到左
            if(Random.Range(0, 2) == 0)
            {
                ruler.direction = new Vector3(5, 5, 0);

                // 重新选取随机数,并使不同飞碟的飞入位置尽可能分开
                ruler.beginPos = new Vector3(-15, (Random.Range(0, 3) + 1) * 2, 0);
            }
            else
            {
                ruler.direction = new Vector3(-5, 5, 0);

                // 重新选取随机数,并使不同飞碟的飞入位置尽可能分开
                ruler.beginPos = new Vector3(15, (Random.Range(0, 3)+1)* 2, 0);
            }


            // 根据ruler从工厂中生成一个飞碟
            GameObject disk = Singleton<DiskFactory>.Instance.GetDisk(ruler);

            // 设置飞碟的飞行动作
            actionManager.PlayDisk(disk, ruler.speed, ruler.direction);
        }
    }

    // 释放工厂飞碟
    public void FreeFactoryDisk(GameObject disk)
    {
        Singleton<DiskFactory>.Instance.FreeDisk(disk);
    }

    // 释放所有工厂飞碟
    public void FreeAllFactoryDisk()
    {
        GameObject[] obj = FindObjectsOfType(typeof(GameObject)) as GameObject[];
        foreach (GameObject g in obj)
        {
            if (g.gameObject.name == "Disk(Clone)(Clone)")
            {
                Singleton<DiskFactory>.Instance.FreeDisk(g);
            }
        }
    }

    void Update()
    {
        Debug.Log(mainController.GetGameState());
        if (mainController.GetGameState() == (int)GameState.Playing)
        {
            Debug.Log("roundSum:" + ruler.roundSum);
            ruler.sendTime += Time.deltaTime;
            // 每隔2s发送一次飞碟(trial)
            if (ruler.sendTime > 2)
            {
                Debug.Log("roundNum:" + ruler.roundNum);
                ruler.sendTime = 0;
                // 如果为无限回合或还未到设定回合数
                if ( ruler.roundNum < ruler.roundSum)
                {
                    Debug.Log("trialNum:"+ ruler.trialNum);
                    // 发射飞碟,次数trial增加
                    LaunchDisk();
                    ruler.trialNum++;
                    // 当次数trial等于2时,说明一个回合已经结束,回合加一,重新生成飞碟数组
                    if (ruler.trialNum == 5)
                    {
                        ruler.trialNum = 0;
                        ruler.roundNum++;
                        generateRoundDisksNum();
                    }
                }
                // 否则游戏结束,提示重新进行游戏
                else
                {
                    mainController.SetGameState((int)GameState.GameOver);
                }
            }
        }
    }
}

计分器

完成每次游戏的计分任务,由回合控制器控制。

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

public class ScoreRecorder
{
    public int score; // 游戏分数

    public ScoreRecorder()
    {
        score = 0;
    }

    /* 记录分数,根据点击中的飞碟的大小,速度,颜色计算得分 */
    public void Record(DiskData disk)
    {
        // 飞碟越小分就越高,大小为1(1.5×1.5×1)得2分,大小(2×2×1.5)为2得1分
        int diskSize = disk.size;
        switch (diskSize)
        {
            case 1:
                score = 2;
                break;
            case 2:
                score = 1;
                break;
            default: break;
        }


        // 颜色为红色得1分,颜色为蓝色得2分
        Color diskColor = disk.color;
        if (diskColor == Color.red)
        {
            score += 1;
        }
        else if (diskColor == Color.blue)
        {
            score += 2;
        }
        // 速度越快分就越高
        score += disk.speed;
    }

    /* 重置分数,设为0 */
    public void Reset()
    {
        score = 0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值