framerate independent movement

This commit is contained in:
Norman Köhring 2023-02-10 15:45:04 +01:00
parent e146052f33
commit 7711c112e2
3 changed files with 71 additions and 29 deletions

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def' import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, type Block } from './level/def'
import createLevel from './level' import createLevel from './level'
import useTime from './util/useTime' import useTime from './util/useTime'
@ -9,7 +9,7 @@ import usePlayer from './util/usePlayer'
const { updateTime, timeOfDay, clock } = useTime() const { updateTime, timeOfDay, clock } = useTime()
const { player, direction, dx, dy } = usePlayer() const { player, direction, dx, dy } = usePlayer()
const { inputX, inputY, digging, paused } = useInput(player) const { inputX, inputY, running, digging, paused } = useInput()
const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2) const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
let animationFrame = 0 let animationFrame = 0
@ -23,24 +23,56 @@ const tx = computed(() => (x.value - floorX.value) * -BLOCK_SIZE)
const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE) const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE)
const rows = computed(() => level.grid(floorX.value, floorY.value)) const rows = computed(() => level.grid(floorX.value, floorY.value))
// TODO: mock type Surroundings = {
const blocked = { at: Block,
left: false, left: Block,
right: false, right: Block,
up: false, up: Block,
down: false, 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() { function dig() {
console.warn('digging not yet implemented') console.warn('digging not yet implemented')
} }
function move(thisTick) { let lastTimeUpdate = 0
const move = (thisTick: number): void => {
animationFrame = requestAnimationFrame(move) animationFrame = requestAnimationFrame(move)
// do nothing when paused, otherwise keep roughly 20 fps // do nothing when paused
if (paused.value || thisTick - lastTick < 50) return if (paused.value) return
updateTime()
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.vx = inputX.value
player.vy = inputY.value player.vy = inputY.value
@ -50,19 +82,24 @@ function move(thisTick) {
let dx_ = dx.value let dx_ = dx.value
let dy_ = dy.value let dy_ = dy.value
if (dx > 0 && blocked.right) dx_ = 0 if (running.value) dx_ *= 2
else if (dx < 0 && blocked.left) dx_ = 0
if (dy > 0 && blocked.down) dy_ = 0 if (dx_ > 0 && blocked.value.right) dx_ = 0
else if (dy < 0 && blocked.up) dy_ = 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) { if (!inputY.value && digging.value) {
dx_ = 0 dx_ = 0
dig() dig()
} }
x.value += dx_ * 32 const optimal = 16 // 16ms per tick => 60 FPS
y.value += dy_ * 32 const movementMultiplier = (tickDelta / optimal) * 2
x.value += dx_ * movementMultiplier
y.value += dy_ * movementMultiplier
lastTick = thisTick lastTick = thisTick
} }
@ -86,8 +123,7 @@ onMounted(() => {
x:{{ floorX }}, y:{{ floorY }} x:{{ floorX }}, y:{{ floorY }}
<template v-if="paused">(PAUSED)</template> <template v-if="paused">(PAUSED)</template>
<template v-else>({{ clock }})</template> <template v-else>({{ clock }})</template>
<div>{{ inputX }}, {{ inputY }}, {{ player.lastDir }}</div> <div>{{ player.vx }}, {{ player.vy }}</div>
<div>{{ dx }}, {{ dy }}, {{ direction }}</div>
</div> </div>
</div> </div>
</template> </template>

View file

@ -2,12 +2,16 @@ import { ref } from 'vue'
export default function useInput() { export default function useInput() {
let inputX = ref(0) let inputX = ref(0)
let inputY = ref(0) let inputY = ref(1)
let running = ref(false)
let digging = ref(false) let digging = ref(false)
let paused = ref(false) let paused = ref(false)
function handleKeyDown(event: KeyboardEvent) { function handleKeyDown(event: KeyboardEvent) {
switch (event.key) { switch (event.key) {
case 'Shift':
running.value = true
break
case 'ArrowUp': case 'ArrowUp':
inputY.value = -1 inputY.value = -1
break break
@ -31,12 +35,13 @@ export default function useInput() {
function handleKeyUp(event: KeyboardEvent) { function handleKeyUp(event: KeyboardEvent) {
switch (event.key) { switch (event.key) {
case 'Shift':
running.value = false
break
// Arrow Keys // Arrow Keys
case 'ArrowUp': case 'ArrowUp':
inputY.value = inputY.value === -1 ? 0 : 1
break
case 'ArrowDown': case 'ArrowDown':
inputY.value = inputY.value === 1 ? 0 : 1 inputY.value = 1
break break
case 'ArrowRight': case 'ArrowRight':
inputX.value = inputX.value === 1 ? 0 : -1 inputX.value = inputX.value === 1 ? 0 : -1
@ -59,6 +64,7 @@ export default function useInput() {
return { return {
inputX, inputX,
inputY, inputY,
running,
digging, digging,
paused, paused,
} }

View file

@ -2,19 +2,19 @@ import { computed, reactive } from 'vue'
import { RECIPROCAL } from '../level/def' import { RECIPROCAL } from '../level/def'
export interface Moveable { export interface Moveable {
x: number, // position on x-axis (always 0 for the player) x: number, // position on x-axis (fixed for the player)
y: number, // position on y-axis (always 0 for the player) y: number, // position on y-axis (fixed for the player)
lastDir: number, // store last face direction lastDir: number, // store last face direction
vx: number, // velocity on the x-axis vx: number, // velocity on the x-axis
vy: number, // velocity on the y-axis vy: number, // velocity on the y-axis
} }
const player = reactive({ const player = reactive({
x: 0, x: 16,
y: 0, y: 10,
lastDir: 0, lastDir: 0,
vx: 0, vx: 0,
vy: 0, vy: 1, // always falling, because of gravity
}) })
export default function usePlayer() { export default function usePlayer() {