unity 简易游戏打飞碟V2
一、简介
在上一个游戏简易打飞碟的基础上,根据Adapter模式对场景控制器调用的接口作了如下图修改:
保留CCActionManager的同时增加了PhysicActionManager,它们实现了同一个接口IActionManager,分别表示运动学运动(不考虑刚体)与物理运动(考虑刚体)。
接下来对增加或作了修改的代码进行介绍。
二、代码
1. PhysicFlyAction
与CCFlyAction同样继承自SSAction,不过现在只需要一个水平方向的初速度,垂直方向因为有重力作用所以不用额外给它速度。
Update函数里可以看到两种运动的明显差别,CCFlyAction在Update函数中通过position.translate来改变物体的位置,而PhysicFlyAction什么都不用做,物体自身带有的物理属性(重力和初速度)会使它动起来。
另外需要注意的是做物理运动时要将isKinematic设为false,反之要设为true(这是CCFlyAction唯一需要修改的地方,在此顺带一提)。
public class PhysicFlyAction : SSAction
{
public float speedX;
public static PhysicFlyAction GetSSAction(float x) {
PhysicFlyAction action = ScriptableObject.CreateInstance<PhysicFlyAction>();
action.speedX = x;
return action;
}
// Start is called before the first frame update
public override void Start()
{
gameObject.GetComponent<Rigidbody>().isKinematic = false;
gameObject.GetComponent<Rigidbody>().velocity = new Vector3(speedX * 10, 0, 0);
gameObject.GetComponent<Rigidbody>().drag = 1;
}
// Update is called once per frame
public override void Update()
{
//Debug.Log("flyaction update");
if (this.transform.gameObject.activeSelf == false) {//飞碟已经被"销毁"
//Debug.Log("1");
this.destroy = true;
this.callback.SSActionEvent(this);
return;
}
Vector3 vec3 = Camera.main.WorldToScreenPoint (this.transform.position);
if (vec3.x < -100 || vec3.x > Camera.main.pixelWidth + 100 || vec3.y < -100 || vec3.y > Camera.main.pixelHeight + 100) {
//Debug.Log("2");
this.destroy = true;
this.callback.SSActionEvent(this);
return;
}
}
}
2. IActionManager
提供了一个最简单的接口给主场景控制器调用,原先主场景控制器调用的是SSActionManager即动作管理器基类中的RunAction函数,现在被Fly替代了。
另一个接口其实是版本一中SSActionManager的同名函数,用于返回当前回合剩余的飞碟数。因为现在主场景控制器只能使用IActionManager提供的接口,所以将其移动到这里。
public interface IActionManager
{
void Fly(GameObject disk);
int RemainActionCount() ;
}
3. PhysicActionManager
SSActionManager类没有变,CCActionManager和PhysicActionManager两个动作管理器依然继承自它,主要包含一些动作的管理。
另一方面,两个动作管理器又继承并实现了IActionManager接口。
鉴于前面版本中动作管理器已经实现了RunAction这个类似于接口但不是接口的虚函数,所以为了省事直接将RunAction嵌在Fly的里面。
可以这样做的原因是两个动作管理类都继承自基本动作管理类,本质上是同一样东西,本就可以用同一个接口。如果要更好地体现Adapter模式的话,最好有另一个不继承自SSActionManager的类同样实现Fly接口,但我们游戏中不需要罢了(道理懂就行)。
public class PhysicActionManager : SSActionManager, IActionCallback, IActionManager
{
public RoundController sceneController;
public PhysicFlyAction action;
public DiskFactory factory;
// Start is called before the first frame update
protected new void Start()
{
sceneController = (RoundController)SSDirector.getInstance().currentSceneController;
sceneController.actionManager = this as IActionManager;
factory = Singleton<DiskFactory>.Instance;
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null) {
factory.FreeDisk(source.transform.gameObject);
}
public override void MoveDisk(GameObject disk) {
action = PhysicFlyAction.GetSSAction(disk.GetComponent<DiskAttributes>().speedX);
RunAction(disk, action, this);
}
public void Fly(GameObject disk) {
MoveDisk(disk);
}
public int RemainActionCount() {
return actions.Count;
}
}
4. UserGUI
增加了一个成员变量isKinematic表示当前是否为运动学运动。
以及一个按钮提供给用户用于设置isKinematic。
public bool isKinematic;
if (GUI.Button(new Rect(Screen.width / 2 - menu_width * 0.9f, 0, menu_width * 1.8f, menu_height), "Kinematic/Not Kinematic")) {
isKinematic = !isKinematic;
}
5. RoundController
第一个改变自然是actionManager的类型由SSActionManager变为IActionManager。
Update时根据UserGUI中的isKinematic来设置actionManager。
由飞碟工厂得到飞碟后用actionManager中的Fly函数使其运动。
public class RoundController : MonoBehaviour, ISceneController, IUserAction
{
int round = 0;
int max_round = 5;
float timer = 0.5f;
GameObject disk;
DiskFactory factory ;
public IActionManager actionManager;
public ScoreController scoreController;
public UserGUI userGUI;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (userGUI.mode == 0) return;
if (userGUI.isKinematic == false) {
actionManager = gameObject.GetComponent<PhysicActionManager>() as IActionManager;
}
else {
actionManager = gameObject.GetComponent<CCActionManager>() as IActionManager;
}
GetHit();
gameOver();
if (round > max_round) {
return;
}
timer -= Time.deltaTime;
if (timer <= 0 && actionManager.RemainActionCount() == 0) {
//if (timer <= 0) {
//从工厂中得到10个飞碟,为其加上动作
for (int i = 0; i < 10; ++i) {
disk = factory.GetDisk(round);
actionManager.Fly(disk);
//Thread.Sleep(100);
}
round += 1;
if (round <= max_round) {
userGUI.round = round;
}
timer = 4.0f;
}
}
void Awake() {
SSDirector director = SSDirector.getInstance();
director.currentSceneController = this;
director.currentSceneController.LoadSource();
gameObject.AddComponent<UserGUI>();
gameObject.AddComponent<PhysicActionManager>();
gameObject.AddComponent<CCActionManager>();
gameObject.AddComponent<ScoreController>();
gameObject.AddComponent<DiskFactory>();
factory = Singleton<DiskFactory>.Instance;
userGUI = gameObject.GetComponent<UserGUI>();
}
public void LoadSource()
{
}
public void gameOver()
{
if (round > max_round && actionManager.RemainActionCount() == 0)
//if (round > max_round)
userGUI.gameMessage = "Game Over!";
}
public void GetHit() {
if (Input.GetButtonDown("Fire1")) {
Camera ca = Camera.main;
Ray ray = ca.ScreenPointToRay(Input.mousePosition);
//Return the ray's hit
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
scoreController.Record(hit.transform.gameObject);
hit.transform.gameObject.SetActive(false);
}
}
}
}
三、效果
游戏进行时点击屏幕中间按钮可以切换物体的运动模式(作用到下一个round)
可以看出不同运动模式下的明显差别,物理模式下飞碟会相互碰撞。