游戏规则
- 游戏包括了n个round(回合),每个回合有十次trial。
- 通过按空格开始每个trial,同时会发射飞碟(UFO),发射飞碟的数量会逐渐递增,为了减低游戏难度我把发射飞碟的数量限制为最多3个(因为自己体验的时候发现5个的话要全命中难度较大)。颜色/角度/速度均随机产生。
- 用户在每一个trial必须在飞碟落地前击毙所有UFO才能成功进入下一个trial,当通关十个trial的时候会自动进入下一轮。
- 用户每击中一个飞碟加100分,但如果无法通过trail会使分数清0。
游戏设计
UML图
游戏开发
接口
这里IUserAction接口定义了与用户交互相关的动作,用户可以通过按下空格发射飞碟,也可以点击鼠标打飞碟。而IGameStatusOp接口定义了与游戏状态相关的工作,由场记(SceneController)控制。
public interface IUserAction
{
void launchUFO();
void hitUFO(Vector3 mousePos);
}
public interface IGameStatusOp
{
int getRoundNum();
void addScore();
void subScore();
}
具体的用户接口实现如下:
public class UserInterface : MonoBehaviour {
private IUserAction action;
// Use this for initialization
void Start () {
action = SceneController.getInstance() as IUserAction;
}
// Update is called once per frame
void Update () {
//检测鼠标点击
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Input.mousePosition;
action.hitUFO(mousePos);
}
//检测用户按下空格
if (Input.GetKeyDown(KeyCode.Space))
{
action.launchUFO();
}
}
}
场记
场记采用了单例模式,这样在其他地方调用getInstance()获取到的就是同一个SceneController对象。不同于前面几个星期,游戏由导演、场记、动作管理员、演员构成;在这个打飞碟游戏中,场记(由于突然有钱)请来了记分员和飞碟管理者,当用户按下空格,场记监控到只需要大喊一声“嘿!飞碟管理者,大胆放出你的飞碟吧~~”,具体怎么发射飞碟由飞碟管理者控制,场记就可以躺下来好好休息了~当然,记分相关的工作也都分发给了记分员,有钱真好~
public class SceneController : System.Object, IUserAction, IGameStatusOp
{
private static SceneController instance; //单例模式
private UFOController myUFOCtrl; //飞碟管理者
private StatusController myStatusCtrl; //记分员
public static SceneController getInstance()
{
if (instance == null) instance = new SceneController();
return instance;
}
internal void setUFOController(UFOController ufoCtrl)
{
if (myUFOCtrl == null) myUFOCtrl = ufoCtrl;
}
internal void setStatusController(StatusController gameStatus)
{
if (myStatusCtrl == null) myStatusCtrl = gameStatus;
; }
//记分员负责的工作
public void addScore()
{
myStatusCtrl.addScore();
}
public int getRoundNum()
{
return myStatusCtrl.getRoundNum();
}
public void subScore()
{
myStatusCtrl.subScore();
}
//飞碟管家的任务
public void launchUFO()
{
//每次发射之前清0分数
myStatusCtrl.resetScore();
myUFOCtrl.launchUFO();
}
public void hitUFO(Vector3 mousePos)
{
myUFOCtrl.hitUFO(mousePos);
}
}
记分员
记分员负责记录回合数以及当前玩家的得分情况,它持有几个用来显示分数及回合的GameObject对象,然后等待场记的命令。当它收到一个扣分命令的时候,就执行subScore()函数,给用户减分直到不能再减(0);当收到加分命令,就为用户添加100分,如果分数达到了当前关卡上限,会自动进入下一个trial,若trial超过10则成功进入了下一回合。同时,场记也可以命令记分员清0分数,以便重新开始。
public class StatusController : MonoBehaviour
{
//各种预设资源
public GameObject canvasItem, roundTextItem, scoreTextItem, TipsTextItem;
//回合数
private int roundNum = 1;
//trial数
private int trialNum = 1;
//得分
private int score = 0;
//每一个trial的得分上界
private int scoreUpBound = 100;
//Tips显示的时间
private const float TIPS_TEXT_SHOW_TIME = 0.8f;
private GameObject canvas, roundText, scoreText, TipsText;
private SceneController scene;
// Use this for initialization
void Start()
{
scene = SceneController.getInstance();
scene.setStatusController(this);
canvas = Instantiate(canvasItem);
roundText = Instantiate(roundTextItem, canvas.transform);
roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
scoreText = Instantiate(scoreTextItem, canvas.transform);
scoreText.GetComponent<Text>().text = "Score: " + score + " / " + (roundNum * 100);
TipsText = Instantiate(TipsTextItem, canvas.transform);
showTipsText();
}
public int getRoundNum() { return roundNum; }
void addRoundNum()
{
roundNum++;
roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
}
public int getScore()
{
return score;
}
public int getTrialNum() { return trialNum; }
public void addScore()
{
score += 100;
scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
Debug.Log(scoreUpBound);
//当分数达到当前回合最大分数自动进入下一轮
if (score >= scoreUpBound)
{
trialNum++;
updateScoreUpBound();
roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
resetScore();
showTipsText();
}
if (trialNum > 10)
{
addRoundNum();
trialNum = 1;
roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
updateScoreUpBound();
resetScore();
showTipsText();
}
}
private void updateScoreUpBound()
{
scoreUpBound = trialNum > 3 ? 300 : trialNum * 100;
}
private void showTipsText()
{
TipsText.GetComponent<Text>().text = "Round " + roundNum + ": Trial " + trialNum + "!";
TipsText.SetActive(true);
StartCoroutine(waitForSomeTimeAndDisappearTipsText());
}
IEnumerator waitForSomeTimeAndDisappearTipsText()
{
yield return new WaitForSeconds(TIPS_TEXT_SHOW_TIME);
TipsText.SetActive(false);
}
public void resetScore()
{
score = 0;
scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
}
public void subScore()
{
score = score >= 100 ? score - 100 : 0;
scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
}
}
飞碟管理者
飞碟管理者主要管理发射飞碟动作及判断用户是否击中飞碟。当场记发出一个发射飞碟信号时,飞碟管理者会判断当前是否有飞碟处于发射状态,如果有便不再发射新的飞碟,否则根据当前回合数来发射出一定数量的飞碟,飞碟的颜色也由回合数来决定。而当用户击中飞碟的时候,飞碟管理者会通知场记需要进行加分动作,然后把被击中的飞碟回收。添加了击中爆炸效果(createExplosion)使得看起来更加酷炫。
public class UFOController : MonoBehaviour {
//预设文件
public GameObject PlaneItem, LauncherItem, ExplosionItem;
public Material greenMat, redMat, blueMat;
private GameObject plane, launcher, explosion;
private SceneController scene;
//发射每一个飞碟的时间间隔
private const float LAUNCH_GAP = 0.1f;
// Use this for initialization
void Start () {
scene = SceneController.getInstance();
scene.setUFOController(this);
plane = Instantiate(PlaneItem);
launcher = Instantiate(LauncherItem);
explosion = Instantiate(ExplosionItem);
}
// Update is called once per frame
void Update () {
UFOFactory.getInstance().detectLandingUFOs();
}
public void launchUFO()
{
//限制最大发射飞碟的数量为3
int trailNum = scene.getTrailNum() > 3 ? 3 : scene.getTrailNum();
Debug.Log("发射!");
if (!UFOFactory.getInstance().isLaunching())
{
StartCoroutine(launchUFOs(trailNum));
}
}
IEnumerator launchUFOs(int roundNum)
{
for(int i = 0; i<roundNum; i++)
{
GameObject UFO = UFOFactory.getInstance().getUFO();
UFO.transform.position = launcher.transform.position;
UFO.GetComponent<MeshRenderer>().material = getMaterial(scene.getTrailNum());
Vector3 force = getRandomForce();
UFO.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
//每隔LAUNCH_GAP时间发射一个UFO
yield return new WaitForSeconds(LAUNCH_GAP);
}
}
private Vector3 getRandomForce()
{
int x = UnityEngine.Random.Range(-30, 31);
int y = UnityEngine.Random.Range(30, 41);
int z = UnityEngine.Random.Range(20, 31);
float t = 0.7f + scene.getTrailNum() / 20;
return new Vector3(x, y, z) * t;
}
public void hitUFO(Vector3 mousePos)
{
Ray ray = Camera.main.ScreenPointToRay(mousePos);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
if (hit.collider.gameObject.tag.Equals("UFO"))
{
createExplosion(hit.collider.gameObject.transform.position);
scene.addScore();
UFOFactory.getInstance().RecyclingUFO(hit.collider.gameObject);
}
}
}
private void createExplosion(Vector3 position)
{
explosion.transform.position = position;
explosion.GetComponent<ParticleSystem>().GetComponent<Renderer>().material = getMaterial(scene.getTrailNum());
explosion.GetComponent<ParticleSystem>().Play();
}
private Material getMaterial(int roundNum)
{
switch(roundNum % 3)
{
case 0:
return redMat;
case 1:
return greenMat;
case 2:
return blueMat;
default:
return redMat;
}
}
}
飞碟工厂
由于飞碟对象需要大量地创建和销毁,为了降低开销使用了工厂。工厂里维护了两个列表,一个用来记录正在运行的飞碟,一个用来记录空闲的飞碟。对于使用过的飞碟,当它飞出边界或者被用户击中的时候能够被工厂回收。当需要提供飞碟对象的时候就从空闲列表里面找,如果列表里没有飞碟则新建一个。使用工厂的好处还可以屏蔽创建与销毁的业务逻辑,从而使程序易于扩展。
public class UFOFactory : System.Object {
private static UFOFactory instance; //单例工厂
private List<GameObject> usedUFOList = new List<GameObject>(); //使用中的飞碟
private List<GameObject> freeUFOList = new List<GameObject>(); //空闲的飞碟
private GameObject UFOItem; //飞碟预设
public static UFOFactory getInstance()
{
if (instance == null) instance = new UFOFactory();
return instance;
}
public GameObject getUFO()
{
if(freeUFOList.Count == 0)
{
GameObject newUFO = Camera.Instantiate(UFOItem);
usedUFOList.Add(newUFO);
return newUFO;
}
else
{
GameObject oldUFO = freeUFOList[0];
freeUFOList.RemoveAt(0);
oldUFO.SetActive(true);
usedUFOList.Add(oldUFO);
return oldUFO;
}
}
//检测飞镖落地,回收。由飞碟管理者update()调用
public void detectLandingUFOs()
{
for(int i = 0; i < usedUFOList.Count; i++)
{
if(usedUFOList[i].transform.position.y <= -8)
{
usedUFOList[i].GetComponent<Rigidbody>().velocity = Vector3.zero;
usedUFOList[i].SetActive(false);
freeUFOList.Add(usedUFOList[i]);
usedUFOList.Remove(usedUFOList[i]);
i--;
//SceneController.getInstance().subScore();
}
}
}
//飞镖被击中,回收
public void RecyclingUFO(GameObject UFOObject)
{
UFOObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
UFOObject.SetActive(false);
freeUFOList.Add(UFOObject);
usedUFOList.Remove(UFOObject);
}
//是否处于发射飞碟阶段,即空中是否有飞碟在飞
public bool isLaunching()
{
return (usedUFOList.Count > 0);
}
public void initItems(GameObject ufoItem)
{
UFOItem = ufoItem;
}
}
public class UFOFactoryBC: MonoBehaviour
{
public GameObject ufoItem;
private void Awake()
{
UFOFactory.getInstance().initItems(ufoItem);
}
}
演示
演示视频移步B站:https://www.bilibili.com/video/av22190043/
完整源码见github:https://github.com/CarolSum/Unity3d-Learning/tree/master/hw4