编写一个简单的鼠标打飞碟(Hit UFO)游戏。
源代码:GitHub - ShirohaBili/UnityGame
首先是介绍最重要的Controller类,在这个类中,实现了游戏运行的各项基本逻辑,先看代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Controller : MonoBehaviour, ISceneController, IUserAction
{
// Start is called before the first frame update
public DiskFactory factory = new DiskFactory();
public BoomFactory boom_fac = new BoomFactory();
public Score record = new Score();
public UserGUI User_GUI = new UserGUI();
public FlyActionManager action_manager;
Vector3 startPos = new Vector3(0,-10f,0);
private int count = 0;
void Start()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
factory = Singleton<DiskFactory>.Instance;
boom_fac = Singleton<BoomFactory>.Instance;
record = Singleton<Score>.Instance;
action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
User_GUI = gameObject.AddComponent<UserGUI>() as UserGUI;
}
bool run = false; //是否开始
int trial = 0; //回合内的计数器
public int round = 1; //回合数
float BoomTimer = 0; //计数器
float maxTime = 2; //爆炸特效回收前持续秒数
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("Fire1")){
Vector3 mp = Input.mousePosition;
hit(mp);
}
// Debug.Log(record.getScore());
if (run){
count++;
if (round == 1){ //第一回合
if (count == 300){
float num = Random.Range(0f,1f);
if (num <= 0.75f) send(1);
else if (num > 0.75f && num < 0.95f) send(2);
else send(3);
count = 0;
trial++;
if (trial == 10) {
round++;
trial = 0;
}
}
}
else if (round == 2){ //第二回合
if (count == 250){
float num = Random.Range(0f,1f);
if (num <= 0.5f) send(1);
else if (num > 0.5f && num < 0.9f) send(2);
else send(3);
count = 0;
trial++;
if (trial == 10){
round++;
trial = 0;
}
}
}
else if (round == 3){ //第三回合
if (count == 200){
float num = Random.Range(0f,1f);
if (num <= 0.4f) send(1);
else if (num > 0.4f && num < 0.8f) send(2);
else send(3);
count = 0;
trial++;
if (trial == 10){
run = false;
}
}
}
factory.FreeDisk();
}
if(!boom_fac.getEmpty()){ //回收爆炸特效
BoomTimer +=Time.deltaTime;
if (BoomTimer >= maxTime){
boom_fac.OutList();
BoomTimer = 0;
}
}
}
public void send(int type){ //发送飞盘
GameObject curDisk = factory.GetDisk(type);
//随机产生飞盘
float ran_y = 0;
float ran_x = Random.Range(-1f, 1f);
if (ran_x < 0) ran_x = -1f;
else ran_x = 1f;
float power = 0;
float angle = 0;
if (type == 1){ //蓝色飞盘分数为1,简单一点,速度慢,倾角大
ran_y = Random.Range(0f,2f);
power = Random.Range(1f,2f);
angle = Random.Range(20f,30f);
}
else if (type == 2){ //红色飞盘分数为2,稍难一点,速度稍快,倾角适中
ran_y = Random.Range(0f,2f);
power = Random.Range(2f,3f);
angle = Random.Range(10f,20f);
}
else if (type == 3){ //绿色飞盘分数为3,最难,速度最快,倾角最小
ran_y = Random.Range(0f,3f);
power = Random.Range(3f,4f);
angle = Random.Range(0f,10f);
}
curDisk.transform.position = new Vector3(ran_x*16f, ran_y, 0);
action_manager.DiskFly(curDisk, angle, power);
}
public void hit(Vector3 pos){ //判断是否击中
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit hit;
if (Physics.Raycast(ray,out hit)){
if (hit.collider.gameObject.GetComponent<Disk>() !=null){
Vector3 targetPos = hit.collider.gameObject.transform.position;
boom_fac.getExp(targetPos);
record.add(hit.collider.gameObject);
hit.collider.gameObject.transform.position = startPos;
}
}
}
public void Reset(){ //重新开始
run = true;
round = 1;
trial = 0;
record.clear();
}
public float getScore(){
return record.getScore();
}
public int getRound(){
return round;
}
public int getTrial(){
return trial;
}
public void clearTrial(){
trial = 0;
}
}
DiskFactory和BoomFactory分别是我按照工厂模式设计的负责生产和回收的类,分别负责飞碟和爆炸特效的生成和回收。
Score是我设计的负责计分的类,类似于现实游戏中的裁判。其中,击中蓝色飞盘记一分,击中红色飞盘记两分,击中绿色飞盘记三分。
UserGUI为用户接口,提供了界面UI与用户交互,提供了计分板和选择难度按钮供用户选择。
发射飞盘我采用了随机发射的方法,利用随机数来产生飞盘的初始位置、速度和倾角,按难度高低设置了不同的范围。
剩下的就是各项游戏对象的具体实现方法了,因为采用了动作分离,所以类会有点多,这里就选其中一些最重要的类进行介绍。
首先是DIsk基类,规定了DIsk应具备的基本性质,再按照工厂模式设计了一个DiskFactory类,用于产生和回收飞碟,代码如下:
Disk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//飞碟基类,Disk只需要记录三个特征即可
public class Disk : MonoBehaviour
{
public int type = 1;
public Color color = Color.black;
public int score = 0;
}
DiskFactory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 工厂
public class DiskFactory : MonoBehaviour {
private List<Disk> used = new List<Disk>();
private List<Disk> free = new List<Disk>();
Vector3 startPos = new Vector3(0,-10f,0);
public GameObject GetDisk(int type) {
GameObject curdisk = null;
if (free.Count>0) {
for(int i = 0; i < free.Count; i++) {
if(!(free[i].type==1||free[i].type==2||free[i].type==3))
Debug.Log("error");
if (free[i].type == type) {
curdisk = free[i].gameObject;
free.Remove(free[i]);
break;
}
}
}
//Debug.Log(curdisk == null);
if(curdisk == null) {
if (type == 1) {
curdisk = Instantiate(Resources.Load<GameObject>("Prefabs/BlueDisk"),startPos, Quaternion.identity);
}
else if(type == 2) {
curdisk = Instantiate(Resources.Load<GameObject>("Prefabs/RedDisk"),startPos, Quaternion.identity);
}
else if (type == 3){
curdisk = Instantiate(Resources.Load<GameObject>("Prefabs/GreenDisk"),startPos, Quaternion.identity);
}
curdisk.GetComponent<Renderer>().material.color = curdisk.GetComponent<Disk>().color;
}
used.Add(curdisk.GetComponent<Disk>());
curdisk.SetActive(true);
return curdisk;
}
public void FreeDisk() {
// Debug.Log("before");
// Debug.Log(used.Count);
// Debug.Log(free.Count);
for(int i=0; i<used.Count; i++) {
if (used[i].gameObject.transform.position.y <= -9f) {
Disk t = used[i];
free.Add(t);
used.Remove(used[i]);
}
}
// Debug.Log("after");
// Debug.Log(used.Count);
// Debug.Log(free.Count);
}
public void Reset() {
FreeDisk();
}
}
在这之前,我利用Unity内自带的3DGameObject对象生成了三个DIsk的预设,分别为BlueDisk、RedDisk和GreenDisk,它们各自有不同的分数,速度也不一样。
在生成了Disk之后,有如下用于控制飞碟飞行的DiskFly类,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 具体动作的实现,继承于SSAction动作基类
public class DiskFlyAction : SSAction {
public float gravity = -0.5f;
private Vector3 start_vector;
private Vector3 gravity_vector = Vector3.zero;
private Vector3 current_angle = Vector3.zero;
private float time;
private DiskFlyAction() { }
public static DiskFlyAction GetSSAction(int lor, float angle, float power) {
DiskFlyAction action = CreateInstance<DiskFlyAction>();
if (lor == -1) {
action.start_vector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
}
else {
action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
}
return action;
}
public override void Update() {
time += Time.fixedDeltaTime;
gravity_vector.y = gravity * time * 0.1f;
transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
transform.eulerAngles = current_angle;
if (this.transform.position.y < -10) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
public override void Start() { }
}
完成之后,还需要一个Manager去管理这个动作,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlyActionManager : SSActionManager {
public DiskFlyAction fly;
public Controller scene_controller;
protected void Start() {
scene_controller = (Controller)SSDirector.GetInstance().CurrentScenceController;
scene_controller.action_manager = this;
}
//飞碟飞行
public void DiskFly(GameObject disk, float angle, float power) {
int lor = 1;
if (disk.transform.position.x > 0) lor = -1;
fly = DiskFlyAction.GetSSAction(lor, angle, power);
this.RunAction(disk, fly, this);
}
}
lor的作用在于判断是左飞还是往右飞,方向会有不同的变化。
接下来的是管理爆炸特效相关的“爆炸工厂”,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoomFactory : MonoBehaviour
{
public Queue<GameObject> que = new Queue<GameObject>();
// Start is called before the first frame update
public void getExp(Vector3 pos){
GameObject effect = Instantiate(Resources.Load("Prefabs/Boom") as GameObject, pos, Quaternion.identity);
que.Enqueue(effect);
}
public void OutList(){
GameObject curEff = que.Dequeue();
Destroy(curEff);
}
public bool getEmpty(){
bool judge = false;
if (que.Count == 0) judge = true;
return judge;
}
}
剩下的一些类就和上一次作业中第一题的动作分离的类差不太多了,虽然有一些细微的改变,但总体上的逻辑并没有很大的变化,所以就略过不讲,具体的代码可去我上传的GIthub仓库内查看。