1. 程式人生 > >Unity3D 從入門到放棄(五)----射箭遊戲

Unity3D 從入門到放棄(五)----射箭遊戲

Unity3D 從入門到放棄(五)

—-射箭遊戲

填坑啊填坑,每週都補上週作業,啥時候才能到頭啊= =

作業需求

遊戲規則:
設計一個射箭小遊戲,點選滑鼠射箭:
 靶物件為 5 環,按環計分;
 箭物件,射中後要插在靶上;
 遊戲僅一輪,無限 trials;

例項化部分

本次作業有箭,靶兩種例項,實現如下:

  1. 箭:
    箭分為三個部分,箭頭,箭身,箭尾。
    箭頭:通過一個圓錐和一個圓來實現。圓可以由高度為0的圓錐生成。箭頭加上Mesh碰撞器並設定為觸發器。(圓錐可以通過Editor資料夾中的CreateCone中創造的選單實現)
    箭身:通過一個圓柱來實現。
    箭尾:通過兩個雙面四邊形實現,由於unity3d預設渲染單面,因此要四個。(四邊形可以通過Editor資料夾中的CreateRectangle中創造的選單實現)
    為了新增重力,箭總體加入一個剛體。
    最後,將製作好的箭加入預設,作為初始物體。

  2. 靶:
    製作六個圓柱體,分別上不同顏色,並將內圈scale.Y的大小稍微調大一點以顯示顏色,調整好角度,加上Mesh碰撞器即可。

  3. 文字框:
    通過UI中的Text建立文字框,此處不再闡述。

效果如圖:

箭:

箭構造

這裡寫圖片描述

這裡寫圖片描述

靶:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

打地基部分:

由於這次用的圓錐和四邊形都不是unity3d自帶的結構,因此需要自己創造。

首先,先建立一個Editor資料夾:(unity3d預設在Editor資料夾中的內容不能掛載,用於實現一些自動呼叫的指令碼)

這裡寫圖片描述

然後,在資料夾內新增CreateCone和CreateRectangle指令碼:

這裡寫圖片描述

指令碼內容如下:

CreateCone:

/*
 * 描 述:用於建立圓錐的檔案,我只是大自然的搬運工
 * 作 者:hza 
 * 建立時間:2017/04/06 13:20:14
 * 版 本:v 1.0
 */

using UnityEngine;
using UnityEditor;
using System.Collections;

