[이전 편]
https://youcheachae.tistory.com/6
[Unity] 빌딩 시스템 만들기 #1. UI , Connector Data
[이전 편]https://youcheachae.tistory.com/5#comment21549004 [Unity] 빌딩 시스템 만들기 #0. 구상[시스템 구성 전]1. 한글로된 빌딩 snap 기능이 없어서 검색을 해보다가 connector를 사용하여 raycast를 사용한 빌딩
youcheachae.tistory.com
1. <HousingManager.cs>
1-1. 사용할 레이어를 초기화 합니다
[Header("===Layer===")]
public LayerMask _buildFinishedLayer; // 다 지은 블럭의 layermask
public int _buildFinishedint; // 다 지은 블럭의 layer int
public List<Tuple<LayerMask, int>> _connectorLayer; // 커넥터 레이어
public LayerMask _ConnectorWholelayer; // 모든 커넥터 레이어 다 합친
private int _placedItemLayerInt; // 설치 완료한 오브젝트 layer int
private void F_InitLayer()
{
// 설치완료한 오브젝트 레이어
_placedItemLayerInt = LayerMask.NameToLayer("PlacedItemLayer");
// 설치완료한 블럭 레이어
_buildFinishedint = LayerMask.NameToLayer("BuildFinishedBlock");
_buildFinishedLayer = LayerMask.GetMask("BuildFinishedBlock");
// 커넥터 레이어
_connectorLayer = new List<Tuple<LayerMask, int>>
{
new Tuple <LayerMask , int>( LayerMask.GetMask("FloorConnectorLayer") , LayerMask.NameToLayer("FloorConnectorLayer") ), // temp floor 레이어
new Tuple <LayerMask , int>( LayerMask.GetMask("CellingConnectorLayer") , LayerMask.NameToLayer("CellingConnectorLayer") ), // temp celling 레이어
new Tuple <LayerMask , int>( LayerMask.GetMask("WallConnectorLayer") , LayerMask.NameToLayer("WallConnectorLayer") ), // temp wall 레이어
new Tuple <LayerMask , int>( LayerMask.GetMask("WallConnectorLayer") , LayerMask.NameToLayer("WallConnectorLayer") ), // temp wall 레이어 ( destory 도구 위해서 )
};
// 커넥터 다 합친 레이어
_ConnectorWholelayer = _connectorLayer[0].Item1 | _connectorLayer[1].Item1 | _connectorLayer[2].Item1;
}
1-1. HousingUiManager에서 F_BuildingMovement()함수를 호출 후 동작을 위한 코루틴이 실행됩니다.
[System.Serializable]
public enum SelectedBuildType
{
Floor,
Celling,
Wall,
Door,
Window,
RepairTools
}
public class HousingManager : MonoBehaviour
{
[Header("===Transform===")]
public GameObject _playerCamera;
[HideInInspector] public GameObject _tempObject; // 임시 오브젝트
[HideInInspector] public Transform _modelTransform; // 모델 오브젝트
[HideInInspector] public Transform _colliderGroupTrasform; // 콜라이더 부모 오브젝트
[HideInInspector] Transform _otherConnectorTr; // 충돌한 다른 오브젝트 (커넥터)
[HideInInspector] private SelectedBuildType _SelectBuildType; // 현재 type
private int _snapObjectTypeIdx; // typeidx
private int _snapObjectDetailIdx; // detailIdx
private List<Material> _snapOrimaterial; // 원래 material 저장 list
[Header("State")]
[SerializeField] bool _isTempValidPosition; // 임시 오브젝트가 지어질 수 있는지
[SerializeField] bool _isntColliderPlacedItem; // 설치완료된 오브젝트와 겹치는지 ?
[Header("Prefab")]
[SerializeField] public List<List<GameObject>> _bundleBulingPrefab;
[SerializeField] private List<GameObject> _floorList;
[SerializeField] private List<GameObject> _cellingList;
[SerializeField] private List<GameObject> _wallList;
[SerializeField] private List<GameObject> _doorList;
[SerializeField] private List<GameObject> _windowList;
[Header("===LayerMask===")]
[SerializeField] LayerMask _currTempLayer; // 현재 (temp 블럭이 감지할) 레이어
[Header("===Material===")]
[SerializeField] private Material _greenMaterial;
[SerializeField] private List<Material> _oriMaterialList;
[SerializeField] private Material _nowBuildMaterial;
private void Awake()
{
// 1. 초기화
_bundleBulingPrefab = new List<List<GameObject>> // 각 block 프리팹 List를 하나의 List로 묶기
{
_floorList,
_cellingList,
_wallList,
_doorList,
_windowList
};
}
public void F_BuildingMovement(int v_type , int v_detail)
{
// 0. Ui상 잘못된 idx가 넘어왔을 때
if (v_type < 0 && v_detail < 0)
return;
// 1. idx 저장
_snapObjectTypeIdx = v_type;
_snapObjectDetailIdx = v_detail;
// 2. 임시 오브젝트 확인
if (_tempObject != null)
Destroy(_tempObject);
_tempObject = null;
// 3-1. Material 리스트 초기화
_oriMaterialList.Clear();
// 3-1. 위치 state 초기화
_isTempValidPosition = true;
// 3-2. 다른오브젝트와 겹치는지에 대한 state 초기화
_isntColliderPlacedItem = false;
// 4. 동작 시작
StopAllCoroutines();
StartCoroutine(F_TempBuild());
}
}
1-3. F_TempBuild() 코루틴
IEnumerator F_TempBuild()
{
// 0. index에 해당하는 게임 오브젝트 return
GameObject _currBuild = F_SetTypeReturnObj(_snapObjectTypeIdx, _snapObjectDetailIdx);
// 0.1. 내 블럭 타입에 따라 검사할 layer
F_SettingCurrLayer(_SelectBuildType);
while (true)
{
// 수리 & 파괴도구 일 때
if (_SelectBuildType == SelectedBuildType.RepairTools)
{
F_RepairAndDestroyTool( _housingManager_Detailidx , _currTempLayer );
}
// 그 외 (건축 type 일 때)
else
{
// 1. index에 해당하는 게임오브젝트 생성 , tempObjectbuilding 오브젝트 생성
F_CreateTempPrefab(_currBuild);
// 2. 생성한 오브젝트를 snap , tempObjectBuilding 오브젝트 넘기기
F_OtherBuildBlockBuild();
}
// update 효과
yield return new WaitForSeconds(0.02f);
}
}
// SelectbuildType지정, 해당하는 prefab return
public GameObject F_SetTypeReturnObj(int v_type, int v_detail)
{
_SelectBuildType = (SelectedBuildType)v_type;
return _bundleBulingPrefab[v_type][v_detail];
}
// type에 따른 Layer 설정
private void F_SettingCurrLayer(SelectedBuildType v_type)
{
switch (v_type)
{
case SelectedBuildType.Floor:
_currTempLayer = BuildMaster.Instance._connectorLayer[0].Item1; // floor 레이어
break;
case SelectedBuildType.Celling:
_currTempLayer = BuildMaster.Instance._connectorLayer[1].Item1; // celling 레이어
break;
case SelectedBuildType.Wall: // window, door, window는 같은 wall 레이어 사용
case SelectedBuildType.Door:
case SelectedBuildType.Window:
_currTempLayer = BuildMaster.Instance._connectorLayer[2].Item1; // wall 레이어
break;
case SelectedBuildType.RepairTools: // repair 툴 일 때
_currTempLayer = BuildMaster.Instance._buildFinishedLayer; // buildFinished
break;
}
}
0️⃣ Update 효과를 주기위해 코루틴 내부에서 0.02f만큼 지연을 줍니다.
1️⃣type Idx와 detail Idx를 사용해서 SelectBuildType를 지정후 prefabBundle에 접근하여 오브젝트를 return합니다.
2️⃣type에 따라 Raycast할 layer을 설정합니다.
3️⃣타입에 따라 동작이 나뉩니다
3-1. Repair 도구일 때
: 수리 & 파괴 동작
3-2. 그 외 타입일 때
: 블럭을 snap
: 블럭을 build
[Snap]
private void F_CreateTempPrefab(GameObject v_temp)
{
// 실행조건
// temp오브젝트가 null이되면 바로 생성됨!
if (_tempObject == null)
{
// 1. 생성 & 100f,100f,100f는 임시위치
_tempObject = Instantiate(v_temp, new Vector3(100f, 100f, 100f), Quaternion.identity);
// 2. model Transform & collider group Transform
_modelTransform = _tempObject.transform.GetChild(0);
_colliderGroupTrasform = _tempObject.transform.GetChild(1);
// 2-1. 오브젝트 하위 collider 오브젝트의 하위 콜라이더를 trigger
F_ColliderTriggerOnOff(_colliderGroupTrasform, true);
// 3. 원래 material 저장
_oriMaterialList.Clear();
for (int i = 0; i < _modelTransform.childCount; i++)
{
_oriMaterialList.Add(_modelTransform.GetChild(i).GetComponent<MeshRenderer>().material);
}
// 4. modeld의 Material 바꾸기
F_ChangeMaterial(_modelTransform, _greenMaterial);
}
}
public void F_OtherBuildBlockBuild()
{
// 1. 해당 블럭이랑 같은 Layer만 raycast
F_Raycast(_currTempLayer);
// 2. temp 오브젝트의 콜라이더 검사
F_CheckCollision(_currTempLayer);
// 3. 우클릭 시 설치
if (Input.GetMouseButtonDown(0))
F_FinishBuild();
}
// 블럭 하위 colliderGroup의 자식들 collider on / off
public void F_ColliderTriggerOnOff(Transform v_trs, bool v_flag)
{
for (int i = 0; i < v_trs.childCount; i++)
{
v_trs.GetChild(i).GetComponent<Collider>().isTrigger = v_flag;
}
}
// 블럭 하위 material을 바꾸기
public void F_ChangeMaterial(Transform v_pa, Material material)
{
foreach (MeshRenderer msr in v_pa.GetComponentsInChildren<MeshRenderer>())
{
msr.material = material;
}
}
✅ F_CreateTempPrefab()
0️⃣_tempObject가 null 일 때만 _tempObject를 생성합니다.
1️⃣prefab 하위의 model의 Transfrom과, collider의 Transform을 담아놓습니다.
: 최상위 Empty 오브젝트 하위에 Model와 Collider Group 의 Empty 오브젝트가 존재합니다.
- Model
: 하위에 MeshRenderer과 MeshFilter가 부착되어있는 자식 Object가 있습니다.
- collider Group
: mesh collider을 사용하지 않고 Primitive 콜라이더를 사용하기 위해서 존재합니다.
: 하위에 Primitive 콜라이더가 부착되어있는 자식 Object 가 있습니다.
2️⃣ collider Group 하위의 오브젝트의 기본 layer은 BuildFinished 레이어 입니다.
: snap 시 플레이어와 충돌하지 않기 위해서 collider의 onTrigger을 On 합니다.
3️⃣ Model 하위의 MeshRenderer의 Material을 List에 저장한 후, 프리팹을 초록색으로 바꿉니다.
: 임시 build 상태를 나타내기 위해서 초록색으로 변환합니다.
✅F_Raycast()
private void F_Raycast(LayerMask v_layer)
{
// 1. 넘어온 Layer'만' rayCast
RaycastHit _hit;
// 2. raycast 되면 -> 임시 오브젝트를 그 위치로 옮기기
if (Physics.Raycast
(_playerCamera.transform.position,
_playerCamera.transform.forward * 10, out _hit, 5f, v_layer)) // 타입 : LayerMask
{
_tempObject.transform.position = _hit.point;
}
}
1️⃣ 플레이어 카메라 위치에서, 플레이어 카메라 위치 앞쪽으로 , 최대거리 5f만큼 v_layer만 검사합니다.
2️⃣ raycast가 충돌되면 , 임시 오브젝트의위치를 충돌된 위치로 이동합니다.
✅F_CheckCollision()
private void F_CheckCollision(LayerMask v_layer)
{
// 1. 콜라이더 검사
Collider[] _coll = Physics.OverlapSphere(_tempObject.transform.position, 1f, v_layer); // 타입 : LayerMask
// 2. 검사되면 -> 오브젝트 Snap
if (_coll.Length > 0)
F_Snap(_coll);
// 2-1. 검사되지 않으면 , 올바른 위치에 있지 않음
else
_isTempValidPosition = false;
}
1️⃣블럭의 중심에서 OverlapSphere로 콜라이더를 검사합니다.
: 콜라이더가 검출되면 ? Snap 동작합니다.
2️⃣검사되지 않으면 ?
: 올바른 위치가 아닙니다.
✅F_Snap()
private void F_Snap(Collider[] v_coll)
{
// 0. 다른 커넥터? -> 배열에 처음으로 들어온 collider
_otherConnectorTr = v_coll[0].transform;
// 1. 타입이 wall 일때는 회전
if (_snapSelectBuildType == SelectedBuildType.Wall || _snapSelectBuildType == SelectedBuildType.Window
|| _snapSelectBuildType == SelectedBuildType.Door)
{
// 내 temp 블럭 회전 += 접촉한 커넥터의 회전
Quaternion qu = _snapTempObject.transform.rotation;
qu.eulerAngles = new Vector3(qu.eulerAngles.x, _otherConnectorTr.eulerAngles.y, qu.eulerAngles.z);
_snapTempObject.transform.rotation = qu;
}
// 2. Snap!!
_tempObject.transform.position
= _otherConnectorTr.position;
// 3. 설치가능
_isTempValidPosition = true;
}
1️⃣ 충돌한 다른 오브젝트 즉 , 커넥터를 저장합니다. 매개변수로 넘어온 collider 배열에서 맨 처음 오브젝트입니다.
2️⃣ 현재 type이 wall , window , door 이면 충돌한 다른 오브젝트 즉 커넥터의 회전을 적용합니다.
3️⃣ 현재 임시오브젝트 ( _tempObject )를 충돌한 커넥터의 위치로 이동합니다. ( Snap 동작)
✅F_BuildTemp()
: 우클릭시 동작합니다.
private void F_BuildTemp()
{
// 0. 올바른 위치에 있으면, 다른 오브젝트랑 충돌한 상태가 아니면
if (_isTempValidPosition != true)
return;
// 1. 설치
if (_tempObject != null)
{
// 0. 생성
GameObject _nowbuild = Instantiate(F_SetTypeReturnObj(_snapObjectTypeIdx, _snapObjectDetailIdx),
_tempObject.transform.position, _tempObject.transform.rotation );
// 1. destory
Destroy(_tempObject);
_tempObject = null;
// 3. model의 material 변경
Transform _nowBuildObjModel = _nowbuild.transform.GetChild(0);
for (int i = 0; i < _nowBuildObjModel.childCount; i++)
_nowBuildObjModel.GetChild(i).GetComponent<MeshRenderer>().material = _oriMaterialList[i];
// 4-1. collider group 오브젝트의 하위 콜라이더를 trigger Off
F_ColliderTriggerOnOff(_nowbuild.transform.GetChild(1), false);
// 5. 커넥터 지정
F_CreateConnector( _nowBuildBlock.transform );
// 6. 그 자리에 원래있던 커넥터 destory
Destroy(_otherConnectorTr.gameObject);
}
}
0️⃣ 올바른 위치에 있으면
1️⃣ 현재 typeidx와 detailidx에 맞는 오브젝트를 Instantiate 합니다.
2️⃣ _tempObject를 null로 지정합니다
: F_CreateTempPrefab(GameObject)에서 _tempObject를 매프레임 검사하는데, null이 되는 순간 함수내 조건이 true가 되면서 _tempObject를 생성합니다.
3️⃣ 현재 블럭에 해당하는 커넥터를 생성합니다. (Destroy부분에서 설명예정)
4️⃣ F_Snap 부분에서 저장했던 커넥터 오브젝트를 destory 합니다
https://github.com/churush912837465
churush912837465 - Overview
churush912837465 has 4 repositories available. Follow their code on GitHub.
github.com
'Unity' 카테고리의 다른 글
[Unity] 씬 전환 (DontDestroyOnLoad) 오브젝트 Missing 문제 (0) | 2025.01.31 |
---|---|
[Unity] 스크립트 실행 순서 (0) | 2025.01.21 |
[Unity] Event System 이벤트 시스템 ( IPointerClickHandler 등 ) (1) | 2025.01.20 |
[Unity] 빌딩 시스템 만들기 #1. UI , Connector Data (1) | 2024.06.20 |
[Unity] 빌딩 시스템 만들기 #0. 구상 (1) | 2024.06.18 |