
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.
163 lines
4.2 KiB
Vue
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>
|