From 8b40e53c2cd11d68420394a90757fc01fa927f24 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sat, 13 Sep 2025 22:23:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=A4=A9=E6=B0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages-user/client-modules/src/index.ts | 1 - .../client-modules/src/render/index.tsx | 2 + .../client-modules/src/render/ui/main.tsx | 8 +- .../src/render/weather/controller.ts | 146 ++++++++++ .../src/render/weather/index.ts | 14 + .../src/render/weather/presets/cloud.ts | 16 ++ .../src/render/weather/presets/index.ts | 4 + .../weather/presets}/rain.ts | 140 ++++------ .../src/render/weather/presets/snow.ts | 16 ++ .../src/render/weather/presets/sun.ts | 16 ++ .../src/render/weather/types.ts | 82 ++++++ .../src/render/weather/weather.ts | 32 +++ .../client-modules/src/weather/cloud.ts | 0 .../client-modules/src/weather/index.ts | 11 - .../client-modules/src/weather/snow.ts | 251 ------------------ .../client-modules/src/weather/sun.ts | 11 - .../client-modules/src/weather/weather.ts | 130 --------- packages/common/src/logger.json | 3 +- 18 files changed, 392 insertions(+), 491 deletions(-) create mode 100644 packages-user/client-modules/src/render/weather/controller.ts create mode 100644 packages-user/client-modules/src/render/weather/index.ts create mode 100644 packages-user/client-modules/src/render/weather/presets/cloud.ts create mode 100644 packages-user/client-modules/src/render/weather/presets/index.ts rename packages-user/client-modules/src/{weather => render/weather/presets}/rain.ts (76%) create mode 100644 packages-user/client-modules/src/render/weather/presets/snow.ts create mode 100644 packages-user/client-modules/src/render/weather/presets/sun.ts create mode 100644 packages-user/client-modules/src/render/weather/types.ts create mode 100644 packages-user/client-modules/src/render/weather/weather.ts delete mode 100644 packages-user/client-modules/src/weather/cloud.ts delete mode 100644 packages-user/client-modules/src/weather/index.ts delete mode 100644 packages-user/client-modules/src/weather/snow.ts delete mode 100644 packages-user/client-modules/src/weather/sun.ts delete mode 100644 packages-user/client-modules/src/weather/weather.ts diff --git a/packages-user/client-modules/src/index.ts b/packages-user/client-modules/src/index.ts index 65061e4..9f8fc7c 100644 --- a/packages-user/client-modules/src/index.ts +++ b/packages-user/client-modules/src/index.ts @@ -17,4 +17,3 @@ export * from './audio'; export * from './fallback'; export * from './loader'; export * from './render'; -export * from './weather'; diff --git a/packages-user/client-modules/src/render/index.tsx b/packages-user/client-modules/src/render/index.tsx index cc65342..eec6604 100644 --- a/packages-user/client-modules/src/render/index.tsx +++ b/packages-user/client-modules/src/render/index.tsx @@ -10,6 +10,7 @@ import { createAction } from './action'; import { createLegacy } from './legacy'; import { sceneController } from './scene'; import { GameTitleUI } from './ui/title'; +import { createWeather } from './weather'; export function createGameRenderer() { const App = defineComponent(_props => { @@ -32,6 +33,7 @@ export function createRender() { createUI(); createAction(); createLoopMap(); + createWeather(); loading.on('loaded', () => { sceneController.open(GameTitleUI, {}); diff --git a/packages-user/client-modules/src/render/ui/main.tsx b/packages-user/client-modules/src/render/ui/main.tsx index 9f410de..11bc2bc 100644 --- a/packages-user/client-modules/src/render/ui/main.tsx +++ b/packages-user/client-modules/src/render/ui/main.tsx @@ -7,7 +7,7 @@ import { Sprite, onTick } from '@motajs/render'; -import { WeatherController } from '../../weather'; +import { WeatherController } from '../weather'; import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'; import { Textbox, Tip } from '../components'; import { GameUI } from '@motajs/system-ui'; @@ -79,10 +79,12 @@ const MainScene = defineComponent(() => { const map = ref(); const hideStatus = ref(false); const locked = ref(false); - const weather = new WeatherController('main'); + const weather = new WeatherController(); onMounted(() => { - weather.bind(map.value); + if (map.value) { + weather.bind(map.value); + } }); const leftStatus: ILeftHeroStatus = reactive({ diff --git a/packages-user/client-modules/src/render/weather/controller.ts b/packages-user/client-modules/src/render/weather/controller.ts new file mode 100644 index 0000000..81643d8 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/controller.ts @@ -0,0 +1,146 @@ +import { RenderItem } from '@motajs/render-core'; +import { IWeather, IWeatherController, IWeatherInstance } from './types'; +import { logger } from '@motajs/common'; +import { isNil } from 'lodash-es'; +import { Ticker } from 'mutate-animate'; + +type WeatherConstructor = new () => IWeather; + +export class WeatherController implements IWeatherController { + /** 暴露到全局的控制器 */ + static extern: Map = new Map(); + /** 注册的天气 */ + static weathers: Map = new Map(); + + private static ticker: Ticker = new Ticker(); + + /** 暴露至全局的 id */ + private externId?: string; + /** 天气元素纵深 */ + private zIndex: number = 100; + + readonly active: Set = new Set(); + + container: RenderItem | null = null; + + constructor() { + WeatherController.ticker.add(this.tick); + } + + private tick = (time: number) => { + this.active.forEach(v => v.weather.tick(time)); + }; + + /** + * 设置天气元素纵深,第一个天气会被设置为 `zIndex`,之后依次是 `zIndex+1` `zIndex+2` ... + * @param zIndex 第一个天气的纵深 + */ + setZIndex(zIndex: number) { + this.zIndex = zIndex; + let n = zIndex; + this.active.forEach(v => { + v.setZIndex(n); + n++; + }); + } + + bind(container: RenderItem): void { + if (this.container) { + logger.warn(65); + return; + } + this.container = container; + } + + private getWeatherObject>( + weather: string | T + ): T | null { + if (typeof weather === 'string') { + const Weather = WeatherController.weathers.get(weather); + if (!Weather) { + logger.warn(25, weather); + return null; + } + return new Weather() as T; + } else { + return weather; + } + } + + activate(weather: string, level?: number): IWeatherInstance | null; + activate>( + weather: T, + level?: number + ): IWeatherInstance | null; + activate>( + weather: string | T, + level: number = 5 + ): IWeatherInstance | null { + const obj = this.getWeatherObject(weather); + if (!obj) return null; + const element = obj.create(level); + const instance = new WeatherInstance(obj, element); + instance.setZIndex(this.zIndex + this.active.size); + this.active.add(instance); + this.container?.appendChild(element); + return instance; + } + + deactivate(instance: IWeatherInstance): void { + this.container?.removeChild(instance.element); + instance.weather.destroy(); + this.active.delete(instance); + } + + /** + * 将此控制器暴露至全局,允许使用 {@link WeatherController.get} 获取到实例 + * @param id 暴露给全局的 id + */ + extern(id: string) { + WeatherController.extern.set(id, this); + this.externId = id; + } + + destroy() { + this.active.forEach(v => { + v.weather.destroy(); + }); + this.active.clear(); + WeatherController.ticker.remove(this.tick); + if (!isNil(this.externId)) { + WeatherController.extern.delete(this.externId); + } + } + + /** + * 获取暴露至全局的控制器 + * @param id 控制器暴露至全局的 id + */ + static get(id: string): IWeatherController | null { + return this.extern.get(id) ?? null; + } + + /** + * 注册一个天气 + * @param id 天气的名称 + * @param weather 天气的构造器 + */ + static register(id: string, weather: WeatherConstructor) { + this.weathers.set(id, weather); + } +} + +export class WeatherInstance< + R extends RenderItem = RenderItem, + T extends IWeather = IWeather +> implements IWeatherInstance +{ + constructor( + readonly weather: T, + readonly element: R + ) {} + + setZIndex(zIndex: number): void { + this.element.setZIndex(zIndex); + } +} diff --git a/packages-user/client-modules/src/render/weather/index.ts b/packages-user/client-modules/src/render/weather/index.ts new file mode 100644 index 0000000..67bf445 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/index.ts @@ -0,0 +1,14 @@ +import { WeatherController } from './controller'; +import { CloudWeather, RainWeather, SnowWeather, SunWeather } from './presets'; + +export function createWeather() { + WeatherController.register('cloud', CloudWeather); + WeatherController.register('rain', RainWeather); + WeatherController.register('snow', SnowWeather); + WeatherController.register('sun', SunWeather); +} + +export * from './presets'; +export * from './controller'; +export * from './types'; +export * from './weather'; diff --git a/packages-user/client-modules/src/render/weather/presets/cloud.ts b/packages-user/client-modules/src/render/weather/presets/cloud.ts new file mode 100644 index 0000000..993cd57 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/presets/cloud.ts @@ -0,0 +1,16 @@ +import { Sprite } from '@motajs/render-core'; +import { Weather } from '../weather'; + +export class CloudWeather extends Weather { + tick(timestamp: number): void { + throw new Error('Method not implemented.'); + } + + createElement(level: number): Sprite { + throw new Error('Method not implemented.'); + } + + onDestroy(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages-user/client-modules/src/render/weather/presets/index.ts b/packages-user/client-modules/src/render/weather/presets/index.ts new file mode 100644 index 0000000..90f5779 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/presets/index.ts @@ -0,0 +1,4 @@ +export * from './cloud'; +export * from './rain'; +export * from './snow'; +export * from './sun'; diff --git a/packages-user/client-modules/src/weather/rain.ts b/packages-user/client-modules/src/render/weather/presets/rain.ts similarity index 76% rename from packages-user/client-modules/src/weather/rain.ts rename to packages-user/client-modules/src/render/weather/presets/rain.ts index ebe4b63..098c944 100644 --- a/packages-user/client-modules/src/weather/rain.ts +++ b/packages-user/client-modules/src/render/weather/presets/rain.ts @@ -1,12 +1,10 @@ import { Shader, ShaderProgram, - MotaRenderer, - Container, IShaderUniform, UniformType } from '@motajs/render'; -import { IWeather } from './weather'; +import { Weather } from '../weather'; const rainVs = /* glsl */ ` in vec2 a_rainVertex; @@ -82,95 +80,25 @@ void main() { /** 雨滴顶点坐标 */ const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); -export class RainWeather implements IWeather { - readonly shader: RainShader; - readonly program: ShaderProgram; - +export class RainWeather extends Weather { + /** 下雨流程的 uniform 变量 */ private progress: IShaderUniform | null = null; + /** 使用的着色器程序 */ + private program: ShaderProgram | null = null; - constructor(readonly level: number = 5) { - const shader = new RainShader(); - const gl = shader.gl; - shader.size(480, 480); - shader.setHD(true); - shader.setZIndex(100); - const program = shader.createProgram(ShaderProgram); - program.fs(rainFs); - program.vs(rainVs); - program.requestCompile(); - const pos = program.defineAttribArray('a_rainVertex'); - program.defineAttribArray('a_offset'); - program.defineAttribArray('a_data'); - program.defineUniform('u_progress', shader.UNIFORM_1f); - program.defineUniform('u_color', shader.UNIFORM_4f); - program.mode(shader.DRAW_ARRAYS_INSTANCED); - shader.useProgram(program); - - if (pos) { - pos.buffer(vertex, gl.STATIC_DRAW); - pos.pointer(2, gl.FLOAT, false, 0, 0); - pos.enable(); - } - this.shader = shader; - this.program = program; - } - - activate(): void { - const render = MotaRenderer.get('render-main'); - const draw = render?.getElementById('map-draw') as Container; - if (!draw) return; - const shader = this.shader; - shader.appendTo(draw); - - const gl = shader.gl; - const program = this.program; - program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level); - - this.progress = program.getUniform('u_progress'); - shader.useProgram(program); - shader.generateRainPath( - this.level * 100, - (((Math.random() - 0.5) * Math.PI) / 30) * this.level, - (Math.PI / 180) * (12 - this.level), - program - ); - } - - frame(): void { - this.shader.update(this.shader); - const time = 5000 - 400 * this.level; - const progress = (Date.now() % time) / time; - - this.shader.useProgram(this.program); - this.progress?.set(progress); - } - - deactivate(): void { - const render = MotaRenderer.get('render-main'); - const draw = render?.getElementById('map-draw') as Container; - const layer = draw.children; - if (!layer || !draw) return; - const shader = this.shader; - draw.appendChild(...layer); - shader.remove(); - } -} - -class RainShader extends Shader { /** * 生成雨滴 * @param num 雨滴数量 */ - generateRainPath( - num: number, - angle: number, - deviation: number, - program: ShaderProgram - ) { + generateRainPath(level: number, program: ShaderProgram, shader: Shader) { + const num = level * 100; + const angle = (((Math.random() - 0.5) * Math.PI) / 30) * level; + const deviation = (Math.PI / 180) * (12 - level); + const aOffset = program.getAttribArray('a_offset'); const aData = program.getAttribArray('a_data'); const color = program.getUniform('u_color'); - const gl = this.gl; + const gl = shader.gl; if (!aOffset || !aData) return; const tan = Math.tan(angle); @@ -209,4 +137,50 @@ class RainShader extends Shader { program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, num); color?.set(1, 1, 1, 0.1); } + + createElement(level: number): Shader { + const shader = new Shader(); + const gl = shader.gl; + shader.size(480, 480); + shader.setHD(true); + shader.setZIndex(100); + const program = shader.createProgram(ShaderProgram); + program.fs(rainFs); + program.vs(rainVs); + program.requestCompile(); + const pos = program.defineAttribArray('a_rainVertex'); + program.defineAttribArray('a_offset'); + program.defineAttribArray('a_data'); + program.defineUniform('u_progress', shader.UNIFORM_1f); + program.defineUniform('u_color', shader.UNIFORM_4f); + program.mode(shader.DRAW_ARRAYS_INSTANCED); + shader.useProgram(program); + + if (pos) { + pos.buffer(vertex, gl.STATIC_DRAW); + pos.pointer(2, gl.FLOAT, false, 0, 0); + pos.enable(); + } + + program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level); + + this.progress = program.getUniform('u_progress'); + this.generateRainPath(level, program, shader); + + this.program = program; + return shader; + } + + tick(timestamp: number): void { + if (!this.element) return; + this.element.update(); + const time = 5000 - 400 * this.level; + const progress = (timestamp % time) / time; + this.progress?.set(progress); + } + + onDestroy(): void { + if (!this.element || !this.program) return; + this.element.deleteProgram(this.program); + } } diff --git a/packages-user/client-modules/src/render/weather/presets/snow.ts b/packages-user/client-modules/src/render/weather/presets/snow.ts new file mode 100644 index 0000000..ec378f4 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/presets/snow.ts @@ -0,0 +1,16 @@ +import { EShaderEvent, Shader } from '@motajs/render-core'; +import { Weather } from '../weather'; + +export class SnowWeather extends Weather { + tick(timestamp: number): void { + throw new Error('Method not implemented.'); + } + + createElement(level: number): Shader { + throw new Error('Method not implemented.'); + } + + onDestroy(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages-user/client-modules/src/render/weather/presets/sun.ts b/packages-user/client-modules/src/render/weather/presets/sun.ts new file mode 100644 index 0000000..752d670 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/presets/sun.ts @@ -0,0 +1,16 @@ +import { Sprite } from '@motajs/render-core'; +import { Weather } from '../weather'; + +export class SunWeather extends Weather { + tick(timestamp: number): void { + throw new Error('Method not implemented.'); + } + + createElement(level: number): Sprite { + throw new Error('Method not implemented.'); + } + + onDestroy(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/packages-user/client-modules/src/render/weather/types.ts b/packages-user/client-modules/src/render/weather/types.ts new file mode 100644 index 0000000..c3e4d32 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/types.ts @@ -0,0 +1,82 @@ +import { RenderItem } from '@motajs/render-core'; + +export interface IWeather { + /** 天气的等级,-1 表示未创建 */ + readonly level: number; + + /** + * 创建天气 + * @param level 天气等级 + * @returns 天气的渲染元素 + */ + create(level: number): T; + + /** + * 摧毁这个天气 + */ + destroy(): void; + + /** + * 每帧执行一次的函数 + * @param timestamp 当前时间戳 + */ + tick(timestamp: number): void; +} + +export interface IWeatherInstance< + R extends RenderItem = RenderItem, + T extends IWeather = IWeather +> { + /** 天气对象 */ + readonly weather: T; + /** 天气的渲染元素 */ + readonly element: R; + + /** + * 设置这个天气的纵深 + * @param zIndex 纵深 + */ + setZIndex(zIndex: number): void; +} + +export interface IWeatherController { + /** 天气控制器绑定到的渲染元素 */ + readonly container: RenderItem | null; + /** 所有已激活的天气 */ + readonly active: Set; + + /** + * 绑定控制器到渲染元素 + * @param container 绑定的渲染元素,需要能够添加子元素,一般绑定到 `container` + */ + bind(container: RenderItem): void; + + /** + * 使用天气名称添加天气 + * @param weather 天气的名称 + * @param level 天气的等级 + * @returns 天气实例 + */ + activate(weather: string, level?: number): IWeatherInstance | null; + /** + * 使用天气对象添加天气 + * @param weather 天气对象 + * @param level 天气的等级 + * @returns 天气实例 + */ + activate>( + weather: T, + level?: number + ): IWeatherInstance | null; + + /** + * 取消天气 + * @param instance 天气实例 + */ + deactivate(instance: IWeatherInstance): void; + + /** + * 摧毁这个控制器 + */ + destroy(): void; +} diff --git a/packages-user/client-modules/src/render/weather/weather.ts b/packages-user/client-modules/src/render/weather/weather.ts new file mode 100644 index 0000000..1658a02 --- /dev/null +++ b/packages-user/client-modules/src/render/weather/weather.ts @@ -0,0 +1,32 @@ +import { RenderItem } from '@motajs/render-core'; +import { IWeather } from './types'; + +export abstract class Weather implements IWeather { + level: number = -1; + + protected element: T | null = null; + + create(level: number): T { + const element = this.createElement(level); + this.element = element; + return element; + } + + destroy(): void { + this.element?.destroy(); + this.onDestroy(); + } + + abstract tick(timestamp: number): void; + + /** + * 创建天气的渲染元素,并进行初始化 + * @param level 天气的等级 + */ + abstract createElement(level: number): T; + + /** + * 当天气被摧毁时调用,进行清理工作,会自动摧毁渲染元素,不需要手动处理 + */ + abstract onDestroy(): void; +} diff --git a/packages-user/client-modules/src/weather/cloud.ts b/packages-user/client-modules/src/weather/cloud.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages-user/client-modules/src/weather/index.ts b/packages-user/client-modules/src/weather/index.ts deleted file mode 100644 index 6c29960..0000000 --- a/packages-user/client-modules/src/weather/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RainWeather } from './rain'; -import { SnowWeather } from './snow'; -import { SunWeather } from './sun'; -import { WeatherController } from './weather'; - -WeatherController.register('rain', RainWeather); -WeatherController.register('sun', SunWeather); -WeatherController.register('snow', SnowWeather); - -export * from './weather'; -export * from './rain'; diff --git a/packages-user/client-modules/src/weather/snow.ts b/packages-user/client-modules/src/weather/snow.ts deleted file mode 100644 index 2b8373f..0000000 --- a/packages-user/client-modules/src/weather/snow.ts +++ /dev/null @@ -1,251 +0,0 @@ -import { - Shader, - ShaderProgram, - MotaRenderer, - Container, - GL2Program, - IShaderUniform, - UniformType, - Transform, - MotaOffscreenCanvas2D -} from '@motajs/render'; -import { IWeather } from './weather'; -import { loading } from '@user/data-base'; - -const snowVs = /* glsl */ ` -in vec2 a_snowVertex; -in vec2 a_offset; // 雨滴的中心位置 -in vec4 a_data; // x: 雨滴宽度; y: 雨滴长度; z: 雨滴旋转角度,0表示向下,逆时针为正; - // w: 属于哪一种雨,需要两种雨反复循环,一种无法实现循环,0表示第一种,1表示第二种 - -uniform float u_progress; // 下雨进度,从最上落到最下是0.5个进度,以保证不会出现奇怪的问题 - -out vec2 v_center; -out vec2 v_data; // 雨滴宽度与高度 -out vec2 v_pos; - -mat2 createScaleMatrix(float x, float y) { - return mat2( - x, 0, - 0, y - ); -} - -vec2 getOffsetByProgress(vec2 offset) { - if (a_data.w == 0.0) { - if (u_progress < 0.5) { - return offset * u_progress * 2.0; - } else { - return offset * (u_progress - 1.0) * 2.0; - } - } else { - return offset * u_progress * 2.0; - } -} - -void main() { - float cosTheta = cos(a_data.z); - float sinTheta = sin(a_data.z); - mat2 rotate = mat2( - cosTheta, -sinTheta, - sinTheta, cosTheta - ); - vec2 offset = getOffsetByProgress(vec2(-sinTheta * 2.0, -cosTheta * 2.0)); - mat2 scale = createScaleMatrix(a_data.x, a_data.y); - vec2 off = a_offset + offset; - - // const int segments = 32; // 圆的分段数 - // float angleIncrement = 2.0 * 3.14159265359 / float(segments); - // vec2 a_roundVertex[segments]; - // for (int i = 0; i < segments; i++) { - // float angle = float(i) * angleIncrement; - // a_roundVertex[i] = vec2(cos(angle), sin(angle)); // 生成单位圆的顶点 - // } - - // vec2 pos = rotate * scale * a_roundVertex + off; - vec2 pos = rotate * scale * a_snowVertex + off; - v_center = off; - v_pos = pos; - gl_Position = vec4(pos, 0.0, 1.0); -} -`; - -const snowFs = /* glsl */ ` -in vec2 v_center; -in vec2 v_data; // 雨滴的宽度与长度 -in vec2 v_pos; - -uniform vec4 u_color; // 雨滴的颜色 - -out vec4 outColor; - -float random(vec2 uv) { - return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453); -} - -void main() { - - float distanceFromCenter = length(v_pos - v_center); - if (distanceFromCenter < 0.05) { - outColor = u_color; // 雪花颜色 - } else { - // outColor = vec4(0.0, 0.0, 0.0, 0.2); - discard; // 不绘制超出圆的部分 - } - - // float ran = random(v_pos); - // vec2 pos = vec2(v_pos.x + ran * 0.01, v_pos.y); - // vec2 texPos = (pos + 1.0) / 2.0; - // texPos.y = 1.0 - texPos.y; - // vec4 tex = texture(u_sampler, texPos); - // // outColor = mix(u_color, tex, 0.9); - // outColor = u_color; -} -`; - -/** 雨滴顶点坐标 */ -const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]); - -loading.once('coreInit', () => { - const shader = new SnowShader(); - const gl = shader.gl; - shader.size(480, 480); - shader.setHD(true); - SnowWeather.shader = shader; - const program = shader.createProgram(ShaderProgram); - program.fs(snowFs); - program.vs(snowVs); - program.requestCompile(); - const pos = program.defineAttribArray('a_snowVertex'); - program.defineAttribArray('a_offset'); - program.defineAttribArray('a_data'); - program.defineUniform('u_progress', shader.UNIFORM_1f); - program.defineUniform('u_color', shader.UNIFORM_4f); - program.mode(shader.DRAW_ARRAYS_INSTANCED); - SnowShader.snowProgram = program; - shader.useProgram(program); - - if (pos) { - pos.buffer(vertex, gl.STATIC_DRAW); - pos.pointer(2, gl.FLOAT, false, 0, 0); - pos.enable(); - } -}); - -export class SnowWeather implements IWeather { - static id: string = 'snow'; - - static shader: SnowShader; - - private progress: IShaderUniform | null = null; - - constructor(readonly level: number = 5) {} - - activate(): void { - const render = MotaRenderer.get('render-main'); - const draw = render?.getElementById('map-draw') as Container; - if (!draw) return; - const shader = SnowWeather.shader; - shader.appendTo(draw); - - const gl = shader.gl; - const program = SnowShader.snowProgram; - program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level); - - this.progress = program.getUniform('u_progress'); - shader.generateSnowPath( - this.level * 100, - (((Math.random() - 0.5) * Math.PI) / 30) * this.level, - (Math.PI / 180) * (12 - this.level) - ); - } - - frame(): void { - SnowWeather.shader.update(SnowWeather.shader); - const time = 5000 - 400 * this.level; - const progress = (Date.now() % time) / time; - - SnowWeather.shader.useProgram(SnowShader.snowProgram); - this.progress?.set(progress); - } - - deactivate(): void { - const render = MotaRenderer.get('render-main'); - const layer = render?.getElementById('layer-main'); - const draw = render?.getElementById('map-draw') as Container; - if (!layer || !draw) return; - const shader = SnowWeather.shader; - layer.appendTo(draw); - shader.remove(); - } -} - -class SnowShader extends Shader { - static snowProgram: ShaderProgram; - static backProgram: ShaderProgram; - - /** - * 生成雨滴 - * @param num 雨滴数量 - */ - generateSnowPath(num: number, angle: number, deviation: number) { - const program = SnowShader.snowProgram; - SnowWeather.shader.useProgram(program); - const aOffset = program.getAttribArray('a_offset'); - const aData = program.getAttribArray('a_data'); - const color = program.getUniform('u_color'); - const gl = this.gl; - if (!aOffset || !aData) return; - - const tan = Math.tan(angle); - - const offset = new Float32Array(num * 2); - const data = new Float32Array(num * 4); - const half = num / 2; - for (let i = 0; i < half; i++) { - const ox = Math.random() * 3 - 1.5; - const oy = Math.random() * 2 - 1; - const rad = angle + (Math.random() - 0.5) * Math.PI * deviation; - const length = Math.random() * 0.05 + 0.03; - const width = Math.random() * 0.05 + 0.03; - offset.set([ox, oy], i * 2); - data.set([width, length, rad, 0], i * 4); - } - for (let i = half; i < num; i++) { - const ox = Math.random() * 3 - 1.5 + tan * 2; - const oy = Math.random() * 2 + 1; - const rad = angle + (Math.random() - 0.5) * Math.PI * deviation; - const length = Math.random() * 0.1 + 0.05; - const width = Math.random() * 0.1 + 0.05; - offset.set([ox, oy], i * 2); - data.set([width, length, rad, 1], i * 4); - } - - aOffset.buffer(offset, gl.STATIC_DRAW); - aData.buffer(data, gl.STATIC_DRAW); - aOffset.pointer(2, gl.FLOAT, false, 0, 0); - aOffset.divisor(1); - aOffset.enable(); - aData.pointer(4, gl.FLOAT, false, 0, 0); - aData.divisor(1); - aData.enable(); - - program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, num); - color?.set(1, 1, 1, 0.1); - } - - protected preDraw( - canvas: MotaOffscreenCanvas2D, - transform: Transform, - gl: WebGL2RenderingContext, - program: GL2Program - ): boolean { - const snow = SnowShader.snowProgram; - const snowParam = snow.getDrawParams(this.DRAW_ARRAYS_INSTANCED); - if (!snowParam) return false; - this.useProgram(snow); - if (!snow.ready()) return false; - this.draw(gl, snow); - return false; - } -} diff --git a/packages-user/client-modules/src/weather/sun.ts b/packages-user/client-modules/src/weather/sun.ts deleted file mode 100644 index 7959e5b..0000000 --- a/packages-user/client-modules/src/weather/sun.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IWeather } from './weather'; - -export class SunWeather implements IWeather { - static id: string = 'sun'; - - activate(): void {} - - frame(): void {} - - deactivate(): void {} -} diff --git a/packages-user/client-modules/src/weather/weather.ts b/packages-user/client-modules/src/weather/weather.ts deleted file mode 100644 index cbdcd62..0000000 --- a/packages-user/client-modules/src/weather/weather.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { logger } from '@motajs/common'; -import { RenderItem } from '@motajs/render'; -import { Ticker } from 'mutate-animate'; - -export interface IWeather { - /** - * 初始化天气,当天气被添加时会被立刻调用 - */ - activate(item: RenderItem): void; - - /** - * 每帧执行的函数 - */ - frame(): void; - - /** - * 摧毁这个天气,当天气被删除时会执行 - */ - deactivate(item: RenderItem): void; -} - -type Weather = new (level?: number) => IWeather; - -export class WeatherController { - static list: Map = new Map(); - static map: Map = new Map(); - - /** 当前的所有天气 */ - active: Set = new Set(); - ticker: Ticker = new Ticker(); - - private binded?: RenderItem; - - constructor(public readonly id: string) { - WeatherController.map.set(id, this); - } - - private tick = () => { - this.active.forEach(v => { - v.frame(); - }); - }; - - /** - * 清空所有天气 - */ - clearWeather() { - if (this.binded) { - this.active.forEach(v => { - v.deactivate(this.binded!); - }); - } - this.active.clear(); - } - - /** - * 获取一个天气 - * @param weather 要获取的天气 - */ - getWeather(weather: Weather): T | null { - return ([...this.active].find(v => v instanceof weather) as T) ?? null; - } - - /** - * 添加一个天气,如果天气不存在则抛出警告。注意虽然原则上不允许天气重复。 - * @param id 天气的id - * @param level 天气的等级 - * @returns 天气实例,可以操作天气的效果,也可以用来删除 - */ - activate(id: string, level?: number) { - const Weather = WeatherController.list.get(id); - if (!Weather) { - logger.warn(25, id); - return; - } - const weather = new Weather(level); - this.active.add(weather); - if (this.binded) { - weather.activate(this.binded); - } - if (!this.ticker.funcs.has(this.tick)) { - this.ticker.add(this.tick); - } - return weather; - } - - /** - * 删除一个天气 - * @param weather 要删除的天气 - */ - deactivate(weather: IWeather) { - this.active.delete(weather); - if (this.active.size === 0) { - this.ticker.remove(this.tick); - } - if (this.binded) { - weather.deactivate(this.binded); - } - } - - /** - * 将这个天气控制器绑定至一个渲染元素上 - * @param item 要绑定的元素,不填表示取消绑定 - */ - bind(item?: RenderItem) { - if (this.binded) { - this.active.forEach(v => v.deactivate(this.binded!)); - } - this.binded = item; - if (item) { - this.active.forEach(v => v.activate(item)); - } - } - - /** - * 摧毁这个天气控制器 - */ - destroy() { - WeatherController.map.delete(this.id); - this.clearWeather(); - } - - static get(id: string) { - return this.map.get(id); - } - - static register(id: string, weather: Weather) { - this.list.set(id, weather); - } -} diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 90c888f..a45a561 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -56,7 +56,7 @@ "22": "There is already an active camera for delivered render item. Consider using 'Camera.for' or disable the active camera to avoid some exceptions.", "23": "Render item with id of '$1' has already exists. Please avoid repeat id since it may cause issues when use 'getElementById'.", "24": "Uniform block can only be used in glsl version es 300.", - "25": "Cannot activate weather since there's no weather with id of '$1'.", + "25": "Cannot activate weather since there's no weather named '$1'.", "26": "Cannot set attribute when only element number specified. Use 'pointer' or 'pointerI' instead.", "27": "Cannot vertex attribute integer point when specified as float. Use 'set' or 'pointer' instead.", "28": "Redefinition of shader $1: '$2'", @@ -96,6 +96,7 @@ "62": "Recursive fallback fonts in '$1'.", "63": "Uncaught promise error in waiting box component. Error reason: $1", "64": "Text node type and block type mismatch: '$1' vs '$2'", + "65": "Cannot bind a weather controller twice.", "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.", "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance." }