public class CreateCone : ScriptableWizard
{
    /**
     * Num Vertices is the number of vertices each end will have.
     * Radius Top is the radius at the top. The center point will be located at (0/0/0).
     * Radius Bottom is the radius at the bottom. The center point will be located at (0/0/Length).(底圓的半徑大小)
     * Length is the number of world units long the plane will be (+Z direction).
     * Opening Angle If this is >0, the top radius is set to 0, and the bottom radius is computed depending on the length, so that the given opening angle is created.(貌似是底圓是多少邊形,即越大越接近圓形)
     * Outside defines whether the outside is visible (default).
     * Inside defines whether the inside is visible. Set both outside and inside to create a double-sided primitive.
     * Add Collider creates a matching mesh collider for the cone if checked.
     */
public int numVertices = 10; public float radiusTop = 0f; public float radiusBottom = 1f; public float length = 1f; public float openingAngle = 0f; // if >0, create a cone with this angle by setting radiusTop to 0, and adjust radiusBottom according to length; public bool outside = true; public bool inside = false; public bool addCollider = false; [MenuItem("GameObject/Create Other/Cone")] static void CreateWizard() { ScriptableWizard.DisplayWizard("Create Cone", typeof(CreateCone)); } void OnWizardCreate() { GameObject newCone = new GameObject("Cone"); if (openingAngle > 0 && openingAngle < 180) { radiusTop = 0; radiusBottom = length * Mathf.Tan(openingAngle * Mathf.Deg2Rad / 2); } string meshName = newCone.name + numVertices + "v" + radiusTop + "t" + radiusBottom + "b" + length + "l" + length + (outside ? "o" : "") + (inside ? "i" : ""); string meshPrefabPath = "Assets/Editor/" + meshName + ".asset"; Mesh mesh = (Mesh)AssetDatabase.LoadAssetAtPath(meshPrefabPath, typeof(Mesh)); if (mesh == null) { mesh = new Mesh(); mesh.name = meshName; // can't access Camera.current //newCone.transform.position = Camera.current.transform.position + Camera.current.transform.forward * 5.0f; int multiplier = (outside ? 1 : 0) + (inside ? 1 : 0); int offset = (outside && inside ? 2 * numVertices : 0); Vector3[] vertices = new Vector3[2 * multiplier * numVertices]; // 0..n-1: top, n..2n-1: bottom Vector3[] normals = new Vector3[2 * multiplier * numVertices]; Vector2[] uvs = new Vector2[2 * multiplier * numVertices]; int[] tris; float slope = Mathf.Atan((radiusBottom - radiusTop) / length); // (rad difference)/height float slopeSin = Mathf.Sin(slope); float slopeCos = Mathf.Cos(slope); int i; for (i = 0; i < numVertices; i++) { float angle = 2 * Mathf.PI * i / numVertices; float angleSin = Mathf.Sin(angle); float angleCos = Mathf.Cos(angle); float angleHalf = 2 * Mathf.PI * (i + 0.5f) / numVertices; // for degenerated normals at cone tips float angleHalfSin = Mathf.Sin(angleHalf); float angleHalfCos = Mathf.Cos(angleHalf); vertices[i] = new Vector3(radiusTop * angleCos, radiusTop * angleSin, 0); vertices[i + numVertices] = new Vector3(radiusBottom * angleCos, radiusBottom * angleSin, length); if (radiusTop == 0) normals[i] = new Vector3(angleHalfCos * slopeCos, angleHalfSin * slopeCos, -slopeSin); else normals[i] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin); if (radiusBottom == 0) normals[i + numVertices] = new Vector3(angleHalfCos * slopeCos, angleHalfSin * slopeCos, -slopeSin); else normals[i + numVertices] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin); uvs[i] = new Vector2(1.0f * i / numVertices, 1); uvs[i + numVertices] = new Vector2(1.0f * i / numVertices, 0); if (outside && inside) { // vertices and uvs are identical on inside and outside, so just copy vertices[i + 2 * numVertices] = vertices[i]; vertices[i + 3 * numVertices] = vertices[i + numVertices]; uvs[i + 2 * numVertices] = uvs[i]; uvs[i + 3 * numVertices] = uvs[i + numVertices]; } if (inside) { // invert normals normals[i + offset] = -normals[i]; normals[i + numVertices + offset] = -normals[i + numVertices]; } } mesh.vertices = vertices; mesh.normals = normals; mesh.uv = uvs; // create triangles // here we need to take care of point order, depending on inside and outside int cnt = 0; if (radiusTop == 0) { // top cone tris = new int[numVertices * 3 * multiplier]; if (outside) for (i = 0; i < numVertices; i++) { tris[cnt++] = i + numVertices; tris[cnt++] = i; if (i == numVertices - 1) tris[cnt++] = numVertices; else tris[cnt++] = i + 1 + numVertices; } if (inside) for (i = offset; i < numVertices + offset; i++) { tris[cnt++] = i; tris[cnt++] = i + numVertices; if (i == numVertices - 1 + offset) tris[cnt++] = numVertices + offset; else tris[cnt++] = i + 1 + numVertices; } } else if (radiusBottom == 0) { // bottom cone tris = new int[numVertices * 3 * multiplier]; if (outside) for (i = 0; i < numVertices; i++) { tris[cnt++] = i; if (i == numVertices - 1) tris[cnt++] = 0; else tris[cnt++] = i + 1; tris[cnt++] = i + numVertices; } if (inside) for (i = offset; i < numVertices + offset; i++) { if (i == numVertices - 1 + offset) tris[cnt++] = offset; else tris[cnt++] = i + 1; tris[cnt++] = i; tris[cnt++] = i + numVertices; } } else { // truncated cone tris = new int[numVertices * 6 * multiplier]; if (outside) for (i = 0; i < numVertices; i++) { int ip1 = i + 1; if (ip1 == numVertices) ip1 = 0; tris[cnt++] = i; tris[cnt++] = ip1; tris[cnt++] = i + numVertices; tris[cnt++] = ip1 + numVertices; tris[cnt++] = i + numVertices; tris[cnt++] = ip1; } if (inside) for (i = offset; i < numVertices + offset; i++) { int ip1 = i + 1; if (ip1 == numVertices + offset) ip1 = offset; tris[cnt++] = ip1; tris[cnt++] = i; tris[cnt++] = i + numVertices; tris[cnt++] = i + numVertices; tris[cnt++] = ip1 + numVertices; tris[cnt++] = ip1; } } mesh.triangles = tris; AssetDatabase.CreateAsset(mesh, meshPrefabPath); AssetDatabase.SaveAssets(); } MeshFilter mf = newCone.AddComponent<MeshFilter>(); mf.mesh = mesh; newCone.AddComponent<MeshRenderer>(); if (addCollider) { MeshCollider mc = newCone.AddComponent<MeshCollider>(); mc.sharedMesh = mf.sharedMesh; } Selection.activeObject = newCone; } }

CreateRectangle:

/*
 * 描 述:用於建立四邊形的檔案,我只是大自然的搬運工
 * 作 者:hza 
 * 建立時間:2017/04/06 13:50:10
 * 版 本:v 1.0
 */

using UnityEngine;
using UnityEditor;
using System.Collections;


public class CreateRectangle : ScriptableWizard
{
    /* 屬性含義:
     * width: 四邊形寬度(x軸)
     * length: 四邊形高度(z軸)
     * angle: 兩邊的夾角,預設為90°[0, 180]
     * addCollider: 是否需要新增碰撞器,預設為false
     */
    public float width = 1;
    public float length = 1;
    public float angle = 90;
    public bool addCollider = false;

