使用圆柱体作为飞碟的模型,用不同的颜色区分不同分值和大小的飞碟,从屏幕边缘(相机选择正交投影)抛出飞碟,用鼠标点击进行射击。
实现上,通过为飞碟添加刚体组件,并勾选重力效果,就可以使运动轨迹为抛物线。抛出飞碟时,在下半屏幕的边缘(左边缘下半,右边缘下边,下边缘)选取随机点作为抛出点,以上半屏幕的中央区域的随机点为目标,并按照设定的速度抛出,就可以使得飞碟具有随机的运动轨迹,并且轨迹大体上位于屏幕内。
程序的UML图如下
为了方便游戏的配置,使用ScriptableObject对象,记录游戏规则的参数,定义如下。
using System;
using UnityEngine;
[CreateAssetMenu(fileName = "RuleAttributes", menuName = "(ScriptableObject)RuleAttributes")]
public class GameruleAttribute : ScriptableObject
{
[Tooltip("回合数")]
public int trailNum;
[Tooltip("每n回合增加一次飞碟数量")]
public int increaseRound;
[Tooltip("每次增加的飞碟数量")]
public int increaseNum;
[Tooltip("初始飞碟数量")]
public int initNum;
[Tooltip("3分飞碟阈值")]
public int Rate3;
[Tooltip("2分飞碟阈值")]
public int Rate2;
[Tooltip("1分飞碟阈值")]
public int Rate1;
[Tooltip("3分飞碟速度")]
public int Speed3;
[Tooltip("2分飞碟速度")]
public int Speed2;
[Tooltip("1分飞碟速度")]
public int Speed1;
}
因为需要重复10轮,每轮抛出数个飞盘,如果使用对象池管理飞盘对象,则可以减少创建和销毁游戏对象的开销。对象池实现如下。使用三个List存储三类飞碟的对象,每当需要激活并使用飞盘时,就从中寻找休眠的飞盘。如果没有空闲的飞盘,则创建一个并加入List。当飞盘被击中或者离开游戏区域时,将其设置为休眠状态,而不是销毁。飞碟的分值设计为1、2、3三种,分别制作为预制件:蓝色扁平圆柱、黄色扁平圆柱和红色扁平圆柱,半径依次减小。
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Pool;
public class ObjectPoolManager
{
public static ObjectPoolManager Instance { get; private set; }
public int initialPoolSize1 = 10; // 初始对象池大小
public int initialPoolSize2 = 10; // 初始对象池大小
public int initialPoolSize3 = 10; // 初始对象池大小
private List objectPool1; // 对象池
private List objectPool2; // 对象池
private List objectPool3; // 对象池
public static ObjectPoolManager getInstance()//提供单例的访问
{
if (Instance == null)
{
Instance = new ObjectPoolManager();
}
return Instance;
}
public ObjectPoolManager()
{
objectPool1 = new List();
objectPool2 = new List();
objectPool3 = new List();
for (int i = 0; i < initialPoolSize1; i++)
{
GameObject obj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish1"));
obj.SetActive(false);
obj.name = "score1";
obj.GetComponent().position =new Vector3(-10000, 0, 0);
objectPool1.Add(obj);
}
for (int i = 0; i < initialPoolSize2; i++)
{
GameObject obj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish2"));
obj.SetActive(false);
obj.name = "score2";
obj.GetComponent().position = new Vector3(-10000, 0, 0);
objectPool2.Add(obj);
}
for (int i = 0; i < initialPoolSize3; i++)
{
GameObject obj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish3"));
obj.SetActive(false);
obj.name = "score3";
obj.GetComponent().position = new Vector3(-10000, 0, 0);
objectPool3.Add(obj);
}
}
public GameObject GetObjectFromPool1()
{
foreach (GameObject obj in objectPool1)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
// 如果没有可用对象,则动态创建一个新对象
GameObject newObj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish1"));
newObj.name = "score1";
newObj.GetComponent().position = new Vector3(-10000, 0, 0);
objectPool1.Add(newObj);
return newObj;
}
public GameObject GetObjectFromPool2()
{
foreach (GameObject obj in objectPool2)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
// 如果没有可用对象,则动态创建一个新对象
GameObject newObj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish2"));
newObj.name = "score2";
newObj.GetComponent().position = new Vector3(-10000, 0, 0);
objectPool2.Add(newObj);
return newObj;
}
public GameObject GetObjectFromPool3()
{
foreach (GameObject obj in objectPool3)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
// 如果没有可用对象,则动态创建一个新对象
GameObject newObj = UnityEngine.Object.Instantiate(Resources.Load("Prefabs/dish3"));
newObj.name = "score3";
newObj.GetComponent().position = new Vector3(-10000, 0, 0);
objectPool3.Add(newObj);
return newObj;
}
public void ReturnObjectToPool(GameObject obj)
{
obj.SetActive(false);
}
}
抛出飞碟的代码如下,其中rule是上面的GameruleAttribute的实例。在需要抛出飞盘时,通过生成随机数,按照阈值参数选取要抛出的实体类型,从对象池中拿取并激活,然后选取随机初始位置和目标位置进行发射。
public void throwDish()
{
int r = UnityEngine.Random.Range(1, 101);
GameObject dish;
int speed;
if (r >= rule.Rate3)
{
dish = pool.GetObjectFromPool3();
speed = rule.Speed3;
}else if(r>= rule.Rate2)
{
dish = pool.GetObjectFromPool2();
speed = rule.Speed2;
}
else
{
dish = pool.GetObjectFromPool1();
speed = rule.Speed1;
}
r = UnityEngine.Random.Range(1,4);
float weight= UnityEngine.Random.Range(0f, 1f);
Vector3 p1 = new Vector3(0, 0, -680), p2 = new Vector3(0, -300, -680), p3 = new Vector3(0, -300, 680), p4 = new Vector3(0, 0, 680);
switch (r)
{
case 1:
dish.GetComponent().position = weight * p1 + (1 - weight) * p2;
break;
case 2:
dish.GetComponent().position = weight * p2 + (1 - weight) * p3;
break;
case 3:
dish.GetComponent().position = weight * p3 + (1 - weight) * p4;
break;
}
float targetz= UnityEngine.Random.Range(-150,150);
float targety = UnityEngine.Random.Range(100, 300);
Vector3 target = new Vector3(0,targety,targetz);
Vector3 direction = (target - dish.GetComponent().position).normalized;
dish.GetComponent().velocity = direction * speed;
}
为了能销毁离开游戏空间的飞碟,使用plane对象将游戏空间包围,当飞碟发生碰撞时移入休眠状态。为了避免飞碟相互碰撞导致的意外休眠,将飞碟设置为独立图层,并在项目设置中将取消勾选该图层对象之间的相互碰撞。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DishCllision : MonoBehaviour
{
// Start is called before the first frame update
private void OnCollisionEnter(Collision collision)
{
this.gameObject.GetComponent().position = new Vector3(100000, 0, 0);
SSDirector.getInstance().currentSceneController.RemoveCollision(this.gameObject);
}
}
游戏逻辑部分,通过使用场记对象管理其他对象,提供初始化场景、抛飞碟、读取用户点击对象等功能,管理对象池,并保存和维护记录游戏进度和得分的变量。通过调用裁判类提供的方法,维护得分,判断回合以及游戏是否结束。
裁判类的实现如下,该类不继承MonoBehaviour,实现为单例模式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Referee : IReferee
{
public FirstController controller;
public static Referee Instance;
public int Score{get; set;}
public static Referee getInstance()//提供单例的访问
{
Debug.Log("getReferee");
if (Instance == null)
{
Instance = new Referee();
}
return Instance;
}
// Update is called once per frame
public void check()
{
Debug.Log(controller.activeDish);
bool b1 = CheckRoundOver();
bool b2 = CheckGameOver();
if (b1 && !b2)
{
controller.startNextTurn();
}
}
public bool CheckRoundOver()
{
if (controller.activeDish == 0)
{
controller.curtrail++;
return true;
}
return false;
}
public bool CheckGameOver()
{
if (controller.rule.trailNum == controller.curtrail)
{
controller.GameOver();
return true;
}
return false;
}
public void ShootDish(GameObject dish)
{
switch (dish.name)
{
case "score1":
Score += 1;
break;
case "score2":
Score += 2;
break;
case "score3":
Score += 3;
break;
}
dish.GetComponent().position = new Vector3(-10000, 0, 0);
controller.pool.ReturnObjectToPool(dish);
controller.activeDish--;
}
public void SetController(ISceneController c)
{
controller = c as FirstController;
}
public int getScore()
{
return Score;
}
public void Restart()
{
Score = 0;
}
}
游戏视屏:
完整代码:GitHub - xu-yongjia/3DgameDesign: Homework