mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-02 00:14:30 +08:00
feat: fog weather & refactor: rain weather
This commit is contained in:
1 parent
1b8624041b
commit
df8328c33b
23 files changed
+296
-188
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-elements';
|
||||
import {
|
||||
MotaOffscreenCanvas2D,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-core';
|
||||
|
||||
// 经过测试(https://www.measurethat.net/Benchmarks/Show/30741/1/drawimage-img-vs-canvas-vs-bitmap-cropping-fix-loading)
|
||||
// 得出结论,ImageBitmap和Canvas的绘制性能不如Image,于是直接画Image就行,所以缓存基本上就是存Image
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { RenderAdapter } from '@motajs/render-core';
|
||||
import { RenderAdapter, SizedCanvasImageSource } from '@motajs/render-core';
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILayerRenderExtends, Layer, LayerMovingRenderable } from './layer';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-elements';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { texture } from './cache';
|
||||
import { TimingFn } from 'mutate-animate';
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
RenderItem,
|
||||
RenderItemPosition,
|
||||
MotaOffscreenCanvas2D,
|
||||
Transform
|
||||
Transform,
|
||||
SizedCanvasImageSource
|
||||
} from '@motajs/render-core';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-elements';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { RenderableData, AutotileRenderable, texture } from './cache';
|
||||
import { IAnimateFrame, renderEmits } from './frame';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseProps, TagDefine } from '@motajs/render-vue';
|
||||
import { Transform } from '@motajs/render-core';
|
||||
import { CanvasStyle, Transform } from '@motajs/render-core';
|
||||
import {
|
||||
ILayerGroupRenderExtends,
|
||||
FloorLayer,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { EAnimateEvent } from './animate';
|
||||
import { EIconEvent, EWinskinEvent } from './misc';
|
||||
import { IEnemyCollection } from '@motajs/types';
|
||||
import { CanvasStyle } from '@motajs/render-elements';
|
||||
|
||||
export interface AnimateProps extends BaseProps {}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export abstract class EffectBase<T> {
|
||||
this.program = program;
|
||||
this.shader = shader;
|
||||
|
||||
shader.useProgram(program);
|
||||
this.initProgram(program, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,14 @@ import {
|
||||
onTick
|
||||
} from '@motajs/render';
|
||||
import { WeatherController } from '../weather';
|
||||
import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef
|
||||
} from 'vue';
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI } from '@motajs/system-ui';
|
||||
import {
|
||||
@@ -76,7 +83,7 @@ const MainScene = defineComponent(() => {
|
||||
width: 480
|
||||
};
|
||||
|
||||
const map = ref<LayerGroup>();
|
||||
const map = shallowRef<LayerGroup>();
|
||||
const hideStatus = ref(false);
|
||||
const locked = ref(false);
|
||||
const weather = new WeatherController();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { WeatherController } from './controller';
|
||||
import { CloudWeather, RainWeather, SunWeather } from './presets';
|
||||
import { CloudWeather, FogWeather, RainWeather, SunWeather } from './presets';
|
||||
|
||||
export function createWeather() {
|
||||
WeatherController.register('cloud', CloudWeather);
|
||||
WeatherController.register('rain', RainWeather);
|
||||
WeatherController.register('sun', SunWeather);
|
||||
WeatherController.register('fog', FogWeather);
|
||||
// WeatherController.register('snow', SnowWeather);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +1,9 @@
|
||||
import { MotaOffscreenCanvas2D, Sprite } from '@motajs/render-core';
|
||||
import { Weather } from '../weather';
|
||||
import { CloudLike } from './cloudLike';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
|
||||
export class CloudWeather extends Weather<Sprite> {
|
||||
/** 云层的不透明度 */
|
||||
private alpha: number = 0;
|
||||
/** 水平速度 */
|
||||
private vx: number = 0;
|
||||
/** 竖直速度 */
|
||||
private vy: number = 0;
|
||||
/** 水平位置 */
|
||||
private cx: number = 0;
|
||||
/** 竖直位置 */
|
||||
private cy: number = 0;
|
||||
/** 云层移动的最大速度 */
|
||||
private maxSpeed: number = 1;
|
||||
/** 云层图像 */
|
||||
private image: HTMLImageElement | null = null;
|
||||
/** 上一次执行速度变换的时刻 */
|
||||
private lastTick = 0;
|
||||
|
||||
private drawCloud(canvas: MotaOffscreenCanvas2D) {
|
||||
const ctx = canvas.ctx;
|
||||
if (!this.image) return;
|
||||
ctx.globalAlpha = this.alpha;
|
||||
const { width, height } = this.image;
|
||||
for (let x = -1; x < 2; x++) {
|
||||
for (let y = -1; y < 2; y++) {
|
||||
const dx = x * width + this.cx;
|
||||
const dy = y * height + this.cy;
|
||||
if (dx > canvas.width || dy > canvas.height) continue;
|
||||
if (dx + width < 0 || dy + height < 0) continue;
|
||||
ctx.drawImage(this.image, dx, dy, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick(time: number): void {
|
||||
if (!this.element || !this.image) return;
|
||||
this.element.update();
|
||||
const dt = time - this.lastTick;
|
||||
this.lastTick = time;
|
||||
if (dt > 100) return;
|
||||
const dvx = (Math.random() - 0.5) * this.level * 10;
|
||||
const dvy = (Math.random() - 0.5) * this.level * 10;
|
||||
const addx = (dvx * dt) / 1000;
|
||||
const addy = (dvy * dt) / 1000;
|
||||
if (Math.sign(addx) === Math.sign(this.vx)) {
|
||||
const ratio = Math.sqrt(
|
||||
(this.maxSpeed - Math.abs(this.vx)) / this.maxSpeed
|
||||
);
|
||||
const value = Math.abs(addx) * ratio;
|
||||
this.vx += value * Math.sign(addx);
|
||||
} else {
|
||||
this.vx += addx;
|
||||
}
|
||||
if (Math.sign(addy) === Math.sign(this.vy)) {
|
||||
const ratio = Math.sqrt(
|
||||
(this.maxSpeed - Math.abs(this.vy)) / this.maxSpeed
|
||||
);
|
||||
const value = Math.abs(addy) * ratio;
|
||||
this.vy += value * Math.sign(addy);
|
||||
} else {
|
||||
this.vy += addy;
|
||||
}
|
||||
this.cx += (this.vx * dt) / 1000;
|
||||
this.cy += (this.vy * dt) / 1000;
|
||||
this.cx %= this.image.width;
|
||||
this.cy %= this.image.height;
|
||||
}
|
||||
|
||||
createElement(level: number): Sprite {
|
||||
const element = new Sprite('static', true);
|
||||
element.setRenderFn(canvas => this.drawCloud(canvas));
|
||||
this.maxSpeed = Math.sqrt(level) * 100;
|
||||
this.vx = ((Math.random() - 0.5) * this.maxSpeed) / 2;
|
||||
this.vy = ((Math.random() - 0.5) * this.maxSpeed) / 2;
|
||||
this.alpha = Math.sqrt(level) / 10;
|
||||
this.image = core.material.images.images['cloud.png'];
|
||||
return element;
|
||||
export class CloudWeather extends CloudLike {
|
||||
getImage(): SizedCanvasImageSource | null {
|
||||
return core.material.images.images['cloud.png'] ?? null;
|
||||
}
|
||||
|
||||
onDestroy(): void {}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { MotaOffscreenCanvas2D, Sprite } from '@motajs/render-core';
|
||||
import { Weather } from '../weather';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
|
||||
export abstract class CloudLike extends Weather<Sprite> {
|
||||
/** 不透明度 */
|
||||
private alpha: number = 0;
|
||||
/** 水平速度 */
|
||||
private vx: number = 0;
|
||||
/** 竖直速度 */
|
||||
private vy: number = 0;
|
||||
/** 水平位置 */
|
||||
private cx: number = 0;
|
||||
/** 竖直位置 */
|
||||
private cy: number = 0;
|
||||
/** 移动的最大速度 */
|
||||
private maxSpeed: number = 1;
|
||||
/** 上一次执行速度变换的时刻 */
|
||||
private lastTick = 0;
|
||||
/** 绘制天气使用的图片 */
|
||||
private image: SizedCanvasImageSource | null = null;
|
||||
|
||||
/**
|
||||
* 获取类多云天气所使用的图片
|
||||
*/
|
||||
abstract getImage(): SizedCanvasImageSource | null;
|
||||
|
||||
private drawImage(canvas: MotaOffscreenCanvas2D) {
|
||||
const ctx = canvas.ctx;
|
||||
if (!this.image) return;
|
||||
ctx.globalAlpha = this.alpha;
|
||||
const { width, height } = this.image;
|
||||
for (let x = -1; x < 2; x++) {
|
||||
for (let y = -1; y < 2; y++) {
|
||||
const dx = x * width + this.cx;
|
||||
const dy = y * height + this.cy;
|
||||
if (dx > canvas.width || dy > canvas.height) continue;
|
||||
if (dx + width < 0 || dy + height < 0) continue;
|
||||
ctx.drawImage(this.image, dx, dy, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick(time: number): void {
|
||||
if (!this.element || !this.image) return;
|
||||
this.element.update();
|
||||
const dt = time - this.lastTick;
|
||||
this.lastTick = time;
|
||||
if (dt > 100) return;
|
||||
const dvx = (Math.random() - 0.5) * this.level * 10;
|
||||
const dvy = (Math.random() - 0.5) * this.level * 10;
|
||||
const addx = (dvx * dt) / 1000;
|
||||
const addy = (dvy * dt) / 1000;
|
||||
if (Math.sign(addx) === Math.sign(this.vx)) {
|
||||
const ratio = Math.sqrt(
|
||||
(this.maxSpeed - Math.abs(this.vx)) / this.maxSpeed
|
||||
);
|
||||
const value = Math.abs(addx) * ratio;
|
||||
this.vx += value * Math.sign(addx);
|
||||
} else {
|
||||
this.vx += addx;
|
||||
}
|
||||
if (Math.sign(addy) === Math.sign(this.vy)) {
|
||||
const ratio = Math.sqrt(
|
||||
(this.maxSpeed - Math.abs(this.vy)) / this.maxSpeed
|
||||
);
|
||||
const value = Math.abs(addy) * ratio;
|
||||
this.vy += value * Math.sign(addy);
|
||||
} else {
|
||||
this.vy += addy;
|
||||
}
|
||||
this.cx += (this.vx * dt) / 1000;
|
||||
this.cy += (this.vy * dt) / 1000;
|
||||
this.cx %= this.image.width;
|
||||
this.cy %= this.image.height;
|
||||
}
|
||||
|
||||
createElement(level: number): Sprite {
|
||||
const element = new Sprite('static', true);
|
||||
element.setRenderFn(canvas => this.drawImage(canvas));
|
||||
this.maxSpeed = Math.sqrt(level) * 100;
|
||||
this.vx = ((Math.random() - 0.5) * this.maxSpeed) / 2;
|
||||
this.vy = ((Math.random() - 0.5) * this.maxSpeed) / 2;
|
||||
this.alpha = Math.sqrt(level) / 10;
|
||||
this.image = this.getImage();
|
||||
return element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
|
||||
import { CloudLike } from './cloudLike';
|
||||
import { SizedCanvasImageSource } from '@motajs/render-core';
|
||||
|
||||
export class FogWeather extends CloudLike {
|
||||
/** 雾天气的图像比较小,因此将四个进行合并 */
|
||||
private static mergedFog: MotaOffscreenCanvas2D | null = null;
|
||||
|
||||
getImage(): SizedCanvasImageSource | null {
|
||||
if (FogWeather.mergedFog) {
|
||||
return FogWeather.mergedFog.canvas;
|
||||
} else {
|
||||
return FogWeather.mergeFog();
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(): void {}
|
||||
|
||||
/**
|
||||
* 将雾天气的图片 2x2 合并
|
||||
*/
|
||||
static mergeFog() {
|
||||
const image = core.material.images.images['fog.png'];
|
||||
if (!image) return null;
|
||||
const { width, height } = image;
|
||||
this.mergedFog = new MotaOffscreenCanvas2D();
|
||||
this.mergedFog.size(width * 2, height * 2);
|
||||
const ctx = this.mergedFog.ctx;
|
||||
ctx.drawImage(image, 0, 0, width, width);
|
||||
ctx.drawImage(image, width, 0, width, width);
|
||||
ctx.drawImage(image, 0, height, width, width);
|
||||
ctx.drawImage(image, width, height, width, width);
|
||||
return this.mergedFog.canvas;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './cloud';
|
||||
export * from './cloudLike';
|
||||
export * from './fog';
|
||||
export * from './rain';
|
||||
export * from './snow';
|
||||
export * from './sun';
|
||||
@@ -1,8 +1,9 @@
|
||||
import {
|
||||
Shader,
|
||||
ShaderProgram,
|
||||
IShaderUniform,
|
||||
UniformType
|
||||
UniformType,
|
||||
MotaOffscreenCanvas2D,
|
||||
GL2
|
||||
} from '@motajs/render';
|
||||
import { Weather } from '../weather';
|
||||
|
||||
@@ -73,24 +74,61 @@ void main() {
|
||||
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 = mix(u_color, tex, 0.8);
|
||||
}
|
||||
`;
|
||||
|
||||
/** 雨滴顶点坐标 */
|
||||
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;
|
||||
interface RainCreateData {
|
||||
/** 进度变量 */
|
||||
readonly uProgress: IShaderUniform<UniformType.Uniform1f> | null;
|
||||
/** 下雨着色器程序 */
|
||||
readonly program: ShaderProgram;
|
||||
}
|
||||
|
||||
class RainShader extends GL2 {
|
||||
/** 下雨程序 */
|
||||
private rainProgram: ShaderProgram | null = null;
|
||||
/** 背景程序 */
|
||||
private backProgram: ShaderProgram | null = null;
|
||||
|
||||
create(level: number): RainCreateData {
|
||||
const gl = this.gl;
|
||||
const program = this.createProgram(ShaderProgram);
|
||||
program.fs(rainFs);
|
||||
program.vs(rainVs);
|
||||
program.requestCompile();
|
||||
this.useProgram(program);
|
||||
const pos = program.defineAttribArray('a_rainVertex');
|
||||
program.defineAttribArray('a_offset');
|
||||
program.defineAttribArray('a_data');
|
||||
program.defineUniform('u_color', this.UNIFORM_4f);
|
||||
const uProgress = program.defineUniform('u_progress', this.UNIFORM_1f);
|
||||
program.mode(this.DRAW_ARRAYS_INSTANCED);
|
||||
|
||||
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 * level);
|
||||
|
||||
this.generateRainPath(level, program);
|
||||
this.rainProgram = program;
|
||||
this.backProgram = this.createProgram(ShaderProgram);
|
||||
this.backProgram.requestCompile();
|
||||
|
||||
return { uProgress, program };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成雨滴
|
||||
* @param num 雨滴数量
|
||||
*/
|
||||
generateRainPath(level: number, program: ShaderProgram, shader: Shader) {
|
||||
generateRainPath(level: number, program: ShaderProgram) {
|
||||
const num = level * 100;
|
||||
const angle = (((Math.random() - 0.5) * Math.PI) / 30) * level;
|
||||
const deviation = (Math.PI / 180) * (12 - level);
|
||||
@@ -98,8 +136,8 @@ export class RainWeather extends Weather<Shader> {
|
||||
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 gl = this.gl;
|
||||
if (!aOffset || !aData || !color) return;
|
||||
|
||||
const tan = Math.tan(angle);
|
||||
|
||||
@@ -135,52 +173,47 @@ export class RainWeather extends Weather<Shader> {
|
||||
aData.enable();
|
||||
|
||||
program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, num);
|
||||
color?.set(1, 1, 1, 0.1);
|
||||
color.set(1, 1, 1, 0.1);
|
||||
}
|
||||
|
||||
createElement(level: number): Shader {
|
||||
const shader = new Shader();
|
||||
const gl = shader.gl;
|
||||
shader.size(480, 480);
|
||||
protected drawScene(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
gl: WebGL2RenderingContext
|
||||
): void {
|
||||
const program1 = this.backProgram;
|
||||
const program2 = this.rainProgram;
|
||||
if (!program1 || !program2) return;
|
||||
this.useProgram(program1);
|
||||
program1.texTexture('u_sampler', canvas.canvas);
|
||||
this.draw(gl, program1);
|
||||
this.useProgram(program2);
|
||||
program2.texTexture('u_sampler', canvas.canvas);
|
||||
this.draw(gl, program2);
|
||||
}
|
||||
}
|
||||
|
||||
export class RainWeather extends Weather<GL2> {
|
||||
/** 下雨流程的 uniform 变量 */
|
||||
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
|
||||
/** 下雨着色器程序 */
|
||||
private program: ShaderProgram | null = null;
|
||||
|
||||
createElement(level: number): GL2 {
|
||||
const shader = new RainShader();
|
||||
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);
|
||||
|
||||
const { uProgress, program } = shader.create(level);
|
||||
this.progress = uProgress;
|
||||
this.program = program;
|
||||
return shader;
|
||||
}
|
||||
|
||||
tick(timestamp: number): void {
|
||||
if (!this.element) return;
|
||||
if (!this.element || !this.program) 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);
|
||||
}
|
||||
onDestroy(): void {}
|
||||
}
|
||||
Reference in new issue
Block a user