Initial commit
This commit is contained in:
commit
d2f693ccf5
14
.babelrc
Normal file
14
.babelrc
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
{
|
||||||
|
"corejs": false,
|
||||||
|
"helpers": true,
|
||||||
|
"regenerator": true,
|
||||||
|
"useESModules": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Matches multiple files with brace expansion notation
|
||||||
|
# Set default charset
|
||||||
|
[*.js]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/package-lock.json
|
||||||
|
/**/dna.txt
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
10
index.html
Normal file
10
index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<style type="text/css">*{margin:0;padding:0;}body{width:100%;height:100%;}body,html{overflow:hidden;}</style>
|
||||||
|
<title>bechunked</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "bechunked",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "testing canvas stuff",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"serve": "node ./serve.js",
|
||||||
|
"build": "webpack -p",
|
||||||
|
"watch": "webpack -w --mode=development"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"private": true,
|
||||||
|
"author": "Evert \"Diamond\" Prants <evert@lunasqu.ee>",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.1.6",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||||
|
"@babel/preset-env": "^7.1.6",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"copy-webpack-plugin": "^5.0.4",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"standard": "^12.0.1",
|
||||||
|
"webpack": "^4.26.0",
|
||||||
|
"webpack-command": "^0.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.5",
|
||||||
|
"simplex-noise": "^2.4.0"
|
||||||
|
}
|
||||||
|
}
|
11
serve.js
Executable file
11
serve.js
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const express = require('express')
|
||||||
|
const path = require('path')
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.set('env', 'development')
|
||||||
|
app.use('/', express.static(path.join(__dirname, 'dist')))
|
||||||
|
|
||||||
|
app.listen(3000, function () {
|
||||||
|
console.log('server listening on 0.0.0.0:3000')
|
||||||
|
})
|
63
src/image.js
Normal file
63
src/image.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import Resource from './resource'
|
||||||
|
|
||||||
|
let imageCache = {}
|
||||||
|
|
||||||
|
class Image {
|
||||||
|
constructor (file, img) {
|
||||||
|
this.file = file
|
||||||
|
this.img = img
|
||||||
|
}
|
||||||
|
|
||||||
|
static async load (file) {
|
||||||
|
if (imageCache[file]) return imageCache[file]
|
||||||
|
let img = await Resource.loadImage(file)
|
||||||
|
let imgCl = new Image(file, img)
|
||||||
|
imageCache[file] = imgCl
|
||||||
|
return imgCl
|
||||||
|
}
|
||||||
|
|
||||||
|
get width () {
|
||||||
|
return this.img.width
|
||||||
|
}
|
||||||
|
|
||||||
|
get height () {
|
||||||
|
return this.img.height
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (ctx, x, y, w, h) {
|
||||||
|
ctx.drawImage(this.img, x, y, w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TileMap extends Image {
|
||||||
|
constructor (file, img, xaspect, yaspect) {
|
||||||
|
super(file, img)
|
||||||
|
this.xaspect = xaspect
|
||||||
|
this.yaspect = yaspect
|
||||||
|
this.xcount = this.width / xaspect
|
||||||
|
this.ycount = this.height / yaspect
|
||||||
|
this.tiles = []
|
||||||
|
|
||||||
|
for (let y = 0; y < this.ycount; y++) {
|
||||||
|
for (let x = 0; x < this.xcount; x++) {
|
||||||
|
this.tiles.push({ x, y })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (ctx, index, x, y, w, h) {
|
||||||
|
let box = this.tiles[index]
|
||||||
|
if (!box) return
|
||||||
|
ctx.drawImage(this.img, box.x * this.xaspect, box.y * this.yaspect, this.xaspect, this.yaspect, x, y, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async load (file, x, y) {
|
||||||
|
if (imageCache[file]) return imageCache[file]
|
||||||
|
let img = await Resource.loadImage(file, true)
|
||||||
|
let imgCl = new TileMap(file, img, x, y)
|
||||||
|
imageCache[file] = imgCl
|
||||||
|
return imgCl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Image, TileMap }
|
271
src/index.js
Normal file
271
src/index.js
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
/* global requestAnimationFrame */
|
||||||
|
import Simplex from 'simplex-noise'
|
||||||
|
import Input from './input'
|
||||||
|
|
||||||
|
class BetterNoise extends Simplex {
|
||||||
|
// amplitude - Controls the amount the height changes. The higher, the taller the hills.
|
||||||
|
// persistence - Controls details, value in [0,1]. Higher increases grain, lower increases smoothness.
|
||||||
|
// octaves - Number of noise layers
|
||||||
|
// period - Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain.
|
||||||
|
// lacunarity - Controls period change across octaves. 2 is usually a good value to address all detail levels.
|
||||||
|
constructor (seed, amplitude = 15, persistence = 0.4, octaves = 5, period = 80, lacunarity = 2) {
|
||||||
|
super(seed)
|
||||||
|
this.seed = seed
|
||||||
|
|
||||||
|
this.amplitude = amplitude
|
||||||
|
this.period = period
|
||||||
|
this.lacunarity = lacunarity
|
||||||
|
this.octaves = octaves
|
||||||
|
this.persistence = persistence
|
||||||
|
}
|
||||||
|
|
||||||
|
getNoise (zx, zy) {
|
||||||
|
let x = zx / this.period
|
||||||
|
let y = zy / this.period
|
||||||
|
|
||||||
|
let amp = 1.0
|
||||||
|
let max = 1.0
|
||||||
|
let sum = this.noise2D(x, y)
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
while (++i < this.octaves) {
|
||||||
|
x *= this.lacunarity
|
||||||
|
y *= this.lacunarity
|
||||||
|
amp *= this.persistence
|
||||||
|
max += amp
|
||||||
|
sum += this.noise2D(x, y) * amp
|
||||||
|
}
|
||||||
|
|
||||||
|
return (sum / max) * this.amplitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
document.body.appendChild(canvas)
|
||||||
|
|
||||||
|
canvas.width = window.innerWidth
|
||||||
|
canvas.height = window.innerHeight
|
||||||
|
|
||||||
|
const ChunkSize = 16
|
||||||
|
const TileSize = 32
|
||||||
|
const TotalSize = ChunkSize * TileSize
|
||||||
|
|
||||||
|
class Tile {
|
||||||
|
constructor (name, color = '#000', image, size = TileSize) {
|
||||||
|
this.name = name
|
||||||
|
this.size = size
|
||||||
|
this.color = color
|
||||||
|
this.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render tile at precise render coordinates on the canvas, no relativity
|
||||||
|
draw (r, x, y, s = 1) {
|
||||||
|
let scaledSize = this.size / s
|
||||||
|
if (this.image) {
|
||||||
|
if (this.image.tiles && this.index != null) {
|
||||||
|
return this.image.draw(r, this.index, x, y, scaledSize, scaledSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.image.src) {
|
||||||
|
return r.drawImage(this.image, x, y, scaledSize, scaledSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.image.draw(r, x, y, scaledSize, scaledSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.fillStyle = this.color
|
||||||
|
r.fillRect(x, y, scaledSize, scaledSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chunk {
|
||||||
|
constructor (x, y) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.tiles = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTile (x, y) {
|
||||||
|
if (x > ChunkSize || y > ChunkSize || x < 0 || y < 0) throw new Error('Out Of Bounds!')
|
||||||
|
return this.getTilei(Chunk.ptoi(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
getTilei (i) {
|
||||||
|
if (i < 0 || i > ChunkSize * ChunkSize) return null
|
||||||
|
return this.tiles[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
setTile (x, y, j) {
|
||||||
|
if (x > ChunkSize || y > ChunkSize || x < 0 || y < 0) throw new Error('Out Of Bounds!')
|
||||||
|
this.setTilei(Chunk.ptoi(x, y), j)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTilei (i, j) {
|
||||||
|
if (i < 0 || i > ChunkSize * ChunkSize) throw new Error('Out Of Bounds!')
|
||||||
|
this.tiles[i] = j
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index -> 2D Position
|
||||||
|
static itop (i) {
|
||||||
|
return { x: Math.floor(i % ChunkSize), y: Math.floor(i / ChunkSize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2D Position -> Index
|
||||||
|
static ptoi (x, y) {
|
||||||
|
return x + y * ChunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// @r : canvas context
|
||||||
|
// @c : chunk
|
||||||
|
// @o : offset
|
||||||
|
// @s : scale
|
||||||
|
static render (r, c, o = { x: 0, y: 0 }, s = 1) {
|
||||||
|
if (!c) return null
|
||||||
|
for (let i in c.tiles) {
|
||||||
|
let tile = c.tiles[i]
|
||||||
|
if (tile == null || !(tile instanceof Tile)) continue
|
||||||
|
let pos = Chunk.itop(i)
|
||||||
|
tile.draw(r,
|
||||||
|
Math.floor(((pos.x * TileSize / s) + (c.x * TotalSize / s)) + o.x),
|
||||||
|
Math.floor(((pos.y * TileSize / s) + (c.y * TotalSize / s)) + o.y),
|
||||||
|
s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChunkWorld {
|
||||||
|
constructor () {
|
||||||
|
this.chunks = []
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateTerrain (c) {
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
for (let j = 0; j < 16; j++) {
|
||||||
|
let h = noise.getNoise(i + (c.x * ChunkSize), j + (c.y * ChunkSize))
|
||||||
|
let tile = 0
|
||||||
|
if (h > noise.amplitude * 0.5) {
|
||||||
|
tile = 3
|
||||||
|
} else if (h > noise.amplitude * 0.3) {
|
||||||
|
tile = 2
|
||||||
|
} else if (h > noise.amplitude * 0.2) {
|
||||||
|
tile = 1
|
||||||
|
}
|
||||||
|
c.setTile(i, j, tiles[tile])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPositionsInViewport (pos, scale) {
|
||||||
|
// Calculate how many chunks could theoretically fit in this viewport
|
||||||
|
let swidth = Math.ceil(canvas.width / (TotalSize / scale)) + 1
|
||||||
|
let sheight = Math.ceil(canvas.height / (TotalSize / scale)) + 1
|
||||||
|
let positions = []
|
||||||
|
|
||||||
|
// Loop through the theoretical positions
|
||||||
|
for (let px = 0; px < swidth; px++) {
|
||||||
|
for (let py = 0; py < sheight; py++) {
|
||||||
|
let cx = (px * (TotalSize / scale)) - pos.x
|
||||||
|
let cy = (py * (TotalSize / scale)) - pos.y
|
||||||
|
|
||||||
|
positions.push({
|
||||||
|
x: Math.floor(cx / (TotalSize / scale)),
|
||||||
|
y: Math.floor(cy / (TotalSize / scale))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions
|
||||||
|
}
|
||||||
|
|
||||||
|
update (pos, scale) {
|
||||||
|
let positions = ChunkWorld.getPositionsInViewport(pos, scale)
|
||||||
|
for (let p in positions) {
|
||||||
|
let cpos = positions[p]
|
||||||
|
let found = false
|
||||||
|
for (let i in this.chunks) {
|
||||||
|
let chunk = this.chunks[i]
|
||||||
|
if (chunk.x == cpos.x && chunk.y == cpos.y) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
let chunk = new Chunk(cpos.x, cpos.y)
|
||||||
|
ChunkWorld.generateTerrain(chunk)
|
||||||
|
this.chunks.push(chunk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render (r, o, s) {
|
||||||
|
for (let i in this.chunks) {
|
||||||
|
let chunk = this.chunks[i]
|
||||||
|
let scaledSize = TotalSize / s
|
||||||
|
// Convert to chunk-space coordinates
|
||||||
|
let localX = Math.floor(cursor.x / scaledSize) * -1
|
||||||
|
let localY = Math.floor(cursor.y / scaledSize) * -1
|
||||||
|
let aw = Math.ceil(canvas.width / scaledSize) + 1
|
||||||
|
let ah = Math.ceil(canvas.height / scaledSize)
|
||||||
|
|
||||||
|
if (chunk.x <= localX + aw && chunk.x + 1 >= localX &&
|
||||||
|
chunk.y <= localY + ah && chunk.y + 1 >= localY) {
|
||||||
|
Chunk.render(r, chunk, o, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let world = new ChunkWorld()
|
||||||
|
let input = new Input(canvas)
|
||||||
|
let noise = new BetterNoise(1, /*amplitude = */15, /*persistence = */0.4, /*octaves = */5, /*period = */120, /*lacunarity = */2)
|
||||||
|
let zoom = 2
|
||||||
|
|
||||||
|
let tiles = [
|
||||||
|
new Tile('water', '#028fdb'),
|
||||||
|
new Tile('sand', '#cec673'),
|
||||||
|
new Tile('grass', '#007c1f'),
|
||||||
|
new Tile('stone', '#515151')
|
||||||
|
]
|
||||||
|
|
||||||
|
let cursor = {x: 0, y: 0}
|
||||||
|
|
||||||
|
function render () {
|
||||||
|
ctx.fillStyle = '#00aaff'
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
|
world.render(ctx, cursor, zoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
function update (dt) {
|
||||||
|
if (input.isDown('w')) {
|
||||||
|
cursor.y += 6
|
||||||
|
} else if (input.isDown('s')) {
|
||||||
|
cursor.y -= 6
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.isDown('a')) {
|
||||||
|
cursor.x += 6
|
||||||
|
} else if (input.isDown('d')) {
|
||||||
|
cursor.x -= 6
|
||||||
|
}
|
||||||
|
|
||||||
|
world.update(cursor, zoom)
|
||||||
|
input.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameLoop () {
|
||||||
|
requestAnimationFrame(gameLoop)
|
||||||
|
update()
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
canvas.width = window.innerWidth
|
||||||
|
canvas.height = window.innerHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
gameLoop()
|
222
src/input.js
Normal file
222
src/input.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
|
||||||
|
const specialKeyMap = {
|
||||||
|
'backspace': 8,
|
||||||
|
'tab': 9,
|
||||||
|
'enter': 13,
|
||||||
|
'shift': 16,
|
||||||
|
'ctrl': 17,
|
||||||
|
'alt': 18,
|
||||||
|
'pausebreak': 19,
|
||||||
|
'capslock': 20,
|
||||||
|
'escape': 27,
|
||||||
|
'pgup': 33,
|
||||||
|
'pgdown': 34,
|
||||||
|
'end': 35,
|
||||||
|
'home': 36,
|
||||||
|
'left': 37,
|
||||||
|
'up': 38,
|
||||||
|
'right': 39,
|
||||||
|
'down': 40,
|
||||||
|
'insert': 45,
|
||||||
|
'delete': 46,
|
||||||
|
'left-window': 91,
|
||||||
|
'right-window': 92,
|
||||||
|
'select': 93,
|
||||||
|
'numpad0': 96,
|
||||||
|
'numpad1': 97,
|
||||||
|
'numpad2': 98,
|
||||||
|
'numpad3': 99,
|
||||||
|
'numpad4': 100,
|
||||||
|
'numpad5': 101,
|
||||||
|
'numpad6': 102,
|
||||||
|
'numpad7': 103,
|
||||||
|
'numpad8': 104,
|
||||||
|
'numpad9': 105,
|
||||||
|
'multiply': 106,
|
||||||
|
'add': 107,
|
||||||
|
'subtract': 109,
|
||||||
|
'decimal': 110,
|
||||||
|
'divide': 111,
|
||||||
|
'f1': 112,
|
||||||
|
'f2': 113,
|
||||||
|
'f3': 114,
|
||||||
|
'f4': 115,
|
||||||
|
'f5': 116,
|
||||||
|
'f6': 117,
|
||||||
|
'f7': 118,
|
||||||
|
'f8': 119,
|
||||||
|
'f9': 120,
|
||||||
|
'f10': 121,
|
||||||
|
'f11': 122,
|
||||||
|
'f12': 123,
|
||||||
|
'numlock': 144,
|
||||||
|
'scrolllock': 145,
|
||||||
|
'semi-colon': 186,
|
||||||
|
'equals': 187,
|
||||||
|
'comma': 188,
|
||||||
|
'dash': 189,
|
||||||
|
'period': 190,
|
||||||
|
'fwdslash': 191,
|
||||||
|
'grave': 192,
|
||||||
|
'open-bracket': 219,
|
||||||
|
'bkslash': 220,
|
||||||
|
'close-braket': 221,
|
||||||
|
'single-quote': 222
|
||||||
|
}
|
||||||
|
|
||||||
|
class Input {
|
||||||
|
constructor (canvas) {
|
||||||
|
this.keyList = {}
|
||||||
|
this.previousKeyList = {}
|
||||||
|
this.canvas = canvas
|
||||||
|
|
||||||
|
this.mouse = {
|
||||||
|
pos: { x: 0, y: 0 },
|
||||||
|
frame: { x: 0, y: 0 },
|
||||||
|
previous: { x: 0, y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (e) => this.keyDown(e), false)
|
||||||
|
window.addEventListener('keyup', (e) => this.keyUp(e), false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
let x
|
||||||
|
let y
|
||||||
|
|
||||||
|
if (e.changedTouches) {
|
||||||
|
let touch = e.changedTouches[0]
|
||||||
|
if (touch) {
|
||||||
|
e.pageX = touch.pageX
|
||||||
|
e.pageY = touch.pageY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.pageX || e.pageY) {
|
||||||
|
x = e.pageX
|
||||||
|
y = e.pageY
|
||||||
|
} else {
|
||||||
|
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||||
|
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= canvas.offsetLeft
|
||||||
|
y -= canvas.offsetTop
|
||||||
|
|
||||||
|
this.mouse.frame.x = x
|
||||||
|
this.mouse.frame.y = y
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.mouse['btn' + e.button] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.mouse['btn' + e.button] = false
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get mousePos () {
|
||||||
|
return this.mouse.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
get mouseMoved () {
|
||||||
|
return (this.mouse.pos.x !== this.mouse.previous.x ||
|
||||||
|
this.mouse.pos.y !== this.mouse.previous.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
get mouseOffset () {
|
||||||
|
return {
|
||||||
|
x: this.mouse.previous.x - this.mouse.pos.x,
|
||||||
|
y: this.mouse.previous.y - this.mouse.pos.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleKey (keyCode, on) {
|
||||||
|
// Find key in special key list
|
||||||
|
let key = null
|
||||||
|
for (let k in specialKeyMap) {
|
||||||
|
let val = specialKeyMap[k]
|
||||||
|
if (keyCode === val) {
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use fromCharCode
|
||||||
|
if (!key) {
|
||||||
|
key = String.fromCharCode(keyCode).toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keyList[key] = (on === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown (e) {
|
||||||
|
let keycode
|
||||||
|
|
||||||
|
if (window.event) {
|
||||||
|
keycode = window.event.keyCode
|
||||||
|
} else if (e) {
|
||||||
|
keycode = e.which
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleKey(keycode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp (e) {
|
||||||
|
let keycode
|
||||||
|
|
||||||
|
if (window.event) {
|
||||||
|
keycode = window.event.keyCode
|
||||||
|
} else if (e) {
|
||||||
|
keycode = e.which
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleKey(keycode, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
down (key) {
|
||||||
|
return this.keyList[key] != null ? this.keyList[key] : false
|
||||||
|
}
|
||||||
|
|
||||||
|
downLast (key) {
|
||||||
|
return this.previousKeyList[key] != null ? this.previousKeyList[key] : false
|
||||||
|
}
|
||||||
|
|
||||||
|
isDown (key) {
|
||||||
|
return this.down(key) && this.downLast(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
isUp (key) {
|
||||||
|
return !this.isDown(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
isPressed (key) {
|
||||||
|
return this.down(key) === true && this.downLast(key) === false
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
this.previousKeyList = {}
|
||||||
|
for (let k in this.keyList) {
|
||||||
|
if (this.keyList[k] === true) {
|
||||||
|
this.previousKeyList[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse positions in the previous frame
|
||||||
|
this.mouse.previous.x = this.mouse.pos.x
|
||||||
|
this.mouse.previous.y = this.mouse.pos.y
|
||||||
|
|
||||||
|
// Mouse positions in the current frame
|
||||||
|
// Convert to OpenGL coordinate system
|
||||||
|
this.mouse.pos.x = this.mouse.frame.x / this.canvas.width * 2 - 1
|
||||||
|
this.mouse.pos.y = this.mouse.frame.y / this.canvas.height * 2 - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input
|
101
src/resource.js
Normal file
101
src/resource.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/* global XMLHttpRequest, Image */
|
||||||
|
|
||||||
|
let imgCache = {}
|
||||||
|
|
||||||
|
function powerOfTwo (n) {
|
||||||
|
return n && (n & (n - 1)) === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function GET (url, istext) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var xmlHttp = new XMLHttpRequest()
|
||||||
|
|
||||||
|
xmlHttp.onreadystatechange = function () {
|
||||||
|
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
|
||||||
|
resolve(xmlHttp.responseText)
|
||||||
|
} else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
|
||||||
|
let err = new Error(xmlHttp.status)
|
||||||
|
err.request = xmlHttp
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlHttp.open('GET', url, true)
|
||||||
|
istext && (xmlHttp.responseType = 'text')
|
||||||
|
xmlHttp.send(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function smartGET (data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = {
|
||||||
|
url: data,
|
||||||
|
type: 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.type) data.type = 'text'
|
||||||
|
let istext = (data.type !== 'image' && data.type !== 'file')
|
||||||
|
|
||||||
|
let url = data.url
|
||||||
|
if (!url) throw new Error('URL is required!')
|
||||||
|
|
||||||
|
if (data.type === 'json') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GET(url).then((dtext) => {
|
||||||
|
try {
|
||||||
|
let jsonp = JSON.parse(dtext)
|
||||||
|
return resolve(jsonp)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
}, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(data.url, istext)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImage (url, nowarn = false) {
|
||||||
|
url = '/assets/images/' + url
|
||||||
|
|
||||||
|
// Ensure we don't load a texture multiple times
|
||||||
|
if (imgCache[url]) return new Promise((resolve, reject) => resolve(imgCache[url]))
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let img = new Image()
|
||||||
|
|
||||||
|
img.onload = function () {
|
||||||
|
// Friendly warnings
|
||||||
|
if ((!powerOfTwo(img.width) || !powerOfTwo(img.height)) && !nowarn) {
|
||||||
|
console.warn(`warn: image ${url} does not have dimensions that are powers of two. 16x16 to 128x128 recommended.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((img.width / img.height) !== 1 && !nowarn) {
|
||||||
|
console.warn(`warn: image ${url} does not have an aspect ratio of 1 to 1.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
imgCache[url] = img
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = function (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageToSampler (img) {
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
let ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = img.width
|
||||||
|
canvas.height = img.height
|
||||||
|
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||||
|
return function (x, y) {
|
||||||
|
return ctx.getImageData(x, y, 1, 1).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { GET: smartGET, imageToSampler, loadImage }
|
32
webpack.config.js
Normal file
32
webpack.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
return {
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'app.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-env']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
devtool: env.mode === 'development' ? 'inline-source-map' : '',
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({ template: 'index.html' }),
|
||||||
|
new CopyWebpackPlugin([ { from: 'static' } ])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user