亚洲一久久久久久久久,国产免费天天看高清影视在线,精品人妻伦九区久久aaa片,性荡视频播放在线视频

【當(dāng)前熱聞】【Unity3D】魔方

來(lái)源: 博客園2023-06-18 20:15:50
  1 需求實(shí)現(xiàn)

? 繪制魔方 中基于OpenGL ES 實(shí)現(xiàn)了魔方的繪制,實(shí)現(xiàn)較復(fù)雜,本文基于 Unity3D 實(shí)現(xiàn)了 2 ~ 10 階魔方的整體旋轉(zhuǎn)和局部旋轉(zhuǎn)。

? 本文完整代碼資源見→基于 Unity3D 的 2 ~ 10 階魔方實(shí)現(xiàn)。下載資源后,進(jìn)入【Build/Windows】目錄,打開【魔方.exe】文件即可體驗(yàn)產(chǎn)品。

? 詳細(xì)需求如下:


(資料圖片僅供參考)

? 1)魔方渲染模塊

用戶選擇魔方階數(shù),渲染指定階數(shù)的魔方;

? 2)魔方整體控制模塊

用戶 Scroll 或 Ctrl + Scroll,控制魔方放大和縮??;用戶 Drag 空白處(或右鍵 Drag),控制魔方整體連續(xù)旋轉(zhuǎn);用戶點(diǎn)擊翻面按鈕(或方向鍵,或 Ctrl + Drag,或 Alt + Drag),控制魔方翻面;用戶點(diǎn)擊朝上的面按鈕,控制魔方指定面朝上;可以實(shí)時(shí)識(shí)別用戶視覺(jué)下魔方的正面、上面、右面;

? 3)魔方局部控制模塊

用戶點(diǎn)擊刷新按鈕,打亂魔方;用戶 Drag 魔方相鄰的兩個(gè)方塊,控制該層旋轉(zhuǎn),Drag 結(jié)束自動(dòng)對(duì)齊魔方(局部旋轉(zhuǎn));用戶輸入公式,提交后執(zhí)行公式旋轉(zhuǎn)對(duì)應(yīng)層;每次局部旋轉(zhuǎn)結(jié)束,檢驗(yàn)?zāi)Х绞欠衿闯桑羝闯?,彈出通關(guān)提示;

? 4)魔方動(dòng)畫模塊

魔方翻面動(dòng)畫;魔方指定面朝上動(dòng)畫;魔方打亂動(dòng)畫;魔方局部旋轉(zhuǎn)對(duì)齊動(dòng)畫;公式控制魔方旋轉(zhuǎn)動(dòng)畫;通關(guān)彈窗動(dòng)畫(漸變+縮放+平移);撤銷和逆撤銷動(dòng)畫;整體旋轉(zhuǎn)和局部旋轉(zhuǎn)動(dòng)畫互不干擾,可以并行;

? 5)魔方撤銷和逆撤銷模塊

Drag 魔方連續(xù)整體旋轉(zhuǎn)支持撤銷和逆撤銷;魔方翻面支持撤銷和逆撤銷;魔方指定面朝上支持撤銷和逆撤銷;魔方局部旋轉(zhuǎn)支持撤銷和逆撤銷;公式控制魔方旋轉(zhuǎn)支持撤銷和逆撤銷(撤銷整個(gè)公式,而不是其中的一步);

? 6)其他模塊

用戶點(diǎn)擊返回按鈕,可以返回到選擇階數(shù)界面;用戶每進(jìn)行一次局部旋轉(zhuǎn),記步加 1,公式每走一步,記步加1 ;顯示計(jì)時(shí)器;用戶點(diǎn)擊開始 / 暫停按鈕,可以控制計(jì)時(shí)器運(yùn)行 / 暫停,暫停時(shí),只能整體旋轉(zhuǎn),不能局部旋轉(zhuǎn);用戶異常操作,彈出 Toast 提示(主要是公式輸入合法性校驗(yàn));

? 選擇階數(shù)界面如下:

? 魔方界面如下:

