欢迎来到本次教程,我将为您讲解如何使用 Unity 引擎来开发一个建造与防御类 RTS(即实时战略)游戏。
在本教程中,我们将学习如何创建 2D 场景、设计 2D 精灵、制作 2D 动画、响应用户输入、管理游戏数据、以及其他有关游戏开发的重要话题。我们还将使用 C# 编程语言来实现游戏逻辑,并且会介绍一些常用的游戏编程模式和工具。
作为一个项目实战教程,我们不仅将讲解理论,还将创建一个完整的建造与防御类 RTS 游戏,并且在整个过程中,您将深入了解游戏开发流程、工作流程和实现细节。我们将从创建游戏场景开始,逐步添加游戏元素、实现游戏逻辑、处理用户输入、创建用户界面等等。这样,您将有足够的机会学习如何将理论知识应用到实践中。
在完成本教程后,您将有能力设计、创建和发布自己的 2D RTS 游戏,并且可以运用所学知识进行更深入的游戏开发工作。让我们开始吧!
最终效果,项目还在完善当中,目前做到一半,后续内容还会不断更新迭代,尽情期待。
链接:https://pan.baidu.com/s/1CFEWC2o5xUtp-bGJD3-cig
提取码:7omd
实现了一个建筑管理器,当玩家按下鼠标左键时,在鼠标点击的位置创建一个木材采集机的实例。其中,pfWoodHarvester是木材采集机的预制体,mainCamera是主摄像机的引用。
using UnityEngine;
public class BuildingManager : MonoBehaviour
{
[SerializeField] private Transform pfWoodHarvester; // 木材采集机预制体
private Camera mainCamera;
private void Start()
{
mainCamera = Camera.main; // 获取主摄像机对象
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 在鼠标点击位置创建一个木材采集机实例
Instantiate(pfWoodHarvester, GetMouseWorldPosition(), Quaternion.identity);
}
}
// 获取鼠标点击位置对应的世界坐标
private Vector3 GetMouseWorldPosition()
{
Vector3 mouseWorldPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
mouseWorldPosition.z = 0f; // 将Z轴坐标设为0,以保证在二维平面上创建实例
return mouseWorldPosition;
}
}
效果
定义一个继承自ScriptableObject的建筑类型类。通过在Unity编辑器的菜单中创建ScriptableObject的选项,可以方便地创建建筑类型的实例,并在实例中设置名称和预制体。
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjects/建筑类型")]
public class BuildingType : ScriptableObject
{
public string nameString; // 建筑类型的名称字符串
public Transform prefab; // 建筑类型对应的预制体
}
定义一个包含一个名为buildingTypeList的List成员变量,用于存储建筑类型的列表。
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjects/建筑类型列表")]
public class BuildingTypeList : ScriptableObject
{
public List<BuildingType> buildingTypeList; // 建筑类型列表
}
修改BuildingManager ,其中,buildingTypeList是一个ScriptableObject,包含了多个建筑类型,buildingType表示当前选中的建筑类型。
public class BuildingManager : MonoBehaviour
{
private BuildingTypeList buildingTypeList; // 建筑类型列表对象
private BuildingType buildingType; // 当前选中的建筑类型对象
private Camera mainCamera;
private void Start()
{
mainCamera = Camera.main; // 获取主摄像机对象
buildingTypeList = Resources.Load<BuildingTypeList>("ScriptableObject/建筑类型列表"); // 加载建筑类型列表
buildingType = buildingTypeList.buildingTypeList[0]; // 初始化为列表中的第一个建筑类型
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 在鼠标点击位置创建一个木材采集机实例
Instantiate(buildingType.prefab, GetMouseWorldPosition(), Quaternion.identity);
}
if (Input.GetKeyDown(KeyCode.T))
{
buildingType = buildingTypeList.buildingTypeList[0]; // 切换为列表中的第一个建筑类型
}
else if (Input.GetKeyDown(KeyCode.Y))
{
buildingType = buildingTypeList.buildingTypeList[1]; // 切换为列表中的第二个建筑类型
}
}
// 获取鼠标点击位置对应的世界坐标
private Vector3 GetMouseWorldPosition()
{
//。。。
}
}
定义一个继承自ScriptableObject的资源类型类
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjects/资源类型")]
public class ResourceType : ScriptableObject
{
public string nameString; // 资源类型的名称
}
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjects/资源类型列表")]
public class ResourceTypeList : ScriptableObject
{
public List<ResourceTypeSo> list; // 资源类型的列表
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResourceManager : MonoBehaviour
{
private Dictionary<ResourceType, int> resourceAmountDictionary; // 资源类型与数量的字典
private void Awake()
{
resourceAmountDictionary = new Dictionary<ResourceType, int>(); // 初始化资源字典
// 加载资源类型列表
ResourceTypeList resourceTypeList = Resources.Load<ResourceTypeList>("ScriptableObject/资源类型/资源类型列表");
// 遍历资源类型列表,将每个资源类型添加到资源字典并初始化数量为0
foreach (ResourceType resourceType in resourceTypeList.list)
{
resourceAmountDictionary[resourceType] = 0;
}
TestLogResourceAmountDictionary(); // 测试输出资源字典
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
// 加载资源类型列表
ResourceTypeList resourceTypeList = Resources.Load<ResourceTypeList>("ScriptableObject/资源类型/资源类型列表");
// 将列表中第二个资源类型的数量增加2
resourceAmountDictionary[resourceTypeList.list[1]] += 2;
TestLogResourceAmountDictionary(); // 测试输出资源字典
}
}
private void TestLogResourceAmountDictionary()
{
// 遍历资源字典,输出每个资源类型及其对应的数量
foreach (ResourceType resourceType in resourceAmountDictionary.Keys)
{
Debug.Log(resourceType.nameString + ": " + resourceAmountDictionary[resourceType]);
}
}
}
定义了一个名为 ResourceGeneratorData 的类,作为数据存储单元,用于管理和配置生成资源的信息。并添加了 [System.Serializable] 属性,使其可以在Unity编辑器中进行序列化和显示。
[System.Serializable]
public class ResourceGeneratorData
{
public float timerMax; // 生成资源的时间间隔
public ResourceType resourceType; // 资源类型
}
修改BuildingType
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjects/建筑类型")]
public class BuildingType : ScriptableObject
{
public string nameString; // 建筑类型的名称字符串
public Transform prefab; // 建筑类型对应的预制体
public ResourceGeneratorData resourceGeneratorData; // 资源生成器的数据
}
配置对应参数
BuildingTypeHolder 脚本,配置建筑类型
using UnityEngine;
public class BuildingTypeHolder : MonoBehaviour
{
public BuildingType buildingType; // 建筑类型对象
}
修改ResourceManager
public static ResourceManager Instance { get; private set;}
private void Awake()
{
Instance = this;
}
public void AddResource(ResourceTypeso resourceType, int amount){
resourceAmountDictionary[resourceType] += amount; // 增加资源数量
TestLogResourceAmountDictionary(); // 调用测试方法,输出资源数量
}
新增ResourceGenerator脚本,资源生成者,控制资源生成
using UnityEngine;
public class ResourceGenerator : MonoBehaviour
{
private BuildingType buildingType; // 建筑类型对象
private float timer; // 计时器
private float timerMax; // 计时器最大值
private void Awake()
{
buildingType = GetComponent<BuildingTypeHolder>().buildingType; // 获取建筑类型
timerMax = buildingType.resourceGeneratorData.timerMax; // 获取计时器最大值
}
private void Update()
{
timer -= Time.deltaTime; // 更新计时器
if (timer <= 0f) // 检查计时器是否到达或超过最大值
{
timer += timerMax; // 重置计时器
// 调用 ResourceManager 的 AddResource 方法,增加资源
ResourceManager.Instance.AddResource(buildingType.resourceGeneratorData.resourceType, 1);
}
}
}
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
public class ResourcesUI : MonoBehaviour
{
private ResourceTypeList resourceTypeList; // 资源类型列表对象
private Dictionary<ResourceType, Transform> resourceTypeTransformDictionary; // 资源类型与UI Transform的映射字典
[SerializeField] private Transform resourceTemplate; // 资源UI模板
private void Awake()
{
resourceTypeList = Resources.Load<ResourceTypeList>("ScriptableObject/资源类型/资源类型列表"); // 加载资源类型列表对象
resourceTypeTransformDictionary = new Dictionary<ResourceType, Transform>(); // 创建资源类型与UI Transform的映射字典
resourceTemplate.gameObject.SetActive(false); // 禁用资源UI模板
int index = 0; // 索引计数器
foreach (ResourceType resourceType in resourceTypeList.list) // 遍历资源类型列表
{
Transform resourceTransform = Instantiate(resourceTemplate, transform); // 实例化资源UI
resourceTransform.gameObject.SetActive(true); // 启用资源UI
resourceTransform.Find("image").GetComponent<Image>().sprite = resourceType.sprite; // 设置资源UI的图片
resourceTypeTransformDictionary[resourceType] = resourceTransform; // 将资源类型与UI Transform进行映射
index++;
}
}
private void Start()
{
UpdateResourceAmount(); // 更新资源数量
}
private void UpdateResourceAmount()
{
foreach (ResourceType resourceType in resourceTypeList.list) // 遍历资源类型列表
{
Transform resourceTransform = resourceTypeTransformDictionary[resourceType]; // 获取对应资源类型的UI Transform
int resourceAmount = ResourceManager.Instance.GetResourceAmount(resourceType); // 获取资源数量
resourceTransform.Find("text").GetComponent<TextMeshProUGUI>().SetText(resourceAmount.ToString()); // 设置资源UI的文本
}
}
}
修改ResourceType ,新增资源的图标变量
public Sprite sprite; // 资源的图标
修改ResourceManager,获取资源数量方法
// 获取资源数量
public int GetResourceAmount(ResourceType resourceType){
return resourceAmountDictionary[resourceType];
}
在 ResourceManager 类中进行修改,添加了一个 OnResourceAmountChanged 事件。这个事件用于在资源数量发生变化时通知其他对象。
在 AddResource 方法中,每次增加资源数量后,会触发 OnResourceAmountChanged 事件,通知其他对象资源数量已发生改变。
using System;
public event EventHandler OnResourceAmountChanged;
public void AddResource(ResourceType resourceType, int amount){
resourceAmountDictionary[resourceType] += amount; // 增加资源数量
//使用了 ?.Invoke 运算符来避免空引用异常
OnResourceAmountChanged?.Invoke(this, EventArgs.Empty);
TestLogResourceAmountDictionary(); // 调用测试方法,输出资源数量
}
修改ResourcesUI,在 ResourcesUI 类中的 Start 方法中,订阅了 ResourceManager.Instance.OnResourceAmountChanged 事件,并指定了一个回调方法 ResourceManager_OnResourceAmountChanged
在 ResourceManager_OnResourceAmountChanged 方法中,调用了 UpdateResourceAmount 方法,实现资源数量发生变化时更新资源UI的功能。
private void Start()
{
ResourceManager.Instance.OnResourceAmountChanged += ResourceManager_OnResourceAmountChanged;
UpdateResourceAmount(); // 更新资源数量
}
private void ResourceManager_OnResourceAmountChanged(object sender, System.EventArgs e){
UpdateResourceAmount();
}
添加虚拟相机
新建一个物体,作为虚拟相机Follow物体
新增CameraHandler脚本,控制虚拟相机的移动和缩放
using UnityEngine;
using Cinemachine;
public class CameraHandler : MonoBehaviour
{
[SerializeField] private CinemachineVirtualCamera cinemachinevirtualCamera;
private float orthographicSize;
private float targetOrthographicSize;
// 获取初始的正交大小
private void Start()
{
orthographicSize = cinemachinevirtualCamera.m_Lens.OrthographicSize;
targetOrthographicSize = orthographicSize;
}
private void Update()
{
HandleMovement();
HandleZoom();
}
// 处理摄像机移动
private void HandleMovement()
{
float x = Input.GetAxisRaw("Horizontal");
float y = Input.GetAxisRaw("Vertical");
Vector3 moveDir = new Vector3(x, y).normalized;
float moveSpeed = 60f;
transform.position += moveDir * moveSpeed * Time.deltaTime;
}
// 处理缩放
private void HandleZoom()
{
float zoomAmount = 2f;
targetOrthographicSize += Input.mouseScrollDelta.y * zoomAmount;
float minOrthographicSize = 10;
float maxOrthographicSize = 30;
targetOrthographicSize = Mathf.Clamp(targetOrthographicSize, minOrthographicSize, maxOrthographicSize);
float zoomSpeed = 5f;
orthographicSize = Mathf.Lerp(orthographicSize, targetOrthographicSize, Time.deltaTime * zoomSpeed);
// 设置摄像机的正交大小
cinemachinevirtualCamera.m_Lens.OrthographicSize = orthographicSize;
}
}
效果
修改BuildingType,新增建筑的图标变量
public Sprite sprite; //建筑的图标
新增BuildingTypeSelectUI脚本控制建筑按钮的显示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BuildingTypeSelectUI : MonoBehaviour
{
// 建筑按钮模板
public Transform btnTemplate;
private void Awake()
{
// 加载建筑类型列表资源
BuildingTypeList buildingTypeList = Resources.Load<BuildingTypeList>("ScriptableObject/建筑类型/建筑类型列表");
int index = 0;
// 遍历建筑类型列表,创建对应的按钮
foreach (BuildingType buildingType in buildingTypeList.buildingTypeList)
{
Transform btnTransform = Instantiate(btnTemplate, transform);
// 设置图片
btnTransform.Find("image").GetComponent<Image>().sprite = buildingType.sprite;
index++;
}
}
}
正常我们是不希望在UI上放置物品的
修改BuildingManager
using UnityEngine.EventSystems;
private void Update()
{
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
// 在鼠标点击位置创建一个木材采集机实例
Instantiate(buildingType.prefab, GetMouseWorldPosition(), Quaternion.identity);
}
}
ps:EventSystem.current.IsPointerOverGameObject()是一个用于判断鼠标指针是否位于UI元素上的方法。
效果
修改BuildingManager为单例,并添加修改当前选中的建筑类型对象方法
public static BuildingManager Instance {get; private set;}
private BuildingType activeBuildingType; // 当前选中的建筑类型对象
void Awake(){
Instance = this;
//。。。
}
// 修改当前选中的建筑类型对象
public void SetActiveBuildingType(BuildingType buildingType){
activeBuildingType = buildingType;
}
修改BuildingTypeSelectUI绑定点击事件
// 遍历建筑类型列表,创建对应的按钮
foreach (BuildingType buildingType in buildingTypeList.buildingTypeList)
{
//。。。
//绑定点击事件
btnTransform.GetComponent<Button>().onClick.AddListener(()=>{
BuildingManager.Instance.SetActiveBuildingType(buildingType);
});
}
效果
新增选中select底图
修改BuildingTypeSelectUI
private Dictionary<BuildingType, Transform> btnTransformDictionary;
private void Awake(){
btnTransformDictionary = new Dictionary<BuildingType, Transform>();
//。。。
// 遍历建筑类型列表,创建对应的按钮
foreach (BuildingType buildingType in buildingTypeList.buildingTypeList)
{
//。。。
btnTransformDictionary[buildingType] = btnTransform;
}
}
private void Update(){
UpdateActiveBuildingTypeButton();
}
// 更新当前选中建筑类型按钮的样式
private void UpdateActiveBuildingTypeButton(){
//默认关闭选中图像
foreach (BuildingType buildingType in btnTransformDictionary.Keys){
Transform btnTransform = btnTransformDictionary[buildingType];
btnTransform.Find("selected").gameObject.SetActive(false);
}
//开启选中图像
BuildingType activeBuildingType = BuildingManager.Instance.GetActiveBuildingType();
btnTransformDictionary[activeBuildingType].Find("selected").gameObject.SetActive(true);
}
BuildingManager新增方法,获取选中的建筑类型
//获取选中的建筑类型
public BuildingType GetActiveBuildingType(){
return activeBuildingType;
}
效果
修改BuildingManager
private void Update()
{
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
if(activeBuildingType.prefab != null){
// 在鼠标点击位置创建一个建筑实例
Instantiate(activeBuildingType.prefab, GetMouseWorldPosition(), Quaternion.identity);
}
}
}
using UnityEngine;
public static class Utilsclass
{
private static Camera mainCamera;
// 获取鼠标在世界坐标系中的位置
public static Vector3 GetMouseWorldPosition()
{
// 如果主摄像机对象为空,则获取主摄像机对象
if (mainCamera == null)
mainCamera = Camera.main;
// 将鼠标当前位置从屏幕坐标系转换为世界坐标系
Vector3 mouseWorldPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
// 将鼠标世界位置的z坐标设置为零
mouseWorldPosition.z = 0f;
// 返回鼠标在世界坐标系中的位置
return mouseWorldPosition;
}
}
修改BuildingManager,通过事件通知其他对象
using System;
public event EventHandler<OnActiveBuildingTypeChangedEventArgs> OnActiveBuildingTypeChanged;
public class OnActiveBuildingTypeChangedEventArgs : EventArgs{
public BuildingType activeBuildingType;
}
// 修改当前选中的建筑类型对象
public void SetActiveBuildingType(BuildingType buildingType){
activeBuildingType = buildingType;
OnActiveBuildingTypeChanged?.Invoke(this,new OnActiveBuildingTypeChangedEventArgs {activeBuildingType = activeBuildingType});
}
新增BuildingGhost脚本,控制鼠标建筑物显示隐藏
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BuildingGhost : MonoBehaviour
{
private GameObject spriteGameobject;
// 初始时隐藏建筑物
private void Awake()
{
spriteGameobject = transform.Find("sprite").gameObject;
Hide();
}
// 监听BuildingManager中的事件
private void Start()
{
BuildingManager.Instance.OnActiveBuildingTypeChanged += BuildingManager_OnActiveBuildingTypeChanged;
}
// 处理BuildingManager中的事件
private void BuildingManager_OnActiveBuildingTypeChanged(object sender, BuildingManager.OnActiveBuildingTypeChangedEventArgs e)
{
if (e.activeBuildingType.prefab == null)
{
Hide();
}
else
{
Show(e.activeBuildingType.sprite);
}
}
// 每帧更新建筑物的位置
private void Update()
{
transform.position = Utilsclass.GetMouseWorldPosition();
}
// 显示建筑物
private void Show(Sprite ghostSprite)
{
spriteGameobject.SetActive(true);
spriteGameobject.GetComponent<SpriteRenderer>().sprite = ghostSprite;
}
// 隐藏建筑物
private void Hide()
{
spriteGameobject.SetActive(false);
}
}
修改BuildingTypeSelectUI,优化代码,使用事件更新当前选中建筑类型按钮的样式
// private void Update(){
// UpdateActiveBuildingTypeButton();
// }
private void Start(){
BuildingManager.Instance.OnActiveBuildingTypeChanged += BuildingManager_OnActiveBuildingTypeChanged;
UpdateActiveBuildingTypeButton();
}
private void BuildingManager_OnActiveBuildingTypeChanged(object sender, BuildingManager.OnActiveBuildingTypeChangedEventArgs e){
UpdateActiveBuildingTypeButton();
}
效果
如果我们直接添加一些资源物体,会发现排序变得很乱
我们可以通过脚本来控制资源的排序,大致逻辑就是按物体的y轴来控制排序
using UnityEngine;
public class SpritePositionSortingOrder : MonoBehaviour
{
[SerializeField] private bool runOnce; // 是否只运行一次
[SerializeField] private float positionOffsetY; // Y轴位置偏移量
private SpriteRenderer spriteRenderer;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>(); // 获取当前对象的SpriteRenderer组件
}
private void LateUpdate()
{
float precisionMultiplier = 5f; // 精度乘数,可以根据需要调整
// 根据当前对象的位置和Y轴偏移量计算出sortingOrder值,并将其赋给SpriteRenderer组件的sortingOrder属性
spriteRenderer.sortingOrder = (int)(-(transform.position.y + positionOffsetY) * precisionMultiplier);
if (runOnce)
{
Destroy(this); // 如果设置了只运行一次,就在完成一次排序后销毁脚本组件
}
}
}
新建shader graphs
新建材质
将材质挂载在树叶身上,效果
新增脚本,挂载在建筑物上
using UnityEngine;
public class ResourceNode : MonoBehaviour
{
public ResourceType resourceType;
}
修改ResourceGeneratorData资源生成器数据类
public float resourecDetectionRadius; //资源检测半径
public int maxResourceAmount; //最大资源数量
修改ResourceGenerator
using UnityEngine;
public class ResourceGenerator : MonoBehaviour
{
private ResourceGeneratorData resourceGeneratorData;
// private BuildingType buildingType; // 建筑类型对象
private float timer; // 计时器
private float timerMax; // 计时器最大值
private void Awake()
{
resourceGeneratorData = GetComponent<BuildingTypeHolder>().buildingType.resourceGeneratorData; // 获取建筑类型
timerMax = resourceGeneratorData.timerMax; // 获取计时器最大值
}
private void Start(){
// 获取附近的资源节点数量
Collider2D[] collider2DArray = Physics2D.OverlapCircleAll(transform.position, resourceGeneratorData.resourecDetectionRadius);
int nearbyResourceAmount = 0;
foreach (Collider2D collider2D in collider2DArray){
ResourceNode resourceNode = collider2D.GetComponent<ResourceNode>();
if (resourceNode != null){
// 如果资源节点的资源类型与此资源生成器的资源类型匹配,则增加附近资源节点的数量
if (resourceNode.resourceType == resourceGeneratorData.resourceType){
nearbyResourceAmount++;
}
}
}
// 将附近的资源节点数量限制在最大值范围内,并禁用此资源生成器的 Update 方法
nearbyResourceAmount = Mathf.Clamp(nearbyResourceAmount, 0, resourceGeneratorData.maxResourceAmount);
if (nearbyResourceAmount == 0 ){
enabled = false;
}else{
//按附近的资源数控制资源的增加速度
timerMax = (resourceGeneratorData.timerMax / 2f)+resourceGeneratorData.timerMax*(1 -(float)nearbyResourceAmount / resourceGeneratorData.maxResourceAmount);
}
// 输出附近资源节点数量,用于调试
Debug.Log("附近资源量:" + nearbyResourceAmount+";计时器最大值:" + timerMax);
}
private void Update()
{
timer -= Time.deltaTime; // 更新计时器
if (timer <= 0f) // 检查计时器是否到达或超过最大值
{
timer += timerMax; // 重置计时器
// 调用 ResourceManager 的 AddResource 方法,增加资源
ResourceManager.Instance.AddResource(resourceGeneratorData.resourceType, 1);
}
}
}
效果
修改BuildingType,新增变量控制施工半径
public float minConstructionRadius; //最小施工半径
修改BuildingManager
private void Update()
{
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
//测试打印
Debug.Log(CanSpawnBuilding(activeBuildingType, Utilsclass.GetMouseWorldPosition()));
if(activeBuildingType.prefab != null && CanSpawnBuilding(activeBuildingType, Utilsclass.GetMouseWorldPosition())){
// 在鼠标点击位置创建一个建筑实例
Instantiate(activeBuildingType.prefab, Utilsclass.GetMouseWorldPosition(), Quaternion.identity);
}
}
}
///
/// 检查是否可以在给定位置生成建筑物
///
/// 要生成的建筑物类型
/// 生成建筑物的位置
/// 如果可以生成建筑物,则返回 true,否则返回 false
private bool CanSpawnBuilding(BuildingType buildingType, Vector3 position)
{
// 获取建筑物预制体的碰撞器
BoxCollider2D boxCollider2D = buildingType.prefab.GetComponent<BoxCollider2D>();
// 在指定位置使用盒形检测获取所有重叠的碰撞体
Collider2D[] collider2DArray = Physics2D.OverlapBoxAll(position + (Vector3)boxCollider2D.offset, boxCollider2D.size, 0);
// 判断是否有其他碰撞体与要生成的建筑物重叠,如果有则返回 false
bool isAreaClear = collider2DArray.Length == 0;
if (!isAreaClear)
{
return false;
}
// 在指定位置使用圆形检测获取所有在最小施工半径内的碰撞体
collider2DArray = Physics2D.OverlapCircleAll(position, buildingType.minConstructionRadius);
// 遍历所有与最小施工半径内碰撞的碰撞体
foreach (Collider2D collider2D in collider2DArray)
{
// 获取碰撞体上的 BuildingTypeHolder 组件
BuildingTypeHolder buildingTypeHolder = collider2D.GetComponent<BuildingTypeHolder>();
// 如果碰撞体上有 BuildingTypeHolder 组件
if (buildingTypeHolder != null)
{
// 检查该建筑物的类型是否与要生成的建筑物类型相同,如果是则返回 false
if (buildingTypeHolder.buildingType == buildingType)
{
return false;
}
}
}
// 如果以上条件都满足,则可以生成建筑物,返回 true
return true;
}
效果
待续
为了防止大家变懒,源码就不提供了,大家直接可以照着文章思路进行学习
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。点赞越多,更新越快哦!当然,如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~