[이전 편]

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을 담아놓습니다.

하위에 Model과 collider Group 이 존재

            : 최상위 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 합니다. 

Layer가 BuildFinishedBlock

        3️⃣ Model 하위의 MeshRenderer의 Material을 List에 저장한 후, 프리팹을 초록색으로 바꿉니다. 

              : 임시 build 상태를 나타내기 위해서 초록색으로 변환합니다. 

Material을 초록색으로 바꿈

✅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

 

[이전 편]

https://youcheachae.tistory.com/5#comment21549004

 

[Unity] 빌딩 시스템 만들기 #0. 구상

[시스템 구성 전]1. 한글로된 빌딩 snap 기능이 없어서 검색을 해보다가 connector를 사용하여  raycast를 사용한 빌딩 시스템을 발견.2. 현재 프로젝트에는 위의 방법으로 하는것은 어렵다고 판단.3. Sn

youcheachae.tistory.com

 


[Connector]

#0.구상 편에서 언급한 Connector에 대한 정보를 저장해야 합니다.

 

<ConnectorGroup>

[System.Serializable]
public enum ConnectorGroupType
{
    FloorConnectorGroup,
    CellingConnectorGroup,
    BasicWallConnectorGroup,
    RotatedWallConnnectorGroup,
    None
}

- 블럭 type에 따라 사용하는 커넥터를 나타내기 위한 enum입니다.

- type이 floor이라면 connectorGroup는 FloorConnectorGroup이고, celling또한 마찬가지 입니다.

- wall일때는 두가지로 나뉩니다. y가 90도로 회전이 되어있으면 connectorGroup은 rotatedWallConnectorGroup , 회전이 없다면 BasicWallConnectorGroup 입니다.

- 커넥터를 생성, 삭제하지 않는 블럭도 존재하니, None 멤버도 선언합니다.

 

<ConnectorType>

[System.Serializable]
public enum ConnectorType
{
    FloorConnector,
    CellingConnector,
    BasicWallConnector,
    RotatedWallConnector
}

 

- 커넥터를 설치 할 때 해당 커넥터의 회전, layermask를 설정하기 위한 enum입니다.

커넥터 오브젝트

- ConenctorType이 FloorConenctor : 회전값 : (90f, 0f, 0f) , LayerMask : FloorConnectorLayer

- ConenctorType이 CellingConnector : 회전값 : (90f, 0f, 0f) , LayerMask : CellingConnectorLayer

- ConenctorType이 BasicWallConnector : 회전값 : (0f,0f,0f) , LayerMask : WallConnectorLayer

- ConnectorType이 RotatedWallConnector : 회전값 : (0f, 90f, 0f) , LayerMask : WallConnector

 

(+) 참고 : building System에 사용하는 LayerMask 입니다.

 

<Connector 구조체>

// 커넥터 구조체 
[System.Serializable]
public struct Connector
{
    public string name;
    private List< Tuple<ConnectorType , Vector3 >> _connectorTypeList;         // 커넥터들의 타입 ,  위치 

    public List<Tuple<ConnectorType, Vector3>> connectorList => _connectorTypeList;   

    public void F_SetConector(List<Tuple<ConnectorType, Vector3>> v_typeList)
    {
        this._connectorTypeList = v_typeList;
    }

    public static readonly Connector Defalt = new Connector()
    {
        name = string.Empty,
        _connectorTypeList = new List<Tuple<ConnectorType, Vector3>>()
    };
}

- 커넥터 구조체를 선언합니다.

- ConnectorGroup별 사용하는 ConnectorType과 Vector3 위치값을 저장합니다.

보라색 : floor , 주황색 : wall , 파랑색 celling

1. Type이 Foor인 블럭이 가지는 커넥터 

 // 1. floor 데이터 지정 
 List<Tuple<ConnectorType, Vector3>> _connType1 = new List<Tuple<ConnectorType, Vector3>>
 {
      new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(-2.5f , 2.5f ,0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, 2.5f , 2.5f)),
      new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(2.5f , 2.5f ,0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, 2.5f , -2.5f)),
      new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(-2.5f , -2.5f ,0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, -2.5f , 2.5f) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(2.5f , -2.5f ,0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, -2.5f , -2.5f) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.FloorConnector, new Vector3(-5 , 0, 0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.FloorConnector, new Vector3(0,0, 5) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.FloorConnector, new Vector3(5 ,0,0) ),
      new Tuple<ConnectorType, Vector3>( ConnectorType.FloorConnector, new Vector3(0,0, -5) )
 };

