本次作业参考了学长的博客:博客地址
编写一个简单的鼠标打飞碟(Hit UFO)游戏
- 游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
- 游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
游戏的UML图:
项目代码结构:
飞碟预制体的创建:
比较简单,一个带厚度的圆盘套上一个球体或胶囊体就可以了。
由于需要不同的颜色,所以还要再加上颜色的material
比较关键的部分代码:
UFOFactory:
包含 GetDisk 函数产生飞碟和飞碟回收方法 Free
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MyGame;
public class DiskFactory : MonoBehaviour
{
public GameObject diskPrefab;
public List<DiskData> used = new List<DiskData>();
public List<DiskData> free = new List<DiskData>();
private Director director = Director.getInstance();
private void Awake()
{
diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
diskPrefab.SetActive(false);
}
public GameObject getDisk(int round,bool isRigidbody)
{
GameObject disk = null;
if (free.Count > 0)
{
disk = free[0].gameObject;
free.Remove(free[0]);
}
else
{
disk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
disk.AddComponent<DiskData>();
}
if (isRigidbody && disk.GetComponent<Rigidbody>() == null) /* 添加刚体性质 */
{
disk.AddComponent<Rigidbody>();
}
int start;
switch (round)
{
case 0: start = 0; break;
case 1: start = 100; break;
default: start = 200; break;
}
int selectColor = Random.Range(start, round * 499);
round = selectColor / 250;
DiskData diskData = disk.GetComponent<DiskData>();
Renderer renderer = disk.GetComponent<Renderer>();
Renderer childRenderer = disk.transform.GetChild(0).GetComponent<Renderer>();
float ranX = Random.Range(-1, 1) < 0 ? -1.2f : 1.2f;
Vector3 direction = new Vector3(ranX, 1, 0);
switch (round)
{
case 0:
diskData.setDiskData(new Vector3(1.35f, 1.35f, 1.35f), Color.white, 4.0f, direction);
renderer.material.color = Color.white;
childRenderer.material.color = Color.white;
break;
case 1:
diskData.setDiskData(new Vector3(1f, 1f, 1f), Color.gray, 6.0f, direction);
renderer.material.color = Color.yellow;
childRenderer.material.color = Color.yellow;
break;
case 2:
diskData.setDiskData(new Vector3(0.7f, 0.7f, 0.7f), Color.black, 8.0f, direction);
renderer.material.color = Color.blue;
childRenderer.material.color = Color.blue;
break;
}
used.Add(diskData);
diskData.name = diskData.GetInstanceID().ToString();
disk.transform.localScale = diskData.getSize();
return disk;
}
public void freeDisk(GameObject disk)
{
DiskData temp = null;
foreach (DiskData i in used)
{
if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
{
temp = i;
}
}
if (temp != null)
{
temp.gameObject.SetActive(false);
free.Add(temp);
used.Remove(temp);
}
}
}
DiskData(飞碟数据):
包括初始化颜色、位置、方向、速度等
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskData : MonoBehaviour
{
private Vector3 size;
private Color color;
private float speed;
private Vector3 direction;
public DiskData() { }
public Vector3 getSize()
{
return size;
}
public float getSpeed()
{
return speed;
}
public Vector3 getDirection()
{
return direction;
}
public Color getColor()
{
return color;
}
public void setDiskData(Vector3 size, Color color, float speed, Vector3 direction)
{
this.size = size;
this.color = color;
this.speed = speed;
this.direction = direction;
}
}
ScoreRecorder(分数记录):
记录游戏分数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MyGame
{
public class ScoreRecorder : MonoBehaviour
{
public int score;
private Dictionary<Color, int> scoreTable = new Dictionary<Color, int>();
void Start() /* 初始化各种颜色的分数值 */
{
score = 0;
scoreTable.Add(Color.white, 1);
scoreTable.Add(Color.gray, 2);
scoreTable.Add(Color.black, 4);
}
public void reset()
{
score = 0;
}
public void record(GameObject disk) /* 击中时触发该函数,加分 */
{
score += scoreTable[disk.GetComponent<DiskData>().getColor()];
}
}
}
控制飞碟运动的代码(CC FlyAction):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 继承自SSAction类
public class CCFlyAction : SSAction
{
float acceleration; // 加速度
float horizontalSpeed; // 水平速度
Vector3 direction; // 飞行方向
float time; // 经过的时间
bool flag = false; // 记录游戏是否经过暂停状态
Vector3 temp; // 记录游戏暂停时刚体游戏对象的速度矢量
Rigidbody rigidbody; // 物理运动,添加刚体
// 获取CCFlyAction实例的静态方法
public static CCFlyAction getCCFlyAction()
{
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
return action;
}
// 在动作开始时调用的方法
public override void Start()
{
enable = true; // 启用动作
acceleration = 9.8f; // 设置加速度
time = 0; // 初始化时间
horizontalSpeed = gameObject.GetComponent<DiskData>().getSpeed(); // 获取飞碟的水平速度
direction = gameObject.GetComponent<DiskData>().getDirection(); // 获取飞碟的飞行方向
rigidbody = gameObject.GetComponent<Rigidbody>(); // 获取飞碟的刚体组件
// 如果飞碟有刚体组件,则设置刚体的速度属性
if (rigidbody)
{
rigidbody.velocity = horizontalSpeed * direction;
temp = rigidbody.velocity; /* temp的初始化 */
}
}
// 在每一帧更新时调用的方法
public override void Update()
{
if (gameObject.activeSelf)
{
time += Time.deltaTime;
transform.Translate(Vector3.down * acceleration * time * Time.deltaTime); // 下落运动
transform.Translate(direction * horizontalSpeed * Time.deltaTime); // 水平运动
// 如果飞碟位置低于一定高度,则销毁飞碟并触发动作事件
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
// 在固定时间间隔更新时调用的方法
public override void FixedUpdate()
{
if (gameObject.activeSelf)
{
// 如果飞碟位置低于一定高度,则销毁飞碟并触发动作事件
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
// 停止刚体动作的方法
public override void rigidbodyStopAction()
{
if (rigidbody)
{
if (!flag)
{
temp = rigidbody.velocity;
flag = true;
}
rigidbody.velocity = Vector3.zero;
rigidbody.useGravity = false;
}
}
// 启动刚体动作的方法
public override void rigidbodyStartAction()
{
if (rigidbody)
{
rigidbody.useGravity = true;
if (flag)
{
rigidbody.velocity = temp;
flag = false;
}
}
}
}
FirstSceneController:包含游戏运行逻辑的关键代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MyGame;
namespace MyGame
{
// 实现ISceneControl和IUserAction接口
public class FirstSceneControl : MonoBehaviour, ISceneControl, IUserAction
{
public ActionManagerAdapter actionManager { set; get; } // 动作管理适配器
public ScoreRecorder scoreRecorder { set; get; } // 成绩记录器
public Queue<GameObject> diskQueue = new Queue<GameObject>(); // 飞碟对象队列
private int diskNumber = 0;
private int currentRound = -1;
private float time = 0;
private GameState gameState = GameState.START; // 游戏状态
UserGUI userGUI; // 用户界面
private bool isPhysical = false; /* 决定是否进行物理运动 */
void Awake()
{
Director director = Director.getInstance();
director.current = this;
diskNumber = 10;
// 添加成绩记录器和飞碟工厂组件
this.gameObject.AddComponent<ScoreRecorder>();
this.gameObject.AddComponent<DiskFactory>();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
// 加载游戏资源
director.current.loadResources();
}
public void loadResources()
{
// 游戏资源加载逻辑
}
private void Update()
{
if (actionManager == null)
{
return;
}
// 判断游戏状态
if (actionManager.getDiskNumber() == 0 && gameState == GameState.RUNNING)
{
gameState = GameState.ROUND_FINISH;
if (currentRound == 2) // 如果round = 2,表示已经完成了三局游戏,游戏结束
{
gameState = GameState.FUNISH;
return;
}
}
// 如果飞碟数为空,并且是round开始状态,则准备开始下一round游戏
if (actionManager.getDiskNumber() == 0 && gameState == GameState.ROUND_START)
{
currentRound++;
nextRound();
actionManager.setDiskNumber(10);
gameState = GameState.RUNNING;
}
// 控制抛飞碟的时间间隔
if (time > 1 && gameState != GameState.PAUSE)
{
throwDisk();
time = 0;
}
else
{
time += Time.deltaTime;
}
}
private void nextRound()
{
DiskFactory diskFactory = Singleton<DiskFactory>.Instance;
for (int i = 0; i < diskNumber; i++)
{
// 根据当前round数和是否使用物理引擎来取出飞碟,并加入本局抛的飞碟队列
diskQueue.Enqueue(diskFactory.getDisk(currentRound, isPhysical));
}
actionManager.startThrow(diskQueue); // 开始抛这个队列的飞碟
}
void throwDisk()
{
if (diskQueue.Count != 0)
{
GameObject disk = diskQueue.Dequeue(); // 从队列取出一个飞碟
Vector3 pos = new Vector3(-disk.GetComponent<DiskData>().getDirection().x * 10, Random.Range(0f, 4f), 0);
disk.transform.position = pos;
disk.SetActive(true);
}
}
public int getScore()
{
return scoreRecorder.score;
}
public GameState getGameState()
{
return gameState;
}
public void setGameState(GameState gameState)
{
this.gameState = gameState;
}
public bool getActionMode()
{
return isPhysical;
}
public void setActionMode(bool mode)
{
this.isPhysical = mode;
if (isPhysical)
{
this.gameObject.AddComponent<ModifiedActionManager>();
}
else
{
this.gameObject.AddComponent<CCActionManager>();
}
}
public void hit(Vector3 pos)
{
RaycastHit[] hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(pos));
for (int i = 0; i < hits.Length; i++)
{
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<DiskData>() != null)
{
scoreRecorder.record(hit.collider.gameObject);
hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
}
}
}
}
}
游戏视频:
打飞碟
代码地址:代码地址