    [MenuItem("GameObject/Create Other/Rectangle")]
    static void CreateWizard()
    {
        ScriptableWizard.DisplayWizard("Create Rectangle", typeof(CreateRectangle));
    }

    void OnWizardCreate()
    {
        GameObject newRectangle = new GameObject("Rectangle");

        // 儲存mash的名字和路徑
        string meshName = newRectangle.name + "w" + width + "l" + length + "a" + angle;
        string meshPrefabPath = "Assets/Editor/" + meshName + ".asset";

        // 對角度進行處理
        while (angle > 180) angle -= 180;
        while (angle < 0) angle += 180;

        angle *= Mathf.PI / 180;

        /* 1. 頂點,三角形,法線,uv座標, 絕對必要的部分只有頂點和三角形。 
         * 如果模型中不需要場景中的光照,那麼就不需要法線。
         * 如果模型不需要貼材質,那麼就不需要UV 
         */
        Vector3[] vertices = new Vector3[4];
        Vector3[] normals = new Vector3[4];
        Vector2[] uv = new Vector2[4];

        vertices[0] = new Vector3(0, 0, 0);
        uv[0] = new Vector2(0, 0);
        normals[0] = Vector3.up;

        vertices[1] = new Vector3(0, 0, length);
        uv[1] = new Vector2(0, 1);
        normals[1] = Vector3.up;


        vertices[2] = new Vector3(width * Mathf.Sin(angle), 0, length + width * Mathf.Cos(angle));
        uv[2] = new Vector2(1, 1);
        normals[2] = Vector3.up;

        vertices[3] = new Vector3(width * Mathf.Sin(angle), 0, width * Mathf.Cos(angle));
        uv[3] = new Vector2(1, 0);
        normals[3] = Vector3.up;

        /* 2. 三角形,頂點索引: 
         * 三角形是由3個整數確定的,各個整數就是角的頂點的index。
         * 各個三角形的頂點的順序通常由下往上數, 可以是順時針也可以是逆時針,這通常取決於我們從哪個方向看三角形。
         * 通常,當mesh渲染時,"逆時針" 的面會被擋掉。 我們希望保證順時針的面與法線的主向一致 
         */
        int[] indices = new int[6];
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;

        indices[3] = 0;
        indices[4] = 2;
        indices[5] = 3;

        Mesh mesh = new Mesh();
        mesh.name = meshName;
        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.uv = uv;
        mesh.triangles = indices;
        mesh.RecalculateBounds();
        AssetDatabase.CreateAsset(mesh, meshPrefabPath);
        AssetDatabase.SaveAssets();

        // 新增MeshFilter
        MeshFilter filter = newRectangle.gameObject.AddComponent<MeshFilter>();
        if (filter != null)
        {
            filter.sharedMesh = mesh;
        }

        // 新增MeshRendered
        MeshRenderer meshRender = newRectangle.gameObject.AddComponent<MeshRenderer>();
        Shader shader = Shader.Find("Standard");
        meshRender.sharedMaterial = new Material(shader);

        // 如果願意新增碰撞器,則新增碰撞器
        if (addCollider)
        {
            MeshCollider mc = newRectangle.AddComponent<MeshCollider>();
            mc.sharedMesh = filter.sharedMesh;
        }

        Selection.activeObject = newRectangle;
    }
}

指令碼放進去後,可以在Hierarchy裡的create找到createother這個選項了,然後就可以自己創造圓錐和四邊形啦。

邏輯部分

老規矩,先放UML圖:

這裡寫圖片描述

(和上次做業大體邏輯一樣,僅僅是多了幾個掛載指令碼)

程式碼如下:

BaseAction:

/*
 * 描 述:基類動作檔案
 * 作 者:hza 
 * 建立時間:2017/04/07 17:39:08
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Tem.Action
{
    public enum SSActionEventType : int { STARTED, COMPLETED }

    public interface ISSActionCallback
    {
        void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED,
            int intParam = 0, string strParam = null, Object objParam = null);
    }

    public class SSAction : ScriptableObject // 動作的基類
    {
        public bool enable = true;
        public bool destory = false;

        public GameObject gameObject { get; set; }
        public Transform transform { get; set; }
        public ISSActionCallback callback { get; set; }

        public virtual void Start()
        {
            throw new System.NotImplementedException("Action Start Error!");
        }

        public virtual void FixedUpdate()
        {
            throw new System.NotImplementedException("Physics Action Start Error!");
        }

        public virtual void Update()
        {
            throw new System.NotImplementedException("Action Update Error!");
        }
    }

    public class CCMoveToAction : SSAction
    {
        public Vector3 target;
        public float speed;

        public static CCMoveToAction GetSSAction(Vector3 _target, float _speed)
        {
            CCMoveToAction currentAction = ScriptableObject.CreateInstance<CCMoveToAction>();
            currentAction.target = _target;
            currentAction.speed = _speed;
            return currentAction;
        }

        public override void Start()
        {

        }

        public override void Update()
        {
            this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
            if (this.transform.position == target)
            {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }
    }

    public class CCBezierMoveAction : SSAction // 僅用於三次貝塞爾曲線
    {
        public List<Vector3> vectors;
        public Vector3 target;
        public float speed;

        private Vector3 begin;
        private float t;
        private bool firstTime;

        /*private Vector3 vector2begin;
        private Vector3 vector2mid;
        private Vector3 vector2end;
        private Vector3 vector3begin;
        private Vector3 vector3end;
        private float timeBegin;
        private float timeDiff;*/

