feat(sasch40): polish retro game layout, clean tavern scene, and improve mobile HUD
- Hide GameBoy D-Pad and action buttons during RPG and dialogue modes - Add prequel intro landing screen for QR code arrivals with Haumdaucher lore - Clean baked-in text from tavern scene background and streamline navigation bar - Add safe-zone margins and HTML HUD in platformer and party express acts - Optimize Certificate modal layout and scoring breakdown for mobile screens - Add automated end-to-end browser playtest script with Puppeteer - Accelerate Docker build via native pre-compilation
This commit is contained in:
@@ -35,7 +35,7 @@ interface Particle {
|
||||
|
||||
let player = {
|
||||
x: 60,
|
||||
y: 200,
|
||||
y: 350,
|
||||
w: 32,
|
||||
h: 48,
|
||||
vx: 0,
|
||||
@@ -62,33 +62,33 @@ let keys = {
|
||||
function initLevel() {
|
||||
platforms = [
|
||||
// Ground base
|
||||
{ x: 0, y: 280, w: 700, h: 50, type: 'roof' },
|
||||
{ x: 780, y: 280, w: 600, h: 50, type: 'roof' },
|
||||
{ x: 1460, y: 280, w: 700, h: 50, type: 'roof' },
|
||||
{ x: 2240, y: 280, w: 600, h: 50, type: 'roof' },
|
||||
{ x: 0, y: 400, w: 700, h: 80, type: 'roof' },
|
||||
{ x: 780, y: 400, w: 600, h: 80, type: 'roof' },
|
||||
{ x: 1460, y: 400, w: 700, h: 80, type: 'roof' },
|
||||
{ x: 2240, y: 400, w: 600, h: 80, type: 'roof' },
|
||||
|
||||
// Platforms
|
||||
{ x: 180, y: 210, w: 100, h: 18, type: 'wood' },
|
||||
{ x: 340, y: 160, w: 120, h: 18, type: 'wood' },
|
||||
{ x: 520, y: 210, w: 90, h: 18, type: 'barrel' },
|
||||
{ x: 180, y: 330, w: 100, h: 18, type: 'wood' },
|
||||
{ x: 340, y: 260, w: 120, h: 18, type: 'wood' },
|
||||
{ x: 520, y: 330, w: 90, h: 18, type: 'barrel' },
|
||||
|
||||
{ x: 860, y: 200, w: 110, h: 18, type: 'wood' },
|
||||
{ x: 1040, y: 150, w: 130, h: 18, type: 'wood' },
|
||||
{ x: 1220, y: 210, w: 80, h: 18, type: 'barrel' },
|
||||
{ x: 860, y: 310, w: 110, h: 18, type: 'wood' },
|
||||
{ x: 1040, y: 240, w: 130, h: 18, type: 'wood' },
|
||||
{ x: 1220, y: 320, w: 80, h: 18, type: 'barrel' },
|
||||
|
||||
{ x: 1560, y: 190, w: 120, h: 18, type: 'wood' },
|
||||
{ x: 1740, y: 140, w: 140, h: 18, type: 'wood' },
|
||||
{ x: 1950, y: 200, w: 90, h: 18, type: 'barrel' },
|
||||
{ x: 1560, y: 290, w: 120, h: 18, type: 'wood' },
|
||||
{ x: 1740, y: 220, w: 140, h: 18, type: 'wood' },
|
||||
{ x: 1950, y: 300, w: 90, h: 18, type: 'barrel' },
|
||||
|
||||
{ x: 2320, y: 190, w: 130, h: 18, type: 'wood' },
|
||||
{ x: 2500, y: 140, w: 160, h: 18, type: 'wood' }
|
||||
{ x: 2320, y: 280, w: 130, h: 18, type: 'wood' },
|
||||
{ x: 2500, y: 220, w: 160, h: 18, type: 'wood' }
|
||||
]
|
||||
|
||||
items = []
|
||||
// Scatter 35 bottle caps and 5 beers
|
||||
for (let i = 0; i < 35; i++) {
|
||||
const px = 120 + i * 72
|
||||
const py = 120 + (i % 3) * 45
|
||||
const py = 220 + (i % 3) * 60
|
||||
items.push({
|
||||
x: px,
|
||||
y: py,
|
||||
@@ -102,7 +102,7 @@ function initLevel() {
|
||||
;[450, 1100, 1800, 2450].forEach(bx => {
|
||||
items.push({
|
||||
x: bx,
|
||||
y: 110,
|
||||
y: 190,
|
||||
type: 'beer',
|
||||
collected: false,
|
||||
bobOffset: 0
|
||||
@@ -185,8 +185,8 @@ function updatePhysics() {
|
||||
}
|
||||
|
||||
// Prevent falling to oblivion (respawn smoothly with cheer)
|
||||
if (player.y > 360) {
|
||||
player.y = 150
|
||||
if (player.y > 460) {
|
||||
player.y = 250
|
||||
player.vy = 0
|
||||
player.x = Math.max(60, player.x - 120)
|
||||
}
|
||||
@@ -254,9 +254,9 @@ function drawGame(ctx: CanvasRenderingContext2D, width: number, height: number)
|
||||
for (let m = -1; m < 5; m++) {
|
||||
const mx = m * 240 - (cameraX * 0.2) % 240
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(mx, 260)
|
||||
ctx.lineTo(mx + 120, 110)
|
||||
ctx.lineTo(mx + 240, 260)
|
||||
ctx.moveTo(mx, 400)
|
||||
ctx.lineTo(mx + 120, 200)
|
||||
ctx.lineTo(mx + 240, 400)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
@@ -264,11 +264,11 @@ function drawGame(ctx: CanvasRenderingContext2D, width: number, height: number)
|
||||
ctx.fillStyle = 'rgba(44, 24, 16, 0.65)'
|
||||
for (let c = -1; c < 4; c++) {
|
||||
const cx = c * 320 - (cameraX * 0.35) % 320
|
||||
ctx.fillRect(cx + 60, 120, 50, 140)
|
||||
ctx.fillRect(cx + 60, 220, 50, 180)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(cx + 50, 120)
|
||||
ctx.lineTo(cx + 85, 80)
|
||||
ctx.lineTo(cx + 120, 120)
|
||||
ctx.moveTo(cx + 50, 220)
|
||||
ctx.lineTo(cx + 85, 160)
|
||||
ctx.lineTo(cx + 120, 220)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
@@ -352,13 +352,13 @@ function drawGame(ctx: CanvasRenderingContext2D, width: number, height: number)
|
||||
|
||||
// Draw Goal Gate: Taverne 'Zum wummernden Ritter'
|
||||
ctx.fillStyle = '#3e2723'
|
||||
ctx.fillRect(levelWidth - 140, 160, 110, 120)
|
||||
ctx.fillRect(levelWidth - 140, 270, 110, 130)
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.font = 'bold 9px MedievalSharp, serif'
|
||||
ctx.fillText('ZUM WUMMERNDEN RITTER', levelWidth - 135, 175)
|
||||
ctx.fillText('ZUM WUMMERNDEN RITTER', levelWidth - 135, 285)
|
||||
// Glowing door
|
||||
ctx.fillStyle = '#f59e0b'
|
||||
ctx.fillRect(levelWidth - 105, 200, 40, 80)
|
||||
ctx.fillRect(levelWidth - 105, 320, 40, 80)
|
||||
|
||||
// Draw Player: König Sascha (Pixel King with sunglasses, crown & ermine coat)
|
||||
const px = player.x
|
||||
@@ -424,18 +424,7 @@ function drawGame(ctx: CanvasRenderingContext2D, width: number, height: number)
|
||||
ctx.arc(px + 24, py + 16, 4, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
ctx.restore()
|
||||
|
||||
// On-screen HUD
|
||||
ctx.fillStyle = 'rgba(26, 15, 10, 0.75)'
|
||||
ctx.fillRect(8, 8, 160, 28)
|
||||
ctx.strokeStyle = '#d4af37'
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.strokeRect(8, 8, 160, 28)
|
||||
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.font = '10px "Press Start 2P", monospace'
|
||||
ctx.fillText(`KRONEN: ${store.kronkorken}`, 16, 26)
|
||||
ctx.restore() // End camera translation
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
@@ -484,14 +473,17 @@ onUnmounted(() => {
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
width="360"
|
||||
height="320"
|
||||
height="480"
|
||||
class="game-canvas"
|
||||
@touchstart.prevent="onJump"
|
||||
></canvas>
|
||||
|
||||
<div class="level-overlay">
|
||||
<span class="level-title">AKTE II: DIE HOPFEN-PFADE</span>
|
||||
<span class="level-hint">Tippe oder drücke SPRUNG zum Hüpfen</span>
|
||||
<div class="platformer-hud-top">
|
||||
<div class="hud-korken-badge">👑 {{ store.kronkorken }} KORKEN</div>
|
||||
<div class="level-overlay">
|
||||
<span class="level-title">AKTE II: DIE HOPFEN-PFADE</span>
|
||||
<span class="level-hint">Tippe oder drücke SPRUNG zum Hüpfen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -511,19 +503,38 @@ onUnmounted(() => {
|
||||
.game-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-fit: cover;
|
||||
image-rendering: pixelated;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
.level-overlay {
|
||||
.platformer-hud-top {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hud-korken-badge {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 8px;
|
||||
color: #ffd700;
|
||||
background: rgba(26, 15, 10, 0.85);
|
||||
border: 1.5px solid #d4af37;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.level-overlay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.level-title {
|
||||
|
||||
Reference in New Issue
Block a user