simplify light mask, gradient shadows, torches (without light)
This commit is contained in:
parent
8f281fbdf9
commit
72c5cd1d19
11 changed files with 100 additions and 97 deletions
BIN
public/Items/torch.png
Normal file
BIN
public/Items/torch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
46
src/App.vue
46
src/App.vue
|
@ -5,7 +5,7 @@ import Help from './screens/help.vue'
|
|||
import Inventory from './screens/inventory.vue'
|
||||
import Background from './Background.vue'
|
||||
|
||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'
|
||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, softTerrain, hardTerrain } from './level/def'
|
||||
import { getItem, getItemClass } from './level/items'
|
||||
import createLevel from './level'
|
||||
|
||||
|
@ -25,11 +25,12 @@ let updateLightMap = (() => {}) as ReturnType<typeof useLightMask>
|
|||
pocket(getItem('tool_shovel_wood'))
|
||||
pocket(getItem('tool_sword_wood'))
|
||||
pocket(getItem('tool_pickaxe_wood'))
|
||||
pocket(getItem('fixture_torch'))
|
||||
|
||||
let animationFrame = 0
|
||||
let lastTick = 0
|
||||
|
||||
const debug = ref(false)
|
||||
const debug = ref(true)
|
||||
const x = ref(0)
|
||||
const y = ref(0)
|
||||
const floorX = computed(() => Math.floor(x.value))
|
||||
|
@ -46,6 +47,8 @@ const lightBarrier = computed<number[]>(() => {
|
|||
return level.sunLight(floorX.value)
|
||||
})
|
||||
|
||||
const lightSources = computed(() => [])
|
||||
|
||||
const mapUpdateCount = ref(0)
|
||||
const mapGrid = computed<Block[][]>(() => {
|
||||
const _update = mapUpdateCount.value // reactivity trigger
|
||||
|
@ -127,20 +130,29 @@ function build(blockX: number, blockY: number, block: InventoryItem) {
|
|||
}
|
||||
|
||||
function interactWith(blockX: number, blockY: number, block: Block) {
|
||||
if (debug) console.debug('interact with', blockX, blockY, block.type)
|
||||
if (debug) console.debug('interact with', blockX, blockY, block.type, inventorySelection.value.type, 'in hand')
|
||||
// § 4 ArbZG
|
||||
if (paused.value) return
|
||||
|
||||
const blockInHand = inventorySelection.value.type === 'block'
|
||||
const toolInHand = inventorySelection.value.type === 'tool'
|
||||
const emptyBlock = block.type === 'air' || block.type === 'cave'
|
||||
const blockInHand = inventorySelection.value
|
||||
const shovelInHand = blockInHand.id.indexOf('shovel') >= 0
|
||||
const pickaxeInHand = blockInHand.id.indexOf('pickaxe') >= 0
|
||||
|
||||
const canBuild = !!blockInHand.builds
|
||||
const hasTool = blockInHand.type === 'tool'
|
||||
const hasSpace = block.type === 'air' || block.type === 'cave'
|
||||
const canUseShovel = softTerrain.indexOf(block.type) >= 0
|
||||
const canUsePickaxe = hardTerrain.indexOf(block.type) >= 0
|
||||
|
||||
console.log({ canBuild, hasSpace, hasTool, blockInHand })
|
||||
|
||||
// put the selected block
|
||||
if (blockInHand && emptyBlock) {
|
||||
build(blockX, blockY, inventorySelection.value)
|
||||
if (canBuild && hasSpace) {
|
||||
build(blockX, blockY, blockInHand)
|
||||
// dig a block with shovel or pick axe
|
||||
} else if (toolInHand && !emptyBlock) {
|
||||
dig(blockX, blockY, block)
|
||||
} else if (hasTool && !hasSpace) {
|
||||
if (shovelInHand && canUseShovel) dig(blockX, blockY, block)
|
||||
else if (pickaxeInHand && canUsePickaxe) dig(blockX, blockY, block)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +226,11 @@ onMounted(() => {
|
|||
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
|
||||
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
|
||||
const ctx = canvas.getContext('2d')!
|
||||
updateLightMap = useLightMask(ctx, floorX, floorY, tx, ty, time, lightBarrier)
|
||||
updateLightMap = useLightMask(
|
||||
ctx, floorX, floorY,
|
||||
tx, ty, time,
|
||||
lightBarrier, lightSources,
|
||||
)
|
||||
} else {
|
||||
console.warn('lightmap deactivated')
|
||||
}
|
||||
|
@ -265,7 +281,13 @@ onMounted(() => {
|
|||
<div id="beam" v-if="arriving"></div>
|
||||
<div id="level-indicator">
|
||||
x:{{ floorX }}, y:{{ floorY }}
|
||||
<template v-if="paused">(PAUSED)</template>
|
||||
<template v-if="paused">
|
||||
<template v-if="debug">
|
||||
({{ clock }})<br/>
|
||||
time: <input type="number" max="0" min="1000" v-model="time" />
|
||||
</template>
|
||||
<template v-else>(PAUSED)</template>
|
||||
</template>
|
||||
<template v-else>({{ clock }})</template>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,18 +21,15 @@ onMounted(() => {
|
|||
|
||||
const drawBackground = useBackground(
|
||||
canvasEl,
|
||||
~~(STAGE_WIDTH * BLOCK_SIZE / 2.0),
|
||||
~~(STAGE_HEIGHT * BLOCK_SIZE / 2.0),
|
||||
~~(STAGE_WIDTH * BLOCK_SIZE / 1.0),
|
||||
~~(STAGE_HEIGHT * BLOCK_SIZE / 1.0),
|
||||
)
|
||||
|
||||
watch(props, () => {
|
||||
console.log('drawing background', sunY.value)
|
||||
drawBackground(props.x, sunY.value)
|
||||
}, { immediate: true })
|
||||
watch(props, () => drawBackground(props.x, sunY.value), { immediate: true })
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="canvas" id="background"></canvas>
|
||||
<canvas ref="canvas" id="background" />
|
||||
</template>
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
.block.brickWall {
|
||||
background-image: url(/Tiles/brick_grey.png);
|
||||
}
|
||||
.block.torch { background-image: url("/Items/torch.png"); }
|
||||
|
||||
#field {
|
||||
user-select: none;
|
||||
|
@ -112,17 +113,17 @@
|
|||
|
||||
.morning0 .block,
|
||||
.morning0 #player {
|
||||
filter: saturate(50%);
|
||||
filter: saturate(50%) brightness(0.6);
|
||||
}
|
||||
|
||||
.morning1 .block,
|
||||
.morning1 #player {
|
||||
filter: saturate(100%);
|
||||
filter: saturate(100%) brightness(0.8);
|
||||
}
|
||||
|
||||
.morning2 .block,
|
||||
.morning2 #player {
|
||||
filter: saturate(120%);
|
||||
filter: saturate(120%) brightness(0.9);
|
||||
}
|
||||
|
||||
.evening0 .block,
|
||||
|
@ -132,17 +133,17 @@
|
|||
|
||||
.evening1 .block,
|
||||
.evening1 #player {
|
||||
filter: saturate(70%);
|
||||
filter: saturate(70%) brightness(0.8);
|
||||
}
|
||||
|
||||
.evening2 .block,
|
||||
.evening2 #player {
|
||||
filter: saturate(50%);
|
||||
filter: saturate(50%) brightness(0.6);
|
||||
}
|
||||
|
||||
.night .block,
|
||||
.night #player {
|
||||
filter: saturate(30%);
|
||||
filter: saturate(30%) brightness(0.4);
|
||||
}
|
||||
|
||||
#blocks {
|
||||
|
|
|
@ -14,3 +14,5 @@
|
|||
.item.block-dirt { background-image: url("/Tiles/dirt.png"); }
|
||||
.item.block-stone { background-image: url("/Tiles/stone.png"); }
|
||||
.item.block-gravel { background-image: url("/Tiles/gravel_stone.png"); }
|
||||
|
||||
.item.fixture-torch { background-image: url("/Items/torch.png"); }
|
||||
|
|
|
@ -26,9 +26,13 @@ export const blockTypes: Record<BlockType, Block> = {
|
|||
stone: { type: 'stone', hp: 10, walkable: false, drops: 'block_stone' },
|
||||
bedrock: { type: 'bedrock', hp: 25, walkable: false, drops: 'block_stone' },
|
||||
// Built Blocks
|
||||
brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel' },
|
||||
brickWall: { type: 'brickWall', hp: 25, walkable: false, drops: 'block_gravel', fixture: true },
|
||||
torch: { type: 'torch', hp: 1, walkable: true, transparent: true, drops: 'fixture_torch', illuminated: true, fixture: true },
|
||||
}
|
||||
|
||||
export const softTerrain: BlockType[] = ['grass', 'soil', 'soilGravel', 'torch']
|
||||
export const hardTerrain: BlockType[] = ['stone', 'stoneGravel', 'bedrock', 'brickWall']
|
||||
|
||||
export const level = {
|
||||
treeTop: 9,
|
||||
ground: 14,
|
||||
|
|
|
@ -3,10 +3,10 @@ import { createNoise2D, type NoiseFunction2D } from 'simplex-noise'
|
|||
import createBlockGenerator from './blockGen'
|
||||
import createBlockExtender from './blockExt'
|
||||
|
||||
import { blockTypes, blockTypes as T } from './def'
|
||||
import { level as L, blockTypes, blockTypes as T } from './def'
|
||||
import type { Block, Change } from '../types.d'
|
||||
|
||||
const MAX_LIGHT = 100 // maximum level where light shines
|
||||
const MAX_LIGHT = L.underground // maximum level where light shines
|
||||
|
||||
export default function createLevel(width: number, height: number, seed = 'extremely random seed') {
|
||||
const prng = alea(seed)
|
||||
|
|
|
@ -26,6 +26,8 @@ export const items = {
|
|||
ore_ruby: { id: 'ore_ruby', name: 'ruby', type: 'ore', icon: 'ore_ruby' } as Item,
|
||||
ore_diamond: { id: 'ore_diamond', name: 'diamond', type: 'ore', icon: 'ore_diamond' } as Item,
|
||||
ore_emerald: { id: 'ore_emerald', name: 'emerald', type: 'ore', icon: 'ore_emerald' } as Item,
|
||||
|
||||
fixture_torch: { id: 'fixture_torch', name: 'Torch', type: 'fixture', icon: 'torch', builds: 'torch' } as Item,
|
||||
} as const
|
||||
|
||||
export type ItemId = keyof typeof items
|
||||
|
|
10
src/types.d.ts
vendored
10
src/types.d.ts
vendored
|
@ -1,6 +1,6 @@
|
|||
import type { ItemId } from './level/items'
|
||||
export type ItemQuality = 'wood' | 'iron' | 'diamond'
|
||||
export type ItemType = 'tool' | 'block' | 'ore'
|
||||
export type ItemType = 'tool' | 'block' | 'ore' | 'fixture'
|
||||
|
||||
export interface Item {
|
||||
id: string
|
||||
|
@ -28,6 +28,7 @@ export type BlockType =
|
|||
| 'stone' | 'stoneGravel'
|
||||
| 'bedrock' | 'cave'
|
||||
| 'brickWall'
|
||||
| 'torch'
|
||||
|
||||
export type Block = {
|
||||
type: BlockType, // what is it?
|
||||
|
@ -36,6 +37,7 @@ export type Block = {
|
|||
climbable?: boolean, // can I climb it?
|
||||
transparent?: boolean, // can I see through it?
|
||||
illuminated?: boolean, // is it glowing?
|
||||
fixture?: boolean, // is it built by the player?
|
||||
drops?: ItemId, // what do I get, when loot it?
|
||||
}
|
||||
|
||||
|
@ -78,3 +80,9 @@ export interface Player extends Moveable {
|
|||
|
||||
export type Direction = 'at' | 'left' | 'right' | 'up' | 'down'
|
||||
|
||||
|
||||
export interface LightSource {
|
||||
x: number
|
||||
y: number
|
||||
strength: number
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Ref, ComputedRef } from 'vue'
|
||||
import type { LightSource } from '../types.d'
|
||||
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from '../level/def'
|
||||
|
||||
type RefOrComputed<T> = Ref<T> | ComputedRef<T>
|
||||
|
@ -11,37 +12,17 @@ export default function useLightMask(
|
|||
ty: RefOrComputed<number>,
|
||||
time: RefOrComputed<number>,
|
||||
lightBarrier: RefOrComputed<number[]>,
|
||||
lightSources: RefOrComputed<LightSource[]>,
|
||||
) {
|
||||
const W = ((STAGE_WIDTH + 2) * BLOCK_SIZE)
|
||||
const H = ((STAGE_HEIGHT + 2) * BLOCK_SIZE)
|
||||
const B = BLOCK_SIZE - 4 // no idea why there is a difference, but it is 4px
|
||||
const BHalf = B / 2
|
||||
|
||||
const playerX = (W - B) / 2 + B / 4
|
||||
const playerY = H / 2 - B / 2
|
||||
const playerY = H / 2 - BHalf
|
||||
const playerLightSize = B * 1.8
|
||||
|
||||
function getAmbientLightColor() {
|
||||
const t = time.value
|
||||
|
||||
// Night time (pale bluish dark: hslpicker.com/#2b293d )
|
||||
if (t > 900 || t < 100) {
|
||||
return `hsl(245, 20%, 20%)`
|
||||
}
|
||||
// Morning hours (gradually more reddish hue)
|
||||
if (t < 250) {
|
||||
const s = Math.round((t - 100) / 1.5) // 0-100%
|
||||
const l = Math.round((t - 100) / 1.875) + 20 // 20-100%
|
||||
return `hsl(0, ${s}%, ${l}%)`
|
||||
}
|
||||
// Evening hours (from neutral white to bluish hue with low saturation)
|
||||
if (t > 700) {
|
||||
const s = 100 - Math.round((t - 700) / 2.5) // 100-20%
|
||||
return `hsl(245, ${s}%, ${s}%)`
|
||||
}
|
||||
// day (neutral white)
|
||||
return `hsl(0, 0%, 100%)`
|
||||
}
|
||||
|
||||
function drawPlayerLight(sizeMul:number) {
|
||||
const t = time.value
|
||||
|
||||
|
@ -50,67 +31,53 @@ export default function useLightMask(
|
|||
playerX - tx.value, playerY - ty.value, playerLightSize * sizeMul
|
||||
)
|
||||
|
||||
// Add color stops for a light around the player
|
||||
// Night time, lets tone down the light a bit
|
||||
if (t > 900 || t < 100) {
|
||||
playerLight.addColorStop(0.7, "#AA7A");
|
||||
playerLight.addColorStop(1, "#AA70");
|
||||
|
||||
// Morning
|
||||
} else if (t < 150) {
|
||||
playerLight.addColorStop(0.7, "#CCAA");
|
||||
playerLight.addColorStop(1, "#CCA0");
|
||||
|
||||
// Day (neutral white)
|
||||
} else {
|
||||
playerLight.addColorStop(0.7, "#FFFA");
|
||||
playerLight.addColorStop(1, "#FFF0");
|
||||
}
|
||||
playerLight.addColorStop(0.7, "#FFFA");
|
||||
playerLight.addColorStop(1, "#FFF0");
|
||||
|
||||
// Set the fill style and draw a rectangle
|
||||
ctx.fillStyle = playerLight;
|
||||
ctx.fillRect(0, 0, W, H)
|
||||
}
|
||||
|
||||
function drawLights() {
|
||||
// used for everything above ground
|
||||
const ambientLight = getAmbientLightColor()
|
||||
function drawLightSources() {
|
||||
for (const src of lightSources.value) {
|
||||
const x = src.x * B
|
||||
const y = src.y * B
|
||||
|
||||
const light = ctx.createRadialGradient(x, y, 0, x, y, src.strength)
|
||||
light.addColorStop(0.0, "#CC8F")
|
||||
light.addColorStop(0.4, "#CC8A")
|
||||
light.addColorStop(1, "#CC80")
|
||||
}
|
||||
}
|
||||
|
||||
function drawShadows() {
|
||||
const barrier = lightBarrier.value
|
||||
|
||||
ctx.fillStyle = ambientLight
|
||||
for (let col = 0; col < W / B; col++) {
|
||||
for (let col = 0; col < barrier.length; col++) {
|
||||
const level = (barrier[col] - y.value) * B
|
||||
const sw = B
|
||||
const sh = level
|
||||
const sx = col * sw
|
||||
const sy = 0
|
||||
const x = B*col
|
||||
|
||||
ctx.fillRect(sx, sy, sw, sh)
|
||||
// gradient for the shadow that is cast down from the surface
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, H)
|
||||
gradient.addColorStop(0, '#FFF')
|
||||
gradient.addColorStop(Math.min(level / H, 1), '#FFF')
|
||||
gradient.addColorStop(Math.min((level + B) / H, 1), '#000')
|
||||
ctx.fillStyle = gradient
|
||||
ctx.fillRect(x, 0, B, H)
|
||||
}
|
||||
|
||||
// make light columns wider to illuminate surrounding blocks
|
||||
const extra = Math.floor(B / 2)
|
||||
ctx.fillStyle = ambientLight.slice(0, -1) + ', 40%)'
|
||||
for (let col = 0; col < W / B; col++) {
|
||||
const level = (barrier[col] - y.value) * B
|
||||
const sw = B
|
||||
const sh = level
|
||||
const sx = col * sw
|
||||
const sy = 0
|
||||
|
||||
ctx.fillRect(sx - extra, sy - extra, sw + extra * 2, sh + extra * 2)
|
||||
}
|
||||
|
||||
// TODO: draw light for candles and torches
|
||||
}
|
||||
|
||||
return function update() {
|
||||
// first, throw the world in complete darkness
|
||||
ctx.fillStyle = '#000000'
|
||||
ctx.fillStyle = '#FFFFFF'
|
||||
ctx.fillRect(0, 0, W, H)
|
||||
|
||||
// second, find and bring light into the world
|
||||
drawLights()
|
||||
// second, hide what is beneath
|
||||
drawShadows()
|
||||
|
||||
// third, fight the darkness
|
||||
drawLightSources()
|
||||
|
||||
// finally, draw the players light
|
||||
// with a size multiplicator which might be later used to
|
||||
|
|
|
@ -46,7 +46,7 @@ export function calcSunAngle(tick: number): number {
|
|||
|
||||
export default function useTime() {
|
||||
// the day is split in 1000 parts, so we start in the morning
|
||||
const time = ref(250)
|
||||
const time = ref(240)
|
||||
|
||||
function updateTime() {
|
||||
time.value = (time.value + 0.1) % 1000
|
||||
|
|
Loading…
Add table
Reference in a new issue