Unity - PuzzleGame 8 (類金庫輪盤鎖)

  保險箱轉盤遊戲,基本上就是密碼解鎖而已,依據提示或是任何遊戲系統給的資訊,來左右旋轉轉盤到特定刻度,直到一串組合是符合打開的順序,就解決問題了。

  通常在旋轉的階段是非常簡單的,只要照著順序轉就好,因此遊戲通常會把密碼順序的提示給放在不同的地方,玩家需要拼湊出密碼的組合之後才能解開這個鎖,而尋找提示的過程又可以加入其他不同類型的謎題來增加阻礙。




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


  實作測試(WebGL build)  (這邊簡化遊戲,一開始就會把密碼的組合顯示在畫面上,依據L或R,來把轉盤往左或往右旋轉),滑鼠點住轉盤不放就可以開始旋轉,這邊因為用亂數決定的關係,所以有可能會遇到連續兩次都同方向同數值,就要往那個方向旋轉一圈再到同數值才行。






1、轉盤

  先製作一個金庫轉盤的圖片,這邊我直接從網路上擷取一張圖片來用,直接把轉盤放在場景上,建立一個空的物件來裝轉盤跟指標,這邊指標判斷的位置是在12點鐘的方向,所以在那邊加了一條紅色的線。



  接著來製作一個放在轉盤上的Component,這個Component用來判斷滑鼠是不是點到轉盤上(所以給這個class加上RequireComponent[typeof(CircleCollider2D)]的屬性),同時計算轉盤旋轉的角度,以及當滑鼠放開的時候告訴Board現在轉盤往哪個方向轉到多少刻度。

[RequireComponent(typeof(CircleCollider2D))] //因為轉盤是圓形的且需要判斷滑鼠的點擊,所以這邊加上一個Require
public class SafeDial : MonoBehaviour
{
    public System.Action dialInput; //做個簡單的轉盤輸入的delegate
    //每個區塊的夾角,這邊圖片的小刻度不看只使用上面的10個數字(所以360 / 10 = 36)
    //除非轉盤的數字數量會變動,因為這邊是固定的所以就就直接用了
    private int blockAngles = 36;
    private float totalRotate; //轉盤旋轉時總共轉的角度

    void OnMouseDown()
    {
        StartCoroutine(RotatingDial());
    }

    void OnMouseUp()
    {
        StopAllCoroutines();

        //滑鼠放開,檢查轉盤現在的角度,讓它往最接近的刻度移動
        int num = Mathf.RoundToInt(this.transform.localEulerAngles.z / blockAngles);
        Vector3 rotation = this.transform.localEulerAngles;
        rotation.z = num * blockAngles;
        float roundAngle = this.transform.localEulerAngles.z - rotation.z;
        this.transform.localEulerAngles = rotation;

        totalRotate = Mathf.RoundToInt(totalRotate + roundAngle);
        if (totalRotate == 0) //總旋轉的角度是0,沒有移動
            return;

        if(totalRotate > 360 || totalRotate < -360) {
            //轉超過一圈,看是照樣接受或是就當作本次不算,這邊就忽略
        } else {
            //把旋轉的方向跟刻度轉成字串送出
            dialInput.Invoke(((totalRotate > 0) ? "R" : "L") + Mathf.Abs((num % 10) * 10).ToString());
        }
    }

    private IEnumerator RotatingDial()
    {
        float preAngle = this.transform.localEulerAngles.z;
        Vector3 preMousePos = Input.mousePosition;
        totalRotate = 0;

        while (true)
        {
            //基本三角點
            Vector2 dialWorldPos = new Vector2(this.transform.position.x, this.transform.position.y);
            Vector2 mouseInitWorldPos = Camera.main.ScreenToWorldPoint(preMousePos);
            Vector2 mouseCurrWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);

            //計算前一tick跟此tick滑鼠位置的夾角
            float angle = Vector2.Angle(mouseInitWorldPos - dialWorldPos, mouseCurrWorldPos - dialWorldPos);
            Vector3 cross = Vector3.Cross(mouseInitWorldPos - dialWorldPos, mouseCurrWorldPos - dialWorldPos);
            if (cross.z > 0) angle = -angle;
            
            this.transform.localEulerAngles = new Vector3(0, 0, preAngle - angle); //調整圖片新的角度
            totalRotate += angle; //累積總旋轉的角度
            preMousePos = Input.mousePosition; //重設滑鼠位置紀錄
            preAngle = this.transform.localEulerAngles.z; //重設角度紀錄

            yield return null;
        }
    }
}

  這樣子這個Component就做完了,這邊暫時不需要放到物件上,因為我在Board初始化的時候會Add上去,那如果要預先手動把這個Component放到轉盤物件上的話,Board那邊的AddComponent就可以改掉,改為GetComponent。





