2-step level generator
This commit is contained in:
parent
4e3fda1732
commit
5f66a76cb6
12 changed files with 175 additions and 135 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "building-game",
|
||||
"description": "A blocky, side-scrolling, building and exploration game",
|
||||
"version": "1.0.0",
|
||||
"name": "digging-game",
|
||||
"description": "A blocky, side-scrolling, digging and exploration game",
|
||||
"version": "0.0.1",
|
||||
"author": "koehr <n@koehr.in>",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Level from './lib/level-generator'
|
||||
import Level from './level'
|
||||
|
||||
const WIDTH = 32
|
||||
const HEIGHT = 32
|
||||
|
|
28
src/level/def.js
Normal file
28
src/level/def.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
export const type = {
|
||||
air: {type: 'air', hp: 0},
|
||||
grass: {type: 'grass', hp: 1},
|
||||
leaves: {type: 'leaves', hp: 1},
|
||||
wood: {type: 'wood', hp: 5},
|
||||
soil: {type: 'soil', hp: 2},
|
||||
gravel: {type: 'gravel', hp: 5},
|
||||
stone: {type: 'stone', hp: 10},
|
||||
bedrock: {type: 'bedrock', hp: 25},
|
||||
cave: {type: 'cave', hp: 0}
|
||||
}
|
||||
|
||||
export const level = {
|
||||
peak: 24,
|
||||
ground: 28,
|
||||
rock: 32,
|
||||
underground: 48,
|
||||
cave_max: 250
|
||||
}
|
||||
|
||||
export const probability = {
|
||||
tree: 0.1,
|
||||
soil_hole: 0.3,
|
||||
soil_gravel: 0.2,
|
||||
rock_gravel: 0.1,
|
||||
cave: 0.5,
|
||||
fray: 0.4
|
||||
}
|
58
src/level/first-iteration.js
Normal file
58
src/level/first-iteration.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import SeedRng from 'seedrandom'
|
||||
import FastSimplexNoise from 'fast-simplex-noise'
|
||||
import {type as T, level as L, probability as P} from './def'
|
||||
|
||||
export default class BlockGen {
|
||||
constructor (seed = 'so freakin random') {
|
||||
const simplex = new FastSimplexNoise({ random: SeedRng(seed) })
|
||||
this.rand = (x, y) => 0.5 + 0.5 * simplex.raw2D(x, y)
|
||||
}
|
||||
|
||||
level (level, row, previousRow) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
row[i] = this.block(level, 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) return this.treeTop(r)
|
||||
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.wood
|
||||
return T.air
|
||||
}
|
||||
|
||||
// returns mostly soil and grass, sometimes gravel and sometimes air
|
||||
ground (r) {
|
||||
if (r < P.soil_gravel) return T.gravel
|
||||
return T.soil
|
||||
}
|
||||
|
||||
// returns mostly stones, sometimes gravel
|
||||
rock (r) {
|
||||
return r < P.rock_gravel ? T.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
|
||||
}
|
||||
}
|
39
src/level/index.js
Normal file
39
src/level/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import SeedRng from 'seedrandom'
|
||||
import FastSimplexNoise from 'fast-simplex-noise'
|
||||
|
||||
import {type as T, level as L} from './def'
|
||||
import BlockGen from './first-iteration'
|
||||
import BlockExt from './second-iteration'
|
||||
|
||||
export default class Level {
|
||||
constructor (width, height, seed = 'super random seed') {
|
||||
const random = SeedRng(seed)
|
||||
const noiseGen = new FastSimplexNoise({ random })
|
||||
this._x = 0
|
||||
this._y = 0
|
||||
this._w = width
|
||||
this._h = height
|
||||
this._grid = new Array(this._h)
|
||||
this.blockGen = new BlockGen(noiseGen)
|
||||
this.blockExt = new BlockExt(noiseGen)
|
||||
}
|
||||
|
||||
grid (x, y) {
|
||||
this._x = x
|
||||
this._y = y
|
||||
|
||||
this.generate()
|
||||
return this._grid
|
||||
}
|
||||
|
||||
generate () {
|
||||
for (let i = 0; i < this._h; i++) {
|
||||
const level = this._y + i
|
||||
const row = Array(this._w)
|
||||
const previousRow = this._grid[i - 1] || Array()
|
||||
this.blockGen.level(level, row, previousRow)
|
||||
this.blockExt.level(level, row, previousRow)
|
||||
this._grid[i] = row
|
||||
}
|
||||
}
|
||||
}
|
46
src/level/second-iteration.js
Normal file
46
src/level/second-iteration.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
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.raw2D(x, y)
|
||||
}
|
||||
|
||||
level (level, row, previousRow) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const r = Math.abs(this.rand(level, 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) {
|
||||
if (row[i] === T.wood && previousRow[i] === T.air) {
|
||||
if (row[i - 1] === T.air) row[i - 1] = T.leaves
|
||||
if (row[i + 1] === T.air) row[i + 1] = T.leaves
|
||||
previousRow[i] = T.leaves
|
||||
} else if (previousRow[i] === T.wood) {
|
||||
row[i] = T.wood
|
||||
if (row[i - 1] === T.wood) row[i - 1] = T.air
|
||||
}
|
||||
}
|
||||
|
||||
ground (r, i, row, previousRow) {
|
||||
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 (previousRow[i] === T.wood && row[i - 1] === T.grass) {
|
||||
row[i] = T.wood
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
0
src/level/third-iteration.js
Normal file
0
src/level/third-iteration.js
Normal file
|
@ -1,57 +0,0 @@
|
|||
import SeedRng from 'seedrandom'
|
||||
import FastSimplexNoise from 'fast-simplex-noise'
|
||||
import * as T from './block-types'
|
||||
import * as P from './block-probabilities'
|
||||
import * as L from './block-levels'
|
||||
|
||||
export default class BlockGen {
|
||||
constructor (seed = 'so freakin random') {
|
||||
const simplex = new FastSimplexNoise({ random: SeedRng(seed) })
|
||||
this.rand = (x, y) => 0.5 + 0.5 * simplex.raw2D(x, y)
|
||||
}
|
||||
|
||||
block (level, column, above, before) {
|
||||
if (level < L.PEAK) return this.air()
|
||||
|
||||
const r = Math.abs(this.rand(level, column))
|
||||
if (level < L.GROUND) return this.tree(r, above)
|
||||
if (level < L.ROCK) return this.ground(r, above)
|
||||
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
|
||||
tree (r, above) {
|
||||
const peak = above === T.AIR && r < P.TREE
|
||||
if (peak || above === T.WOOD) return T.WOOD
|
||||
return T.AIR
|
||||
}
|
||||
|
||||
// returns mostly soil and grass, sometimes gravel and sometimes air
|
||||
ground (r, above) {
|
||||
if (above === T.AIR && r < P.SOIL_HOLE) return T.AIR
|
||||
if (above === T.AIR) return T.GRASS
|
||||
if (above === T.WOOD) return T.SOIL
|
||||
return r < P.SOIL_GRAVEL ? T.GRAVEL : T.SOIL
|
||||
}
|
||||
|
||||
// returns mostly stones, sometimes gravel
|
||||
rock (r) {
|
||||
return r < P.ROCK_GRAVEL ? T.GRAVEL : T.STONE
|
||||
}
|
||||
|
||||
// return mostly bedrock, sometimes caves, depending on the level
|
||||
underground (r, above, before, level) {
|
||||
if (above === T.STONE || above === T.GRAVEL) return T.BEDROCK
|
||||
const a = P.CAVE / P.CAVE_MAX**2
|
||||
const p = Math.min(P.CAVE, a * level**2)
|
||||
|
||||
if (r < p) return T.CAVE
|
||||
return T.BEDROCK
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
export const PEAK = 24
|
||||
export const GROUND = 28
|
||||
export const ROCK = 32
|
||||
export const UNDERGROUND = 48
|
|
@ -1,6 +0,0 @@
|
|||
export const TREE = 0.1
|
||||
export const SOIL_HOLE = 0.3
|
||||
export const SOIL_GRAVEL = 0.2
|
||||
export const ROCK_GRAVEL = 0.1
|
||||
export const CAVE = 0.5
|
||||
export const CAVE_MAX = 250
|
|
@ -1,9 +0,0 @@
|
|||
export const AIR = {type: 'air', hp: 0, damage: 0}
|
||||
export const GRASS = {type: 'grass', hp: 1, damage: 0}
|
||||
export const LEAVES = {type: 'leaves', hp: 1, damage: 0}
|
||||
export const WOOD = {type: 'wood', hp: 5, damage: 0}
|
||||
export const SOIL = {type: 'soil', hp: 2, damage: 0}
|
||||
export const GRAVEL = {type: 'gravel', hp: 5, damage: 0}
|
||||
export const STONE = {type: 'stone', hp: 10, damage: 0}
|
||||
export const BEDROCK = {type: 'bedrock', hp: 25, damage: 0}
|
||||
export const CAVE = {type: 'cave', hp: 0, damage: 0}
|
|
@ -1,55 +0,0 @@
|
|||
import * as T from './block-types'
|
||||
import * as L from './block-levels'
|
||||
import BlockGen from './block-generator'
|
||||
|
||||
export default class Level {
|
||||
constructor (width, height) {
|
||||
this._x = 0
|
||||
this._y = 0
|
||||
this._w = width
|
||||
this._h = height
|
||||
this._grid = new Array(height)
|
||||
this.blockGen = new BlockGen('super random seed')
|
||||
}
|
||||
|
||||
grid (x, y) {
|
||||
this._x = x
|
||||
this._y = y
|
||||
this.generate()
|
||||
return this._grid
|
||||
}
|
||||
|
||||
generate () {
|
||||
// TODO: caching
|
||||
for (let i = 0; i < this._h; i++) {
|
||||
this._grid[i] = this._row(i + this._y)
|
||||
}
|
||||
}
|
||||
|
||||
_row (level = 0) {
|
||||
const row = Array(this._w)
|
||||
const previousRow = this._grid[level - 1] || Array()
|
||||
|
||||
// first step: generate a row for the given level
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const above = previousRow[i]
|
||||
row[i] = this.blockGen.block(level, i, above, row[i - 1])
|
||||
}
|
||||
|
||||
// second step: add extras like tree leaves
|
||||
if (level < L.GROUND && level > L.PEAK) {
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const above = previousRow[i]
|
||||
const block = row[i]
|
||||
|
||||
if (block === T.WOOD && above === T.AIR) {
|
||||
if (row[i - 1] === T.AIR) row[i - 1] = T.LEAVES
|
||||
if (row[i + 1] === T.AIR) row[i + 1] = T.LEAVES
|
||||
previousRow[i] = T.LEAVES
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return row
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue