~koehr/dig

59ac454bc5fc21a8e8f0247a9913af9b4a1987f7 — koehr 3 years ago 7c6e716
initiates central physics handling
5 files changed, 91 insertions(+), 61 deletions(-)

M src/App.vue
M src/Background.vue
M src/Field.vue
M src/level/def.js
A src/physics.js
M src/App.vue => src/App.vue +3 -2
@@ 19,7 19,9 @@ export default {

<style>
html,body,#app {
  display: block;
  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  background: black;


@@ 27,5 29,4 @@ html,body,#app {
  padding: 0;
  overflow: hidden;
}

</style>

M src/Background.vue => src/Background.vue +9 -10
@@ 4,6 4,7 @@

<script>
import solarQuartet from './solar-quartet'
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'

export default {
  name: 'background',


@@ 21,18 22,16 @@ export default {
    time () { this.refresh() }
  },
  mounted () {
    const canvasSize = 512
    const godraysSize = 128
    const canvas = this.$refs.canvas
    const godraysCanvas = document.createElement('canvas')
    canvas.width = canvasSize
    canvas.height = canvasSize
    godraysCanvas.width = godraysSize
    godraysCanvas.height = godraysSize
    canvas.width = STAGE_WIDTH * BLOCK_SIZE
    canvas.height = STAGE_HEIGHT * BLOCK_SIZE
    godraysCanvas.width = ~~(canvas.width / 8.0)
    godraysCanvas.height = ~~(canvas.height / 8.0)
    this.redraw = solarQuartet.bind(
      null,
      canvas, canvas.getContext('2d'), canvasSize, canvasSize,
      godraysCanvas, godraysCanvas.getContext('2d'), godraysSize, godraysSize,
      canvas, canvas.getContext('2d'), ~~(canvas.width / 2.0), ~~(canvas.height / 2.0),
      godraysCanvas, godraysCanvas.getContext('2d'), godraysCanvas.width, godraysCanvas.height,
    )
    this.refresh()
  },


@@ 65,8 64,8 @@ export default {
<style>
#background {
  display: block;
  width: 100%;
  height: 100%;
  width: var(--field-width);
  height: var(--field-height);
  object-fit: contain;
  background: black;
}

M src/Field.vue => src/Field.vue +45 -49
@@ 1,13 1,14 @@
<template>
  <div id="field" :class="daytimeClass">
    <input v-keep-focussed type="text"
      @keydown.up="jump = jump || !blocked.down ? jump : 20"
      @keydown.down="moveTo = 'down'"
      @keydown.right="moveTo = 'right'"
      @keydown.left="moveTo = 'left'"
      @keyup.down="moveTo = null"
      @keyup.right="moveTo = null"
      @keyup.left="moveTo = null"
      @keydown.up="inputY = -1"
      @keydown.down="inputY = 1"
      @keydown.right="inputX = -1"
      @keydown.left="inputX = 1"
      @keyup.up="inputY = inputY === -1 ? 0 : 1"
      @keyup.down="inputY = inputY === 1 ? 0 : 1"
      @keyup.right="inputX = inputX === -1 ? 0 : 1"
      @keyup.left="inputX = inputX === 1 ? 0: -1"
      @keypress.p="togglePause"
      @keydown.space="digging = true"
      @keyup.space="digging = false"


@@ 18,7 19,7 @@
        <div v-for="(block, x) in row" class="block" :class="[block.type]" />
      </template>
    </div>
    <div id="player" :class="[playerDirection]" />
    <div id="player" :class="[player.direction]" />
    <div id="level-indicator">
      x:{{ floorX }}, y:{{ floorY }}
      <template v-if="moving !== false">({{clock}})</template>


@@ 29,36 30,39 @@

<script>
// import throttle from 'lodash/throttle'
import Level from './level'
import MountainBackground from './Background'

const BLOCK_SIZE = 32
const RECIPROCAL = 1 / BLOCK_SIZE
const PLAYER_X = ~~(BLOCK_SIZE / 2) + 1
const PLAYER_Y = BLOCK_SIZE - 15
const PLAYER_MAX_VELOCITY = 32
const level = new Level(BLOCK_SIZE + 2, BLOCK_SIZE + 2)
import Level from './level'
import { Moveable } from './physics'
import {
  BLOCK_SIZE,
  RECIPROCAL,
  STAGE_WIDTH,
  STAGE_HEIGHT,
  PLAYER_X,
  PLAYER_Y
} from './level/def'

const level = new Level(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
const player = new Moveable(PLAYER_X, PLAYER_Y)

export default {
  name: 'field',
  components: { MountainBackground },
  data () {
    return {
      player,
      x: 0,
      y: 0,
      playerDirection: 'left',
      playerVelocityX: 0,
      playerVelocityY: 8,
      moveTo: null,
      jump: 0,
      digging: false,
      gravity: 8.0 / 20,
      y: 12,
      inputX: 0,
      inputY: 0,
      time: 250,
      moving: false,
      time: 250
      lastTick: 0
    }
  },
  mounted () {
    this.move()
    this.lastTick = performance.now()
    this.move(this.lastTick)
  },
  computed: {
    rows () { return level.grid(this.floorX, this.floorY) },


@@ 110,28 114,21 @@ export default {
    }
  },
  methods: {
    move () {
      // set time of day in ticks
      this.time = (this.time + 0.1) % 1000
    move (thisTick) {
      this.moving = requestAnimationFrame(this.move)

      const x = this.x
      const y = this.y
      // keep roughly 20 fps
      if (thisTick - this.lastTick < 50) return

      if (this.moveTo !== null) this.playerDirection = this.moveTo

      if (this.moveTo === 'right') {
        this.playerVelocityX = 8
      } else if (this.moveTo === 'left') {
        this.playerVelocityX = -8
      } else {
        this.playerVelocityX = 0
      }
      // set time of day in ticks
      this.time = (this.time + 0.1) % 1000

      // this.player_velocity_y += this.gravity
      let dx = this.playerVelocityX * RECIPROCAL
      let dy = (this.playerVelocityY - this.jump) * RECIPROCAL
      const player = this.player
      const x = player.x
      const y = player.y

      if (this.jump > 0) this.jump -= 2
      let dx = player.vx * player.dir * RECIPROCAL
      let dy = player.vy * RECIPROCAL

      // don't walk / fall into blocks
      if (dx > 0 && this.blocked.right) dx = 0


@@ 140,14 137,14 @@ export default {
      if (dy < 0 && this.blocked.up) dy = 0

      // don't walk, work!
      if (!this.jump && this.digging) {
      if (!this.inputY && this.digging) {
        dx = 0
        this.dig()
      }

      this.x += dx
      this.y += dy
      this.moving = setTimeout(() => this.move(), 64) // roughly every 4 frames
      this.lastTick = thisTick
    },
    dig () {
      console.log('dig', this.playerDirection, this.surroundings[this.playerDirection])


@@ 163,10 160,9 @@ export default {
    },
    togglePause () {
      if (this.moving === false) { // is paused
        this.moving = true // avoid (unlikely) race condition
        this.move()
      } else {
        clearTimeout(this.moving)
        cancelAnimationFrame(this.moving)
        this.moving = false
      }
    }


@@ 179,7 175,7 @@ export default {
:root {
  --block-size: 32px;
  --field-width: 1024px;
  --field-height: 1024px;
  --field-height: 576px;
  --spare-blocks: 2;
}


M src/level/def.js => src/level/def.js +12 -0
@@ 1,3 1,15 @@
export const BLOCK_SIZE = 32 // each block is 32̨̣̌̇x32 pixel in size and equals 1m
export const RECIPROCAL = 1 / BLOCK_SIZE

export const STAGE_WIDTH = 32 // 32*32 = 1024 pixel wide stage
export const STAGE_HEIGHT = ~~(STAGE_WIDTH * 0.5625) // 16:9 😎

// the player position is fixed to the middle of the x axis
export const PLAYER_X = ~~(STAGE_WIDTH / 2) + 1
export const PLAYER_Y = ~~(STAGE_HEIGHT * 0.5) // fall from the center

export const GRAVITY = 10 // blocks per second

export const type = {
  air: {type: 'air', hp: Infinity, walkable: true},
  grass: {type: 'grass', hp: 1, walkable: false},

A src/physics.js => src/physics.js +22 -0
@@ 0,0 1,22 @@
import { GRAVITY } from './level/def'

/** physics gets input like
      instance of Moveable,
      position: [x, y],
      surroundings: [top, right, bottom, left] where each is a block type
    and updates the Moveable instance values accordingly
*/

export class Moveable {
  constructor (x, y, direction = 1) {
    this.x = x
    this.y = y
    this.dir = direction
    this.vx = 0
    this.vy = 0
  }

  get direction () {
    return this.dir > 0 ? 'left' : 'right'
  }
}