벽 근처 배치 전략 시스템
목차
1. 시스템 요구 사항
건축 시스템에서 일부 가구나 오브젝트는 맵 어디든 자유롭게 놓을 수 있는 것이 아니라, 반드시 벽과 인접한 위치에만 배치되어야 한다.
이때 벽 근처 조건은 단순히 주변에 벽 데이터가 존재하는지만 보는 것이 아니라, 현재 오브젝트의 크기와 회전까지 고려했을 때 실제로 벽에 붙어 있는지, 그리고 그 배치가 벽이나 벽 내부 오브젝트를 비정상적으로 가로지르지 않는지까지 함께 판단해야 한다.
또한 이 전략은 완전히 새로운 선택 시스템을 따로 만드는 것이 아니라, 이미 구현되어 있는 자유 배치 전략의 흐름을 최대한 재사용하면서 벽 인접 조건만 추가하는 방향으로 동작한다.
입력 처리 구조는 자유 배치 전략과 거의 같지만, 배치 가능 여부를 판단하는 규칙만 더 강하게 만드는 방식이다.
따라서 NearWallPlacementStrategy는 현재 마우스 위치를 Grid 좌표로 해석하고, 위치가 바뀌었을 때만 선택 데이터를 다시 만들며, 그 위치가 유효한 범위 안에 있는지, 현재 레이어가 비어 있는지, 벽 또는 벽 내부 오브젝트와 실제로 인접해 있는지, 그리고 벽/벽 내부 구조물을 가로지르지 않는지를 순차적으로 검사해야 한다.
이 전략은 자유 배치 전략 위에 벽 인접 조건을 추가한 특수화 전략이라고 볼 수 있다.
2. 흐름도
StartSelection() // 부모 클래스 구현 사용
↓
lastDetectedPosition 초기화
↓
ModifySelection() 즉시 호출
↓
현재 마우스 위치를 Grid 좌표로 변환
↓
이전 감지 위치와 비교
├─ 같으면 종료
└─ 다르면 선택 데이터 초기화
↓
현재 Grid/World 좌표 기록
↓
ValidatePlacement()
↓
Grid 범위 유효성 검사
↓
현재 배치 레이어 비어 있음 검사
↓
벽 인접 여부 검사
↓
벽 내부 오브젝트 인접 여부 보조 검사
↓
벽 교차 여부 검사
↓
벽 내부 오브젝트 교차 여부 검사
↓
PlacementValidity 저장
↓
FinishSelection() // 부모 클래스 구현 사용
↓
lastDetectedPosition 초기화
이 전략의 흐름은 자유 배치 전략과 매우 비슷하다.
시작 시점에 별도의 시작점을 저장하지 않고, 현재 마우스가 가리키는 위치 하나를 즉시 선택 대상으로 해석한다.
이후 마우스가 다른 셀로 이동했을 때만 선택 데이터를 다시 계산한다.
차이는 검증 단계에 있다.
자유 배치 전략은 범위와 점유 여부, 벽 교차 여부까지만 검사했다면, 이 전략은 그 중간에 실제로 벽 근처인가라는 조건을 추가한다.
그래서 흐름상 입력 처리나 선택 방식은 단순하지만, 검증 단계가 더 강한 제약을 가진다고 볼 수 있다.
3. 구현
3.1. 클래스 구조와 상속 관계
public class NearWallPlacementStrategy : FreeObjectPlacementStrategy이 클래스는 SelectionStrategy를 직접 상속하지 않고 FreeObjectPlacementStrategy를 상속한다.
이 한 줄이 이 전략의 성격을 가장 잘 보여준다.
NearWallPlacementStrategy는 완전히 별개의 배치 방식이 아니라, 기본적으로는 자유 배치 전략과 같은 선택 흐름을 사용하되 그 위에 벽 근처 배치 규칙만 덧씌운 구조라는 뜻이다.
이 상속 구조의 장점은 매우 명확하다.
자유 배치 전략에 이미 구현되어 있는 공통 로직, 예를 들어 StartSelection, FinishSelection, lastDetectedPosition 활용 방식, placementData와 wallPlacementData, inWallPlacementData 보유 구조를 그대로 재사용할 수 있다.
만약 이 클래스를 SelectionStrategy에서 바로 다시 만들었다면, 자유 배치 전략과 거의 같은 코드를 다시 반복하게 되었을 가능성이 크다.
반대로 단점은 부모 클래스 구현에 강하게 의존하게 된다는 점이다.
그래서 부모 클래스의 설계를 정확히 이해하지 않으면 자식 클래스 동작을 읽기 어려울 수 있다.
하지만 현재 코드는 '자유 배치 + 벽 근처 조건 추가' 라는 관계가 아주 분명하기 때문에 이 상속 구조가 상당히 적절하다.
또한 NearWallPlacementStrategy에는 별도의 필드 선언이 없다.
이것도 중요한 코드 사실이다.
이 클래스는 자신의 상태를 추가로 가지지 않고, 부모인 FreeObjectPlacementStrategy가 이미 들고 있는 placementData, wallPlacementData, inWallPlacementData, gridManager, lastDetectedPosition을 그대로 사용한다.
다시 말해, 이 클래스의 차별점은 '같은 데이터를 다른 규칙으로 검증' 이다.
3.2. 생성자와 부모 데이터 연결
public NearWallPlacementStrategy(PlacementGridData placementData, PlacementGridData wallPlacementData,
PlacementGridData inWallPlacementData, GridManager gridManager)
: base(placementData, wallPlacementData, inWallPlacementData, gridManager)
{
}이 생성자는 코드 양은 적지만 의미는 분명하다.
NearWallPlacementStrategy는 별도의 초기화 로직을 만들지 않고, 필요한 모든 의존성을 부모 클래스인 FreeObjectPlacementStrategy 생성자에 그대로 넘긴다.
현재 오브젝트 레이어를 의미하는 placementData, 벽 데이터를 의미하는 wallPlacementData, 벽 내부 오브젝트 데이터를 의미하는 inWallPlacementData, 그리고 좌표 변환을 담당하는 gridManager를 모두 부모가 관리하도록 위임하는 구조다.
이렇게 작성한 이유는 NearWallPlacementStrategy가 자유 배치 전략의 기본 구조를 그대로 사용하기 때문이다.
부모 쪽에서 이미 세 가지 PlacementGridData를 모두 필드에 저장하고 있기 때문에, 자식 클래스에서 다시 같은 값을 받아 별도로 저장할 필요가 없다.
이 코드는 새로운 데이터 구조를 만들지 않고 부모의 준비 과정을 그대로 따른다.
또한 생성자 본문이 비어 있다는 점도 중요하다.
이는 이 클래스의 차이가 초기화 과정이 아니라 이후 함수 오버라이드, 즉 ModifySelection, ValidatePlacement, HandleRotation의 동작 차이에 있다는 뜻이다.
생성 시점의 구조는 자유 배치 전략과 동일하고, 실제 차별화는 실행 시점의 규칙에서 이루어진다.
3.3. StartSelection은 왜 없는가
NearWallPlacementStrategy 코드에는 StartSelection이 직접 구현되어 있지 않다.
이건 빠진 것이 아니라, 부모 클래스인 FreeObjectPlacementStrategy의 StartSelection을 그대로 상속해서 사용한다는 의미다.
부모 코드를 보면 StartSelection은 다음처럼 동작한다.
public override void StartSelection(Vector3 mousePosition, SelectionData selectionData)
{
this.lastDetectedPosition.Reset();
ModifySelection(mousePosition, selectionData);
}NearWallPlacementStrategy도 선택이 시작될 때 먼저 lastDetectedPosition을 초기화하고, 바로 자신의 ModifySelection을 호출하게 된다.
여기서 중요한 점은 StartSelection은 부모 것을 쓰지만, 그 안에서 호출되는 ModifySelection은 자식인 NearWallPlacementStrategy의 오버라이드 버전이 실행된다는 것이다.
C#의 가상 메서드 dispatch 특성 때문에, 부모 메서드 안에서 호출하더라도 실제 인스턴스 타입이 NearWallPlacementStrategy이면 자식의 ModifySelection이 실행된다.
이 구조는 꽤 좋은 설계다. 선택 시작 자체는 자유 배치 전략과 동일하고, 실제 선택을 어떻게 갱신할지는 자식 전략이 다르게 가져갈 수 있기 때문이다.
그래서 NearWallPlacementStrategy는 StartSelection을 굳이 다시 쓰지 않고도 자신의 선택 시작 흐름을 자연스럽게 구성할 수 있다.
3.4. 선택 진행 처리
public override bool ModifySelection(Vector3 mousePosition, SelectionData selectionData)
{
Vector3Int tempPos = gridManager.GetCellPosition(mousePosition, selectionData.PlacedItemData.objectPlacementType);
if (lastDetectedPosition.TryUpdatingPositon(tempPos))
{
// 선택 데이터를 초기화
selectionData.Clear();
selectionData.AddToWorldPositions(gridManager.GetWorldPosition(lastDetectedPosition.GetPosition()));
selectionData.AddToGridPositions(lastDetectedPosition.GetPosition());
selectionData.PlacementValidity = ValidatePlacement(selectionData);
return true;
}
return false;
}이 함수는 NearWallPlacementStrategy에서 실제 선택 상태를 갱신할 때 호출되는 핵심 함수이다.
StartSelection이 시작 시점에 한 번 호출하고, 이후에는 마우스가 움직일 때마다 PlacementSelector를 통해 반복적으로 호출된다.
이 함수는 현재 마우스 위치를 지금 선택 결과로 반영할 것인지를 결정하는 실시간 선택 갱신 함수다.
첫 줄에서는 현재 마우스 위치를 Grid 좌표로 변환한다.
Vector3Int tempPos = gridManager.GetCellPosition(mousePosition, selectionData.PlacedItemData.objectPlacementType);이 코드는 단순히 화면상의 마우스 좌표를 셀 인덱스로 바꾸는 것처럼 보이지만, 실제로는 현재 오브젝트의 배치 타입까지 반영하는 좌표 해석이다.
selectionData.PlacedItemData.objectPlacementType를 넘기는 이유는 GridManager가 셀 중심 기준인지, 경계 기준인지 같은 배치 규칙을 함께 고려해야 하기 때문이다.
NearWallPlacementStrategy는 일반 가구 배치 계열이므로 보통 셀 중심 기반이지만, 이런 식으로 타입을 명시해 두면 같은 좌표 변환 함수를 전략 전체에서 일관되게 재사용할 수 있다.
그 다음 조건문은 성능과 직결되는 중요한 필터다.
if (lastDetectedPosition.TryUpdatingPositon(tempPos))TryUpdatingPositon은 내부적으로 이전에 처리한 Grid 좌표와 현재 좌표를 비교해서, 실제로 값이 바뀌었을 때만 true를 반환한다.
NearWallPlacementStrategy는 벽 인접 여부까지 검사하기 때문에, 같은 셀 위에 마우스가 그대로 있는 동안에도 ValidatePlacement를 계속 돌리면 불필요한 계산이 반복된다.
Unity 입력 시스템은 보통 Update 기반으로 돌아가므로, 마우스가 미세하게 흔들리더라도 같은 셀 안이면 같은 결과가 계속 나올 수 있다.
그래서 이 조건은 동일한 셀에서는 선택을 다시 계산하지 않는다는 최적화 장치다.
반환값이 bool인 이유도 여기에 있다.
상위 PlacementSelector는 true일 때만 OnSelectionChanged를 발생시키므로, 불필요한 프리뷰 갱신과 이벤트 전송까지 함께 줄일 수 있다.
위치가 바뀌었다면 가장 먼저 selectionData.Clear()를 호출한다.
이 전략은 자유 배치 전략과 마찬가지로 현재 위치 하나를 기준으로 선택 결과를 다시 구성하는 방식이기 때문에, 이전 프레임의 선택 데이터를 유지하면 안 된다.
SelectionData 안에는 Grid 위치, World 위치, 회전 정보, 유효성 값 등이 들어 있으므로, 이 초기화는 단순 정리가 아니라 현재 프레임 기준으로 선택을 다시 만들기 위한 전처리 단계다.
그 다음 현재 선택 위치를 World 좌표와 Grid 좌표로 각각 저장한다.
selectionData.AddToWorldPositions(gridManager.GetWorldPosition(lastDetectedPosition.GetPosition()));
selectionData.AddToGridPositions(lastDetectedPosition.GetPosition());여기서도 Grid 좌표와 World 좌표를 분리해서 저장하는 구조가 유지된다.
Grid 좌표는 이후 PlacementValidator가 충돌 검사와 유효성 검사를 수행할 때 쓰이고, World 좌표는 프리뷰 오브젝트를 실제 씬에서 움직이거나 나중에 배치 실행 시 Transform 위치를 정할 때 쓰인다.
이 두 데이터는 같은 위치를 다른 좌표계로 표현한 것이지만, 사용 목적이 전혀 다르기 때문에 둘 다 따로 보관하는 것이다.
마지막으로 이 전략의 핵심이자 자유 배치 전략과의 차별점인 검증이 실행된다.
selectionData.PlacementValidity = ValidatePlacement(selectionData);NearWallPlacementStrategy는 ModifySelection 안에서 회전값을 별도로 세팅하지 않는다.
자유 배치 전략과 달리 여기서는 현재 선택 위치를 먼저 기록하고, 실제 배치 가능 여부는 ValidatePlacement에서 벽 인접 조건까지 포함해 한 번에 판단한다.
따라서 ModifySelection의 역할은 현재 위치를 선택 데이터로 구성하고, 그 결과가 유효한지 판단하는 트리거를 거는 것이다.
3.5. 배치 검증
protected override bool ValidatePlacement(SelectionData selectionData)
{
// 위치가 유효한지 확인
bool validity = PlacementValidator.CheckIfPositionsAreValid(
selectionData.GetSelectedGridPositions(),
placementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
// 이전 검사가 TRUE를 반환한 경우에만 확인을 수행
if (validity)
{
validity = PlacementValidator.CheckIfPositionsAreFree(
selectionData.GetSelectedGridPositions(),
placementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
}
if (validity)
{
// 객체를 벽 근처에 배치하려고 하는지 확인
// 회전이 0도일 때 위/앞 방향을 확인
validity = PlacementValidator.CheckIfPositionsAreNearWall(
selectionData.GetSelectedGridPositions(),
wallPlacementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
if(!validity)
{
// 창문과 문 근처에 객체를 배치할 수 있게 허용
validity = PlacementValidator.CheckIfPositionsAreNearWall(
selectionData.GetSelectedGridPositions(),
inWallPlacementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
}
}
if (validity)
{
// 벽을 교차하지 않는지 확인
validity = PlacementValidator.CheckIfNotCrossingEdgeObject(
selectionData.GetSelectedGridPositions(),
wallPlacementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
}
if (validity)
{
// 벽 내부 객체와 교차하지 않는지 확인
validity = PlacementValidator.CheckIfNotCrossingEdgeObject(
selectionData.GetSelectedGridPositions(),
inWallPlacementData,
selectionData.PlacedItemData.size,
selectionData.GetSelectedPositionsGridRotation(),
selectionData.PlacedItemData.objectPlacementType.IsEdgePlacement());
}
return validity;
}이 함수는 NearWallPlacementStrategy의 핵심 규칙이 실제로 구현된 함수이며, 현재 선택된 위치가 벽 근처 배치 규칙을 만족하는지를 판단할 때 호출된다.
이 함수는 ModifySelection 내부에서 호출되므로, 선택 위치가 바뀔 때마다 다시 실행된다.
단순히 마지막에 한 번 호출되는 검사가 아니라, 프리뷰가 움직일 때마다 실시간으로 배치 가능 여부를 계산하는 함수다.
첫 번째 단계는 기본 유효성 검사다.
bool validity = PlacementValidator.CheckIfPositionsAreValid(...)이 검사는 현재 선택된 Grid 위치가 유효한 좌표인지 확인한다.
여기서 size와 rotation, IsEdgePlacement 결과까지 함께 넘기는 이유는 단순히 시작 좌표 하나만 보는 것이 아니라, 현재 오브젝트 전체의 점유 범위를 기준으로 검사해야 하기 때문이다.
예를 들어 2x1 오브젝트는 시작 셀 하나는 유효해도, 회전에 따라 옆 셀이 Grid 바깥으로 나갈 수 있다.
따라서 이 단계는 현재 배치 자체가 물리적으로 Grid 범위 안에 들어오는지를 검사하는 가장 기초적인 필터다.
두 번째 단계는 현재 오브젝트 레이어에서 해당 위치가 비어 있는지 검사하는 부분이다.
if (validity)
{
validity = PlacementValidator.CheckIfPositionsAreFree(...)
}이 검사는 첫 번째 검사가 통과했을 때만 수행된다.
이미 좌표 자체가 유효하지 않다면 점유 여부를 검사할 필요가 없기 때문이다.
이 구조는 계산량을 줄이는 데도 도움이 되고, 코드 논리도 더 명확하게 만든다.
여기서는 현재 placementData, 즉 일반 오브젝트 레이어를 기준으로 비어 있는지를 확인한다.
다시 말해 같은 종류의 오브젝트끼리 겹치지 않는지 보는 단계다.
세 번째 단계가 NearWallPlacementStrategy를 다른 전략과 구분 짓는 핵심이다.
if (validity)
{
validity = PlacementValidator.CheckIfPositionsAreNearWall(... wallPlacementData ...);
if(!validity)
{
validity = PlacementValidator.CheckIfPositionsAreNearWall(... inWallPlacementData ...);
}
}이 부분은 현재 선택 위치가 실제로 벽 근처인지를 검사한다.
먼저 wallPlacementData를 기준으로 벽 인접 여부를 확인한다.
이 검사는 현재 선택 위치와 회전 방향을 기준으로, 해당 오브젝트가 벽에 붙는 방향으로 놓이게 되는지를 판단한다.
회전이 0도일 때는 오브젝트의 앞/위 방향을 기준으로 벽 근처 조건을 해석한다.
단순히 주변 셀에 벽이 하나 있느냐가 아니라, 현재 오브젝트 방향과 벽의 상대 관계가 올바른지를 보는 것이다.
만약 일반 벽 근처로는 판단되지 않았다면, 바로 false로 끝내지 않고 inWallPlacementData를 기준으로 한 번 더 검사한다.
이것도 굉장히 중요한 코드다.
주석대로 창문과 문 같은 벽 내부 오브젝트 근처에도 배치를 허용하기 위해서다.
NearWallPlacementStrategy는 벽 근처를 오직 Wall 레이어만 의미하는 것으로 제한하지 않고, InWall 레이어까지 포함하는 더 넓은 개념으로 해석한다.
이 보조 검사가 있기 때문에, 창문이나 문 옆에 붙는 가구 같은 것도 자연스럽게 허용할 수 있다.
다시 말해 이 코드는 벽 근처 규칙을 더 실제적인 공간 규칙으로 확장하는 부분이다.
그 다음 두 단계는 교차 검사다.
if (validity)
{
validity = PlacementValidator.CheckIfNotCrossingEdgeObject(... wallPlacementData ...);
}
if (validity)
{
validity = PlacementValidator.CheckIfNotCrossingEdgeObject(... inWallPlacementData ...);
}벽 근처에 있다는 것과, 실제로 벽이나 벽 내부 오브젝트를 가로지르지 않는 것은 별개의 문제다.
예를 들어 어떤 가구는 벽에 충분히 가깝지만, 크기가 커서 일부 셀이 벽 경계나 문/창문 경계를 침범할 수 있다.
그래서 NearWallPlacementStrategy는 벽 인접 조건이 통과하더라도 마지막으로 벽과 벽 내부 오브젝트를 교차하지 않는지를 별도로 검사한다.
이 순서도 중요하다.
먼저 벽 근처 조건을 만족하는지 보고, 그 다음 그 상태에서 구조를 가로지르지 않는지 확인하는 것이다.
이 함수 전체는 '유효 좌표 → 현재 레이어 비어 있음 → 벽 근처 조건 → 벽 교차 없음 → InWall 교차 없음' 라는 다단계 필터로 구성되어 있다.
결과적으로 NearWallPlacementStrategy의 ValidatePlacement는 자유 배치 전략보다 더 강한 공간 규칙을 코드로 구현한 함수라고 볼 수 있다.
3.6. 회전 처리
public override Quaternion HandleRotation(Quaternion rotation, SelectionData selectionData)
{
selectionData.SetObjectRotation(new() { rotation });
selectionData.SetGridCheckRotation(new() { rotation });
return rotation;
}이 함수는 NearWallPlacementStrategy에서 회전을 처리할 때 호출되는 함수이다.
자유 배치 전략의 HandleRotation이 직사각형 오브젝트에 대해 0도와 90도 계열로 회전을 강하게 제한했다면, NearWallPlacementStrategy는 그런 제한을 두지 않고 입력된 rotation을 그대로 사용한다.
회전을 보정하는 것이 아니라, 현재 회전을 그대로 SelectionData에 반영하는 역할을 한다.
코드 흐름을 보면 먼저 selectionData.SetObjectRotation(new() { rotation })를 호출한다.
이는 실제 오브젝트가 시각적으로 어떤 방향을 향할지를 저장하는 부분이다.
그 다음 SetGridCheckRotation(new() { rotation })를 호출하는데, 이는 검증 단계에서 사용할 회전 기준값을 저장하는 부분이다.
현재 전략에서는 둘 다 같은 rotation을 사용하며, 이는 시각적 방향과 검증용 방향이 동일하다는 뜻이다.
이 함수가 중요한 이유는 NearWallPlacementStrategy가 벽 근처 라는 조건을 검사할 때 회전 방향을 그대로 의미 있게 사용하기 때문이다.
부모의 자유 배치 전략처럼 회전을 특정 각도로 강제 제한해 버리면, 벽을 기준으로 자연스럽게 방향을 맞추는 가구 배치가 어려워질 수 있다.
그래서 이 전략은 회전을 단순히 받아서 저장하는 방식으로 더 유연하게 처리하고 있다.
결과적으로 HandleRotation은 이 전략에서 입력 회전을 그대로 시스템 데이터에 반영하는 동기화 함수라고 볼 수 있다.
3.7. FinishSeletion은 왜 없는가
NearWallPlacementStrategy 코드에는 FinishSelection이 직접 구현되어 있지 않다.
이 역시 빠진 것이 아니라, 부모 클래스인 FreeObjectPlacementStrategy의 FinishSelection을 그대로 상속해서 사용하고 있기 때문이다.
부모의 구현은 다음과 같다.
public override void FinishSelection(SelectionData selectionData)
{
lastDetectedPosition.Reset();
}NearWallPlacementStrategy도 선택이 끝날 때 마지막 감지 위치를 초기화한다.
이 초기화가 필요한 이유는 다음 선택이 시작될 때 이전 위치 캐시가 남아 있으면, 첫 입력이 이미 처리한 위치처럼 오인될 수 있기 때문이다.
NearWallPlacementStrategy는 ModifySelection에서 lastDetectedPosition을 매우 중요한 필터로 사용하므로, FinishSelection에서 이를 초기화하는 것은 필수다.
부모 구현을 재사용한다는 것은 이 전략 역시 자유 배치 전략과 동일한 선택 생명주기를 따르고 있다는 뜻이기도 하다.
4. 개발 의도
NearWallPlacementStrategy의 핵심 설계 의도는 자유 배치 전략의 입력 구조를 유지하면서, 그 위에 벽 근처여야 한다는 공간 제약을 추가하는 것이다.
선택 시작, 위치 갱신, 마지막 위치 캐싱 같은 흐름은 자유 배치 전략과 동일하게 가져가고, 실제 차별점은 ValidatePlacement에서 더 강한 검증 규칙을 적용하는 방식으로 구현한 것이다.
이런 구조 덕분에 입력 시스템과 SelectionData 구성 방식은 재사용하면서도, 게임 플레이 측면에서는 전혀 다른 배치 체험을 만들 수 있다.
또한 이 전략은 단순히 벽과 인접한지만 보는 것이 아니라, 벽 내부 오브젝트 근처도 허용하고, 마지막에는 벽/벽 내부 오브젝트를 가로지르지 않는지도 검사한다.
이 덕분에 NearWallPlacementStrategy는 '벽에 붙는 오브젝트' 라는 개념을 단순 인접성 수준이 아니라, 실제 게임 공간에서 자연스럽고 충돌 없는 배치 규칙으로 확장한 전략이라고 볼 수 있다.
결과적으로 이 클래스는 자유 배치 전략을 기반으로 하면서도, 환경 의존형 오브젝트 배치를 구현하기 위한 특수화 전략으로 설계되었다.
