Homework05
项目地址
演示视频
使用说明:创建一个空GameObject将FirstSceneController.cs
挂载到新建游戏对象即可
1. 编写一个简单的鼠标打飞碟(Hit UFO)游戏
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制
- 每个 trial 的飞碟有随机性,总体难度随 round 上升
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定
游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 尽可能使用前面 MVC 结构实现人机交互与游戏模型分离
设计实现如下:
1. 制作UFO prefabs如下
2. BaseCode.cs
定义UFO类,并定义SSDriector
在
UFO
命名空间下定义,方便其他class调用
namespace UFO {
public class Disk : MonoBehaviour{
public float size; //大小
public Color color; //颜色
public float speed; //速度
public Vector3 position; //初始位置
public Vector3 direction; //运动方向
}
public class Director : System.Object {
private static Director _instance;
public ISceneController currentSceneController { get; set; }
public static Director getInstance() {
if (_instance == null) {
_instance = new Director();
}
return _instance;
}
}
public interface ISceneController { //加载场景
void Init ();
SceneController getSceneController();
//initQueue(mode)
}
}
3. 实现DiskFactory.cs
以工厂模式管理飞碟的生产与回收
- UFO根据回合数设定不同速度和大小
- UFO设定随机初始位置和初始速度方向
- UFO6种颜色随机(白、黄、红、蓝、绿、黑)
public class DiskFactory : MonoBehaviour
{
private List<Disk> toDelete = new List<Disk>();
private List<Disk> toUse = new List<Disk>();
public Color[] colors = {Color.white, Color.yellow, Color.red, Color.blue, Color.green, Color.black};//可选颜色
public GameObject GetDisk(int round) { //根据回合数对飞碟设置属性并返回
GameObject newDisk = null;
if (toUse.Count > 0) {
newDisk = toUse[0].gameObject;
toUse.Remove(toUse[0]);
} else {
newDisk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"), Vector3.zero, Quaternion.identity);
newDisk.AddComponent<Disk>();
}
// 飞碟的速度为 round * 7
newDisk.GetComponent<Disk>().speed = 7.0f * round;
// 飞碟随 round 越来越小
newDisk.GetComponent<Disk>().size = (1 - round*0.1f);
// 飞碟颜色随机
int color = UnityEngine.Random.Range(0, 6);//共有六种颜色
newDisk.GetComponent<Disk>().color = colors[color];
// 飞碟的发射方向
float RanX = UnityEngine.Random.Range(-1, 3) < 1 ? -1 : 1;//-1,0则为负方向,1,2则为正方向
newDisk.GetComponent<Disk>().direction = new Vector3(-RanX, UnityEngine.Random.Range(-2f, 2f), 0);
// 飞碟的初始位置
newDisk.GetComponent<Disk>().position = new Vector3(RanX*13, UnityEngine.Random.Range(-2f, 2f), UnityEngine.Random.Range(-1f, 1f));
toDelete.Add(newDisk.GetComponent<Disk>());
newDisk.SetActive(false);
newDisk.name = newDisk.GetInstanceID().ToString();
return newDisk;
}
public void FreeDisk(GameObject disk){
Disk cycledDisk = null;
foreach (Disk toCycle in toDelete){
if (disk.GetInstanceID() == toCycle.gameObject.GetInstanceID()){
cycledDisk = toCycle;
}
}
if (cycledDisk != null){
cycledDisk.gameObject.SetActive(false);
toUse.Add(cycledDisk);
toDelete.Remove(cycledDisk);
}
}
}
3. 设计ActionManager.cs
管理场景的动作
- 管理飞碟的运动以及与用户交互
- 点击飞碟,分数加一,飞碟消失
public class ActionManager : MonoBehaviour
{
public Vector3 direction;//运动方向
public float speed;//初速度
public GameObject cam;
public void diskFly(Vector3 direction,float speed) {//赋予飞碟初速度和方向
this.direction = direction;
this.speed = speed;
}
// Start is called before the first frame update
void Start(){
cam = GameObject.Find("Main Camera");
}
// Update is called once per frame
void Update(){
this.gameObject.transform.position += speed * direction * Time.deltaTime;
if (Input.GetButtonDown("Fire1")){ //光标拾取物体的结果,即鼠标集中飞碟的结果
Debug.Log("Fired Pressed");
Debug.Log(Input.mousePosition);
Vector3 mp = Input.mousePosition; //get Screen Position
//create ray, origin is camera, and direction to mousepoint
Camera ca;
if (cam != null) ca = cam.GetComponent<Camera>();
else ca = Camera.main;
Ray ray = ca.ScreenPointToRay(Input.mousePosition);
//Return the ray's hits
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit hit in hits){
print(hit.transform.gameObject.name);
if (hit.collider.gameObject.tag.Contains("Finish")){ //plane tag
Debug.Log("hit " + hit.collider.gameObject.name + "!");
}
Singleton<DiskFactory>.Instance.FreeDisk(hit.transform.gameObject);//飞碟消失
//this.gameObject.GetComponent<UserGUI>().score ++;
//SceneController.getInstance().addScore();
Director.getInstance ().currentSceneController.getSceneController().addScore();//分数加一
}
}
}
}
4. 设计SceneController.cs
场景控制器
- 显示信息:回合数,总飞碟数,所得分数
public class SceneController
{
public int round ; //回合数
public int total ; //总飞碟数
public int score ;//得到的分数
private static SceneController sceneCtrl;
public SceneController() {//用于SceneController归0
score = 0;
total = 0;
score = 0;
}
public static SceneController getInstance(){
if (sceneCtrl == null){
sceneCtrl = new SceneController();
}
return sceneCtrl;
}
public void addRound(){
round++;
}
public void addTotal(){
total++;
}
public void addScore(){
score++;
}
public int getRound(){
return round;
}
public int getTotal() {
return total;
}
public int getScore() {
return score;
}
}
5. 设计FirstSceneController.cs
第一场景控制器
- 控制已发射的UFO个数,10/round
- 控制飞碟发射时间间隔(与回合数成反比)
- 负责控制游戏场景的加载和切换
public class FirstSceneController : MonoBehaviour, ISceneController{
public int diskFlyTimes; //已经发射的飞碟个数,每回合10个,最多30个
public float time;// 时间,用于控制飞碟发射间隔
public int round; // 当前回合数
// 飞碟队列
public Queue<GameObject> diskQueue = new Queue<GameObject>();//飞碟队列
public SceneController sceneCtrl;
// Start is called before the first frame update
void Start() {
// 当前场景控制器
Director.getInstance().currentSceneController = this;
this.gameObject.AddComponent<DiskFactory>();
this.gameObject.AddComponent<UserGUI>();
Director.getInstance().currentSceneController.Init();//初始化FirstSceneController相关数据
}
// 初始化每个回合的飞碟队列,每个回合的飞碟属性不同
void initQueue() {
for(int i = 0; i < 10; i++)
diskQueue.Enqueue(Singleton<DiskFactory>.Instance.GetDisk(round));
}
// Update is called once per frame
void Update() {
// round = sceneCtrl.getRound();
time += Time.deltaTime;
// 发射飞碟的间隔回合数成反比
if(time >= 2.0f-0.3*round) {
if(diskFlyTimes >= 30) { //游戏结束
Reset();
} else if ((diskFlyTimes % 10) == 0 ) { //更新回合(此步骤必须在发射飞碟前面)
round++; //在initQueue()之前
sceneCtrl.addRound(); //回合数增加
initQueue(); //初始化新的飞盘队列
}
if (diskFlyTimes < 30) {
time = 0;
ThrowDisk(); //发射飞盘
diskFlyTimes++; //飞盘数增加
sceneCtrl.addTotal(); //综费盘数增加
}
}
}
public void ThrowDisk() {
if(diskQueue.Count > 0) {
GameObject disk = diskQueue.Dequeue();
disk.GetComponent<Renderer>().material.color = disk.GetComponent<Disk>().color;
disk.transform.position = disk.GetComponent<Disk>().position;
disk.transform.localScale = disk.GetComponent<Disk>().size * disk.transform.localScale;
disk.SetActive(true);
disk.AddComponent<ActionManager>();
disk.GetComponent<ActionManager>().diskFly(disk.GetComponent<Disk>().direction, disk.GetComponent<Disk>().speed);
}
}
public void Init() {
sceneCtrl = new SceneController(); //SceneController元素归0
diskFlyTimes = 0;
time = 0;
round = 0;
diskQueue.Clear(); //清空飞盘队列
}
public SceneController getSceneController() { //返回SceneController
return sceneCtrl;
}
void Reset() { //游戏重置
this.gameObject.GetComponent<UserGUI>().reset = 1;
}
}
7. 设计UserGUI.cs
负责显示信息
- 回合数Round,未击中飞碟数Miss,分数Score
- 重新开始按钮
- 游戏界面
public class UserGUI : MonoBehaviour
{
public int reset;
GUIStyle style;
GUIStyle buttonStyle;
// Start is called before the first frame update
void Start()
{
reset = 0;
style = new GUIStyle();
style.fontSize = 30;
style.normal.textColor = Color.green;//
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 30;
buttonStyle.normal.textColor = Color.green;//
}
// Update is called once per frame
void Update()
{
}
private void OnGUI() {
if(reset == 1) {
if(GUI.Button(new Rect(380, 250, 100, 80), "Reset", buttonStyle)) {
Director.getInstance().currentSceneController.Init();
reset = 0;
}
}
int round = Director.getInstance().currentSceneController.getSceneController().getRound();
int total = Director.getInstance().currentSceneController.getSceneController().getTotal();
int score = Director.getInstance().currentSceneController.getSceneController().getScore();
int miss = total - score;//未击中的飞碟数
string text = "Round: " + round.ToString() + "\nMiss: " + miss.ToString() + "\nScores: " + score.ToString();
GUI.Label(new Rect(10, 10, Screen.width, 50),text,style);
}
}
2. 编写一个简单的自定义 Component (选做)
用自定义组件定义几种飞碟,做成预制
- 参考官方脚本手册
- 实现自定义组件,编辑并赋予飞碟一些属性
实现旋转UFO、透明UFO
public class Rotate: MonoBehaviour
{
public float speed;
public float xangle, yangle, zangle;
void Update()
{
Vector3 axis = new Vector3(xangle, yangle, zangle);
this.transform.RotateAround(new Vector3(0, 0, 0), axis, speed * Time.deltaTime);
this.transform.Rotate(Vector3.up * 100 * Time.deltaTime);
}
}