2019-10-09

与游戏世界交互

一.游戏内容要求:

  • 游戏有 n 个 round,每个 round 都包括10 次 trial;
  • 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
  • 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  • 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

    二.游戏实现

    游戏架构

  • Director:游戏的导演
  • FirstSceneController:总控制器
  • ActionManager:动作管理者,负责管理动作的产生
  • UserGUI:负责渲染整个页面的布局,主要是功能按钮的实现
  • ScoreRecorder:负责分数的计算,根据飞碟的大小,速度,颜色,计算打中的得分
  • DiskDate:挂在飞碟预制上的组件,规定了飞碟的属性
  • DiskFactory:负责生产不同大小,速度,颜色的飞碟

    UML

    预制和挂载信息



    预制Disk,要把DiskData挂载在上面

    代码

    场景单实例要求场景中至少有一个 T 类型的 Mono 子类Singleton模板代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class Singleton<T> where T : MonoBehaviour
    {
    private static T instance;

    public static T Instance
    {
    get
    {
    if (instance == null)
    {
    instance = (T)Object.FindObjectOfType(typeof(T));
    if (instance == null)
    {
    Debug.LogError("Can't find instance of " + typeof(T));
    }
    }
    return instance;
    }
    }
    }

DiskData记录每个飞碟自身的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskData : MonoBehaviour
{

public Vector3 direction; // 飞碟的初始位置
public Vector3 scale; // 飞碟的相对大小
public Color color; // 飞碟的颜色
public float speed; // 飞碟的飞行速度

}

飞碟工厂DiskFactory
有两个链表,分别存储使用和闲置的飞碟,可以避免飞碟的销毁和过多加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskFactory : MonoBehaviour
{

public GameObject diskPrefab = null; // 飞碟预制,是工厂返回的对象
private List<DiskData> used = new List<DiskData>(); // 正在使用的飞碟
private List<DiskData> free = new List<DiskData>(); // 未在使用的飞碟

// 获取飞碟
public GameObject GetDisk(int round)
{

int choice = 0; // 决定飞碟种类的变量
diskPrefab = null;

// 若存在正在使用的飞碟,则从 used 列表中获取;若不存在,则新建飞碟
if (free.Count > 0)
{
diskPrefab = free[0].gameObject;
free.Remove(free[0]);
}
else
{
diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
diskPrefab.SetActive(false);
}

// 根据回合随机选出飞碟种类
switch (round)
{
case 1:
choice = Random.Range(0, 2);
break;
case 2:
choice = Random.Range(0, 4);
break;
case 3:
choice = Random.Range(0, 8);
break;
}
// 设置不同种类飞碟的属性
if (choice < 2)
{
diskPrefab.GetComponent<DiskData>().color = Color.yellow;
diskPrefab.GetComponent<DiskData>().speed = 7.0f + round;
diskPrefab.GetComponent<DiskData>().scale = new Vector3(4, 0.2f, 4);
}
else if (choice > 2 && choice < 4)
{
diskPrefab.GetComponent<DiskData>().color = Color.red;
diskPrefab.GetComponent<DiskData>().speed = 9.0f + round;
diskPrefab.GetComponent<DiskData>().scale = new Vector3(2, 0.1f, 2);
}
else if (choice > 4)
{
diskPrefab.GetComponent<DiskData>().color = Color.white;
diskPrefab.GetComponent<DiskData>().speed = 11.0f + round;
diskPrefab.GetComponent<DiskData>().scale = new Vector3(1, 0.05f, 1);
}
float RanX = Random.Range(-1f, 1f) < 0 ? -1 : 1;
diskPrefab.GetComponent<DiskData>().direction = new Vector3(RanX, 1, 0);
diskPrefab.transform.localScale = diskPrefab.GetComponent<DiskData>().scale;
diskPrefab.GetComponent<Renderer>().material.color = diskPrefab.GetComponent<DiskData>().color;

// 将飞碟添加到 used 列表并返回
used.Add(diskPrefab.GetComponent<DiskData>());
diskPrefab.name = diskPrefab.GetInstanceID().ToString();
return diskPrefab;
}

// 回收飞碟
public void FreeDisk(GameObject disk)
{
foreach (DiskData data in used)
{
if (data.gameObject.GetInstanceID() == disk.GetInstanceID())
{
data.gameObject.SetActive(false);
free.Add(data);
used.Remove(data);
break;
}
}
}

}

记分员ScoreRecorder根据飞碟大小和颜色计分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour
{

public int score; // 分数

private Dictionary<Color, int> scoreTable = new Dictionary<Color, int>(); // 用字典来记录不同飞碟对应的分数

void Start()
{
score = 0;
scoreTable.Add(Color.yellow, 1);
scoreTable.Add(Color.red, 2);
scoreTable.Add(Color.white, 4);
}

// 记录分数的增加
public void Record(GameObject disk)
{
score += scoreTable[disk.GetComponent<DiskData>().color];
}

public void Reset()
{
score = 0;
}
}

