配置連接點(diǎn)
材質(zhì)
連接器控制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Connector : MonoBehaviour
{
[Header("連接器位置")]
public ConnectorPosition connectorPosition;
[Header("連接器所屬建筑類型")]
public SelectedBuildType connectorParentType;
[Header("是否可以連接地面")]
private bool canConnectToFloor = true;
[Header("是否可以連接墻壁")]
private bool canConnectToWall = true;
[HideInInspector] public bool isConnectedToFloor = false; // 是否連接到地面
[HideInInspector] public bool isConnectedToWall = false; // 是否連接到墻壁
[HideInInspector] public bool canConnectTo = true; // 是否可以連接其他建筑
//在場景中繪制連接器的可視化表示
private void OnDrawGizmos()
{
// 根據(jù) canConnectTo 變量設(shè)置顏色
Gizmos.color = isConnectedToFloor ? (isConnectedToFloor ? Color.red : Color.blue) : (!isConnectedToWall ? Color.green : Color.yellow);
// 在連接器位置繪制一個圓形表示連接狀態(tài)
Gizmos.DrawWireSphere(transform.position, transform.lossyScale.x / 2f);
}
// 更新連接器和附近的其他連接器的連接狀態(tài)
public void updateConnectors(bool rootCall = false)
{
Collider[] colliders = Physics.OverlapSphere(transform.position, transform.lossyScale.x / 2f); // 獲取連接范圍內(nèi)的所有碰撞體
isConnectedToFloor = !canConnectToFloor;
isConnectedToWall = !canConnectToWall;
foreach (Collider collider in colliders)
{
// 忽略自身的碰撞體
if (collider.GetInstanceID() == GetComponent<Collider>().GetInstanceID())
{
continue;
}
//忽略處于非激活狀態(tài)的碰撞體
if (!collider.gameObject.activeInHierarchy)
{
continue;
}
if (collider.gameObject.layer == gameObject.layer)
{
// 獲取相鄰連接器的信息
Connector foundConnector = collider.GetComponent<Connector>();
if(!foundConnector) continue;
// 如果相鄰連接器是地面連接器,則更新 isConnectedToFloor 為 true
if (foundConnector.connectorParentType == SelectedBuildType.floor)
isConnectedToFloor = true;
// 如果相鄰連接器是墻壁連接器,則更新 isConnectedToWall 為 true
if (foundConnector.connectorParentType == SelectedBuildType.wall)
isConnectedToWall = true;
// 如果是根調(diào)用,則繼續(xù)遞歸更新相鄰連接器的狀態(tài)
if (rootCall)
foundConnector.updateConnectors();
}
}
// 根據(jù)連接狀態(tài)更新 canConnectTo 的值
canConnectTo = true;
if (isConnectedToFloor && isConnectedToWall)
{
canConnectTo = false;
}
}
}
// 連接器位置枚舉
[System.Serializable]
public enum ConnectorPosition
{
left, // 左側(cè)
right, // 右側(cè)
top, // 頂部
bottom // 底部
}
配置
建造系統(tǒng)代碼
using System.Collections.Generic;
using UnityEngine;
public class BuildingManager : MonoBehaviour
{
[Header("建筑物對象列表")]
[SerializeField] private List<GameObject> floorObjects = new List<GameObject>(); // 地板建筑物的列表
[SerializeField] private List<GameObject> wallObjects = new List<GameObject>(); // 墻壁建筑物的列表
[Header("建筑設(shè)置")]
[SerializeField] private SelectedBuildType currentBuildType; // 當(dāng)前選中的建筑類型
[SerializeField] private LayerMask connectorLayer; // 連接器所在的層
[Header("鬼影設(shè)置")]
[SerializeField] private Material ghostMaterialValid; // 鬼影建筑物有效時的材質(zhì)
[SerializeField] private Material ghostMaterialInvalid; // 鬼影建筑物無效時的材質(zhì)
[SerializeField] private float connectorOverlapRadius = 1f; // 尋找附近連接器的檢測半徑
[SerializeField] private float maxGroundAngle = 45f; // 地面的最大可放置傾斜角度
[SerializeField] private float placementDistance = 20f; // 放置距離
[Header("內(nèi)部狀態(tài)")]
[SerializeField] private bool isBuilding = false; // 是否正在建造中
[SerializeField] private int currentBuildingIndex; // 當(dāng)前建造物的索引
private GameObject ghostBuildGameobject; // 鬼影建筑物對象
private bool isGhostInValidPosition = false; // 鬼影建筑物是否在有效位置
private Transform ModelParent = null; // 模型父對象的Transform
[Header("拆除設(shè)置")]
[SerializeField] private bool isDestroying = false; // 是否在拆除建筑物
private Transform lastHitDestroyTransform; // 上次點(diǎn)擊的拆除目標(biāo)的Transform
private List<Material> lastHitMaterials = new List<Material>(); // 上次點(diǎn)擊的拆除目標(biāo)的材質(zhì)列表
private void Update()
{
// 遍歷數(shù)字1到7
for (int i = 1; i <= 7; i++)
{
// 檢查是否按下對應(yīng)的數(shù)字鍵
if (Input.GetKeyDown(KeyCode.Alpha0 + i))
{
select(i);
}
}
if (Input.GetKeyDown(KeyCode.X)) // 按下X鍵切換拆除模式
isDestroying = !isDestroying;
if (isBuilding && !isDestroying) // 如果正在建造且不在拆除狀態(tài)
{
ghostBuild(); // 顯示鬼影建筑物
if (Input.GetMouseButtonDown(0)) placeBuild(); // 點(diǎn)擊鼠標(biāo)左鍵放置建筑物
}
else if (ghostBuildGameobject) // 如果沒有在建造且鬼影建筑物存在,則銷毀鬼影建筑物對象
{
Destroy(ghostBuildGameobject);
ghostBuildGameobject = null;
}
if (isDestroying) // 如果在拆除狀態(tài)
{
ghostDestroy(); // 顯示拆除的鬼影效果
if (Input.GetMouseButtonDown(0)) destroyBuild(); // 點(diǎn)擊鼠標(biāo)左鍵拆除建筑物
}
}
//選擇建造測試
void select(int number)
{
isBuilding = !isBuilding;
if (number == 1)
{
currentBuildingIndex = 0;
currentBuildType = SelectedBuildType.floor;
}
if (number == 2)
{
currentBuildingIndex = 0;
currentBuildType = SelectedBuildType.wall;
}
if (number == 3)
{
currentBuildingIndex = 1;
currentBuildType = SelectedBuildType.wall;
}
if (number == 4)
{
currentBuildingIndex = 2;
currentBuildType = SelectedBuildType.wall;
}
if (number == 5)
{
currentBuildingIndex = 3;
currentBuildType = SelectedBuildType.wall;
}
}
private void ghostBuild()
{
GameObject currentBuild = getCurrentBuild(); // 獲取當(dāng)前建筑物類型
createGhostPrefab(currentBuild); // 創(chuàng)建鬼影建筑物
moveGhostPrefabToRaycast(); // 將鬼影建筑物移動到光線投射點(diǎn)
checkBuildValidity(); // 檢查建筑物的有效性
}
//創(chuàng)建鬼影建筑物
private void createGhostPrefab(GameObject currentBuild)
{
if (ghostBuildGameobject == null) // 如果鬼影建筑物對象不存在,則創(chuàng)建
{
ghostBuildGameobject = Instantiate(currentBuild);
ModelParent = ghostBuildGameobject.transform.GetChild(0); // 獲取模型父對象的Transform
ghostifyModel(ModelParent, ghostMaterialValid); // 設(shè)置模型為鬼影材質(zhì)
ghostifyModel(ghostBuildGameobject.transform); // 設(shè)置建筑物為鬼影材質(zhì)
}
}
//將鬼影建筑物移動到光線投射點(diǎn)
private void moveGhostPrefabToRaycast()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, placementDistance)) // 光線投射檢測
{
ghostBuildGameobject.transform.position = hit.point; // 將鬼影建筑物移動到光線投射點(diǎn)
}
}
//檢查建筑物的有效性
private void checkBuildValidity()
{
Collider[] colliders = Physics.OverlapSphere(ghostBuildGameobject.transform.position, connectorOverlapRadius, connectorLayer); // 檢測鬼影建筑物附近的連接器碰撞體
if (colliders.Length > 0) // 如果有連接器碰撞體
{
ghostConnectBuild(colliders); // 連接鬼影建筑物到連接器上
}
else // 如果沒有連接器碰撞體
{
ghostSeparateBuild(); // 鬼影建筑物與連接器分離
if (isGhostInValidPosition) // 如果鬼影建筑物在有效位置
{
Collider[] overlapColliders = Physics.OverlapBox(ghostBuildGameobject.transform.position, new Vector3(2f, 2f, 2f), ghostBuildGameobject.transform.rotation); // 檢測鬼影建筑物周圍是否與其他物體重疊
foreach (Collider overlapCollider in overlapColliders)
{
if (overlapCollider.gameObject != ghostBuildGameobject && overlapCollider.transform.root.CompareTag("Buildables")) // 如果與其他可建造物體重疊,則設(shè)置鬼影建筑物為無效狀態(tài)
{
ghostifyModel(ModelParent, ghostMaterialInvalid);
isGhostInValidPosition = false;
return;
}
}
}
}
}
// 連接鬼影建筑物到連接器上
private void ghostConnectBuild(Collider[] colliders)
{
Connector bestConnector = null;
foreach (Collider collider in colliders) // 遍歷連接器碰撞體
{
Connector connector = collider.GetComponent<Connector>();
if (connector && connector.canConnectTo) // 如果連接器存在且可連接
{
bestConnector = connector;
break;
}
}
if (bestConnector == null || currentBuildType == SelectedBuildType.floor && bestConnector.isConnectedToFloor || currentBuildType == SelectedBuildType.wall && bestConnector.isConnectedToWall) // 如果沒有找到合適的連接器或者當(dāng)前建筑類型與連接器不匹配,則設(shè)置鬼影建筑物為無效狀態(tài)
{
// 如果建筑無法連接或連接不合法,則將建筑模型設(shè)為不可放置的材質(zhì)
ghostifyModel(ModelParent, ghostMaterialInvalid);
isGhostInValidPosition = false;
return;
}
snapGhostPrefabToConnector(bestConnector); // 將鬼影建筑物對齊到連接器上
}
//將鬼影建筑物對齊到連接器上
private void snapGhostPrefabToConnector(Connector connector)
{
Transform ghostConnector = findSnapConnector(connector.transform, ghostBuildGameobject.transform.GetChild(1)); // 查找鬼影建筑物中對應(yīng)的連接器
ghostBuildGameobject.transform.position = connector.transform.position - (ghostConnector.position - ghostBuildGameobject.transform.position); // 將鬼影建筑物移動到連接器位置
if (currentBuildType == SelectedBuildType.wall) // 如果當(dāng)前建筑類型為墻壁,則調(diào)整鬼影建筑物的旋轉(zhuǎn)角度
{
Quaternion newRotation = ghostBuildGameobject.transform.rotation;
newRotation.eulerAngles = new Vector3(newRotation.eulerAngles.x, connector.transform.rotation.eulerAngles.y, newRotation.eulerAngles.z);
ghostBuildGameobject.transform.rotation = newRotation;
}
// 將建筑模型設(shè)為可放置的材質(zhì)
ghostifyModel(ModelParent, ghostMaterialValid);
isGhostInValidPosition = true;
}
// 鬼影建筑物與連接器分離
private void ghostSeparateBuild()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (currentBuildType == SelectedBuildType.wall) // 如果當(dāng)前建筑類型為墻,則將建筑模型設(shè)為不可放置的材質(zhì)
{
ghostifyModel(ModelParent, ghostMaterialInvalid);
isGhostInValidPosition = false;
return;
}
if (Vector3.Angle(hit.normal, Vector3.up) < maxGroundAngle) // 如果當(dāng)前建筑類型為地板且法線與y軸夾角小于最大可放置角度,則將建筑模型設(shè)為可放置的材質(zhì)
{
ghostifyModel(ModelParent, ghostMaterialValid);
isGhostInValidPosition = true;
}
else // 否則,將模型修改為不可放置的材質(zhì)
{
ghostifyModel(ModelParent, ghostMaterialInvalid);
isGhostInValidPosition = false;
}
}
}
// 查找鬼影建筑物中對應(yīng)的連接器
private Transform findSnapConnector(Transform snapConnector, Transform ghostConnectorParent)
{
// 查找鬼影建筑預(yù)制體中與連接器相對應(yīng)的連接器
ConnectorPosition oppositeConnectorTag = getOppositePosition(snapConnector.GetComponent<Connector>());
foreach (Connector connector in ghostConnectorParent.GetComponentsInChildren<Connector>())
{
if (connector.connectorPosition == oppositeConnectorTag)
{
return connector.transform;
}
}
return null;
}
// 查找鬼影建筑預(yù)制體中與連接器相對應(yīng)的連接器
private ConnectorPosition getOppositePosition(Connector connector)
{
// 獲取連接器的相反位置
ConnectorPosition position = connector.connectorPosition;
// 如果當(dāng)前建筑類型是墻且連接點(diǎn)的父級類型是地板,則返回底部連接點(diǎn)
if (currentBuildType == SelectedBuildType.wall && connector.connectorParentType == SelectedBuildType.floor)
return ConnectorPosition.bottom;
// 如果當(dāng)前建筑類型是地板、連接點(diǎn)的父級類型是墻且連接點(diǎn)位置為頂部
if (currentBuildType == SelectedBuildType.floor && connector.connectorParentType == SelectedBuildType.wall && connector.connectorPosition == ConnectorPosition.top)
{
// 如果連接點(diǎn)所在物體的Y軸旋轉(zhuǎn)角度為0(即朝向?yàn)檎妫?,則返回離玩家最近的連接點(diǎn)
if (connector.transform.root.rotation.y == 0)
{
return getConnectorClosestToPlayer(true);
}
else
{
// 否則返回離玩家最近的連接點(diǎn)
return getConnectorClosestToPlayer(false);
}
}
// 根據(jù)連接點(diǎn)位置返回相反的連接點(diǎn)位置
switch (position)
{
case ConnectorPosition.left:
return ConnectorPosition.right;
case ConnectorPosition.right:
return ConnectorPosition.left;
case ConnectorPosition.bottom:
return ConnectorPosition.top;
case ConnectorPosition.top:
return ConnectorPosition.bottom;
default:
return ConnectorPosition.bottom;
}
}
// 獲取距離玩家最近的連接點(diǎn)
private ConnectorPosition getConnectorClosestToPlayer(bool topBottom)
{
Transform cameraTransform = Camera.main.transform;
// 如果topBottom為true,根據(jù)玩家位置返回頂部或底部連接點(diǎn)
if (topBottom)
{
return cameraTransform.position.z >= ghostBuildGameobject.transform.position.z ? ConnectorPosition.bottom : ConnectorPosition.top;
}
else
{
// 否則,根據(jù)玩家位置返回左側(cè)或右側(cè)連接點(diǎn)
return cameraTransform.position.x >= ghostBuildGameobject.transform.position.x ? ConnectorPosition.left : ConnectorPosition.right;
}
}
// 修改模型為鬼影材質(zhì)
private void ghostifyModel(Transform modelParent, Material ghostMaterial = null)
{
// 如果提供了鬼影材質(zhì),將模型的材質(zhì)設(shè)為鬼影材質(zhì)
if (ghostMaterial != null)
{
foreach (MeshRenderer meshRenderer in modelParent.GetComponentsInChildren<MeshRenderer>())
{
meshRenderer.material = ghostMaterial;
}
}
else
{
// 否則,禁用模型的碰撞器
foreach (Collider modelColliders in modelParent.GetComponentsInChildren<Collider>())
{
modelColliders.enabled = false;
}
}
}
// 獲取當(dāng)前建筑對象
private GameObject getCurrentBuild()
{
switch (currentBuildType)
{
case SelectedBuildType.floor:
return floorObjects[currentBuildingIndex];
case SelectedBuildType.wall:
return wallObjects[currentBuildingIndex];
}
return null;
}
// 放置建筑
private void placeBuild()
{
// 如果鬼影模型存在且在有效位置上
if (ghostBuildGameobject != null & isGhostInValidPosition)
{
// 在鼠標(biāo)指針位置實(shí)例化新的建筑物并銷毀鬼影模型
GameObject newBuild = Instantiate(getCurrentBuild(), ghostBuildGameobject.transform.position, ghostBuildGameobject.transform.rotation);
Destroy(ghostBuildGameobject);
ghostBuildGameobject = null;
isBuilding = false;
// 更新新建筑物的連接點(diǎn)
foreach (Connector connector in newBuild.GetComponentsInChildren<Connector>())
{
connector.updateConnectors(true);
}
}
}
//顯示拆除的鬼影效果
private void ghostDestroy()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.transform.root.CompareTag("Buildables")) // 判斷是否是可建造的對象
{
if (!lastHitDestroyTransform) // 如果上一次點(diǎn)擊的建筑物為空,則記錄當(dāng)前點(diǎn)擊的建筑物
{
lastHitDestroyTransform = hit.transform.root;
lastHitMaterials.Clear();
// 獲取建筑物的所有 MeshRenderer 組件的材質(zhì),并添加到材質(zhì)列表
foreach (MeshRenderer meshRenderer in lastHitDestroyTransform.GetComponentsInChildren<MeshRenderer>())
{
lastHitMaterials.Add(meshRenderer.material);
}
// 將建筑物設(shè)置為鬼影材質(zhì)
ghostifyModel(lastHitDestroyTransform.GetChild(0), ghostMaterialInvalid);
}
else if (hit.transform.root != lastHitDestroyTransform) // 如果當(dāng)前點(diǎn)擊的建筑物與上次不同,則重置上一次點(diǎn)擊的建筑物
{
resetLastHitDestroyTransform();
}
}
else // 如果點(diǎn)擊的不是可建造的對象,則重置上一次點(diǎn)擊的建筑物
{
resetLastHitDestroyTransform();
}
}
}
//重置上一個選中的建筑的材質(zhì)
private void resetLastHitDestroyTransform()
{
int counter = 0;
foreach (MeshRenderer meshRenderer in lastHitDestroyTransform.GetComponentsInChildren<MeshRenderer>())
{
meshRenderer.material = lastHitMaterials[counter];
counter++;
}
lastHitDestroyTransform = null;
}
//拆除建筑物
private void destroyBuild()
{
if (lastHitDestroyTransform)
{
// 禁用建筑物的所有連接點(diǎn)
foreach (Connector connector in lastHitDestroyTransform.GetComponentsInChildren<Connector>())
{
connector.gameObject.SetActive(false);
connector.updateConnectors(true);
}
Destroy(lastHitDestroyTransform.gameObject); // 銷毀建筑物
isDestroying = false;
lastHitDestroyTransform = null;
}
}
}
// 建筑類型枚舉
[System.Serializable]
public enum SelectedBuildType
{
floor,//地面
wall//墻
}
配置
效果
刪除建筑
源碼
https://gitcode.net/unity1/3dbuildsystem
完結(jié)
贈人玫瑰,手有余香!如果文章內(nèi)容對你有所幫助,請不要吝嗇你的點(diǎn)贊評論和關(guān)注
,以便我第一時間收到反饋,你的每一次支持
都是我不斷創(chuàng)作的最大動力。當(dāng)然如果你發(fā)現(xiàn)了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奮斗的開發(fā)者,出于興趣愛好,最近開始自學(xué)unity,閑暇之余,邊學(xué)習(xí)邊記錄分享,站在巨人的肩膀上,通過學(xué)習(xí)前輩們的經(jīng)驗(yàn)總是會給我很多幫助和啟發(fā)!php是工作,unity是生活!如果你遇到任何問題,也歡迎你評論私信找我, 雖然有些問題我也不一定會,但是我會查閱各方資料,爭取給出最好的建議,希望可以幫助更多想學(xué)編程的人,共勉~文章來源:http://www.zghlxwxcb.cn/news/detail-825723.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-825723.html
到了這里,關(guān)于【unity實(shí)戰(zhàn)】使用unity制作一個類似Rust的3D生存建造建筑系統(tǒng)(附項(xiàng)目源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!