目錄
PlaceManager.cs(放置管理類)
Ground.cs(地板類)?和?GroundData.cs(地板數據類)
額外知識點說明
1、MeshFilter和MeshRenderer的Bounds區別
?2、Gizmos 繪制一個平行于斜面的立方體
通過網盤分享的文件:PlaceGameDemo2.unitypackage
鏈接: https://pan.baidu.com/s/1Jobzy8JaDqnBmRofNk2-Mw?pwd=fpfm 提取碼: fpfm
PlaceManager.cs(放置管理類)
1、負責加載建筑數據表(BUILD_CONFIG_JSON_STR)json內容
? ? ? ? id:1,Build_A(立方體)、id:2,Build_B(球體)
2、構建世界World類對象(1000*1000*1000大小)以100*100*100的立方體為房間Room填充World空間。
? ? ? ? World由若干個Room組成,Room下可存放若干個處于Room范圍內的物體(Unit封裝)
? ? ? ? 每個Unit均是動態創建并放入相關的Room對象內,Unit會持有1個所屬Room列表(belongRoomList)。
? ? ? ? 每個Unit創建時會存入<unitId, Unit>字典(unitDict)方便獲取以及序列化使用
3、Update方法
? ? ? ? 3.1 控制物體交互(點擊物體開始拖拽、拖拽中、放下物體)
? ? ? ? 3.2?可視處理,從<unitId, Unit>字典遍歷所有已存在Unit,遍歷Unit的belongRoomList房間是否位于攝像機視椎體內(是否可見),若可見則顯示Unit持有的物體,否則隱藏。(可優化)
? ? ? ? 3.3 按下鍵盤空格鍵,動態創建建筑配表的A或B物體并存放于默認地板defaultGround(應動態獲取地板)(可優化 僅創建Unit對象放入World相應的Room對象內,由【可視處理】動態創建或顯示物體)
? ? ? ? 3.4 按下鍵盤D鍵,清空場景物體,加載場景序列化文件,反序列化構建World對象(globalWorld),之后會由【可視處理】動態創建或顯示物體。
? ? ? ? 3.5 按下鍵盤S鍵,序列化World對象(globalWorld)保存為(world_json.json)序列化<unitId, Unit>字典(unitDict)保存為(unit_json.json),反序列化會使用到unit_json.json去輔助生成Unit對象填入相應的Room對象的Unit列表。
4、可搜索#region 世界 房間 單元 實體 找到World、Room、Unit類。
????????注意使用了[JsonIgnore]忽略某些字段序列化,以及使用[JsonConstructor]強制使用默認類去反序列化時的構造方法。個人認為反序列化時不應該依賴構造方法,而是手動組裝所有類成員,曾試過會出問題,特別是有參構造方法,參數傳入會是空的。
5、MoveGo方法,檢測到地板物體后會拿到地板物體身上的Ground類對象,判斷ground.IsCanPlaceBuild(movingGo)是否可放置在地板上,若可放置就調用ground.PlaceBuild(movingGo)放到地板,地板類會記錄這個物體相關的信息用于判定是否可放置,若不可放置,那么會回到movingStartPos移動開始位置。
Ground.cs(地板類)?和?GroundData.cs(地板數據類)
Ground.cs持有1個GroundData.cs類構建一個以地板為中心的特殊空間。這個類沒有太多作用,基本是與GroundData類對象溝通,用它只是用了一個Gizmos繪制地板的已占據格子。
案例中使用了一個Plane(灰色)地板以及一個Cube(綠色)地板,它們都掛載了Ground類。
每個Ground類是依據地板網格Mesh的Bounds.size和地板自身LocalScale大小相乘得到真實大小去構建GroundData的,并且以SlotSize切割出若干個格子Slot。
如上圖,由于Plane網格是(10,1,10)大小,乘上Scale(100,1,100)得到(1000,1,1000)大小的GroundData,計算出的地板左下角,右上角坐標如上圖(-500,-500), (500,500),總共會有1000*1000個Slot格子,注意Slot格子是動態創建的。
那么此時HGround物體的GroundData為(1000x1000)的平面,內有1000000個Slot。
為了支持Slot是動態創建的,GroundData存儲Slot是使用Dictionary<int, SlotData> map字典,字典Key是由(y*width+x)構成。
重點分析方法:
1、IsCanPlaceBuild(GameObject go, Action<SlotData> action = null)是否可放置物體,action委托方法是處理一個將可放置Slot對象的方法,PlaceBuild方法會使用到這個。
????????將go物體的世界坐標點轉為地板局部坐標,會得到一個[-5,5]范圍內的坐標,但我們需要的是一個[-500,500]的以上面的HGround大小為例,所以需要【局部坐標】乘以地板的LocalScale,得到一個【相對Ground平面的物體坐標】,之后會使用這個坐標和根據移動物體大小計算出【相對Ground平米的物體MinX,MaxX,MinY,MaxY邊界值】,用這4個邊界值分別處于SlotSize取整才能得到GroundData的下標邊界值【left, right, bottom, top】,遍歷這個邊界值范圍動態獲取或創建SlotData,判斷SlotData有物體hasGo且物體的unitId不等于當前移動物體的unitId時,會立刻退出循環遍歷,返回false不可放置,否則是可放置的,會執行action方法將SlotData傳遞出去處理,返回true可放置。(這里有好幾個坐標系 下標概念?要好好理解下)
? ? ? ? 創建SlotData時,其中的center格子中心點是世界坐標系的,主要用途是用于繪制Gizmos使用,它是通過將【相對Ground平面的坐標】除以地板的LocalScale得到【局部坐標】再轉世界坐標,具體代碼說明:
float localPosY = groundSize.y / 2f + slotSize / 2f;
Vector3 localPos = new Vector3((x + 0.5f) * slotSize / localScale.x,
localPosY / localScale.y, (y + 0.5f) * slotSize / localScale.z);
data.center = ground.TransformPoint(localPos);
localPosY是相對Ground坐標的格子Y軸偏移值,等于地板深度/2加上格子高度/2,因為地板是有深度的,例如使用Cube作為地板網格時,深度是Bounds.size.y*LocalScale.y,如果不偏移這個深度其格子會埋沒在地板內。【localPosY是相對Ground平面的格子坐標Y值】
x,y坐標是GroundData的map下標,它是格子的左下角下標,需要+0.5偏移到中心點再乘以slotSize得到【相對Ground平面的格子坐標】,之后則是除以LocalScale得到【局部坐標】再轉世界坐標。
2、PlaceBuild(GameObject go) 放置物體
????????這個方法使用到了IsCanPlaceBuild方法判定是否可放置,且傳了action委托方法,將所有物體相關的可放入SlotData存儲如List<SlotData>?tempSlotDataList,確定是可放置后會遍歷tempSlotDataList列表將所有SlotData的hasGo設置為True,unitId設置為當前物體unitId。
放置后會將<當前物體,tempSlotDataList>存儲入字典cacheGoSlotDataDict,用于Gizmos繪制紅色方框代表已放置的格子。
3、OnDrawGizmos方法 繪制所占據格子的紅色方框
//通過rotationMatrix4矩陣將空間轉到以繪制物體點為中心并且旋轉角度保持與Ground一致的空間 直接進行原點繪制格子
Matrix4x4 oldMatrix4 = Handles.matrix;
Transform groundTrans = slotData.groundData.ground.transform;
Matrix4x4 rotationMatrix4 = Matrix4x4.TRS(slotData.center, Quaternion.FromToRotation(Vector3.up, groundTrans.up.normalized), Vector3.one);
Gizmos.matrix = rotationMatrix4; //轉移矩陣
Gizmos.DrawWireCube(Vector3.zero, new Vector3(slotSize, slotSize, slotSize));
Gizmos.matrix = oldMatrix4; //復原矩陣
可優化Room也可以使用動態形式場景,類似SlotData一樣的方式即可。
額外知識點說明
1、MeshFilter和MeshRenderer的Bounds區別
MeshFilter.mesh.bounds是網格的AABB盒,其大小和位置均是網格實際大小位置。
MeshRenderer.bounds是場景物體的AABB盒,其大小和位置會隨受物體的TRS矩陣影響,即位移、旋轉、縮放影響。
如上圖,立方體(1,1,1)大小,MeshFilter是前三行數據,MeshRenderer是后三行數據,所以當你想獲取物體的真實大小時,你應該用MeshFilter的形式獲取Mesh.bounds知道它的大小,再乘上它的LocalScale得到,上面的代碼如下
Debug.Log(GetComponent<MeshFilter>().mesh.bounds);
Debug.Log(GetComponent<MeshFilter>().mesh.bounds.center);
Debug.Log(GetComponent<MeshFilter>().mesh.bounds.size);Debug.Log(" ");
Debug.Log(GetComponent<MeshRenderer>().bounds);
Debug.Log(GetComponent<MeshRenderer>().bounds.center);
Debug.Log(GetComponent<MeshRenderer>().bounds.size);
?2、Gizmos 繪制一個平行于斜面的立方體
例如這個小方塊的位置畫一個和它一樣重疊的紅色線框方框,你會發現沒有旋轉。
你必須使用Gizmos.matrix去將空間轉以這個繪制方塊為中心的空間,再進行繪制
using UnityEditor;
using UnityEngine;public class Test : MonoBehaviour
{public Transform ground;private void OnDrawGizmos(){Gizmos.color = Color.red;//Gizmos.DrawWireCube(transform.position, transform.localScale);Matrix4x4 oldMatrix4 = Gizmos.matrix;Matrix4x4 rotationMatrix4 = Matrix4x4.TRS(transform.position,Quaternion.FromToRotation(Vector3.up, ground.up.normalized), Vector3.one);Gizmos.matrix = rotationMatrix4; //轉移矩陣Gizmos.DrawWireCube(Vector3.zero, transform.localScale);Gizmos.matrix = oldMatrix4; //復原矩陣}
}
可能會有一些Bug,例如銷毀物體時,MarkPlaceLight物體需要移出去,還有銷毀物體時要將相關聯的Unit、SlotData移除之類的操作沒有做的,所以這方面的代碼請自行修復吧...