2. Type이 Celling인 블럭이 가지는 커넥터 

 

// 2. celling 데이터 지정 
List<Tuple<ConnectorType, Vector3>> _connType2 = new List<Tuple<ConnectorType, Vector3>>
 {
     new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(-2.5f , 2.5f ,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, 2.5f , 2.5f)),
     new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(2.5f , 2.5f ,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, 2.5f , -2.5f)),
     new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(-2.5f , -2.5f ,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, -2.5f , 2.5f) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3(2.5f , -2.5f ,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3(0, -2.5f , -2.5f) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3(-5 , 0, 0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3(0,0, 5) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3(5 ,0,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3(0,0, -5) )
 };

3. Type이 Wall인 블럭이 가지는 커넥터 

 

// 3. wall 데이터 지정
List<Tuple<ConnectorType, Vector3>> _connType3 = new List<Tuple<ConnectorType, Vector3>>()
{
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3( 0, 5f , 0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3( 5f, 0 , 0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3( 0 , -5f ,0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.BasicWallConnector, new Vector3( -5f, 0, 0) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 0f ,2.5f, -2.5f) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 0f, 2.5f, 2.5f) ),
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 0 , -2.5f ,2.5f) ),    
     new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 0 , -2.5f, -2.5f) ),
 };

4. Type이 Rotated wall 인 블럭이 가지는 커넥터 

 

        // 4. rotated wall 데이터 지정
        List<Tuple<ConnectorType, Vector3>> _connTyp4 = new List<Tuple<ConnectorType, Vector3>>()
        {
             new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3( 0, 5f , 0) ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3( 0, 0 , 5f)  ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3( 0 , -5f ,0) ), 
             new Tuple<ConnectorType, Vector3>( ConnectorType.RotatedWallConnector, new Vector3( 0 , 0, -5f) ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 2.5f , 2.5f, 0) ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3(-2.5f , 2.5f ,0) ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( -2.5f , -2.5f ,0) ),
             new Tuple<ConnectorType, Vector3>( ConnectorType.CellingConnector, new Vector3( 2.5f , -2.5f, 0) ),
        };

 

- 각 type에 해당하는 커넥터를 선언한 후 , Connector 타입의 배열에 담아줍니다.

- 이로써 블럭 type에 맞는 ConnectorGroup을 배열을 통해 쉽게 접근할 수 있습니다.

    [SerializeField] private Connector[] _connectorContainer;
    
    public void F_SetConnArr(Connector con1, Connector con2, Connector con3, Connector con4)
    {
        _connectorContainer = new Connector[System.Enum.GetValues(typeof(ConnectorGroupType)).Length];       // 커넥터 타입만큼 배열 생성

        _connectorContainer[ (int)ConnectorGroupType.FloorConnectorGroup ]        = con1;
        _connectorContainer[ (int)ConnectorGroupType.CellingConnectorGroup ]      = con2;
        _connectorContainer[ (int)ConnectorGroupType.BasicWallConnectorGroup ]    = con3;
        _connectorContainer[ (int)ConnectorGroupType.RotatedWallConnnectorGroup ] = con4;
        _connectorContainer[ (int)ConnectorGroupType.None ]                       = Connector.Defalt;
    }

 


[Block]

1. 각 블럭이 가지고 있어야할 데이터

 

<HousingBlock.cs>

    private int _blockTypeNum;                      // 블럭 type num
    private int _blockDetailNum;                    // 블럭 detail num
    private ConnectorGroupType _blockConnectorGroup;// 어떤 connector group을 사용하는지
    private Vector3 _blockRotation;                 // 'r' input시 얼마나 회전 할 것인지 
    private int _blockHp;                           // hp
    private int _blockMaxHp;                        // max hp
    private Sprite _blockSprite;                    // ui상 사용할 이미지
    private string _blockName;                      // ui상 사용할 block 이름
    private string _blockToopTip;                   // ui상 사용할 block 설명

 

