项目传送门:
Hit-UFO-v2
打靶小游戏
1、改进飞碟(Hit UFO)游戏:
改进要求
游戏内容要求:
- 按 adapter模式 设计图修改飞碟游戏
- 使它同时支持物理运动与运动学(变换)运动
有如下设计图:
实现思路
-
首先,是飞碟的预制
我们想要实现飞碟的物理学运动,那就要给飞碟增加一个刚体组件。
-
1.IActionManager
根据设计要求,使用Adapter模式,需要我们实现一个IActionManager类,用于接口转换,把原本不兼容的类连接起来一起工作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IActionManager
{
void Fly(GameObject disk, float speed, Vector3 direction);
}
- 2.PhysisFlyAction
这就是物理学运动的基类,对刚体进行操作,实现运动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysisFlyAction : SSAction
{
float speed; //水平速度
Vector3 direction; //飞行方向
public static PhysisFlyAction GetSSAction(Vector3 direction, float speed)
{
PhysisFlyAction action = ScriptableObject.CreateInstance<PhysisFlyAction>();
action.speed = speed;
action.direction = direction;
return action;
}
public override void Start()
{
gameObject.GetComponent<Rigidbody>().isKinematic = false;
gameObject.GetComponent<Rigidbody>().velocity = speed * direction;
}
public override void Update()
{
//如果飞碟到达底部,回调
if (this.transform.position.y < -6)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
- 3.PhysisActionManager
管理PhysisFlyAction,包括飞行动作控制,回收操作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager
{
PhysisFlyAction flyAction;
FirstController controller;
protected new void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
}
public void Fly(GameObject disk, float speed, Vector3 direction)
{
flyAction = PhysisFlyAction.GetSSAction(direction, speed);
RunAction(disk, flyAction, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competed,
int intParam = 0, string strParam = null, Object objectParam = null)
{
//飞碟结束飞行后进行回收
controller.FreeDisk(source.gameObject);
}
}
- 4.Interface增加用户控制接口
增加一个接口IUserAction,用于用户选择运动学还是物理学运动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction
{
void SetFlyMode(bool isPhysis);
void Hit(Vector3 position);
void Restart();
float GetScore();
int GetRound();
//void GameOver();
}
- 5.FirstController的改进
根据设计图,我们需要简化FirstController的功能,否则会过于臃肿。
所以我们把round的控制交给一个新的类——RoundController,现在FirstController只管理游戏的启动、资源加载等内容。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
DiskFactory diskFactory; //飞碟工厂
RoundController roundController;
UserGUI userGUI;
void Start()
{
SSDirector.GetInstance().CurrentScenceController = this;
gameObject.AddComponent<DiskFactory>();
gameObject.AddComponent<CCActionManager>();
gameObject.AddComponent<PhysisActionManager>();
gameObject.AddComponent<RoundController>();
gameObject.AddComponent<UserGUI>();
LoadResources();
}
public void LoadResources()
{
diskFactory = Singleton<DiskFactory>.Instance;
roundController = Singleton<RoundController>.Instance;
userGUI = Singleton<UserGUI>.Instance;
}
public void Hit(Vector3 position)
{
Camera ca = Camera.main;
Ray ray = ca.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)
{
//击中后操作、计分
hit.collider.gameObject.transform.position = new Vector3(0, -7, 0);
roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());
userGUI.SetPoints(roundController.GetPoints());
}
}
}
public void Restart()
{
userGUI.SetMessage("");
userGUI.SetPoints(0);
roundController.Reset();
}
public float GetScore()
{
return roundController.GetPoints();
}
public int GetRound()
{
return roundController.GetRound();
}
public void SetFlyMode(bool isPhysis)
{
roundController.SetFlyMode(isPhysis);
}
public void FreeDisk(GameObject disk)
{
diskFactory.FreeDisk(disk);
}
void Update()
{
}
}
- 6.RoundController
轮次控制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RoundController : MonoBehaviour
{
FirstController controller;
IActionManager actionManager; //动作管理者
DiskFactory diskFactory; //飞碟工厂
ScoreRecorder scoreRecorder;
UserGUI userGUI;
int[] roundDisks; //对应轮次的飞碟数量
bool isInfinite; //游戏当前模式
int round; //游戏当前轮次
int sendCnt; //当前已发送的飞碟数量
float sendTime; //发送时间
void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
actionManager = Singleton<CCActionManager>.Instance;
diskFactory = Singleton<DiskFactory>.Instance;
scoreRecorder = new ScoreRecorder();
userGUI = Singleton<UserGUI>.Instance;
sendCnt = 0;
round = 0;
sendTime = 0;
isInfinite = false;
roundDisks = new int[] { 3, 5, 8, 13, 21 };
}
public void Reset()
{
sendCnt = 0;
round = 0;
sendTime = 0;
scoreRecorder.Reset();
}
public void Record(DiskData disk)
{
scoreRecorder.Record(disk);
}
public int GetPoints()
{
return scoreRecorder.GetPoints();
}
public int GetRound()
{
return round;
}
public void SetMode(bool isInfinite)
{
this.isInfinite = isInfinite;
}
public void SetFlyMode(bool isPhysis)
{
actionManager = isPhysis ? Singleton<PhysisActionManager>.Instance : Singleton<CCActionManager>.Instance as IActionManager;
}
public void SendDisk()
{
//从工厂生成一个飞碟
GameObject disk = diskFactory.GetDisk(round);
//随机位置
disk.transform.position = new Vector3(-disk.GetComponent<DiskData>().direction.x * 7, UnityEngine.Random.Range(0f, 8f), 0);
disk.SetActive(true);
actionManager.Fly(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);
}
// Update is called once per frame
void Update()
{
sendTime += Time.deltaTime;
if (sendTime > 1)
{
sendTime = 0;
for (int i = 0; i < 5 && sendCnt < roundDisks[round]; i++)
{
sendCnt++;
SendDisk();
}
if (sendCnt == roundDisks[round] && round == roundDisks.Length - 1)
{
if (isInfinite)
{
round = 0;
sendCnt = 0;
userGUI.SetMessage("");
}
else
{
userGUI.SetMessage("Game Over!");
}
}
//更新轮次
if (sendCnt == roundDisks[round] && round < roundDisks.Length - 1)
{
sendCnt = 0;
round++;
}
}
}
}
- 7.最后是UI界面的改动
添加了两个按钮,用于用户选择运动学还是物理学模式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
IUserAction userAction;
string gameMessage;
int points;
GUIStyle text_style = new GUIStyle();
GUIStyle bold_style = new GUIStyle();
GUIStyle over_style = new GUIStyle();
//控制规则显示
bool isShow = false;
private bool game_start = false;
public void SetMessage(string gameMessage)
{
this.gameMessage = gameMessage;
}
public void SetPoints(int points)
{
this.points = points;
}
void Start()
{
points = 0;
gameMessage = "";
userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;
}
void OnGUI()
{
//小字体初始化
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
style.fontSize = 30;
//大字体初始化
GUIStyle bigStyle = new GUIStyle();
bigStyle.normal.textColor = Color.white;
bigStyle.fontSize = 50;
text_style.normal.textColor = new Color(0, 0, 0, 1);
text_style.fontSize = 16;
bold_style.normal.textColor = new Color(1, 0, 0);
bold_style.fontSize = 16;
over_style.normal.textColor = new Color(1, 0, 0);
over_style.fontSize = 25;
GUIStyle button_style = new GUIStyle("button")
{
fontSize = 15
};
if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
{
if (isShow)
isShow = false;
else
isShow = true;
}
if(isShow)
{
GUI.Label(new Rect(Screen.width / 2 - 400 , 70, 100, 50), "点击Start开始游戏", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 90, 250, 50), "游戏过程中鼠标左键为hit", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 110, 250, 50), "随着时间的推移,难度会上升", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 130, 250, 50), "上吧!!!", text_style);
}
if (GUI.Button(new Rect(20, 200, 100, 40), "运动学"))
{
userAction.SetFlyMode(false);
}
if (GUI.Button(new Rect(20, 250, 100, 40), "物理学"))
{
userAction.SetFlyMode(true);
}
if (Input.GetButtonDown("Fire1"))
{
userAction.Hit(Input.mousePosition);
}
if (game_start) {
GUI.Label(new Rect(Screen.width - 150, 5, 200, 50), "Score:"+ points, text_style);
GUI.Label(new Rect(100, 5, 50, 50), "Round:" + userAction.GetRound().ToString(), text_style);
if (userAction.GetRound() == 4 ) {
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 100, 100, 100), "GAME OVER", over_style);
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 50, 50, 50), "YOUR SCORE: " + userAction.GetScore().ToString(), over_style);
if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.height / 2, 100, 50), "RESTART")) {
userAction.Restart();
return;
}
//userAction.GameOver();
}
}
else {
GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 100, 100, 100), "Hit UFO", over_style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2, 100, 50), "START")) {
game_start = true;
userAction.Restart();
}
}
}
}
运行演示
将FirstController挂载到一个空对象上,就可以运行游戏了。
运动学演示:
物理学演示:
此时飞碟有碰撞现象,并且不会直接消失。
2、打靶游戏(可选作业):
设计要求
游戏内容要求:
- 靶对象为 5 环,按环计分;
- 箭对象,射中后要插在靶上
- 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后 - 游戏仅一轮,无限 trials;
- 增强要求:添加一个风向和强度标志,提高难度
设计思路
-
模型预制
首先是箭的预制,我发现自己使用自带的Object做出来的很丑,所以就到Asset Store搜到了一个做的比较好的箭的模型,就拿来使用了。
然后是靶的预制,本来也想搜一个,但是发现搜到的预制不能实现不同环上分数不同的效果,所以就自己做了一个。思路就是五个圆盘叠加,为了区分出着五个圆盘,把直径小的圆盘置于更上面,就能实现出类似“圆环”的效果。
-
代码实现
根据MVC架构,实现一个靶模型类(Circle),一个箭模型类(Arrow),一个箭控制类(ArrowController),然后就是Score和ScoreRecoder,最后是UserGUI用于UI界面实现。
靶模型类(Circle)
创建靶,并且给定各个环的分数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Circle
{
private GameObject circle1;
private GameObject circle2;
private GameObject circle3;
private GameObject circle4;
private GameObject circle5;
public Circle()
{
circle1 = Resources.Load<GameObject>("Prefab/Circle1");
circle1 = GameObject.Instantiate(circle1);
circle2 = Resources.Load<GameObject>("Prefab/Circle2");
circle2 = GameObject.Instantiate(circle2);
circle3 = Resources.Load<GameObject>("Prefab/Circle3");
circle3 = GameObject.Instantiate(circle3);
circle4 = Resources.Load<GameObject>("Prefab/Circle4");
circle4 = GameObject.Instantiate(circle4);
circle5 = Resources.Load<GameObject>("Prefab/Circle5");
circle5 = GameObject.Instantiate(circle5);
circle3.transform.parent = circle1.transform;
circle4.transform.parent = circle1.transform;
circle5.transform.parent = circle1.transform;
circle1.AddComponent<Score>();
circle2.AddComponent<Score>();
circle3.AddComponent<Score>();
circle4.AddComponent<Score>();
circle5.AddComponent<Score>();
circle1.name = "1";
circle2.name = "2";
circle3.name = "3";
circle4.name = "4";
circle5.name = "5";
}
}
一个箭模型类(Arrow)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Arrow : MonoBehaviour
{
private GameObject sth;
private int screen_width;
private int screen_height;
// Start is called before the first frame update
void Start()
{
sth = Resources.Load<GameObject>("Prefab/Arrow");
screen_width = Screen.width;
screen_height = Screen.height;
}
// Update is called once per frame
void Update()
{
//射线检测
if(Input.GetMouseButtonDown(0))
{
Ray _ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit _hit;
Vector3 vec;
if(Physics.Raycast(_ray, out _hit))
{
vec = _hit.point;
vec.z = Camera.main.transform.position.z + 1;
sth.transform.position = vec;
GameObject arrow = GameObject.Instantiate(sth);
arrow.AddComponent<ArrowController>();
}
}
}
}
一个箭控制类(ArrowController)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowController : MonoBehaviour
{
private float speed;
private bool fly;
// Start is called before the first frame update
void Start()
{
fly = true;
speed = 20.0f;
}
// Update is called once per frame
void Update()
{
if(this.gameObject.transform.position.y < -5)
{
Destroy(this.gameObject);
}
if(fly)
{
Vector3 vec = this.gameObject.transform.position;
vec.z -= -speed * Time.deltaTime;
this.gameObject.transform.position = vec;
}
}
void OnCollisionEnter(Collision collision)
{
fly = false;
Destroy(GetComponent<Rigidbody>());
Destroy(GetComponent<Collider>());
}
}
Score和ScoreRecoder
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Score : MonoBehaviour
{
public int goal;
// Start is called before the first frame update
void Start()
{
goal = int.Parse(this.gameObject.name);
}
// Update is called once per frame
void Update()
{
}
void OnCollisionEnter(Collision collision)
{
ScoreRecorder.score += goal;
}
}
/ScoreRecoder//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder
{
public static int score = 0;
// public void SetScore(int temp)
// {
// score = temp;
// }
// public int GetScore()
// {
// return score;
// }
}
UserGUI
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
private GUIStyle style;
private Circle mcircle;
//控制规则显示
bool isShow = false;
//字体样式
GUIStyle text_style = new GUIStyle();
// Start is called before the first frame update
void Start()
{
style = new GUIStyle();
style.fontSize = 20;
mcircle = new Circle();
}
// Update is called once per frame
void Update()
{
}
void OnGUI()
{
GUI.Label(new Rect(Screen.width / 2 + 320, Screen.height / 2 - 180, 100, 100), "Score:" + ScoreRecorder.score, style);
text_style.normal.textColor = new Color(0, 0, 0, 1);
text_style.fontSize = 16;
GUIStyle button_style = new GUIStyle("button")
{
fontSize = 15
};
if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
{
if (isShow)
isShow = false;
else
isShow = true;
}
if(isShow)
{
GUI.Label(new Rect(Screen.width / 2 - 400 , 70, 100, 50), "直接点击靶子开始游戏", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 90, 250, 50), "游戏只有一轮", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 110, 250, 50), "越靠近中心的环分数越高", text_style);
GUI.Label(new Rect(Screen.width / 2 - 400, 130, 250, 50), "加油!得到更高的分数", text_style);
}
}
}
- 项目说明及优化思路
由于最近实在很忙,所以没有把打靶的增强功能实现,关键是我也想不到要怎样实验箭尾的抖动,然后关于风向的问题倒是有思路,我想到通过给箭施加一个力来模拟风的作用,并且可以在界面中指示风的大小和方向,参考下图。但是要实现这个功能还得多些好几个类,比较麻烦,就放弃了,接下来有时间的话会继续更新。
然后是优化思路,其实像这样的打靶小游戏网上可以搜到很多,我参考其中的一些提出几个可以继续提升的方向:第一,可以提升难度,让靶子变成移动靶,这里需要使用到后面的动画的知识; 第二,设定关卡,可以逐步提升难度; 第三,射箭的动作可以继续优化,我们实现的射箭是直接就发射出去的,这天然减少了“瞄准”的难度,所以可以给一个瞄准的过程,提高难度。