首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

如何使用Unity写一个基地建造系统

  • 25-02-20 16:01
  • 4692
  • 6186
blog.csdn.net

在Unity中编写一个基地建造系统需要结合Unity的核心功能(如GameObject、Raycasting、UI和Prefab等)来实现。这是一个分步的教程,帮助你理解和实现一个基本的基地建造系统。


功能需求分解

一个简单的基地建造系统通常包括以下功能:

  1. 建筑选择:玩家可以选择要建造的建筑类型。
  2. 建筑放置:玩家可以将建筑放置在地形上。
  3. 放置验证:建筑物只能放置在合法的区域(如不重叠、不超出边界)。
  4. 建筑确认:确认放置后,建筑从“预览模式”变为“固定模式”。
  5. 资源消耗(可选):建造需要消耗资源。
  6. 建筑交互(可选):玩家可以与建造的建筑物进行交互。

实现步骤

1. 创建基础项目
  1. 设置场景:

    • 创建一个平坦的地形(例如,使用Plane作为地面)。
    • 准备一些建筑的Prefab(例如房子、工厂等)。
  2. 准备资源:

    • 创建一个UI面板,用于选择建筑类型。
    • 准备一个用于放置预览的透明建筑材质(例如半透明的绿色和红色材质,用于合法和非法放置的反馈)。

2. 编码实现
Step 1: 建立建筑管理器

创建一个脚本BuildingManager来管理建筑的创建和放置逻辑。

  1. using UnityEngine;
  2. public class BuildingManager : MonoBehaviour
  3. {
  4. public GameObject[] buildingPrefabs; // 可建造的建筑Prefab列表
  5. public LayerMask groundLayer; // 地形的Layer
  6. public Material validPlacementMat; // 合法放置的材质
  7. public Material invalidPlacementMat;// 非法放置的材质
  8. private GameObject currentBuilding; // 当前正在放置的建筑
  9. private bool isPlacing = false; // 是否处于放置模式
  10. private Renderer currentRenderer; // 当前建筑的Renderer
  11. void Update()
  12. {
  13. // 检查是否在放置模式
  14. if (isPlacing)
  15. {
  16. HandlePlacement();
  17. }
  18. }
  19. public void StartPlacing(int buildingIndex)
  20. {
  21. if (buildingIndex < 0 || buildingIndex >= buildingPrefabs.Length) return;
  22. // 创建一个建筑的预览对象
  23. currentBuilding = Instantiate(buildingPrefabs[buildingIndex]);
  24. currentRenderer = currentBuilding.GetComponent<Renderer>();
  25. // 设置为放置模式
  26. isPlacing = true;
  27. }
  28. private void HandlePlacement()
  29. {
  30. // 使用Raycast检测鼠标位置
  31. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  32. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer))
  33. {
  34. // 将建筑移动到鼠标位置
  35. currentBuilding.transform.position = hit.point;
  36. // 检测是否可以放置
  37. if (CanPlaceBuilding())
  38. {
  39. ApplyMaterial(validPlacementMat); // 合法放置
  40. if (Input.GetMouseButtonDown(0)) // 左键点击确认建造
  41. {
  42. ConfirmPlacement();
  43. }
  44. }
  45. else
  46. {
  47. ApplyMaterial(invalidPlacementMat); // 非法放置
  48. }
  49. }
  50. // 取消建造模式(右键取消)
  51. if (Input.GetMouseButtonDown(1))
  52. {
  53. CancelPlacement();
  54. }
  55. }
  56. private bool CanPlaceBuilding()
  57. {
  58. // 检测建筑是否与其他物体重叠
  59. Collider[] colliders = Physics.OverlapBox(currentBuilding.transform.position, currentRenderer.bounds.extents);
  60. return colliders.Length == 0; // 没有碰撞则可以放置
  61. }
  62. private void ApplyMaterial(Material material)
  63. {
  64. if (currentRenderer != null)
  65. {
  66. currentRenderer.material = material;
  67. }
  68. }
  69. private void ConfirmPlacement()
  70. {
  71. // 确认放置后,取消透明材质
  72. ApplyMaterial(null);
  73. // 固定建筑状态
  74. currentBuilding = null;
  75. currentRenderer = null;
  76. isPlacing = false;
  77. }
  78. private void CancelPlacement()
  79. {
  80. // 销毁当前建筑预览
  81. Destroy(currentBuilding);
  82. currentBuilding = null;
  83. isPlacing = false;
  84. }
  85. }

