forked from noxious/client
Finish improving effects component
This commit is contained in:
parent
967cb1893d
commit
284ca6f64e
@ -10,8 +10,30 @@ import { useMapStore } from '@/stores/mapStore'
|
|||||||
import { Scene } from 'phavuer'
|
import { Scene } from 'phavuer'
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
interface LightConfig {
|
||||||
|
SUNRISE_HOUR: number
|
||||||
|
SUNSET_HOUR: number
|
||||||
|
DAY_STRENGTH: number
|
||||||
|
NIGHT_STRENGTH: number
|
||||||
|
TRANSITION_HOURS: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EffectObjects {
|
||||||
|
light: Phaser.GameObjects.Graphics | null
|
||||||
|
rain: Phaser.GameObjects.Particles.ParticleEmitter | null
|
||||||
|
fog: Phaser.GameObjects.Sprite | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EffectValues {
|
||||||
|
light?: number
|
||||||
|
rain?: number
|
||||||
|
fog?: number
|
||||||
|
[key: string]: number | undefined
|
||||||
|
}
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const LIGHT_CONFIG = {
|
const LIGHT_CONFIG: LightConfig = {
|
||||||
SUNRISE_HOUR: 6,
|
SUNRISE_HOUR: 6,
|
||||||
SUNSET_HOUR: 20,
|
SUNSET_HOUR: 20,
|
||||||
DAY_STRENGTH: 100,
|
DAY_STRENGTH: 100,
|
||||||
@ -19,134 +41,143 @@ const LIGHT_CONFIG = {
|
|||||||
TRANSITION_HOURS: 3
|
TRANSITION_HOURS: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores and refs
|
// Composables
|
||||||
|
const useEffects = () => {
|
||||||
|
const effects = ref<EffectObjects>({
|
||||||
|
light: null,
|
||||||
|
rain: null,
|
||||||
|
fog: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const initializeEffects = (scene: Phaser.Scene) => {
|
||||||
|
effects.value.light = scene.add.graphics().setDepth(1000)
|
||||||
|
|
||||||
|
effects.value.rain = scene.add
|
||||||
|
.particles(0, 0, 'raindrop', {
|
||||||
|
x: { min: 0, max: window.innerWidth },
|
||||||
|
y: -50,
|
||||||
|
quantity: 5,
|
||||||
|
lifespan: 2000,
|
||||||
|
speedY: { min: 300, max: 500 },
|
||||||
|
scale: { start: 0.005, end: 0.005 },
|
||||||
|
alpha: { start: 0.5, end: 0 },
|
||||||
|
blendMode: 'ADD'
|
||||||
|
})
|
||||||
|
.setDepth(900)
|
||||||
|
effects.value.rain.stop()
|
||||||
|
|
||||||
|
effects.value.fog = scene.add
|
||||||
|
.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
||||||
|
.setScale(2)
|
||||||
|
.setAlpha(0)
|
||||||
|
.setDepth(950)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyEffects = (effectValues: EffectValues) => {
|
||||||
|
if (effects.value.light) {
|
||||||
|
const darkness = 1 - (effectValues.light ?? 0) / 100
|
||||||
|
effects.value.light.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effects.value.rain) {
|
||||||
|
if (effectValues.rain) {
|
||||||
|
effects.value.rain.start().setQuantity(effectValues.rain / 10)
|
||||||
|
} else {
|
||||||
|
effects.value.rain.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effects.value.fog && effectValues.fog !== undefined) {
|
||||||
|
effects.value.fog.setAlpha(effectValues.fog / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (effects.value.rain) {
|
||||||
|
effects.value.rain.updateConfig({ x: { min: 0, max: window.innerWidth } })
|
||||||
|
}
|
||||||
|
if (effects.value.fog) {
|
||||||
|
effects.value.fog.setPosition(window.innerWidth / 2, window.innerHeight / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { effects, initializeEffects, applyEffects, handleResize }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store instances
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const mapStore = useMapStore()
|
const mapStore = useMapStore()
|
||||||
const mapStorage = new MapStorage()
|
const mapStorage = new MapStorage()
|
||||||
|
|
||||||
|
// State
|
||||||
const sceneRef = ref<Phaser.Scene | null>(null)
|
const sceneRef = ref<Phaser.Scene | null>(null)
|
||||||
const mapObject = ref<Map | null>(null)
|
const mapObject = ref<Map | null>(null)
|
||||||
|
|
||||||
// Effect objects
|
|
||||||
const effects = {
|
|
||||||
light: ref<Phaser.GameObjects.Graphics | null>(null),
|
|
||||||
rain: ref<Phaser.GameObjects.Particles.ParticleEmitter | null>(null),
|
|
||||||
fog: ref<Phaser.GameObjects.Sprite | null>(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Weather state
|
|
||||||
const weatherState = ref<WeatherState>({
|
const weatherState = ref<WeatherState>({
|
||||||
rainPercentage: 0,
|
rainPercentage: 0,
|
||||||
fogDensity: 0
|
fogDensity: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
// Scene setup
|
// Effects management
|
||||||
const preloadScene = (scene: Phaser.Scene) => {
|
const { effects, initializeEffects, applyEffects, handleResize } = useEffects()
|
||||||
scene.load.image('raindrop', 'assets/raindrop.png')
|
|
||||||
scene.load.image('fog', 'assets/fog.png')
|
|
||||||
}
|
|
||||||
|
|
||||||
const createScene = (scene: Phaser.Scene) => {
|
// Utility functions
|
||||||
sceneRef.value = scene
|
const lerp = (x: number, y: number, a: number): number => x * (1 - a) + y * a
|
||||||
initializeEffects(scene)
|
|
||||||
setupSocketListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadMap = async () => {
|
|
||||||
if (!mapStore.mapId) return
|
|
||||||
mapObject.value = await mapStorage.get(mapStore.mapId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for mapId changes and load map when it's available
|
|
||||||
watch(
|
|
||||||
() => mapStore.mapId,
|
|
||||||
async (newMapId) => {
|
|
||||||
if (newMapId) {
|
|
||||||
await loadMap()
|
|
||||||
updateScene()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const initializeEffects = (scene: Phaser.Scene) => {
|
|
||||||
// Light
|
|
||||||
effects.light.value = scene.add.graphics().setDepth(1000)
|
|
||||||
|
|
||||||
// Rain
|
|
||||||
effects.rain.value = scene.add
|
|
||||||
.particles(0, 0, 'raindrop', {
|
|
||||||
x: { min: 0, max: window.innerWidth },
|
|
||||||
y: -50,
|
|
||||||
quantity: 5,
|
|
||||||
lifespan: 2000,
|
|
||||||
speedY: { min: 300, max: 500 },
|
|
||||||
scale: { start: 0.005, end: 0.005 },
|
|
||||||
alpha: { start: 0.5, end: 0 },
|
|
||||||
blendMode: 'ADD'
|
|
||||||
})
|
|
||||||
.setDepth(900)
|
|
||||||
effects.rain.value.stop()
|
|
||||||
|
|
||||||
// Fog
|
|
||||||
effects.fog.value = scene.add
|
|
||||||
.sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog')
|
|
||||||
.setScale(2)
|
|
||||||
.setAlpha(0)
|
|
||||||
.setDepth(950)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effect updates
|
|
||||||
const updateScene = () => {
|
|
||||||
const timeBasedLight = calculateLightStrength(gameStore.world.date)
|
|
||||||
const mapEffects = mapObject.value?.mapEffects?.reduce(
|
|
||||||
(acc, curr) => ({
|
|
||||||
...acc,
|
|
||||||
[curr.effect]: curr.strength
|
|
||||||
}),
|
|
||||||
{}
|
|
||||||
) as { [key: string]: number }
|
|
||||||
|
|
||||||
const finalEffects = { ...mapEffects, light: timeBasedLight, rain: weatherState.value.rainPercentage, fog: weatherState.value.fogDensity }
|
|
||||||
applyEffects(finalEffects)
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyEffects = (effectValues: any) => {
|
|
||||||
if (effects.light.value) {
|
|
||||||
const darkness = 1 - effectValues.light / 100
|
|
||||||
effects.light.value.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effects.rain.value) {
|
|
||||||
if (effectValues.rain.value) effects.rain.value.start().setQuantity(effectValues.rain / 10)
|
|
||||||
else effects.rain.value.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effectValues.fog.value && effects.fog.value) effects.fog.value.setAlpha(effectValues.fog / 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linear Interpolation
|
|
||||||
// moves a value between x and y where a is the percentage of progress the value has made in moving from x to y
|
|
||||||
// a = 0 return x
|
|
||||||
// a = 0.5 return (x+y)/2
|
|
||||||
// a = 1 return y
|
|
||||||
const lerp = (x: number, y: number, a: number) => x * (1 - a) + y * a
|
|
||||||
|
|
||||||
const calculateLightStrength = (time: Date): number => {
|
const calculateLightStrength = (time: Date): number => {
|
||||||
const hour = time.getHours()
|
const hour = time.getHours()
|
||||||
const minute = time.getMinutes()
|
const minute = time.getMinutes()
|
||||||
|
|
||||||
const totalMinutes = (hour - (LIGHT_CONFIG.SUNSET_HOUR - 2)) * 60 + minute
|
if (hour >= LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS && hour < LIGHT_CONFIG.SUNSET_HOUR) {
|
||||||
|
return lerp(LIGHT_CONFIG.DAY_STRENGTH, LIGHT_CONFIG.NIGHT_STRENGTH, (hour + minute / 60 - (LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS)) / LIGHT_CONFIG.TRANSITION_HOURS)
|
||||||
//Transition from daylight to night
|
} else if (hour >= LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNRISE_HOUR + LIGHT_CONFIG.TRANSITION_HOURS) {
|
||||||
if (hour >= LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS && hour < LIGHT_CONFIG.SUNSET_HOUR) return lerp(LIGHT_CONFIG.DAY_STRENGTH, LIGHT_CONFIG.NIGHT_STRENGTH, (hour + minute / 60 - (LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS)) / LIGHT_CONFIG.TRANSITION_HOURS)
|
return lerp(LIGHT_CONFIG.NIGHT_STRENGTH, LIGHT_CONFIG.DAY_STRENGTH, (hour + minute / 60 - LIGHT_CONFIG.SUNRISE_HOUR) / LIGHT_CONFIG.TRANSITION_HOURS)
|
||||||
//Transition from sunrise to morning
|
} else if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR) {
|
||||||
else if (hour >= LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNRISE_HOUR + LIGHT_CONFIG.TRANSITION_HOURS) return lerp(LIGHT_CONFIG.NIGHT_STRENGTH, LIGHT_CONFIG.DAY_STRENGTH, (hour + minute / 60 - LIGHT_CONFIG.SUNRISE_HOUR) / LIGHT_CONFIG.TRANSITION_HOURS)
|
return LIGHT_CONFIG.DAY_STRENGTH
|
||||||
else if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR) return LIGHT_CONFIG.DAY_STRENGTH
|
}
|
||||||
else return LIGHT_CONFIG.NIGHT_STRENGTH
|
return LIGHT_CONFIG.NIGHT_STRENGTH
|
||||||
}
|
}
|
||||||
|
|
||||||
// Socket and window handlers
|
// Scene handlers
|
||||||
const setupSocketListeners = () => {
|
const preloadScene = (scene: Phaser.Scene): void => {
|
||||||
|
scene.load.image('raindrop', 'assets/raindrop.png')
|
||||||
|
scene.load.image('fog', 'assets/fog.png')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createScene = (scene: Phaser.Scene): void => {
|
||||||
|
sceneRef.value = scene
|
||||||
|
initializeEffects(scene)
|
||||||
|
setupSocketListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateScene = (): void => {
|
||||||
|
const timeBasedLight = calculateLightStrength(gameStore.world.date)
|
||||||
|
const mapEffects =
|
||||||
|
mapObject.value?.mapEffects?.reduce<Record<string, number>>(
|
||||||
|
(acc, curr) => ({
|
||||||
|
...acc,
|
||||||
|
[curr.effect]: curr.strength
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
) ?? {}
|
||||||
|
|
||||||
|
const finalEffects: EffectValues = {
|
||||||
|
...mapEffects,
|
||||||
|
light: timeBasedLight,
|
||||||
|
rain: weatherState.value.rainPercentage,
|
||||||
|
fog: weatherState.value.fogDensity
|
||||||
|
}
|
||||||
|
|
||||||
|
applyEffects(finalEffects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map management
|
||||||
|
const loadMap = async (): Promise<void> => {
|
||||||
|
if (!mapStore.mapId) return
|
||||||
|
mapObject.value = await mapStorage.get(mapStore.mapId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket handlers
|
||||||
|
const setupSocketListeners = (): void => {
|
||||||
gameStore.connection?.emit('weather', (response: WeatherState) => {
|
gameStore.connection?.emit('weather', (response: WeatherState) => {
|
||||||
weatherState.value = response
|
weatherState.value = response
|
||||||
updateScene()
|
updateScene()
|
||||||
@ -160,12 +191,17 @@ const setupSocketListeners = () => {
|
|||||||
gameStore.connection?.on('date', updateScene)
|
gameStore.connection?.on('date', updateScene)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
// Watchers
|
||||||
if (effects.rain.value) effects.rain.value.updateConfig({ x: { min: 0, max: window.innerWidth } })
|
watch(
|
||||||
if (effects.fog.value) effects.fog.value.setPosition(window.innerWidth / 2, window.innerHeight / 2)
|
() => mapStore.mapId,
|
||||||
}
|
async (newMapId) => {
|
||||||
|
if (newMapId) {
|
||||||
|
await loadMap()
|
||||||
|
updateScene()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
watch(
|
watch(
|
||||||
() => mapObject.value,
|
() => mapObject.value,
|
||||||
() => {
|
() => {
|
||||||
@ -173,6 +209,7 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Lifecycle hooks
|
||||||
onMounted(() => window.addEventListener('resize', handleResize))
|
onMounted(() => window.addEventListener('resize', handleResize))
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user