2 相關(guān)技術(shù)棧MonoBehaviour的生命周期Transform組件人機(jī)交互Input場(chǎng)景切換、全屏/恢復(fù)切換、退出游戲、截屏燈光組件Light碰撞體組件Collider發(fā)射(Raycast)物理射線(Ray)相機(jī)縮放、平移、旋轉(zhuǎn)場(chǎng)景UGUI概述UGUI之TextUGUI之Image和RawImageUGUI之ButtonUGUI之InputFieldUGUI回調(diào)函數(shù)UGUI之布局組件協(xié)同程序空間和變換3 原理介紹3.1 魔方編碼

? 為方便計(jì)算,需要對(duì)魔方的軸、層序、小立方體、方塊、旋轉(zhuǎn)層進(jìn)行編碼,編碼規(guī)則如下(假設(shè)魔方階數(shù)為 n):

軸:x、y、z 軸分別編碼為 0、1、2,x、y、z 軸分別指向 right、up、forward(由魔方的正面指向背面,左手坐標(biāo)系);層序:每個(gè)軸向,由負(fù)方向到正方向分別編碼為 0 ~ (n-1);小立方體:使用僅包含 3 個(gè)元素的一維數(shù)組 loc 標(biāo)記,loc[axis] 表示該小立方體在 axis 軸下的層序;方塊:紅、橙、綠、藍(lán)、粉、黃、黑色方塊分別編碼為:0、1、2、3、4、5、-1;旋轉(zhuǎn)層:旋轉(zhuǎn)層由旋轉(zhuǎn)軸 (axis) 和層序 (seq) 決定。3.2 渲染原理

? 在 Hierarchy 窗口新建一個(gè)空對(duì)象,重命名為 Cube,在 Cube 下創(chuàng)建 6 個(gè) Quad 對(duì)象,分別重命名為 0 (x = -0.5)、1 (x = 0.5)、2 (y = -0.5)、3 (y = 0.5)、4 (z = -0.5)、5 (z = 0.5) (方塊的命名標(biāo)識(shí)了魔方所屬的面,在魔方還原檢測(cè)中會(huì)用到),調(diào)整位置和旋轉(zhuǎn)角度,使得它們圍成一個(gè)小立方體,將 Cube 拖拽到 Assets 窗口作為預(yù)設(shè)體。

? 在創(chuàng)建一個(gè) n 階魔方時(shí),新建一個(gè)空對(duì)象,重命名為 Rubik,復(fù)制 n^3 個(gè) Cube 作為 Rubik 的子對(duì)象,調(diào)整所有 Cube 的位置使其拼成魔方結(jié)構(gòu),根據(jù)立方體和方塊位置,為每個(gè)方塊設(shè)置紋理圖片,如下:

? 說(shuō)明:對(duì)于任意小方塊 Square,Square.forward 始終指向小立方體中心,該結(jié)論在旋轉(zhuǎn)層檢測(cè)中會(huì)用到;Inside.png 為魔方內(nèi)部色塊,用粉紅色塊代替白色塊是為了凸顯白色線框。

? 每個(gè)小立方體的貼圖代碼如下:

? Cube.cs

