mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-08-05 16:41:48 +08:00
Compare commits
No commits in common. "f29c76ffc9e4dc9b2a8aa6ca8a8d08bb126799b9" and "b48dfae2112e482943059d3b7ff168b593ede3b0" have entirely different histories.
f29c76ffc9
...
b48dfae211
1
.gitignore
vendored
1
.gitignore
vendored
@ -48,4 +48,3 @@ script/special.ts
|
|||||||
script/people.ts
|
script/people.ts
|
||||||
docs/
|
docs/
|
||||||
user.ts
|
user.ts
|
||||||
.antlr
|
|
||||||
|
@ -13,4 +13,4 @@ public/editor.html
|
|||||||
keyCodes.ts
|
keyCodes.ts
|
||||||
src/core/main/setting.ts
|
src/core/main/setting.ts
|
||||||
src/core/fx/shadow.ts
|
src/core/fx/shadow.ts
|
||||||
dist/
|
src/core/fx/shadow_upload.js
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -1,9 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": ["Vue.volar"]
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode",
|
|
||||||
"vue.volar",
|
|
||||||
"slevesque.shader",
|
|
||||||
"tobermory.es6-string-html"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
import eslint from '@eslint/js';
|
|
||||||
import globals from 'globals';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import eslintPluginVue from 'eslint-plugin-vue';
|
|
||||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{
|
|
||||||
ignores: ['node_modules', 'dist', 'public']
|
|
||||||
},
|
|
||||||
eslint.configs.recommended,
|
|
||||||
...tseslint.configs.recommended,
|
|
||||||
...eslintPluginVue.configs['flat/recommended'],
|
|
||||||
{
|
|
||||||
files: ['**/*.{js,mjs,cjs,vue}'],
|
|
||||||
rules: {
|
|
||||||
'no-console': 'warn'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
wx: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.vue'],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
parser: tseslint.parser,
|
|
||||||
ecmaVersion: 'latest',
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'vue/no-mutating-props': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
shallowOnly: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx,vue}'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-empty-object-type': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
args: 'all',
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
caughtErrors: 'all',
|
|
||||||
caughtErrorsIgnorePattern: '^_',
|
|
||||||
destructuredArrayIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
ignoreRestSiblings: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-namespace': 'off',
|
|
||||||
'@typescript-eslint/no-this-alias': 'off'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
eslintPluginPrettierRecommended
|
|
||||||
);
|
|
@ -17,7 +17,6 @@
|
|||||||
"@ant-design/icons-vue": "^6.1.0",
|
"@ant-design/icons-vue": "^6.1.0",
|
||||||
"@emotion/css": "^11.13.0",
|
"@emotion/css": "^11.13.0",
|
||||||
"@vueuse/core": "^10.11.1",
|
"@vueuse/core": "^10.11.1",
|
||||||
"@wasm-audio-decoders/ogg-vorbis": "^0.1.16",
|
|
||||||
"anon-tokyo": "0.0.0-alpha.0",
|
"anon-tokyo": "0.0.0-alpha.0",
|
||||||
"ant-design-vue": "^3.2.20",
|
"ant-design-vue": "^3.2.20",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
@ -53,20 +52,15 @@
|
|||||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"compressing": "^1.10.1",
|
"compressing": "^1.10.1",
|
||||||
"eslint": "^9.17.0",
|
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
|
||||||
"fontmin": "^0.9.9",
|
"fontmin": "^0.9.9",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"globals": "^15.14.0",
|
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"postcss-preset-env": "^9.6.0",
|
"postcss-preset-env": "^9.6.0",
|
||||||
"rollup": "^3.29.4",
|
"rollup": "^3.29.4",
|
||||||
"terser": "^5.31.6",
|
"terser": "^5.31.6",
|
||||||
"tsx": "^4.17.0",
|
"tsx": "^4.17.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"typescript-eslint": "^8.18.2",
|
|
||||||
"unplugin-vue-components": "^0.22.12",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
"vite": "^4.5.3",
|
"vite": "^4.5.3",
|
||||||
"vue-tsc": "^2.1.6",
|
"vue-tsc": "^2.1.6",
|
||||||
|
853
pnpm-lock.yaml
853
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -855,7 +855,7 @@ actions.prototype._sys_keyDownCtrl = function () {
|
|||||||
core.status.event.id == 'action' &&
|
core.status.event.id == 'action' &&
|
||||||
core.status.event.data.type == 'text'
|
core.status.event.data.type == 'text'
|
||||||
) {
|
) {
|
||||||
this._clickAction_text();
|
core.doAction();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -1117,25 +1117,16 @@ actions.prototype._clickAction_text = function () {
|
|||||||
// 正在淡入淡出的话不执行
|
// 正在淡入淡出的话不执行
|
||||||
if (core.status.event.animateUI) return;
|
if (core.status.event.animateUI) return;
|
||||||
|
|
||||||
const Store = Mota.require('module', 'Render').TextboxStore;
|
var data = core.clone(core.status.event.data.current);
|
||||||
const store = Store.get('main-textbox');
|
if (typeof data == 'string') data = { type: 'text', text: data };
|
||||||
|
|
||||||
// var data = core.clone(core.status.event.data.current);
|
|
||||||
// if (typeof data == 'string') data = { type: 'text', text: data };
|
|
||||||
|
|
||||||
// 打字机效果显示全部文字
|
// 打字机效果显示全部文字
|
||||||
if (store.typing) {
|
if (core.status.event.interval != null) {
|
||||||
store.endType();
|
data.showAll = true;
|
||||||
|
core.insertAction(data);
|
||||||
|
core.doAction();
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
store.hide();
|
|
||||||
}
|
}
|
||||||
// if (core.status.event.interval != null) {
|
|
||||||
// data.showAll = true;
|
|
||||||
// core.insertAction(data);
|
|
||||||
// core.doAction();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!data.code) {
|
if (!data.code) {
|
||||||
core.ui._animateUI('hide', null, core.doAction);
|
core.ui._animateUI('hide', null, core.doAction);
|
||||||
|
@ -1553,70 +1553,24 @@ events.prototype.__action_doAsyncFunc = function (isAsync, func) {
|
|||||||
|
|
||||||
events.prototype._action_text = function (data, x, y, prefix) {
|
events.prototype._action_text = function (data, x, y, prefix) {
|
||||||
if (this.__action_checkReplaying()) return;
|
if (this.__action_checkReplaying()) return;
|
||||||
const Store = Mota.require('module', 'Render').TextboxStore;
|
data.text = core.replaceText(data.text, prefix);
|
||||||
const store = Store.get('main-textbox');
|
var ctx = data.code ? '__text__' + data.code : null;
|
||||||
const { text } = data;
|
data.ctx = ctx;
|
||||||
let title = '';
|
if (core.getContextByName(ctx) && !data.showAll) {
|
||||||
let inTitle = false;
|
core.ui._animateUI('hide', ctx, function () {
|
||||||
let titleStartIndex = 0;
|
core.ui.drawTextBox(data.text, data);
|
||||||
let titleEndIndex = 0;
|
core.ui._animateUI('show', ctx, function () {
|
||||||
for (let i = 0; i < text.length; i++) {
|
if (data.async) core.doAction();
|
||||||
const char = text[i];
|
});
|
||||||
|
});
|
||||||
if (inTitle) {
|
return;
|
||||||
if (char === '\\' && text[i + 1] === ']') {
|
}
|
||||||
title += ']';
|
core.ui.drawTextBox(data.text, data);
|
||||||
i++;
|
if (!data.showAll) {
|
||||||
} else if (char === ']') {
|
core.ui._animateUI('show', ctx, function () {
|
||||||
inTitle = false;
|
if (data.async) core.doAction();
|
||||||
titleEndIndex = i + 1;
|
});
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
title += char;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '\t' && text[i + 1] === '[') {
|
|
||||||
inTitle = true;
|
|
||||||
titleStartIndex = i;
|
|
||||||
// 跳转至方括号内
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '\\' && text[i + 1] === 't' && text[i + 2] === '[') {
|
|
||||||
inTitle = true;
|
|
||||||
titleStartIndex = i;
|
|
||||||
// 跳转至方括号内
|
|
||||||
i += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const showTitle =
|
|
||||||
text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
|
|
||||||
store.show();
|
|
||||||
store.modify({ text: showTitle, title });
|
|
||||||
|
|
||||||
// data.text = core.replaceText(data.text, prefix);
|
|
||||||
// var ctx = data.code ? '__text__' + data.code : null;
|
|
||||||
// data.ctx = ctx;
|
|
||||||
// if (core.getContextByName(ctx) && !data.showAll) {
|
|
||||||
// core.ui._animateUI('hide', ctx, function () {
|
|
||||||
// core.ui.drawTextBox(data.text, data);
|
|
||||||
// core.ui._animateUI('show', ctx, function () {
|
|
||||||
// if (data.async) core.doAction();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// core.ui.drawTextBox(data.text, data);
|
|
||||||
// if (!data.showAll) {
|
|
||||||
// core.ui._animateUI('show', ctx, function () {
|
|
||||||
// if (data.async) core.doAction();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
events.prototype._action_moveTextBox = function (data, x, y, prefix) {
|
events.prototype._action_moveTextBox = function (data, x, y, prefix) {
|
||||||
|
@ -22,7 +22,7 @@ main.floors.snowShop=
|
|||||||
"而且,一共就只有三件装备("
|
"而且,一共就只有三件装备("
|
||||||
],
|
],
|
||||||
"7,5": [
|
"7,5": [
|
||||||
"\t[商店老板]请随意挑选",
|
"\t[商店老板,N636]\b[up,7,5]请随意挑选",
|
||||||
{
|
{
|
||||||
"type": "openShop",
|
"type": "openShop",
|
||||||
"id": "snowShop",
|
"id": "snowShop",
|
||||||
|
@ -76,7 +76,6 @@ import './render/index';
|
|||||||
import * as RenderUtils from './render/utils';
|
import * as RenderUtils from './render/utils';
|
||||||
import '@/module';
|
import '@/module';
|
||||||
import { MotaOffscreenCanvas2D } from './fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from './fx/canvas2d';
|
||||||
import { TextboxStore } from './render/index';
|
|
||||||
|
|
||||||
// ----- 类注册
|
// ----- 类注册
|
||||||
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
||||||
@ -164,8 +163,7 @@ Mota.register('module', 'Render', {
|
|||||||
LayerGroupFloorBinder,
|
LayerGroupFloorBinder,
|
||||||
Camera,
|
Camera,
|
||||||
MotaOffscreenCanvas2D,
|
MotaOffscreenCanvas2D,
|
||||||
Utils: RenderUtils,
|
Utils: RenderUtils
|
||||||
TextboxStore
|
|
||||||
});
|
});
|
||||||
Mota.register('module', 'Action', {
|
Mota.register('module', 'Action', {
|
||||||
HeroKeyMover
|
HeroKeyMover
|
||||||
|
@ -16,17 +16,26 @@ import { Transform } from '../transform';
|
|||||||
import { isSetEqual } from '../utils';
|
import { isSetEqual } from '../utils';
|
||||||
import { logger } from '@/core/common/logger';
|
import { logger } from '@/core/common/logger';
|
||||||
import { Sprite } from '../sprite';
|
import { Sprite } from '../sprite';
|
||||||
import { ContainerProps, onTick } from '../renderer';
|
import { onTick } from '../renderer';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { SetupComponentOptions } from './types';
|
import { SetupComponentOptions } from './types';
|
||||||
import EventEmitter from 'eventemitter3';
|
import EventEmitter from 'eventemitter3';
|
||||||
import { Text } from '../preset';
|
import { Container } from '../container';
|
||||||
import {
|
|
||||||
buildFont,
|
export const enum WordBreak {
|
||||||
TextAlign,
|
/** 不换行 */
|
||||||
ITextContentRenderData,
|
None,
|
||||||
WordBreak
|
/** 仅空格和连字符等可换行,CJK 字符可任意换行,默认值 */
|
||||||
} from './textboxHelper';
|
Space,
|
||||||
|
/** 所有字符都可以换行 */
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum TextAlign {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
let testCanvas: MotaOffscreenCanvas2D;
|
let testCanvas: MotaOffscreenCanvas2D;
|
||||||
Mota.require('var', 'loading').once('coreInit', () => {
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
@ -37,9 +46,41 @@ Mota.require('var', 'loading').once('coreInit', () => {
|
|||||||
testCanvas.freeze();
|
testCanvas.freeze();
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface TextContentProps
|
export interface TextContentProps {
|
||||||
extends ContainerProps,
|
text: string;
|
||||||
ITextContentRenderData {}
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
/** 字体 */
|
||||||
|
font?: string;
|
||||||
|
/** 是否持续上一次的文本,开启后,如果修改后的文本以修改前的文本为开头,那么会继续播放而不会从头播放 */
|
||||||
|
keepLast?: boolean;
|
||||||
|
/** 打字机时间间隔,即两个字出现之间相隔多长时间 */
|
||||||
|
interval?: number;
|
||||||
|
/** 行高 */
|
||||||
|
lineHeight?: number;
|
||||||
|
/** 分词规则 */
|
||||||
|
wordBreak?: WordBreak;
|
||||||
|
/** 文字对齐方式 */
|
||||||
|
textAlign?: TextAlign;
|
||||||
|
/** 行首忽略字符,即不会出现在行首的字符 */
|
||||||
|
ignoreLineStart?: Iterable<string>;
|
||||||
|
/** 行尾忽略字符,即不会出现在行尾的字符 */
|
||||||
|
ignoreLineEnd?: Iterable<string>;
|
||||||
|
/** 会被分词规则识别的分词字符 */
|
||||||
|
breakChars?: Iterable<string>;
|
||||||
|
/** 填充样式 */
|
||||||
|
fillStyle?: CanvasStyle;
|
||||||
|
/** 描边样式 */
|
||||||
|
strokeStyle?: CanvasStyle;
|
||||||
|
/** 线宽 */
|
||||||
|
strokeWidth?: number;
|
||||||
|
/** 是否填充 */
|
||||||
|
fill?: boolean;
|
||||||
|
/** 是否描边 */
|
||||||
|
stroke?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type TextContentEmits = {
|
export type TextContentEmits = {
|
||||||
typeEnd: () => void;
|
typeEnd: () => void;
|
||||||
@ -62,8 +103,9 @@ interface TextContentData {
|
|||||||
|
|
||||||
interface TextContentRenderable {
|
interface TextContentRenderable {
|
||||||
x: number;
|
x: number;
|
||||||
|
y: number;
|
||||||
/** 行高,为0时表示两行间为默认行距 */
|
/** 行高,为0时表示两行间为默认行距 */
|
||||||
lineHeight: number;
|
height: number;
|
||||||
/** 这一行文字的高度,即 measureText 算出的高度 */
|
/** 这一行文字的高度,即 measureText 算出的高度 */
|
||||||
textHeight: number;
|
textHeight: number;
|
||||||
/** 这一行的文字 */
|
/** 这一行的文字 */
|
||||||
@ -79,10 +121,7 @@ interface TextContentRenderable {
|
|||||||
const textContentOptions = {
|
const textContentOptions = {
|
||||||
props: [
|
props: [
|
||||||
'breakChars',
|
'breakChars',
|
||||||
'fontFamily',
|
'font',
|
||||||
'fontSize',
|
|
||||||
'fontBold',
|
|
||||||
'fontItalic',
|
|
||||||
'height',
|
'height',
|
||||||
'ignoreLineEnd',
|
'ignoreLineEnd',
|
||||||
'ignoreLineStart',
|
'ignoreLineStart',
|
||||||
@ -99,8 +138,7 @@ const textContentOptions = {
|
|||||||
'fillStyle',
|
'fillStyle',
|
||||||
'strokeStyle',
|
'strokeStyle',
|
||||||
'strokeWidth',
|
'strokeWidth',
|
||||||
'stroke',
|
'stroke'
|
||||||
'showAll'
|
|
||||||
],
|
],
|
||||||
emits: ['typeEnd', 'typeStart']
|
emits: ['typeEnd', 'typeStart']
|
||||||
} satisfies SetupComponentOptions<
|
} satisfies SetupComponentOptions<
|
||||||
@ -117,18 +155,14 @@ export const TextContent = defineComponent<
|
|||||||
if (props.width && props.width <= 0) {
|
if (props.width && props.width <= 0) {
|
||||||
logger.warn(41, String(props.width));
|
logger.warn(41, String(props.width));
|
||||||
}
|
}
|
||||||
const renderData: Required<ITextContentRenderData> = shallowReactive({
|
const renderData: Required<TextContentProps> = {
|
||||||
text: props.text,
|
text: props.text,
|
||||||
textAlign: props.textAlign ?? TextAlign.Left,
|
textAlign: props.textAlign ?? TextAlign.Left,
|
||||||
x: props.x ?? 0,
|
x: props.x ?? 0,
|
||||||
y: props.y ?? 0,
|
y: props.y ?? 0,
|
||||||
width: !props.width || props.width <= 0 ? 200 : props.width,
|
width: !props.width || props.width <= 0 ? 200 : props.width,
|
||||||
height: props.height ?? 200,
|
height: props.height ?? 200,
|
||||||
fontFamily:
|
font: props.font ?? core.status.globalAttribute?.font ?? '16px Verdana',
|
||||||
props.fontFamily ?? core.status.globalAttribute?.font ?? 'Verdana',
|
|
||||||
fontSize: props.fontSize ?? 16,
|
|
||||||
fontBold: props.fontWeight ?? false,
|
|
||||||
fontItalic: props.fontItalic ?? false,
|
|
||||||
ignoreLineEnd: props.ignoreLineEnd ?? new Set(),
|
ignoreLineEnd: props.ignoreLineEnd ?? new Set(),
|
||||||
ignoreLineStart: props.ignoreLineStart ?? new Set(),
|
ignoreLineStart: props.ignoreLineStart ?? new Set(),
|
||||||
keepLast: props.keepLast ?? false,
|
keepLast: props.keepLast ?? false,
|
||||||
@ -140,9 +174,8 @@ export const TextContent = defineComponent<
|
|||||||
strokeStyle: props.strokeStyle ?? 'transparent',
|
strokeStyle: props.strokeStyle ?? 'transparent',
|
||||||
fill: props.fill ?? true,
|
fill: props.fill ?? true,
|
||||||
stroke: props.stroke ?? false,
|
stroke: props.stroke ?? false,
|
||||||
strokeWidth: props.strokeWidth ?? 2,
|
strokeWidth: props.strokeWidth ?? 2
|
||||||
showAll: props.showAll ?? false
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const ensureProps = () => {
|
const ensureProps = () => {
|
||||||
for (const [key, value] of Object.entries(props)) {
|
for (const [key, value] of Object.entries(props)) {
|
||||||
@ -155,7 +188,7 @@ export const TextContent = defineComponent<
|
|||||||
renderData.width = value;
|
renderData.width = value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// @ts-expect-error may use spread?
|
// @ts-ignore
|
||||||
renderData[key] = value;
|
renderData[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,12 +200,7 @@ export const TextContent = defineComponent<
|
|||||||
return {
|
return {
|
||||||
text: renderData.text,
|
text: renderData.text,
|
||||||
width: renderData.width!,
|
width: renderData.width!,
|
||||||
font: buildFont(
|
font: renderData.font!,
|
||||||
renderData.fontFamily,
|
|
||||||
renderData.fontSize,
|
|
||||||
renderData.fontWeight,
|
|
||||||
renderData.fontItalic
|
|
||||||
),
|
|
||||||
wordBreak: renderData.wordBreak!,
|
wordBreak: renderData.wordBreak!,
|
||||||
ignoreLineStart: new Set(renderData.ignoreLineStart),
|
ignoreLineStart: new Set(renderData.ignoreLineStart),
|
||||||
ignoreLineEnd: new Set(renderData.ignoreLineEnd),
|
ignoreLineEnd: new Set(renderData.ignoreLineEnd),
|
||||||
@ -214,11 +242,9 @@ export const TextContent = defineComponent<
|
|||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
const char =
|
const char =
|
||||||
Math.floor((time - startTime) / renderData.interval!) + fromChar;
|
Math.floor((time - startTime) / renderData.interval!) + fromChar;
|
||||||
if (!isFinite(char) || renderData.showAll) {
|
if (!isFinite(char)) {
|
||||||
renderable.forEach(v => (v.pointer = v.text.length));
|
renderable.forEach(v => (v.pointer = v.text.length));
|
||||||
needUpdate = false;
|
needUpdate = false;
|
||||||
linePointer = dirtyIndex.length;
|
|
||||||
emit('typeEnd');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
while (linePointer < dirtyIndex.length) {
|
while (linePointer < dirtyIndex.length) {
|
||||||
@ -233,7 +259,6 @@ export const TextContent = defineComponent<
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linePointer >= dirtyIndex.length) {
|
if (linePointer >= dirtyIndex.length) {
|
||||||
needUpdate = false;
|
needUpdate = false;
|
||||||
renderable.forEach(v => (v.pointer = v.text.length));
|
renderable.forEach(v => (v.pointer = v.text.length));
|
||||||
@ -252,28 +277,26 @@ export const TextContent = defineComponent<
|
|||||||
transform: Transform
|
transform: Transform
|
||||||
) => {
|
) => {
|
||||||
const ctx = canvas.ctx;
|
const ctx = canvas.ctx;
|
||||||
// ctx.font = renderData.font;
|
ctx.font = renderData.font;
|
||||||
ctx.fillStyle = renderData.fillStyle;
|
ctx.fillStyle = renderData.fillStyle;
|
||||||
ctx.strokeStyle = renderData.strokeStyle;
|
ctx.strokeStyle = renderData.strokeStyle;
|
||||||
ctx.lineWidth = renderData.strokeWidth;
|
ctx.lineWidth = renderData.strokeWidth;
|
||||||
|
|
||||||
let y = renderable[0]?.textHeight ?? 0;
|
|
||||||
renderable.forEach(v => {
|
renderable.forEach(v => {
|
||||||
if (v.pointer === 0) return;
|
if (v.pointer === 0) return;
|
||||||
const text = v.text.slice(0, v.pointer);
|
const text = v.text.slice(0, v.pointer);
|
||||||
if (renderData.textAlign === TextAlign.Left) {
|
if (renderData.textAlign === TextAlign.Left) {
|
||||||
if (renderData.stroke) ctx.strokeText(text, v.x, y);
|
if (renderData.stroke) ctx.strokeText(text, v.x, v.y);
|
||||||
if (renderData.fill) ctx.fillText(text, v.x, y);
|
if (renderData.fill) ctx.fillText(text, v.x, v.y);
|
||||||
} else if (renderData.textAlign === TextAlign.Center) {
|
} else if (renderData.textAlign === TextAlign.Center) {
|
||||||
const x = (renderData.width - v.x) / 2 + v.x;
|
const x = (renderData.width - v.x) / 2 + v.x;
|
||||||
if (renderData.stroke) ctx.strokeText(text, x, y);
|
if (renderData.stroke) ctx.strokeText(text, x, v.y);
|
||||||
if (renderData.fill) ctx.fillText(text, x, y);
|
if (renderData.fill) ctx.fillText(text, x, v.y);
|
||||||
} else {
|
} else {
|
||||||
const x = renderData.width;
|
const x = renderData.width;
|
||||||
if (renderData.stroke) ctx.strokeText(text, x, y);
|
if (renderData.stroke) ctx.strokeText(text, x, v.y);
|
||||||
if (renderData.fill) ctx.fillText(text, x, y);
|
if (renderData.fill) ctx.fillText(text, x, v.y);
|
||||||
}
|
}
|
||||||
y += v.textHeight + v.lineHeight;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -290,52 +313,54 @@ export const TextContent = defineComponent<
|
|||||||
index: number,
|
index: number,
|
||||||
from: number
|
from: number
|
||||||
) => {
|
) => {
|
||||||
// renderable.splice(index);
|
renderable.splice(index);
|
||||||
// dirtyIndex.splice(0);
|
dirtyIndex.splice(0);
|
||||||
// dirtyIndex.push(index);
|
dirtyIndex.push(index);
|
||||||
// // 初始化渲染
|
// 初始化渲染
|
||||||
// linePointer = 0;
|
linePointer = 0;
|
||||||
// startTime = Date.now();
|
startTime = Date.now();
|
||||||
// fromChar = from;
|
fromChar = from;
|
||||||
// needUpdate = true;
|
needUpdate = true;
|
||||||
|
|
||||||
// let startY = renderable.reduce(
|
let startY = renderable.reduce(
|
||||||
// (prev, curr) => prev + curr.textHeight + curr.lineHeight,
|
(prev, curr) => prev + curr.textHeight + curr.height,
|
||||||
// 0
|
0
|
||||||
// );
|
);
|
||||||
// // 第一个比较特殊,需要特判
|
// 第一个比较特殊,需要特判
|
||||||
// const start = lines[index - 1] ?? 0;
|
const start = lines[index - 1] ?? 0;
|
||||||
// const end = lines[index];
|
const end = lines[index];
|
||||||
// const startPointer = from > start && from < end ? from - start : 0;
|
const startPointer = from > start && from < end ? from - start : 0;
|
||||||
// const height = testHeight(text, renderData.font!);
|
const height = testHeight(text, renderData.font!);
|
||||||
// startY += height;
|
startY += height;
|
||||||
// renderable.push({
|
renderable.push({
|
||||||
// text: text.slice(start, end),
|
text: text.slice(start, end),
|
||||||
// x: 0,
|
x: 0,
|
||||||
// lineHeight: renderData.lineHeight!,
|
y: startY,
|
||||||
// textHeight: height,
|
height: renderData.lineHeight!,
|
||||||
// pointer: startPointer,
|
textHeight: height,
|
||||||
// from: start,
|
pointer: startPointer,
|
||||||
// to: end ?? text.length
|
from: start,
|
||||||
// });
|
to: end
|
||||||
|
});
|
||||||
|
|
||||||
// for (let i = index + 1; i < lines.length; i++) {
|
for (let i = index + 1; i < lines.length; i++) {
|
||||||
// dirtyIndex.push(i);
|
dirtyIndex.push(i);
|
||||||
// const start = lines[i - 1] ?? 0;
|
const start = lines[i - 1] ?? 0;
|
||||||
// const end = lines[i];
|
const end = lines[i];
|
||||||
// const height = testHeight(text, renderData.font!);
|
const height = testHeight(text, renderData.font!);
|
||||||
// startY += height;
|
startY += height;
|
||||||
|
|
||||||
// renderable.push({
|
renderable.push({
|
||||||
// text: text.slice(start, end),
|
text: text.slice(start, end),
|
||||||
// x: 0,
|
x: 0,
|
||||||
// lineHeight: renderData.lineHeight!,
|
y: startY,
|
||||||
// textHeight: height,
|
height: renderData.lineHeight!,
|
||||||
// pointer: 0,
|
textHeight: height,
|
||||||
// from: start,
|
pointer: 0,
|
||||||
// to: end ?? text.length
|
from: start,
|
||||||
// });
|
to: end
|
||||||
// }
|
});
|
||||||
|
}
|
||||||
emit('typeStart');
|
emit('typeStart');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -418,8 +443,9 @@ export const TextContent = defineComponent<
|
|||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<sprite
|
<sprite
|
||||||
{...renderData}
|
|
||||||
ref={spriteElement}
|
ref={spriteElement}
|
||||||
|
hd
|
||||||
|
antiAliasing={true}
|
||||||
x={renderData.x}
|
x={renderData.x}
|
||||||
y={renderData.y}
|
y={renderData.y}
|
||||||
width={renderData.width}
|
width={renderData.width}
|
||||||
@ -430,52 +456,24 @@ export const TextContent = defineComponent<
|
|||||||
};
|
};
|
||||||
}, textContentOptions);
|
}, textContentOptions);
|
||||||
|
|
||||||
export interface TextboxProps extends TextContentProps, ContainerProps {
|
export interface TextboxProps extends TextContentProps {
|
||||||
|
id?: string;
|
||||||
/** 背景颜色 */
|
/** 背景颜色 */
|
||||||
backColor?: CanvasStyle;
|
backColor?: CanvasStyle;
|
||||||
/** 背景 winskin */
|
/** 背景 winskin */
|
||||||
winskin?: string;
|
winskin?: string;
|
||||||
/** 边框与文字间的距离,默认为8 */
|
/** 边框与文字间的距离,默认为8 */
|
||||||
padding?: number;
|
padding?: number;
|
||||||
/** 标题 */
|
|
||||||
title?: string;
|
|
||||||
/** 标题字体 */
|
|
||||||
titleFont?: string;
|
|
||||||
/** 标题填充样式 */
|
|
||||||
titleFill?: CanvasStyle;
|
|
||||||
/** 标题描边样式 */
|
|
||||||
titleStroke?: CanvasStyle;
|
|
||||||
/** 标题文字与边框间的距离,默认为4 */
|
|
||||||
titlePadding?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextboxEmits = TextContentEmits;
|
type TextboxEmits = TextContentEmits;
|
||||||
type TextboxSlots = SlotsType<{
|
type TextboxSlots = SlotsType<{ default: (data: TextboxProps) => VNode[] }>;
|
||||||
default: (data: TextboxProps) => VNode[];
|
|
||||||
title: (data: TextboxProps) => VNode[];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
const textboxOptions = {
|
const textboxOptions = {
|
||||||
props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
|
props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
|
||||||
'backColor',
|
'backColor',
|
||||||
'winskin',
|
'winskin',
|
||||||
'id',
|
'id'
|
||||||
'padding',
|
|
||||||
'alpha',
|
|
||||||
'hidden',
|
|
||||||
'anchorX',
|
|
||||||
'anchorY',
|
|
||||||
'antiAliasing',
|
|
||||||
'cache',
|
|
||||||
'composite',
|
|
||||||
'fall',
|
|
||||||
'hd',
|
|
||||||
'transform',
|
|
||||||
'type',
|
|
||||||
'zIndex',
|
|
||||||
'titleFill',
|
|
||||||
'titleStroke',
|
|
||||||
'titleFont'
|
|
||||||
]),
|
]),
|
||||||
emits: textContentOptions.emits
|
emits: textContentOptions.emits
|
||||||
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
|
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
|
||||||
@ -496,113 +494,24 @@ export const Textbox = defineComponent<
|
|||||||
data.width ??= 200;
|
data.width ??= 200;
|
||||||
data.height ??= 200;
|
data.height ??= 200;
|
||||||
data.id ??= '';
|
data.id ??= '';
|
||||||
data.alpha ??= 1;
|
|
||||||
data.titleFill ??= '#000';
|
|
||||||
data.titleStroke ??= 'transparent';
|
|
||||||
data.titleFont ??= '16px Verdana';
|
|
||||||
data.titlePadding ??= 4;
|
|
||||||
|
|
||||||
const titleElement = ref<Text>();
|
const store = TextboxStore.use(props.id ?? getNextTextboxId(), data);
|
||||||
const titleWidth = ref(data.titlePadding * 2);
|
const hidden = ref(false);
|
||||||
const titleHeight = ref(data.titlePadding * 2);
|
|
||||||
const contentY = computed(() => {
|
|
||||||
const height = titleHeight.value;
|
|
||||||
return data.title ? height : 0;
|
|
||||||
});
|
|
||||||
const contentWidth = computed(() => data.width! - data.padding! * 2);
|
|
||||||
const contentHeight = computed(
|
|
||||||
() => data.height! - data.padding! * 2 - contentY.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const calTitleSize = (text: string) => {
|
|
||||||
if (!titleElement.value) return;
|
|
||||||
const { width, height } = titleElement.value;
|
|
||||||
titleWidth.value = width + data.titlePadding! * 2;
|
|
||||||
titleHeight.value = height + data.titlePadding! * 2;
|
|
||||||
data.title = text;
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(titleElement, (value, old) => {
|
|
||||||
old?.off('setText', calTitleSize);
|
|
||||||
value?.on('setText', calTitleSize);
|
|
||||||
if (value) calTitleSize(value?.text);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
titleElement.value?.off('setText', calTitleSize);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ----- store
|
|
||||||
|
|
||||||
/** 结束打字机 */
|
|
||||||
const storeEmits: TextboxStoreEmits = {
|
|
||||||
endType() {
|
|
||||||
data.showAll = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const store = TextboxStore.use(
|
|
||||||
props.id ?? getNextTextboxId(),
|
|
||||||
data,
|
|
||||||
storeEmits
|
|
||||||
);
|
|
||||||
const hidden = ref(data.hidden);
|
|
||||||
store.on('hide', () => (hidden.value = true));
|
store.on('hide', () => (hidden.value = true));
|
||||||
store.on('show', () => (hidden.value = false));
|
store.on('show', () => (hidden.value = false));
|
||||||
store.on('update', value => {
|
onUpdated(() => {
|
||||||
if (value.title) {
|
for (const [key, value] of Object.entries(props)) {
|
||||||
titleElement.value?.requestBeforeFrame(() => {
|
// @ts-ignore
|
||||||
const { width, height } = titleElement.value!;
|
if (!isNil(value)) data[key] = value;
|
||||||
titleWidth.value = width + data.padding! * 2;
|
|
||||||
titleHeight.value = height + data.padding! * 2;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onTypeStart = () => {
|
const contentWidth = computed(() => data.width! - data.padding! * 2);
|
||||||
store.emitTypeStart();
|
const contentHeight = computed(() => data.height! - data.padding! * 2);
|
||||||
};
|
|
||||||
|
|
||||||
const onTypeEnd = () => {
|
|
||||||
data.showAll = false;
|
|
||||||
store.emitTypeEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<container {...data} hidden={hidden.value} alpha={data.alpha}>
|
<container hidden={hidden.value} id="11111">
|
||||||
{data.title ? (
|
|
||||||
<container
|
|
||||||
zIndex={10}
|
|
||||||
width={titleWidth.value}
|
|
||||||
height={titleHeight.value}
|
|
||||||
>
|
|
||||||
{slots.title ? (
|
|
||||||
slots.title(data)
|
|
||||||
) : props.winskin ? (
|
|
||||||
<winskin></winskin>
|
|
||||||
) : (
|
|
||||||
<g-rect
|
|
||||||
x={0}
|
|
||||||
y={0}
|
|
||||||
width={titleWidth.value}
|
|
||||||
height={titleHeight.value}
|
|
||||||
fillStyle={data.backColor}
|
|
||||||
></g-rect>
|
|
||||||
)}
|
|
||||||
<text
|
|
||||||
ref={titleElement}
|
|
||||||
text={data.title}
|
|
||||||
x={data.titlePadding}
|
|
||||||
y={data.titlePadding}
|
|
||||||
fillStyle={data.titleFill}
|
|
||||||
strokeStyle={data.titleStroke}
|
|
||||||
font={data.titleFont}
|
|
||||||
></text>
|
|
||||||
</container>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
{slots.default ? (
|
{slots.default ? (
|
||||||
slots.default(data)
|
slots.default(data)
|
||||||
) : props.winskin ? (
|
) : props.winskin ? (
|
||||||
@ -612,37 +521,25 @@ export const Textbox = defineComponent<
|
|||||||
// todo
|
// todo
|
||||||
<g-rect
|
<g-rect
|
||||||
x={0}
|
x={0}
|
||||||
y={contentY.value}
|
y={0}
|
||||||
width={data.width ?? 200}
|
width={data.width ?? 200}
|
||||||
height={(data.height ?? 200) - contentY.value}
|
height={data.height ?? 200}
|
||||||
fill
|
fill
|
||||||
fillStyle={data.backColor}
|
fillStyle={data.backColor}
|
||||||
></g-rect>
|
></g-rect>
|
||||||
)}
|
)}
|
||||||
<TextContent
|
<TextContent
|
||||||
{...data}
|
{...data}
|
||||||
hidden={false}
|
x={data.padding}
|
||||||
x={data.padding!}
|
y={data.padding}
|
||||||
y={contentY.value + data.padding!}
|
|
||||||
width={contentWidth.value}
|
width={contentWidth.value}
|
||||||
height={contentHeight.value}
|
height={contentHeight.value}
|
||||||
onTypeEnd={onTypeEnd}
|
|
||||||
onTypeStart={onTypeStart}
|
|
||||||
zIndex={0}
|
|
||||||
showAll={data.showAll}
|
|
||||||
></TextContent>
|
></TextContent>
|
||||||
</container>
|
</container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}, textboxOptions);
|
}, textboxOptions);
|
||||||
|
|
||||||
interface LineSplitData {
|
|
||||||
text: string;
|
|
||||||
font: string;
|
|
||||||
wait: number;
|
|
||||||
icon: AllIds | AllNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontSizeGuessScale = new Map<string, number>([
|
const fontSizeGuessScale = new Map<string, number>([
|
||||||
['px', 1],
|
['px', 1],
|
||||||
['%', 0.2],
|
['%', 0.2],
|
||||||
@ -672,7 +569,7 @@ function splitLines(data: TextContentData) {
|
|||||||
// 对文字二分,然后计算长度
|
// 对文字二分,然后计算长度
|
||||||
const text = data.text;
|
const text = data.text;
|
||||||
const res: number[] = [];
|
const res: number[] = [];
|
||||||
const fontSize = data.font.match(/\s*[\d\.-]+[a-zA-Z%]+\s*/)?.[0].trim();
|
const fontSize = data.font.match(/\s*[\d\.-]+[a-zA-Z%]*\s*/)?.[0].trim();
|
||||||
const unit = fontSize?.match(/[a-zA-Z%]+/)?.[0];
|
const unit = fontSize?.match(/[a-zA-Z%]+/)?.[0];
|
||||||
const guessScale = fontSizeGuessScale.get(unit ?? '') ?? 0.2;
|
const guessScale = fontSizeGuessScale.get(unit ?? '') ?? 0.2;
|
||||||
const guessSize = parseInt(fontSize ?? '1') * guessScale;
|
const guessSize = parseInt(fontSize ?? '1') * guessScale;
|
||||||
@ -824,53 +721,19 @@ function testHeight(text: string, font: string) {
|
|||||||
return ctx.measureText(text).fontBoundingBoxAscent;
|
return ctx.measureText(text).fontBoundingBoxAscent;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextboxStoreEmits {
|
|
||||||
endType: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TextboxStoreEvent {
|
interface TextboxStoreEvent {
|
||||||
update: [value: TextboxProps];
|
update: [value: TextboxProps];
|
||||||
show: [];
|
show: [];
|
||||||
hide: [];
|
hide: [];
|
||||||
typeStart: [];
|
|
||||||
typeEnd: [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
|
export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
|
||||||
static list: Map<string, TextboxStore> = new Map();
|
static list: Map<string, TextboxStore> = new Map();
|
||||||
|
|
||||||
typing: boolean = false;
|
private constructor(private readonly data: TextboxProps) {
|
||||||
|
|
||||||
private constructor(
|
|
||||||
private readonly data: TextboxProps,
|
|
||||||
private readonly emits: TextboxStoreEmits
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始打字,由组件调用,而非组件外调用
|
|
||||||
*/
|
|
||||||
emitTypeStart() {
|
|
||||||
this.typing = true;
|
|
||||||
this.emit('typeStart');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 结束打字,由组件调用,而非组件外调用
|
|
||||||
*/
|
|
||||||
emitTypeEnd() {
|
|
||||||
this.typing = false;
|
|
||||||
this.emit('typeEnd');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 结束打字机的打字
|
|
||||||
*/
|
|
||||||
endType() {
|
|
||||||
this.emits.endType();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改渲染数据
|
* 修改渲染数据
|
||||||
*/
|
*/
|
||||||
@ -909,8 +772,8 @@ export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
|
|||||||
* @param id 文本框id
|
* @param id 文本框id
|
||||||
* @param props 文本框渲染数据
|
* @param props 文本框渲染数据
|
||||||
*/
|
*/
|
||||||
static use(id: string, props: TextboxProps, emits: TextboxStoreEmits) {
|
static use(id: string, props: TextboxProps) {
|
||||||
const store = new TextboxStore(props, emits);
|
const store = new TextboxStore(props);
|
||||||
if (this.list.has(id)) {
|
if (this.list.has(id)) {
|
||||||
logger.warn(42, id);
|
logger.warn(42, id);
|
||||||
}
|
}
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
|
||||||
import EventEmitter from 'eventemitter3';
|
|
||||||
|
|
||||||
export const enum WordBreak {
|
|
||||||
/** 不换行 */
|
|
||||||
None,
|
|
||||||
/** 仅空格和连字符等可换行,CJK 字符可任意换行,默认值 */
|
|
||||||
Space,
|
|
||||||
/** 所有字符都可以换行 */
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum TextAlign {
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
End
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITextContentRenderData {
|
|
||||||
text: string;
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
/** 字体类型 */
|
|
||||||
fontFamily?: string;
|
|
||||||
/** 字体大小 */
|
|
||||||
fontSize?: number;
|
|
||||||
/** 字体线宽 */
|
|
||||||
fontWeight?: number;
|
|
||||||
/** 是否斜体 */
|
|
||||||
fontItalic?: boolean;
|
|
||||||
/** 是否持续上一次的文本,开启后,如果修改后的文本以修改前的文本为开头,那么会继续播放而不会从头播放 */
|
|
||||||
keepLast?: boolean;
|
|
||||||
/** 打字机时间间隔,即两个字出现之间相隔多长时间 */
|
|
||||||
interval?: number;
|
|
||||||
/** 行高 */
|
|
||||||
lineHeight?: number;
|
|
||||||
/** 分词规则 */
|
|
||||||
wordBreak?: WordBreak;
|
|
||||||
/** 文字对齐方式 */
|
|
||||||
textAlign?: TextAlign;
|
|
||||||
/** 行首忽略字符,即不会出现在行首的字符 */
|
|
||||||
ignoreLineStart?: Iterable<string>;
|
|
||||||
/** 行尾忽略字符,即不会出现在行尾的字符 */
|
|
||||||
ignoreLineEnd?: Iterable<string>;
|
|
||||||
/** 会被分词规则识别的分词字符 */
|
|
||||||
breakChars?: Iterable<string>;
|
|
||||||
/** 填充样式 */
|
|
||||||
fillStyle?: CanvasStyle;
|
|
||||||
/** 描边样式 */
|
|
||||||
strokeStyle?: CanvasStyle;
|
|
||||||
/** 线宽 */
|
|
||||||
strokeWidth?: number;
|
|
||||||
/** 是否填充 */
|
|
||||||
fill?: boolean;
|
|
||||||
/** 是否描边 */
|
|
||||||
stroke?: boolean;
|
|
||||||
/** 是否无视打字机,强制全部显示 */
|
|
||||||
showAll?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum TextContentType {
|
|
||||||
Text,
|
|
||||||
Wait,
|
|
||||||
Icon
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITextContentRenderable {
|
|
||||||
type: TextContentType;
|
|
||||||
text: string;
|
|
||||||
wait?: number;
|
|
||||||
icon?: AllNumbers;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TextContentTyperEvent {
|
|
||||||
typeStart: [];
|
|
||||||
typeEnd: [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
|
||||||
testCanvas: MotaOffscreenCanvas2D;
|
|
||||||
|
|
||||||
constructor(public readonly data: Required<ITextContentRenderData>) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.testCanvas = new MotaOffscreenCanvas2D(false);
|
|
||||||
this.testCanvas.withGameScale(false);
|
|
||||||
this.testCanvas.setHD(false);
|
|
||||||
this.testCanvas.size(32, 32);
|
|
||||||
this.testCanvas.freeze();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置显示文本
|
|
||||||
*/
|
|
||||||
setText(text: string) {
|
|
||||||
this.data.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parse(text: string) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFont(
|
|
||||||
family: string,
|
|
||||||
size: number,
|
|
||||||
weight: number = 500,
|
|
||||||
italic: boolean = false
|
|
||||||
) {
|
|
||||||
return `${italic ? 'italic ' : ''}${weight} ${size}px ${family}`;
|
|
||||||
}
|
|
@ -13,68 +13,55 @@ import { PopText } from '@/plugin/fx/pop';
|
|||||||
import { FloorChange } from '@/plugin/fallback';
|
import { FloorChange } from '@/plugin/fallback';
|
||||||
import { createApp } from './renderer';
|
import { createApp } from './renderer';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { Textbox } from './components';
|
|
||||||
import { ILayerGroupRenderExtends, ILayerRenderExtends } from './preset';
|
|
||||||
import { Props } from './utils';
|
|
||||||
|
|
||||||
let main: MotaRenderer;
|
let main: MotaRenderer;
|
||||||
|
|
||||||
Mota.require('var', 'loading').once('coreInit', () => {
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
main = new MotaRenderer();
|
main = new MotaRenderer();
|
||||||
|
|
||||||
const App = defineComponent(_props => {
|
const Com = defineComponent(props => {
|
||||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
|
||||||
new FloorDamageExtends(),
|
|
||||||
new FloorItemDetail(),
|
|
||||||
new LayerGroupFilter(),
|
|
||||||
new LayerGroupPortal(),
|
|
||||||
new LayerGroupHalo(),
|
|
||||||
new LayerGroupAnimate(),
|
|
||||||
new FloorViewport()
|
|
||||||
];
|
|
||||||
const eventExtends: ILayerRenderExtends[] = [
|
|
||||||
new HeroRenderer(),
|
|
||||||
new LayerDoorAnimate(),
|
|
||||||
new LayerShadowExtends()
|
|
||||||
];
|
|
||||||
const mapDrawProps: Props<'container'> = {
|
|
||||||
width: core._PX_,
|
|
||||||
height: core._PY_
|
|
||||||
};
|
|
||||||
const mainTextboxProps: Props<typeof Textbox> = {
|
|
||||||
text: '',
|
|
||||||
hidden: true,
|
|
||||||
width: 480,
|
|
||||||
height: 150,
|
|
||||||
y: 330,
|
|
||||||
zIndex: 30,
|
|
||||||
fillStyle: '#fff',
|
|
||||||
titleFill: 'gold',
|
|
||||||
fontFamily: 'normal',
|
|
||||||
titleFont: '700 20px normal',
|
|
||||||
winskin: 'winskin2.png',
|
|
||||||
interval: 25,
|
|
||||||
lineHeight: 6
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<container id="map-draw" {...mapDrawProps}>
|
<container
|
||||||
<layer-group id="layer-main" ex={layerGroupExtends}>
|
id="map-draw"
|
||||||
|
hd
|
||||||
|
antiAliasing={false}
|
||||||
|
width={core._PX_}
|
||||||
|
height={core._PY_}
|
||||||
|
>
|
||||||
|
<layer-group
|
||||||
|
id="layer-main"
|
||||||
|
ex={[
|
||||||
|
new FloorDamageExtends(),
|
||||||
|
new FloorItemDetail(),
|
||||||
|
new LayerGroupFilter(),
|
||||||
|
new LayerGroupPortal(),
|
||||||
|
new LayerGroupHalo(),
|
||||||
|
new LayerGroupAnimate(),
|
||||||
|
new FloorViewport()
|
||||||
|
]}
|
||||||
|
>
|
||||||
<layer layer="bg" zIndex={10}></layer>
|
<layer layer="bg" zIndex={10}></layer>
|
||||||
<layer layer="bg2" zIndex={20}></layer>
|
<layer layer="bg2" zIndex={20}></layer>
|
||||||
<layer layer="event" zIndex={30} ex={eventExtends}></layer>
|
<layer
|
||||||
|
layer="event"
|
||||||
|
zIndex={30}
|
||||||
|
ex={[
|
||||||
|
new HeroRenderer(),
|
||||||
|
new LayerDoorAnimate(),
|
||||||
|
new LayerShadowExtends()
|
||||||
|
]}
|
||||||
|
></layer>
|
||||||
<layer layer="fg" zIndex={40}></layer>
|
<layer layer="fg" zIndex={40}></layer>
|
||||||
<layer layer="fg2" zIndex={50}></layer>
|
<layer layer="fg2" zIndex={50}></layer>
|
||||||
<PopText id="pop-main" zIndex={80}></PopText>
|
<PopText id="pop-main" zIndex={80}></PopText>
|
||||||
</layer-group>
|
</layer-group>
|
||||||
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
|
|
||||||
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
<FloorChange id="floor-change" zIndex={50}></FloorChange>
|
||||||
</container>
|
</container>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
main.hide();
|
main.hide();
|
||||||
createApp(App).mount(main);
|
createApp(Com).mount(main);
|
||||||
// render(<Com></Com>, main);
|
// render(<Com></Com>, main);
|
||||||
|
|
||||||
console.log(main);
|
console.log(main);
|
||||||
|
@ -446,9 +446,10 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
endFn: end
|
endFn: end
|
||||||
};
|
};
|
||||||
RenderItem.tickerMap.set(id, delegation);
|
RenderItem.tickerMap.set(id, delegation);
|
||||||
|
RenderItem.ticker.add(fn);
|
||||||
if (typeof time === 'number' && time < 2147438647 && time > 0) {
|
if (typeof time === 'number' && time < 2147438647 && time > 0) {
|
||||||
delegation.timeout = window.setTimeout(() => {
|
delegation.timeout = window.setTimeout(() => {
|
||||||
RenderItem.tickerMap.delete(id);
|
RenderItem.ticker.remove(fn);
|
||||||
end?.();
|
end?.();
|
||||||
}, time);
|
}, time);
|
||||||
}
|
}
|
||||||
@ -540,7 +541,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
* 添加子元素,默认没有任何行为且会抛出警告,你需要在自己的RenderItem继承类中复写它,才可以使用
|
* 添加子元素,默认没有任何行为且会抛出警告,你需要在自己的RenderItem继承类中复写它,才可以使用
|
||||||
* @param child 子元素
|
* @param child 子元素
|
||||||
*/
|
*/
|
||||||
appendChild(..._child: RenderItem<any>[]): void {
|
appendChild(...child: RenderItem<any>[]): void {
|
||||||
logger.warn(35);
|
logger.warn(35);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +549,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
* 移除子元素,默认没有任何行为且会抛出警告,你需要在自己的RenderItem继承类中复写它,才可以使用
|
* 移除子元素,默认没有任何行为且会抛出警告,你需要在自己的RenderItem继承类中复写它,才可以使用
|
||||||
* @param child 子元素
|
* @param child 子元素
|
||||||
*/
|
*/
|
||||||
removeChild(..._child: RenderItem<any>[]): void {
|
removeChild(...child: RenderItem<any>[]): void {
|
||||||
logger.warn(36);
|
logger.warn(36);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,10 +613,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
key: string,
|
key: string,
|
||||||
prevValue: any,
|
prevValue: any,
|
||||||
nextValue: any,
|
nextValue: any,
|
||||||
_namespace?: ElementNamespace,
|
namespace?: ElementNamespace,
|
||||||
_parentComponent?: ComponentInternalInstance | null
|
parentComponent?: ComponentInternalInstance | null
|
||||||
): void {
|
): void {
|
||||||
if (isNil(prevValue) && isNil(nextValue)) return;
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'x': {
|
case 'x': {
|
||||||
if (!this.assertType(nextValue, 'number', key)) return;
|
if (!this.assertType(nextValue, 'number', key)) return;
|
||||||
@ -722,16 +722,13 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderItem.ticker.add(time => {
|
RenderItem.ticker.add(() => {
|
||||||
// slice 是为了让函数里面的 request 进入下一帧执行
|
// slice 是为了让函数里面的 request 进入下一帧执行
|
||||||
if (beforeFrame.length > 0) {
|
if (beforeFrame.length > 0) {
|
||||||
const arr = beforeFrame.slice();
|
const arr = beforeFrame.slice();
|
||||||
beforeFrame.splice(0);
|
beforeFrame.splice(0);
|
||||||
arr.forEach(v => v());
|
arr.forEach(v => v());
|
||||||
}
|
}
|
||||||
RenderItem.tickerMap.forEach(v => {
|
|
||||||
v.fn(time);
|
|
||||||
});
|
|
||||||
if (renderFrame.length > 0) {
|
if (renderFrame.length > 0) {
|
||||||
const arr = renderFrame.slice();
|
const arr = renderFrame.slice();
|
||||||
renderFrame.splice(0);
|
renderFrame.splice(0);
|
||||||
|
@ -76,7 +76,7 @@ export class LayerGroupAnimate implements ILayerGroupRenderExtends {
|
|||||||
this.listen();
|
this.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(_group: LayerGroup): void {
|
onDestroy(group: LayerGroup): void {
|
||||||
if (this.checkHero()) {
|
if (this.checkHero()) {
|
||||||
this.hero!.off('moveTick', this.onMoveTick);
|
this.hero!.off('moveTick', this.onMoveTick);
|
||||||
LayerGroupAnimate.animateList.delete(this);
|
LayerGroupAnimate.animateList.delete(this);
|
||||||
|
@ -16,9 +16,7 @@ import { logger } from '@/core/common/logger';
|
|||||||
|
|
||||||
type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
||||||
|
|
||||||
export interface ETextEvent extends ERenderItemEvent {
|
export interface ETextEvent extends ERenderItemEvent {}
|
||||||
setText: [text: string];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Text extends RenderItem<ETextEvent> {
|
export class Text extends RenderItem<ETextEvent> {
|
||||||
text: string;
|
text: string;
|
||||||
@ -78,7 +76,6 @@ export class Text extends RenderItem<ETextEvent> {
|
|||||||
this.text = text;
|
this.text = text;
|
||||||
this.calBox();
|
this.calBox();
|
||||||
if (this.parent) this.update(this);
|
if (this.parent) this.update(this);
|
||||||
this.emit('setText', text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,10 +129,10 @@ export class Text extends RenderItem<ETextEvent> {
|
|||||||
this.setText(nextValue);
|
this.setText(nextValue);
|
||||||
return;
|
return;
|
||||||
case 'fillStyle':
|
case 'fillStyle':
|
||||||
this.setStyle(nextValue, this.strokeStyle);
|
this.setStyle(nextValue);
|
||||||
return;
|
return;
|
||||||
case 'strokeStyle':
|
case 'strokeStyle':
|
||||||
this.setStyle(this.fillStyle, nextValue);
|
this.setStyle(void 0, nextValue);
|
||||||
return;
|
return;
|
||||||
case 'font':
|
case 'font':
|
||||||
if (!this.assertType(nextValue, 'string', key)) return;
|
if (!this.assertType(nextValue, 'string', key)) return;
|
||||||
|
@ -67,10 +67,8 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
const halfHeight = core._PY_ / 2;
|
const halfHeight = core._PY_ / 2;
|
||||||
const cell = this.group.cellSize;
|
const cell = this.group.cellSize;
|
||||||
const half = cell / 2;
|
const half = cell / 2;
|
||||||
this.applyPosition(
|
this.nx = -(nx - halfWidth + half) / this.group.cellSize;
|
||||||
-(nx - halfWidth + half) / this.group.cellSize,
|
this.ny = -(ny - halfHeight + half) / this.group.cellSize;
|
||||||
-(ny - halfHeight + half) / this.group.cellSize
|
|
||||||
);
|
|
||||||
this.mutateTo(x, y);
|
this.mutateTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +116,8 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
if (!this.enabled) return;
|
if (!this.enabled) return;
|
||||||
const { x: nx, y: ny } = this.getBoundedPosition(x, y);
|
const { x: nx, y: ny } = this.getBoundedPosition(x, y);
|
||||||
this.group.removeTicker(this.transition, false);
|
this.group.removeTicker(this.transition, false);
|
||||||
this.applyPosition(nx, ny);
|
this.nx = nx;
|
||||||
|
this.ny = ny;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,7 +198,8 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
|
|
||||||
const { x, y } = this.hero!.renderable;
|
const { x, y } = this.hero!.renderable;
|
||||||
const { x: nx, y: ny } = this.getBoundedPosition(x, y);
|
const { x: nx, y: ny } = this.getBoundedPosition(x, y);
|
||||||
this.applyPosition(nx, ny);
|
this.nx = nx;
|
||||||
|
this.ny = ny;
|
||||||
|
|
||||||
if (ending) {
|
if (ending) {
|
||||||
if (this.ox === xTarget && this.oy == yTarget) {
|
if (this.ox === xTarget && this.oy == yTarget) {
|
||||||
@ -264,32 +264,41 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
const progress = fn((now - start) / time);
|
const progress = fn((now - start) / time);
|
||||||
const tx = dx * progress;
|
const tx = dx * progress;
|
||||||
const ty = dy * progress;
|
const ty = dy * progress;
|
||||||
this.applyPosition(tx + sx, ty + sy);
|
this.nx = tx + sx;
|
||||||
|
this.ny = ty + sy;
|
||||||
},
|
},
|
||||||
time,
|
time,
|
||||||
() => {
|
() => {
|
||||||
this.applyPosition(x, y);
|
this.nx = x;
|
||||||
|
this.ny = y;
|
||||||
this.inTransition = false;
|
this.inTransition = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyPosition(x: number, y: number) {
|
private create() {
|
||||||
if (!this.enabled) return;
|
let nx = this.nx;
|
||||||
if (x === this.nx && y === this.ny) return;
|
let ny = this.ny;
|
||||||
const halfWidth = core._PX_ / 2;
|
const halfWidth = core._PX_ / 2;
|
||||||
const halfHeight = core._PY_ / 2;
|
const halfHeight = core._PY_ / 2;
|
||||||
const cell = this.group.cellSize;
|
this.delegation = this.group.delegateTicker(() => {
|
||||||
const half = cell / 2;
|
if (!this.enabled) return;
|
||||||
this.nx = x;
|
if (this.nx === nx && this.ny === ny) {
|
||||||
this.ny = y;
|
return;
|
||||||
const { x: bx, y: by } = this.getBoundedPosition(x, y);
|
}
|
||||||
const rx = bx * cell - halfWidth + half;
|
const cell = this.group.cellSize;
|
||||||
const ry = by * cell - halfHeight + half;
|
const half = cell / 2;
|
||||||
core.bigmap.offsetX = rx;
|
nx = this.nx;
|
||||||
core.bigmap.offsetY = ry;
|
ny = this.ny;
|
||||||
this.group.camera.setTranslate(-rx, -ry);
|
const { x: bx, y: by } = this.getBoundedPosition(nx, ny);
|
||||||
this.group.update(this.group);
|
const rx = bx * cell - halfWidth + half;
|
||||||
|
const ry = by * cell - halfHeight + half;
|
||||||
|
core.bigmap.offsetX = rx;
|
||||||
|
core.bigmap.offsetY = ry;
|
||||||
|
this.group.camera.setTranslate(-rx, -ry);
|
||||||
|
this.group.update(this.group);
|
||||||
|
});
|
||||||
|
// this.createMoving();
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkDependency() {
|
private checkDependency() {
|
||||||
@ -310,6 +319,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
|||||||
|
|
||||||
awake(group: LayerGroup): void {
|
awake(group: LayerGroup): void {
|
||||||
this.group = group;
|
this.group = group;
|
||||||
|
this.create();
|
||||||
adapter.add(this);
|
adapter.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
import { ERenderItemEvent, RenderItem } from '../item';
|
import { ERenderItemEvent, RenderItem } from '../item';
|
||||||
import { tagMap } from './map';
|
import { tagMap } from './map';
|
||||||
import { logger } from '@/core/common/logger';
|
import { logger } from '@/core/common/logger';
|
||||||
import { Comment, ETextEvent, Text } from '../preset/misc';
|
import { Comment, Text } from '../preset/misc';
|
||||||
|
|
||||||
export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
||||||
patchProp: function (
|
patchProp: function (
|
||||||
@ -24,7 +24,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
|||||||
insert: function (
|
insert: function (
|
||||||
el: RenderItem<ERenderItemEvent>,
|
el: RenderItem<ERenderItemEvent>,
|
||||||
parent: RenderItem,
|
parent: RenderItem,
|
||||||
_anchor?: RenderItem<ERenderItemEvent> | null
|
anchor?: RenderItem<ERenderItemEvent> | null
|
||||||
): void {
|
): void {
|
||||||
parent.appendChild(el);
|
parent.appendChild(el);
|
||||||
},
|
},
|
||||||
@ -48,7 +48,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
|
|||||||
return onCreate(namespace, isCustomizedBuiltIn, vnodeProps);
|
return onCreate(namespace, isCustomizedBuiltIn, vnodeProps);
|
||||||
},
|
},
|
||||||
|
|
||||||
createText: function (text: string): RenderItem<ETextEvent> {
|
createText: function (text: string): RenderItem<ERenderItemEvent> {
|
||||||
if (!/^\s*$/.test(text)) logger.warn(38);
|
if (!/^\s*$/.test(text)) logger.warn(38);
|
||||||
return new Text(text);
|
return new Text(text);
|
||||||
},
|
},
|
||||||
|
@ -21,9 +21,9 @@ import {
|
|||||||
Line,
|
Line,
|
||||||
Path,
|
Path,
|
||||||
QuadraticCurve,
|
QuadraticCurve,
|
||||||
Rect,
|
Rect
|
||||||
RectR
|
|
||||||
} from '../preset/graphics';
|
} from '../preset/graphics';
|
||||||
|
import { BaseProps } from './props';
|
||||||
|
|
||||||
type OnItemCreate<
|
type OnItemCreate<
|
||||||
E extends ERenderItemEvent = ERenderItemEvent,
|
E extends ERenderItemEvent = ERenderItemEvent,
|
||||||
@ -217,10 +217,10 @@ tagMap.register('layer-group', (_0, _1, props) => {
|
|||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, _props) => {
|
tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, props) => {
|
||||||
return new Damage();
|
return new Damage();
|
||||||
});
|
});
|
||||||
tagMap.register('animation', (_0, _1, _props) => {
|
tagMap.register('animation', (_0, _1, props) => {
|
||||||
return new Animate();
|
return new Animate();
|
||||||
});
|
});
|
||||||
tagMap.register('g-rect', se(Rect, 'absolute', ElementState.None));
|
tagMap.register('g-rect', se(Rect, 'absolute', ElementState.None));
|
||||||
@ -230,7 +230,7 @@ tagMap.register('g-line', se(Line, 'absolute', ElementState.None));
|
|||||||
tagMap.register('g-bezier', se(BezierCurve, 'absolute', ElementState.None));
|
tagMap.register('g-bezier', se(BezierCurve, 'absolute', ElementState.None));
|
||||||
tagMap.register('g-quad', se(QuadraticCurve, 'absolute', ElementState.None));
|
tagMap.register('g-quad', se(QuadraticCurve, 'absolute', ElementState.None));
|
||||||
tagMap.register('g-path', se(Path, 'absolute', ElementState.None));
|
tagMap.register('g-path', se(Path, 'absolute', ElementState.None));
|
||||||
tagMap.register('g-rectr', se(RectR, 'absolute', ElementState.None));
|
tagMap.register('g-rectr', se(Path, 'absolute', ElementState.None));
|
||||||
tagMap.register('icon', standardElementNoCache(Icon));
|
tagMap.register('icon', standardElementNoCache(Icon));
|
||||||
tagMap.register('winskin', (_0, _1, props) => {
|
tagMap.register('winskin', (_0, _1, props) => {
|
||||||
if (!props) return new Winskin(core.material.images.images['winskin.png']);
|
if (!props) return new Winskin(core.material.images.images['winskin.png']);
|
||||||
|
@ -1,21 +1,6 @@
|
|||||||
import { Ticker, TimingFn } from 'mutate-animate';
|
import { Ticker, TimingFn } from 'mutate-animate';
|
||||||
import { RenderAdapter } from './adapter';
|
import { RenderAdapter } from './adapter';
|
||||||
import { FloorViewport } from './preset/viewport';
|
import { FloorViewport } from './preset/viewport';
|
||||||
import { JSX } from 'vue/jsx-runtime';
|
|
||||||
import { Component, DefineComponent, DefineSetupFnComponent } from 'vue';
|
|
||||||
|
|
||||||
export type Props<
|
|
||||||
T extends
|
|
||||||
| keyof JSX.IntrinsicElements
|
|
||||||
| DefineSetupFnComponent<any>
|
|
||||||
| DefineComponent
|
|
||||||
> = T extends keyof JSX.IntrinsicElements
|
|
||||||
? JSX.IntrinsicElements[T]
|
|
||||||
: T extends DefineSetupFnComponent<any>
|
|
||||||
? InstanceType<T>['$props']
|
|
||||||
: T extends DefineComponent
|
|
||||||
? InstanceType<T>['$props']
|
|
||||||
: unknown;
|
|
||||||
|
|
||||||
export function disableViewport() {
|
export function disableViewport() {
|
||||||
const adapter = RenderAdapter.get<FloorViewport>('viewport');
|
const adapter = RenderAdapter.get<FloorViewport>('viewport');
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from './ui';
|
|
@ -1,55 +0,0 @@
|
|||||||
import { Component, VNodeProps } from 'vue';
|
|
||||||
|
|
||||||
export interface IUIControllerConfig<Element, UI> {
|
|
||||||
/**
|
|
||||||
* 将一个ui挂载至目标元素时的操作
|
|
||||||
* @param element 要挂载至的目标元素
|
|
||||||
* @param ui 要挂载的ui对象
|
|
||||||
*/
|
|
||||||
insert(element: Element, ui: UI): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将一个ui从目标元素上移除时的操作
|
|
||||||
* @param element 被移除ui的父元素
|
|
||||||
* @param ui 要被移除的ui元素
|
|
||||||
*/
|
|
||||||
remove(element: Element, ui: UI): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个新UI
|
|
||||||
* @param component UI组件
|
|
||||||
* @param props UI传递的props
|
|
||||||
*/
|
|
||||||
createUI(
|
|
||||||
component: Component,
|
|
||||||
props?: (VNodeProps & { [key: string]: any }) | null
|
|
||||||
): UI;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum OpenOption {
|
|
||||||
Push,
|
|
||||||
Unshift
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum CloseOption {
|
|
||||||
Splice,
|
|
||||||
Pop,
|
|
||||||
Shift
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UIController<Element, UI> {
|
|
||||||
constructor(config: IUIControllerConfig<Element, UI>) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当ui改变时控制器的行为
|
|
||||||
* @param open 打开时的行为
|
|
||||||
* @param close 关闭时的行为
|
|
||||||
*/
|
|
||||||
setChangeMode(open: OpenOption, close: CloseOption) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将这个UI控制器挂载至容器上
|
|
||||||
* @param container 要挂载至的容器
|
|
||||||
*/
|
|
||||||
mount(container: Element) {}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './controller';
|
|
@ -69,7 +69,7 @@
|
|||||||
"39": "Plain text is not supported outside Text element.",
|
"39": "Plain text is not supported outside Text element.",
|
||||||
"40": "Cannot return canvas that is not provided by this pool.",
|
"40": "Cannot return canvas that is not provided by this pool.",
|
||||||
"41": "Width of text content components must be positive. receive: $1",
|
"41": "Width of text content components must be positive. receive: $1",
|
||||||
"42": "Repeated Textbox id: '$1'.",
|
"42": "Repeat Textbox id: '$1'.",
|
||||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
"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."
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@ import type * as Animation from 'mutate-animate';
|
|||||||
import type * as RenderUtils from '@/core/render/utils';
|
import type * as RenderUtils from '@/core/render/utils';
|
||||||
import type { WeatherController } from '@/module/weather/weather';
|
import type { WeatherController } from '@/module/weather/weather';
|
||||||
import type { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
import type { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
import type { TextboxStore } from '@/core/render';
|
|
||||||
|
|
||||||
interface ClassInterface {
|
interface ClassInterface {
|
||||||
// 渲染进程与游戏进程通用
|
// 渲染进程与游戏进程通用
|
||||||
@ -124,7 +123,6 @@ interface ModuleInterface {
|
|||||||
Camera: typeof Camera;
|
Camera: typeof Camera;
|
||||||
MotaOffscreenCanvas2D: typeof MotaOffscreenCanvas2D;
|
MotaOffscreenCanvas2D: typeof MotaOffscreenCanvas2D;
|
||||||
Utils: typeof RenderUtils;
|
Utils: typeof RenderUtils;
|
||||||
TextboxStore: typeof TextboxStore;
|
|
||||||
};
|
};
|
||||||
State: {
|
State: {
|
||||||
ItemState: typeof ItemState;
|
ItemState: typeof ItemState;
|
||||||
@ -157,6 +155,7 @@ type InterfaceType = keyof SystemInterfaceMap;
|
|||||||
|
|
||||||
interface PluginInterface {
|
interface PluginInterface {
|
||||||
// 渲染进程定义的插件
|
// 渲染进程定义的插件
|
||||||
|
pop_r: typeof import('../plugin/pop');
|
||||||
use_r: typeof import('../plugin/use');
|
use_r: typeof import('../plugin/use');
|
||||||
fly_r: typeof import('../plugin/ui/fly');
|
fly_r: typeof import('../plugin/ui/fly');
|
||||||
chase_r: typeof import('../plugin/chase');
|
chase_r: typeof import('../plugin/chase');
|
||||||
|
@ -17,11 +17,8 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
|
|||||||
|
|
||||||
/** 开始时刻 */
|
/** 开始时刻 */
|
||||||
private startTime: number = 0;
|
private startTime: number = 0;
|
||||||
|
|
||||||
/** 当前帧数 */
|
/** 当前帧数 */
|
||||||
frame: number = 0;
|
frame: number = 0;
|
||||||
/** 上一帧的时刻 */
|
|
||||||
lastTime: number = 0;
|
|
||||||
|
|
||||||
/** 这个boss战的主渲染元素,所有弹幕都会在此之上渲染 */
|
/** 这个boss战的主渲染元素,所有弹幕都会在此之上渲染 */
|
||||||
abstract readonly main: BossSprite;
|
abstract readonly main: BossSprite;
|
||||||
@ -34,19 +31,17 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
|
|||||||
* boss的ai,战斗开始后,每帧执行一次
|
* boss的ai,战斗开始后,每帧执行一次
|
||||||
* @param time 从战斗开始算起至现在经过了多长时间
|
* @param time 从战斗开始算起至现在经过了多长时间
|
||||||
* @param frame 从战斗开始算起至现在经过了多少帧,即当前是第几帧
|
* @param frame 从战斗开始算起至现在经过了多少帧,即当前是第几帧
|
||||||
* @param dt 本帧距上一帧多长时间,即上一帧持续了多长时间
|
|
||||||
*/
|
*/
|
||||||
abstract ai(time: number, frame: number, dt: number): void;
|
abstract ai(time: number, frame: number): void;
|
||||||
|
|
||||||
private tick = () => {
|
private tick = () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const dt = now - this.lastTime;
|
this.ai(now - this.startTime, this.frame);
|
||||||
this.ai(now - this.startTime, this.frame, dt);
|
|
||||||
this.frame++;
|
this.frame++;
|
||||||
this.projectiles.forEach(v => {
|
this.projectiles.forEach(v => {
|
||||||
const time = now - v.startTime;
|
const time = now - v.startTime;
|
||||||
v.time = time;
|
v.time = time;
|
||||||
v.ai(this, time, v.frame, dt);
|
v.ai(this, time, v.frame);
|
||||||
v.frame++;
|
v.frame++;
|
||||||
if (time > 60_000) {
|
if (time > 60_000) {
|
||||||
this.destroyProjectile(v);
|
this.destroyProjectile(v);
|
||||||
@ -55,7 +50,6 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
|
|||||||
v.doDamage(this.state);
|
v.doDamage(this.state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.lastTime = now;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,9 +230,8 @@ export abstract class Projectile<T extends BarrageBoss = BarrageBoss> {
|
|||||||
* @param boss 从属的boss
|
* @param boss 从属的boss
|
||||||
* @param time 从弹幕生成开始算起至现在经过了多长时间
|
* @param time 从弹幕生成开始算起至现在经过了多长时间
|
||||||
* @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
|
* @param frame 从弹幕生成开始算起至现在经过了多少帧,即当前是第几帧
|
||||||
* @param dt 本帧距上一帧多长时间,即上一帧持续了多长时间
|
|
||||||
*/
|
*/
|
||||||
abstract ai(boss: T, time: number, frame: number, dt: number): void;
|
abstract ai(boss: T, time: number, frame: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这个弹幕的渲染函数,原则上一个boss的弹幕应该全部画在同一层,而且渲染前画布不进行矩阵变换
|
* 这个弹幕的渲染函数,原则上一个boss的弹幕应该全部画在同一层,而且渲染前画布不进行矩阵变换
|
||||||
@ -416,8 +409,8 @@ export namespace Hitbox {
|
|||||||
export function checkCircleCircle(circle1: Circle, circle2: Circle) {
|
export function checkCircleCircle(circle1: Circle, circle2: Circle) {
|
||||||
const dx = circle1.x - circle2.x;
|
const dx = circle1.x - circle2.x;
|
||||||
const dy = circle1.y - circle2.y;
|
const dy = circle1.y - circle2.y;
|
||||||
const dis = dx ** 2 + dy ** 2;
|
const dis = Math.hypot(dx, dy);
|
||||||
return dis <= (circle1.radius + circle2.radius) ** 2;
|
return dis <= circle1.radius + circle2.radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
import { IStateDamageable } from '@/game/state/interface';
|
|
||||||
import { BarrageBoss, BossSprite, Hitbox } from './barrage';
|
|
||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
|
||||||
import {
|
|
||||||
Container,
|
|
||||||
HeroRenderer,
|
|
||||||
LayerGroup,
|
|
||||||
MotaRenderer,
|
|
||||||
RenderItem,
|
|
||||||
Shader,
|
|
||||||
Transform
|
|
||||||
} from '@/core/render';
|
|
||||||
import { Pop } from '../fx/pop';
|
|
||||||
import { SplittableBall } from './palaceBossProjectile';
|
|
||||||
import { PointEffect } from '../fx/pointShader';
|
|
||||||
|
|
||||||
Mota.require('var', 'loading').once('coreInit', () => {
|
|
||||||
const shader = new Shader();
|
|
||||||
shader.size(480, 480);
|
|
||||||
shader.setHD(true);
|
|
||||||
shader.setZIndex(120);
|
|
||||||
PalaceBoss.shader = shader;
|
|
||||||
PalaceBoss.effect.create(shader, 40);
|
|
||||||
});
|
|
||||||
|
|
||||||
const enum BossStage {
|
|
||||||
Prologue,
|
|
||||||
|
|
||||||
Stage1,
|
|
||||||
Stage2,
|
|
||||||
Stage3,
|
|
||||||
Stage4,
|
|
||||||
|
|
||||||
End
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PalaceBoss extends BarrageBoss {
|
|
||||||
static effect: PointEffect = new PointEffect();
|
|
||||||
static shader: Shader;
|
|
||||||
|
|
||||||
main: BossSprite<BarrageBoss>;
|
|
||||||
hitbox: Hitbox.Circle;
|
|
||||||
state: IStateDamageable;
|
|
||||||
|
|
||||||
private stage: BossStage = BossStage.Prologue;
|
|
||||||
|
|
||||||
/** 用于展示傅里叶频谱的背景元素 */
|
|
||||||
private back: SonicBack;
|
|
||||||
/** 楼层渲染元素 */
|
|
||||||
private group: LayerGroup;
|
|
||||||
/** 楼层渲染容器 */
|
|
||||||
private mapDraw: Container;
|
|
||||||
/** 伤害弹出 */
|
|
||||||
pop: Pop;
|
|
||||||
|
|
||||||
private heroHp: number = 0;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
const render = MotaRenderer.get('render-main')!;
|
|
||||||
this.group = render.getElementById('layer-main') as LayerGroup;
|
|
||||||
this.mapDraw = render.getElementById('map-draw') as Container;
|
|
||||||
this.pop = render.getElementById('pop-main') as Pop;
|
|
||||||
|
|
||||||
this.state = core.status.hero;
|
|
||||||
this.main = new BossEffect('static', this);
|
|
||||||
this.back = new SonicBack('static');
|
|
||||||
const { x, y } = core.status.hero.loc;
|
|
||||||
const cell = 32;
|
|
||||||
this.hitbox = new Hitbox.Circle(x + cell / 2, y + cell / 2, cell / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
override start(): void {
|
|
||||||
super.start();
|
|
||||||
|
|
||||||
PalaceBoss.shader.append(this.mapDraw);
|
|
||||||
this.main.append(this.group);
|
|
||||||
|
|
||||||
// const event = this.group.getLayer('event');
|
|
||||||
// const hero = event?.getExtends('floor-hero') as HeroRenderer;
|
|
||||||
// hero?.on('moveTick', this.moveTick);
|
|
||||||
|
|
||||||
SplittableBall.init({});
|
|
||||||
this.heroHp = core.status.hero.hp;
|
|
||||||
}
|
|
||||||
|
|
||||||
override end(): void {
|
|
||||||
super.end();
|
|
||||||
|
|
||||||
PalaceBoss.shader.remove();
|
|
||||||
this.main.remove();
|
|
||||||
this.back.remove();
|
|
||||||
this.main.destroy();
|
|
||||||
this.back.destroy();
|
|
||||||
|
|
||||||
// const event = this.group.getLayer('event');
|
|
||||||
// const hero = event?.getExtends('floor-hero') as HeroRenderer;
|
|
||||||
// hero?.off('moveTick', this.moveTick);
|
|
||||||
|
|
||||||
SplittableBall.end();
|
|
||||||
|
|
||||||
PalaceBoss.effect.end();
|
|
||||||
core.status.hero.hp = this.heroHp;
|
|
||||||
|
|
||||||
Mota.Plugin.require('replay_g').clip('choices:0');
|
|
||||||
}
|
|
||||||
|
|
||||||
ai(time: number, frame: number): void {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BossEffect extends BossSprite<PalaceBoss> {
|
|
||||||
protected preDraw(
|
|
||||||
canvas: MotaOffscreenCanvas2D,
|
|
||||||
transform: Transform
|
|
||||||
): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected postDraw(
|
|
||||||
canvas: MotaOffscreenCanvas2D,
|
|
||||||
transform: Transform
|
|
||||||
): void {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SonicBack extends RenderItem {
|
|
||||||
protected render(
|
|
||||||
canvas: MotaOffscreenCanvas2D,
|
|
||||||
transform: Transform
|
|
||||||
): void {}
|
|
||||||
}
|
|
@ -1,271 +0,0 @@
|
|||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
|
||||||
import { Transform } from '@/core/render';
|
|
||||||
import { IStateDamageable } from '@/game/state/interface';
|
|
||||||
import { Hitbox, Projectile } from './barrage';
|
|
||||||
import type { PalaceBoss } from './palaceBoss';
|
|
||||||
import { clamp } from '../utils';
|
|
||||||
|
|
||||||
function popDamage(damage: number, boss: PalaceBoss, color: string) {
|
|
||||||
const { x, y } = core.status.hero.loc;
|
|
||||||
boss.pop.addPop(
|
|
||||||
(-damage).toString(),
|
|
||||||
1000,
|
|
||||||
x * 32 + 16,
|
|
||||||
y * 32 + 16,
|
|
||||||
color
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISplitData {
|
|
||||||
split: boolean;
|
|
||||||
/** 分裂时刻,以弹幕被创建时刻为基准 */
|
|
||||||
time: number;
|
|
||||||
/** 分裂起始角度,以该弹幕朝向方向为 0 */
|
|
||||||
startAngle: number;
|
|
||||||
/** 分裂终止角度,以该弹幕朝向方向为 0 */
|
|
||||||
endAngle: number;
|
|
||||||
/** 每秒加速度 */
|
|
||||||
acc: number;
|
|
||||||
/** 初始速度 */
|
|
||||||
startVel: number;
|
|
||||||
/** 终止速度 */
|
|
||||||
endVel: number;
|
|
||||||
/** 持续时长 */
|
|
||||||
lastTime: number;
|
|
||||||
/** 分裂数量 */
|
|
||||||
count: number;
|
|
||||||
/** 这个弹幕分裂产生的弹幕的分裂信息,不填则表示产生的弹幕不会分裂 */
|
|
||||||
data?: ISplitData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SplittableBall extends Projectile<PalaceBoss> {
|
|
||||||
damage: number = 10000;
|
|
||||||
hitbox: Hitbox.Circle = new Hitbox.Circle(0, 0, 8);
|
|
||||||
|
|
||||||
static ball: Map<string, MotaOffscreenCanvas2D> = new Map();
|
|
||||||
|
|
||||||
private damaged: boolean = false;
|
|
||||||
private splitData?: ISplitData;
|
|
||||||
private last: number = 60_000;
|
|
||||||
|
|
||||||
/** 角度,水平向右为 0,顺时针旋转一圈为 Math.PI * 2 */
|
|
||||||
private angle: number = 0;
|
|
||||||
/** 每秒加速度 */
|
|
||||||
private acc: number = 0;
|
|
||||||
/** 初始速度,每秒多少像素 */
|
|
||||||
private startVel: number = 0;
|
|
||||||
/** 终止速度 */
|
|
||||||
private endVel: number = 0;
|
|
||||||
/** 弹幕颜色 */
|
|
||||||
private color?: string;
|
|
||||||
|
|
||||||
private startVelX: number = 0;
|
|
||||||
private startVelY: number = 0;
|
|
||||||
private endVelX: number = 0;
|
|
||||||
private endVelY: number = 0;
|
|
||||||
private vx: number = 0;
|
|
||||||
private vy: number = 0;
|
|
||||||
// 加速度
|
|
||||||
private ax: number = 0;
|
|
||||||
private ay: number = 0;
|
|
||||||
|
|
||||||
/** 是否已经分裂过 */
|
|
||||||
private splitted: boolean = false;
|
|
||||||
|
|
||||||
static init(colors: Record<string, string[]>) {
|
|
||||||
this.ball.clear();
|
|
||||||
for (const [key, color] of Object.entries(colors)) {
|
|
||||||
const canvas = new MotaOffscreenCanvas2D();
|
|
||||||
canvas.size(32, 32);
|
|
||||||
canvas.withGameScale(true);
|
|
||||||
canvas.setHD(true);
|
|
||||||
const ctx = canvas.ctx;
|
|
||||||
const gradient = ctx.createRadialGradient(16, 16, 8, 16, 16, 16);
|
|
||||||
const step = 1 / (color.length - 1);
|
|
||||||
for (let i = 0; i < color.length; i++) {
|
|
||||||
gradient.addColorStop(i * step, color[i]);
|
|
||||||
}
|
|
||||||
ctx.fillStyle = gradient;
|
|
||||||
ctx.arc(16, 16, 16, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
canvas.freeze();
|
|
||||||
this.ball.set(key, canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static end() {
|
|
||||||
this.ball.forEach(v => {
|
|
||||||
v.clear();
|
|
||||||
v.delete();
|
|
||||||
});
|
|
||||||
this.ball.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置持续时长
|
|
||||||
* @param time 持续时长
|
|
||||||
*/
|
|
||||||
setLastTime(time: number) {
|
|
||||||
this.last = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置这个弹幕的分裂数据
|
|
||||||
* @param data 分裂数据,不填表示该弹幕不会分裂
|
|
||||||
*/
|
|
||||||
setSplitData(data?: ISplitData) {
|
|
||||||
this.splitData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算速度分量信息
|
|
||||||
*/
|
|
||||||
private calVel() {
|
|
||||||
const sin = Math.sin(this.angle);
|
|
||||||
const cos = Math.cos(this.angle);
|
|
||||||
const vel = Math.hypot(this.vx, this.vy);
|
|
||||||
|
|
||||||
this.startVelX = this.startVel * cos;
|
|
||||||
this.startVelY = this.startVel * sin;
|
|
||||||
this.endVelX = this.endVel * cos;
|
|
||||||
this.endVelY = this.endVel * sin;
|
|
||||||
this.ax = this.acc * cos;
|
|
||||||
this.ay = this.acc * sin;
|
|
||||||
this.vx = vel * cos;
|
|
||||||
this.vy = vel * sin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置弹幕速度朝向
|
|
||||||
* @param angle 朝向
|
|
||||||
*/
|
|
||||||
setAngle(angle: number) {
|
|
||||||
this.angle = angle;
|
|
||||||
this.calVel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置速度
|
|
||||||
* @param start 起始速度
|
|
||||||
* @param end 终止速度
|
|
||||||
*/
|
|
||||||
setVel(start: number, end: number) {
|
|
||||||
this.startVel = start;
|
|
||||||
this.endVel = end;
|
|
||||||
this.calVel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置加速度
|
|
||||||
* @param acc 加速度,每秒加速多少像素
|
|
||||||
*/
|
|
||||||
setAcc(acc: number) {
|
|
||||||
this.acc = acc;
|
|
||||||
this.calVel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置弹幕的颜色
|
|
||||||
* @param color 颜色
|
|
||||||
*/
|
|
||||||
setColor(color: string) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
isIntersect(hitbox: Hitbox.HitboxType): boolean {
|
|
||||||
if (hitbox instanceof Hitbox.Circle) {
|
|
||||||
return Hitbox.checkCircleCircle(hitbox, this.hitbox);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHitbox(x: number, y: number): void {
|
|
||||||
this.hitbox.setCenter(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
doDamage(target: IStateDamageable): boolean {
|
|
||||||
if (this.damaged) return false;
|
|
||||||
target.hp -= this.damage;
|
|
||||||
this.damaged = true;
|
|
||||||
core.drawHeroAnimate('hand');
|
|
||||||
popDamage(this.damage, this.boss, '#ff8180');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private split(boss: PalaceBoss) {
|
|
||||||
if (!this.splitData?.split) return;
|
|
||||||
if (this.splitted) return;
|
|
||||||
this.splitted = true;
|
|
||||||
const {
|
|
||||||
startAngle,
|
|
||||||
endAngle,
|
|
||||||
startVel,
|
|
||||||
endVel,
|
|
||||||
acc,
|
|
||||||
lastTime,
|
|
||||||
count,
|
|
||||||
data
|
|
||||||
} = this.splitData;
|
|
||||||
|
|
||||||
const sa = this.angle + startAngle;
|
|
||||||
const ea = this.angle + endAngle;
|
|
||||||
const step = (ea - sa - 1) / count;
|
|
||||||
const { x, y } = this.hitbox;
|
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const proj = boss.createProjectile(SplittableBall, x, y);
|
|
||||||
proj.setAngle(sa + step * i);
|
|
||||||
proj.setAcc(acc);
|
|
||||||
proj.setVel(startVel, endVel);
|
|
||||||
proj.setLastTime(lastTime);
|
|
||||||
proj.setSplitData(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ai(boss: PalaceBoss, time: number, frame: number, dt: number): void {
|
|
||||||
if (this.splitData?.split) {
|
|
||||||
if (time > this.splitData.time) {
|
|
||||||
this.split(boss);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (time > this.last) {
|
|
||||||
this.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const p = dt / 1000;
|
|
||||||
this.vx += this.ax * p;
|
|
||||||
this.vy += this.ay * p;
|
|
||||||
|
|
||||||
const sx = Math.sign(this.vx);
|
|
||||||
const sy = Math.sign(this.vy);
|
|
||||||
const cx = clamp(
|
|
||||||
Math.abs(this.vx),
|
|
||||||
Math.abs(this.startVelX),
|
|
||||||
Math.abs(this.endVelX)
|
|
||||||
);
|
|
||||||
const cy = clamp(
|
|
||||||
Math.abs(this.vy),
|
|
||||||
Math.abs(this.startVelY),
|
|
||||||
Math.abs(this.endVelY)
|
|
||||||
);
|
|
||||||
this.vx = cx * sx;
|
|
||||||
this.vy = cy * sy;
|
|
||||||
|
|
||||||
const { x, y } = this.hitbox;
|
|
||||||
this.setPosition(x + this.vx * p, y + this.vy * p);
|
|
||||||
}
|
|
||||||
|
|
||||||
render(canvas: MotaOffscreenCanvas2D, transform: Transform): void {
|
|
||||||
if (!this.color) return;
|
|
||||||
const texture = SplittableBall.ball.get(this.color);
|
|
||||||
if (!texture) return;
|
|
||||||
const ctx = canvas.ctx;
|
|
||||||
ctx.drawImage(texture.canvas, this.x - 16, this.y - 16, 32, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): void {
|
|
||||||
this.split(this.boss);
|
|
||||||
super.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
@ -179,8 +179,6 @@ export class TowerBoss extends BarrageBoss {
|
|||||||
this.healthBar.remove();
|
this.healthBar.remove();
|
||||||
this.word.remove();
|
this.word.remove();
|
||||||
this.main.remove();
|
this.main.remove();
|
||||||
this.main.destroy();
|
|
||||||
this.healthBar.destroy();
|
|
||||||
|
|
||||||
const event = this.group.getLayer('event');
|
const event = this.group.getLayer('event');
|
||||||
const hero = event?.getExtends('floor-hero') as HeroRenderer;
|
const hero = event?.getExtends('floor-hero') as HeroRenderer;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as fly from './ui/fly';
|
import * as fly from './ui/fly';
|
||||||
import * as chase from './chase';
|
import * as chase from './chase';
|
||||||
import * as completion from './completion';
|
import * as completion from './completion';
|
||||||
|
import * as pop from './pop';
|
||||||
import * as use from './use';
|
import * as use from './use';
|
||||||
import * as gameCanvas from './fx/gameCanvas';
|
import * as gameCanvas from './fx/gameCanvas';
|
||||||
import * as animateController from './animateController';
|
import * as animateController from './animateController';
|
||||||
@ -12,6 +13,7 @@ import './loopMap';
|
|||||||
Mota.Plugin.register('fly_r', fly);
|
Mota.Plugin.register('fly_r', fly);
|
||||||
Mota.Plugin.register('chase_r', chase);
|
Mota.Plugin.register('chase_r', chase);
|
||||||
Mota.Plugin.register('completion_r', completion, completion.init);
|
Mota.Plugin.register('completion_r', completion, completion.init);
|
||||||
|
Mota.Plugin.register('pop_r', pop, pop.init);
|
||||||
Mota.Plugin.register('use_r', use);
|
Mota.Plugin.register('use_r', use);
|
||||||
Mota.Plugin.register('gameCanvas_r', gameCanvas);
|
Mota.Plugin.register('gameCanvas_r', gameCanvas);
|
||||||
Mota.Plugin.register(
|
Mota.Plugin.register(
|
||||||
|
60
src/plugin/pop.ts
Normal file
60
src/plugin/pop.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 示例插件:文字弹出
|
||||||
|
// todo: 重写
|
||||||
|
|
||||||
|
let pop: any[] = [];
|
||||||
|
|
||||||
|
let time = 0;
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
// core.registerAnimationFrame('pop', true, popValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出文字
|
||||||
|
*/
|
||||||
|
function popValue(t: number) {
|
||||||
|
if (t - time < 15) return;
|
||||||
|
let ctx = core.getContextByName('pop')!;
|
||||||
|
if (!ctx) ctx = core.createCanvas('pop', 0, 0, core._PX_, core._PY_, 90);
|
||||||
|
core.clearMap(ctx);
|
||||||
|
let count = 0;
|
||||||
|
pop.forEach(function (one) {
|
||||||
|
// 由frame计算出dy
|
||||||
|
const dy = 6 - one.frame * 0.2;
|
||||||
|
const dx = 1;
|
||||||
|
one.py -= dy;
|
||||||
|
one.px += dx;
|
||||||
|
one.frame++;
|
||||||
|
// 绘制
|
||||||
|
if (one.frame >= 60) core.setAlpha(ctx, 3 - one.frame / 30);
|
||||||
|
else core.setAlpha(ctx, 1);
|
||||||
|
core.fillBoldText(
|
||||||
|
ctx,
|
||||||
|
one.value,
|
||||||
|
one.px,
|
||||||
|
one.py,
|
||||||
|
'#f22',
|
||||||
|
'#000',
|
||||||
|
'24px normal'
|
||||||
|
);
|
||||||
|
if (one.frame >= 90) count++;
|
||||||
|
});
|
||||||
|
if (count > 0) pop.splice(0, count);
|
||||||
|
time = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加弹出文字
|
||||||
|
* @param px 弹出的横坐标
|
||||||
|
* @param py 弹出的纵坐标
|
||||||
|
* @param value 弹出的文字
|
||||||
|
*/
|
||||||
|
export function addPop(px: number, py: number, value: string) {
|
||||||
|
var data = {
|
||||||
|
px: px,
|
||||||
|
py: py,
|
||||||
|
value: value,
|
||||||
|
frame: 0
|
||||||
|
};
|
||||||
|
pop.push(data);
|
||||||
|
}
|
@ -472,10 +472,10 @@ export function formatSize(size: number) {
|
|||||||
return size < 1 << 10
|
return size < 1 << 10
|
||||||
? `${size.toFixed(2)}B`
|
? `${size.toFixed(2)}B`
|
||||||
: size < 1 << 20
|
: size < 1 << 20
|
||||||
? `${(size / (1 << 10)).toFixed(2)}KB`
|
? `${(size / (1 << 10)).toFixed(2)}KB`
|
||||||
: size < 1 << 30
|
: size < 1 << 30
|
||||||
? `${(size / (1 << 20)).toFixed(2)}MB`
|
? `${(size / (1 << 20)).toFixed(2)}MB`
|
||||||
: `${(size / (1 << 30)).toFixed(2)}GB`;
|
: `${(size / (1 << 30)).toFixed(2)}GB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let num = 0;
|
let num = 0;
|
||||||
@ -517,11 +517,3 @@ export function calStringSize(str: string) {
|
|||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clamp(num: number, start: number, end: number) {
|
|
||||||
const s = Math.min(start, end);
|
|
||||||
const e = Math.max(start, end);
|
|
||||||
if (num < s) return s;
|
|
||||||
else if (num > e) return e;
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
@ -11,7 +11,7 @@ const FSHOST = 'http://127.0.0.1:3000/';
|
|||||||
|
|
||||||
const custom = [
|
const custom = [
|
||||||
'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom',
|
'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom',
|
||||||
'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon', 'winskin'
|
'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon'
|
||||||
]
|
]
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|
Loading…
Reference in New Issue
Block a user