BT5-Dev #99: EnemyWall Layer 18·투명벽 자동 생성 (PD 명시 채택)

PD 명시 (2026-05-08): "낭떠러지 앞에 몬스터만 지나갈 수 없는 투명한 벽을 세우면 안돼?"

본 PM 17회 가설 누적 부정확 자인 후 PD 직접 단순 해결 채택.

변경:
1. ProjectSettings/TagManager.asset:
   - Layer 18 = 'EnemyWall' 신규 추가
2. GameOptimizer.cs Init():
   - Physics2D.IgnoreLayerCollision(13, 18, true) — Player ↔ EnemyWall 충돌 OFF (Player 통과)
3. GameOptimizer.cs SetupCliffWalls() 신규:
   - 모든 Tilemap (Level·AutoForeground·PD Foreground) Tile 영역 순회
   - Tile 좌·우 인접 영역이 모든 Tilemap에 Tile X 시 = 절벽 가장자리
   - 가장자리 위치에 BoxCollider2D GameObject (CliffWall) 자동 생성
   - Layer 18 (EnemyWall) + size (0.1×3) + offset (0, 1)
   - parent = CliffWalls GameObject (그룹 영역)

효과:
- Player ↔ EnemyWall 충돌 OFF → Player 자유 통과
- Enemy ↔ EnemyWall 충돌 ON → Enemy 절벽 가장자리 도달 시 차단
- 알고리즘 부정합 무관 (물리 영역 차단)
- BT98 R1 방어 영역 = 보조 (투명벽 차단으로 X 도달 가설)
This commit is contained in:
깃 관리자 2026-05-08 14:55:16 +09:00
parent b3cbbdbf40
commit 63cecf04ec
2 changed files with 71 additions and 1 deletions

View File

@ -29,6 +29,8 @@ namespace Platformer.Mechanics
// Layer Matrix: Player(13) ↔ Enemy(14) 충돌 OFF. // Layer Matrix: Player(13) ↔ Enemy(14) 충돌 OFF.
Physics2D.IgnoreLayerCollision(13, 14, true); Physics2D.IgnoreLayerCollision(13, 14, true);
// BT99 — Player(13) ↔ EnemyWall(18) 충돌 OFF (투명벽은 Enemy만 차단).
Physics2D.IgnoreLayerCollision(13, 18, true);
} }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
@ -150,10 +152,78 @@ namespace Platformer.Mechanics
var lvlTc = levelGo.GetComponent<UnityEngine.Tilemaps.TilemapCollider2D>(); var lvlTc = levelGo.GetComponent<UnityEngine.Tilemaps.TilemapCollider2D>();
if (lvlTc != null) lvlTc.ProcessTilemapChanges(); if (lvlTc != null) lvlTc.ProcessTilemapChanges();
Debug.Log($"[GameOptimizer] AutoForeground moved={movedHigh + movedSmall} (high={movedHigh} small={movedSmall} threshold={airThresholdY:F2}) / Layer16 applied={applied} levelKept0={levelKept} excluded={excluded}"); Debug.Log($"[GameOptimizer] AutoForeground moved={movedHigh + movedSmall} (high={movedHigh} small={movedSmall} threshold={airThresholdY:F2}) / Layer16 applied={applied} levelKept0={levelKept} excluded={excluded}");
// BT99 — 절벽 영역 자동 검출·투명벽 (EnemyWall Layer 18) GameObject 자동 생성
SetupCliffWalls(levelTilemap, fgTilemap, pdForeground);
} }
} }
} }
/// <summary>
/// BT99 — 절벽 영역 자동 검출 + 투명벽 GameObject 생성 (Enemy 차단·Player 통과).
/// 모든 Tilemap (Level·AutoForeground·PD Foreground) 영역에서 Tile 좌·우 인접 영역이 모든 Tilemap에 Tile X 시 = 절벽 가장자리.
/// </summary>
static void SetupCliffWalls(UnityEngine.Tilemaps.Tilemap levelTm, UnityEngine.Tilemaps.Tilemap autoFgTm, GameObject pdFg)
{
UnityEngine.Tilemaps.Tilemap pdFgTm = pdFg != null ? pdFg.GetComponent<UnityEngine.Tilemaps.Tilemap>() : null;
GameObject wallParent = GameObject.Find("CliffWalls");
if (wallParent == null) wallParent = new GameObject("CliffWalls");
int wallCount = 0;
var allTilemaps = new System.Collections.Generic.List<UnityEngine.Tilemaps.Tilemap>();
if (levelTm != null) allTilemaps.Add(levelTm);
if (autoFgTm != null) allTilemaps.Add(autoFgTm);
if (pdFgTm != null) allTilemaps.Add(pdFgTm);
foreach (var tm in allTilemaps)
{
var bounds = tm.cellBounds;
for (int x = bounds.xMin; x <= bounds.xMax; x++)
{
for (int y = bounds.yMin; y <= bounds.yMax; y++)
{
var pos = new Vector3Int(x, y, 0);
if (!tm.HasTile(pos)) continue;
bool leftCliff = !HasTileInAny(allTilemaps, pos + Vector3Int.left);
bool rightCliff = !HasTileInAny(allTilemaps, pos + Vector3Int.right);
if (!leftCliff && !rightCliff) continue;
Vector3 worldPos = tm.CellToWorld(pos) + new Vector3(0.5f, 0.5f, 0f); // cell center
if (leftCliff)
{
CreateCliffWall(wallParent, worldPos + new Vector3(-0.5f, 0f, 0f));
wallCount++;
}
if (rightCliff)
{
CreateCliffWall(wallParent, worldPos + new Vector3(0.5f, 0f, 0f));
wallCount++;
}
}
}
}
Debug.Log($"[GameOptimizer] CliffWalls (EnemyWall Layer 18) 생성: {wallCount}개");
}
static bool HasTileInAny(System.Collections.Generic.List<UnityEngine.Tilemaps.Tilemap> tms, Vector3Int pos)
{
foreach (var tm in tms) if (tm != null && tm.HasTile(pos)) return true;
return false;
}
static void CreateCliffWall(GameObject parent, Vector3 worldPos)
{
GameObject wall = new GameObject("CliffWall");
wall.transform.SetParent(parent.transform, false);
wall.transform.position = worldPos;
wall.layer = 18; // EnemyWall
var box = wall.AddComponent<BoxCollider2D>();
box.size = new Vector2(0.1f, 3f); // 좁고 높은 영역
box.offset = new Vector2(0f, 1f); // Tile 위 영역 차단 (지면 위 1.5m)
}
/// <summary> /// <summary>
/// 작은 공중 발판 판별: 위·아래 인접 Tile이 모두 빈 공간 + 가로 연속 길이 maxWidth 이하. /// 작은 공중 발판 판별: 위·아래 인접 Tile이 모두 빈 공간 + 가로 연속 길이 maxWidth 이하.
/// 일반 지면(10+ tile 가로) 잘못 분류 방지. /// 일반 지면(10+ tile 가로) 잘못 분류 방지.

View File

@ -24,7 +24,7 @@ TagManager:
- -
- -
- -
- - EnemyWall
- -
- -
- -