3d游戏设计读书笔记五
一、编写一个简单的鼠标打飞碟(Hit UFO)游戏
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
如果你的使用工厂有疑问,参考:弹药和敌人:减少,重用和再利用;参考:Unity对象池(Object Pooling)理解与简单应用 代码质量较低,比较凌乱
实现
- 脚本前览
- 飞碟预设
球+圆盘
使用标准材质 - Director
Director 使用单例模式,设置一个 Director类 的private static成员变量,并设置一个与之配套的 getInstance() 的成员函数。同时设置一个 currentController 的成员变量。
public class Director : System.Object
{
private static Director _instance;
public Controller currentController { get; set; }
public static Director getInstance()
{
if (_instance == null)
{
_instance = new Director();
}
return _instance;
}
}
- Controller
Controller 负责统筹各个组件之间的关系,所以在其中我们需要设置一下几个成员变量:_UFOfactory、_director、my_diskfactory ,并对其进行初始化、实例化。需要注意的是, _UFOfactory 采用的是单例模式。
public class Controller : MonoBehaviour
{
public UFOFactory _UFOfactory;
public Director _director;
private GameObject my_diskfactory;
void Awake()
{
Random.InitState((int)System.DateTime.Now.Ticks);
my_diskfactory = new GameObject("Disk_Factory");
my_diskfactory.AddComponent<UFOFactory>();
_director = Director.getInstance();
_UFOfactory = Singleton<UFOFactory>.Instance;
_director.currentController = this;
}
}
- UFOaction
UFOaction 定义了UFO的飞行动作,其中:
(1)成员变量:
public Director _director;
public GameObject player; //记录该动作所归属的对象
Vector3 start; //记录UFO飞行的初始位置
Vector3 end; //记录UFO飞行的结束位置
public int speed=3; //记录UFO飞行的速度
public bool running = true;//运行态标志位
(2)成员函数
a. Start()
设置UFO 开始位置 以及 结束位置 ,并调用 setColor函数 调整其颜色。
public void Start()
{
_director = Director.getInstance();
start = new Vector3(Random.Range(-20f,20f), Random.Range(-20f, 20f), 0);
if (start.x < 10 && start.x > -10)
start.x *= 4;
if (start.y < 10 && start.y > -10)
start.y *= 4;
end = new Vector3(-start.x, -start.y, 0);
player.transform.position = start;
setColor();
}
b. Update()
使用 MoveTowards函数 移动UFO。并且判定UFO是否移动到其结束位置,若是移动到其结束位置,证明其未被集中,则调用 not_hit 函数,回收UFO。
public void Update()
{
if (running)
{
player.transform.position = Vector3.MoveTowards(player.transform.position, end, speed * Time.deltaTime);
if (player.transform.position == end)
{
this._director.currentController._UFOfactory.not_hit(this.player);
}
}
}
c. setColor()
使用 Random 随机一个数,并根据这个数设置UFO的颜色。
public void setColor()
{
int color = Random.Range(1, 4);
switch (color)
{
case 1:
player.GetComponent<MeshRenderer>().material.color = Color.red;
foreach (Transform child in player.transform)
{
child.gameObject.GetComponent<MeshRenderer>().material.color = Color.red;
}
break;
case 2:
player.GetComponent<MeshRenderer>().material.color = Color.yellow;
foreach (Transform child in player.transform)
{
child.gameObject.GetComponent<MeshRenderer>().material.color = Color.yellow;
}
break;
case 3:
player.GetComponent<MeshRenderer>().material.color = Color.blue;
foreach (Transform child in player.transform)
{
child.gameObject.GetComponent<MeshRenderer>().material.color = Color.blue;
}
break;
default:
break;
}
}
- UFOfactory
UFOfactory整合了UFO的动作管理员。
(1)成员变量
public List<GameObject> used; //存储已经放飞的UFO
public List<GameObject> not_used; //存储飞回来了的UFO
public List<UFO_action> actions; //存储UFO动作,小型动作管理器
public int round = 0; //阶段
public int score = 0; //得分
(2)成员函数
a. Start()
为各个List指针创建List对象,并为各个List添加其对应成员。并为各个 UfO_action 绑定对应的 Player 。
private void Start()
{
used = new List<GameObject>();
not_used = new List<GameObject>();
actions = new List<UFO_action>();
for(int i = 0; i < 10; i++)
{
not_used.Add(Object.Instantiate(Resources.Load("Prefabs/UFO", typeof(GameObject)), new Vector3(0, -20, 0), Quaternion.identity, null) as GameObject);
actions.Add(ScriptableObject.CreateInstance<UFO_action>());
}
for(int i = 0; i < 10; i++)
{
actions[i].player = not_used[i];
}
}
b. Update()
调用各个 处于工作状态的UFO_action 的 Update函数 ,在 round < 10 的时候调用 get_ready函数 来更新轮次。并在 round == 11 的时候停止对 UFO_action 的操作。
private void Update()
{
if (round <= 10)
{
for (int i = 0; i < 10; i++)
{
actions[i].Update();
}
if (not_used.Count == 10)
{
round += 1;
if (round <= 10)
get_ready(round);
}
}
}
c. hitted(GameObject g)
判别被击中的UFO是什么色的,并对应给分。将击中的UFO从 used列表 移动到 not_used列表 ,并调整其为初始位置,再将其对应的 UFO_action 调为非running。
public void hitted(GameObject g)
{
if (g.gameObject.GetComponent<MeshRenderer>().material.color == Color.red)
score += 3;
else if (g.gameObject.GetComponent<MeshRenderer>().material.color == Color.yellow)
score += 2;
else if (g.gameObject.GetComponent<MeshRenderer>().material.color == Color.blue)
score += 1;
this.used.Remove(g);
g.transform.position = new Vector3(0, -20, 0);
for(int i = 0; i < 10; i++)
{
if (actions[i].player == g)
actions[i].running = false;
}
this.not_used.Add(g);
}
d. not_hit(GameObject g)
not_hit函数 与 hitted函数相似,但减去了加分的环节。
public void not_hit(GameObject g)
{
this.used.Remove(g);
g.transform.position = new Vector3(0, -20, 0);
for (int i = 0; i < 10; i++)
{
if (actions[i].player == g)
actions[i].running = false;
}
this.not_used.Add(g);
}
e. get_ready(int round)
用于做UFO起飞之前的准备工作,它将 not_used列表中所有的UFO移入 used列表 并按轮次调整其飞行速度,并调用 UFO_action 的 Start函数 做初始化,并将其设为 running态 。
public void get_ready(int round)
{
for(int i = 0; i < 10; i++)
{
used.Add(not_used[0]);
not_used.Remove(not_used[0]);
actions[i].speed = round + 3;
actions[i].Start();
actions[i].running = true;
}
}
- Hit_UFO
使用 Ray 来定位鼠标的位置以及鼠标点击到的游戏物体,当点击到物体的时候调用 UFOfactory 的 hitted函数。
public class Hit_UFO : MonoBehaviour
{
public GameObject cam;
public Director director;
private void Start()
{
director = Director.getInstance();
}
// Update is called once per frame
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
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 hit
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
director.currentController._UFOfactory.hitted(hit.transform.gameObject);
}
}
}
}
- My_GUI
创建 Score 和 Round 的label。并在轮次到达11的时候,停止显示 Score 和 Round ,而在屏幕中间显示 Final Score 。
private void OnGUI()
{
int my_round = _director.currentController._UFOfactory.round;
if (my_round == 11)
{
GUIStyle ending = new GUIStyle();
ending.normal.background = null;
ending.normal.background = null;
ending.normal.textColor = new Color(0, 0, 0);
ending.fontSize = 80;
string ending_score = "Final Score: " + _director.currentController._UFOfactory.score.ToString();
GUI.Label(new Rect(0.13f * Screen.width, 0.4f * Screen.height, 300, 300), ending_score, ending);
}
else
{
string round = my_round.ToString();
round = "Round: " + round;
GUIStyle bb = new GUIStyle(); //创建GUI的格式
bb.normal.background = null;
bb.normal.textColor = new Color(0, 0, 0);
bb.fontSize = 25;
GUI.Label(new Rect(0.8f * Screen.width, 240, 150, 35), round, bb);
string score = _director.currentController._UFOfactory.score.ToString();
score = "Score:" + score;
GUI.Label(new Rect(0.8f * Screen.width, 270, 150, 35), score, bb);
}
}
- Singleton
使用 FindObjectOfType 来寻找一个类型的单例,并返回这个单例。
public class Singleton<T> : MonoBehaviour where T: MonoBehaviour
{
protected static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null)
{
Debug.LogError("No instance of " + typeof(T));
}
}
return instance;
}
}
}
- 游戏截图
二、编写一个简单的自定义 Component (选做)
用自定义组件定义几种飞碟,做成预制 实现自定义组件,编辑并赋予飞碟一些属性
例如编写旋转脚本组件,让UFO使用不同的转动轴进行转动。通过,使用不同的速度与轴方向,就可以让我们的UFO按照不同的旋转轴旋转。将设计好的不同的组件挂载到不同的UFO上,就做好了几种飞碟的预制。
public class around : MonoBehaviour
{
public float speed;
public float yangle, zangle;
void Update()
{
Vector3 axis = new Vector3(0, yangle, zangle);
this.transform.RotateAround(new Vector3(0, 0, 0), axis, speed * Time.deltaTime);
this.transform.Rotate(Vector3.up * 100 * Time.deltaTime);
}
}