        public static CCBezierMoveAction GetCCBezierMoveAction(List<Vector3> _vectors, float _speed)
            // vector裡面最後一個值是目標位置
        {
            CCBezierMoveAction action = ScriptableObject.CreateInstance<CCBezierMoveAction>();
            action.vectors = _vectors;
            action.target = _vectors[_vectors.Count - 1];
            action.vectors.RemoveAt(action.vectors.Count - 1);
            action.speed = _speed;
            return action;
        }


        public override void Start() // 公式寫法
        {
            //timeDiff = 0;
            firstTime = true;
            t = 0;
        }

        public override void Update()
        {
            if (firstTime)
            {
                speed = speed / Vector3.Distance(this.transform.position, target) / 1.5f;
                // 速度除以相對距離併除以2作為速度比
                begin = this.transform.position;
                firstTime = false;
            }
            t += Time.deltaTime * speed;
            if (t > 1) t = 1;
            float _t = 1 - t;
            this.transform.position = begin * Mathf.Pow(_t, 3) + 3 * vectors[0] * Mathf.Pow(_t, 2) * t
                + 3 * vectors[1] * _t * Mathf.Pow(t, 2) + target * Mathf.Pow(t, 3);
            if (this.transform.position == target)
            {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }

        /*public override void Update() // 正常寫法
        {
            timeDiff += Time.deltaTime;

            vector2begin = Vector3.Lerp(this.transform.position, vectors[0], speed * timeDiff);
            vector2mid = Vector3.Lerp(vectors[0], vectors[1], speed * timeDiff);
            vector2end = Vector3.Lerp(vectors[1], target, speed * timeDiff);
            // 第一次計算差值
            vector3begin = Vector3.Lerp(vector2begin, vector2mid, speed * timeDiff);
            vector3end = Vector3.Lerp(vector2mid, vector2end, speed * timeDiff);
            // 第二次計算差值
            this.transform.position = Vector3.Lerp(vector3begin, vector3end, speed * timeDiff);
            // 最後一次計算差值
            if (this.transform.position == target)
            {
                this.destory = true;
                this.callback.SSEventAction(this);
            }
        }*/
    }

    public class CCSequenceAction : SSAction, ISSActionCallback
    {
        public List<SSAction> sequence;
        public int repeat = -1;
        public int start = 0;

        public static CCSequenceAction GetSSAction(List<SSAction> _sequence, int _start = 0, int _repead = 1)
        {
            CCSequenceAction actions = ScriptableObject.CreateInstance<CCSequenceAction>();
            actions.sequence = _sequence;
            actions.start = _start;
            actions.repeat = _repead;
            return actions;
        }

        public override void Start()
        {
            foreach (SSAction ac in sequence)
            {
                ac.gameObject = this.gameObject;
                ac.transform = this.transform;
                ac.callback = this;
                ac.Start();
            }
        }

        public override void Update()
        {
            if (sequence.Count == 0) return;
            if (start < sequence.Count) sequence[start].Update();
        }

        public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED,
            int intParam = 0, string strParam = null, Object objParam = null) //通過對callback函式的呼叫執行下個動作
        {
            source.destory = false; // 當前動作不能銷燬(有可能執行下一次)
            this.start++;
            if (this.start >= this.sequence.Count)
            {
                this.start = 0;
                if (this.repeat > 0) repeat--;
                if (this.repeat == 0)
                {
                    this.destory = true;
                    this.callback.SSEventAction(this);
                }
            }
        }

        private void OnDestroy()
        {
            this.destory = true;
        }
    }

    public class SSActionManager : MonoBehaviour
    {
        private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>();
        private List<SSAction> watingAddAction = new List<SSAction>();
        private List<int> watingDelete = new List<int>();

        protected void Start()
        {

        }

        protected void Update()
        {
            foreach (SSAction ac in watingAddAction) dictionary[ac.GetInstanceID()] = ac;
            watingAddAction.Clear();
            // 將待加入動作加入dictionary執行

            foreach (KeyValuePair<int, SSAction> dic in dictionary)
            {
                SSAction ac = dic.Value;
                if (ac.destory) watingDelete.Add(ac.GetInstanceID());
                else if (ac.enable) ac.Update();
            }
            // 如果要刪除,加入要刪除的list,否則更新

            foreach (int id in watingDelete)
            {
                SSAction ac = dictionary[id];
                dictionary.Remove(id);
                DestroyObject(ac);
            }
            watingDelete.Clear();
            // 將deletelist中的動作刪除
        }

        public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback)
        {
            action.gameObject = gameObject;
            action.transform = gameObject.transform;
            action.callback = callback;
            watingAddAction.Add(action);
            action.Start();
        }
    }

    public class PYActionManager : MonoBehaviour
    {
        private Dictionary<int, SSAction> dictionary = new Dictionary<int, SSAction>();
        private List<SSAction> watingAddAction = new List<SSAction>();
        private List<int> watingDelete = new List<int>();

        protected void Start()
        {

        }

        protected void FixedUpdate()
        {
            foreach (SSAction ac in watingAddAction) dictionary[ac.GetInstanceID()] = ac;
            watingAddAction.Clear();
            // 將待加入動作加入dictionary執行

            foreach (KeyValuePair<int, SSAction> dic in dictionary)
            {
                SSAction ac = dic.Value;
                if (ac.destory) watingDelete.Add(ac.GetInstanceID());
                else if (ac.enable) ac.FixedUpdate();
            }
            // 如果要刪除,加入要刪除的list,否則更新

            foreach (int id in watingDelete)
            {
                SSAction ac = dictionary[id];
                dictionary.Remove(id);
                DestroyObject(ac);
            }
            watingDelete.Clear();
            // 將deletelist中的動作刪除
        }

        public void runAction(GameObject gameObject, SSAction action, ISSActionCallback callback)
        {
            action.gameObject = gameObject;
            action.transform = gameObject.transform;
            action.callback = callback;
            watingAddAction.Add(action);
            action.Start();
        }
    }
}

BaseCode:

/*
 * 描 述:設定遊戲基本資訊
 * 作 者:hza 
 * 建立時間:2017/04/07 20:29:47
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BaseCode : MonoBehaviour
{
    public string GameName;
    public string GameRule;

    void Start()
    {
        GameName = "Shoot";
        GameRule = "左鍵點選射箭,射中靶子加分,靶心從內到外分別為60-10分,點選clear清除所有的箭";
    }
}

ChangeCamera:

/*
 * 描 述:射中後,攝像頭拉近,持續兩秒
 * 作 者:hza 
 * 建立時間:2017/04/07 14:46:35
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ChangeCamera : MonoBehaviour {

    public GameObject mainCamera;
    public GameObject closeCamera;
    // 引用兩個攝像機

    bool active;
    float beginTime;

    private void Start()
    {
        active = false;
    }

    // Update is called once per frame
    void Update () {
        if (!active) return;

        beginTime -= Time.deltaTime;
        if (beginTime < 0)
        {
            resetCount();
        }
    }

    // 顯示近身攝像機,持續2秒
    public void ShowCloseCamera()
    {
        mainCamera.SetActive(false);
        closeCamera.SetActive(true);
        // 顯示close攝像機

        runCount();
    }

    void runCount()
    {
        active = true;
        beginTime = 2f;
        // 啟用計數按鈕
    }

    void resetCount()
    {
        active = false;
        mainCamera.SetActive(true);
        closeCamera.SetActive(false);
    }
}

MouseRotate:

/*
 * 描 述:控制攝像機隨滑鼠移動的指令碼,掛載在攝像機上即可
 * 作 者:hza 
 * 建立時間:2017/04/07 17:01:57
 * 版 本:v 1.0
 */

using UnityEngine;
using System.Collections;
using System.Collections.Generic;


//[AddComponentMenu("Camera-Control/Mouse Look")]
public class MouseRotate : MonoBehaviour {
    public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 }
    public RotationAxes axes = RotationAxes.MouseXAndY;
    // 滑鼠移動方式
    public float sensitivityX = 15F;
    public float sensitivityY = 15F;
    // 靈敏度

    public float minimumX = -360F;
    public float maximumX = 360F;
    // X最大偏移

    public float minimumY = -60F;
    public float maximumY = 60F;
    // Y最大偏移

    public bool isVisible = true;
    // 滑鼠是否可見

    float rotationY = 0F;
    float rotationX = 0F;

    void Start()
    {
        // Make the rigid body not change rotation  
        if (GetComponent<Rigidbody>())
            GetComponent<Rigidbody>().freezeRotation = true;
    }

    void Update()
    {
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = isVisible;
        // 滑鼠保持在螢幕中間

        if (axes == RotationAxes.MouseXAndY)
        {
            rotationX += Input.GetAxis("Mouse X") * sensitivityX;
            rotationX = Mathf.Clamp(rotationX, minimumX, maximumX);

            rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
            rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);

            this.transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0);
        }
        else if (axes == RotationAxes.MouseX)
        {
            rotationX += Input.GetAxis("Mouse X") * sensitivityX;
            rotationX = Mathf.Clamp(rotationX, minimumX, maximumX);

            this.transform.localEulerAngles = new Vector3(0, rotationX, 0);
        }
        else
        {
            rotationY += Input.GetAxis("Mouse Y") * sensitivityY;
            rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);

            this.transform.localEulerAngles = new Vector3(-rotationY, 0, 0);
        }
    }
}

RowActionManager:

