diff --git a/packages-user/client-modules/src/fallback/weather.ts b/packages-user/client-modules/src/fallback/weather.ts index e21bfca..0003f5b 100644 --- a/packages-user/client-modules/src/fallback/weather.ts +++ b/packages-user/client-modules/src/fallback/weather.ts @@ -1,5 +1,5 @@ import { Patch, PatchClass } from '@motajs/legacy-common'; -import { WeatherController } from '../weather'; +import { WeatherController } from '../render/weather'; import { isNil } from 'lodash-es'; // todo: 添加弃用警告 logger.warn(56) diff --git a/packages-user/client-modules/src/render/index.tsx b/packages-user/client-modules/src/render/index.tsx index eec6604..2ea5fa4 100644 --- a/packages-user/client-modules/src/render/index.tsx +++ b/packages-user/client-modules/src/render/index.tsx @@ -54,6 +54,7 @@ export * from './fx'; export * from './legacy'; export * from './ui'; export * from './utils'; +export * from './weather'; export * from './renderer'; export * from './shared'; export * from './use'; diff --git a/packages-user/client-modules/src/render/ui/main.tsx b/packages-user/client-modules/src/render/ui/main.tsx index 11bc2bc..88d7328 100644 --- a/packages-user/client-modules/src/render/ui/main.tsx +++ b/packages-user/client-modules/src/render/ui/main.tsx @@ -80,6 +80,7 @@ const MainScene = defineComponent(() => { const hideStatus = ref(false); const locked = ref(false); const weather = new WeatherController(); + weather.extern('main'); onMounted(() => { if (map.value) { @@ -201,7 +202,6 @@ const MainScene = defineComponent(() => { const step = core.status.stepPostfix; if (!step) return; const ctx = canvas.ctx; - ctx.save(); ctx.fillStyle = '#fff'; step.forEach(({ x, y, direction }) => { ctx.fillRect(x * 32 + 12, y * 32 + 12, 8, 8); @@ -222,7 +222,6 @@ const MainScene = defineComponent(() => { } } }); - ctx.restore(); }; //#region 交互监听 diff --git a/packages-user/client-modules/src/render/ui/statusBar.tsx b/packages-user/client-modules/src/render/ui/statusBar.tsx index 15f7c3a..e816541 100644 --- a/packages-user/client-modules/src/render/ui/statusBar.tsx +++ b/packages-user/client-modules/src/render/ui/statusBar.tsx @@ -297,7 +297,6 @@ export const RightStatusBar = defineComponent>( let linked = false; const drawMinimap = (canvas: MotaOffscreenCanvas2D) => { const ctx = canvas.ctx; - ctx.save(); ctx.scale( 1 / core.domStyle.scale / devicePixelRatio, 1 / core.domStyle.scale / devicePixelRatio @@ -320,7 +319,6 @@ export const RightStatusBar = defineComponent>( ) ?? ''; minimapDrawer.locateMap(minimapDrawer.nowFloor); minimapDrawer.drawMap(); - ctx.restore(); }; watch( diff --git a/packages-user/client-modules/src/render/ui/title.tsx b/packages-user/client-modules/src/render/ui/title.tsx index a4d8859..d7e6cda 100644 --- a/packages-user/client-modules/src/render/ui/title.tsx +++ b/packages-user/client-modules/src/render/ui/title.tsx @@ -376,11 +376,9 @@ export const GameTitle = defineComponent(props => { createMaskGradient(ctx); } const pos = maskPos.value; - ctx.save(); ctx.translate(pos, 0); ctx.fillStyle = maskGradient!; ctx.fillRect(0, 0, MAIN_WIDTH + MAIN_HEIGHT + 200, MAIN_HEIGHT); - ctx.restore(); }; const renderTitle = (canvas: MotaOffscreenCanvas2D) => { @@ -388,7 +386,6 @@ export const GameTitle = defineComponent(props => { if (titleGradient === null) { createTitleGradient(ctx); } - ctx.save(); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = titleFont; @@ -400,12 +397,10 @@ export const GameTitle = defineComponent(props => { blur(1px) `; ctx.fillText(core.firstData.title, 320, 50); - ctx.restore(); }; const renderCursor = (canvas: MotaOffscreenCanvas2D) => { const ctx = canvas.ctx; - ctx.save(); ctx.translate(0, 5); ctx.scale(1, cursorScale); ctx.beginPath(); @@ -415,7 +410,6 @@ export const GameTitle = defineComponent(props => { ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); - ctx.restore(); }; return () => ( diff --git a/packages-user/client-modules/src/render/utils/use.ts b/packages-user/client-modules/src/render/utils/use.ts index b231958..a5302eb 100644 --- a/packages-user/client-modules/src/render/utils/use.ts +++ b/packages-user/client-modules/src/render/utils/use.ts @@ -1,11 +1,8 @@ import { onUnmounted } from 'vue'; -import { WeatherController } from '../../weather'; - -let weatherId = 0; +import { WeatherController } from '../weather'; export function useWeather(): [WeatherController] { - const weather = new WeatherController(`@weather-${weatherId}`); - weatherId++; + const weather = new WeatherController(); onUnmounted(() => { weather.destroy(); diff --git a/packages-user/client-modules/src/render/weather/controller.ts b/packages-user/client-modules/src/render/weather/controller.ts index 81643d8..f7c04e6 100644 --- a/packages-user/client-modules/src/render/weather/controller.ts +++ b/packages-user/client-modules/src/render/weather/controller.ts @@ -77,8 +77,9 @@ export class WeatherController implements IWeatherController { level: number = 5 ): IWeatherInstance | null { const obj = this.getWeatherObject(weather); - if (!obj) return null; + if (!obj || !this.container) return null; const element = obj.create(level); + element.size(this.container.width, this.container.height); const instance = new WeatherInstance(obj, element); instance.setZIndex(this.zIndex + this.active.size); this.active.add(instance); @@ -92,6 +93,13 @@ export class WeatherController implements IWeatherController { this.active.delete(instance); } + clearWeather(): void { + this.active.forEach(v => { + v.weather.destroy(); + }); + this.active.clear(); + } + /** * 将此控制器暴露至全局,允许使用 {@link WeatherController.get} 获取到实例 * @param id 暴露给全局的 id @@ -102,10 +110,7 @@ export class WeatherController implements IWeatherController { } destroy() { - this.active.forEach(v => { - v.weather.destroy(); - }); - this.active.clear(); + this.clearWeather(); WeatherController.ticker.remove(this.tick); if (!isNil(this.externId)) { WeatherController.extern.delete(this.externId); diff --git a/packages-user/client-modules/src/render/weather/index.ts b/packages-user/client-modules/src/render/weather/index.ts index 67bf445..8d68878 100644 --- a/packages-user/client-modules/src/render/weather/index.ts +++ b/packages-user/client-modules/src/render/weather/index.ts @@ -1,11 +1,11 @@ import { WeatherController } from './controller'; -import { CloudWeather, RainWeather, SnowWeather, SunWeather } from './presets'; +import { CloudWeather, RainWeather } from './presets'; export function createWeather() { WeatherController.register('cloud', CloudWeather); WeatherController.register('rain', RainWeather); - WeatherController.register('snow', SnowWeather); - WeatherController.register('sun', SunWeather); + // WeatherController.register('snow', SnowWeather); + // WeatherController.register('sun', SunWeather); } export * from './presets'; diff --git a/packages-user/client-modules/src/render/weather/presets/cloud.ts b/packages-user/client-modules/src/render/weather/presets/cloud.ts index 993cd57..61a8eba 100644 --- a/packages-user/client-modules/src/render/weather/presets/cloud.ts +++ b/packages-user/client-modules/src/render/weather/presets/cloud.ts @@ -1,16 +1,82 @@ -import { Sprite } from '@motajs/render-core'; +import { MotaOffscreenCanvas2D, Sprite } from '@motajs/render-core'; import { Weather } from '../weather'; export class CloudWeather extends Weather { - tick(timestamp: number): void { - throw new Error('Method not implemented.'); + /** 云层的不透明度 */ + 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 lastDvTime = 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(); + if (time - this.lastDvTime > 50) { + this.lastDvTime = time; + const dvx = ((Math.random() - 0.5) * this.level) / 20; + const dvy = ((Math.random() - 0.5) * this.level) / 20; + if (Math.sign(dvx) === Math.sign(this.vx)) { + const ratio = Math.sqrt( + (this.maxSpeed - Math.abs(this.vx)) / this.maxSpeed + ); + const value = Math.abs(dvx) * ratio; + this.vx += value * Math.sign(dvx); + } else { + this.vx += dvx; + } + if (Math.sign(dvy) === Math.sign(this.vy)) { + const ratio = Math.sqrt( + (this.maxSpeed - Math.abs(this.vy)) / this.maxSpeed + ); + const value = Math.abs(dvy) * ratio; + this.vy += value * Math.sign(dvy); + } else { + this.vy += dvy; + } + } + this.cx += this.vx; + this.cy += this.vy; + this.cx %= this.image.width; + this.cy %= this.image.height; } createElement(level: number): Sprite { - throw new Error('Method not implemented.'); + const element = new Sprite('static', true); + element.setRenderFn(canvas => this.drawCloud(canvas)); + this.maxSpeed = Math.sqrt(level) * 5; + 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; } - onDestroy(): void { - throw new Error('Method not implemented.'); - } + onDestroy(): void {} } diff --git a/packages-user/client-modules/src/render/weather/types.ts b/packages-user/client-modules/src/render/weather/types.ts index c3e4d32..c389f63 100644 --- a/packages-user/client-modules/src/render/weather/types.ts +++ b/packages-user/client-modules/src/render/weather/types.ts @@ -75,6 +75,11 @@ export interface IWeatherController { */ deactivate(instance: IWeatherInstance): void; + /** + * 清空天气 + */ + clearWeather(): void; + /** * 摧毁这个控制器 */ diff --git a/packages/legacy-common/src/resource.ts b/packages/legacy-common/src/resource.ts index 3d27bfd..8681431 100644 --- a/packages/legacy-common/src/resource.ts +++ b/packages/legacy-common/src/resource.ts @@ -575,7 +575,7 @@ export function loadDefaultResource() { ] = res.resource; }); }); - const weathers: (keyof Weather)[] = ['fog', 'cloud', 'sun']; + const weathers: (keyof Weather)[] = ['fog', 'sun']; weathers.forEach(v => { const res = LoadTask.add('material', `material/${v}.png`); res.once('load', res => { @@ -618,7 +618,7 @@ export async function loadCompressedResource() { HTMLImageElement >[]; materialImages.push('keyboard'); - const weathers: (keyof Weather)[] = ['fog', 'cloud', 'sun']; + const weathers: (keyof Weather)[] = ['fog', 'sun']; Object.entries(list).forEach(v => { const [uri, list] = v; diff --git a/packages/render-core/src/sprite.ts b/packages/render-core/src/sprite.ts index a55877b..63cf4ae 100644 --- a/packages/render-core/src/sprite.ts +++ b/packages/render-core/src/sprite.ts @@ -33,7 +33,9 @@ export class Sprite< canvas: MotaOffscreenCanvas2D, transform: Transform ): void { + canvas.ctx.save(); this.renderFn(canvas, transform); + canvas.ctx.restore(); } setRenderFn(fn: RenderFunction) { diff --git a/public/project/data.js b/public/project/data.js index 0742e7d..2a24e45 100644 --- a/public/project/data.js +++ b/public/project/data.js @@ -127,6 +127,7 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d = "bg.webp", "boom.png", "botton.png", + "cloud.png", "def.png", "exp.png", "hero1.png", diff --git a/public/project/floors/MT14.js b/public/project/floors/MT14.js index a6a5352..eec45c9 100644 --- a/public/project/floors/MT14.js +++ b/public/project/floors/MT14.js @@ -420,7 +420,7 @@ main.floors.MT14= ], "weather": [ "cloud", - 1 + 5 ], "beforeBattle": {}, "cannotMoveIn": {}, diff --git a/public/project/materials/cloud.png b/public/project/images/cloud.png similarity index 100% rename from public/project/materials/cloud.png rename to public/project/images/cloud.png diff --git a/script/declare.ts b/script/declare.ts index 1ebd7dd..b47b115 100644 --- a/script/declare.ts +++ b/script/declare.ts @@ -61,16 +61,16 @@ import fs from 'fs/promises'; names += '}'; // 5. 写入文件 - await fs.writeFile('./src/source/cls.d.ts', id2cls, 'utf-8'); - await fs.writeFile('./src/source/events.d.ts', eventDec, 'utf-8'); - await fs.writeFile('./src/source/items.d.ts', itemDec, 'utf-8'); + await fs.writeFile('./src/types/source/cls.d.ts', id2cls, 'utf-8'); + await fs.writeFile('./src/types/source/events.d.ts', eventDec, 'utf-8'); + await fs.writeFile('./src/types/source/items.d.ts', itemDec, 'utf-8'); await fs.writeFile( - './src/source/maps.d.ts', + './src/types/source/maps.d.ts', `${id2num}\n${num2id}`, 'utf-8' ); await fs.writeFile( - './src/source/data.d.ts', + './src/types/source/data.d.ts', ` ${floorId} ${d.images.length > 0 ? imgs : 'type ImageIds = never\n'} diff --git a/script/dev.ts b/script/dev.ts index ed0898c..78c5dce 100644 --- a/script/dev.ts +++ b/script/dev.ts @@ -350,16 +350,17 @@ const apiWriteFile = withSafeCheck(async (req, res, path) => { encoding: type as BufferEncoding }); res.end(); - if (path.resolved.endsWith('project/events.js')) { + + if (/project(\/|\\)events\.js/.test(path.resolved)) { doDeclaration('events', value); } - if (path.resolved.endsWith('project/items.js')) { + if (/project(\/|\\)items\.js/.test(path.resolved)) { doDeclaration('items', value); } - if (path.resolved.endsWith('project/maps.js')) { + if (/project(\/|\\)maps\.js/.test(path.resolved)) { doDeclaration('maps', value); } - if (path.resolved.endsWith('project/data.js')) { + if (/project(\/|\\)data\.js/.test(path.resolved)) { doDeclaration('data', value); } } catch (e) { @@ -516,7 +517,7 @@ async function doDeclaration(type: string, data: string) { for (const id in eventData.commonEvent) { eventDec += ` | '${id}'\n`; } - await writeFile('src/source/events.d.ts', eventDec, 'utf-8'); + await writeFile('src/types/source/events.d.ts', eventDec, 'utf-8'); } else if (type === 'items') { // 道具 const itemData = JSON.parse(data.split('\n').slice(1).join('')); @@ -526,7 +527,7 @@ async function doDeclaration(type: string, data: string) { itemDec += ` ${id}: '${itemData[id].cls}';\n`; } itemDec += '}'; - await writeFile('src/source/items.d.ts', itemDec, 'utf-8'); + await writeFile('src/types/source/items.d.ts', itemDec, 'utf-8'); } else if (type === 'maps') { // 映射 const d = JSON.parse(data.split('\n').slice(1).join('')); @@ -543,7 +544,7 @@ async function doDeclaration(type: string, data: string) { id2cls += '}'; id2num += '}'; num2id += '}'; - await writeFile('src/source/cls.d.ts', id2cls, 'utf-8'); + await writeFile('src/types/source/cls.d.ts', id2cls, 'utf-8'); await writeFile( 'src/source/maps.d.ts', `${id2num}\n${num2id}`, @@ -573,7 +574,7 @@ async function doDeclaration(type: string, data: string) { names += '}'; await writeFile( - 'src/source/data.d.ts', + 'src/types/source/data.d.ts', ` ${floorId} ${d.images.length > 0 ? imgs : 'type ImageIds = never\n'} diff --git a/src/types/declaration/core.d.ts b/src/types/declaration/core.d.ts index 5df6718..eceace2 100644 --- a/src/types/declaration/core.d.ts +++ b/src/types/declaration/core.d.ts @@ -258,6 +258,7 @@ interface AnimateFrame { leftLeg: boolean; /** + * @deprecated * 当前天气信息 */ readonly weather: Weather; diff --git a/src/types/source/data.d.ts b/src/types/source/data.d.ts index 9fffa0a..de5b1a5 100644 --- a/src/types/source/data.d.ts +++ b/src/types/source/data.d.ts @@ -1,3 +1,4 @@ + type FloorIds = | 'empty' | 'MT0' @@ -105,7 +106,7 @@ type FloorIds = | 'MT94' | 'MT95' | 'MT96' - | 'MT97'; + | 'MT97' type ImageIds = | 'IQ.png' @@ -114,6 +115,7 @@ type ImageIds = | 'bg.webp' | 'boom.png' | 'botton.png' + | 'cloud.png' | 'def.png' | 'exp.png' | 'hero1.png' @@ -139,7 +141,7 @@ type ImageIds = | 'tower7.webp' | 'winskin.png' | 'winskin2.png' - | 'winskin3.png'; + | 'winskin3.png' type AnimationIds = | 'amazed' @@ -163,7 +165,7 @@ type AnimationIds = | 'sweat' | 'sweat2' | 'sword' - | 'zone'; + | 'zone' type SoundIds = | '008-System08.opus' @@ -205,7 +207,7 @@ type SoundIds = | 'shop.opus' | 'thunder.opus' | 'tree.opus' - | 'zone.opus'; + | 'zone.opus' type BgmIds = | 'beforeBoss.opus' @@ -228,33 +230,35 @@ type BgmIds = | 'towerBoss2.opus' | 'towerBoss3.opus' | 'winter.opus' - | 'winterTown.opus'; + | 'winterTown.opus' -type FontIds = 'normal' | 'FiraCode'; +type FontIds = + | 'normal' + | 'FiraCode' interface NameMap { - 确定: 'confirm.opus'; - 取消: 'cancel.opus'; - 操作失败: 'error.opus'; - 光标移动: 'cursor.opus'; - 打开界面: 'open_ui.opus'; - 读档: 'load.opus'; - 存档: 'save.opus'; - 获得道具: 'item.opus'; - 回血: 'recovery.opus'; - 炸弹: 'bomb.opus'; - 飞行器: 'centerFly.opus'; - 开关门: 'door.opus'; - 上下楼: 'floor.opus'; - 跳跃: 'jump.opus'; - 破墙镐: 'pickaxe.opus'; - 破冰镐: 'icePickaxe.opus'; - 宝石: 'gem.opus'; - 阻激夹域: 'zone.opus'; - 穿脱装备: 'equip.opus'; - 背景音乐: 'bgm.opus'; - 攻击: 'attack.opus'; - 背景图: 'bg.jpg'; - 商店: 'shop.opus'; - 领域: 'zone'; + '确定': 'confirm.opus'; + '取消': 'cancel.opus'; + '操作失败': 'error.opus'; + '光标移动': 'cursor.opus'; + '打开界面': 'open_ui.opus'; + '读档': 'load.opus'; + '存档': 'save.opus'; + '获得道具': 'item.opus'; + '回血': 'recovery.opus'; + '炸弹': 'bomb.opus'; + '飞行器': 'centerFly.opus'; + '开关门': 'door.opus'; + '上下楼': 'floor.opus'; + '跳跃': 'jump.opus'; + '破墙镐': 'pickaxe.opus'; + '破冰镐': 'icePickaxe.opus'; + '宝石': 'gem.opus'; + '阻激夹域': 'zone.opus'; + '穿脱装备': 'equip.opus'; + '背景音乐': 'bgm.opus'; + '攻击': 'attack.opus'; + '背景图': 'bg.jpg'; + '商店': 'shop.opus'; + '领域': 'zone'; }