Hit UFO(Food Edition)
游戏介绍
游戏规则
- 点击飞碟来摧毁它
- 飞碟飞出屏幕外时扣除生命值,生命值归零时游戏结束
- 点击特定种类(Ribs)飞碟时,生命值增加1
- 摧毁飞碟时会获得点数,随着点数增加,游戏难度增大
游戏演示
游戏代码
游戏实现
代码结构
动作管理
本次游戏的动作管理基本遵循之前的架构,其中由于使用了两套运动方案,故使用了适配器模式(adaptor)连接了CCActionManager与PhysisActionManager。
Adaptor
public class Adapter : MonoBehaviour
{
// Start is called before the first frame update
public CCActionManager actionManager;
public PhysicActionManager physicActionManager;
private FirstController sceneController;
void Start()
{
actionManager = gameObject.AddComponent<CCActionManager>();
physicActionManager = gameObject.AddComponent<PhysicActionManager>();
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.actionManager = this;
}
public void playDisk(DiskData disk, bool mode){
if(mode){
actionManager.playDisk(disk);
} else {
physicActionManager.playDisk(disk);
}
}
void Update()
{
}
}
CCAcionManager
public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager{
private FirstController sceneController;
CCFlyAction action;
protected void Start() {
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
base.Start();
}
// Update is called once per frame
protected void Update ()
{
base.Update();
}
#region ISSActionCallback implementation
public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
{
}
public void playDisk(DiskData disk){
DataManager.MoveData m = sceneController.dataManager.GetMoveData(disk);
action = CCFlyAction.GetSSAction(m.speed, m.angle, m.gravity);
RunAction(disk.gameObject, action, this);
}
#endregion
}
CCFlyAction
public class CCFlyAction : SSAction
{
public float Power = 10f;
public float Angle = 45f;
public float Gravity = -10f;
private Vector3 XSpeed;
private Vector3 YSpeed;
private DataManager dataManager;
private DiskFactory diskFactory;
public static CCFlyAction GetSSAction(float Power, float Angle, float Gravity){
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction> ();
action.Power = Power;
action.Angle = Angle;
action.Gravity = Gravity;
action.XSpeed = new Vector3(1,0,0) * Mathf.Cos(action.Angle / 180 * Mathf.PI) * action.Power;
action.YSpeed = new Vector3(0,1,0) * Mathf.Sin(action.Angle / 180 * Mathf.PI) * action.Power;
return action;
}
public override void Update ()
{
transform.position += (XSpeed + YSpeed) * Time.deltaTime;
YSpeed += Gravity * new Vector3(0,1,0) * Time.deltaTime;
if (!dataManager.IsInScene(transform.position.x, transform.position.y) && gameobject.activeSelf) {
//waiting for destroy
this.destory = true;
this.callback.SSActionEvent (this);
dataManager.DeductLife();
diskFactory.FreeDisk(gameobject);
}
}
public override void Start () {
FirstController sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
dataManager = sceneController.dataManager;
diskFactory = sceneController.diskFactory;
}
}
PhysisActionManager
public class PhysicActionManager : SSActionManager, ISSActionCallback, IActionManager
{
// Start is called before the first frame update
private PhysicFlyAction action;
protected void Start()
{
base.Start();
}
// Update is called once per frame
protected void Update()
{
base.Update();
}
#region ISSActionCallback implementation
public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null)
{
}
public void playDisk(DiskData disk){
action = PhysicFlyAction.GetSSAction(disk.speed, disk.angle);
RunAction(disk.gameObject, action, this);
}
#endregion
}
PhysisFlyAction
public class PhysicFlyAction : SSAction
{
private Vector3 XSpeed;
private Vector3 YSpeed;
private Rigidbody rb;
private DataManager dataManager;
private DiskFactory diskFactory;
public static PhysicFlyAction GetSSAction(float Speed, float Angle){
PhysicFlyAction action = ScriptableObject.CreateInstance<PhysicFlyAction> ();
action.XSpeed = Speed * Mathf.Cos(Angle / 180 * Mathf.PI) * new Vector3(1, 0, 0);
action.YSpeed = Speed * Mathf.Sin(Angle / 180 * Mathf.PI) * new Vector3(0, 1, 0);
return action;
}
public override void Update ()
{
if (!dataManager.IsInScene(transform.position.x, transform.position.y) && gameobject.activeSelf) {
//waiting for destroy
diskFactory.FreeDisk(gameobject);
dataManager.DeductLife();
this.destory = true;
this.callback.SSActionEvent (this);
}
}
public override void Start () {
FirstController sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
dataManager = sceneController.dataManager;
diskFactory = sceneController.diskFactory;
gameobject.AddComponent<Rigidbody>();
rb = gameobject.GetComponent<Rigidbody>();
rb.velocity = XSpeed + YSpeed;
}
}
对象管理
对象管理主要通过工厂模式与对象池实现,场景控制器可在DiskFactorry获取对象与释放对象,而DiskFactory则创建对象池回收释放的对象,提高对象的利用率。
DiskFactory
public class DiskFactory : MonoBehaviour
{
public GameObject diskPrefab;
private List<DiskData> used = new List<DiskData>();
private List<DiskData> free = new List<DiskData>();
// private FirstController sceneController;
private DataManager dataManager;
public GameObject GetDisk (int round) {
int choice;
int scope1 = 1, scope2 = 5, scope3 = 8, scope4 = 10;
string tag;
diskPrefab = null;
choice = Random.Range(0, scope4);
if (choice <= scope1) {
tag = "Apple";
}
else if (choice <= scope2 && choice > scope1) {
tag = "Orange";
}
else if (choice <= scope3 && choice > scope2){
tag = "Tomato";
} else {
tag = "Ribs";
}
for(int i=0;i<free.Count;i++) {
if(free[i].gameObject.CompareTag(tag)) {
diskPrefab = free[i].gameObject;
free[i].gameObject.SetActive(true);
free.Remove(free[i]);
break;
}
}
if (diskPrefab == null) {
GameObject prefab = Resources.Load("Prefabs/" + tag, typeof(GameObject)) as GameObject;
diskPrefab = Instantiate(prefab,new Vector3(0, 0, 0), prefab.transform.rotation);
}
float ran_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
used.Add(diskPrefab.GetComponent<DiskData>());
// 随机生成位置
float Xedge = 5;
float Yedge = 3.5f;
choice = Random.Range(1, 4);
if (choice == 1) {
diskPrefab.transform.position = new Vector3(-Xedge, Random.Range(0, Yedge / 2f), -1);
diskPrefab.GetComponent<DiskData>().angle = Random.Range(-30, 60);
} else if (choice == 2) {
diskPrefab.transform.position = new Vector3(Random.Range(-Xedge / 3f, Xedge / 3f), Yedge, -1);
diskPrefab.GetComponent<DiskData>().angle = Random.Range(-135, -45);
} else {
diskPrefab.transform.position = new Vector3(Xedge, Random.Range(0, Yedge / 2f), -1);
diskPrefab.GetComponent<DiskData>().angle = Random.Range(-225, -135);
}
return diskPrefab;
}
private void IniDisk (GameObject disk) {
if (disk.GetComponent<Rigidbody>() != null) {
Destroy(disk.GetComponent<Rigidbody>());
}
}
public void FreeDisk (GameObject disk) {
for (int i = 0; i < used.Count; i++) {
if (used[i].gameObject == disk) {
IniDisk(used[i].gameObject);
used[i].gameObject.SetActive(false);
free.Add(used[i]);
used.Remove(used[i]);
break;
}
}
}
// Start is called before the first frame update
void Start()
{
FirstController sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.diskFactory = this;
dataManager = sceneController.dataManager;
}
// Update is called once per frame
void Update()
{
}
}
DiskData
public class DiskData : MonoBehaviour
{
public int score;
public float speed;
public float angle;
public string foodName;
}
用户交互
用户交互部分与往期作业相比变化不大,是使场景控制器实现IUserAction完成的。
UserGUI
public class UserGUI : MonoBehaviour
{
public FirstController userAction;
private String ButtonName1 = "Start";
private String ButtonName2 = "Physis";
public GUIStyle customButton;
public GUIStyle customLabel;
public GameObject catchFood, gameOver;
private float w;
private float h;
private int score;
private int life;
private bool IsGameOver;
// Start is called before the first frame update
void Start()
{
userAction = (FirstController)SSDirector.getInstance ().currentSceneController;
userAction.userGUI = this;
w = Screen.width;
h = Screen.height;
gameOver.SetActive(false);
catchFood.SetActive(true);
}
// Update is called once per frame
void Update()
{
}
void OnGUI()
{
if (IsGameOver) {
userAction.GameOver();
gameOver.SetActive(true);
catchFood.SetActive(false);
}
if (GUI.Button(new Rect(h * 0.02f, h * 0.08f, w * 0.15f, h * 0.065f),ButtonName1,customButton)) {
if (ButtonName1 == "Start") {
userAction.GameStart();
ButtonName1 = "Restart";
} else {
userAction.GameRestart();
ButtonName1 = "Start";
gameOver.SetActive(false);
catchFood.SetActive(true);
}
}
if (GUI.Button(new Rect(h * 0.02f, h * 0.18f, w * 0.15f, h * 0.065f),ButtonName2,customButton)) {
if (ButtonName2 == "Physis") {
ButtonName2 = "Kinematics";
userAction.GameModeSet(false);
} else {
ButtonName2 = "Physis";
userAction.GameModeSet(true);
}
}
GUI.Label(new Rect(h * 0.02f, 20, 100, 50),"Score:" + score, customLabel);
GUI.Label(new Rect(w * 0.15f, 20, 100, 50), "Life:" + life, customLabel);
}
public void SetScore(int score){
this.score = score;
}
public void SetLife(int life){
this.life = life;
}
public void SetIsGameOver(bool IsGameOver){
this.IsGameOver = IsGameOver;
}
}
数据管理
本次作业中要求实现ScoreManager,但是由于本游戏中同时包含生命值,游戏round等数值,最终决定通过创建DataManager进行统一地数据管理。
DataManager
public class DataManager : MonoBehaviour
{
private FirstController sceneController;
public GameObject background;
private int score = 0;
private int live;
public float interval = 1f;
public int round = 1;
public float gravity = -10f;
public bool mode = true;
public struct EdgeData{
public float Xedge;
public float Yedge;
}
public struct MoveData{
public float gravity;
public float speed;
public float angle;
}
// Start is called before the first frame update
void Start()
{
sceneController = (FirstController)SSDirector.getInstance ().currentSceneController;
sceneController.dataManager = this;
Physics.gravity = new Vector3(0,gravity,0);
}
// Update is called once per frame
void Update()
{
if (score >= (int)Mathf.Pow(round, 1.5f) * 200) round++;
}
public void Init(){
score = 0;
live = 5;
round = 1;
sceneController.userGUI.SetIsGameOver(false);
sceneController.userGUI.SetScore(score);
sceneController.userGUI.SetLife(live);
}
public void UpdateScore(GameObject disk) {
DiskData diskData = disk.GetComponent<DiskData>();
score += diskData.score * (int)Math.Sqrt(round);
interval = 1f - round * 0.1f;
sceneController.userGUI.SetScore(score);
}
public void DeductLife() {
if (live > 0) live--;
sceneController.userGUI.SetLife(live);
if (live == 0) sceneController.userGUI.SetIsGameOver(true);
}
public void AddLife() {
live++;
sceneController.userGUI.SetLife(live);
}
public MoveData GetMoveData(DiskData disk){
MoveData m;
m.gravity = gravity;
m.speed = disk.speed;
m.angle = disk.angle;
return m;
}
public EdgeData GetEdgeData(){
EdgeData e;
e.Xedge = background.transform.localScale.x / 2f;
e.Yedge = background.transform.localScale.y / 2f;
return e;
}
public bool IsInScene(float x, float y) {
EdgeData e;
e.Xedge = background.transform.localScale.x / 2f;
e.Yedge = background.transform.localScale.y / 2f;
if (x >= -e.Xedge && x <= e.Xedge && y >= -e.Yedge) return true;
return false;
}
}
场景控制
场景控制器通过调用ActionManager、DataManager、DiskFactory提供的方法,完成整个场景的调度。
FirstController
public class FirstController : MonoBehaviour, ISceneController, IUserAction {
public Adapter actionManager { get; set;}
public DiskFactory diskFactory{ get; set; }
public DataManager dataManager{ get; set; }
public UserGUI userGUI{ get; set; }
public List<GameObject> flyingDisks = new List<GameObject>();
private float timer = 0f;
public bool IsRunning = false;
// the first scripts
void Awake () {
SSDirector director = SSDirector.getInstance ();
director.setFPS (60);
director.currentSceneController = this;
director.currentSceneController.LoadResources ();
Debug.Log ("awake FirstController!");
}
// loading resources for first scence
public void LoadResources () {
;
}
public void Pause ()
{
throw new System.NotImplementedException ();
}
public void Resume ()
{
throw new System.NotImplementedException ();
}
#region IUserAction implementation
public void GameOver ()
{
SSDirector.getInstance ().NextScene ();
IsRunning = false;
}
public void GameStart() {
IsRunning = true;
dataManager.Init();
}
public void GameRestart() {
IsRunning = true;
dataManager.Init();
}
public void GameModeSet(bool mode){
dataManager.mode = mode;
}
#endregion
// Use this for initialization
void Start () {
//give advice first
}
// Update is called once per frame
void Update () {
if (IsRunning) {
if(Input.GetMouseButton(0)){
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray,out hit)){
//划出射线,只有在scene视图中才能看到
Debug.DrawLine(ray.origin,hit.point);
GameObject gameObject = hit.collider.gameObject;
if(gameObject.GetComponent<DiskData>().foodName == "Ribs") dataManager.AddLife();
dataManager.UpdateScore(gameObject);
diskFactory.FreeDisk(gameObject);
}
}
timer += Time.deltaTime;
if (timer >= dataManager.interval) {
timer = 0f;
GameObject disk = diskFactory.GetDisk(dataManager.round);
actionManager.playDisk(disk.GetComponent<DiskData>(), dataManager.mode);
}
}
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。