一.准备一个建筑物
1,创建一个立方体Cube,改名为Mesh
2,创建一个空物体,起名叫Building
3,将Mesh放到Building下,将Mesh的BoxCollider组件删除,在Building的组件中新加BoxCollider和Rigidbody组件
4,创建Building.cs,将脚本挂载到Building下
注意点:因为BoxCollider没有挂载到Mesh上,所以在调整碰撞器大小时,应该调整到和Mesh原来BoxCollider大小位置一样 (使用导入资源如果涉及到旋转等,注意做好调整)
5,在Assets创建Resources文件夹,在Resources文件夹下创建Prefabs文件夹,在Prefabs文件夹下创建Buildings文件夹,将Building拖入保存为预制件
二.配置环境
1,创建一个地形Terrain(Layer为第八层,可以自己设置,不过在Globals 里面自己改)
2,创建一个空物体,改名为Game,创建一个BuildingPlacer.cs,将脚本挂载到Game上
3,在Resources文件夹下创建Materials文件夹,然后创建2个Material,将名字改为下面的,然后将颜色改一下
三.创建建筑物数据和脚本
public class BuildingData
{
private string _name;
private int _hp;
public BuildingData(string name, int hp)
{
_name = name;
_hp = hp;
}
public string Name { get { return _name; } }
public int HP { get { return _hp; } }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BuildingPlacer : MonoBehaviour
{
//作为建筑放置类,手里必须拿着建筑
private Building _building;
private Ray _ray;
private RaycastHit _raycastHit;
//这个点的作用主要就是为了判断是否鼠标发生了移动,这个时候就可以更改值了
private Vector3 SavePoint;
private void Start()
{
_prepareBuilding(0);
}
private void Update()
{
if (_building != null)
{
//如果按下esc就取消建筑物
if (Input.GetKeyDown(KeyCode.Escape))
{
_cancelBuilding();
return;
}
_ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(_ray,out _raycastHit, 1000f, Globals.TERRAIN_LAYER_MASK))
{
_building.gameObject.transform.position = _raycastHit.point;
//只要一动就检查建筑的状态,随时进行切换
if(SavePoint != _raycastHit.point)
{
_building.ChangeState();
}
SavePoint = _raycastHit.point;
if (_building.HasValidPlace && Input.GetMouseButtonDown(0))
{
_PlaceBuilding();
}
}
}
}
/// <summary>
/// 准备建筑物
/// </summary>
public void _prepareBuilding(int index)
{
if(_building != null)
{
Destroy(_building.gameObject);
}
else
{
GameObject _gameObject = (GameObject)GameObject.Instantiate(Resources.Load($"Prefabs/Buildings/{Globals.buildingDatas[index].Name}"));
_building = _gameObject.GetComponent<Building>();
_building.setvaults(Globals.buildingDatas[index]);
}
SavePoint = Vector3.zero;
}
/// <summary>
/// 取消建筑物
/// </summary>
void _cancelBuilding()
{
Destroy(_building.gameObject);
_building = null;
}
/// <summary>
/// 放置建筑物
/// </summary>
void _PlaceBuilding()
{
_building.place();
_building = null;
_prepareBuilding(0);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum BuildingPlacementState
{
VALID,
INVALID,
FIXED
};
[RequireComponent(typeof(BoxCollider))]
public class Building : MonoBehaviour
{
private BuildingData _buildingData;
private int currentHp;
private BuildingPlacementState _state;
private List<Material> _materials;
private int _nCollisions = 0;
public void setvaults(BuildingData buildingData)
{
this._buildingData = buildingData;
this.currentHp = buildingData.HP;
}
public string Name { get { return _buildingData.Name; } }
public int CurrentHp { get { return currentHp; } }
public int Hp { get { return _buildingData.HP; } }
public int DataIndex
{
get
{
for (int i = 0; i < Globals.buildingDatas.Length; i++)
{
if (Globals.buildingDatas[i].Name == _buildingData.Name)
{
return i;
}
}
return -1;
}
}
private void Awake()
{
_materials = new List<Material>();
_state = BuildingPlacementState.VALID;
foreach (Material material in gameObject.transform.Find("Mesh").GetComponent<Renderer>().materials)
{
_materials.Add(new Material(material));
}
SetMaterials(_state);
}
/// <summary>
/// 根据状态改变颜色
/// </summary>
/// <param name="buildingState"></param>
public void SetMaterials(BuildingPlacementState buildingState)
{
List<Material> materials;
if (buildingState == BuildingPlacementState.VALID)
{
materials = new List<Material>();
for (int i = 0; i < _materials.Count; i++)
{
materials.Add(Globals.Valid);
}
}
else if (buildingState == BuildingPlacementState.INVALID)
{
materials = new List<Material>();
for (int i = 0; i < _materials.Count; i++)
{
materials.Add(Globals.Invalid);
}
}
else if (buildingState == BuildingPlacementState.FIXED)
{
materials = _materials;
}
else
{
return;
}
gameObject.transform.Find("Mesh").GetComponent<Renderer>().materials = materials.ToArray();
}
/// <summary>
/// 触发进入
/// </summary>
/// <param name="other"></param>
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Terrain") return;
_nCollisions++;
ChangeState();
}
/// <summary>
/// 触发离开
/// </summary>
/// <param name="other"></param>
private void OnTriggerExit(Collider other)
{
if (other.tag == "Terrain") return;
_nCollisions--;
ChangeState();
}
/// <summary>
/// 根据自身的触发检测来改变状态
/// </summary>
public void ChangeState()
{
if (_state == BuildingPlacementState.FIXED)
{
return;
};
if (_nCollisions > 0)
{
_state = BuildingPlacementState.INVALID;
SetMaterials(_state);
}
else
{
_state = BuildingPlacementState.VALID;
SetMaterials(_state);
}
}
/// <summary>
/// 放置建筑物,改变建筑物状态和恢复颜色
/// </summary>
public void place()
{
_state = BuildingPlacementState.FIXED;
SetMaterials(_state);
gameObject.GetComponent<BoxCollider>().isTrigger = false;
}
public bool HasValidPlace{ get => _state == BuildingPlacementState.VALID; }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Globals : MonoBehaviour
{
public static BuildingData[] buildingDatas = new BuildingData[]
{
new BuildingData("Building",100)
};
//这里只保留地面的检测
public static int TERRAIN_LAYER_MASK = 1 << 8;
public static Material Valid = Resources.Load("Materials/Valid") as Material;
public static Material Invalid = Resources.Load("Materials/Invalid") as Material;
}
4.总结
项目运行起来后,就可以进行Building实体的放置了,当放置时碰到已经放置的实体时会显示红色,点击鼠标左键也是没有效果的,如果是绿色,则可以直接左键放置。本博客只是一个小Demo,只是提供一个学习思路。