mirror of
https://github.com/motajs/template.git
synced 2026-11-02 23:14:34 +08:00
refactor: 客户端加载
This commit is contained in:
1 parent
fdeee99d40
commit
4fc6db3e79
55 files changed
+1699
-1234
No files matched your search
@@ -5,7 +5,7 @@ import { MotaOffscreenCanvas2D, SizedCanvasImageSource } from '@motajs/render';
|
||||
// 得出结论,ImageBitmap和Canvas的绘制性能不如Image,于是直接画Image就行,所以缓存基本上就是存Image
|
||||
|
||||
type ImageMapKeys = Exclude<Cls, 'tileset' | 'autotile'>;
|
||||
type ImageMap = Record<ImageMapKeys, HTMLImageElement>;
|
||||
type ImageMap = Record<ImageMapKeys, ImageBitmap>;
|
||||
|
||||
const i = (img: ImageMapKeys) => {
|
||||
return core.material.images[img];
|
||||
@@ -21,10 +21,10 @@ interface AutotileCache {
|
||||
type AutotileCaches = Record<AllNumbersOf<'autotile'>, AutotileCache>;
|
||||
|
||||
interface TextureRequire {
|
||||
tileset: Record<string, HTMLImageElement>;
|
||||
material: Record<ImageMapKeys, HTMLImageElement>;
|
||||
tileset: Record<string, ImageBitmap>;
|
||||
material: Record<ImageMapKeys, ImageBitmap>;
|
||||
autotile: AutotileCaches;
|
||||
images: Record<ImageIds, HTMLImageElement>;
|
||||
images: Record<ImageIds, ImageBitmap>;
|
||||
}
|
||||
|
||||
interface RenderableDataBase {
|
||||
@@ -49,10 +49,10 @@ export interface AutotileRenderable extends RenderableDataBase {
|
||||
}
|
||||
|
||||
class TextureCache {
|
||||
tileset!: Record<string, HTMLImageElement>;
|
||||
material: Record<ImageMapKeys, HTMLImageElement>;
|
||||
tileset!: Record<string, ImageBitmap>;
|
||||
material: Record<ImageMapKeys, ImageBitmap>;
|
||||
autotile!: AutotileCaches;
|
||||
images!: Record<ImageIds, HTMLImageElement>;
|
||||
images!: Record<ImageIds, ImageBitmap>;
|
||||
|
||||
idNumberMap!: IdToNumber;
|
||||
|
||||
@@ -76,7 +76,7 @@ class TextureCache {
|
||||
characterTurn2: Dir2[] = ['leftup', 'rightup', 'rightdown', 'leftdown'];
|
||||
|
||||
constructor() {
|
||||
this.material = imageMap as Record<ImageMapKeys, HTMLImageElement>;
|
||||
this.material = imageMap as Record<ImageMapKeys, ImageBitmap>;
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { GameTitleUI } from './ui/title';
|
||||
import { createWeather } from './weather';
|
||||
import { createMainExtension } from './commonIns';
|
||||
import { createApp } from './renderer';
|
||||
import { LoadSceneUI } from './ui/load';
|
||||
|
||||
export function createGameRenderer() {
|
||||
const App = defineComponent(_props => {
|
||||
@@ -23,6 +24,9 @@ export function createGameRenderer() {
|
||||
|
||||
mainRenderer.hide();
|
||||
createApp(App).mount(mainRenderer);
|
||||
|
||||
sceneController.open(LoadSceneUI, {});
|
||||
mainRenderer.show();
|
||||
}
|
||||
|
||||
export function createRender() {
|
||||
@@ -31,11 +35,6 @@ export function createRender() {
|
||||
createAction();
|
||||
createWeather();
|
||||
|
||||
loading.once('loaded', () => {
|
||||
sceneController.open(GameTitleUI, {});
|
||||
mainRenderer.show();
|
||||
});
|
||||
|
||||
loading.once('assetBuilt', () => {
|
||||
createMainExtension();
|
||||
});
|
||||
|
||||
@@ -54,6 +54,8 @@ export const MOVING_TOLERANCE = 60;
|
||||
/** 开关门动画的动画时长 */
|
||||
export const DOOR_ANIMATE_INTERVAL = 50;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 状态栏
|
||||
|
||||
/** 状态栏像素宽度 */
|
||||
@@ -69,6 +71,8 @@ export const STATUS_BAR_COUNT = ENABLE_RIGHT_STATUS_BAR ? 2 : 1;
|
||||
/** 状态栏宽度的一半 */
|
||||
export const HALF_STATUS_WIDTH = STATUS_BAR_WIDTH / 2;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 游戏画面
|
||||
|
||||
/** 游戏画面像素宽度,宽=地图宽度+状态栏宽度*状态栏数量 */
|
||||
@@ -91,6 +95,8 @@ export const CENTER_LOC: ElementLocator = [
|
||||
0.5
|
||||
];
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 通用配置
|
||||
|
||||
/** 弹框的宽度,使用在内置 UI 与组件中,包括确认框、选择框、等待框等 */
|
||||
@@ -98,6 +104,31 @@ export const POP_BOX_WIDTH = MAP_WIDTH / 2;
|
||||
/** 默认字体 */
|
||||
export const DEFAULT_FONT = new Font('Verdana', 16);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 加载界面
|
||||
|
||||
/** 加载界面的任务进度条半径 */
|
||||
export const LOAD_TASK_RADIUS = Math.min(MAIN_WIDTH, MAIN_HEIGHT) / 6;
|
||||
/** 加载界面的字节进度条纵轴位置 */
|
||||
export const LOAD_BYTE_HEIGHT = MAIN_HEIGHT / 2 + MAIN_HEIGHT / 4;
|
||||
/** 加载界面任务进度条的纵轴位置 */
|
||||
export const LOAD_TASK_CENTER_HEIGHT = MAIN_HEIGHT / 2 - MAIN_HEIGHT / 8;
|
||||
/** 加载界面字节进度条的长度 */
|
||||
export const LOAD_BYTE_LENGTH = MAIN_WIDTH - MAIN_WIDTH / 12;
|
||||
/** 加载界面任务进度条的粗细 */
|
||||
export const LOAD_TASK_LINE_WIDTH = 6;
|
||||
/** 加载界面字节进度条的粗细 */
|
||||
export const LOAD_BYTE_LINE_WIDTH = 6;
|
||||
/** 已加载部分进度条的颜色 */
|
||||
export const LOAD_LOADED_COLOR = '#57ff78';
|
||||
/** 未加载部分进度条的颜色 */
|
||||
export const LOAD_UNLOADED_COLOR = '#ccc';
|
||||
/** 加载界面的文字颜色 */
|
||||
export const LOAD_FONT_COLOR = '#fff';
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 存档界面
|
||||
|
||||
/** 存档缩略图尺寸 */
|
||||
@@ -115,8 +146,13 @@ export const SAVE_DOWN_PAD = 30;
|
||||
/** 存档页码数,调高并不会影响性能,但是如果玩家存档太多的话会导致存档体积很大 */
|
||||
export const SAVE_PAGES = 1000;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 标题界面
|
||||
|
||||
/** 标题图 */
|
||||
export const TITLE_BACKGROUND_IMAGE = 'bg.jpg';
|
||||
|
||||
/** 标题文字中心横坐标 */
|
||||
export const TITLE_X = HALF_WIDTH;
|
||||
/** 标题文字中心纵坐标 */
|
||||
@@ -136,3 +172,5 @@ export const BUTTONS_HEIGHT = 200;
|
||||
export const BUTTONS_X = HALF_WIDTH;
|
||||
/** 标题界面按钮左上角纵坐标 */
|
||||
export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT;
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,165 @@
|
||||
import { DefaultProps } from '@motajs/render-vue';
|
||||
import {
|
||||
GameUI,
|
||||
SetupComponentOptions,
|
||||
UIComponentProps
|
||||
} from '@motajs/system';
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
FULL_LOC,
|
||||
LOAD_BYTE_HEIGHT,
|
||||
LOAD_BYTE_LENGTH,
|
||||
LOAD_BYTE_LINE_WIDTH,
|
||||
LOAD_FONT_COLOR,
|
||||
LOAD_LOADED_COLOR,
|
||||
LOAD_TASK_CENTER_HEIGHT,
|
||||
LOAD_TASK_LINE_WIDTH,
|
||||
LOAD_TASK_RADIUS,
|
||||
LOAD_UNLOADED_COLOR,
|
||||
MAIN_WIDTH
|
||||
} from '../shared';
|
||||
import { ElementLocator, Font, MotaOffscreenCanvas2D } from '@motajs/render';
|
||||
import { transitioned } from '../use';
|
||||
import { cosh, CurveMode, linear } from '@motajs/animate';
|
||||
import { loader } from '@user/client-base';
|
||||
import { clamp } from 'lodash-es';
|
||||
import { sleep } from '@motajs/common';
|
||||
import { loading } from '@user/data-base';
|
||||
import { GameTitleUI } from './title';
|
||||
|
||||
export interface ILoadProps extends UIComponentProps, DefaultProps {}
|
||||
|
||||
const loadSceneProps = {
|
||||
props: ['controller', 'instance']
|
||||
} satisfies SetupComponentOptions<ILoadProps>;
|
||||
|
||||
export const LoadScene = defineComponent<ILoadProps>(props => {
|
||||
const taskFont = new Font('Verdana', 24);
|
||||
const byteFont = new Font('Verdana', 12);
|
||||
|
||||
/** 当前加载进度 */
|
||||
const taskProgress = transitioned(0, 500, cosh(2, CurveMode.EaseOut))!;
|
||||
const byteProgress = transitioned(0, 500, cosh(2, CurveMode.EaseOut))!;
|
||||
const alpha = transitioned(1, 400, linear())!;
|
||||
|
||||
// 两个进度条的位置
|
||||
const taskLoc: ElementLocator = [
|
||||
MAIN_WIDTH / 2,
|
||||
LOAD_TASK_CENTER_HEIGHT,
|
||||
LOAD_TASK_RADIUS * 2 + LOAD_TASK_LINE_WIDTH * 2,
|
||||
LOAD_TASK_RADIUS * 2 + LOAD_TASK_LINE_WIDTH * 2,
|
||||
0.5,
|
||||
0.5
|
||||
];
|
||||
const byteLoc: ElementLocator = [
|
||||
MAIN_WIDTH / 2,
|
||||
LOAD_BYTE_HEIGHT,
|
||||
LOAD_BYTE_LENGTH + LOAD_BYTE_LINE_WIDTH,
|
||||
LOAD_BYTE_LINE_WIDTH * 2 + byteFont.size,
|
||||
0.5,
|
||||
0.5
|
||||
];
|
||||
|
||||
const loadEnd = async () => {
|
||||
loading.emit('loaded');
|
||||
alpha.set(0);
|
||||
await sleep(400);
|
||||
props.controller.closeAll();
|
||||
props.controller.open(GameTitleUI, {});
|
||||
};
|
||||
|
||||
const startLoad = async () => {
|
||||
loader.initSystemLoadTask();
|
||||
loader.load().then(() => {
|
||||
loadEnd();
|
||||
});
|
||||
for await (const _ of loader.progress) {
|
||||
taskProgress.set(loader.progress.getLoadedTasks());
|
||||
byteProgress.set(loader.progress.getLoadedByte());
|
||||
}
|
||||
};
|
||||
|
||||
// 开始加载
|
||||
startLoad();
|
||||
|
||||
/** 渲染加载任务进度 */
|
||||
const renderTaskList = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineWidth = LOAD_TASK_LINE_WIDTH;
|
||||
ctx.font = taskFont.string();
|
||||
const loaded = loader.progress.getLoadedTasks();
|
||||
const total = loader.progress.getAddedTasks();
|
||||
// 这里使用渐变参数,因为要有动画效果
|
||||
const progress = clamp(taskProgress.value / total, 0, 1);
|
||||
const cx = taskLoc[2]! / 2;
|
||||
const cy = taskLoc[3]! / 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, LOAD_TASK_RADIUS, 0, Math.PI * 2);
|
||||
ctx.strokeStyle = LOAD_UNLOADED_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
const end = progress * Math.PI * 2 - Math.PI / 2;
|
||||
ctx.arc(cx, cy, LOAD_TASK_RADIUS, -Math.PI / 2, end);
|
||||
ctx.strokeStyle = LOAD_LOADED_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = LOAD_FONT_COLOR;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(`${loaded} / ${total}`, cx, cy + 3);
|
||||
};
|
||||
|
||||
/** 渲染加载字节进度 */
|
||||
const renderByteList = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineWidth = LOAD_BYTE_LINE_WIDTH;
|
||||
ctx.font = byteFont.string();
|
||||
const total = loader.progress.getTotalByte();
|
||||
const loaded = loader.progress.getLoadedByte();
|
||||
// 这里使用渐变参数,因为要有动画效果
|
||||
const progress = clamp(byteProgress.value / total, 0, 1);
|
||||
const sx = LOAD_BYTE_LINE_WIDTH;
|
||||
const sy = byteFont.size + LOAD_BYTE_LINE_WIDTH;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx, sy);
|
||||
ctx.lineTo(sx + LOAD_BYTE_LENGTH, sy);
|
||||
ctx.strokeStyle = LOAD_UNLOADED_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sx, sy);
|
||||
ctx.lineTo(sx + progress * LOAD_BYTE_LENGTH, sy);
|
||||
ctx.strokeStyle = LOAD_LOADED_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.textAlign = 'right';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.fillStyle = LOAD_FONT_COLOR;
|
||||
const loadedMB = (loaded / 2 ** 20).toFixed(2);
|
||||
const totalMB = (total / 2 ** 20).toFixed(2);
|
||||
const percent = loader.progress.getByteRatio() * 100;
|
||||
ctx.fillText(
|
||||
`${loadedMB}MB / ${totalMB}MB | ${percent.toFixed(2)}%`,
|
||||
byteLoc[2]! - LOAD_BYTE_LINE_WIDTH,
|
||||
byteLoc[3]! - LOAD_BYTE_LINE_WIDTH * 2
|
||||
);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<container loc={FULL_LOC} alpha={alpha.ref.value}>
|
||||
<custom
|
||||
loc={taskLoc}
|
||||
render={renderTaskList}
|
||||
bindings={[taskProgress.ref]}
|
||||
nocache
|
||||
/>
|
||||
<custom
|
||||
loc={byteLoc}
|
||||
render={renderByteList}
|
||||
bindings={[byteProgress.ref]}
|
||||
nocache
|
||||
/>
|
||||
</container>
|
||||
);
|
||||
}, loadSceneProps);
|
||||
|
||||
export const LoadSceneUI = new GameUI('load-scene', LoadScene);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GameUI, SetupComponentOptions } from '@motajs/system';
|
||||
import { computed, ComputedRef, defineComponent, shallowReactive } from 'vue';
|
||||
import { TextContent } from '../components';
|
||||
import { ElementLocator, Font, SizedCanvasImageSource } from '@motajs/render';
|
||||
import { ElementLocator, Font, ITexture } from '@motajs/render';
|
||||
import { MixedToolbar, ReplayingStatus } from './toolbar';
|
||||
import { openViewMap } from './viewmap';
|
||||
import { mainUIController } from './controller';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
STATUS_BAR_WIDTH
|
||||
} from '../shared';
|
||||
import { DefaultProps } from '@motajs/render-vue';
|
||||
import { materials } from '@user/client-base';
|
||||
|
||||
export interface ILeftHeroStatus {
|
||||
/** 楼层 id */
|
||||
@@ -69,27 +70,27 @@ export interface IRightHeroStatus {
|
||||
|
||||
interface StatusInfo {
|
||||
/** 图标 */
|
||||
icon: SizedCanvasImageSource;
|
||||
readonly icon: ITexture | null;
|
||||
/** 属性值,经过格式化 */
|
||||
value: ComputedRef<string>;
|
||||
readonly value: ComputedRef<string>;
|
||||
/** 字体 */
|
||||
font: Font;
|
||||
readonly font: Font;
|
||||
/** 文字颜色 */
|
||||
color: CanvasStyle;
|
||||
readonly color: CanvasStyle;
|
||||
}
|
||||
|
||||
interface KeyLikeItem {
|
||||
/** 属性值,经过格式化 */
|
||||
value: ComputedRef<string>;
|
||||
readonly value: ComputedRef<string>;
|
||||
/** 字体 */
|
||||
font: Font;
|
||||
readonly font: Font;
|
||||
/** 文字颜色 */
|
||||
color: CanvasStyle;
|
||||
readonly color: CanvasStyle;
|
||||
}
|
||||
|
||||
interface KeyLikeInfo {
|
||||
/** 这一行包含的内容 */
|
||||
items: KeyLikeItem[];
|
||||
readonly items: KeyLikeItem[];
|
||||
}
|
||||
|
||||
interface StatusBarProps<T> extends DefaultProps {
|
||||
@@ -115,15 +116,15 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||
/** 状态属性的开始纵坐标 */
|
||||
const STATUS_Y = TITLE_HEIGHT + STATUS_PAD;
|
||||
|
||||
// 可以换成 core.material.images.images['xxx.png'] 来使用全塔属性注册的图片
|
||||
const hpIcon = core.statusBar.icons.hp;
|
||||
const atkIcon = core.statusBar.icons.atk;
|
||||
const defIcon = core.statusBar.icons.def;
|
||||
const mdefIcon = core.statusBar.icons.mdef;
|
||||
const moneyIcon = core.statusBar.icons.money;
|
||||
const expIcon = core.statusBar.icons.exp;
|
||||
const manaIcon = core.statusBar.icons.mana;
|
||||
const lvIcon = core.statusBar.icons.lv;
|
||||
// 可以换成 materials.getImageByAlias('xxx.png') 来使用全塔属性注册的图片
|
||||
const hpIcon = materials.getImageByAlias('icon-hp');
|
||||
const atkIcon = materials.getImageByAlias('icon-atk');
|
||||
const defIcon = materials.getImageByAlias('icon-def');
|
||||
const mdefIcon = materials.getImageByAlias('icon-mdef');
|
||||
const moneyIcon = materials.getImageByAlias('icon-money');
|
||||
const expIcon = materials.getImageByAlias('icon-exp');
|
||||
const manaIcon = materials.getImageByAlias('icon-mana');
|
||||
const lvIcon = materials.getImageByAlias('icon-lv');
|
||||
|
||||
const s = p.status;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
HALF_WIDTH,
|
||||
MAIN_HEIGHT,
|
||||
MAIN_WIDTH,
|
||||
TITLE_BACKGROUND_IMAGE,
|
||||
TITLE_FILL,
|
||||
TITLE_STROKE,
|
||||
TITLE_STROKE_WIDTH,
|
||||
@@ -34,6 +35,7 @@ import { MainSceneUI } from './main';
|
||||
import { adjustCover } from '../utils';
|
||||
import { cosh, CurveMode, linear } from '@motajs/animate';
|
||||
import { sleep } from '@motajs/common';
|
||||
import { materials } from '@user/client-base';
|
||||
|
||||
const enum TitleButton {
|
||||
StartGame,
|
||||
@@ -62,12 +64,12 @@ const gameTitleProps = {
|
||||
} satisfies SetupComponentOptions<GameTitleProps>;
|
||||
|
||||
export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
const bg = core.material.images.images['bg.jpg'];
|
||||
const bg = materials.getImageByAlias(TITLE_BACKGROUND_IMAGE);
|
||||
|
||||
//#region 计算背景图
|
||||
const [width, height] = adjustCover(
|
||||
bg.width,
|
||||
bg.height,
|
||||
bg?.width ?? MAIN_WIDTH,
|
||||
bg?.height ?? MAIN_HEIGHT,
|
||||
MAIN_WIDTH,
|
||||
MAIN_HEIGHT
|
||||
);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { MAIN_HEIGHT, FULL_LOC, POP_BOX_WIDTH, CENTER_LOC } from '../shared';
|
||||
import { openReplay, openSettings } from './settings';
|
||||
import { openViewMap } from './viewmap';
|
||||
import { DefaultProps } from '@motajs/render-vue';
|
||||
import { materials } from '@user/client-base';
|
||||
|
||||
interface ToolbarProps extends DefaultProps {
|
||||
loc?: ElementLocator;
|
||||
@@ -73,15 +74,15 @@ export const PlayingToolbar = defineComponent<
|
||||
ToolbarEmits,
|
||||
keyof ToolbarEmits
|
||||
>((props, { emit }) => {
|
||||
const bookIcon = core.statusBar.icons.book;
|
||||
const flyIcon = core.statusBar.icons.fly;
|
||||
const toolIcon = core.statusBar.icons.toolbox;
|
||||
const equipIcon = core.statusBar.icons.equipbox;
|
||||
const keyIcon = core.statusBar.icons.keyboard;
|
||||
const shopIcon = core.statusBar.icons.shop;
|
||||
const saveIcon = core.statusBar.icons.save;
|
||||
const loadIcon = core.statusBar.icons.load;
|
||||
const setIcon = core.statusBar.icons.settings;
|
||||
const bookIcon = materials.getImageByAlias('icon-book');
|
||||
const flyIcon = materials.getImageByAlias('icon-fly');
|
||||
const toolIcon = materials.getImageByAlias('icon-toolbox');
|
||||
const equipIcon = materials.getImageByAlias('icon-equipbox');
|
||||
const keyIcon = materials.getImageByAlias('icon-keyboard');
|
||||
const shopIcon = materials.getImageByAlias('icon-shop');
|
||||
const saveIcon = materials.getImageByAlias('icon-save');
|
||||
const loadIcon = materials.getImageByAlias('icon-load');
|
||||
const setIcon = materials.getImageByAlias('icon-settings');
|
||||
|
||||
const iconFont = new Font('Verdana', 12);
|
||||
|
||||
@@ -170,8 +171,8 @@ const replayingProps = {
|
||||
export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
|
||||
const status = props.status;
|
||||
|
||||
const bookIcon = core.statusBar.icons.book;
|
||||
const saveIcon = core.statusBar.icons.save;
|
||||
const bookIcon = materials.getImageByAlias('icon-book');
|
||||
const saveIcon = materials.getImageByAlias('icon-save');
|
||||
const font1 = Font.defaults({ size: 16 });
|
||||
const font2 = new Font('Verdana', 12);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { clamp } from 'lodash-es';
|
||||
|
||||
export class SunWeather extends Weather<CustomRenderItem> {
|
||||
/** 阳光图片 */
|
||||
private image: HTMLImageElement | null = null;
|
||||
private image: ImageBitmap | null = null;
|
||||
/** 阳光图片的不透明度 */
|
||||
private alpha: number = 0;
|
||||
/** 阳光的最大不透明度 */
|
||||
|
||||
Reference in new issue
Block a user