2. 블럭 데이터는List에 [_blockTypeNum][_blcokDetailNum]으로 쉽게 접근하기 위해서 2차원 List로 저장되어 있습니다.

 

<HousingDataManager.cs>

    [Header("Housing Block List ")]
    private List<List<HousingBlock>> _blockDataList;
    private List<HousingBlock> _Floor;        // 0. 바닥 
    private List<HousingBlock> _Celling;      // 1. 지붕
    private List<HousingBlock> _Wall;         // 2. 벽
    private List<HousingBlock> _Door;         // 3. 문
    private List<HousingBlock> _Window;       // 4. 창문
    private List<HousingBlock> _Repair;       // 5. 수리도구
    
    private void F_InitHousingBLockList() 
    {
        _Floor = new List<HousingBlock>();
        _Celling = new List<HousingBlock>();
        _Wall = new List<HousingBlock>();
        _Door = new List<HousingBlock>();
        _Window = new List<HousingBlock>();
        _Repair = new List<HousingBlock>();

        _blockDataList = new List<List<HousingBlock>>
        {
            _Floor,
            _Celling,
            _Wall,
            _Door,
            _Window,
            _Repair
        };
    }

 

3. CVS로 데이터 가져오기 (추후 포스팅 예정)

엑셀로 정리한 데이터 , 추후 cvs 파일로 가지고 있을예정


[ UI ]

1. 해당 slot는 type Num과 detail Num이 존재합니다.

typeNum , Detail Num 순서입니다.

 

2. <HousingSlot.cs>

: 마우스커서를 slot에 enter할 시 인덱스를 저장합니다 

public class HousingSlot : MonoBehaviour , IPointerEnterHandler 
{
    [SerializeField]
    private int _typeNum;
    [SerializeField]
    private int _detialNum;
    
    // 프로퍼티
    public int typeNum { get => _typeNum; set { _typeNum = value; } }
    public int detialNum { get => _detialNum; set { _detialNum = value; } }

    public void OnPointerEnter(PointerEventData eventData)
    {
        HousingUiManager.instance.F_SetBlockNum( _typeNum , _detialNum);
    }

}

 

3. <HousingManager.cs>

: 추후에 저장된 idx로 housingBlock 데이터와 하우징 프리팹 List에 접근합니다.

public class MyBuildManager : MonoBehaviour
{   
    [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;
    
    private void Awake()
    {
        // 1. 초기화                      
        _bundleBulingPrefab = new List<List<GameObject>> // 각 block 프리팹 List를 하나의 List로 묶기
        {
            _floorList,
            _cellingList,
            _wallList,
            _doorList,
            _windowList
        };
        
     }
}

: 하우징 프리팹 또한 [_blockTypeNum][_blcokDetailNum]으로 쉽게 접근하기 위해서 2차원 List로 저장되어 있습니다.

 

4. <HousingUiManager.cs>

우클릭 시 ui가 켜지고 , 블럭 선택 후 우클릭을 해제하면 building이 시작 됩니다.

    [SerializeField] GameObject _buildingBlockSelectUi;     // block 선택 ui 
    [SerializeField] int _typeIdx;			// 타입 인덱스
    [SerializeField] int _detailIdx;	        	// 디테일 인덱스
    
    // housing UI On Off 
    private void Update()
    {
        // 우클릭시 : Ui On
         if (Input.GetMouseButtonDown(1)) 
             _buildingBlockSelectUi.SetActive(true);
	
	// 우클릭 떼면 : Ui Off
     	else if(Input.GetMouseButtonUp(1))
        {
            // 1. Ui Off
            _buildingBlockSelectUi.SetActive(false);
            // 2. Ui off시 동작
            F_WhenHousingUiOff();
        }

    }
    
    private void F_WhenHousingUiOff() 
    {
	   // 1. index 검사
       // 1-1. 유효하지 않으면 return
        if (_nowOpenPanel < 0 || _nowOpenDetailSlot < 0)
            return;
        // 1-2. 유효하면 : building 시작
        else 
            HousingManager.Instance.F_BuildingMovement(_typeIdx , _detailIdx);
    }
    
