refactor: 天气

This commit is contained in:
AncTe committed 2025-09-13 22:23:09 +08:00
1 parent 8db7411def
commit 8b40e53c2c
18 files changed
+392 -491

No files matched your search

@@ -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, {});
@@ -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<LayerGroup>();
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({
@@ -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<string, IWeatherController> = new Map();
/** 注册的天气 */
static weathers: Map<string, WeatherConstructor> = new Map();
private static ticker: Ticker = new Ticker();
/** 暴露至全局的 id */
private externId?: string;
/** 天气元素纵深 */
private zIndex: number = 100;
readonly active: Set<IWeatherInstance> = 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<R extends RenderItem, T extends IWeather<R>>(
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<R extends RenderItem, T extends IWeather<R>>(
weather: T,
level?: number
): IWeatherInstance<R, T> | null;
activate<R extends RenderItem, T extends IWeather<R>>(
weather: string | T,
level: number = 5
): IWeatherInstance<R, T> | null {
const obj = this.getWeatherObject<R, T>(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<R> = IWeather<R>
> implements IWeatherInstance<R, T>
{
constructor(
readonly weather: T,
readonly element: R
) {}
setZIndex(zIndex: number): void {
this.element.setZIndex(zIndex);
}
}
@@ -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';
@@ -0,0 +1,16 @@
import { Sprite } from '@motajs/render-core';
import { Weather } from '../weather';
export class CloudWeather extends Weather<Sprite> {
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.');
}
}
@@ -0,0 +1,4 @@
export * from './cloud';
export * from './rain';
export * from './snow';
export * from './sun';
@@ -0,0 +1,186 @@
import {
Shader,
ShaderProgram,
IShaderUniform,
UniformType
} from '@motajs/render';
import { Weather } from '../weather';
const rainVs = /* glsl */ `
in vec2 a_rainVertex;
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;
vec2 pos = rotate * scale * a_rainVertex + off;
v_center = off;
v_pos = pos;
gl_Position = vec4(pos, 0.0, 1.0);
}
`;
const rainFs = /* 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 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);
}
`;
/** 雨滴顶点坐标 */
const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
export class RainWeather extends Weather<Shader> {
/** 下雨流程的 uniform 变量 */
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
/** 使用的着色器程序 */
private program: ShaderProgram | null = null;
/**
* 生成雨滴
* @param num 雨滴数量
*/
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<UniformType.Uniform4f>('u_color');
const gl = shader.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.002 + 0.002;
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.002 + 0.002;
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);
}
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<UniformType.Uniform1f>('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);
}
}
@@ -0,0 +1,16 @@
import { EShaderEvent, Shader } from '@motajs/render-core';
import { Weather } from '../weather';
export class SnowWeather extends Weather<Shader> {
tick(timestamp: number): void {
throw new Error('Method not implemented.');
}
createElement(level: number): Shader<EShaderEvent> {
throw new Error('Method not implemented.');
}
onDestroy(): void {
throw new Error('Method not implemented.');
}
}
@@ -0,0 +1,16 @@
import { Sprite } from '@motajs/render-core';
import { Weather } from '../weather';
export class SunWeather extends Weather<Sprite> {
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.');
}
}
@@ -0,0 +1,82 @@
import { RenderItem } from '@motajs/render-core';
export interface IWeather<T extends RenderItem = RenderItem> {
/** 天气的等级,-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<R> = IWeather<R>
> {
/** 天气对象 */
readonly weather: T;
/** 天气的渲染元素 */
readonly element: R;
/**
* 设置这个天气的纵深
* @param zIndex 纵深
*/
setZIndex(zIndex: number): void;
}
export interface IWeatherController {
/** 天气控制器绑定到的渲染元素 */
readonly container: RenderItem | null;
/** 所有已激活的天气 */
readonly active: Set<IWeatherInstance>;
/**
* 绑定控制器到渲染元素
* @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<R extends RenderItem, T extends IWeather<R>>(
weather: T,
level?: number
): IWeatherInstance<R, T> | null;
/**
* 取消天气
* @param instance 天气实例
*/
deactivate(instance: IWeatherInstance): void;
/**
* 摧毁这个控制器
*/
destroy(): void;
}
@@ -0,0 +1,32 @@
import { RenderItem } from '@motajs/render-core';
import { IWeather } from './types';
export abstract class Weather<T extends RenderItem> implements IWeather<T> {
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;
}