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;
}
}
: 최상위 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를 생성합니다.
// 커넥터 구조체
[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을 배열을 통해 쉽게 접근할 수 있습니다.
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);
}
}
⚡ dfs이해가 안되서 하나하나 그려봤다 : dp [N-1][M-1]을 출력하는 것이 아닌 dp[0][0]를 출력해야 한다 (내가 생각한거랑 반대였다....)
코드
int N , M;
const int MAX = 501;
int arr[MAX][MAX];
int dp[MAX][MAX];
int dx[] = {0,1,0,-1};
int dy[] = {1,0,-1,0};
int dfs(int x, int y) // dp는 -1로 초기화되어있음
{
if (x == N - 1 && y == M - 1) // 도착지에 도착하면 ?
{
return 1;
}
if (dp[x][y] != -1) //방문했으면?
{
return dp[x][y];
}
dp[x][y] = 0; //방문은 했으니까 0으로
for (int i = 0; i < 4; i++)
{
int mx = x + dx[i];
int my = y + dy[i];
if (mx < 0 || my <0 || mx >= N || my >M)
{
continue;
}
// 계단수여서 다음으로 넘어갈수 있으면?
if (arr[mx][my] < arr[x][y])
{
dp[x][y] = dp[x][y] + dfs(mx, my);
}
}
// 최종 정답을 return?
return dp[x][y];
}
int main()
{
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
//백준 1520
cin >> N >> M;
for(int i =0;i<N;i++)
{
for (int j = 0; j < M; j++)
{
cin >> arr[i][j];
}
}
memset(dp, -1, sizeof(dp));
// dp[][]가 -1 : 아직 방문하지 x
// dp[][]가 0 : 0개
int answer = dfs(0, 0);
cout << answer;
}