    public void F_SetBlockNum(v_type , v_detail)
    {
    	this._typeIdx = v_type;
        this._detailIdx = v_detail;
    }

 



https://github.com/churush912837465

 

churush912837465 - Overview

churush912837465 has 4 repositories available. Follow their code on GitHub.

github.com

 

[시스템 구성 전]

1. 한글로된 빌딩 snap 기능이 없어서 검색을 해보다가 connector를 사용하여  raycast를 사용한 빌딩 시스템을 발견.

2. 현재 프로젝트에는 위의 방법으로 하는것은 어렵다고 판단.

3. Snap 구현은 비슷하게 구현하되, 많은 부분의 로직을 재구성 하였다.


[ 빌딩 Snap 로직 ]

1. 플레이어가 raycast 한다.

2. 플레이어가 raycast하는 layer은 현재 건축하고 있는 블럭이 무엇인지에 따라 바뀐다 (ex. 바닥을 설치하고 있으면 raycast할 layer은 floorConnectorLayer)

3. 현재 블럭의 위치는 Raycast hit 위치가 된다. ⬅️ Snap의 포인트  

4. 우클릭시 설치한다.

      4-1. 블럭이 올바른 위치에 있는가 ? 

      4-2. 블럭이 다른 오브젝트와 충돌 했는가 ?

 

[ Conenctor 로직 1차]

1.  블럭 오브젝트는 하위에 가능한 모든 커넥터들을 오브젝트로 가진다.

1-1. 설치 시 커넥터와 '설치가 완료된 블럭'이랑 겹치면, 해당 커넥터는 Destory 한다. 

실제 계획할 때 적었던 메모
하이어러키창으로 보면 이렇다. 상위 오브젝트 밑에 많은 오브젝트가 들어있어 깔끔해 보이진 않았다.

[ Conenctor 로직 2차 ]

1. 1차에서 오브젝트로 가지고 있지 말고 Vector3 위치값으로 가지고 있으면 되지 않을까? 

2. List< Tuple< ConnectorType , Vector3 > > 라는 리스트를 선언 후

   2-1. 커넥터가 어떤 타입인지 ( = 어떤 레이어를 가지게 될 건지 ) 

   2-2. 블럭 기준 어느 위치에 존재하는지에 대한 위치값 을 저장하였다.

   

[Connector 최종 로직]

<블럭 설치>

1. 블럭이 설치될 때 블럭 type에 해당하는 ConnectorGroup안의 List<Tuple<ConnectorType , Vector3>>  리스트의 위치값을 Physics.OverlapSphere 로 검사한다.

2. 건축이 완료된 블럭이 검사되면 ? or 이미 커넥터가 검사되면? ➡️ 커넥터를 생성한다.

3. 그 외 아무것도 검사되지 않으면 ? ➡️ 커넥터를 생성한다.

<블럭 삭제>

1. 삭제할 블럭 type에 해당하는 ConnectorGroup안의 List<Tuple<ConnectorType , Vector3>>  리스트의 위치값을 Physics.OverlapSphere 로 검사한다.

2. Connector Layer가 검사되면 ? ➡️ List에 추가한다.

> 해당 커넥터를 사용하는 블럭이 있다면 삭제하지 않고, 사용하는 블럭이 없다면 삭제한다.

3. List에 추가된 커넥터가 하나의 블럭이라 가정하고, 블럭 type에 해당하는 ConnectorGroup안의 List<tuple>  리스트의 위치값을 Physics.OverlapSphere 로 검사한다.

       3-1. 해당 위치에 '건축이 완료된 블럭' 이 검사되면 ? ➡️ 커넥터는 삭제하지 않는다

       3-2. 해당 위치에 '건축이 완료된 블럭' 이 검사되지 않으면 ? ➡️ 커넥터 삭제 


실제 구현 영상

대표이미지

 


[다음편]

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

 


https://github.com/churush912837465

 

churush912837465 - Overview

churush912837465 has 4 repositories available. Follow their code on GitHub.

github.com

 

+ Recent posts