Unity游戏制作(四)
实验内容
1 .编程实践:编写一个简单的鼠标打飞碟(Hit UFO)游戏
- 游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升;
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
- 游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
实验环境
- Unity 2020.3.18
- Windows
技术日记
一、游戏交互与创新
1. Unity 输入处理
1.1 JoyStick – 虚拟轴与按键
public class Joystick : MonoBehaviour {
public float speedX = 10.0F;
public float speedY = 10.0F;
// Update is called once per frame
void Update () {
float translationY = Input.GetAxis("Vertical") * speedY;
float translationX = Input.GetAxis("Horizontal") * speedX;
translationY *= Time.deltaTime;
translationX *= Time.deltaTime;
//transform.Translate(0, translationY, 0);
//transform.Translate(translationX, 0, 0);
transform.Translate(translationX, translationY, 0);
if (Input.GetButtonDown ("Fire1")) {
Debug.Log ("Fired Pressed");
}
}
}
1.2 光标拾取物体程序
public class PickupObject : MonoBehaviour {
public GameObject cam;
// Update is called once per frame
void Update () {
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 hit
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
print (hit.transform.gameObject.name);
if (hit.collider.gameObject.tag.Contains("Finish")) { //plane tag
Debug.Log ("hit " + hit.collider.gameObject.name +"!" );
}
Destroy (hit.transform.gameObject);
}
}
}
}
程序要点:
- mousePosition 是 Vector3 ,请不要修改 z 坐标
- 获取摄像机的 Camera 部件,构建 Ray
- Camera 部件支持正确生成世界坐标的射线
- Raycast 函数使用了变参(值参与变参),为什么 hit 必须用变参?
- 为了优化性能,Raycast 支持在特定层扫描对象
1.3 光标拾取多个物体程序
public class PickupMultiObjects : MonoBehaviour {
public GameObject cam;
// Update is called once per frame
void Update () {
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 +"!" );
}
Destroy (hit.transform.gameObject);
}
}
}
}
2. 面向对象的游戏编程
2.1 场景单实例
运用模板,可以为每个 MonoBehaviour子类 创建一个对象的实例。Singleten<T>
代码如下所示:
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 ("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
}
}
return instance;
}
}
}
场景单实例的使用很简单,你仅需要将 MonoBehaviour 子类对象挂载任何一个游戏对象上即可。
然后在任意位置使用代码 Singleton<YourMonoType>.Instance
获得该对象。
二、实验部分
1.项目配置过程
新建3d-unity的文件,然后直接把gitee上Assets
文件夹替换新项目的Assets
文件夹,直接点击运行即可开始游戏。
2. 实现思路及核心算法
在打飞碟中,共设置了十二个函数。
1)Singleton.cs
跟如上的模版一样。
2) SSDirector.cs
,SSAction.cs
,SSActionManager.cs
,Interfaces.cs
与之前的作业相差无几,不再解释。
3)UserGUI.cs
实现了初始界面和记分板。
4)FirstController.cs
- Start()函数,完成了DiskFactory和scoreRecord单例模式和创建。
void Start () {
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
disk_factory = Singleton<DiskFactory>.Instance;
score_recorder = Singleton<ScoreRecorder>.Instance;
action_manager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
}
- Update()函数是程序运行的主函数,更新了打飞碟游戏的以下参数:回合、尝试的次数、飞碟的个数。
void Update () {
if(running) {
count++;
if (Input.GetButtonDown("Fire1")) {
Vector3 pos = Input.mousePosition;
Hit(pos);
}
switch (round) {
case 1: {
if (count >= 600) {
count = 0;
SendDisk(trial % 2 + 1);
if(Random.Range(1,3) == 1)
SendDisk(1);
if (trial >= 10) {
round += 1;
trial = 0;
}
trial += 1;
}
break;
}
case 2: {
if (count >= 600) {
if (trial == 11) {
running = false;
}
count = 0;
if(trial <= 9){
SendDisk(Random.Range(1,3));
}
if(Random.Range(1,2) == 1){
SendDisk(Random.Range(1,3));
}
if(Random.Range(1,3) == 1){
SendDisk(Random.Range(1,3));
}
trial += 1;
}
break;
}
default:
break;
}
bool ret = disk_factory.FreeUsedDisks();
if(!ret){
HP--;
if(HP == 0){
GameOver();
}
}
}
}
其中,检测是否有鼠标点击的代码如下:
if (Input.GetButtonDown("Fire1")) {
Vector3 pos = Input.mousePosition;
Hit(pos);
}
- Hit()函数检测是否击中飞碟。
public void Hit(Vector3 pos) {
// Debug.Log("2");
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit[] hits;
hits = Physics.RaycastAll(ray);
for (int i = 0; i < hits.Length; i++) {
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<Disk>() != null) {
score_recorder.RecordScore(hit.collider.gameObject);
hit.collider.gameObject.transform.position = new Vector3(0, -15, 0);
}
}
}
- SendDisk()函数调用飞碟工厂类生成飞碟,并随机赋予速度、角度和位置:
private void SendDisk(int type) {
GameObject disk = disk_factory.GetDisk(type);
float disk_y = Random.Range(0f, 3f);
float disk_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
float speed = 0;
float angle = Random.Range(15f, 25f);
if (type == 1) {
speed = Random.Range(1f, 1.5f);
}
else if (type == 2) {
speed = Random.Range(1.5f, 2f);
}
disk.transform.position = new Vector3(disk_x * 14f, disk_y, 0);
action_manager.DiskFly(disk, angle, speed);
}
5)DiskFlyAction.cs
SSAction是动作基类,DiskFlyAction是飞碟飞行的动作类。
DiskFlyAction继承了SSAction,并且通过传入角度、初速度完成飞行的行为:
6)DiskFactory.cs
DiskFactory类是飞碟工厂类,作用是在需要的时候“生产”和销毁不同的飞碟。
- 使用链表存放使用的和空闲的飞碟:
private List<Disk> BusyDisks = new List<Disk>();
private List<Disk> FreeDisks = new List<Disk>();
- 根据预设生成飞碟:
if(type == 1) {
disk_prefab = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/disk1"),
new Vector3(0, -10f, 0), Quaternion.identity);
}else if (type == 2) {
disk_prefab = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/disk2"),
new Vector3(0, -10f, 0), Quaternion.identity);
}else {
disk_prefab = GameObject.Instantiate(
Resources.Load<GameObject>("Prefabs/disk3"),
new Vector3(0, -10f, 0), Quaternion.identity);
}disk_prefab.GetComponent<Renderer>().material.color = disk_prefab.GetComponent<Disk>().color;
- 通过返回一个bool判断飞碟是否落到屏幕外来判断是否正确击中飞碟,从而实现打飞碟的hp系统。
public bool FreeUsedDisks() {
bool ret = true;
for(int i = 0; i < BusyDisks.Count; i++) {
if (BusyDisks[i].gameObject.transform.position.y + 10f > -0.2f && BusyDisks[i].gameObject.transform.position.y + 10f < 0.2f){
Debug.Log("1");
BusyDisks[i].gameObject.transform.position = new Vector3(0, -20f, 0);
ret =false;
}
else if (BusyDisks[i].gameObject.transform.position.y <= -15f) {
FreeDisks.Add(BusyDisks[i]);
BusyDisks.Remove(BusyDisks[i]);
}
}
return ret;
}
7)ScoreRecorder.cs
统计分数,更新分数。