Начать этап 3 настройками управления

This commit is contained in:
2026-08-18 14:44:30 +03:00
parent 80822b74b1
commit 44067abde3
12 changed files with 492 additions and 22 deletions
@@ -38,7 +38,7 @@ public final class GameView extends View implements Runnable {
private static final float RESPAWN_SECONDS = 0.78f;
private static final int MAX_CORPSES = 16;
private enum Screen { LEVELS, PLAYING, COMPLETE }
private enum Screen { LEVELS, SETTINGS, PLAYING, COMPLETE }
private enum DeathType { SPIKE, SAW, FREEZE, ELECTRIC, PIT }
public static final class AutomationSnapshot {
@@ -160,6 +160,7 @@ public final class GameView extends View implements Runnable {
private final LinearGradient skyGradient = new LinearGradient(
0, 0, 0, VIEW_HEIGHT, Color.rgb(20, 29, 55), Color.rgb(110, 81, 91), Shader.TileMode.CLAMP);
private final ProgressStore progress;
private final ControlSettingsStore controlSettingsStore;
private final SparseArray<PointF> pointers = new SparseArray<>();
private final Player player = new Player();
private final ArrayList<Corpse> corpses = new ArrayList<>();
@@ -185,10 +186,13 @@ public final class GameView extends View implements Runnable {
private float canvasScale = 1f;
private float canvasOffsetX;
private float canvasOffsetY;
private ControlSettings controlSettings;
public GameView(Context context) {
super(context);
progress = new ProgressStore(context);
controlSettingsStore = new ControlSettingsStore(context);
controlSettings = controlSettingsStore.load();
paint.setTypeface(android.graphics.Typeface.create("sans", android.graphics.Typeface.NORMAL));
stroke.setStyle(Paint.Style.STROKE);
stroke.setStrokeCap(Paint.Cap.ROUND);
@@ -227,6 +231,10 @@ public final class GameView extends View implements Runnable {
public boolean handleBack() {
if (screen == Screen.LEVELS) return false;
if (screen == Screen.SETTINGS) {
screen = Screen.LEVELS;
return true;
}
if (screen == Screen.COMPLETE) {
screen = Screen.LEVELS;
return true;
@@ -315,11 +323,11 @@ public final class GameView extends View implements Runnable {
boolean previous = Boolean.TRUE.equals(previousSwitchContact.get(contactKey));
if (item.type == LevelDefinition.SwitchType.TOGGLE && contact && !previous) {
toggleSignals.put(item.signalId, !Boolean.TRUE.equals(toggleSignals.get(item.signalId)));
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
}
if (item.type == LevelDefinition.SwitchType.TIMED && contact && !previous) {
timedSignals.put(item.signalId, item.durationSeconds);
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
}
previousSwitchContact.put(contactKey, contact);
}
@@ -404,7 +412,7 @@ public final class GameView extends View implements Runnable {
player.onGround = false;
player.coyote = 0f;
player.jumpBuffer = 0f;
performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
emitHaptic(HapticFeedbackConstants.CLOCK_TICK);
}
player.vy += GRAVITY * dt;
@@ -577,7 +585,7 @@ public final class GameView extends View implements Runnable {
player.supportIndex = -1;
deaths++;
respawnTimer = RESPAWN_SECONDS;
performHapticFeedback(Build.VERSION.SDK_INT >= 27
emitHaptic(Build.VERSION.SDK_INT >= 27
? HapticFeedbackConstants.LONG_PRESS : HapticFeedbackConstants.KEYBOARD_TAP);
if (type != DeathType.PIT) {
@@ -619,7 +627,7 @@ public final class GameView extends View implements Runnable {
screen = Screen.COMPLETE;
pointers.clear();
deriveControls();
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
emitHaptic(HapticFeedbackConstants.LONG_PRESS);
}
private RectF playerRect() {
@@ -650,6 +658,8 @@ public final class GameView extends View implements Runnable {
canvas.scale(canvasScale, canvasScale);
if (screen == Screen.LEVELS) {
drawLevelSelect(canvas);
} else if (screen == Screen.SETTINGS) {
drawSettings(canvas);
} else {
drawGame(canvas);
if (screen == Screen.COMPLETE) drawComplete(canvas);
@@ -917,21 +927,27 @@ public final class GameView extends View implements Runnable {
drawHudButton(canvas, new RectF(1608, 26, 1770, 112), "Restart", false);
drawHudButton(canvas, new RectF(1790, 26, 1892, 112), "II", false);
drawControlCircle(canvas, 145, 925, 92, "‹", leftPressed);
drawControlCircle(canvas, 365, 925, 92, "›", rightPressed);
drawControlCircle(canvas, 1760, 900, 116, "▲", jumpPressed);
float controlScale = controlSettings.controlScale();
drawControlCircle(canvas, 145, 925, 92 * controlScale, "‹", leftPressed);
drawControlCircle(canvas, 365, 925, 92 * controlScale, "›", rightPressed);
drawControlCircle(canvas, 1760, 900, 116 * controlScale, "▲", jumpPressed);
if (!player.alive) {
drawCenteredText(canvas, "Another volunteer is on the way…", VIEW_WIDTH * 0.5f, 235, 38, Color.rgb(250, 222, 153));
}
}
private void drawControlCircle(Canvas canvas, float x, float y, float radius, String label, boolean active) {
paint.setColor(active ? Color.argb(220, 245, 190, 74) : Color.argb(120, 22, 28, 43));
int inactiveFillAlpha = Math.round(190f * controlSettings.controlOpacity());
int inactiveStrokeAlpha = Math.round(230f * controlSettings.controlOpacity());
paint.setColor(active ? Color.argb(220, 245, 190, 74)
: Color.argb(inactiveFillAlpha, 22, 28, 43));
canvas.drawCircle(x, y, radius, paint);
stroke.setColor(active ? Color.rgb(255, 232, 169) : Color.argb(150, 240, 240, 245));
stroke.setColor(active ? Color.rgb(255, 232, 169)
: Color.argb(inactiveStrokeAlpha, 240, 240, 245));
stroke.setStrokeWidth(5f);
canvas.drawCircle(x, y, radius - 5, stroke);
drawCenteredText(canvas, label, x, y + 21, 68, active ? Color.rgb(34, 36, 45) : Color.WHITE);
drawCenteredText(canvas, label, x, y + 21, 68 * controlSettings.controlScale(),
active ? Color.rgb(34, 36, 45) : Color.WHITE);
}
private void drawLevelSelect(Canvas canvas) {
@@ -968,9 +984,36 @@ public final class GameView extends View implements Runnable {
drawCenteredText(canvas, "Tap to enter", card.centerX(), top + 190, 22, Color.rgb(202, 204, 214));
}
}
drawHudButton(canvas, new RectF(1560, 70, 1850, 160), "Settings", false);
drawCenteredText(canvas, "Landscape · multitouch · progress saves automatically", VIEW_WIDTH * 0.5f, 980, 24, Color.argb(190, 230, 230, 236));
}
private void drawSettings(Canvas canvas) {
drawSky(canvas);
drawCenteredText(canvas, "SETTINGS", VIEW_WIDTH * 0.5f, 145, 64,
Color.rgb(248, 212, 126));
drawCenteredText(canvas, "Tap a row to change it. Changes save automatically.",
VIEW_WIDTH * 0.5f, 205, 27, Color.rgb(225, 226, 233));
drawSettingRow(canvas, new RectF(420, 280, 1500, 410), "Control size",
controlSettings.controlSizeLabel());
drawSettingRow(canvas, new RectF(420, 450, 1500, 580), "Control opacity",
controlSettings.controlOpacityLabel());
drawSettingRow(canvas, new RectF(420, 620, 1500, 750), "Vibration",
controlSettings.hapticsLabel());
drawHudButton(canvas, new RectF(710, 850, 1210, 960), "Back", true);
}
private void drawSettingRow(Canvas canvas, RectF box, String label, String value) {
paint.setColor(Color.argb(220, 38, 45, 65));
canvas.drawRoundRect(box, 22, 22, paint);
stroke.setColor(Color.rgb(139, 145, 160));
stroke.setStrokeWidth(4f);
canvas.drawRoundRect(box, 22, 22, stroke);
drawText(canvas, label, box.left + 55f, box.centerY() + 11f, 32f, Color.WHITE, true);
drawHudButton(canvas, new RectF(1_090f, box.top + 24f, 1_445f, box.bottom - 24f),
value, false);
}
private void drawComplete(Canvas canvas) {
paint.setColor(Color.argb(218, 8, 12, 23));
canvas.drawRect(0, 0, VIEW_WIDTH, VIEW_HEIGHT, paint);
@@ -1047,6 +1090,13 @@ public final class GameView extends View implements Runnable {
}
return true;
}
if (screen == Screen.SETTINGS) {
if (action == MotionEvent.ACTION_UP) {
performClick();
handleSettingsTap(point.x, point.y);
}
return true;
}
if (screen == Screen.COMPLETE) {
if (action == MotionEvent.ACTION_UP) {
performClick();
@@ -1106,14 +1156,20 @@ public final class GameView extends View implements Runnable {
jumpPressed = false;
for (int i = 0; i < pointers.size(); i++) {
PointF point = pointers.valueAt(i);
if (distance(point.x, point.y, 145, 925) <= 115f) leftPressed = true;
if (distance(point.x, point.y, 365, 925) <= 115f) rightPressed = true;
if (distance(point.x, point.y, 1760, 900) <= 145f) jumpPressed = true;
float controlScale = controlSettings.controlScale();
if (distance(point.x, point.y, 145, 925) <= 115f * controlScale) leftPressed = true;
if (distance(point.x, point.y, 365, 925) <= 115f * controlScale) rightPressed = true;
if (distance(point.x, point.y, 1760, 900) <= 145f * controlScale) jumpPressed = true;
}
if (jumpPressed && !wasJumpPressed && player.alive) player.jumpBuffer = JUMP_BUFFER_SECONDS;
}
private void handleLevelSelectTap(float x, float y) {
if (contains(1560, 70, 1850, 160, x, y)) {
screen = Screen.SETTINGS;
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
return;
}
float startX = 110f;
float cardW = 310f;
float cardH = 250f;
@@ -1124,7 +1180,7 @@ public final class GameView extends View implements Runnable {
float left = startX + col * (cardW + gapX);
float top = 260f + row * 310f;
if (contains(left, top, left + cardW, top + cardH, x, y) && i < progress.unlockedCount()) {
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
startLevel(i);
return;
}
@@ -1145,6 +1201,26 @@ public final class GameView extends View implements Runnable {
}
}
private void handleSettingsTap(float x, float y) {
if (contains(420, 280, 1500, 410, x, y)) {
updateControlSettings(controlSettings.nextControlSize());
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
} else if (contains(420, 450, 1500, 580, x, y)) {
updateControlSettings(controlSettings.nextControlOpacity());
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
} else if (contains(420, 620, 1500, 750, x, y)) {
updateControlSettings(controlSettings.toggleHaptics());
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
} else if (contains(710, 850, 1210, 960, x, y)) {
screen = Screen.LEVELS;
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
}
}
private void emitHaptic(int feedbackConstant) {
if (controlSettings.hapticsEnabled()) performHapticFeedback(feedbackConstant);
}
private PointF toCanvasPoint(float x, float y) {
return new PointF((x - canvasOffsetX) / Math.max(0.001f, canvasScale), (y - canvasOffsetY) / Math.max(0.001f, canvasScale));
}
@@ -1170,6 +1246,38 @@ public final class GameView extends View implements Runnable {
restartLevel();
}
public ControlSettings controlSettingsForAutomation() {
return controlSettings;
}
public boolean settingsVisibleForAutomation() {
return screen == Screen.SETTINGS;
}
public void resetControlSettingsForAutomation() {
updateControlSettings(ControlSettings.defaults());
}
public void selectNextControlSizeForAutomation() {
updateControlSettings(controlSettings.nextControlSize());
}
public void selectNextControlOpacityForAutomation() {
updateControlSettings(controlSettings.nextControlOpacity());
}
public void toggleHapticsForAutomation() {
updateControlSettings(controlSettings.toggleHaptics());
}
private void updateControlSettings(ControlSettings updated) {
controlSettings = updated;
controlSettingsStore.save(updated);
pointers.clear();
deriveControls();
invalidate();
}
public void setAutomationInput(boolean moveLeft, boolean moveRight, boolean jump) {
boolean jumpWasPressed = jumpPressed;
leftPressed = moveLeft;