Step 2: 创建建筑选择UI
  1. 创建一个UI面板,用于选择建筑类型。
  2. 为每个按钮绑定一个方法,调用BuildingManager的StartPlacing方法。
  1. using UnityEngine;
  2. public class UIManager : MonoBehaviour
  3. {
  4. public BuildingManager buildingManager;
  5. public void OnSelectBuilding(int buildingIndex)
  6. {
  7. buildingManager.StartPlacing(buildingIndex);
  8. }
  9. }

在Unity Editor中:

  • 创建一个Canvas。
  • 添加建筑类型的按钮,为每个按钮绑定UIManager.OnSelectBuilding方法,并传入不同的建筑索引。

Step 3: 设置地形检测

为地形设置一个专属的Layer(例如Ground),并在BuildingManager中设置groundLayer。


Step 4: 添加合法放置检测

在CanPlaceBuilding()方法中,你可以扩展逻辑,例如:

  • 检测建筑是否超出地形边界。
  • 检测是否与其他建筑重叠。

Step 5: 添加资源系统(可选)

如果需要资源消耗,可以为玩家创建一个资源管理器:

  1. public class ResourceManager : MonoBehaviour
  2. {
  3. public int wood;
  4. public int stone;
  5. public bool SpendResources(int woodCost, int stoneCost)
  6. {
  7. if (wood >= woodCost && stone >= stoneCost)
  8. {
  9. wood -= woodCost;
  10. stone -= stoneCost;
  11. return true;
  12. }
  13. return false;
  14. }
  15. }

然后在ConfirmPlacement中检查是否有足够的资源:

  1. if (resourceManager.SpendResources(woodCost, stoneCost))
  2. {
  3. ConfirmPlacement();
  4. }
  5. else
  6. {
  7. Debug.Log("Not enough resources!");
  8. }

Step 6: 添加交互功能(可选)

在Unity中,为建筑物添加交互功能(如点击建筑后显示选项菜单或执行其他行为)需要结合鼠标点击检测和UI交互。以下是详细的实现步骤和代码:


功能需求

  1. 鼠标点击检测:玩家点击建筑时,能够检测到点击的建筑。
  2. 显示选项菜单:点击建筑后,弹出一个交互菜单(例如升级、拆除等选项)。
  3. 执行建筑功能:通过菜单按钮触发建筑操作(如升级或拆除)。

实现步骤

1. 为建筑添加交互组件

创建一个Building脚本,用于定义每个建筑的基本行为。

  1. using UnityEngine;
  2. public class Building : MonoBehaviour
  3. {
  4. public string buildingName = "Default Building"; // 建筑名称
  5. public int level = 1; // 当前建筑等级
  6. public int maxLevel = 3; // 最大等级
  7. // 升级建筑
  8. public void Upgrade()
  9. {
  10. if (level < maxLevel)
  11. {
  12. level++;
  13. Debug.Log($"{buildingName} 升级到等级 {level}!");
  14. // 在这里添加具体的升级逻辑,例如改变外观或增加属性
  15. }
  16. else
  17. {
  18. Debug.Log($"{buildingName} 已达到最高等级!");
  19. }
  20. }
  21. // 拆除建筑
  22. public void DestroyBuilding()
  23. {
  24. Debug.Log($"{buildingName} 被拆除!");
  25. Destroy(gameObject);
  26. }
  27. }

2. 创建交互菜单(UI部分)
  1. 创建Canvas:

    • 在Hierarchy中创建一个Canvas,将其渲染模式设置为Screen Space - Overlay。
    • 添加一个Panel作为菜单背景。
    • 在Panel中添加几个按钮,例如“升级”和“拆除”。
  2. 设置按钮行为:

    • 将每个按钮的OnClick事件绑定到交互脚本中。

3. 创建交互管理器

交互管理器负责处理玩家的鼠标点击事件以及显示/隐藏交互菜单。

