조작부 아랫줄을 하드 드롭·홀드·소프트 드롭 3등분으로

아랫줄이 좌(액션)/우(이동) 두 컬럼에 걸쳐 있어 하드 드롭·홀드는 각각
화면의 28.7%, 소프트 드롭은 42.6% 를 차지했다. 정확히 1/3 씩 주려면 그
줄이 전체 폭을 써야 하므로, ControlPad 바깥을 Row → Column 으로 바꿔
윗줄(회전|이동)과 아랫줄을 각각 통짜 Row 로 깔았다. 윗줄 비율은 그대로다.

왼손 배치에서는 아랫줄도 좌우를 뒤집어 소프트 드롭이 왼쪽으로 간다.

검증(1080px 폭 실측): 아랫줄 버튼 폭 326/327/327px 로 균등. 윗줄은
↺199 ↻200 180°133 ◀202 ▶202 로 이전과 동일. 새 위치에서 홀드 탭 시
패널 유채색 0 → 5474, 하드 드롭 탭 시 넥스트 47584px 갱신,
소프트 드롭 길게 누름 시 낙하 속도 152 → 1137 px/s, 좌우 이동은
한 번에 정확히 한 칸(76px). 가로 화면은 구조가 달라 영향 없음(확인함).
엔진 테스트 28개 통과. 스토어 스크린샷 2장 교체.
This commit is contained in:
2026-09-29 14:41:23 +09:00
parent 43c8ccd6d2
commit 5464b53f3f
3 changed files with 38 additions and 27 deletions

View File

@@ -318,50 +318,61 @@ private fun MiniPiece(type: Char) {
@Composable @Composable
private fun ControlPad(state: GameState, lefty: Boolean, scale: Float, compactHeight: Boolean) { private fun ControlPad(state: GameState, lefty: Boolean, scale: Float, compactHeight: Boolean) {
val h = ((if (compactHeight) 130 else 168) * scale).dp val h = ((if (compactHeight) 130 else 168) * scale).dp
Row( val gap = (8 * scale).dp
// 아랫줄 3개(하드 드롭·홀드·소프트 드롭)를 정확히 1/3 씩 주려면 그 줄이
// 화면 전체 폭을 차지해야 한다. 그래서 좌/우 컬럼으로 쪼개지 않고
// 바깥을 Column 으로 두어 윗줄·아랫줄을 각각 통짜 Row 로 깐다.
Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.height(h) .height(h)
.padding(horizontal = (10 * scale).dp, vertical = (6 * scale).dp), .padding(horizontal = (10 * scale).dp, vertical = (6 * scale).dp),
horizontalArrangement = Arrangement.spacedBy((8 * scale).dp), verticalArrangement = Arrangement.spacedBy(gap),
) { ) {
val actions: @Composable (Modifier) -> Unit = { m -> // ── 윗줄: 회전 | 좌우 이동 ──
// 회전은 위쪽 한 줄, 아래는 하드 드롭 + 홀드를 반씩 나눈다. Row(Modifier.weight(1.4f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(gap)) {
// 두 컬럼의 행 높이 비율(1.4 : 1)을 맞춰야 좌우가 나란히 떨어진다. val rotates: @Composable (Modifier) -> Unit = { m ->
Column(m.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy((8 * scale).dp)) { Row(m.fillMaxHeight(), horizontalArrangement = Arrangement.spacedBy(gap)) {
Row(Modifier.weight(1.4f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy((8 * scale).dp)) { // 180° 는 폭을 유지해야 해서 4등분 중 1칸을 그대로 두고,
// HOLD 가 아랫줄로 내려가며 빈 한 칸을 회전 두 개가 나눠 갖는다. // ↺·↻ 가 1.5 씩 받아 3/8 폭이 된다.
// 180° 는 폭을 유지해야 하므로 4등분 중 1칸(weight 1)을 그대로
// 두고, ↺·↻ 가 1.5 씩 받아 3/8 폭이 된다.
CtrlButton("↺", Modifier.weight(1.5f).fillMaxHeight(), scale) { state.rotate("ccw") } CtrlButton("↺", Modifier.weight(1.5f).fillMaxHeight(), scale) { state.rotate("ccw") }
CtrlButton("↻", Modifier.weight(1.5f).fillMaxHeight(), scale) { state.rotate("cw") } CtrlButton("↻", Modifier.weight(1.5f).fillMaxHeight(), scale) { state.rotate("cw") }
CtrlButton("180°", Modifier.weight(1f).fillMaxHeight(), scale) { state.rotate("180") } CtrlButton("180°", Modifier.weight(1f).fillMaxHeight(), scale) { state.rotate("180") }
} }
Row(Modifier.weight(1f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy((8 * scale).dp)) {
CtrlButton("하드 드롭", Modifier.weight(1f).fillMaxHeight(), scale, accent = true) { state.hardDrop() }
CtrlButton("HOLD", Modifier.weight(1f).fillMaxHeight(), scale) { state.hold() }
}
}
} }
val moves: @Composable (Modifier) -> Unit = { m -> val moves: @Composable (Modifier) -> Unit = { m ->
Column(m.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy((8 * scale).dp)) { Row(m.fillMaxHeight(), horizontalArrangement = Arrangement.spacedBy(gap)) {
Row(Modifier.weight(1.4f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy((8 * scale).dp)) {
HoldCtrl("◀", Modifier.weight(1f).fillMaxHeight(), scale, { state.press(-1) }, { state.release(-1) }) HoldCtrl("◀", Modifier.weight(1f).fillMaxHeight(), scale, { state.press(-1) }, { state.release(-1) })
HoldCtrl("▶", Modifier.weight(1f).fillMaxHeight(), scale, { state.press(1) }, { state.release(1) }) HoldCtrl("▶", Modifier.weight(1f).fillMaxHeight(), scale, { state.press(1) }, { state.release(1) })
} }
HoldCtrl("▼ 소프트", Modifier.weight(1f).fillMaxWidth(), scale, { state.setSoft(true) }, { state.setSoft(false) })
} }
}
// 액션쪽이 한 줄에 버튼 4개라 폭을 더 준다. 1.1 이면 버튼이 세로로 길쭉해져
// 엄지로 누르기 불편했다.
if (lefty) { if (lefty) {
moves(Modifier.weight(1f)) moves(Modifier.weight(1f))
actions(Modifier.weight(1.35f)) rotates(Modifier.weight(1.35f))
} else { } else {
actions(Modifier.weight(1.35f)) rotates(Modifier.weight(1.35f))
moves(Modifier.weight(1f)) moves(Modifier.weight(1f))
} }
} }
// ── 아랫줄: 하드 드롭 · 홀드 · 소프트 드롭 3등분 ──
Row(Modifier.weight(1f).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(gap)) {
val third = Modifier.weight(1f).fillMaxHeight()
val hardDrop: @Composable (Modifier) -> Unit = { m ->
CtrlButton("하드 드롭", m, scale, accent = true) { state.hardDrop() }
}
val hold: @Composable (Modifier) -> Unit = { m -> CtrlButton("HOLD", m, scale) { state.hold() } }
val soft: @Composable (Modifier) -> Unit = { m ->
HoldCtrl("▼ 소프트", m, scale, { state.setSoft(true) }, { state.setSoft(false) })
}
// 왼손 배치에서는 좌우를 뒤집는다 (소프트가 왼쪽 엄지 쪽으로).
if (lefty) {
soft(third); hold(third); hardDrop(third)
} else {
hardDrop(third); hold(third); soft(third)
}
}
}
} }
@Composable @Composable

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 60 KiB