framerate independent movement
This commit is contained in:
parent
e146052f33
commit
7711c112e2
3 changed files with 71 additions and 29 deletions
76
src/App.vue
76
src/App.vue
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue