Unity - PuzzleGame 7 (連動開關)

  連動開關,當某個按鈕按下去的時候同時會有數個其他的開關也會做切換,這邊做的是一種也容易看到的,當移動石柱(我這邊用石柱的圖片,也可以是其他東西)的時候,會有其他幾個石柱也會跟著移動,方向不一定相同,並且移動的單位也不同。

  場景中可能會有一條基準線或是目標線,遊戲的目的就是要讓所有的物件都到達目標線,另外有一種是並非所有物件都用同一條目標線,而是每個物件需求的高度都不同。



  製作使用的Unity版本為5.4.1 f1。


  實作測試(WebGL build)  (滑鼠點擊每個石柱的上下按鈕讓該石柱移動,這個時候有連動的石柱也會跟著移動,當所有石柱的頂端都位在紅色線段的時候,就遊戲結束)






1、開關

  先來製作一個開關的圖片,如果只是單純的開關只有兩種切換感覺有點單調,所以這邊改用Slider類型的,然後圖片使用了石柱的圖片來代替滑桿。

  把圖片去背匯入到Unity裡面,我這邊大致上排列了一下石柱跟背後的線段,順序的話看自己想怎麼排了,照順序只是比較方便記而已。

  所有的石柱都對齊某個基準線,這個線段就是作為玩家完成遊戲的判斷,這邊偷懶就全部都共用同一條線,並且石柱圖片的Y軸LocalPosition都在0的位置,之後也方便計算。


 




2、板子

  開始來製作板子Component,同樣這次也是讓所有的判斷都寫在這裡,UI點擊按鈕後呼叫裡面的移動方法,然後去移動特定的石柱圖片到正確的位置。


public class Board : MonoBehaviour
{
    public SpriteRenderer[] pillars; //石柱圖片
    public float speed = 2f; //石柱移動的速度
    private int[][] linkedPillars; //每個石柱跟其他石柱連動的參數
    private int[] pillarPositions; //石柱當前的位置
    private bool isMoving = false;

    void Start() {
        InitGame();
    }

    public void InitGame()
    {
        //初始化連動參數 (這邊是亂數決定每個石柱跟其他石柱的連動參數)
        //可以的話最好是預先決定好那些按鈕會跟那些連動,比較不會因為隨機造成每次難度不一
        int pillarCount = pillars.Length;
        pillarPositions = new int[pillarCount];
        linkedPillars = new int[pillarCount][];
        for(int y = 0; y < pillarCount; y++) {
            linkedPillars[y] = new int[pillarCount];
            for (int x = 0; x < pillarCount; x++) {
                linkedPillars[y][x] = Random.Range(-2, 3); //-2 ~ +2 步數
            }
        }

        //隨機設定版面初始狀態
        int randomMoves = Random.Range(5, 11); //隨機移動幾次
        for(int i = 0; i < randomMoves; ++i) {
            int randomPillarIndex = Random.Range(0, pillarCount); //隨機移動某個石柱
            int randomMoveDirection = Random.Range(-2, 3); //隨機移往某個方向
            CalculatePillarsPosition(randomPillarIndex, randomMoveDirection); //計算其他石柱的連動後的正確位置
        }
        //數值設定結束,設定石柱的圖片到正確的位置
        for(int i = 0; i < pillars.Length; ++i) {
            Vector3 pos = pillars[i].transform.localPosition;
            pos.y = pillarPositions[i] * 1.5f;
            pillars[i].transform.localPosition = pos;
        }
    }

    //給UI按鈕呼叫,把編號N的石柱往上移動一個單位
    public void MovePillarUp(int index)
    {
        if (isMoving) return;
        CalculatePillarsPosition(index, 1);
        StartCoroutine(MovingPillarCoroutine());
    }

    //給UI按鈕呼叫,把編號N的石柱往下移動一個單位
    public void MovePillarDown(int index)
    {
        if (isMoving) return;
        CalculatePillarsPosition(index, -1);
        StartCoroutine(MovingPillarCoroutine());
    }