/*
 * 描 述:用於控制箭發射的動作類
 * 作 者:hza 
 * 建立時間:2017/04/07 18:23:30
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Tem.Action;
using UnityEngine.UI;

public class RowAction : SSAction
{
    private Vector3 beginV;

    public static RowAction GetRowAction(Vector3 beginV)
    {
        RowAction currentAction = ScriptableObject.CreateInstance<RowAction>();
        currentAction.beginV = beginV;
        return currentAction;
    }

    public override void Start()
    {
        this.gameObject.GetComponent<Rigidbody>().velocity = beginV;
        // 設定初始速度
    }

    public override void FixedUpdate()
    {
        // 如果掉下去,返回
        if (this.transform.position.y < -2)
        {
            this.destory = true;
            this.callback.SSEventAction(this);
            // 進行回撥操作
        }
    }
}


public class RowActionManager : PYActionManager, ISSActionCallback {

    public GameObject cam;
    public GameObject target;
    public Text scoretext;

    private SceneController scene;
    // 控制該動作的場景

    // Use this for initialization
    new void Start () {
        scene = Singleton<SceneController>.Instance;
    }

    // Update is called once per frame
    new void FixedUpdate () {
        base.FixedUpdate();
        if (cam.activeSelf && Input.GetMouseButtonDown(0))
            // 左鍵點選
        {
            RowFactory fac = Singleton<RowFactory>.Instance;
            GameObject row = fac.setObjectOnPos(cam.transform.position, cam.transform.localRotation);
            if (row.GetComponent<Rigidbody>() == null) row.AddComponent<Rigidbody>();
            row.transform.FindChild("RowHead").gameObject.SetActive(true);
            Debug.Log(row.transform.FindChild("RowHead").gameObject.activeSelf);
            Transform head = row.transform.FindChild("RowHead").FindChild("OutCone");
            if (head.gameObject.GetComponent<RowHeadTrigger>() == null)
            {
                head.gameObject.AddComponent<RowHeadTrigger>();
                head.gameObject.GetComponent<RowHeadTrigger>().Target = target;
                head.gameObject.GetComponent<RowHeadTrigger>().scoretext = scoretext;
            }
            // 得到物體,如果未新增指令碼就設定指令碼,並設定active
            RowAction action = RowAction.GetRowAction(cam.transform.forward * 30);
            // 得到動作
            this.runAction(row, action, this);
        }
    }

    // 回撥函式
    public void SSEventAction(SSAction source, SSActionEventType events = SSActionEventType.COMPLETED,
        int intParam = 0, string strParam = null, Object objParam = null) 
    {
        RowFactory fac = Singleton<RowFactory>.Instance;
        fac.freeObject(source.gameObject);
    }
}

RowFactory:

/*
 * 描 述:用於載入Row的工廠
 * 作 者:hza 
 * 建立時間:2017/04/07 17:31:37
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RowFactory : MonoBehaviour
{

    private static List<GameObject> used = new List<GameObject>();
    // 正在使用的物件連結串列
    private static List<GameObject> free = new List<GameObject>();
    // 正在空閒的物件連結串列
    // 此函式表示將Target物體放到一個位置
    public GameObject setObjectOnPos(Vector3 targetposition, Quaternion faceposition)
    {
        if (free.Count == 0)
        {
            GameObject aGameObject = Instantiate(Resources.Load("prefabs/Row")
                , targetposition, faceposition) as GameObject;
            // 新建例項,將位置設定成為targetposition,將面向方向設定成faceposition
            used.Add(aGameObject);
        }
        else
        {
            used.Add(free[0]);
            free.RemoveAt(0);
            used[used.Count - 1].SetActive(true);
            used[used.Count - 1].transform.position = targetposition;
            used[used.Count - 1].transform.localRotation = faceposition;
        }
        return used[used.Count - 1];
    }

    public void freeObject(GameObject oj)
    {
        oj.SetActive(false);
        used.Remove(oj);
        free.Add(oj);
    }

    public void freeAllObject()
    {
        if (used.Count == 0) return;
        for (int i = 0; i < used.Count; i++)
        {
            used[i].SetActive(false);
            free.Add(used[i]);
        }
        used.Clear();
        Debug.Log(used.Count);
        Debug.Log(free.Count);
        // 清除used裡面所有物體
    }
}

RowHeadTrigger:

/*
 * 描 述:放在rowhead物體上,寫觸發函式
 * 作 者:hza 
 * 建立時間:2017/04/07 13:57:17
 * 版 本:v 1.0
 */

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class RowHeadTrigger : MonoBehaviour