Добавить обучение и полный экран результата
This commit is contained in:
@@ -161,6 +161,7 @@ public final class GameView extends View implements Runnable {
|
||||
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 OnboardingStore onboardingStore;
|
||||
private final SparseArray<PointF> pointers = new SparseArray<>();
|
||||
private final Player player = new Player();
|
||||
private final ArrayList<Corpse> corpses = new ArrayList<>();
|
||||
@@ -187,12 +188,16 @@ public final class GameView extends View implements Runnable {
|
||||
private float canvasOffsetX;
|
||||
private float canvasOffsetY;
|
||||
private ControlSettings controlSettings;
|
||||
private OnboardingFlow onboardingFlow;
|
||||
private CompletionResult completionResult;
|
||||
|
||||
public GameView(Context context) {
|
||||
super(context);
|
||||
progress = new ProgressStore(context);
|
||||
controlSettingsStore = new ControlSettingsStore(context);
|
||||
controlSettings = controlSettingsStore.load();
|
||||
onboardingStore = new OnboardingStore(context);
|
||||
onboardingFlow = OnboardingFlow.firstRun(onboardingStore.isComplete());
|
||||
paint.setTypeface(android.graphics.Typeface.create("sans", android.graphics.Typeface.NORMAL));
|
||||
stroke.setStyle(Paint.Style.STROKE);
|
||||
stroke.setStrokeCap(Paint.Cap.ROUND);
|
||||
@@ -236,7 +241,7 @@ public final class GameView extends View implements Runnable {
|
||||
return true;
|
||||
}
|
||||
if (screen == Screen.COMPLETE) {
|
||||
screen = Screen.LEVELS;
|
||||
levelsFromResult();
|
||||
return true;
|
||||
}
|
||||
if (userPaused) {
|
||||
@@ -265,6 +270,7 @@ public final class GameView extends View implements Runnable {
|
||||
toggleSignals.clear();
|
||||
timedSignals.clear();
|
||||
previousSwitchContact.clear();
|
||||
completionResult = null;
|
||||
for (LevelDefinition.Platform item : level.platforms) platformStates.add(new PlatformState(item));
|
||||
spawnPlayer();
|
||||
lastFrameNanos = System.nanoTime();
|
||||
@@ -622,7 +628,22 @@ public final class GameView extends View implements Runnable {
|
||||
|
||||
private void checkGoal() {
|
||||
if (!intersects(player.x, player.y, PLAYER_WIDTH, PLAYER_HEIGHT, level.goal)) return;
|
||||
progress.recordCompletion(level.number, deaths, (long) (levelElapsed * 1000f));
|
||||
finishLevel(deaths, (long) (levelElapsed * 1000f));
|
||||
}
|
||||
|
||||
private void finishLevel(int completedDeaths, long completedTimeMs) {
|
||||
long safeTimeMs = Math.max(0L, completedTimeMs);
|
||||
deaths = Math.max(0, completedDeaths);
|
||||
levelElapsed = safeTimeMs / 1_000f;
|
||||
progress.recordCompletion(level.number, deaths, safeTimeMs);
|
||||
completionResult = CompletionResult.create(
|
||||
level,
|
||||
deaths,
|
||||
safeTimeMs,
|
||||
progress.bestDeaths(level.number),
|
||||
progress.bestTimeMs(level.number),
|
||||
levelIndex + 1 < LevelCatalog.all().size()
|
||||
);
|
||||
player.vx = player.vy = 0f;
|
||||
screen = Screen.COMPLETE;
|
||||
pointers.clear();
|
||||
@@ -664,6 +685,7 @@ public final class GameView extends View implements Runnable {
|
||||
drawGame(canvas);
|
||||
if (screen == Screen.COMPLETE) drawComplete(canvas);
|
||||
else if (userPaused) drawPause(canvas);
|
||||
else if (onboardingFlow.isVisible()) drawOnboarding(canvas);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
@@ -1017,22 +1039,84 @@ public final class GameView extends View implements Runnable {
|
||||
private void drawComplete(Canvas canvas) {
|
||||
paint.setColor(Color.argb(218, 8, 12, 23));
|
||||
canvas.drawRect(0, 0, VIEW_WIDTH, VIEW_HEIGHT, paint);
|
||||
drawCenteredText(canvas, "RELIC CLAIMED", VIEW_WIDTH * 0.5f, 270, 64, Color.rgb(250, 211, 111));
|
||||
drawCenteredText(canvas, level.title, VIEW_WIDTH * 0.5f, 335, 34, Color.WHITE);
|
||||
drawCenteredText(canvas, "RELIC CLAIMED", VIEW_WIDTH * 0.5f, 205, 64, Color.rgb(250, 211, 111));
|
||||
drawCenteredText(canvas, level.title, VIEW_WIDTH * 0.5f, 263, 34, Color.WHITE);
|
||||
|
||||
boolean deathStar = deaths <= level.targetDeaths;
|
||||
boolean timeStar = levelElapsed <= level.targetSeconds;
|
||||
drawCenteredText(canvas, "Completion ★", VIEW_WIDTH * 0.5f, 470, 38, Color.rgb(250, 211, 111));
|
||||
drawCenteredText(canvas, (deathStar ? "★" : "☆") + " " + deaths + " / " + level.targetDeaths + " volunteers",
|
||||
VIEW_WIDTH * 0.5f, 535, 31, deathStar ? Color.rgb(128, 229, 153) : Color.rgb(210, 211, 218));
|
||||
drawCenteredText(canvas, (timeStar ? "★" : "☆") + " " + GameMath.formatTime((long) (levelElapsed * 1000)) + " / " + GameMath.formatTime(level.targetSeconds * 1000L),
|
||||
VIEW_WIDTH * 0.5f, 588, 31, timeStar ? Color.rgb(128, 229, 153) : Color.rgb(210, 211, 218));
|
||||
CompletionResult result = completionResult;
|
||||
if (result != null) {
|
||||
drawCenteredText(canvas,
|
||||
"This run · " + result.currentDeaths() + " volunteers · "
|
||||
+ GameMath.formatTime(result.currentTimeMs()),
|
||||
VIEW_WIDTH * 0.5f, 380, 34, Color.WHITE);
|
||||
drawCenteredText(canvas,
|
||||
"Saved best · " + result.bestDeaths() + " volunteers · "
|
||||
+ GameMath.formatTime(result.bestTimeMs()),
|
||||
VIEW_WIDTH * 0.5f, 437, 31, Color.rgb(128, 229, 153));
|
||||
drawCenteredText(canvas,
|
||||
"Targets · " + result.targetDeaths() + " volunteers · "
|
||||
+ GameMath.formatTime(result.targetTimeMs()),
|
||||
VIEW_WIDTH * 0.5f, 510, 28, Color.rgb(210, 211, 218));
|
||||
drawCenteredText(canvas, nextGoalText(result), VIEW_WIDTH * 0.5f, 585, 31,
|
||||
Color.rgb(250, 211, 111));
|
||||
}
|
||||
|
||||
drawHudButton(canvas, new RectF(345, 735, 755, 850), "Level select", false);
|
||||
drawHudButton(canvas, new RectF(775, 735, 1145, 850), "Replay", false);
|
||||
if (levelIndex + 1 < LevelCatalog.all().size()) drawHudButton(canvas, new RectF(1165, 735, 1575, 850), "Next level", true);
|
||||
}
|
||||
|
||||
private String nextGoalText(CompletionResult result) {
|
||||
switch (result.nextGoal()) {
|
||||
case TARGET_DEATHS:
|
||||
return "Next goal · finish with " + result.nextGoalValue() + " volunteers";
|
||||
case TARGET_TIME:
|
||||
return "Next goal · finish in " + GameMath.formatTime(result.nextGoalValue());
|
||||
case NEXT_LEVEL:
|
||||
return "Next goal · enter level " + result.nextGoalValue();
|
||||
case PERSONAL_BEST:
|
||||
default:
|
||||
return "Next goal · improve your personal best";
|
||||
}
|
||||
}
|
||||
|
||||
private void drawOnboarding(Canvas canvas) {
|
||||
RectF card = new RectF(535, 175, 1385, 375);
|
||||
paint.setColor(Color.argb(232, 16, 22, 37));
|
||||
canvas.drawRoundRect(card, 24, 24, paint);
|
||||
stroke.setColor(Color.rgb(226, 169, 63));
|
||||
stroke.setStrokeWidth(4f);
|
||||
canvas.drawRoundRect(card, 24, 24, stroke);
|
||||
|
||||
String title;
|
||||
String message;
|
||||
String progressLabel;
|
||||
switch (onboardingFlow.step()) {
|
||||
case MOVEMENT:
|
||||
title = "MOVE";
|
||||
message = "Hold the left or right arrow to explore.";
|
||||
progressLabel = "1 / 3";
|
||||
break;
|
||||
case MULTITOUCH_JUMP:
|
||||
title = "MOVE + JUMP";
|
||||
message = "Use two fingers: hold a direction and tap Jump.";
|
||||
progressLabel = "2 / 3";
|
||||
break;
|
||||
case CORPSES_ARE_TOOLS:
|
||||
default:
|
||||
title = "DEATH LEAVES A TOOL";
|
||||
message = "A fallen volunteer remains. Use the body to open a path.";
|
||||
progressLabel = "3 / 3";
|
||||
break;
|
||||
}
|
||||
drawText(canvas, title, 575, 230, 30, Color.rgb(250, 211, 111), true);
|
||||
drawText(canvas, message, 575, 278, 27, Color.WHITE, false);
|
||||
drawText(canvas, progressLabel, 575, 337, 22, Color.rgb(180, 185, 198), false);
|
||||
drawHudButton(canvas, new RectF(965, 298, 1125, 355), "Skip", false);
|
||||
drawHudButton(canvas, new RectF(1145, 298, 1345, 355),
|
||||
onboardingFlow.step() == OnboardingFlow.Step.CORPSES_ARE_TOOLS ? "Done" : "Next",
|
||||
true);
|
||||
}
|
||||
|
||||
private void drawPause(Canvas canvas) {
|
||||
paint.setColor(Color.argb(218, 8, 12, 23));
|
||||
canvas.drawRect(0, 0, VIEW_WIDTH, VIEW_HEIGHT, paint);
|
||||
@@ -1112,6 +1196,14 @@ public final class GameView extends View implements Runnable {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (onboardingFlow.isVisible() && action == MotionEvent.ACTION_UP
|
||||
&& handleOnboardingTap(point.x, point.y)) {
|
||||
performClick();
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
if (contains(1608, 26, 1770, 112, point.x, point.y)) {
|
||||
restartLevel();
|
||||
@@ -1188,9 +1280,46 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
|
||||
private void handleCompleteTap(float x, float y) {
|
||||
if (contains(345, 735, 755, 850, x, y)) screen = Screen.LEVELS;
|
||||
else if (contains(775, 735, 1145, 850, x, y)) restartLevel();
|
||||
else if (contains(1165, 735, 1575, 850, x, y) && levelIndex + 1 < LevelCatalog.all().size()) startLevel(levelIndex + 1);
|
||||
if (contains(345, 735, 755, 850, x, y)) levelsFromResult();
|
||||
else if (contains(775, 735, 1145, 850, x, y)) replayFromResult();
|
||||
else if (contains(1165, 735, 1575, 850, x, y)) nextLevelFromResult();
|
||||
}
|
||||
|
||||
private boolean handleOnboardingTap(float x, float y) {
|
||||
if (contains(965, 298, 1125, 355, x, y)) {
|
||||
onboardingFlow.skip();
|
||||
onboardingStore.markComplete();
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
if (contains(1145, 298, 1345, 355, x, y)) {
|
||||
advanceOnboarding();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void advanceOnboarding() {
|
||||
onboardingFlow.next();
|
||||
if (onboardingFlow.isComplete()) onboardingStore.markComplete();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void levelsFromResult() {
|
||||
if (screen != Screen.COMPLETE) return;
|
||||
screen = Screen.LEVELS;
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
}
|
||||
|
||||
private void replayFromResult() {
|
||||
if (screen == Screen.COMPLETE) restartLevel();
|
||||
}
|
||||
|
||||
private void nextLevelFromResult() {
|
||||
if (screen == Screen.COMPLETE && levelIndex + 1 < LevelCatalog.all().size()) {
|
||||
startLevel(levelIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePauseTap(float x, float y) {
|
||||
@@ -1254,6 +1383,34 @@ public final class GameView extends View implements Runnable {
|
||||
return screen == Screen.SETTINGS;
|
||||
}
|
||||
|
||||
public boolean levelsVisibleForAutomation() {
|
||||
return screen == Screen.LEVELS;
|
||||
}
|
||||
|
||||
public boolean onboardingVisibleForAutomation() {
|
||||
return screen == Screen.PLAYING && onboardingFlow.isVisible();
|
||||
}
|
||||
|
||||
public OnboardingFlow.Step onboardingStepForAutomation() {
|
||||
return onboardingFlow.step();
|
||||
}
|
||||
|
||||
public void resetOnboardingForAutomation() {
|
||||
onboardingStore.resetForAutomation();
|
||||
onboardingFlow = OnboardingFlow.firstRun(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void advanceOnboardingForAutomation() {
|
||||
advanceOnboarding();
|
||||
}
|
||||
|
||||
public void skipOnboardingForAutomation() {
|
||||
onboardingFlow.skip();
|
||||
onboardingStore.markComplete();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void resetControlSettingsForAutomation() {
|
||||
updateControlSettings(ControlSettings.defaults());
|
||||
}
|
||||
@@ -1270,6 +1427,37 @@ public final class GameView extends View implements Runnable {
|
||||
updateControlSettings(controlSettings.toggleHaptics());
|
||||
}
|
||||
|
||||
public void finishLevelForAutomation(int completedDeaths, long completedTimeMs) {
|
||||
if (screen != Screen.PLAYING || level == null) {
|
||||
throw new IllegalStateException("A level must be playing before it can finish");
|
||||
}
|
||||
finishLevel(completedDeaths, completedTimeMs);
|
||||
}
|
||||
|
||||
public CompletionResult completionResultForAutomation() {
|
||||
return completionResult;
|
||||
}
|
||||
|
||||
public boolean resultVisibleForAutomation() {
|
||||
return screen == Screen.COMPLETE;
|
||||
}
|
||||
|
||||
public boolean levelCompletedForAutomation(int oneBasedLevelNumber) {
|
||||
return progress.isCompleted(oneBasedLevelNumber);
|
||||
}
|
||||
|
||||
public void levelsFromResultForAutomation() {
|
||||
levelsFromResult();
|
||||
}
|
||||
|
||||
public void replayFromResultForAutomation() {
|
||||
replayFromResult();
|
||||
}
|
||||
|
||||
public void nextLevelFromResultForAutomation() {
|
||||
nextLevelFromResult();
|
||||
}
|
||||
|
||||
private void updateControlSettings(ControlSettings updated) {
|
||||
controlSettings = updated;
|
||||
controlSettingsStore.save(updated);
|
||||
|
||||
Reference in New Issue
Block a user