framerate independent movement

main
Norman Köhring 1 year ago
parent e146052f33
commit 7711c112e2

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

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

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

Loading…
Cancel
Save