vue-shovel/src/App.vue
Norman Köhring 43e8242619 fix falling-on-pause bug
The issue happened because of not resetting lastTick: The lastTick is stored to
calculate movement speed independent of frame rate. When paused, the delta is
calculated with the time before the pause, so it gets huge and results in high
velocity values that are used to calculate how far the player moved.
2023-02-14 00:48:46 +01:00

163 lines
4.2 KiB
Vue

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import Help from './screens/help.vue'
import Inventory from './screens/inventory.vue'
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, type Block } from './level/def'
import createLevel from './level'
import useTime from './util/useTime'
import useInput from './util/useInput'
import usePlayer from './util/usePlayer'
const { updateTime, timeOfDay, clock } = useTime()
const { player, direction, dx, dy } = usePlayer()
const { inputX, inputY, running, digging, paused, help, inventory } = useInput()
const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
player.inventory.push(
{ name: 'Shovel', type: 'tool', icon: 'shovel', quality: 'bronze' },
{ name: 'Sword', type: 'weapon', icon: 'sword', quality: 'bronze' },
{ name: 'Pick Axe', type: 'tool', icon: 'pick', quality: 'bronze' },
)
let animationFrame = 0
let lastTick = 0
const x = ref(0)
const y = ref(0)
const floorX = computed(() => Math.floor(x.value))
const floorY = computed(() => Math.floor(y.value))
const tx = computed(() => (x.value - floorX.value) * -BLOCK_SIZE)
const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE)
const rows = computed(() => level.grid(floorX.value, floorY.value))
const walking = ref(false)
const inventorySelection = ref<InventoryItem | null>(null)
type Surroundings = {
at: Block,
left: Block,
right: Block,
up: Block,
down: Block,
}
const surroundings = computed<Surroundings>(() => {
const px = player.x
const py = player.y
const row = rows.value
return {
at: row[py][px],
left: row[py][px - 1],
right: row[py][px + 1],
up: row[py - 1][px],
down: row[py + 1][px],
}
})
const blocked = computed(() => {
const { left, right, up, down } = surroundings.value
return {
left: !left.walkable,
right: !right.walkable,
up: !up.walkable,
down: !down.walkable,
}
})
function dig() {
console.warn('digging not yet implemented')
}
let lastTimeUpdate = 0
const move = (thisTick: number): void => {
animationFrame = requestAnimationFrame(move)
// do nothing when paused
if (paused.value) {
lastTick = thisTick // reset tick, to avoid huge tickDelta
return
}
const tickDelta = thisTick - lastTick
lastTimeUpdate += tickDelta
// update in-game time every 60ms by 0.1
// then a day needs 10000 updates, and it takes about 10 minutes
if (lastTimeUpdate > 60) {
updateTime()
lastTimeUpdate = 0
}
player.vx = inputX.value
player.vy = inputY.value
if (inputX.value) player.lastDir = inputX.value
let dx_ = dx.value
let dy_ = dy.value
if (running.value) dx_ *= 2
if (dx_ > 0 && blocked.value.right) dx_ = 0
else if (dx_ < 0 && blocked.value.left) dx_ = 0
if (dy_ > 0 && blocked.value.down) dy_ = 0
else if (dy_ < 0 && blocked.value.up) dy_ = 0
if (!inputY.value && digging.value) {
dx_ = 0
dig()
}
const optimal = 16 // 16ms per tick => 60 FPS
const movementMultiplier = (tickDelta / optimal) * 2
walking.value = !!dx_
x.value += dx_ * movementMultiplier
y.value += dy_ * movementMultiplier
lastTick = thisTick
}
onMounted(() => {
lastTick = performance.now()
move(lastTick)
})
</script>
<template>
<div id="field" :class="timeOfDay">
<div id="blocks" :style="{transform: `translate(${tx}px, ${ty}px)`}">
<template v-for="(row, y) in rows">
<div v-for="(block, x) in row" class="block" :class="[block.type]" />
</template>
</div>
<div id="player" :class="[direction, { walking }]" @click="inventory = !inventory">
<div class="head"></div>
<div class="body"></div>
<div class="legs">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="arms">
<div v-if="inventorySelection"
:class="['item', `${inventorySelection.type}-${inventorySelection.icon}-${inventorySelection.quality}`]"
></div>
</div>
</div>
<div id="level-indicator">
x:{{ floorX }}, y:{{ floorY }}
<template v-if="paused">(PAUSED)</template>
<template v-else>({{ clock }})</template>
</div>
<Inventory :shown="inventory"
:items="player.inventory"
@selection="inventorySelection = $event"
/>
<Help v-show="help" />
</div>
</template>