private void GetTextures(){ // 獲取紋理textures = new Texture[COUNT];for (int i = 0; i < COUNT; i++){textures[i] = RubikRes.INSET_TEXTURE;squares[i].name = "-1";}for(int i = 0; i < COUNT; i++){int axis = i / 2;        // loc為小立方體的位置序號(hào)(以魔方的左下后為坐標(biāo)原點(diǎn), 向右、向上、向前分別為x軸、y軸、z軸, 小立方體的邊長(zhǎng)為單位刻度)if (loc[axis] == 0 && i % 2 == 0 || loc[axis] == Rubik.Info().order - 1 && i % 2 == 1){textures[i] = RubikRes.TEXTURES[i];squares[i].name = i.ToString();}squares[i].GetComponent().material.mainTexture = textures[i];}}
3.3 整體旋轉(zhuǎn)原理

? 通過(guò)調(diào)整相機(jī)前進(jìn)和后退,控制魔方放大和縮?。煌ㄟ^(guò)調(diào)整相機(jī)的位置和姿態(tài),使得相機(jī)繞魔方旋轉(zhuǎn),實(shí)現(xiàn)魔方整體旋轉(zhuǎn)。詳情見縮放、平移、旋轉(zhuǎn)場(chǎng)景。

? 使用相機(jī)繞魔方旋轉(zhuǎn)以實(shí)現(xiàn)魔方整體旋轉(zhuǎn)的好處主要有:

整體旋轉(zhuǎn)和局部旋轉(zhuǎn)可以獨(dú)立執(zhí)行,互不干擾,方便實(shí)現(xiàn)整體旋轉(zhuǎn)和局部旋轉(zhuǎn)的動(dòng)畫并行;魔方的姿態(tài)始終固定,其 x、y、z 軸始終與世界坐標(biāo)系的 x、y、z 軸平行,便于后續(xù)計(jì)算,不用進(jìn)行一系列的投影計(jì)算,也節(jié)省了性能;整體旋轉(zhuǎn)的誤差不會(huì)對(duì)局部旋轉(zhuǎn)造成影響,不會(huì)影響魔方結(jié)構(gòu),不會(huì)出現(xiàn)魔方崩塌問(wèn)題。3.4 用戶視覺(jué)下魔方坐標(biāo)軸檢測(cè)原理

? 用戶翻面、選擇朝上的面等整體旋轉(zhuǎn)操作,會(huì)改變魔方的正面、右面、上面(即魔方朝上的面不一定是藍(lán)色面、朝右的面不一定是橙色面、朝前的面不一定是粉色面),用戶視覺(jué)下魔方的 x、y、z 軸也會(huì)發(fā)生變化。假設(shè)魔方的 x、y、z 軸正方向單位向量為 ox、oy、oz,用戶視覺(jué)下魔方的 x、y、z 軸正方向單位向量為 ux、uy、uz,相機(jī)的 right、up、forward 軸正方向單位向量分別為 cx、cy、cz,則 ux、uy、uz 的取值滿足以下關(guān)系:

? 相關(guān)代碼如下:

? AxisUtils.cs

using UnityEngine;/* * 坐標(biāo)軸工具類 * 坐標(biāo)軸相關(guān)計(jì)算 */public class AxisUtils{    private static Vector3[] worldAxis = new Vector3[] { Vector3.right, Vector3.up, Vector3.forward }; // 世界坐標(biāo)軸    public static Vector3 Axis(int axis)    { // 獲取axis軸向量        return worldAxis[axis];    }    public static Vector3 NextAxis(int axis)    { // 獲取axis的下一個(gè)軸向量        return worldAxis[(axis + 1) % 3];    }    public static Vector3 Axis(Transform trans, int axis)    { // 獲取trans的axis軸向量        if (axis == 0)        {            return trans.right;        }        else if (axis == 1)        {            return trans.up;        }        return trans.forward;    }    public static Vector3 NextAxis(Transform trans, int axis)    { // 獲取trans的axis下一個(gè)軸向量        return Axis(trans, (axis + 1) % 3);    }    public static Vector3 FaceAxis(int face)    { // 獲取face面對(duì)應(yīng)的軸向量        Vector3 vec = worldAxis[face / 2];        if (face % 2 == 0)        {            vec = -vec;        }        return vec;    }    public static Vector3 GetXAxis()    { // 獲取與相機(jī)right軸夾角最小的世界坐標(biāo)軸        return GetXAxis(Camera.main.transform.right);    }    public static Vector3 GetYAxis()    { // 獲取與相機(jī)up軸夾角最小的世界坐標(biāo)軸        return GetYAxis(Camera.main.transform.up);    }    public static Vector3 GetZAxis()    { // 獲取與相機(jī)forward軸夾角最小的世界坐標(biāo)軸        return GetZAxis(Camera.main.transform.forward);    }    public static Vector3 GetXAxis(Vector3 right)    { // 獲取與right向量夾角最小的世界坐標(biāo)軸        int x = GetZAxisIndex(right);        Vector3 xAxis = worldAxis[x];        if (Vector3.Dot(worldAxis[x], right) < 0)        {            xAxis = -xAxis;        }        return xAxis;    }    public static Vector3 GetYAxis(Vector3 up)    { // 獲取與up向量軸夾角最小的世界坐標(biāo)軸        int y = GetZAxisIndex(up);        Vector3 yAxis = worldAxis[y];        if (Vector3.Dot(worldAxis[y], up) < 0)        {            yAxis = -yAxis;        }        return yAxis;    }    public static Vector3 GetZAxis(Vector3 forward)    { // 獲取與forward向量夾角最小的世界坐標(biāo)軸        int z = GetZAxisIndex(forward);        Vector3 zAxis = worldAxis[z];        if (Vector3.Dot(worldAxis[z], forward) < 0)        {            zAxis = -zAxis;        }        return zAxis;    }    public static int GetAxis(int flag)    { // 根據(jù)flag值, 獲取與相機(jī)坐標(biāo)軸較近的軸        if (flag == 0)        {            return GetXAxisIndex(Camera.main.transform.right);        }        if (flag == 1)        {            return GetXAxisIndex(Camera.main.transform.up);        }        if (flag == 2)        {            return GetXAxisIndex(Camera.main.transform.forward);        }        return -1;    }    private static int GetXAxisIndex(Vector3 right)    { // 獲取與right向量夾角最小的世界坐標(biāo)軸索引        float[] dot = new float[3];        for (int i = 0; i < 3; i++)        { // 計(jì)算世界坐標(biāo)系的坐標(biāo)軸在相機(jī)right軸上的投影            dot[i] = Mathf.Abs(Vector3.Dot(worldAxis[i], right));        }        int x = 0;        if (dot[x] < dot[1])        {            x = 1;        }        if (dot[x] < dot[2])        {            x = 2;        }        return x;    }    private static int GetYAxisIndex(Vector3 up)    { // 獲取與up向量軸夾角最小的世界坐標(biāo)軸索引        float[] dot = new float[3];        for (int i = 0; i < 3; i++)        { // 計(jì)算世界坐標(biāo)系的坐標(biāo)軸在相機(jī)up軸上的投影            dot[i] = Mathf.Abs(Vector3.Dot(worldAxis[i], up));        }        int y = 1;        if (dot[y] < dot[2])        {            y = 2;        }        if (dot[y] < dot[0])        {            y = 0;        }        return y;    }    private static int GetZAxisIndex(Vector3 forward)    { // 獲取與forward向量夾角最小的世界坐標(biāo)軸索引        float[] dot = new float[3];        for (int i = 0; i < 3; i++)        { // 計(jì)算世界坐標(biāo)系的坐標(biāo)軸在相機(jī)forward軸上的投影            dot[i] = Mathf.Abs(Vector3.Dot(worldAxis[i], forward));        }        int z = 2;        if (dot[z] < dot[0])        {            z = 0;        }        if (dot[z] < dot[1])        {            z = 1;        }        return z;    }}
3.5 選擇朝上的面原理

? 首先生成 24 個(gè)視覺(jué)方向(6 個(gè)面,每個(gè)面 4 個(gè)視覺(jué)方向),如下(不同顏色的線條代表該顏色的面對(duì)應(yīng)的 4 個(gè)視覺(jué)方向),記錄相機(jī)在這些視覺(jué)方向下的 forward 和 right 向量,分別記為:forwardViews、rightViews(數(shù)據(jù)類型:Vector3[6][4])。

? 當(dāng)選擇 face 面朝上時(shí),需要在 forwardViews[face] 的 4 個(gè)向量中尋找與相機(jī)的 forward 夾角最小的向量,記該向量的索引為 index,旋轉(zhuǎn)相機(jī),使其 forward 和 right 分別指向 forwardViews[face][index]、rightViews[face][index]。

3.6 旋轉(zhuǎn)層檢測(cè)原理

? 1)旋轉(zhuǎn)軸檢測(cè)

? 假設(shè)屏幕射線檢測(cè)到的兩個(gè)相鄰方塊分別為 square1、square2。

如果 square1 與 square2 在同一個(gè)小立方體里,square1.forward 與 square2.forward 叉乘的向量就是旋轉(zhuǎn)軸方向向量;如果 square1 與 square2 在相鄰小立方體里,square1.forward 與 (square2.position - square1.position) 叉乘的向量就是旋轉(zhuǎn)軸方向向量;

? 假設(shè)叉乘后的向量的單位向量為 crossDir,我們將 crossDir 與 3 個(gè)坐標(biāo)軸的單位方向向量進(jìn)行點(diǎn)乘(記為 project),如果 Abs(project) > 0.99(夾角小于 8°),就選取該軸作為旋轉(zhuǎn)軸,如果每個(gè)軸的點(diǎn)乘絕對(duì)值結(jié)果都小于 0.99,說(shuō)明屏幕射線拾取的兩個(gè)方塊不在同一旋轉(zhuǎn)層,舍棄局部旋轉(zhuǎn)。補(bǔ)充:project 在 3)中會(huì)再次用到。

