改进飞碟(Hit UFO)游戏:
游戏截图:
1,作业要求
游戏内容要求:
1,按adapter模式设计图修改飞碟游戏
2,使它同时支持物理运动与运动学(变换)运动
2,设计思路
1,分析adapter模式
adapter模式的核心思想是让不同的实现类同时实现一个接口,其他类再用这个接口来实例化使得其他类不会因为接口不兼容而只能使用其中一个类。比如我们做一个游戏IO,它要有一个统一输入接口,能够兼容游戏手柄、键鼠、手机等多种输入设备。
adapter模式如图所示:
2,分析物理运动
刚体是一个组件。一个游戏对象加上刚体之后游戏引擎就会对其进行物理效果模拟。同时我们也可以给这个对象施加各种作用力,让它运动起来。
刚体有各种属性:
Mass:指定物体的质量
Drag:空气阻力,影响物体的移动速度和移动距离
Angular Drag:物体旋转时受到的空气阻力
Use Gravity:是否使用重力
Is Kinematic:是否动态
Interpolate:平滑方式
Collision Detection:碰撞检测
Freeze Position/Rotation :在三个轴上防止进行位置/旋转的变化
通过各种属性的设定,可以模拟现实中物体的物理效果。
3,具体设计
添加一个有关物理运动的类PhysicActionManager,实现在FirstSceneController类中能够由用户选择使用哪种运动方式来决定使用哪个动作管理类。题目要求使用adapter模式,即两个动作管理类应该同时实现一个接口,于是抽象出两个动作管理类应该在FirstSceneController类中使用的方法,作为一个接口IActionManager。这样就符合了题目要求。
具体而言,PhysicActionManager类也继承了SSActionManager类,复制CCActionManager类的代码,然后修改Update()和Start()方法,通知SSActionManager类它的子类是谁。
然后在SSActionManager类中添加一个属性flag,两个子类可以修改它,进而影响SSActionManager类中的Update()方法。
在FirstSceneManager类中修改动作管理器的类型为接口IActionManager,添加某些方法来影响IActionManager的具体实例。
另外,在DiskFactory中为新生成的对象添加刚体,在UserGUI中添加按钮代码给用户选择使用哪一种方式来使飞碟运动。
以上就是对前一次作业的修改设计。
3,部分代码
PhysicActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysicActionManager : SSActionManager, ISSActionCallback, IActionManager
{
private FirstSceneControl sceneControl;
private List<CCFlyAction> flys = new List<CCFlyAction>();
private int diskNumber = 0;
private List<SSAction> used = new List<SSAction>();
private List<SSAction> free = new List<SSAction>();
public void setDiskNumber(int dn)
{
diskNumber = dn;
}
public int getDiskNumber()
{
return diskNumber;
}
public SSAction getSSAction()
{
SSAction action = null;
if (free.Count > 0)
{
action = free[0];
free.Remove(free[0]);
}
else
{
action = ScriptableObject.Instantiate<CCFlyAction>(flys[0]);
}
used.Add(action);
return action;
}
public void freeSSAction(SSAction action)
{
foreach (SSAction a in used)
{
if (a.GetInstanceID() == action.GetInstanceID())
{
a.reset();
free.Add(a);
used.Remove(a);
break;
}
}
}
protected void Start()
{
sceneControl = (FirstSceneControl)Director.getInstance().current;
sceneControl.actionManager = this;
flys.Add(CCFlyAction.getCCFlyAction());
base.flag = false;
}
private new void Update()
{
if (sceneControl.getGameState() == GameState.RUNNING)
{
base.Update();
}
else
{
base.stopRigidbodyAction();
}
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed, int intPram = 0
, string strParm = null, Object objParm = null)
{
if (source is CCFlyAction)
{
diskNumber--;
Singleton<DiskFactory>.Instance.freeDisk(source.gameObject);
freeSSAction(source);
}
}
public void startThrow(Queue<GameObject> diskQueue)
{
foreach (GameObject i in diskQueue)
{
runAction(i, getSSAction(), (ISSActionCallback)this);
}
}
}
SSActionManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SSActionManager : MonoBehaviour {
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> waitingAdd = new List<SSAction>();
private List<int> waitingDelete = new List<int>();
protected bool flag = true;
protected void Update()
{
foreach (SSAction action in waitingAdd)
{
actions[action.GetInstanceID()] = action;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> i in actions)
{
SSAction value = i.Value;
if (value.destroy)
{
waitingDelete.Add(value.GetInstanceID());
}
else if (value.enable && flag)
{
value.Update();
}
else if (value.enable && flag == false)
{
value.FixedUpdate();
}
}
foreach (int i in waitingDelete)
{
SSAction ac = actions[i];
actions.Remove(i);
DestroyObject(ac);
}
}
public void runAction(GameObject gameObject, SSAction action, ISSActionCallback manager)
{
action.gameObject = gameObject;
action.transform = gameObject.transform;
action.callback = manager;
waitingAdd.Add(action);
action.Start();
}
public void stopRigidbodyAction()
{
foreach (SSAction action in actions.Values)
{
action.stopAction();
}
}
}
CCFlyAction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCFlyAction : SSAction
{
float acceleration;
float horizontalSpeed;
Vector3 direction;
float time;
Rigidbody rigidbody;
// Vector3 old;
public static CCFlyAction getCCFlyAction()
{
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
return action;
}
public override void Start()
{
enable = true;
acceleration = 9.8f;
time = 0;
// old = gameObject.transform.position;
horizontalSpeed = gameObject.GetComponent<DiskData>().getSpeed();
direction = gameObject.GetComponent<DiskData>().getDirection();
rigidbody = gameObject.GetComponent<Rigidbody>();
if (rigidbody)
{
rigidbody.velocity = horizontalSpeed * direction;
}
}
public override void Update()
{
if (gameObject.activeSelf)
{
time += Time.deltaTime;
transform.Translate(Vector3.down * acceleration * time * Time.deltaTime);
transform.Translate(direction * horizontalSpeed * Time.deltaTime);
clear();
}
}
public override void FixedUpdate()
{
if (gameObject.activeSelf)
{
clear();
}
}
private void clear()
{
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
public override void stopAction()
{
if (rigidbody)
{
rigidbody.velocity = Vector3.zero;
rigidbody.useGravity = false;
}
}
}
FirstSceneManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstSceneControl : MonoBehaviour, ISceneControl, IUserAction {
public IActionManager actionManager { set; get; }
public ScoreRecorder scoreRecorder { set; get; }
public Queue<GameObject> diskQueue = new Queue<GameObject>();
private ActionMode actionMode = ActionMode.NOTSET;
private int diskNumber = 0;
private int currentRound = -1;
private float time = 0;
private GameState gameState = GameState.START;
void Awake()
{
Director director = Director.getInstance();
director.current = this;
diskNumber = 10;
this.gameObject.AddComponent<ScoreRecorder>();
this.gameObject.AddComponent<DiskFactory>();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
director.current.loadResources();
}
public void loadResources()
{
}
private void Update()
{
if(actionMode == ActionMode.NOTSET || actionManager == null)
{
return;
}
if(actionManager.getDiskNumber() == 0 && gameState == GameState.RUNNING)
{
gameState = GameState.ROUND_FINISH;
gameState = GameState.ROUND_FINISH;
if(actionMode == ActionMode.PHYSIC)
{
gameState = GameState.FUNISH;
return;
}
if(currentRound == 2)
{
gameState = GameState.FUNISH;
currentRound = 0;
return;
}
}
if(actionManager.getDiskNumber() == 0 && gameState == GameState.ROUND_START)
{
currentRound++;
nextRound();
actionManager.setDiskNumber(10);
gameState = GameState.RUNNING;
}
if(time > 1 && gameState != GameState.PAUSE)
{
throwDisk();
time = 0;
}
else
{
time += Time.deltaTime;
}
}
private void nextRound()
{
DiskFactory diskFactory = Singleton<DiskFactory>.Instance;
for(int i = 0; i < diskNumber; i++)
{
diskQueue.Enqueue(diskFactory.getDisk(currentRound, actionMode));
}
actionManager.startThrow(diskQueue);
}
void throwDisk()
{
if(diskQueue.Count != 0)
{
GameObject disk = diskQueue.Dequeue();
Vector3 pos = new Vector3(-disk.GetComponent<DiskData>().getDirection().x * 10, Random.Range(0f, 4f), 0);
disk.transform.position = pos;
disk.SetActive(true);
}
}
public int getScore()
{
return scoreRecorder.score;
}
public GameState getGameState()
{
return gameState;
}
public ActionMode getActionMode()
{
return actionMode;
}
public void setGameState(GameState gameState)
{
this.gameState = gameState;
}
public void setActionMode(ActionMode mode)
{
actionMode = mode;
if(actionMode == ActionMode.PHYSIC)
{
this.gameObject.AddComponent<PhysicActionManager>();
}
else if(actionMode == ActionMode.KINEMATIC)
{
this.gameObject.AddComponent<CCActionManager>();
}
}
public void hit(Vector3 pos)
{
RaycastHit[] hits = Physics.RaycastAll(Camera.main.ScreenPointToRay(pos));
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, -5, 0);
}
}
}
}
DiskFactory.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskFactory : MonoBehaviour {
public GameObject diskPrefab;
public List<DiskData> used = new List<DiskData>();
public List<DiskData> free = new List<DiskData>();
private void Awake()
{
diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
diskPrefab.SetActive(false);
}
public GameObject getDisk(int round, ActionMode mode)
{
GameObject disk = null;
if(free.Count > 0)
{
disk = free[0].gameObject;
free.Remove(free[0]);
}
else
{
disk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
disk.AddComponent<DiskData>();
}
if(mode == ActionMode.PHYSIC && disk.GetComponent<Rigidbody>() == null)
{
disk.AddComponent<Rigidbody>();
}
int start;
switch (round)
{
case 0: start = 0; break;
case 1: start = 100; break;
default: start = 200; break;
}
int selectColor = Random.Range(start, round * 499);
round = selectColor / 250;
DiskData diskData = disk.GetComponent<DiskData>();
Renderer renderer = disk.GetComponent<Renderer>();
Renderer childRenderer = disk.transform.GetChild(0).GetComponent<Renderer>();
float ranX = Random.Range(-1, 1) < 0 ? -1.2f : 1.2f;
Vector3 direction = new Vector3(ranX, 1, 0);
switch (round)
{
case 0:
diskData.setDiskData(new Vector3(1.35f, 1.35f, 1.35f), Color.white, 4.0f, direction);
renderer.material.color = Color.white;
childRenderer.material.color = Color.white;
break;
case 1:
diskData.setDiskData(new Vector3(1f, 1f, 1f), Color.gray, 6.0f, direction);
renderer.material.color = Color.gray;
childRenderer.material.color = Color.gray;
break;
case 2:
diskData.setDiskData(new Vector3(0.7f, 0.7f, 0.7f), Color.black, 8.0f, direction);
renderer.material.color = Color.black;
childRenderer.material.color = Color.black;
break;
}
used.Add(diskData);
diskData.name = diskData.GetInstanceID().ToString();
disk.transform.localScale = diskData.getSize();
return disk;
}
public void freeDisk(GameObject disk)
{
DiskData temp = null;
foreach (DiskData i in used)
{
if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
{
temp = i;
}
}
if (temp != null)
{
temp.gameObject.SetActive(false);
free.Add(temp);
used.Remove(temp);
}
}
}
UserGUI.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class UserGUI : MonoBehaviour {
private IUserAction action;
bool isFirst = true;
GUIStyle red;
GUIStyle black;
bool pause_flag = true;
void Start () {
action = Director.getInstance().current as IUserAction;
black = new GUIStyle("button");
black.fontSize = 20;
red = new GUIStyle();
red.fontSize = 30;
red.fontStyle = FontStyle.Bold;
red.normal.textColor = Color.red;
red.alignment = TextAnchor.UpperCenter;
}
private void OnGUI()
{
if (action.getGameState() == GameState.FUNISH)
{
GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 150, 200, 100), action.getScore() >= 30 ? "你胜利了" : "你失败了", red);
if(GUI.Button(new Rect(Screen.width / 2 - 60, Screen.height / 2 - 50, 120, 40), "重新开始", black))
{
SceneManager.LoadScene("DiskAttack");
}
return;
}
Rect rect = new Rect(Screen.width / 2 - 100, 0, 200, 40);
Rect rect2 = new Rect(Screen.width / 2 - 60, 60, 120, 40);
if (Input.GetButtonDown("Fire1") && action.getGameState() != GameState.PAUSE)
{
Vector3 pos = Input.mousePosition;
action.hit(pos);
}
if (!isFirst)
{
GUI.Label(rect, "你的分数: " + action.getScore().ToString(), red);
}
else
{
GUIStyle blackLabel = new GUIStyle();
blackLabel.fontSize = 16;
blackLabel.normal.textColor = Color.black;
GUI.Label(new Rect(Screen.width / 2 - 250, 120, 500, 200), "一共3个关卡,每一个关卡有10个飞碟,飞碟" +
"的颜色是不一样的\n如果攻击白色的飞碟,你会得到1分。如果攻击灰色的飞碟\n" +
"则会得到2分,如果攻击黑色的飞碟(速度最快),你会得到4分。\n" +
"游戏结束时得到超过30分就胜利了!", blackLabel);
}
if (pause_flag)
{
if (action.getGameState() == GameState.RUNNING && GUI.Button(rect2, "暂停", black))
{
action.setGameState(GameState.PAUSE);
}
else if (action.getGameState() == GameState.PAUSE && GUI.Button(rect2, "继续", black))
{
action.setGameState(GameState.RUNNING);
}
}
if(action.getActionMode() == ActionMode.NOTSET)
{
if(GUI.Button(new Rect(Screen.width / 2 - 60, 0, 120, 40), "物理模式", black))
{
action.setActionMode(ActionMode.PHYSIC);
pause_flag = false;
}
if (GUI.Button(rect2, "运动学模式", black))
{
action.setActionMode(ActionMode.KINEMATIC);
}
}
else if (isFirst && GUI.Button(rect2, "开始", black))
{
isFirst = false;
action.setGameState(GameState.ROUND_START);
}
if(!isFirst && action.getGameState() == GameState.ROUND_FINISH && GUI.Button(rect2, "下一关卡", black))
{
action.setGameState(GameState.ROUND_START);
}
}
}