2、板子

  開始來製作板子Component,主要遊戲的初始化,跟遊戲是否結束的判斷都在這邊運作,轉盤的輸入這邊我們就視它為一個連續數據,只看最後輸入的四次,來跟設定的密碼做比對。

public class Board : MonoBehaviour
{
    public GameObject dial; //轉盤物件
    public int passLength = 4; //密碼長度(轉動的次數)
    private string passCombination; //正確密碼組合的字串
    private string[] inputValues; //密碼盤輸入的紀錄
    private int inputIndex;

    void Start()
    {
        SafeDial safeDial = dial.AddComponent<SafeDial>(); //加上給轉盤用的Component
        safeDial.dialInput = DialInput; //設定回呼的委派
        InitGame();
    }

    //初始化遊戲所需的參數
    public void InitGame()
    {
        dial.GetComponent<CircleCollider2D>().enabled = true; //如果有重新遊戲的需求,在呼叫的時候啟動,避免因為遊戲結束時的關閉造成無法開下一場
        inputIndex = 0;
        inputValues = new string[passLength];
        passCombination = string.Empty;
        for (int i = 0; i < passLength; ++i)
        {
            //+往右轉 -往左轉
            //這邊是隨機決定左右,有可能出現都是左或都是右的情況,也可以改為不會有連續兩個同方向的
            int direction = (1 - (Random.Range(0, 2) * 2));
            int passValue = Random.Range(0, 10);
            digiTexts[i].text = (direction > 0 ? "R" : "L") + (passValue * 10).ToString();
            passCombination += (direction > 0 ? "R" : "L") + (passValue * 10).ToString();
        }
    }

    //轉盤輸入的數值,用string[]記錄下來每次的輸入
    private void DialInput(string value)
    {
        inputValues[inputIndex] = value;
        string result = "";
        for(int i = 0; i < passLength; ++i)
        {
            int index = (inputIndex + 1 + i) % passLength;
            result += inputValues[index];
        }
        inputIndex = (inputIndex + 1 + passLength) % passLength;

        //判斷答案是否正確,只要最後輸入4次的數值跟密碼相同,就遊戲結束
        if (result.Equals(passCombination))
        {
            Debug.Log("Game Over");
            //停止Collider避免讓滑鼠還可以點
            dial.GetComponent<CircleCollider2D>().enabled = false;
        }
    }
}





3、設定

  這邊在剛剛轉盤跟線段的父物件上掛上這個Board component,然後把轉盤的Sprite拉進去,設定好密碼長度,這邊用4段。



  基本上就完成了,不過這邊是看不到密碼的,所以可以把Board物件裡面的passCombination取出來用,不過這邊是一整串的並不好做為顯示,或是額外用個陣列來記錄,然後可以讓別的地方來讀取這個資訊,不然的話密碼沒有任何的提示這樣遊戲也沒辦法玩了。





  正常來說應該是每次轉的方向會跟前一次相反,如果以四次來說就是左右左右或右左右左,不過這邊不是真實的轉盤同時也只是測試而已,所以密碼方向跟刻度就用隨機組合了。就如同一開始說的,通常到了轉的時候就是很單純的轉動到特定刻度而已,所以有可能是把密碼的提示藏到別的地方,也有可能提示明顯但是有編碼過,玩家要經過解碼才知道正確的答案,當然還有更多增加麻煩的方式就是了。

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

No comments:

Post a Comment