Unity3D学习(7)之物理引擎的应用与代码复用

       Unity3D的物理引擎做得很不错,让物体的运动更加贴近现实了。没有人喜欢高深的数学,去计算复杂的运动曲线和力学。使用物理引擎,而你仅需要高中的物理(牛顿!三定律, F = m * a)。
物理引擎( Physics Engine
  1.        物理引擎是一种软件组件,用于仿真物理世界运动。 这种仿真包括刚体力学、流体力学以及碰撞检测  
  2.         物理引擎通过为刚性物体赋予真实的物理属性,在外部力的作用下,计算运动、旋转和碰撞。

       如何利用物理引擎呢?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看,这是传送门

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值