FirstSceneController场记每秒从飞碟工厂得到一只飞碟,然后让动作管理者发射,让记分员计分,每一帧检查遗留在场景的飞碟,再让飞碟工厂回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstSceneController : MonoBehaviour, SceneController, UserAction
{

public FlyActionManager actionManager;
public DiskFactory diskFactory;
public UserGUI userGUI;
public ScoreRecorder scoreRecorder;

private Queue<GameObject> diskQueue = new Queue<GameObject>(); // 游戏中的飞碟
private List<GameObject> diskNotshot = new List<GameObject>(); // 未被击中的飞碟
private int diskNumber; // 每个回合的飞碟数量
private int currentRound; //当前回合
private int roundNumber; // 回合总数
private float frequency; // 飞碟出现的频率
private GameState gameState = GameState.PAUSE; // 游戏状态

// 场景初始化
void Start()
{
Director director = Director.getInstance();
director.currentSceneController = this;
diskNumber = 10;
currentRound = 1;
roundNumber = 3;
frequency = 2f;
scoreRecorder = Singleton<ScoreRecorder>.Instance;
diskFactory = Singleton<DiskFactory>.Instance;
actionManager = gameObject.AddComponent<FlyActionManager>();
userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
director.setFPS(60);
}

// 将飞碟加载到飞碟队列中
public void LoadResources()
{
diskQueue.Enqueue(diskFactory.GetDisk(currentRound));
}

private void Update()
{
// 判断回合结束
if (diskNumber == 0 && currentRound < roundNumber)
{
CancelInvoke("LoadResources");
}
// 判断游戏结束
if (diskNumber == 0 && currentRound == roundNumber)
{
gameState = GameState.FINISH;
}
else if (diskNumber == 0 && diskNotshot.Count == 0)
{
diskNumber = 10;
frequency -= 0.4f;
gameState = GameState.ROUND_FINISH;
}
// 游戏开始时加载飞碟资源
if (gameState == GameState.START)
{
InvokeRepeating("LoadResources", 1f, frequency);
gameState = GameState.ROUND_START;
}
// 游戏暂停或结束时取消加载飞碟资源
if (gameState == GameState.FINISH || gameState == GameState.ROUND_FINISH || gameState == GameState.PAUSE)
{
CancelInvoke("LoadResources");
}
// 回合开始,抛出飞碟
if (gameState == GameState.ROUND_START)
ThrowDisk();
}

// 抛出飞碟
public void ThrowDisk()
{
float position_x = 16;
if (diskQueue.Count != 0)
{
diskNumber--;
GameObject disk = diskQueue.Dequeue(); // 取出飞碟
diskNotshot.Add(disk);
disk.SetActive(true);

// 设置飞碟的随机位置
float ran_y = Random.Range(1f, 4f);
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk.GetComponent<DiskData>().direction = new Vector3(ran_x, ran_y, 0);
Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
disk.transform.position = position;

// 设置飞碟初始所受的力和角度
float power = Random.Range(10f, 15f);
float angle = Random.Range(15f, 28f);
actionManager.Fly(disk, angle, power);
}

for (int i = 0; i < diskNotshot.Count; i++)
{
GameObject temp = diskNotshot[i];
//飞碟飞出摄像机视野也没被打中
if (temp.transform.position.y < -20 && temp.gameObject.activeSelf == true)
{
diskFactory.FreeDisk(diskNotshot[i]);
diskNotshot.Remove(diskNotshot[i]);
}
}
}

// 击中飞碟
public void Hit(Vector3 position)
{
Ray ray = Camera.main.ScreenPointToRay(position);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray);

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, -20, 0); // 将飞碟移到底部
}
}
}

// 获取分数
public int GetScore()
{
return scoreRecorder.score;
}

// 重新开始
public void Restart()
{
gameState = GameState.START;
scoreRecorder.Reset();
currentRound = 1;
diskNumber = 10;
frequency = 2f;
}

// 暂停游戏
public void Pause()
{
gameState = GameState.PAUSE;
}

// 开始游戏
public void Begin()
{
gameState = GameState.START;
}

// 开始下一回合
public void NextRound()
{
gameState = GameState.START;
currentRound++;
}

// 获取游戏状态
public GameState getGameState()
{
return gameState;
}

// 设置游戏状态
public void setGameState(GameState gs)
{
gameState = gs;
}

// 获取回合
public int GetRound()
{
return this.currentRound;
}

// 暂停几秒后回收飞碟
IEnumerator WaitingParticle(float wait_time, RaycastHit hit, DiskFactory disk_factory, GameObject obj)
{
yield return new WaitForSeconds(wait_time);
// 等待之后执行的动作
hit.collider.gameObject.transform.position = new Vector3(0, -20, 0);
disk_factory.FreeDisk(obj);
}
}

导演类Director

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 导演类采用单例模式
public class Director : System.Object
{

private static Director _instance; // 导演的实例
public SceneController currentSceneController { get; set; } // 当前的场记

private Director() { }

// 获取导演实例
public static Director getInstance()
{
if (_instance == null)
{
_instance = new Director();
}
return _instance;
}

public int getFPS()
{
return Application.targetFrameRate;
}

public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}

}

代码并不完整

三.演示视频

https://www.bilibili.com/video/av70663160