? 2)層序檢測(cè)

? 坐標(biāo)分量與層序的映射關(guān)系如下,其中 order 為魔方階數(shù),seq 為層序,pos 為坐標(biāo)分量,cubeSide 為小立方體的邊長(zhǎng)。由于頻繁使用到 pos 與 seq 的映射,建議將 0 ~ (order-1) 層的層序 seq 對(duì)應(yīng)的 pos 存儲(chǔ)在數(shù)組中,方便快速查找。

? square1 與 square2 在旋轉(zhuǎn)軸方向上的坐標(biāo)分量一致,假設(shè)為 pos(如果旋轉(zhuǎn)軸是 axis,pos = square1.position[axis]),由上述公式就可以推導(dǎo)出層序 seq。

? 3)拖拽正方向

? 拖拽正方向用于確定局部旋轉(zhuǎn)的方向,計(jì)算如下,project 是 1)中計(jì)算的點(diǎn)乘值。

? SquareUtils.cs

private static Vector2 GetDragDire(Transform square1, Transform square2, int project){ // 獲取局部旋轉(zhuǎn)拖拽正方向的單位方向向量Vector2 scrPos1 = Camera.main.WorldToScreenPoint(square1.position);Vector2 scrPos2 = Camera.main.WorldToScreenPoint(square2.position);Vector2 dire = (scrPos2 - scrPos1).normalized;return -dire * Mathf.Sign(project);}
3.7 局部旋轉(zhuǎn)原理

? 1)待旋轉(zhuǎn)的小立方體檢測(cè)

