作业要求
- 改进飞碟(Hit UFO)游戏:
- 游戏内容要求:
- 按 adapter模式 设计图修改飞碟游戏
- 使它同时支持物理运动与运动学(变换)运动
初始飞碟版本
在上一次作业中,游戏的UML图如下
关于Action的说明:
ISSActionCallback
为动作接口SSAction
为动作父类,规定所有Action的属性和方法DiskAction
继承自SSAction,规定了一个Disk的动作,动作即飞碟的移动,由两个参数决定:受力角度和受力大小。构造函数初始化初始速度,Update函数模拟物体的坐标移动过程,当disk在画面之外(通过坐标判定),就callback通知动作做完。SequenceAction
函数不用改动,由于Disk是直线运动也用不到。SSActionManager
函数。动作管理器,管理Action List中的每个Action(不用关心是单一Action还是连续Action)。FirstActionManager
。这个函数封装了Action的相关类的操作,使得FirstController可以简单地调用FirstActionManager中的函数。
在之前的设计中,FirstActionManager
继承SSActionManager
,实现如下:
public class FirstActionManager : SSActionManager
{
...
public FirstController scene_controller; //当前场景的场景控制器
protected void Start()
{
...
}
//飞碟飞行
public void diskFly(GameObject disk, float angle, float power)
{
...
}
}
新设计
根据要求设计后的UML如下:
重新设计之后,FirstActionManager
中的成员变量和函数放入SSActionManager
中,同时实现物理引擎的类PhysicActionManager
,它们实现同一个接口IActionManager
,通过这个接口FirstController
能够以固定的格式调用动作/物理引擎,即完成Adapter模式。
其实更严谨的设计是在SSActionManager
和IActionManager
、PhysicActionManager
和IActionManager
之间再加一层专门的Adapter来解决diskFly
函数不一样的问题(如果事先没有沟通的两个人分别实现两个部分,就可能出现这个问题),比如函数名、参数顺序、参数个数等不一样。但是在这里SSActionManager
和PhysicActionManager
里面的diskFly
就是按照函数接口实现的,所以省去了这一步。
下面是函数的介绍:
IActionManager接口
统一SSActionManager和PhysicActionManager
namespace myGame
{
public interface IActionManager
{
void diskFly(GameObject disk, float angle, float power);
}
}
PhysicActionManager
新增加的PhysicActionManager
采用Rigidbody组件的方法实现Disk的运动,而不是通过坐标变换的方法,所以这个类其实与SSAction和ISSActionCallback接口并没有什么关系。
在这个类中使用的是FixedUpdate:
- FixedUpdate
在固定的时间间隔内执行此方法,不受游戏帧率的影响,所以处理Rigidbody的时候最好使用FixedUpdate。 - Update
在每一帧的时候调用,不同设备渲染帧的时间不同,所以每次执行Update的时候相隔的时间是不一定的
使用Rigidbody实现运动:
- 由于想让飞碟的飞行方向不是固定向下的,所以令useGravity = false
- diskFly传进来的angle(float)是用于位移变化的,由于飞行方向本来就是随机的,所以为了方便,不使用angle,重新ramdom
- 飞碟飞行速度不同,用Rigidbody的属性drag实现,power越大,阻力越小,速度越快。但是这个drag的数值很难把控,因为它会让disk运动一段时间后停止。比如下面这样:
使用Rigidbody物理引擎的游戏效果
public class PhysicActionManager : MonoBehaviour, IActionManager
{
public DiskFlyAction fly; //飞碟飞行的动作
public FirstController scene_controller; //当前场景的场景控制器
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //将执行的动作的字典集合
private List<SSAction> waitingAdd = new List<SSAction>(); //等待去执行的动作列表
private List<int> waitingDelete = new List<int>(); //等待删除的动作的key
protected void Start()
{
scene_controller = (FirstController)Director.GetInstance().currentSceneController;
scene_controller.actionManager = this;
}
protected void FixedUpdate()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null)
{
}
//飞碟飞行
public void diskFly(GameObject disk, float angle, float power)
{
disk.GetComponent<Rigidbody>().velocity = new Vector3(Random.Range(-10, 10), Random.Range(-10, 10), Random.Range(-10, 10));
disk.GetComponent<Rigidbody>().useGravity = false;
disk.GetComponent<Rigidbody>().drag = (30 - power) / 20; //round越大,power越大,阻力越小,速度越快
}
}
SSActionManager
和上次作业的实现一样。不用封装多一个类。
public class SSActionManager : MonoBehaviour, ISSActionCallback, IActionManager
{
public DiskFlyAction fly; //飞碟飞行的动作
public FirstController scene_controller; //当前场景的场景控制器
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //将执行的动作的字典集合
private List<SSAction> waitingAdd = new List<SSAction>(); //等待去执行的动作列表
private List<int> waitingDelete = new List<int>(); //等待删除的动作的key
protected void Start()
{
scene_controller = (FirstController)Director.GetInstance().currentSceneController;
scene_controller.actionManager = this;
}
protected void Update()
{
foreach (SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac;
}
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
waitingDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
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 SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null)
{
}
//飞碟飞行
public void diskFly(GameObject disk, float angle, float power)
{
fly = DiskFlyAction.GetSSAction(angle, power); //disk.GetComponent<Disk>().direction, angle, power);
this.RunAction(disk, fly, this);
}
}
FirstController
原来SSActionManager的地方改成接口,然后实例化的时候解释是哪个类
public class FirstController : MonoBehaviour, ISceneController, UserAction
{
...
public IActionManager actionManager;
void Awake()
{
...
//如果是物理引擎则actionManager = gameObject.AddComponent<PhysicActionManager>() as IActionManager;
actionManager = gameObject.AddComponent<SSActionManager>() as IActionManager;
}
DiskFactory
生产Disk的时候加一个刚体
newDisk.AddComponent<Rigidbody>();
newDisk.GetComponent<Rigidbody>().useGravity = false;
其他函数和上次作业一样
github传送门