    //移動目標石柱後,依據連動參數來計算所有其他石柱調整後的位置
    private void CalculatePillarsPosition(int pillarIndex, int direction)
    {
        int[] linkedSteps = linkedPillars[pillarIndex]; //取得這個石柱的連動參數
        for (int i = 0; i < linkedSteps.Length; ++i)
        {
            //如果i是主要移動的石柱,就移動direction的方向單位
            //如果是其他石柱,則計算連動,連動數值正值移動方向跟目前移動的pillar同方向,負值則是反方向
            int move = (i == pillarIndex) ? direction : (linkedSteps[i] > 0) ? Mathf.Abs(linkedSteps[i]) * direction : linkedSteps[i] * direction;
            pillarPositions[i] = Mathf.Clamp(pillarPositions[i] + move, -2, 2);
        }
    }

    private IEnumerator MovingPillarCoroutine()
    {
        //因為我所有pillar的Y軸localPosition都是0,而我每一格線段的距離是1.5個單位
        //因此就可以直接使用pillarPositions裡面的數值乘上1.5,就是那個pillar object應該要在的Y軸座標
        isMoving = true;
        while (true)
        {
            bool isFinish = true;
            for(int i = 0; i < pillars.Length; ++i)
            {
                //移動圖片到目標位置
                Vector3 endPos = new Vector3(pillars[i].transform.localPosition.x, pillarPositions[i] * 1.5f, pillars[i].transform.localPosition.z);
                pillars[i].transform.localPosition = Vector3.MoveTowards(pillars[i].transform.localPosition, endPos, 1.5f * speed * Time.deltaTime);
                if (Vector3.Distance(pillars[i].transform.localPosition, endPos) < 0.1f)
                {
                    pillars[i].transform.localPosition = endPos;
                }
                else
                {
                    isFinish = false; //只要有一個圖片還沒移動到定點就繼續跑
                }
            }
            if (isFinish) break;
            yield return null;
        }

        //因為目標紅線位置在pillarPositions裡面的數值是0,所以遊戲是不是結束檢查是否都是0就好了,因為有正負所以全部轉正後加總
        if (pillarPositions.Sum(x => Mathf.Abs(x)) == 0)
        {
            Debug.Log("Game over");
        }

        isMoving = false;
    }
}





3、參數設定

  這邊我使用int[][]來記錄每個石柱跟其他石柱的連動參數,因為整個開關群組在畫面中剛好是一整排,用這樣的方式也好記每個石柱在陣列中的位置。


  經過初始化後,int[0][]就是第一個石柱的連動參數,x的位置是他在他自己參數陣列中的位置,基本上在移動的時候會略過這個參數,並竟是要去連動其他的石柱的。

  接著再順便亂數設定參數linkedPillars[y][x] = Random.Range(-2, 3);,使用-2到+2的整數只是因為我這邊用了五條位置線,中間紅色的是0點,那麼上下就是各兩個單位了。

  移動石柱就呼叫MovePillarUp(index)跟MovePillarDown(index),傳送的index當然就是石柱的順序從左到右是0~4了,我這邊偷懶就直接在每個石柱的上下各放一個Button,去呼叫讓那個石柱往上或往下移動而已。







  最後把Board隨便放到一個物件上,然後把場景上的石柱圖片拉進到pillars[]裡面,這邊我也是一樣照順序從左到右1~5,到這邊就差不多完成了,接著記得製作一個UI介面並且配置好按鈕來呼叫MovePillarUp(index)跟MovePillarDown(index) ,同樣製作UI就不在這邊說明了。






  這種連動型的機關也滿有趣的,之後再來做一個變形的連動開關好了。有時候這種機關在遊戲中可能不會單獨出現,會和其他類型的謎題合併在一起來增加難度,這邊會跟那些連動是亂數設定參數,最好還是固定一種或幾種配置,同時遊戲開場石柱的位置也一樣固定一種或幾種版面,不然會不好掌控難易度。

  如果有任何錯誤歡迎提出。

No comments:

Post a Comment