? 對(duì)于每個(gè)小立方體,使用數(shù)組 loc[] 存儲(chǔ)了小立方體在 x、y、z 軸方向上的層序,每次旋轉(zhuǎn)結(jié)束后,根據(jù)小立方體的中心坐標(biāo)可以重寫計(jì)算出 loc 數(shù)組(3.6 節(jié)中公式)。

? 假設(shè)檢測(cè)到的旋轉(zhuǎn)軸為 axis,旋轉(zhuǎn)層為 seq,所有 loc[axis] 等于 seq 的小立方體都是需要旋轉(zhuǎn)的小立方體。

? 2)局部旋轉(zhuǎn)

? 在 Rubik 對(duì)象下創(chuàng)建一個(gè)空對(duì)象,重命名為 RotateLayer,將 RotateLayer 移至坐標(biāo)原點(diǎn),旋轉(zhuǎn)角度全部置 0。

? 將處于旋轉(zhuǎn)層的小立方體的 parent 都設(shè)置為 RotateLayer,對(duì) RotateLayer 進(jìn)行旋轉(zhuǎn),旋轉(zhuǎn)結(jié)束后,將這些小立方體的 parent 重置為 Rubik,RotateLayer 的旋轉(zhuǎn)角度重置為 0,根據(jù)小立方體中心的 position 更新 loc 數(shù)組。

3.8 還原檢測(cè)原理

? 對(duì)于魔方的每個(gè)面,通過(guò)屏幕射線射向每個(gè) Square 的中心,獲取檢測(cè)到的 Square 的 name,如果存在兩個(gè) Square 的 name 不一樣,則魔方未還原,否則繼續(xù)檢測(cè)下一個(gè)面,如果每個(gè)面都還原了,則魔方已還原。

? SuccessDetector.cs