交互管理器代码
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. public class BuildingInteractionManager : MonoBehaviour
  4. {
  5. public LayerMask buildingLayer; // 建筑物的Layer,用于点击检测
  6. public GameObject interactionMenu; // 交互菜单的UI
  7. public Text buildingNameText; // 用于显示建筑名称
  8. public Button upgradeButton; // 升级按钮
  9. public Button destroyButton; // 拆除按钮
  10. private Building selectedBuilding; // 当前选中的建筑
  11. void Update()
  12. {
  13. // 检测鼠标左键点击事件
  14. if (Input.GetMouseButtonDown(0))
  15. {
  16. DetectBuilding();
  17. }
  18. // 隐藏菜单(右键关闭)
  19. if (Input.GetMouseButtonDown(1) && interactionMenu.activeSelf)
  20. {
  21. CloseInteractionMenu();
  22. }
  23. }
  24. private void DetectBuilding()
  25. {
  26. // 使用Raycast检测鼠标点击的建筑物
  27. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  28. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, buildingLayer))
  29. {
  30. Building building = hit.collider.GetComponent<Building>();
  31. if (building != null)
  32. {
  33. // 选中了建筑,打开交互菜单
  34. OpenInteractionMenu(building);
  35. }
  36. }
  37. }
  38. private void OpenInteractionMenu(Building building)
  39. {
  40. selectedBuilding = building;
  41. // 设置菜单UI的位置(在建筑上方)
  42. Vector3 menuPosition = Camera.main.WorldToScreenPoint(building.transform.position);
  43. interactionMenu.transform.position = menuPosition;
  44. // 显示建筑名称
  45. buildingNameText.text = building.buildingName;
  46. // 显示交互菜单
  47. interactionMenu.SetActive(true);
  48. // 设置按钮事件
  49. upgradeButton.onClick.RemoveAllListeners();
  50. upgradeButton.onClick.AddListener(() => OnUpgradeBuilding());
  51. destroyButton.onClick.RemoveAllListeners();
  52. destroyButton.onClick.AddListener(() => OnDestroyBuilding());
  53. }
  54. private void CloseInteractionMenu()
  55. {
  56. interactionMenu.SetActive(false);
  57. selectedBuilding = null;
  58. }
  59. private void OnUpgradeBuilding()
  60. {
  61. if (selectedBuilding != null)
  62. {
  63. selectedBuilding.Upgrade();
  64. }
  65. CloseInteractionMenu();
  66. }
  67. private void OnDestroyBuilding()
  68. {
  69. if (selectedBuilding != null)
  70. {
  71. selectedBuilding.DestroyBuilding();
  72. }
  73. CloseInteractionMenu();
  74. }
  75. }

4. 配置交互系统
  1. 为建筑设置Layer:

    • 为所有可交互的建筑物设置一个专属的Layer(例如Building)。
    • 在BuildingInteractionManager中将buildingLayer设置为该Layer。
  2. 绑定交互菜单UI:

    • 将交互菜单的GameObject、Text组件和按钮组件拖拽到BuildingInteractionManager的对应字段。
  3. 为建筑物添加交互组件:

    • 将Building脚本附加到所有可交互的建筑物上。

5. 测试和优化
  • 点击建筑后,交互菜单应该显示在建筑上方,并显示建筑的名称。
  • 点击“升级”按钮,建筑的等级应该提升。
  • 点击“拆除”按钮,建筑应该被销毁。
  • 右键点击或点击其他地方时,交互菜单应该关闭。

完整交互菜单UI结构

以下是交互菜单的层级结构示例:

  1. Canvas
  2. └── InteractionMenu (Panel)
  3. ├── BuildingName (Text)
  4. ├── UpgradeButton (Button)
  5. └── DestroyButton (Button)

扩展功能

  1. 动态检测建筑是否可升级或拆除:

    • 在OpenInteractionMenu中,根据建筑状态禁用或启用按钮:
      upgradeButton.interactable = selectedBuilding.level < selectedBuilding.maxLevel;
      
  2. 支持多种建筑功能:

    • 添加更多按钮,触发如“生产资源”、“开启操作”等功能。
  3. 动画和特效:

    • 为菜单的打开和关闭添加动画效果。
    • 点击建筑后,让建筑高亮显示或播放特殊效果。
  4. 多建筑选择:

    • 支持框选多个建筑,批量执行操作。

完整交互效果

通过上述实现,玩家可以点击建筑打开交互菜单,菜单显示建筑的名称,并提供操作选项(如升级或拆除)。这种交互方式非常灵活,可以扩展为复杂的基地管理系统。

如果需要更复杂的功能(如多人游戏同步、不同建筑类型的专属操作),可以进一步封装每个建筑的行为逻辑,使用继承或接口来实现多态操作。

7. 添加建筑旋转功能

在大多数建造游戏中,玩家可以通过按键来旋转建筑。我们可以在放置模式中添加此功能。

