first step towards complete rewrite
6
.babelrc
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
48
.eslintrc.cjs
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-essential',
|
||||
'@vue/eslint-config-typescript/recommended',
|
||||
'@vue/eslint-config-prettier',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['cypress/e2e/**.{cy,spec}.{js,ts,jsx,tsx}'],
|
||||
extends: ['plugin:cypress/recommended'],
|
||||
},
|
||||
{
|
||||
// https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
files: ['*.vue'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.story.vue', '*.story.controls.vue'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'vue/require-v-for-key': 'off',
|
||||
'vue/no-mutating-props': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// see https://vuejs.org/guide/extras/reactivity-transform.html
|
||||
'vue/no-setup-props-destructure': 'off',
|
||||
// TODO: discuss if we want to force this
|
||||
'vue/multi-word-component-names': 'off',
|
||||
// see https://eslint.org/docs/latest/rules/no-prototype-builtins#when-not-to-use-it
|
||||
'no-prototype-builtins': 'off',
|
||||
// as long as it is explicit, it is fine to use any
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
}
|
21
.gitignore
vendored
|
@ -1,11 +1,24 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
|
4
.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
10
README.md
|
@ -2,20 +2,22 @@
|
|||
|
||||
> A blocky, side-scrolling, building and exploration game
|
||||
|
||||
This version of DIG! is reimplemented with Vue3 and Typescript. To see the old (and probably broken) version, check the vue2 branch.
|
||||
|
||||
## Build Setup
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
yarn
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
yarn dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
yarn build
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader).
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
8
dist/build.js
vendored
2
dist/build.js.map
vendored
8
env.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
3
foo/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
18
foo/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
20
foo/package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "foo",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.2.45"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.1.0",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
}
|
1
foo/public/vite.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
30
foo/src/App.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
</style>
|
1
foo/src/assets/vue.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 496 B |
38
foo/src/components/HelloWorld.vue
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Install
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
in your IDE for a better DX
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
80
foo/src/style.css
Normal file
|
@ -0,0 +1,80 @@
|
|||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
53
index.html
|
@ -1,11 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>building-game</title>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dig!</title>
|
||||
<style>
|
||||
:root {
|
||||
--block-size: 32px;
|
||||
--blocks-x: 32;
|
||||
--blocks-y: 18;
|
||||
--spare-blocks: 2;
|
||||
--field-width: calc(var(--block-size) * var(--blocks-x));
|
||||
--field-height: calc(var(--block-size) * var(--blocks-y));
|
||||
--spare-blocks: 2;
|
||||
}
|
||||
html,body,#app {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#field {
|
||||
position: relative;
|
||||
width: var(--field-width);
|
||||
height: var(--field-height);
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background-color: #56F;
|
||||
}
|
||||
#input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
#level-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="dist/build.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
6800
package-lock.json
generated
43
package.json
|
@ -1,36 +1,33 @@
|
|||
{
|
||||
"name": "digging-game",
|
||||
"name": "DIG",
|
||||
"description": "A blocky, side-scrolling, digging and exploration game",
|
||||
"version": "0.0.1",
|
||||
"author": "koehr <n@koehr.in>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"seedrandom": "^2.4.4",
|
||||
"vue": "^2.6.10"
|
||||
"alea": "^1.0.1",
|
||||
"simplex-noise": "^4.0.1",
|
||||
"vue": "^3.2.45"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
],
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"lodash": "^4.17.11",
|
||||
"open-simplex-noise": "^1.6.0",
|
||||
"vue-loader": "^13.7.3",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-dev-server": "^2.11.5"
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"typescript": "^4.9.3",
|
||||
"unplugin-vue-macros": "^1.7.3",
|
||||
"vite": "^4.1.0",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 629 B |
115
src/App.vue
|
@ -1,32 +1,93 @@
|
|||
<template>
|
||||
<div id="building-game">
|
||||
<Field />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'
|
||||
import createLevel from './level'
|
||||
|
||||
<script>
|
||||
import Field from './Field'
|
||||
import useTime from './util/useTime'
|
||||
import useInput from './util/useInput'
|
||||
import usePlayer from './util/usePlayer'
|
||||
|
||||
export default {
|
||||
name: 'building-game',
|
||||
components: { Field },
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
const { updateTime, timeOfDay, clock } = useTime()
|
||||
const { player, direction, dx, dy } = usePlayer()
|
||||
const { inputX, inputY, digging, paused } = useInput(player)
|
||||
const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
|
||||
|
||||
let animationFrame = 0
|
||||
let lastTick = 0
|
||||
|
||||
let x = ref(0)
|
||||
let y = ref(12)
|
||||
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))
|
||||
|
||||
// TODO: mock
|
||||
const blocked = {
|
||||
left: false,
|
||||
right: false,
|
||||
up: false,
|
||||
down: false,
|
||||
}
|
||||
|
||||
function dig() {
|
||||
console.warn('digging not yet implemented')
|
||||
}
|
||||
|
||||
function move(thisTick) {
|
||||
animationFrame = requestAnimationFrame(move)
|
||||
|
||||
// do nothing when paused, otherwise keep roughly 20 fps
|
||||
if (paused.value || thisTick - lastTick < 50) return
|
||||
updateTime()
|
||||
|
||||
player.vx = inputX.value
|
||||
player.vy = inputY.value
|
||||
|
||||
if (inputX.value) player.lastDir = inputX.value
|
||||
|
||||
let dx_ = dx.value
|
||||
let dy_ = dy.value
|
||||
|
||||
if (dx > 0 && blocked.right) dx_ = 0
|
||||
else if (dx < 0 && blocked.left) dx_ = 0
|
||||
|
||||
if (dy > 0 && blocked.down) dy_ = 0
|
||||
else if (dy < 0 && blocked.up) dy_ = 0
|
||||
|
||||
if (!inputY.value && digging.value) {
|
||||
dx_ = 0
|
||||
dig()
|
||||
}
|
||||
|
||||
x.value += dx_ * 32
|
||||
y.value += dy_ * 32
|
||||
lastTick = thisTick
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
lastTick = performance.now()
|
||||
move(lastTick)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,body,#app {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<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" />
|
||||
<div id="level-indicator">
|
||||
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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<canvas ref="canvas" id="background"></canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import solarQuartet from './solar-quartet'
|
||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'
|
||||
|
||||
export default {
|
||||
name: 'background',
|
||||
props: {
|
||||
x: Number,
|
||||
time: Number
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
redraw: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// x () { this.refresh() },
|
||||
time () { this.refresh() }
|
||||
},
|
||||
mounted () {
|
||||
const canvas = this.$refs.canvas
|
||||
const godraysCanvas = document.createElement('canvas')
|
||||
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'), ~~(canvas.width / 2.0), ~~(canvas.height / 2.0),
|
||||
godraysCanvas, godraysCanvas.getContext('2d'), godraysCanvas.width, godraysCanvas.height,
|
||||
)
|
||||
this.refresh()
|
||||
},
|
||||
computed: {
|
||||
/* time value to sun position conversion
|
||||
*
|
||||
* The time value rotates from 0 to 1000
|
||||
* sunY convertes it to values between 0 and -100,
|
||||
* while -100 is high sun position (aka day)
|
||||
* and 0 is low (aka night).
|
||||
* My adaption of Solar Quartet renders a static night sky from -30 upwards
|
||||
* and a static day at -70 or lower
|
||||
*/
|
||||
sunY () {
|
||||
// time is between 0 and 1000
|
||||
const p = Math.PI / 1000
|
||||
return Math.sin(this.time * p) * -100
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh () {
|
||||
// console.time('draw background')
|
||||
this.redraw(this.x, this.sunY)
|
||||
// console.timeEnd('draw background')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#background {
|
||||
display: block;
|
||||
width: var(--field-width);
|
||||
height: var(--field-height);
|
||||
object-fit: contain;
|
||||
background: black;
|
||||
}
|
||||
</style>
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<div class="block" :class="type"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'block',
|
||||
props: {
|
||||
type: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.block {
|
||||
flex: 0 0 auto;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: #6DA956;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.block.air { background-color: #33A; }
|
||||
.block.grass { background-color: #33A; height: 28px; border-bottom: 2px solid #0A0; }
|
||||
.block.soil { background-color: #543; }
|
||||
.block.gravel { background-color: #665; }
|
||||
.block.stone { background-color: #555; }
|
||||
.block.bedrock { background-color: #444; }
|
||||
.block:hover {
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
</style>
|
216
src/Field.vue
|
@ -1,216 +0,0 @@
|
|||
<template>
|
||||
<div id="field" :class="daytimeClass">
|
||||
<input v-keep-focussed type="text"
|
||||
@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"
|
||||
/>
|
||||
<mountain-background :x="128 + x / 8" :time="time" />
|
||||
<div id="wrap" :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="[player.direction]" />
|
||||
<div id="level-indicator">
|
||||
x:{{ floorX }}, y:{{ floorY }}
|
||||
<template v-if="moving !== false">({{clock}})</template>
|
||||
<template v-else>(PAUSED)</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import throttle from 'lodash/throttle'
|
||||
import MountainBackground from './Background'
|
||||
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: 12,
|
||||
inputX: 0,
|
||||
inputY: 0,
|
||||
time: 250,
|
||||
moving: false,
|
||||
lastTick: 0
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.lastTick = performance.now()
|
||||
this.move(this.lastTick)
|
||||
},
|
||||
computed: {
|
||||
rows () { return level.grid(this.floorX, this.floorY) },
|
||||
surroundings () {
|
||||
const px = PLAYER_X
|
||||
const py = PLAYER_Y
|
||||
const at = this.rows[py][px]
|
||||
const left = this.rows[py][px]
|
||||
const right = this.rows[py][px + 1]
|
||||
const up = this.rows[py - 1][px] || at
|
||||
const down = this.rows[py + 1][px]
|
||||
|
||||
return { at, left, right, up, down }
|
||||
},
|
||||
blocked () {
|
||||
const { at, left, right, up, down } = this.surroundings
|
||||
|
||||
return {
|
||||
at: !at.walkable,
|
||||
left: !left.walkable,
|
||||
right: !right.walkable,
|
||||
up: !up.walkable,
|
||||
down: !down.walkable
|
||||
}
|
||||
},
|
||||
floorX () { return Math.floor(this.x) },
|
||||
floorY () { return Math.floor(this.y) },
|
||||
tx () { return (this.x - this.floorX) * -BLOCK_SIZE },
|
||||
ty () { return (this.y - this.floorY) * -BLOCK_SIZE },
|
||||
daytimeClass () {
|
||||
const t = this.time
|
||||
if (t >= 900 || t < 80) return "night"
|
||||
|
||||
if (t >= 80 && t < 120) return "morning0"
|
||||
if (t >= 120 && t < 150) return "morning1"
|
||||
if (t >= 150 && t < 240) return "morning2"
|
||||
|
||||
if (t >= 700 && t < 800) return "evening0"
|
||||
if (t >= 800 && t < 850) return "evening1"
|
||||
if (t >= 850 && t < 900) return "evening2"
|
||||
|
||||
return "day"
|
||||
},
|
||||
clock () {
|
||||
const t = this.time * 86.4 // 1000 ticks to 86400 seconds (per day)
|
||||
const h = ~~(t / 3600.0)
|
||||
const m = ~~((t / 3600.0 - h) * 60.0)
|
||||
return `${(h + 2) % 24}:${m < 10 ? '0' : ''}${m}`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
move (thisTick) {
|
||||
this.moving = requestAnimationFrame(this.move)
|
||||
|
||||
// keep roughly 20 fps
|
||||
if (thisTick - this.lastTick < 50) return
|
||||
|
||||
// set time of day in ticks
|
||||
this.time = (this.time + 0.1) % 1000
|
||||
|
||||
const player = this.player
|
||||
const x = player.x
|
||||
const y = player.y
|
||||
|
||||
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
|
||||
if (dx < 0 && this.blocked.left) dx = 0
|
||||
if (dy > 0 && this.blocked.down) dy = 0
|
||||
if (dy < 0 && this.blocked.up) dy = 0
|
||||
|
||||
// don't walk, work!
|
||||
if (!this.inputY && this.digging) {
|
||||
dx = 0
|
||||
this.dig()
|
||||
}
|
||||
|
||||
this.x += dx
|
||||
this.y += dy
|
||||
this.lastTick = thisTick
|
||||
},
|
||||
dig () {
|
||||
console.log('dig', this.playerDirection, this.surroundings[this.playerDirection])
|
||||
// lets not bother with invincible blocks (like air or cave)
|
||||
if (this.surroundings[this.playerDirection].hp >= Infinity) return
|
||||
|
||||
const px = this.floorX + PLAYER_X
|
||||
const py = this.floorY + PLAYER_Y
|
||||
const block = {...this.surroundings[this.playerDirection]}
|
||||
|
||||
block.hp--
|
||||
level.change(py, px, block)
|
||||
},
|
||||
togglePause () {
|
||||
if (this.moving === false) { // is paused
|
||||
this.move()
|
||||
} else {
|
||||
cancelAnimationFrame(this.moving)
|
||||
this.moving = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="./assets/field.css" />
|
||||
<style>
|
||||
:root {
|
||||
--block-size: 32px;
|
||||
--field-width: 1024px;
|
||||
--field-height: 576px;
|
||||
--spare-blocks: 2;
|
||||
}
|
||||
|
||||
#level-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
}
|
||||
#player {
|
||||
position: absolute;
|
||||
left: calc(var(--field-width) / 2);
|
||||
top: calc(var(--field-height) / 2);
|
||||
background-image: url(./assets/dwarf_right.png);
|
||||
}
|
||||
#player.right { background-image: url(./assets/dwarf_right.png); }
|
||||
#player.left { background-image: url(./assets/dwarf_left.png); }
|
||||
#player.up { background-image: url(./assets/dwarf_back.png); }
|
||||
#player.down { background-image: url(./assets/dwarf_back.png); }
|
||||
#player, .block {
|
||||
flex: 0 0 auto;
|
||||
width: var(--block-size);
|
||||
height: var(--block-size);
|
||||
background-color: transparent;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#wrap {
|
||||
position: absolute;
|
||||
top: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||
left: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||
width: calc(var(--field-width) + var(--spare-blocks) * var(--block-size));
|
||||
height: calc(var(--field-height) + var(--spare-blocks) * var(--block-size));
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
</style>
|
|
@ -1 +0,0 @@
|
|||
dwarf_left.png
|
Before Width: | Height: | Size: 14 B After Width: | Height: | Size: 607 B |
BIN
src/assets/dwarf_back.png
Normal file
Before Width: | Height: | Size: 14 B After Width: | Height: | Size: 607 B |
|
@ -1,49 +1,34 @@
|
|||
#field {
|
||||
position: relative;
|
||||
width: var(--field-width);
|
||||
height: var(--field-height);
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background-color: #56F;
|
||||
}
|
||||
#field > input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.block.grass { background-image: url(./grass01.png); }
|
||||
|
||||
.block.tree_top_left { background-image: url(./tree_top_left.png); }
|
||||
.block.tree_top_middle { background-image: url(./tree_top_middle.png); }
|
||||
.block.tree_top_right { background-image: url(./tree_top_right.png); }
|
||||
.block.treeTopLeft { background-image: url(./tree_top_left.png); }
|
||||
.block.treeTopMiddle { background-image: url(./tree_top_middle.png); }
|
||||
.block.treeTopRight { background-image: url(./tree_top_right.png); }
|
||||
|
||||
.block.tree_crown_left { background-image: url(./tree_crown_left.png); }
|
||||
.block.tree_crown_middle { background-image: url(./tree_crown_middle.png); }
|
||||
.block.tree_crown_right { background-image: url(./tree_crown_right.png); }
|
||||
.block.treeCrownLeft { background-image: url(./tree_crown_left.png); }
|
||||
.block.treeCrownMiddle { background-image: url(./tree_crown_middle.png); }
|
||||
.block.treeCrownRight { background-image: url(./tree_crown_right.png); }
|
||||
|
||||
.block.tree_trunk_left { background-image: url(./tree_trunk_left.png); }
|
||||
.block.tree_trunk_middle { background-image: url(./tree_trunk_middle.png); }
|
||||
.block.tree_trunk_right { background-image: url(./tree_trunk_right.png); }
|
||||
.block.treeTrunkLeft { background-image: url(./tree_trunk_left.png); }
|
||||
.block.treeTrunkMiddle { background-image: url(./tree_trunk_middle.png); }
|
||||
.block.treeTrunkRight { background-image: url(./tree_trunk_right.png); }
|
||||
|
||||
.block.tree_root_left { background-image: url(./tree_root_left.png); }
|
||||
.block.tree_root_middle { background-image: url(./tree_root_middle.png); }
|
||||
.block.tree_root_right { background-image: url(./tree_root_right.png); }
|
||||
.block.treeRootLeft { background-image: url(./tree_root_left.png); }
|
||||
.block.treeRootMiddle { background-image: url(./tree_root_middle.png); }
|
||||
.block.treeRootRight { background-image: url(./tree_root_right.png); }
|
||||
|
||||
.block.tree_top_left_mixed { background-image: url(./tree_top_left_mixed.png); }
|
||||
.block.tree_crown_left_mixed { background-image: url(./tree_crown_left_mixed.png); }
|
||||
.block.tree_trunk_left_mixed { background-image: url(./tree_trunk_left_mixed.png); }
|
||||
.block.tree_root_left_mixed { background-image: url(./tree_root_left_mixed.png); }
|
||||
.block.treeTopLeftMixed { background-image: url(./tree_top_left_mixed.png); }
|
||||
.block.treeCrownLeftMixed { background-image: url(./tree_crown_left_mixed.png); }
|
||||
.block.treeTrunkLeftMixed { background-image: url(./tree_trunk_left_mixed.png); }
|
||||
.block.treeRootLeftMixed { background-image: url(./tree_root_left_mixed.png); }
|
||||
|
||||
.block.tree_top_right_mixed { background-image: url(./tree_top_right_mixed.png); }
|
||||
.block.tree_crown_right_mixed { background-image: url(./tree_crown_right_mixed.png); }
|
||||
.block.tree_trunk_right_mixed { background-image: url(./tree_trunk_right_mixed.png); }
|
||||
.block.tree_root_right_mixed { background-image: url(./tree_root_right_mixed.png); }
|
||||
.block.treeTopRightMixed { background-image: url(./tree_top_right_mixed.png); }
|
||||
.block.treeCrownRightMixed { background-image: url(./tree_crown_right_mixed.png); }
|
||||
.block.treeTrunkRightMixed { background-image: url(./tree_trunk_right_mixed.png); }
|
||||
.block.treeRootRightMixed { background-image: url(./tree_root_right_mixed.png); }
|
||||
|
||||
.block.soil { background-image: url(./soil.png); }
|
||||
.block.soil_gravel { background-image: url(./soil_gravel.png); }
|
||||
.block.stone_gravel { background-image: url(./rock_gravel.png); }
|
||||
.block.soilGravel { background-image: url(./soil_gravel.png); }
|
||||
.block.stoneGravel { background-image: url(./rock_gravel.png); }
|
||||
.block.stone { background-image: url(./rock.png); }
|
||||
.block.bedrock { background-image: url(./bedrock.png); }
|
||||
.block.cave { background-color: #000; }
|
||||
|
@ -58,3 +43,32 @@
|
|||
.evening2 .block, .evening2 #player { filter: brightness(0.4) hue-rotate(-10deg) saturate(50%); }
|
||||
|
||||
.night .block, .night #player { filter: brightness(0.3) saturate(30%); }
|
||||
|
||||
#player {
|
||||
position: absolute;
|
||||
left: calc(var(--field-width) / 2);
|
||||
top: calc(var(--field-height) / 2);
|
||||
background-image: url(./dwarf_right.png);
|
||||
}
|
||||
#player.right { background-image: url(./dwarf_right.png); }
|
||||
#player.left { background-image: url(./dwarf_left.png); }
|
||||
#player.up { background-image: url(./dwarf_back.png); }
|
||||
#player.down { background-image: url(./dwarf_back.png); }
|
||||
#player, .block {
|
||||
flex: 0 0 auto;
|
||||
width: var(--block-size);
|
||||
height: var(--block-size);
|
||||
background-color: transparent;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#blocks {
|
||||
position: absolute;
|
||||
top: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||
left: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||
width: calc(var(--field-width) + var(--spare-blocks) * var(--block-size));
|
||||
height: calc(var(--field-height) + var(--spare-blocks) * var(--block-size));
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
|
52
src/level/blockGen.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import type { NoiseFunction2D } from 'simplex-noise'
|
||||
import {blockTypes as T, level as L, probability as P, type Block} from './def'
|
||||
|
||||
export default function createBlockGenerator(noise2D: NoiseFunction2D) {
|
||||
const rand: NoiseFunction2D = (x, y) => 0.5 + 0.5 * noise2D(x, y)
|
||||
|
||||
// randomly generate a block
|
||||
// level: number, smaller is "higher"
|
||||
// column: number, the x-axis
|
||||
// before: Block, the block type left of (before) this block
|
||||
// above: Block, the block type above this block
|
||||
const generateBlock = (level: number, column: number, before: Block, above: Block): Block => {
|
||||
// no randomness needed, there is always air above the trees
|
||||
if (level < L.treeTop) return T.air
|
||||
|
||||
const r = rand(level, column)
|
||||
|
||||
// Air layer: mostly air, sometimes trees
|
||||
if (level < L.ground) {
|
||||
if (level === L.treeTop && r < P.tree) return T.treeTopMiddle
|
||||
return T.air
|
||||
}
|
||||
|
||||
// Soil layer: Mostly soil, sometimes gravel
|
||||
if (level < L.rock) {
|
||||
if (r < P.soilGravel) return T.soilGravel
|
||||
else return T.soil
|
||||
}
|
||||
|
||||
// Rock level: Mostly stone, sometimes gravel
|
||||
if (level < L.underground) {
|
||||
if (r < P.stoneGravel) return T.stoneGravel
|
||||
else return T.stone
|
||||
}
|
||||
|
||||
// Underground: Mostly bedrock, sometimes caves
|
||||
// the probability for a cave rises with the level
|
||||
const a = P.cave / L.caveMax**2
|
||||
const p = Math.min(P.cave, a * level**2)
|
||||
|
||||
if (r < p) return T.cave
|
||||
return T.bedrock
|
||||
}
|
||||
|
||||
const fillRow = (level: number, column: number, row: Block[], previousRow: Block[]) => {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
row[i] = generateBlock(level, column + i, row[i - 1], previousRow[i])
|
||||
}
|
||||
}
|
||||
|
||||
return fillRow
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
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},
|
||||
|
||||
tree_top_left: {type: 'tree_top_left', hp: 5, walkable: true},
|
||||
tree_top_middle: {type: 'tree_top_middle', hp: 5, walkable: true},
|
||||
tree_top_right: {type: 'tree_top_right', hp: 5, walkable: true},
|
||||
|
||||
tree_crown_left: {type: 'tree_crown_left', hp: 5, walkable: true},
|
||||
tree_crown_middle: {type: 'tree_crown_middle', hp: 5, walkable: true, climbable: true},
|
||||
tree_crown_right: {type: 'tree_crown_right', hp: 5, walkable: true},
|
||||
|
||||
tree_trunk_left: {type: 'tree_trunk_left', hp: 5, walkable: true},
|
||||
tree_trunk_middle: {type: 'tree_trunk_middle', hp: 5, walkable: true, climbable: true},
|
||||
tree_trunk_right: {type: 'tree_trunk_right', hp: 5, walkable: true},
|
||||
|
||||
tree_root_left: {type: 'tree_root_left', hp: 5, walkable: true},
|
||||
tree_root_middle: {type: 'tree_root_middle', hp: 5, walkable: true, climbable: true},
|
||||
tree_root_right: {type: 'tree_root_right', hp: 5, walkable: true},
|
||||
|
||||
tree_top_left_mixed: {type: 'tree_top_left_mixed', hp: 5, walkable: true},
|
||||
tree_crown_left_mixed: {type: 'tree_crown_left_mixed', hp: 5, walkable: true},
|
||||
tree_trunk_left_mixed: {type: 'tree_trunk_left_mixed', hp: 5, walkable: true},
|
||||
tree_root_left_mixed: {type: 'tree_root_left_mixed', hp: 5, walkable: true},
|
||||
|
||||
tree_top_right_mixed: {type: 'tree_top_right_mixed', hp: 5, walkable: true},
|
||||
tree_crown_right_mixed: {type: 'tree_crown_right_mixed', hp: 5, walkable: true},
|
||||
tree_trunk_right_mixed: {type: 'tree_trunk_right_mixed', hp: 5, walkable: true},
|
||||
tree_root_right_mixed: {type: 'tree_root_right_mixed', hp: 5, walkable: true},
|
||||
|
||||
soil: {type: 'soil', hp: 2, walkable: false},
|
||||
soil_gravel: {type: 'soil_gravel', hp: 5, walkable: false},
|
||||
stone_gravel: {type: 'stone_gravel', hp: 5, walkable: false},
|
||||
stone: {type: 'stone', hp: 10, walkable: false},
|
||||
bedrock: {type: 'bedrock', hp: 25, walkable: false},
|
||||
cave: {type: 'cave', hp: Infinity, walkable: true}
|
||||
}
|
||||
|
||||
export const level = {
|
||||
treeTop: 24,
|
||||
ground: 28,
|
||||
rock: 32,
|
||||
underground: 48,
|
||||
cave_max: 250
|
||||
}
|
||||
|
||||
export const probability = {
|
||||
tree: 0.2,
|
||||
soil_hole: 0.3,
|
||||
soil_gravel: 0.2,
|
||||
stone_gravel: 0.1,
|
||||
cave: 0.5,
|
||||
fray: 0.4
|
||||
}
|
73
src/level/def.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
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 type Block = {
|
||||
type: string,
|
||||
hp: number,
|
||||
walkable: boolean,
|
||||
climbable?: boolean,
|
||||
}
|
||||
|
||||
export const blockTypes: Record<string, Block> = {
|
||||
air: { type: 'air', hp: Infinity, walkable: true },
|
||||
grass: { type: 'grass', hp: 1, walkable: false },
|
||||
|
||||
treeTopLeft: { type: 'treeTopLeft', hp: 5, walkable: true },
|
||||
treeTopMiddle: { type: 'treeTopMiddle', hp: 5, walkable: true },
|
||||
treeTopRight: { type: 'treeTopRight', hp: 5, walkable: true },
|
||||
|
||||
treeCrownLeft: { type: 'treeCrownLeft', hp: 5, walkable: true },
|
||||
treeCrownMiddle: { type: 'treeCrownMiddle', hp: 5, walkable: true, climbable: true },
|
||||
treeCrownRight: { type: 'treeCrownRight', hp: 5, walkable: true },
|
||||
|
||||
treeTrunkLeft: { type: 'treeTrunkLeft', hp: 5, walkable: true },
|
||||
treeTrunkMiddle: { type: 'treeTrunkMiddle', hp: 5, walkable: true, climbable: true },
|
||||
treeTrunkRight: { type: 'treeTrunkRight', hp: 5, walkable: true },
|
||||
|
||||
treeRootLeft: { type: 'treeRootLeft', hp: 5, walkable: true },
|
||||
treeRootMiddle: { type: 'treeRootMiddle', hp: 5, walkable: true, climbable: true },
|
||||
treeRootRight: { type: 'treeRootRight', hp: 5, walkable: true },
|
||||
|
||||
treeTopLeftMixed: { type: 'treeTopLeftMixed', hp: 5, walkable: true },
|
||||
treeCrownLeftMixed: { type: 'treeCrownLeftMixed', hp: 5, walkable: true },
|
||||
treeTrunkLeftMixed: { type: 'treeTrunkLeftMixed', hp: 5, walkable: true },
|
||||
treeRootLeftMixed: { type: 'treeRootLeftMixed', hp: 5, walkable: true },
|
||||
|
||||
treeTopRightMixed: { type: 'treeTopRightMixed', hp: 5, walkable: true },
|
||||
treeCrownRightMixed: { type: 'treeCrownRightMixed', hp: 5, walkable: true },
|
||||
treeTrunkRightMixed: { type: 'treeTrunkRightMixed', hp: 5, walkable: true },
|
||||
treeRootRightMixed: { type: 'treeRootRightMixed', hp: 5, walkable: true },
|
||||
|
||||
soil: { type: 'soil', hp: 2, walkable: false },
|
||||
soilGravel: { type: 'soilGravel', hp: 5, walkable: false },
|
||||
stoneGravel: { type: 'stoneGravel', hp: 5, walkable: false },
|
||||
stone: { type: 'stone', hp: 10, walkable: false },
|
||||
bedrock: { type: 'bedrock', hp: 25, walkable: false },
|
||||
cave: { type: 'cave', hp: Infinity, walkable: true },
|
||||
}
|
||||
|
||||
export const level = {
|
||||
treeTop: 24,
|
||||
ground: 28,
|
||||
rock: 32,
|
||||
underground: 48,
|
||||
caveMax: 250,
|
||||
}
|
||||
|
||||
export const probability = {
|
||||
tree: 0.2,
|
||||
soilHole: 0.3,
|
||||
soilGravel: 0.2,
|
||||
stoneGravel: 0.1,
|
||||
cave: 0.5,
|
||||
fray: 0.4,
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
import {type as T, level as L, probability as P} from './def'
|
||||
|
||||
export default class BlockGen {
|
||||
constructor (noiseGen) {
|
||||
this.rand = (x, y) => 0.5 + 0.5 * noiseGen.noise2D(x, y)
|
||||
}
|
||||
|
||||
level (level, column, row, previousRow) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
row[i] = this.block(level, column + i, row[i], row[i - 1], previousRow[i])
|
||||
}
|
||||
}
|
||||
|
||||
block (level, column, current, before, above) {
|
||||
if (level < L.peak) return this.air()
|
||||
|
||||
const r = this.rand(level, column)
|
||||
if (level < L.ground) {
|
||||
if (level === L.treeTop) return this.treeTop(r)
|
||||
return this.air()
|
||||
}
|
||||
if (level < L.rock) return this.ground(r)
|
||||
if (level < L.underground) return this.rock(r)
|
||||
return this.underground(r, above, before, level - L.underground)
|
||||
}
|
||||
|
||||
// always returns air
|
||||
air () {
|
||||
return T.air
|
||||
}
|
||||
|
||||
// returns mostly air, but sometimes starts a tree
|
||||
treeTop (r) {
|
||||
if (r < P.tree) return T.tree_top_middle
|
||||
return T.air
|
||||
}
|
||||
|
||||
// returns mostly soil and grass, sometimes gravel and sometimes air
|
||||
ground (r) {
|
||||
if (r < P.soil_gravel) return T.soil_gravel
|
||||
return T.soil
|
||||
}
|
||||
|
||||
// returns mostly stones, sometimes gravel
|
||||
rock (r) {
|
||||
return r < P.stone_gravel ? T.stone_gravel : T.stone
|
||||
}
|
||||
|
||||
// return mostly bedrock, sometimes caves, depending on the level
|
||||
underground (r, above, before, level) {
|
||||
// the probability for a cave rises with the level
|
||||
const a = P.cave / L.cave_max**2
|
||||
const p = Math.min(P.cave, a * level**2)
|
||||
|
||||
if (r < p) return T.cave
|
||||
return T.bedrock
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import SeedRng from 'seedrandom'
|
||||
import SimplexNoise from 'open-simplex-noise'
|
||||
|
||||
import {type as T, level as L} from './def'
|
||||
import BlockGen from './first-iteration'
|
||||
import BlockExt from './second-iteration'
|
||||
import PlayerChanges from './third-iteration'
|
||||
|
||||
export default class Level {
|
||||
constructor (width, height, seed = 'super random seed') {
|
||||
const random = SeedRng(seed)
|
||||
const noiseGen = new SimplexNoise(parseInt(seed, 32))
|
||||
this._w = width
|
||||
this._h = height
|
||||
this._grid = new Array(this._h)
|
||||
this.blockGen = new BlockGen(noiseGen)
|
||||
this.blockExt = new BlockExt(noiseGen)
|
||||
this.playerChanges = new PlayerChanges()
|
||||
}
|
||||
|
||||
change (level, column, newBlock) {
|
||||
if (newBlock.hp <= 0) {
|
||||
newBlock = level > L.rock ? { ...T.cave } : { ...T.air }
|
||||
}
|
||||
this.playerChanges.apply(level, column, newBlock)
|
||||
}
|
||||
|
||||
grid (x, y) {
|
||||
this.generate(x, y, this._w, this._h)
|
||||
return this._grid
|
||||
}
|
||||
|
||||
generate (column, y, width, height) {
|
||||
for (let i = 0; i < height; i++) {
|
||||
const level = y + i
|
||||
const row = Array(width)
|
||||
const previousRow = this._grid[i - 1] || Array()
|
||||
this.blockGen.level(level, column, row, previousRow)
|
||||
this.blockExt.level(level, column, row, previousRow)
|
||||
this.playerChanges.level(level, column, row)
|
||||
this._grid[i] = row
|
||||
}
|
||||
}
|
||||
}
|
40
src/level/index.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import alea from 'alea'
|
||||
import { createNoise2D } from 'simplex-noise'
|
||||
import createBlockGenerator from './blockGen'
|
||||
|
||||
import {blockTypes as T, level as L, type Block} from './def'
|
||||
//import BlockGen from './first-iteration'
|
||||
//import BlockExt from './second-iteration'
|
||||
//import PlayerChanges from './third-iteration'
|
||||
|
||||
export default function createLevel(width: number, height: number, seed = 'very random seed') {
|
||||
const prng = alea(seed)
|
||||
const noise2D = createNoise2D(prng)
|
||||
|
||||
const _grid: Block[][] = new Array(height)
|
||||
const blockGen = createBlockGenerator(noise2D)
|
||||
|
||||
// Apply changes, coming from the player (tocktocktock-plopp!)
|
||||
function change (level: number, column: number, newBlock: Block) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
function generate(column: number, y: number) {
|
||||
for (let i = 0; i < height; i++) {
|
||||
const level = y+i
|
||||
const row: Block[] = Array(width)
|
||||
const previousRow = i ? _grid[i-1] : [] as Block[]
|
||||
|
||||
blockGen(level, column, row, previousRow)
|
||||
|
||||
_grid[i] = row
|
||||
}
|
||||
}
|
||||
|
||||
function grid(x: number, y: number) {
|
||||
generate(x, y)
|
||||
return _grid
|
||||
}
|
||||
|
||||
return { grid, change }
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
import {type as T, level as L, probability as P} from './def'
|
||||
|
||||
export default class BlockExt {
|
||||
constructor (noiseGen) {
|
||||
this.rand = (x, y) => 0.5 + 0.5 * noiseGen.noise2D(x, y)
|
||||
}
|
||||
|
||||
level (level, column, row, previousRow) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const r = Math.abs(this.rand(level, column + i))
|
||||
|
||||
if (level < L.ground) this.trees(r, i, row, previousRow, level)
|
||||
else if (level < L.rock) this.ground(r, i, row, previousRow)
|
||||
else if (level < L.underground) this.rock(r, i, row, previousRow)
|
||||
else this.underground(r, i, row, previousRow)
|
||||
}
|
||||
}
|
||||
|
||||
trees (r, i, row, previousRow, level) {
|
||||
const max = row.length - 1
|
||||
|
||||
if (row[i] === T.tree_top_middle) {
|
||||
if (i) {
|
||||
if (row[i - 1] === T.tree_top_right) row[i - 1] = T.tree_top_left_mixed
|
||||
else row[i - 1] = T.tree_top_left
|
||||
}
|
||||
if (i < max) row[i + 1] = T.tree_top_right
|
||||
|
||||
} else if (previousRow[i] === T.tree_top_middle) {
|
||||
row[i] = T.tree_crown_middle
|
||||
if (i) {
|
||||
if (row[i - 1] === T.tree_crown_right) row[i - 1] = T.tree_crown_left_mixed
|
||||
else row[i - 1] = T.tree_crown_left
|
||||
}
|
||||
if (i < max) row[i + 1] = T.tree_crown_right
|
||||
} else if (previousRow[i] === T.tree_crown_middle) {
|
||||
row[i] = T.tree_trunk_middle
|
||||
if (i) {
|
||||
if (row[i - 1] === T.tree_trunk_right) row[i - 1] = T.tree_trunk_left_mixed
|
||||
else row[i - 1] = T.tree_trunk_left
|
||||
}
|
||||
if (i < max) row[i + 1] = T.tree_trunk_right
|
||||
} else if (previousRow[i] === T.tree_trunk_middle) {
|
||||
row[i] = T.tree_root_middle
|
||||
if (i) {
|
||||
if (row[i - 1] === T.tree_root_right) row[i - 1] = T.tree_root_left_mixed
|
||||
else row[i - 1] = T.tree_root_left
|
||||
}
|
||||
if (i < max) row[i + 1] = T.tree_root_right
|
||||
}
|
||||
}
|
||||
|
||||
ground (r, i, row, previousRow) {
|
||||
const tree_parts = [T.tree_root_left, T.tree_root_middle, T.tree_root_right]
|
||||
if (previousRow[i] === T.air) {
|
||||
if (r < P.soil_hole) row[i] = T.air
|
||||
if (row[i] === T.soil) row[i] = T.grass
|
||||
} else if (tree_parts.indexOf(previousRow[i]) >= 0) {
|
||||
if (row[i] === T.soil) row[i] = T.grass
|
||||
}
|
||||
}
|
||||
|
||||
rock (r, i, row, previousRow) {
|
||||
if (previousRow[i] === T.soil && r < P.fray) row[i] = T.soil
|
||||
}
|
||||
|
||||
underground (r, i, row, previousRow) {
|
||||
if (previousRow[i] === T.stone && r < P.fray) row[i] = T.stone
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
export default class PlayerChanges {
|
||||
constructor () {
|
||||
this.changes = {}
|
||||
}
|
||||
|
||||
getKey (level, column) {
|
||||
return `${column}.${level}`
|
||||
}
|
||||
|
||||
apply (level, column, newBlock) {
|
||||
const key = this.getKey(level, column)
|
||||
this.changes[key] = newBlock
|
||||
console.log('applied', level, column, newBlock, this.changes)
|
||||
}
|
||||
|
||||
level (level, column, row) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const key = this.getKey(level - 1, column + i)
|
||||
const change = this.changes[key]
|
||||
if (change) row[i] = change
|
||||
}
|
||||
}
|
||||
}
|
14
src/main.js
|
@ -1,14 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
Vue.directive('keep-focussed', {
|
||||
inserted (el, binding) {
|
||||
el.focus()
|
||||
el.addEventListener('blur', () => el.focus())
|
||||
}
|
||||
})
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
5
src/main.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { createApp } from "vue";
|
||||
import "./assets/field.css";
|
||||
import App from "./App.vue";
|
||||
|
||||
createApp(App).mount("#app");
|
|
@ -1,22 +0,0 @@
|
|||
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'
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/* Adapted from the original "Solar Quartet" by y0natan
|
||||
* https://codepen.io/y0natan/pen/MVvxBM
|
||||
* https://js1k.com/2018-coins/demo/3075
|
||||
*/
|
||||
|
||||
// sunY sets the height of the sun and with this the time of the day
|
||||
// where 0 is lowest (night) and -100 is highest (day), other values are possible
|
||||
// but don't make much sense / difference
|
||||
export default function drawFrame (canvas, ctx, width, height, grCanvas, grCtx, grWidth, grHeight, frame, sunY) {
|
||||
// reset canvas state
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
grCanvas.width = grWidth
|
||||
grCanvas.height = grHeight
|
||||
|
||||
const sunCenterX = grWidth / 2
|
||||
const sunCenterY = grHeight / 2 + sunY
|
||||
// Set the godrays' context fillstyle to a newly created gradient
|
||||
// which we also run through our abbreviator.
|
||||
let emissionGradient = grCtx.createRadialGradient(
|
||||
sunCenterX, sunCenterY, // The sun's center.
|
||||
0, // Start radius.
|
||||
sunCenterX, sunCenterY, // The sun's center.
|
||||
44 // End radius.
|
||||
)
|
||||
grCtx.fillStyle = emissionGradient
|
||||
|
||||
// Now we addColorStops. This needs to be a dark gradient because our
|
||||
// godrays effect will basically overlay it on top of itself many many times,
|
||||
// so anything lighter will result in lots of white.
|
||||
// If you're not space-bound you can add another stop or two, maybe fade out to black,
|
||||
// but this actually looks good enough.
|
||||
|
||||
// a black "gradient" means no emission, so we fade to black as transition to night or day
|
||||
let emissionStrength = 1.0
|
||||
if (sunY > -30) emissionStrength -= Math.max((30 + sunY) / 5, 0.0)
|
||||
else if (sunY < -60) emissionStrength += Math.min(1 + (60 + sunY) / 5, 0.0)
|
||||
|
||||
emissionGradient.addColorStop(.1, `hsl(30,50%,${3.1 * emissionStrength}%)`) // pixels in radius 0 to 4.4 (44 * .1).
|
||||
emissionGradient.addColorStop(.2, `hsl(12,71%,${1.4 * emissionStrength}%)`) // pixels in radius 0 to 4.4 (44 * .1).
|
||||
|
||||
// Now paint the gradient all over our godrays canvas.
|
||||
grCtx.fillRect(0, 0, grWidth, grHeight)
|
||||
|
||||
// And set the fillstyle to black, we'll use it to paint our occlusion (mountains).
|
||||
grCtx.fillStyle = '#000'
|
||||
|
||||
// Paint the sky
|
||||
const skyGradient = ctx.createLinearGradient(0, 0, 0, height)
|
||||
|
||||
// hue from blue to red depending on the suns position
|
||||
const skyHue = 360 + sunY
|
||||
// lesser saturation at day so that the red fades away
|
||||
const skySaturation = 100 + sunY
|
||||
// darker at night
|
||||
const skyLightness = Math.min(sunY * -1 - 10, 55)
|
||||
|
||||
const skyHSLTop = `hsl(220, 70%, ${skyLightness}%)`
|
||||
const skyHSLBottom = `hsl(${skyHue}, ${skySaturation}%, ${skyLightness}%)`
|
||||
skyGradient.addColorStop(0, skyHSLTop)
|
||||
skyGradient.addColorStop(.7, skyHSLBottom)
|
||||
|
||||
ctx.fillStyle = skyGradient
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
// Our mountains will be made by summing up sine waves of varying frequencies and amplitudes.
|
||||
function mountainHeight(position, roughness) {
|
||||
// Our frequencies (prime numbers to avoid extra repetitions).
|
||||
// TODO: play with the numbers
|
||||
let frequencies = [1721, 947, 547, 233, 73, 31, 7]
|
||||
// Add them up.
|
||||
return frequencies.reduce((height, freq) => height * roughness - Math.cos(freq * position), 0)
|
||||
}
|
||||
|
||||
// Draw 4 layers of mountains.
|
||||
for(let i = 0; i < 4; i++) {
|
||||
// Set the main canvas fillStyle to a shade of green-brown with variable lightness
|
||||
// depending on sunY and depth
|
||||
|
||||
if (sunY > -60) {
|
||||
ctx.fillStyle = `hsl(5, 23%, ${33*emissionStrength - i*6*emissionStrength}%)`
|
||||
} else {
|
||||
ctx.fillStyle = `hsl(${220 - i*40}, 23%, ${33-i*6}%)`
|
||||
}
|
||||
|
||||
// For each column in our canvas...
|
||||
for(let x = width; x--;) {
|
||||
// Ok, I don't really remember the details here, basically the (frame+frame*i*i) makes the
|
||||
// near mountains move faster than the far ones. We divide by large numbers because our
|
||||
// mountains repeat at position 1/7*Math.PI*2 or something like that...
|
||||
let mountainPosition = (frame * 2 * i**2) / 1000 + x / 2000;
|
||||
// Make further mountains more jagged, adds a bit of realism and also makes the godrays
|
||||
// look nicer.
|
||||
let mountainRoughness = i / 19 - .5;
|
||||
// 128 is the middle, i * 25 moves the nearer mountains lower on the screen.
|
||||
let y = 128 + i * 25 + mountainHeight(mountainPosition, mountainRoughness) * 45;
|
||||
// Paint a 1px-wide rectangle from the mountain's top to below the bottom of the canvas.
|
||||
ctx.fillRect(x, y, 1, 999); // 999 can be any large number...
|
||||
// Paint the same thing in black on the godrays emission canvas, which is 1/4 the size,
|
||||
// and move it one pixel down (otherwise there can be a tiny underlit space between the
|
||||
// mountains and the sky).
|
||||
grCtx.fillRect(x/4, y/4+1, 1, 999);
|
||||
}
|
||||
}
|
||||
|
||||
// The godrays are generated by adding up RGB values, gCt is the bane of all js golfers -
|
||||
// globalCompositeOperation. Set it to 'lighter' on both canvases.
|
||||
ctx.globalCompositeOperation = grCtx.globalCompositeOperation = 'lighter';
|
||||
|
||||
// NOW - let's light this motherfucker up! We'll make several passes over our emission canvas,
|
||||
// each time adding an enlarged copy of it to itself so at the first pass we get 2 copies, then 4,
|
||||
// then 8, then 16 etc... We square our scale factor at each iteration.
|
||||
for (let scaleFactor = 1.07; scaleFactor < 5; scaleFactor *= scaleFactor) {
|
||||
// The x, y, width and height arguments for drawImage keep the light source in the same
|
||||
// spot on the enlarged copy. It basically boils down to multiplying a 2D matrix by itself.
|
||||
// There's probably a better way to do this, but I couldn't figure it out.
|
||||
// For reference, here's an AS3 version (where BitmapData:draw takes a matrix argument):
|
||||
// https://github.com/yonatan/volumetrics/blob/d3849027213e9499742cc4dfd2838c6032f4d9d3/src/org/zozuar/volumetrics/EffectContainer.as#L208-L209
|
||||
grCtx.drawImage(
|
||||
grCanvas,
|
||||
(grWidth - grWidth * scaleFactor) / 2,
|
||||
(grHeight - grHeight * scaleFactor) / 2 - sunY * scaleFactor + sunY,
|
||||
grWidth * scaleFactor,
|
||||
grHeight * scaleFactor
|
||||
)
|
||||
}
|
||||
|
||||
// Draw godrays to output canvas (whose globalCompositeOperation is already set to 'lighter').
|
||||
ctx.drawImage(grCanvas, 0, 0, width, height);
|
||||
}
|
65
src/util/useInput.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { ref } from 'vue'
|
||||
|
||||
export default function useInput() {
|
||||
let inputX = ref(0)
|
||||
let inputY = ref(0)
|
||||
let digging = ref(false)
|
||||
let paused = ref(false)
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
inputY.value = -1
|
||||
break
|
||||
case 'ArrowDown':
|
||||
inputY.value = 1
|
||||
break
|
||||
case 'ArrowRight':
|
||||
inputX.value = 1
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
inputX.value = -1
|
||||
break
|
||||
case 'p':
|
||||
paused.value = !paused.value
|
||||
break
|
||||
case ' ':
|
||||
digging.value = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
// Arrow Keys
|
||||
case 'ArrowUp':
|
||||
inputY.value = inputY.value === -1 ? 0 : 1
|
||||
break
|
||||
case 'ArrowDown':
|
||||
inputY.value = inputY.value === 1 ? 0 : 1
|
||||
break
|
||||
case 'ArrowRight':
|
||||
inputX.value = inputX.value === 1 ? 0 : -1
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
inputX.value = inputX.value === -1 ? 0 : 1
|
||||
break
|
||||
case ' ':
|
||||
digging.value = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
document.removeEventListener('keyup', handleKeyUp)
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
document.addEventListener('keyup', handleKeyUp)
|
||||
|
||||
return {
|
||||
inputX,
|
||||
inputY,
|
||||
digging,
|
||||
paused,
|
||||
}
|
||||
}
|
26
src/util/usePlayer.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
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)
|
||||
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,
|
||||
lastDir: 0,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
})
|
||||
|
||||
export default function usePlayer() {
|
||||
const dx = computed(() => player.vx * RECIPROCAL)
|
||||
const dy = computed(() => player.vy * RECIPROCAL)
|
||||
const direction = computed(() => (player.lastDir < 0 ? 'left' : 'right'))
|
||||
|
||||
return { player, direction, dx, dy }
|
||||
}
|
30
src/util/useTime.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { ref, computed } from 'vue'
|
||||
|
||||
export default function useTime() {
|
||||
// the day is split in 1000 parts, so we start in the morning
|
||||
const time = ref(250)
|
||||
|
||||
function updateTime() {
|
||||
time.value = (time.value + 0.1) % 1000
|
||||
}
|
||||
|
||||
const timeOfDay = computed(() => {
|
||||
if (time.value >= 900 || time.value < 80) return 'night'
|
||||
if (time.value >= 80 && time.value < 120) return 'morning0'
|
||||
if (time.value >= 120 && time.value < 150) return 'morning1'
|
||||
if (time.value >= 150 && time.value < 240) return 'morning2'
|
||||
if (time.value >= 700 && time.value < 800) return 'evening0'
|
||||
if (time.value >= 800 && time.value < 850) return 'evening1'
|
||||
if (time.value >= 850 && time.value < 900) return 'evening2'
|
||||
return 'day'
|
||||
})
|
||||
|
||||
const clock = computed(() => {
|
||||
const t = time.value * 86.4 // 1000 ticks to 86400 seconds (per day)
|
||||
const h = ~~(t / 3600.0)
|
||||
const m = ~~((t / 3600.0 - h) * 60.0)
|
||||
return `${(h + 2) % 24}:${m < 10 ? '0' : ''}${m}`
|
||||
})
|
||||
|
||||
return { time, updateTime, timeOfDay, clock }
|
||||
}
|
1
src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -1,47 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Simplex noise</title>
|
||||
<style>
|
||||
body, html { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; background: black; }
|
||||
canvas { display: block; width: 1024px; height: 768px; margin: calc(50vh - 768px / 2) auto 0; border: 2px solid #333; }
|
||||
</style>
|
||||
<canvas></canvas>
|
||||
<script src='simplex.js?8'></script>
|
||||
<script>
|
||||
var canvas = document.getElementsByTagName('canvas')[0];
|
||||
canvas.width = 1024;
|
||||
canvas.height = 768;
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var image = ctx.createImageData(canvas.width, canvas.height);
|
||||
var data = image.data;
|
||||
|
||||
var simplex = new SimplexNoise
|
||||
|
||||
function paint(data, w, h, steps = 100, threshold = 0, inverse = false) {
|
||||
for (var x = 0; x < w; x++) {
|
||||
for (var y = 0; y < h; y++) {
|
||||
var value = simplex.noise(x / steps, y / steps);
|
||||
value = 128 - 128 * value;
|
||||
// value = Math.abs(256 * value);
|
||||
|
||||
var cell = (x + y * w) * 4;
|
||||
if (threshold) value = value < threshold ? 0 : 255
|
||||
if (inverse) value = 255 - value
|
||||
data[cell] = value * x / y; // red
|
||||
data[cell + 1] = value * y / x; // green
|
||||
data[cell + 2] = 0; // blue
|
||||
// data[cell] += Math.max(0, (25 - value) * 8);
|
||||
data[cell + 3] = 255; // alpha.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.time('simplex canvas')
|
||||
paint(data, 1024, 768, 100)
|
||||
console.timeEnd('simplex canvas')
|
||||
|
||||
ctx.fillColor = 'black';
|
||||
ctx.fillRect(0, 0, 100, 100);
|
||||
ctx.putImageData(image, 0, 0);
|
||||
</script>
|
|
@ -1,98 +0,0 @@
|
|||
// Original from Stefan Gustavson's Java implementation,
|
||||
// see http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
||||
// ported to JavaScript by Sean McCullough
|
||||
// Adapted and Ported to ES6 by Norman Köhring <n‹Æt›koehr.in>
|
||||
|
||||
const { floor, sqrt } = Math
|
||||
const grad3 = [
|
||||
[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],
|
||||
[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],
|
||||
[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]
|
||||
]
|
||||
|
||||
const F2 = 0.5 * (sqrt(3.0) - 1.0)
|
||||
const G2 = (3.0 - sqrt(3.0)) / 6.0
|
||||
|
||||
function dot (i, x, y) {
|
||||
const g = grad3[i]
|
||||
return g[0] * x + g[1] * y
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage new SimplexNoise( r: { random: Callable } )
|
||||
*/
|
||||
const SimplexNoise = function (r = Math) {
|
||||
if (!r.hasOwnProperty('random') || !r.random.call) {
|
||||
throw new Error('optional first argument must contain a random function')
|
||||
}
|
||||
|
||||
this.p = new Uint8Array(256)
|
||||
for (let i = 0; i < 256; i++) this.p[i] = i
|
||||
// shuffle the array
|
||||
for (let i = 255; i > 0; i--) {
|
||||
const n = floor((i + 1) * r.random())
|
||||
const q = this.p[i]
|
||||
this.p[i] = this.p[n]
|
||||
this.p[n] = q
|
||||
}
|
||||
|
||||
// To remove the need for index wrapping, double the permutation table length
|
||||
this.perm = new Uint8Array(512)
|
||||
this.permMod12 = new Uint8Array(512)
|
||||
for (let i = 0; i < 512; i++) {
|
||||
this.perm[i] = this.p[i & 255]
|
||||
this.permMod12[i] = this.perm[i] % 12
|
||||
}
|
||||
}
|
||||
|
||||
SimplexNoise.prototype.noise = function (xin, yin) {
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
const s = (xin + yin) * F2 // Hairy factor for 2D
|
||||
const i = floor(xin + s)
|
||||
const j = floor(yin + s)
|
||||
const t = (i + j) * G2
|
||||
|
||||
// The x,y distances from the unskewed cell origin
|
||||
const x0 = xin - (i - t)
|
||||
const y0 = yin - (j - t)
|
||||
|
||||
// For the 2D case, the simplex shape is an equilateral triangle.
|
||||
|
||||
// Determine which simplex we are in.
|
||||
// Offsets for second (middle) corner of simplex in (i,j) coords
|
||||
// lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
||||
const i1 = ~~(x0 > y0) // 1 if true, false otherwise
|
||||
// upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
||||
const j1 = ~~(!i1) // 1 if i1 is 0, 0 otherwise
|
||||
|
||||
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
||||
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
||||
// c = (3-sqrt(3))/6
|
||||
const x1 = x0 - i1 + G2 // Offsets for middle corner in (x,y) unskewed coords
|
||||
const y1 = y0 - j1 + G2
|
||||
const x2 = x0 - 1.0 + 2.0 * G2 // Offsets for last corner in (x,y) unskewed coords
|
||||
const y2 = y0 - 1.0 + 2.0 * G2
|
||||
|
||||
// Work out the hashed gradient indices of the three simplex corners
|
||||
const ii = i & 255
|
||||
const jj = j & 255
|
||||
const gi0 = this.permMod12[ii + this.perm[jj]]
|
||||
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1]]
|
||||
const gi2 = this.permMod12[ii + 1 + this.perm[jj + 1]]
|
||||
|
||||
// Calculate the noise contribution from the three corners
|
||||
// let n0, n1, n2
|
||||
const t0 = 0.5 - x0 * x0 - y0 * y0
|
||||
const t1 = 0.5 - x1 * x1 - y1 * y1
|
||||
const t2 = 0.5 - x2 * x2 - y2 * y2
|
||||
|
||||
const n0 = t0 < 0 ? 0.0 : (t0 ** 4) * dot(gi0, x0, y0) // (x,y) of grad3 used for 2D gradient
|
||||
const n1 = t1 < 0 ? 0.0 : (t1 ** 4) * dot(gi1, x1, y1)
|
||||
const n2 = t2 < 0 ? 0.0 : (t2 ** 4) * dot(gi2, x2, y2)
|
||||
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to return values in the interval [-1,1].
|
||||
return 70.14805770653952 * (n0 + n1 + n2)
|
||||
}
|
||||
|
||||
// export default SimplexNoise
|
|
@ -1,178 +0,0 @@
|
|||
// Original from Stefan Gustavson's Java implementation, ported by Sean McCullough
|
||||
// see http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
||||
//
|
||||
// Adapted and Ported to ES6 by Norman Köhring <n‹Æt›koehr.in>
|
||||
|
||||
/**
|
||||
* You can pass in a random number generator object if you like.
|
||||
* It is assumed to have a random() method.
|
||||
*/
|
||||
var SimplexNoise = function(r) {
|
||||
if (r == undefined) r = Math;
|
||||
this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],
|
||||
[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],
|
||||
[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
|
||||
this.p = [];
|
||||
for (var i=0; i<256; i++) {
|
||||
this.p[i] = Math.floor(r.random()*256);
|
||||
}
|
||||
// To remove the need for index wrapping, double the permutation table length
|
||||
this.perm = [];
|
||||
for(var i=0; i<512; i++) {
|
||||
this.perm[i]=this.p[i & 255];
|
||||
}
|
||||
|
||||
// A lookup table to traverse the simplex around a given point in 4D.
|
||||
// Details can be found where this table is used, in the 4D noise method.
|
||||
this.simplex = [
|
||||
[0,1,2,3],[0,1,3,2],[0,0,0,0],[0,2,3,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,2,3,0],
|
||||
[0,2,1,3],[0,0,0,0],[0,3,1,2],[0,3,2,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,3,2,0],
|
||||
[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
|
||||
[1,2,0,3],[0,0,0,0],[1,3,0,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,3,0,1],[2,3,1,0],
|
||||
[1,0,2,3],[1,0,3,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,0,3,1],[0,0,0,0],[2,1,3,0],
|
||||
[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],
|
||||
[2,0,1,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,0,1,2],[3,0,2,1],[0,0,0,0],[3,1,2,0],
|
||||
[2,1,0,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,1,0,2],[0,0,0,0],[3,2,0,1],[3,2,1,0]];
|
||||
};
|
||||
|
||||
SimplexNoise.prototype.dot = function(g, x, y) {
|
||||
return g[0]*x + g[1]*y;
|
||||
};
|
||||
|
||||
SimplexNoise.prototype.noise = function(xin, yin) {
|
||||
var n0, n1, n2; // Noise contributions from the three corners
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
var F2 = 0.5*(Math.sqrt(3.0)-1.0);
|
||||
var s = (xin+yin)*F2; // Hairy factor for 2D
|
||||
var i = Math.floor(xin+s);
|
||||
var j = Math.floor(yin+s);
|
||||
var G2 = (3.0-Math.sqrt(3.0))/6.0;
|
||||
var t = (i+j)*G2;
|
||||
var X0 = i-t; // Unskew the cell origin back to (x,y) space
|
||||
var Y0 = j-t;
|
||||
var x0 = xin-X0; // The x,y distances from the cell origin
|
||||
var y0 = yin-Y0;
|
||||
// For the 2D case, the simplex shape is an equilateral triangle.
|
||||
// Determine which simplex we are in.
|
||||
var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
|
||||
if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1)
|
||||
else {i1=0; j1=1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1)
|
||||
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
|
||||
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
|
||||
// c = (3-sqrt(3))/6
|
||||
var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
|
||||
var y1 = y0 - j1 + G2;
|
||||
var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
|
||||
var y2 = y0 - 1.0 + 2.0 * G2;
|
||||
// Work out the hashed gradient indices of the three simplex corners
|
||||
var ii = i & 255;
|
||||
var jj = j & 255;
|
||||
var gi0 = this.perm[ii+this.perm[jj]] % 12;
|
||||
var gi1 = this.perm[ii+i1+this.perm[jj+j1]] % 12;
|
||||
var gi2 = this.perm[ii+1+this.perm[jj+1]] % 12;
|
||||
// Calculate the contribution from the three corners
|
||||
var t0 = 0.5 - x0*x0-y0*y0;
|
||||
if(t0<0) n0 = 0.0;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient
|
||||
}
|
||||
var t1 = 0.5 - x1*x1-y1*y1;
|
||||
if(t1<0) n1 = 0.0;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1);
|
||||
}
|
||||
var t2 = 0.5 - x2*x2-y2*y2;
|
||||
if(t2<0) n2 = 0.0;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2);
|
||||
}
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to return values in the interval [-1,1].
|
||||
return 70.0 * (n0 + n1 + n2);
|
||||
};
|
||||
|
||||
// 3D simplex noise
|
||||
SimplexNoise.prototype.noise3d = function(xin, yin, zin) {
|
||||
var n0, n1, n2, n3; // Noise contributions from the four corners
|
||||
// Skew the input space to determine which simplex cell we're in
|
||||
var F3 = 1.0/3.0;
|
||||
var s = (xin+yin+zin)*F3; // Very nice and simple skew factor for 3D
|
||||
var i = Math.floor(xin+s);
|
||||
var j = Math.floor(yin+s);
|
||||
var k = Math.floor(zin+s);
|
||||
var G3 = 1.0/6.0; // Very nice and simple unskew factor, too
|
||||
var t = (i+j+k)*G3;
|
||||
var X0 = i-t; // Unskew the cell origin back to (x,y,z) space
|
||||
var Y0 = j-t;
|
||||
var Z0 = k-t;
|
||||
var x0 = xin-X0; // The x,y,z distances from the cell origin
|
||||
var y0 = yin-Y0;
|
||||
var z0 = zin-Z0;
|
||||
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
|
||||
// Determine which simplex we are in.
|
||||
var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
|
||||
var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
|
||||
if(x0>=y0) {
|
||||
if(y0>=z0)
|
||||
{ i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order
|
||||
else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order
|
||||
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order
|
||||
}
|
||||
else { // x0<y0
|
||||
if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order
|
||||
else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order
|
||||
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order
|
||||
}
|
||||
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
|
||||
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
|
||||
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
|
||||
// c = 1/6.
|
||||
var x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
|
||||
var y1 = y0 - j1 + G3;
|
||||
var z1 = z0 - k1 + G3;
|
||||
var x2 = x0 - i2 + 2.0*G3; // Offsets for third corner in (x,y,z) coords
|
||||
var y2 = y0 - j2 + 2.0*G3;
|
||||
var z2 = z0 - k2 + 2.0*G3;
|
||||
var x3 = x0 - 1.0 + 3.0*G3; // Offsets for last corner in (x,y,z) coords
|
||||
var y3 = y0 - 1.0 + 3.0*G3;
|
||||
var z3 = z0 - 1.0 + 3.0*G3;
|
||||
// Work out the hashed gradient indices of the four simplex corners
|
||||
var ii = i & 255;
|
||||
var jj = j & 255;
|
||||
var kk = k & 255;
|
||||
var gi0 = this.perm[ii+this.perm[jj+this.perm[kk]]] % 12;
|
||||
var gi1 = this.perm[ii+i1+this.perm[jj+j1+this.perm[kk+k1]]] % 12;
|
||||
var gi2 = this.perm[ii+i2+this.perm[jj+j2+this.perm[kk+k2]]] % 12;
|
||||
var gi3 = this.perm[ii+1+this.perm[jj+1+this.perm[kk+1]]] % 12;
|
||||
// Calculate the contribution from the four corners
|
||||
var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
|
||||
if(t0<0) n0 = 0.0;
|
||||
else {
|
||||
t0 *= t0;
|
||||
n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0, z0);
|
||||
}
|
||||
var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
|
||||
if(t1<0) n1 = 0.0;
|
||||
else {
|
||||
t1 *= t1;
|
||||
n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1, z1);
|
||||
}
|
||||
var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
|
||||
if(t2<0) n2 = 0.0;
|
||||
else {
|
||||
t2 *= t2;
|
||||
n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2, z2);
|
||||
}
|
||||
var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
|
||||
if(t3<0) n3 = 0.0;
|
||||
else {
|
||||
t3 *= t3;
|
||||
n3 = t3 * t3 * this.dot(this.grad3[gi3], x3, y3, z3);
|
||||
}
|
||||
// Add contributions from each corner to get the final noise value.
|
||||
// The result is scaled to stay just inside [-1,1]
|
||||
return 32.0*(n0 + n1 + n2 + n3);
|
||||
};
|
33
tsconfig.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": [
|
||||
"unplugin-vue-macros/macros-global"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
9
tsconfig.node.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
14
vite.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import VueMacros from 'unplugin-vue-macros/vite'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
VueMacros({
|
||||
plugins: {
|
||||
vue: Vue(),
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
|
@ -1,78 +0,0 @@
|
|||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'build.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader'
|
||||
],
|
||||
}, {
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
}
|
||||
// other vue-loader options go here
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true,
|
||||
overlay: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
devtool: '#eval-source-map'
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.devtool = '#source-map'
|
||||
// http://vue-loader.vuejs.org/en/workflow/production.html
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
])
|
||||
}
|