public void Detect(){ // 檢測(cè)魔方是否已還原for (int i = 0; i < squareRays.squareRays.Length - 1; i++){ // 檢測(cè)每個(gè)面(只需檢查5個(gè)面)string name = GetSquareName(i, 0);for (int j = 1; j < squareRays.squareRays[i].Length; j++){ // 檢測(cè)每個(gè)方塊if (!name.Equals(GetSquareName(i, j))){return;}}}Success();}private string GetSquareName(int face, int index){ // 獲取方塊名if (Physics.Raycast(squareRays.squareRays[face][index], out hitInfo)){return hitInfo.transform.name;}return "-1";}

? 說(shuō)明:squareRays 里存儲(chǔ)了每個(gè)方塊對(duì)應(yīng)的射線,這些射線由方塊的外部垂直指向方塊中心。

4 運(yùn)行效果

? 1)2 ~ 10 階魔方渲染效果

? 2)魔方打亂動(dòng)畫

? 說(shuō)明:在打亂的過(guò)程中可以縮放和整體旋轉(zhuǎn),體現(xiàn)了局部控制和整體控制相互獨(dú)立,互不干擾。

? 3)按鈕翻面動(dòng)畫

? 4)Ctrl + Drag 翻面動(dòng)畫

? 5)選擇朝上的面動(dòng)畫

? 6)局部旋轉(zhuǎn)動(dòng)畫

? 7)公式控制局部旋轉(zhuǎn)動(dòng)畫

? 說(shuō)明:在公式執(zhí)行過(guò)程中,不影響魔方的整體旋轉(zhuǎn)和縮放。

? 8)通關(guān)動(dòng)畫

? 聲明:本文轉(zhuǎn)自【Unity3D】魔方

關(guān)鍵詞:

責(zé)任編輯:sdnew003

相關(guān)新聞

版權(quán)與免責(zé)聲明:

1 本網(wǎng)注明“來(lái)源:×××”(非商業(yè)周刊網(wǎng))的作品,均轉(zhuǎn)載自其它媒體,轉(zhuǎn)載目的在于傳遞更多信息,并不代表本網(wǎng)贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本網(wǎng)不承擔(dān)此類稿件侵權(quán)行為的連帶責(zé)任。

2 在本網(wǎng)的新聞頁(yè)面或BBS上進(jìn)行跟帖或發(fā)表言論者,文責(zé)自負(fù)。

3 相關(guān)信息并未經(jīng)過(guò)本網(wǎng)站證實(shí),不對(duì)您構(gòu)成任何投資建議,據(jù)此操作,風(fēng)險(xiǎn)自擔(dān)。

4 如涉及作品內(nèi)容、版權(quán)等其它問(wèn)題,請(qǐng)?jiān)?0日內(nèi)同本網(wǎng)聯(lián)系。

熱文排行
  • 財(cái)經(jīng)
  • 綜合
  • 黃金360

男女性杂交内射妇女bbwxz| 日韩欧美aⅴ综合网站发布| 日韩精品无码中文字幕一区二区| 一夲道无码人妻精品一区二区| 亚洲va中文字幕无码一二三区| 国产日韩精品一区二区三区在线| 无码国产一区二区三区四区| 亚洲综合在线一区二区三区| 日韩国产人妻一区二区三区| 色综合色天天久久婷婷基地| 国语对白做受xxxxx在线中国 | 波多野吉衣av无码| 亚洲av无码专区在线播放中文| 久久精品国产清自在天天线| 亚洲精品一区二区另类图片| 亚洲av日韩av永久无码久久| 久久成人麻豆午夜电影| 国产va免费精品高清在线| 久久久国产精品免费a片3d| 国产精品中文久久久久久久| 少妇愉情理伦片丰满丰满| 免费观看全黄做爰大片| 五月婷婷俺也去开心| 国产一区二区内射最近更新| 亚洲婷婷五月综合狠狠爱| 国产精品二区一区二区aⅴ污介绍| 欧美午夜精品久久久久免费视| 国产精品视频二区不卡| 东北寡妇特级毛片免费| 国产精品视频永久免费播放| 国产成a人亚洲精品无码久久网 | 国产美女精品视频线免费播放软件| 亚洲国产精品久久精品| 日韩加勒比一本无码精品| 国产精品亚洲片在线| 精品国产免费人成网站| 国产精品久久久一区二区三区 | 亚洲国产精华液网站w| 久久精品国产免费观看三人同眠 | 午夜理论片yy6080私人影院 | 中国年轻丰满女人毛茸茸|