实现建筑旋转

在BuildingManager中修改HandlePlacement方法,监听键盘按键(例如R键)来旋转当前建筑。

  1. private void HandlePlacement()
  2. {
  3. // 使用Raycast检测鼠标位置
  4. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  5. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer))
  6. {
  7. // 将建筑移动到鼠标位置
  8. currentBuilding.transform.position = hit.point;
  9. // 检测是否可以放置
  10. if (CanPlaceBuilding())
  11. {
  12. ApplyMaterial(validPlacementMat); // 合法放置
  13. if (Input.GetMouseButtonDown(0)) // 左键点击确认建造
  14. {
  15. ConfirmPlacement();
  16. }
  17. }
  18. else
  19. {
  20. ApplyMaterial(invalidPlacementMat); // 非法放置
  21. }
  22. }
  23. // 添加旋转功能:按下R键旋转建筑
  24. if (Input.GetKeyDown(KeyCode.R))
  25. {
  26. RotateBuilding();
  27. }
  28. // 取消建造模式(右键取消)
  29. if (Input.GetMouseButtonDown(1))
  30. {
  31. CancelPlacement();
  32. }
  33. }
  34. private void RotateBuilding()
  35. {
  36. if (currentBuilding != null)
  37. {
  38. // 每次按键旋转90度
  39. currentBuilding.transform.Rotate(0, 90, 0);
  40. }
  41. }

8. 网格对齐功能

为了让建筑物对齐到网格(类似许多RTS游戏的网格建造系统),可以将建筑物的位置“吸附”到一个指定的网格。

实现网格对齐

网格对齐可以通过将建筑物的位置取整到网格大小的倍数来实现。

  1. private void HandlePlacement()
  2. {
  3. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  4. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer))
  5. {
  6. // 将建筑移动到鼠标位置,并对齐到网格
  7. Vector3 alignedPosition = AlignToGrid(hit.point);
  8. currentBuilding.transform.position = alignedPosition;
  9. // 检测是否可以放置
  10. if (CanPlaceBuilding())
  11. {
  12. ApplyMaterial(validPlacementMat);
  13. if (Input.GetMouseButtonDown(0)) // 左键点击确认建造
  14. {
  15. ConfirmPlacement();
  16. }
  17. }
  18. else
  19. {
  20. ApplyMaterial(invalidPlacementMat);
  21. }
  22. }
  23. if (Input.GetMouseButtonDown(1))
  24. {
  25. CancelPlacement();
  26. }
  27. }
  28. private Vector3 AlignToGrid(Vector3 position)
  29. {
  30. float gridSize = 1.0f; // 设置网格大小
  31. float x = Mathf.Round(position.x / gridSize) * gridSize;
  32. float z = Mathf.Round(position.z / gridSize) * gridSize;
  33. return new Vector3(x, position.y, z);
  34. }

9. 添加建筑升级和拆除功能

玩家可能需要对建筑进行升级、移动或拆除。我们可以通过点击建筑触发交互菜单来实现这些功能。

为建筑添加交互脚本

创建一个Building脚本,作为所有建筑的通用逻辑。

  1. using UnityEngine;
  2. public class Building : MonoBehaviour
  3. {
  4. public int level = 1; // 当前建筑等级
  5. public int maxLevel = 3; // 最大等级
  6. // 升级建筑
  7. public void Upgrade()
  8. {
  9. if (level < maxLevel)
  10. {
  11. level++;
  12. Debug.Log($"建筑升级到等级 {level}");
  13. // 在这里可以添加升级逻辑,例如改变外观或属性
  14. }
  15. else
  16. {
  17. Debug.Log("建筑已达到最高等级!");
  18. }
  19. }
  20. // 拆除建筑
  21. public void DestroyBuilding()
  22. {
  23. Debug.Log("建筑被拆除!");
  24. Destroy(gameObject);
  25. }
  26. }
点击建筑触发交互

通过使用Physics.Raycast检测玩家点击的建筑,我们可以打开交互菜单。

  1. using UnityEngine;
  2. public class BuildingInteraction : MonoBehaviour
  3. {
  4. public LayerMask buildingLayer; // 用于检测建筑物的Layer
  5. public GameObject interactionMenu; // 交互菜单的UI
  6. private Building selectedBuilding;
  7. void Update()
  8. {
  9. if (Input.GetMouseButtonDown(0)) // 鼠标左键点击
  10. {
  11. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  12. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, buildingLayer))
  13. {
  14. Building building = hit.collider.GetComponent();
  15. if (building != null)
  16. {
  17. selectedBuilding = building;
  18. OpenInteractionMenu();
  19. }
  20. }
  21. }
  22. }
  23. private void OpenInteractionMenu()
  24. {
  25. // 显示交互菜单
  26. interactionMenu.SetActive(true);
  27. // 将菜单定位到建筑位置
  28. interactionMenu.transform.position = Camera.main.WorldToScreenPoint(selectedBuilding.transform.position);
  29. }
  30. public void OnUpgradeButton()
  31. {
  32. if (selectedBuilding != null)
  33. {
  34. selectedBuilding.Upgrade();
  35. }
  36. }
  37. public void OnDestroyButton()
  38. {
  39. if (selectedBuilding != null)
  40. {
  41. selectedBuilding.DestroyBuilding();
  42. interactionMenu.SetActive(false);
  43. }
  44. }
  45. }

10. 动态网格检测(避免重叠)

为了确保建筑物不会重叠,可以使用一个二维数组或HashSet来跟踪每个网格是否已被占用。

实现动态网格检测

创建一个简单的网格管理器:

  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. public class GridManager : MonoBehaviour
  4. {
  5. private HashSet<Vector2Int> occupiedCells = new HashSet<Vector2Int>();
  6. public bool IsCellOccupied(Vector3 position, float gridSize)
  7. {
  8. Vector2Int cell = GetCell(position, gridSize);
  9. return occupiedCells.Contains(cell);
  10. }
  11. public void MarkCell(Vector3 position, float gridSize)
  12. {
  13. Vector2Int cell = GetCell(position, gridSize);
  14. occupiedCells.Add(cell);
  15. }
  16. private Vector2Int GetCell(Vector3 position, float gridSize)
  17. {
  18. int x = Mathf.RoundToInt(position.x / gridSize);
  19. int z = Mathf.RoundToInt(position.z / gridSize);
  20. return new Vector2Int(x, z);
  21. }
  22. }

在BuildingManager中,集成网格检测逻辑:

  1. private bool CanPlaceBuilding()
  2. {
  3. float gridSize = 1.0f; // 网格大小
  4. if (gridManager.IsCellOccupied(currentBuilding.transform.position, gridSize))
  5. {
  6. return false; // 位置已被占用
  7. }
  8. Collider[] colliders = Physics.OverlapBox(currentBuilding.transform.position, currentRenderer.bounds.extents);
  9. return colliders.Length == 0;
  10. }
  11. private void ConfirmPlacement()
  12. {
  13. float gridSize = 1.0f;
  14. gridManager.MarkCell(currentBuilding.transform.position, gridSize);
  15. ApplyMaterial(null);
  16. currentBuilding = null;
  17. currentRenderer = null;
  18. isPlacing = false;
  19. }

11. 动态地形支持

如果你的游戏有动态地形(例如高低起伏的地形),需要让建筑物自动适应地形的高度。

实现动态地形检测

在HandlePlacement中,通过Raycast检测地形高度,并调整建筑位置。

  1. private void HandlePlacement()
  2. {
  3. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  4. if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, groundLayer))
  5. {
  6. Vector3 alignedPosition = AlignToGrid(hit.point);
  7. alignedPosition.y = hit.point.y; // 设置为地形的高度
  8. currentBuilding.transform.position = alignedPosition;
  9. if (CanPlaceBuilding())
  10. {
  11. ApplyMaterial(validPlacementMat);
  12. if (Input.GetMouseButtonDown(0))
  13. {
  14. ConfirmPlacement();
  15. }
  16. }
  17. else
  18. {
  19. ApplyMaterial(invalidPlacementMat);
  20. }
  21. }
  22. }

结语

通过上述功能的实现,你可以打造一个功能丰富的基地建造系统。以下是一些可以继续改进的方向:

  1. AI路径规划:结合NavMesh,让单位能够避开建筑。
  2. 动态建筑属性:为建筑添加自定义属性,如产出资源、攻击能力等。
  3. 存档系统:保存和加载玩家的基地布局。
  4. 多人同步:在多人游戏中同步基地建造状态。

希望这些内容对你有所帮助,祝你开发顺利!

注:本文转载自blog.csdn.net的小宝哥Code的文章"https://blog.csdn.net/chenby186119/article/details/144210608"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

134
游戏
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top