Unity3D的物理引擎做得很不错,让物体的运动更加贴近现实了。没有人喜欢高深的数学,去计算复杂的运动曲线和力学。使用物理引擎,而你仅需要高中的物理(牛顿!三定律, F = m * a)。
UserGUI.cs
Factory.cs
Sington.cs
Recorder.cs
SSAction.cs
SSActionManager.cs
DiskData2.cs
我们真正需要设计的只是动作和动作管理器的派生类,再看看我们的需求
物理引擎(
Physics Engine
)
- 物理引擎是一种软件组件,用于仿真物理世界运动。 这种仿真包括刚体力学、流体力学以及碰撞检测。
- 物理引擎通过为刚性物体赋予真实的物理属性,在外部力的作用下,计算运动、旋转和碰撞。
如何利用物理引擎呢?Rigidbody(刚体)组件可使游戏对象在物理系统的控制下来运动 。这次我们利用物理引擎来改进飞碟游戏。请记住一点修改物理引擎相关组件属性,必须使用Fixed-Update。
首先为了提高代码的利用率和开发速度,我们先想一想我们可以复用代码。我们看看下上次飞碟游戏留下来的资源吧。
SSDirector,SceneController,UserGUI,Factory,Singleton,Recorder,SSAction,SSActionManager 和DiskData2这些脚本不用改直接实现共用即可。
SSDirector.cs
public interface ISceneController//导演场记通信接口
{
void LoadResources();
//void Pause ();
//void Resume ();
}
public class SSDirector : System.Object
{
private static SSDirector _instance;
public ISceneController currentScenceController { get; set; }
public bool running { get; set; }
public static SSDirector getInstance()
{
if (_instance == null)
{
_instance = new SSDirector();
}
return _instance;
}
public int getFPS()
{
return Application.targetFrameRate;
}
public void setFPS(int fps)
{
Application.targetFrameRate = fps;
}
}
SceneController.cs
public class SceneController : MonoBehaviour, ISceneController, IUserAction
{
public IActionManager actionManager { get; set; }//动作管理器
public DiskFactory factory { get; set; }//游戏工厂
public Recorder scoreRecorder { get; set; }//游戏记分员
public int round = 0;//轮数
public Text RoundText;//轮数文本
public Text GameText;//倒计时文本
public Text FinalText;//结束文本
public int game = 0;//记录游戏进行情况
public int num = 0;//每轮的飞碟数量
GameObject explosion;//爆炸效果
public int CoolTimes = 3; //准备时间
// Use this for initialization
void Awake()
//创建导演实例并载入资源
{
SSDirector director = SSDirector.getInstance();
director.setFPS(60);
director.currentScenceController = this;
director.currentScenceController.LoadResources();
}
void Start()
{
round = 1;//开始游戏设置为第一轮
}
// Update is called once per frame
void Update()
{
RoundText.text = "Round:" + round.ToString();//将轮数信息打印出来
if(game == 2)
{
GameOver();//判断游戏结束
}
}
public IEnumerator waitForOneSecond() //协程技术实现倒计时3秒
{
while (CoolTimes >= 0 && game == 3)
{
GameText.text = CoolTimes.ToString(); //将倒计时剩余时间打印在屏幕上
print("还剩" + CoolTimes);
yield return new WaitForSeconds(1);//等待一秒
CoolTimes--;
}
GameText.text = "";
game = 1; //标记游戏开始
}
public void GameOver()
{
FinalText.text = "Game Over!!!";//游戏结束显示
}
public void StartGame() //实现IUserAction接口开始游戏
{
num = 0;
if (game == 0)
{
game = 3; //进入倒计时模式
StartCoroutine(waitForOneSecond()); //启动协程
}
}
public void ReStart() //实现IUserAction接口重启游戏
{
SceneManager.LoadScene("task1");
game = 0;
}
public void ShowDetail() //实现IUserAction接口显示游戏介绍
{
GUI.Label(new Rect(220, 50, 350, 250), "Use your mouse click disk, you will get 1 point for green Disk,2 for yellow Disk,3 for red Disk,you should get 20 points to pass round1,40 to pass round2,60 to pass round3.There are three round.Good Luck!!!");
}
public void hit() //实现IUserAction接口判断击中事件
{
if (game == 1)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.transform.tag == "Disk")
{
explosion.transform.position = hit.collider.gameObject.transform.position;
explosion.GetComponent<Renderer>().material = hit.collider.gameObject.GetComponent<Renderer>().material;
explosion.GetComponent<ParticleSystem>().Play();
hit.collider.gameObject.SetActive(false);
print("Hit!!!");
hit.collider.gameObject.GetComponent<DiskData2>().hit = true;
scoreRecorder.add();
}
}
}
}
public void LoadResources() //载入资源
{
explosion = Instantiate(Resources.Load("prefabs/Explosion"), new Vector3(-40, 0, 0), Quaternion.identity) as GameObject;
Instantiate(Resources.Load("prefabs/Light"));
}
}
UserGUI.cs
public interface IUserAction
{
void StartGame();
void ShowDetail();
void ReStart();
void hit();
}
public class UserGUI : MonoBehaviour
{
private IUserAction action;
// Use this for initialization
void Start()
{
action = SSDirector.getInstance().currentScenceController as IUserAction;
}
void OnGUI()
{
GUIStyle fontstyle1 = new GUIStyle();
fontstyle1.fontSize = 50;
fontstyle1.normal.textColor = new Color(255, 255, 255);
if (GUI.RepeatButton(new Rect(0, 0, 120, 40), "Shooting Disk"))
{
action.ShowDetail();
}
if (GUI.Button(new Rect(0, 60, 120, 40), "STARTGAME"))
{
action.StartGame();
}
if (GUI.Button(new Rect(0, 120, 120, 40), "RESTART"))
{
action.ReStart();
}
if (Input.GetMouseButtonDown(0))
{
action.hit();
}
}
// Update is called once per frame
void Update()
{
//
}
}
Factory.cs
public class DiskFactory : MonoBehaviour
{
private static DiskFactory _instance;
public SceneController sceneControler { get; set; }//场记
public Recorder scoreRecorder;//计分员
GameObject diskPrefab;//飞碟模板
DiskData2 diskData;//飞碟数据
public List<GameObject> used;
public List<GameObject> free;
// Use this for initialization
private void Awake()
{
if (_instance == null)
{
_instance = Singleton<DiskFactory>.Instance;//单实例化
_instance.used = new List<GameObject>();
_instance.free = new List<GameObject>();
diskPrefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk"), new Vector3(40,0,0), Quaternion.identity);
}
}
public void Start()
{
sceneControler = (SceneController)SSDirector.getInstance().currentScenceController; //引入场记
sceneControler.factory = this;//设置场记的工厂
scoreRecorder = sceneControler.scoreRecorder;//引入记分员
}
public GameObject getDisk(int round) //取得飞碟
{
if (sceneControler.num == 31 && scoreRecorder.Score >= round * 20) //每轮31个飞碟,看分数是否达标决定进入下一轮或者结束游戏
{
sceneControler.round++;
sceneControler.num = 0;
}
else if(sceneControler.num == 31 && scoreRecorder.Score < round * 20)
{
sceneControler.game = 2;//游戏结束
}
GameObject newDisk;
if (free.Count == 0)
{
newDisk = GameObject.Instantiate(diskPrefab) as GameObject;
}
else
{
newDisk = free[0];
free.Remove(free[0]);
}
diskData = newDisk.GetComponent<DiskData2>(); //取出飞碟数据实现复用,提高效率
switch (round) //根据轮数选择飞碟
{
case 1:
diskData.size = 0.8f;
diskData.color = Color.green;
newDisk.transform.localScale = new Vector3(diskData.size, diskData.size, diskData.size);
newDisk.GetComponent<Renderer>().material.color = diskData.color;
break;
case 2:
diskData.size = 0.7f;
diskData.color = Color.yellow;
newDisk.transform.localScale = new Vector3(diskData.size, diskData.size, diskData.size);
newDisk.GetComponent<Renderer>().material.color = diskData.color;
break;
case 3:
diskData.size = 0.6f;
diskData.color = Color.red;
newDisk.transform.localScale = new Vector3(diskData.size, diskData.size, diskData.size);
newDisk.GetComponent<Renderer>().material.color = diskData.color;
break;
}
used.Add(newDisk);
return newDisk;
}
public void freeDisk(GameObject disk1) //释放飞碟
{
for (int i = 0; i < used.Count; i++)
{
if (used[i] == disk1)
{
used.Remove(disk1);
disk1.SetActive(true);
free.Add(disk1);
}
}
return;
}
}
Sington.cs
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour{
//场景单实例
protected static T instance;
public static T Instance
{
get
{
if(instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if(instance == null)
{
Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none.");
}
}
return instance;
}
}
}
Recorder.cs
public class Recorder : MonoBehaviour {
public Text ScoreText;//分数文本
public float Score = 0;//分数
public SceneController sceneControler { get; set; }//引入场记
// Use this for initialization
void Start () {
sceneControler = (SceneController)SSDirector.getInstance().currentScenceController;
sceneControler.scoreRecorder = this;//设置记分员
}
public void add() //加分
{
Score += sceneControler.round;
}
public void miss() //扣分
{
Score -= sceneControler.round;
}
// Update is called once per frame
void Update () {
ScoreText.text = "Score:" + Score.ToString();//更新分数
}
}
SSAction.cs
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback //动作管理器与动作通信接口
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null);
}
//动作基类
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
public virtual void FixedUpdate()
{
//throw new System.NotImplementedException();
}
}
SSActionManager.cs
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>();
// Use this for initialization
void Start()
{
}
// Update is called once per frame
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();
ac.FixedUpdate();
}
}
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();
}
}
DiskData2.cs
public class DiskData2 : MonoBehaviour {
public float size;
public Color color;
public float speed;
public bool hit = false;
public SSAction action;//记录动作
}
我们真正需要设计的只是动作和动作管理器的派生类,再看看我们的需求
首先我们编写IActionManager.cs
public interface IActionManager
{
void playDisk();
}
然后编写下Emit.cs
public class Emit : SSAction
{
bool enableEmit = true;//使力作用一次,不想产生变加速运动
Vector3 force;//力
float startX;//起始位置
public SceneController sceneControler = (SceneController)SSDirector.getInstance().currentScenceController;//引入场记
// Use this for initialization
public override void Start () {
startX = 6 - Random.value * 12;
this.transform.position = new Vector3(startX, 0, 0);
force = new Vector3(6 * Random.Range(-1, 1), 6 * Random.Range(0.5f, 2), 13 + 2 * sceneControler.round);//根据轮数设置力的大小
}
public static Emit GetSSAction()
{
Emit action = ScriptableObject.CreateInstance<Emit>();
return action;
}
public override void Update()
{
//
}
public void Destory()//回调函数
{
this.destroy = true;
this.callback.SSActionEvent(this);
}
// Update is called once per frame
public override void FixedUpdate () {
if(!this.destroy)
{
if(enableEmit)
{
gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero;
gameobject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
enableEmit = false;
}
}
}
}
最后编写Physics.cs
public class physicsManager : SSActionManager, ISSActionCallback, IActionManager
{
public SceneController sceneController;//场记
public DiskFactory diskFactory;//游戏工厂
public Recorder scoreRecorder;//记分员
public Emit EmitDisk;
public GameObject Disk;
int count = 0;
// Use this for initialization
protected void Start()
{
sceneController = (SceneController)SSDirector.getInstance().currentScenceController;
diskFactory = sceneController.factory;
scoreRecorder = sceneController.scoreRecorder;
sceneController.actionManager = this;//设置动作管理器
}
// Update is called once per frame
protected new void Update()
{
if (sceneController.round <= 3 && sceneController.game == 1)//游戏总轮数为3轮
{
count++;
if (count == 60)//每60帧发射一个飞碟
{
playDisk();
sceneController.num++;
//print(sceneController.num);
count = 0;
}
base.Update();
}
}
public void playDisk()
{
EmitDisk = Emit.GetSSAction();
Disk = diskFactory.getDisk(sceneController.round);
this.RunAction(Disk, EmitDisk, this);
Disk.GetComponent<DiskData2>().action = EmitDisk;
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null) //回调函数回收飞碟记录是否失分
{
if (!source.gameobject.GetComponent<DiskData2>().hit)
scoreRecorder.miss();
diskFactory.freeDisk(source.gameobject);
source.gameobject.GetComponent<DiskData2>().hit = false;
}
}
我们还需要确定何时回收飞碟,在背景板设置触发器。碰触到背景板即回收,将check.js挂在背景板上。
public class Check : MonoBehaviour {
public int num;
public Emit EmitDisk;
void OnTriggerEnter(Collider other)
{
Debug.Log(other.gameObject.tag);
if (other.gameObject.tag == "Disk")
{
EmitDisk = (Emit)other.gameObject.GetComponent<DiskData2>().action;
EmitDisk.Destory();
}
}
}
来个成品图压压惊
这是挂在主摄像机的脚本,代码复用的好处这时就体现出来了,如果我想改回原来的运动学控制的飞碟要怎么做呢?很简单勾上CCActionManager然后取消Physics Manager就行了
还有提醒下大家如果有在脚本里使用到其他脚本的东西,一定要注意脚本的执行顺序,否则会出现未初始化的错误,而且往往都是以为自己代码写错了。
有兴趣可以去我的GItHub看,这是传送门