Compare commits

..

No commits in common. "608cd15f76cbaeab05eb39744f8ac8525639c5a2" and "7cbe35e246ee563b716e124992434af4269964d6" have entirely different histories.

35 changed files with 450 additions and 806 deletions

View File

@ -20,8 +20,14 @@
<body> <body>
<!-- injection --> <!-- injection -->
<div id="game-draw"> <div id="game">
<canvas id="render-main"></canvas> <div id="game-draw">
<canvas class='gameCanvas' id='curtain'></canvas>
<canvas class='gameCanvas' id='ui'></canvas>
<canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
<canvas id="render-main"></canvas>
<div id="next"></div>
</div>
</div> </div>
<div id='inputDiv'> <div id='inputDiv'>
<div id='inputDialog'> <div id='inputDialog'>

View File

@ -44,7 +44,6 @@ export class HeroKeyMover {
down: data[config?.down ?? 'moveDown'] down: data[config?.down ?? 'moveDown']
}; };
// 静止时尝试启动移动
this.ticker.add(() => { this.ticker.add(() => {
if (!this.moving) { if (!this.moving) {
if (this.pressedKey.size > 0) { if (this.pressedKey.size > 0) {
@ -57,10 +56,6 @@ export class HeroKeyMover {
}); });
} }
/**
*
* @param code
*/
private onPressKey = (code: KeyCode) => { private onPressKey = (code: KeyCode) => {
if (core.isReplaying() || !core.isPlaying()) return; if (core.isReplaying() || !core.isPlaying()) return;
core.waitHeroToStop(); core.waitHeroToStop();
@ -70,10 +65,6 @@ export class HeroKeyMover {
else if (code === this.hotkeyData.down.key) this.press('down'); else if (code === this.hotkeyData.down.key) this.press('down');
}; };
/**
*
* @param code
*/
private onReleaseKey = (code: KeyCode) => { private onReleaseKey = (code: KeyCode) => {
if (code === this.hotkeyData.left.key) this.release('left'); if (code === this.hotkeyData.left.key) this.release('left');
else if (code === this.hotkeyData.right.key) this.release('right'); else if (code === this.hotkeyData.right.key) this.release('right');
@ -144,26 +135,18 @@ export class HeroKeyMover {
this.controller?.stop(); this.controller?.stop();
} }
/**
*
*/
private onStepEnd = () => { private onStepEnd = () => {
const con = this.controller; const con = this.controller;
if (!con) return; if (!con) return;
// 被禁止操作时
if (core.status.lockControl) { if (core.status.lockControl) {
con.stop(); con.stop();
return; return;
} }
// 未移动时
if (!this.moving) { if (!this.moving) {
con.stop(); con.stop();
return; return;
} }
// 尝试移动
if (this.pressedKey.size > 0) { if (this.pressedKey.size > 0) {
if (con.queue.length === 0) { if (con.queue.length === 0) {
con.push({ type: 'dir', value: this.moveDir }); con.push({ type: 'dir', value: this.moveDir });

View File

@ -1,5 +1,5 @@
import { gameKey } from '@motajs/system-action'; import { gameKey } from '@motajs/system-action';
import { MAIN_WIDTH, MAIN_HEIGHT, POP_BOX_WIDTH, CENTER_LOC } from './shared'; import { MAIN_WIDTH, MAIN_HEIGHT } from './shared';
import { import {
saveSave, saveSave,
mainUIController, mainUIController,
@ -9,7 +9,6 @@ import {
ReplaySettingsUI, ReplaySettingsUI,
openViewMap openViewMap
} from './ui'; } from './ui';
import { ElementLocator } from '@motajs/render-core';
export function createAction() { export function createAction() {
gameKey gameKey
@ -23,13 +22,11 @@ export function createAction() {
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]); saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}) })
.realize('menu', () => { .realize('menu', () => {
const loc = CENTER_LOC.slice() as ElementLocator; openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
loc[2] = POP_BOX_WIDTH;
openSettings(mainUIController, loc);
}) })
.realize('replay', () => { .realize('replay', () => {
mainUIController.open(ReplaySettingsUI, { mainUIController.open(ReplaySettingsUI, {
loc: CENTER_LOC loc: [420, 240, void 0, void 0, 0.5, 0.5]
}); });
}) })
.realize('viewMap', () => { .realize('viewMap', () => {

View File

@ -9,32 +9,18 @@ import { useKey } from '../use';
import { sleep } from 'mutate-animate'; import { sleep } from 'mutate-animate';
export interface ConfirmBoxProps extends DefaultProps, TextContentProps { export interface ConfirmBoxProps extends DefaultProps, TextContentProps {
/** 确认框的提示文本内容 */
text: string; text: string;
/** 确认框对话框的宽度 */
width: number; width: number;
/** 确认框对话框的位置 */
loc: ElementLocator; loc: ElementLocator;
/** 确认/取消按钮的字体样式 */
selFont?: Font; selFont?: Font;
/** 确认/取消按钮的文本颜色 */
selFill?: CanvasStyle; selFill?: CanvasStyle;
/** 对话框内部所有元素的内边距 */
pad?: number; pad?: number;
/** 确认按钮的显示文本,默认为"确认" */
yesText?: string; yesText?: string;
/** 取消按钮的显示文本,默认为"取消" */
noText?: string; noText?: string;
/** 窗口皮肤图片ID用于对话框背景绘制 */
winskin?: ImageIds; winskin?: ImageIds;
/** 是否默认选中确认按钮 */
defaultYes?: boolean; defaultYes?: boolean;
/** 对话框背景颜色,当未设置 winskin 时生效 */
color?: CanvasStyle; color?: CanvasStyle;
/** 对话框边框颜色,当未设置 winskin 时生效 */
border?: CanvasStyle; border?: CanvasStyle;
/** 按键作用域,如果需要同作用域按键,那么需要传入 */
scope?: symbol;
} }
export type ConfirmBoxEmits = { export type ConfirmBoxEmits = {
@ -55,8 +41,7 @@ const confirmBoxProps = {
'winskin', 'winskin',
'defaultYes', 'defaultYes',
'color', 'color',
'border', 'border'
'scope'
], ],
emits: ['no', 'yes'] emits: ['no', 'yes']
} satisfies SetupComponentOptions< } satisfies SetupComponentOptions<
@ -146,7 +131,7 @@ export const ConfirmBox = defineComponent<
noSize.value = [width, height]; noSize.value = [width, height];
}; };
const [key] = useKey(false, props.scope); const [key] = useKey();
key.realize('confirm', () => { key.realize('confirm', () => {
if (selected.value) emit('yes'); if (selected.value) emit('yes');
else emit('no'); else emit('no');
@ -213,40 +198,22 @@ export type ChoiceItem<T extends ChoiceKey = ChoiceKey> = [
]; ];
export interface ChoicesProps extends DefaultProps, TextContentProps { export interface ChoicesProps extends DefaultProps, TextContentProps {
/** 选项数组 */
choices: ChoiceItem[]; choices: ChoiceItem[];
/** 选择框对话框的位置 */
loc: ElementLocator; loc: ElementLocator;
/** 选择框对话框的宽度 */
width: number; width: number;
/** 选择框的最大高度,超过时将分页显示 */
maxHeight?: number; maxHeight?: number;
/** 选择框的提示文本内容 */
text?: string; text?: string;
/** 选择框的标题文本 */
title?: string; title?: string;
/** 窗口皮肤图片ID用于对话框背景绘制 */
winskin?: ImageIds; winskin?: ImageIds;
/** 对话框背景颜色,当未设置 winskin 时生效 */
color?: CanvasStyle; color?: CanvasStyle;
/** 对话框边框颜色,当未设置 winskin 时生效 */
border?: CanvasStyle; border?: CanvasStyle;
/** 选项文本的字体样式 */
selFont?: Font; selFont?: Font;
/** 选项文本的颜色 */
selFill?: CanvasStyle; selFill?: CanvasStyle;
/** 标题文本的字体样式 */
titleFont?: Font; titleFont?: Font;
/** 标题文本的颜色 */
titleFill?: CanvasStyle; titleFill?: CanvasStyle;
/** 对话框内部所有元素的内边距 */
pad?: number; pad?: number;
/** 选项之间的垂直间隔 */
interval?: number; interval?: number;
/** 默认选中的选项索引 */
selected?: number; selected?: number;
/** 按键作用域,如果需要同作用域按键,那么需要传入,例如系统设置 UI */
scope?: symbol;
} }
export type ChoicesEmits = { export type ChoicesEmits = {
@ -270,8 +237,7 @@ const choicesProps = {
'titleFill', 'titleFill',
'pad', 'pad',
'interval', 'interval',
'selected', 'selected'
'scope'
], ],
emits: ['choose'] emits: ['choose']
} satisfies SetupComponentOptions< } satisfies SetupComponentOptions<
@ -470,7 +436,7 @@ export const Choices = defineComponent<
selected.value = 0; selected.value = 0;
}; };
const [key] = useKey(false, props.scope); const [key] = useKey();
key.realize('moveUp', () => { key.realize('moveUp', () => {
if (selected.value === 0) { if (selected.value === 0) {
if (pageCom.value?.now() !== 0) { if (pageCom.value?.now() !== 0) {
@ -511,25 +477,27 @@ export const Choices = defineComponent<
color={props.color ?? '#333'} color={props.color ?? '#333'}
border={props.border} border={props.border}
/> />
<text {hasTitle.value && (
hidden={!hasTitle.value} <text
loc={titleLoc.value} loc={titleLoc.value}
text={props.title} text={props.title}
font={props.titleFont ?? new Font(void 0, 18)} font={props.titleFont ?? new Font(void 0, 18)}
fillStyle={props.titleFill ?? 'gold'} fillStyle={props.titleFill ?? 'gold'}
zIndex={5} zIndex={5}
onSetText={updateTitleHeight} onSetText={updateTitleHeight}
/> />
<TextContent )}
{...attrs} {hasText.value && (
hidden={!hasText.value} <TextContent
text={props.text} {...attrs}
loc={contentLoc.value} text={props.text}
width={contentWidth.value} loc={contentLoc.value}
zIndex={5} width={contentWidth.value}
autoHeight zIndex={5}
onUpdateHeight={updateContentHeight} autoHeight
/> onUpdateHeight={updateContentHeight}
/>
)}
<Page <Page
ref={pageCom} ref={pageCom}
loc={choiceLoc.value} loc={choiceLoc.value}

View File

@ -5,14 +5,6 @@ import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { Scroll, ScrollExpose } from './scroll'; import { Scroll, ScrollExpose } from './scroll';
import { Font } from '@motajs/render-style'; import { Font } from '@motajs/render-style';
import { MotaOffscreenCanvas2D } from '@motajs/render-core'; import { MotaOffscreenCanvas2D } from '@motajs/render-core';
import {
HALF_STATUS_WIDTH,
STATUS_BAR_HEIGHT,
STATUS_BAR_WIDTH
} from '../shared';
const SCROLL_HEIGHT = STATUS_BAR_HEIGHT - 280;
const HALF_HEIGHT = SCROLL_HEIGHT / 2;
export interface FloorSelectorProps extends DefaultProps { export interface FloorSelectorProps extends DefaultProps {
floors: FloorIds[]; floors: FloorIds[];
@ -75,7 +67,7 @@ export const FloorSelector = defineComponent<
const getGradient = (ctx: CanvasRenderingContext2D) => { const getGradient = (ctx: CanvasRenderingContext2D) => {
if (gradient) return gradient; if (gradient) return gradient;
gradient = ctx.createLinearGradient(0, 0, 0, SCROLL_HEIGHT); gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, 'rgba(255,255,255,0)'); gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.2, 'rgba(255,255,255,1)'); gradient.addColorStop(0.2, 'rgba(255,255,255,1)');
gradient.addColorStop(0.8, 'rgba(255,255,255,1)'); gradient.addColorStop(0.8, 'rgba(255,255,255,1)');
@ -120,24 +112,12 @@ export const FloorSelector = defineComponent<
return () => ( return () => (
<container> <container>
<text <text text={floorName.value} loc={[90, 24]} anc={[0.5, 0.5]} />
text={floorName.value} <g-line line={[48, 40, 132, 40]} lineWidth={1} />
loc={[HALF_STATUS_WIDTH, 24]} <g-line line={[48, 440, 132, 440]} lineWidth={1} />
anc={[0.5, 0.5]}
/>
<g-line line={[48, 40, STATUS_BAR_WIDTH - 48, 40]} lineWidth={1} />
<g-line
line={[
48,
STATUS_BAR_HEIGHT - 40,
STATUS_BAR_WIDTH - 48,
STATUS_BAR_HEIGHT - 40
]}
lineWidth={1}
/>
<text <text
text="退出" text="退出"
loc={[90, STATUS_BAR_HEIGHT - 24]} loc={[90, 456]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={close} onClick={close}
@ -158,25 +138,25 @@ export const FloorSelector = defineComponent<
/> />
<text <text
text="「 下移一层 」" text="「 下移一层 」"
loc={[90, STATUS_BAR_HEIGHT - 110]} loc={[90, 370]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={() => changeFloor(-1)} onClick={() => changeFloor(-1)}
/> />
<text <text
text="「 下移十层 」" text="「 下移十层 」"
loc={[90, STATUS_BAR_HEIGHT - 70]} loc={[90, 410]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={() => changeFloor(-10)} onClick={() => changeFloor(-10)}
/> />
<container loc={[0, 140, 144, SCROLL_HEIGHT]}> <container loc={[0, 140, 144, 200]}>
<Scroll <Scroll
ref={scrollRef} ref={scrollRef}
loc={[0, 0, 144, SCROLL_HEIGHT]} loc={[0, 0, 144, 200]}
noscroll noscroll
zIndex={10} zIndex={10}
padEnd={HALF_HEIGHT - 12} padEnd={88}
> >
{floors.value.map((v, i, a) => { {floors.value.map((v, i, a) => {
const floor = core.floors[v]; const floor = core.floors[v];
@ -188,7 +168,7 @@ export const FloorSelector = defineComponent<
return ( return (
<container <container
nocache nocache
loc={[0, i * 24 + HALF_HEIGHT - 12, 144, 24]} loc={[0, i * 24 + 88, 144, 24]}
key={v} key={v}
> >
<text <text
@ -215,14 +195,14 @@ export const FloorSelector = defineComponent<
})} })}
</Scroll> </Scroll>
<g-line <g-line
line={[130, 0, 130, SCROLL_HEIGHT]} line={[130, 0, 130, 200]}
zIndex={5} zIndex={5}
lineWidth={1} lineWidth={1}
strokeStyle="#aaa" strokeStyle="#aaa"
/> />
<sprite <sprite
zIndex={20} zIndex={20}
loc={[0, 0, 144, SCROLL_HEIGHT]} loc={[0, 0, 144, 200]}
nocache nocache
noevent noevent
render={renderMask} render={renderMask}

View File

@ -146,7 +146,6 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
const renderer = MotaRenderer.get('render-main'); const renderer = MotaRenderer.get('render-main');
const canvas = renderer?.getCanvas(); const canvas = renderer?.getCanvas();
if (!canvas) return; if (!canvas) return;
const chain: RenderItem[] = []; const chain: RenderItem[] = [];
let now: RenderItem | undefined = root.value; let now: RenderItem | undefined = root.value;
if (!now) return; if (!now) return;
@ -154,8 +153,6 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
chain.unshift(now); chain.unshift(now);
now = now.parent; now = now.parent;
} }
// 应用内边距偏移
const { clientLeft, clientTop } = canvas; const { clientLeft, clientTop } = canvas;
const trans = new Transform(); const trans = new Transform();
trans.translate(clientLeft, clientTop); trans.translate(clientLeft, clientTop);
@ -166,11 +163,8 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
trans.multiply(item.transform); trans.multiply(item.transform);
} }
trans.translate(padding.value, padding.value); trans.translate(padding.value, padding.value);
// 构建CSS transform的matrix字符串
const [a, b, , c, d, , e, f] = trans.mat; const [a, b, , c, d, , e, f] = trans.mat;
const str = `matrix(${a},${b},${c},${d},${e},${f})`; const str = `matrix(${a},${b},${c},${d},${e},${f})`;
const w = width.value * core.domStyle.scale; const w = width.value * core.domStyle.scale;
const h = height.value * core.domStyle.scale; const h = height.value * core.domStyle.scale;
const font = props.font ?? Font.defaults(); const font = props.font ?? Font.defaults();
@ -243,29 +237,17 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
); );
export interface InputBoxProps extends TextContentProps { export interface InputBoxProps extends TextContentProps {
/** 输入框对话框的位置 */
loc: ElementLocator; loc: ElementLocator;
/** 传递给内部 Input 组件的配置参数,用于自定义输入行为 */
input?: InputProps; input?: InputProps;
/** 窗口皮肤图片ID用于对话框背景绘制 */
winskin?: ImageIds; winskin?: ImageIds;
/** 对话框背景颜色,当未设置 winskin 时生效 */
color?: CanvasStyle; color?: CanvasStyle;
/** 对话框边框颜色,当未设置 winskin 时生效 */
border?: CanvasStyle; border?: CanvasStyle;
/** 对话框内部所有元素的内边距 */
pad?: number; pad?: number;
/** 内部输入框区域的高度 */
inputHeight?: number; inputHeight?: number;
/** 对话框顶部的提示文本 */
text?: string; text?: string;
/** 确认按钮的显示文本,默认为"确认" */
yesText?: string; yesText?: string;
/** 取消按钮的显示文本,默认为"取消" */
noText?: string; noText?: string;
/** 确认/取消按钮的字体样式 */
selFont?: Font; selFont?: Font;
/** 确认/取消按钮的文本颜色 */
selFill?: CanvasStyle; selFill?: CanvasStyle;
} }
@ -523,11 +505,6 @@ export function getInput(
/** /**
* `getInput` {@link getInput} * `getInput` {@link getInput}
* @param controller UI
* @param text
* @param loc
* @param width
* @param props props {@link ConfirmBoxProps}
*/ */
export async function getInputNumber( export async function getInputNumber(
controller: IUIMountable, controller: IUIMountable,

View File

@ -118,16 +118,6 @@ export const Page = defineComponent<
const height = computed(() => props.loc[3] ?? 200); const height = computed(() => props.loc[3] ?? 200);
const round = computed(() => font.value.size / 4); const round = computed(() => font.value.size / 4);
const nowPageFont = computed(() => Font.clone(font.value, { weight: 700 })); const nowPageFont = computed(() => Font.clone(font.value, { weight: 700 }));
/** 页码的横向间距 */
const interval = computed(() => {
const size = font.value.size * 1.5;
const max = size * 9;
if (width.value > max) {
return size;
} else {
return (width.value - size * 5) / 4;
}
});
// 左右箭头的颜色 // 左右箭头的颜色
const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd')); const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd'));
@ -145,12 +135,11 @@ export const Page = defineComponent<
pageLoc.value = [0, height.value - pageH, width.value, pageH]; pageLoc.value = [0, height.value - pageH, width.value, pageH];
const center = width.value / 2; const center = width.value / 2;
const size = font.value.size * 1.5; const size = font.value.size * 1.5;
const int = size + interval.value;
nowPageLoc.value = [center, 0, size, size, 0.5, 0]; nowPageLoc.value = [center, 0, size, size, 0.5, 0];
leftPageLoc.value = [center - int, 0, size, size, 0.5, 0]; leftPageLoc.value = [center - size * 1.5, 0, size, size, 0.5, 0];
leftLoc.value = [center - int * 2, 0, size, size, 0.5, 0]; leftLoc.value = [center - size * 3, 0, size, size, 0.5, 0];
rightPageLoc.value = [center + int, 0, size, size, 0.5, 0]; rightPageLoc.value = [center + size * 1.5, 0, size, size, 0.5, 0];
rightLoc.value = [center + int * 2, 0, size, size, 0.5, 0]; rightLoc.value = [center + size * 3, 0, size, size, 0.5, 0];
}; };
const updateArrowPath = () => { const updateArrowPath = () => {

View File

@ -54,7 +54,6 @@ export interface ScrollExpose {
export interface ScrollProps extends DefaultProps { export interface ScrollProps extends DefaultProps {
loc: ElementLocator; loc: ElementLocator;
hor?: boolean; hor?: boolean;
/** 是否不允许滚动 */
noscroll?: boolean; noscroll?: boolean;
/** /**
* *
@ -361,8 +360,6 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
//#region 事件监听 //#region 事件监听
// todo: 滑动操作时的滚动惯性
const customPropagate = <T extends ActionType>( const customPropagate = <T extends ActionType>(
type: T, type: T,
progress: EventProgress, progress: EventProgress,

View File

@ -130,7 +130,7 @@ export const TextContent = defineComponent<
let needUpdate = false; let needUpdate = false;
const retype = () => { const retype = () => {
if (needUpdate || props.hidden) return; if (needUpdate) return;
needUpdate = true; needUpdate = true;
if (!spriteElement.value) { if (!spriteElement.value) {
needUpdate = false; needUpdate = false;
@ -500,28 +500,18 @@ export const Textbox = defineComponent<
}, textboxOptions); }, textboxOptions);
interface TextboxStoreEmits { interface TextboxStoreEmits {
/** 结束打字机动画的回调函数 */
endType: () => void; endType: () => void;
/** 隐藏文本框的回调函数 */
hide: () => void; hide: () => void;
/** 显示文本框的回调函数 */
show: () => void; show: () => void;
/** 更新文本框配置的回调函数 */
update: (value: TextboxProps) => void; update: (value: TextboxProps) => void;
/** 设置显示文本的回调函数 */
setText: (text: string) => void; setText: (text: string) => void;
} }
interface TextboxStoreEvent { interface TextboxStoreEvent {
/** 文本框配置更新事件,传递更新后的配置值 */
update: [value: TextboxProps]; update: [value: TextboxProps];
/** 文本框显示事件 */
show: []; show: [];
/** 文本框隐藏事件 */
hide: []; hide: [];
/** 打字机开始打字事件 */
typeStart: []; typeStart: [];
/** 打字机结束打字事件 */
typeEnd: []; typeEnd: [];
} }

View File

@ -75,15 +75,10 @@ interface TyperConfig extends ITextContentConfig {
} }
interface ParserStatus { interface ParserStatus {
/** 画布填充描边样式 */
fillStyle: CanvasStyle; fillStyle: CanvasStyle;
/** 描边样式 */
fontFamily: string; fontFamily: string;
/** 字体大小 */
fontSize: number; fontSize: number;
/** 是否斜体 */
fontItalic: boolean; fontItalic: boolean;
/** 字体粗细 */
fontWeight: number; fontWeight: number;
} }

View File

@ -8,15 +8,10 @@ import { defineComponent, ref, watch } from 'vue';
import { SetupComponentOptions } from '@motajs/system-ui'; import { SetupComponentOptions } from '@motajs/system-ui';
export interface ThumbnailProps extends SpriteProps { export interface ThumbnailProps extends SpriteProps {
/** 缩略图的位置 */
loc: ElementLocator; loc: ElementLocator;
/** 楼层 ID */
floorId: FloorIds; floorId: FloorIds;
/** 缩略图填充样式 */
padStyle?: CanvasStyle; padStyle?: CanvasStyle;
/** 楼层信息 */
map?: Block[]; map?: Block[];
/** 角色信息 */
hero?: HeroStatus; hero?: HeroStatus;
// configs // configs
damage?: boolean; damage?: boolean;

View File

@ -8,13 +8,9 @@ import { texture } from '../elements';
import { SetupComponentOptions } from '@motajs/system-ui'; import { SetupComponentOptions } from '@motajs/system-ui';
export interface TipProps extends DefaultProps { export interface TipProps extends DefaultProps {
/** 显示的位置 */
loc: ElementLocator; loc: ElementLocator;
/** 边距 */
pad?: [number, number]; pad?: [number, number];
/** 圆角 */
corner?: number; corner?: number;
/** 显示的图标 */
id?: string; id?: string;
} }

View File

@ -13,12 +13,6 @@ import { RenderableData, texture } from './cache';
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block'; import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
import { IAnimateFrame, renderEmits } from './frame'; import { IAnimateFrame, renderEmits } from './frame';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import {
MAP_BLOCK_HEIGHT,
MAP_BLOCK_WIDTH,
MAP_HEIGHT,
MAP_WIDTH
} from '../shared';
export interface ILayerGroupRenderExtends { export interface ILayerGroupRenderExtends {
/** 拓展的唯一标识符 */ /** 拓展的唯一标识符 */
@ -110,7 +104,7 @@ export class LayerGroup
// static list: Set<LayerGroup> = new Set(); // static list: Set<LayerGroup> = new Set();
cellSize: number = 32; cellSize: number = 32;
blockSize: number = MAP_BLOCK_WIDTH; blockSize: number = core._WIDTH_;
/** 当前楼层 */ /** 当前楼层 */
floorId?: FloorIds; floorId?: FloorIds;
@ -132,7 +126,7 @@ export class LayerGroup
this.setHD(true); this.setHD(true);
this.setAntiAliasing(false); this.setAntiAliasing(false);
this.size(MAP_WIDTH, MAP_HEIGHT); this.size(core._PX_, core._PY_);
this.on('afterRender', () => { this.on('afterRender', () => {
this.releaseNeedRender(); this.releaseNeedRender();
@ -384,8 +378,8 @@ export function calNeedRenderOf(
cell: number, cell: number,
block: BlockCacher<any> block: BlockCacher<any>
): Set<number> { ): Set<number> {
const w = MAP_BLOCK_WIDTH * cell; const w = core._WIDTH_ * cell;
const h = MAP_BLOCK_HEIGHT * cell; const h = core._HEIGHT_ * cell;
const size = block.blockSize; const size = block.blockSize;
const width = block.blockData.width; const width = block.blockData.width;
@ -610,7 +604,7 @@ export class Layer extends Container<ELayerEvent> {
block: BlockCacher<ICanvasCacheItem> = new BlockCacher( block: BlockCacher<ICanvasCacheItem> = new BlockCacher(
0, 0,
0, 0,
MAP_BLOCK_WIDTH, core._WIDTH_,
4 4
); );
@ -631,20 +625,20 @@ export class Layer extends Container<ELayerEvent> {
// this.setHD(false); // this.setHD(false);
this.setAntiAliasing(false); this.setAntiAliasing(false);
this.size(MAP_WIDTH, MAP_HEIGHT); this.size(core._PX_, core._PY_);
this.staticMap.setHD(false); this.staticMap.setHD(false);
this.staticMap.setAntiAliasing(false); this.staticMap.setAntiAliasing(false);
this.staticMap.size(MAP_WIDTH, MAP_HEIGHT); this.staticMap.size(core._PX_, core._PY_);
this.movingMap.setHD(false); this.movingMap.setHD(false);
this.movingMap.setAntiAliasing(false); this.movingMap.setAntiAliasing(false);
this.movingMap.size(MAP_WIDTH, MAP_HEIGHT); this.movingMap.size(core._PX_, core._PY_);
this.backMap.setHD(false); this.backMap.setHD(false);
this.backMap.setAntiAliasing(false); this.backMap.setAntiAliasing(false);
this.backMap.size(MAP_WIDTH, MAP_HEIGHT); this.backMap.size(core._PX_, core._PY_);
this.main.setAntiAliasing(false); this.main.setAntiAliasing(false);
this.main.setHD(false); this.main.setHD(false);
this.main.size(MAP_WIDTH, MAP_HEIGHT); this.main.size(core._PX_, core._PY_);
this.appendChild(this.main); this.appendChild(this.main);
this.main.setRenderFn((canvas, transform) => { this.main.setRenderFn((canvas, transform) => {
@ -795,7 +789,7 @@ export class Layer extends Container<ELayerEvent> {
const [sx, sy, w, h] = data.render[i]; const [sx, sy, w, h] = data.render[i];
canvas.setHD(false); canvas.setHD(false);
canvas.setAntiAliasing(false); canvas.setAntiAliasing(false);
canvas.size(MAP_WIDTH, MAP_HEIGHT); canvas.size(core._PX_, core._PY_);
temp.size(w, h); temp.size(w, h);
const img = data.autotile ? data.image[0b11111111] : data.image; const img = data.autotile ? data.image[0b11111111] : data.image;
@ -1210,7 +1204,7 @@ export class Layer extends Container<ELayerEvent> {
const temp = this.requireCanvas(true, false); const temp = this.requireCanvas(true, false);
temp.setAntiAliasing(false); temp.setAntiAliasing(false);
temp.setHD(false); temp.setHD(false);
temp.size(MAP_WIDTH, MAP_HEIGHT); temp.size(core._PX_, core._PY_);
// 先画到临时画布,用于缓存 // 先画到临时画布,用于缓存
for (let nx = sx; nx < ex; nx++) { for (let nx = sx; nx < ex; nx++) {
@ -1267,7 +1261,7 @@ export class Layer extends Container<ELayerEvent> {
const [a, b, , c, d, , e, f] = mat; const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(a, b, c, d, e, f); ctx.setTransform(a, b, c, d, e, f);
const max1 = 1 / Math.min(a, b, c, d) ** 2; const max1 = 1 / Math.min(a, b, c, d) ** 2;
const max2 = Math.max(MAP_WIDTH, MAP_HEIGHT) * 2; const max2 = Math.max(core._PX_, core._PY_) * 2;
const r = (max1 * max2) ** 2; const r = (max1 * max2) ** 2;
this.movingRenderable.forEach(v => { this.movingRenderable.forEach(v => {

View File

@ -1,103 +1,10 @@
import { ElementLocator } from '@motajs/render-core';
// 本文件为 UI 配置文件,你可以修改下面的每个常量来控制 UI 的显示参数,每个常量都有注释说明
//#region 地图
/** 每个格子的宽高 */
export const CELL_SIZE = 32;
/** 地图格子宽度,此处仅影响画面,不影响游戏内逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
export const MAP_BLOCK_WIDTH = 15;
/** 地图格子高度,此处仅影响画面,不影响游戏内逻辑,游戏内逻辑地图大小请在 core.js 中修改 */
export const MAP_BLOCK_HEIGHT = 15;
/** 地图像素宽度 */
export const MAP_WIDTH = CELL_SIZE * MAP_BLOCK_WIDTH;
/** 地图像素高度 */
export const MAP_HEIGHT = CELL_SIZE * MAP_BLOCK_HEIGHT;
/** 地图宽度的一半 */
export const HALF_MAP_WIDTH = MAP_WIDTH / 2;
/** 地图高度的一半 */
export const HALF_MAP_HEIGHT = MAP_HEIGHT / 2;
//#region 状态栏
/** 状态栏像素宽度 */
export const STATUS_BAR_WIDTH = 180; export const STATUS_BAR_WIDTH = 180;
/** 状态栏像素高度 */ export const STATUS_BAR_HEIGHT = 480;
export const STATUS_BAR_HEIGHT = 32 * MAP_BLOCK_HEIGHT;
/** 右侧状态栏的横坐标 */
export const RIGHT_STATUS_POS = STATUS_BAR_WIDTH + MAP_WIDTH;
/** 是否启用右侧状态栏 */
export const ENABLE_RIGHT_STATUS_BAR = true; export const ENABLE_RIGHT_STATUS_BAR = true;
/** 状态栏数量,启用右侧状态栏为两个,不启用为一个 */
export const STATUS_BAR_COUNT = ENABLE_RIGHT_STATUS_BAR ? 2 : 1;
/** 状态栏宽度的一半 */
export const HALF_STATUS_WIDTH = STATUS_BAR_WIDTH / 2;
//#region 游戏画面 export const MAP_WIDTH = 480;
export const MAP_HEIGHT = 480;
/** 游戏画面像素宽度,宽=地图宽度+状态栏宽度*状态栏数量 */ export const MAIN_WIDTH = 480 + 180 * 2;
export const MAIN_WIDTH = MAP_WIDTH + STATUS_BAR_WIDTH * STATUS_BAR_COUNT; export const MAIN_HEIGHT = 480;
/** 游戏画面像素高度 */
export const MAIN_HEIGHT = MAP_HEIGHT;
/** 游戏画面宽度的一半 */
export const HALF_WIDTH = MAIN_WIDTH / 2;
/** 游戏画面高度的一半 */
export const HALF_HEIGHT = MAIN_HEIGHT / 2;
/** 全屏显示的 loc */
export const FULL_LOC: ElementLocator = [0, 0, MAIN_WIDTH, MAIN_HEIGHT];
/** 居中显示的 loc */
export const CENTER_LOC: ElementLocator = [
HALF_WIDTH,
HALF_HEIGHT,
void 0,
void 0,
0.5,
0.5
];
//#region 通用配置
/** 弹框的宽度,使用在内置 UI 与组件中,包括确认框、选择框、等待框等 */
export const POP_BOX_WIDTH = MAP_WIDTH / 2;
//#region 存档界面
/** 存档缩略图尺寸 */
export const SAVE_ITEM_SIZE = 150;
/** 单个存档上方显示第几号存档的高度 */
export const SAVE_ITEM_TOP = 24;
/** 单个存档下方显示这个存档信息的高度 */
export const SAVE_ITEM_DOWN = 16;
/** 单个存档高度,包括存档下方的信息 */
export const SAVE_ITEM_HEIGHT = SAVE_ITEM_SIZE + SAVE_ITEM_TOP + SAVE_ITEM_DOWN;
/** 存档间距 */
export const SAVE_INTERVAL = 30;
/** 存档下巴高度,即下方显示页码和返回按钮的高度 */
export const SAVE_DOWN_PAD = 30;
/** 存档页码数,调高并不会影响性能,但是如果玩家存档太多的话会导致存档体积很大 */
export const SAVE_PAGES = 1000;
//#region 标题界面
/** 标题文字宽度 */
export const TITLE_WIDTH = 640;
/** 标题文字高度 */
export const TITLE_HEIGHT = 100;
/** 标题文字宽度的一半 */
export const HALF_TITLE_WIDTH = TITLE_WIDTH / 2;
/** 标题文字高度的一半 */
export const HALF_TITLE_HEIGHT = TITLE_HEIGHT / 2;
/** 标题文字中心横坐标 */
export const TITLE_X = HALF_WIDTH;
/** 标题文字中心纵坐标 */
export const TITLE_Y = 120;
/** 标题界面按钮宽度,如果文字被裁剪可以考虑扩大此值 */
export const BUTTONS_WIDTH = 200;
/** 标题界面按钮高度,如果文字被裁剪可以考虑扩大此值 */
export const BUTTONS_HEIGHT = 160;
/** 标题界面按钮左上角横坐标 */
export const BUTTONS_X = 50;
/** 标题界面按钮左上角纵坐标 */
export const BUTTONS_Y = MAIN_HEIGHT - BUTTONS_HEIGHT;

View File

@ -5,8 +5,7 @@ import {
IActionEvent, IActionEvent,
MotaOffscreenCanvas2D, MotaOffscreenCanvas2D,
Sprite, Sprite,
onTick, onTick
transformCanvas
} from '@motajs/render'; } from '@motajs/render';
import { WeatherController } from '../weather'; import { WeatherController } from '../weather';
import { import {
@ -20,12 +19,8 @@ import {
import { Textbox, Tip } from '../components'; import { Textbox, Tip } from '../components';
import { GameUI } from '@motajs/system-ui'; import { GameUI } from '@motajs/system-ui';
import { import {
ENABLE_RIGHT_STATUS_BAR,
MAIN_HEIGHT, MAIN_HEIGHT,
MAIN_WIDTH, MAIN_WIDTH,
MAP_HEIGHT,
MAP_WIDTH,
RIGHT_STATUS_POS,
STATUS_BAR_HEIGHT, STATUS_BAR_HEIGHT,
STATUS_BAR_WIDTH STATUS_BAR_WIDTH
} from '../shared'; } from '../shared';
@ -76,7 +71,7 @@ const MainScene = defineComponent(() => {
const mainTextboxProps: Props<typeof Textbox> = { const mainTextboxProps: Props<typeof Textbox> = {
text: '', text: '',
hidden: true, hidden: true,
loc: [0, MAP_HEIGHT - 150, MAIN_WIDTH, 150], loc: [0, 330, 480, 150],
zIndex: 30, zIndex: 30,
fillStyle: '#fff', fillStyle: '#fff',
titleFill: 'gold', titleFill: 'gold',
@ -85,7 +80,7 @@ const MainScene = defineComponent(() => {
winskin: 'winskin2.png', winskin: 'winskin2.png',
interval: 100, interval: 100,
lineHeight: 4, lineHeight: 4,
width: MAP_WIDTH width: 480
}; };
const map = shallowRef<LayerGroup>(); const map = shallowRef<LayerGroup>();
@ -212,9 +207,7 @@ const MainScene = defineComponent(() => {
const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => { const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => {
const step = core.status.stepPostfix; const step = core.status.stepPostfix;
const camera = map.value?.camera; if (!step) return;
if (!step || !camera) return;
transformCanvas(canvas, camera);
const ctx = canvas.ctx; const ctx = canvas.ctx;
ctx.fillStyle = '#fff'; ctx.fillStyle = '#fff';
step.forEach(({ x, y, direction }) => { step.forEach(({ x, y, direction }) => {
@ -274,13 +267,10 @@ const MainScene = defineComponent(() => {
status={leftStatus} status={leftStatus}
hidden={hideStatus.value} hidden={hideStatus.value}
></LeftStatusBar> ></LeftStatusBar>
<g-line <g-line line={[180, 0, 180, 480]} lineWidth={1} />
line={[STATUS_BAR_WIDTH, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
lineWidth={1}
/>
<container <container
id="map-draw" id="map-draw"
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]} loc={[180, 0, 480, 480]}
zIndex={10} zIndex={10}
onClick={clickMap} onClick={clickMap}
onDown={downMap} onDown={downMap}
@ -305,20 +295,17 @@ const MainScene = defineComponent(() => {
/> />
<sprite <sprite
noevent noevent
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]} loc={[0, 0, 480, 480]}
ref={mapMiscSprite} ref={mapMiscSprite}
zIndex={170} zIndex={170}
render={renderMapMisc} render={renderMapMisc}
/> />
</container> </container>
<g-line <g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
line={[RIGHT_STATUS_POS, 0, RIGHT_STATUS_POS, MAP_HEIGHT]}
lineWidth={1}
/>
<RightStatusBar <RightStatusBar
loc={[RIGHT_STATUS_POS, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]} loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
status={rightStatus} status={rightStatus}
hidden={hideStatus.value && ENABLE_RIGHT_STATUS_BAR} hidden={hideStatus.value}
></RightStatusBar> ></RightStatusBar>
<container <container
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]} loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
@ -335,17 +322,12 @@ const MainScene = defineComponent(() => {
noevent noevent
></g-rect> ></g-rect>
<g-line <g-line
line={[STATUS_BAR_WIDTH, 0, RIGHT_STATUS_POS, 0]} line={[180, 0, 480 + 180, 0]}
hidden={!hideStatus.value} hidden={!hideStatus.value}
zIndex={100} zIndex={100}
/> />
<g-line <g-line
line={[ line={[180, 480, 480 + 180, 480]}
STATUS_BAR_WIDTH,
MAP_HEIGHT,
RIGHT_STATUS_POS,
MAP_HEIGHT
]}
hidden={!hideStatus.value} hidden={!hideStatus.value}
zIndex={100} zIndex={100}
/> />

View File

@ -16,19 +16,7 @@ import {
} from 'vue'; } from 'vue';
import { getConfirm, Page, PageExpose, Thumbnail } from '../components'; import { getConfirm, Page, PageExpose, Thumbnail } from '../components';
import { useKey } from '../use'; import { useKey } from '../use';
import { import { MAP_WIDTH } from '../shared';
HALF_HEIGHT,
HALF_WIDTH,
MAP_WIDTH,
POP_BOX_WIDTH,
SAVE_DOWN_PAD,
SAVE_INTERVAL,
SAVE_ITEM_DOWN,
SAVE_ITEM_HEIGHT,
SAVE_ITEM_SIZE,
SAVE_ITEM_TOP,
SAVE_PAGES
} from '../shared';
import { getSave, SaveData, adjustGrid, IGridLayoutData } from '../utils'; import { getSave, SaveData, adjustGrid, IGridLayoutData } from '../utils';
export const enum SaveMode { export const enum SaveMode {
@ -73,11 +61,10 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
const statusFont = new Font('normal', 14); const statusFont = new Font('normal', 14);
const w = computed(() => props.loc[2] ?? 200); const w = computed(() => props.loc[2] ?? 200);
const h = computed(() => props.loc[3] ?? 200);
const lineWidth = computed(() => (props.selected ? 4 : 2)); const lineWidth = computed(() => (props.selected ? 4 : 2));
const imgLoc = computed<ElementLocator>(() => { const imgLoc = computed<ElementLocator>(() => {
const size = w.value - 4; const size = w.value - 4;
return [2, SAVE_ITEM_TOP, size, size]; return [2, 24, size, size];
}); });
const name = computed(() => { const name = computed(() => {
@ -115,7 +102,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
<text <text
text={name.value} text={name.value}
font={font} font={font}
loc={[w.value / 2, SAVE_ITEM_TOP - 4]} loc={[w.value / 2, 20]}
anc={[0.5, 1]} anc={[0.5, 1]}
/> />
<g-rect <g-rect
@ -143,7 +130,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
text={statusText.value} text={statusText.value}
fillStyle="yellow" fillStyle="yellow"
font={statusFont} font={statusFont}
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN + 2]} loc={[w.value / 2, w.value + 28]}
anc={[0.5, 0]} anc={[0.5, 0]}
/> />
</container> </container>
@ -152,8 +139,12 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>( export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
(props, { emit }) => { (props, { emit }) => {
const font = Font.defaults({ size: 18 }); const itemSize = 150;
const pageFont = Font.defaults({ size: 14 }); const itemHeight = itemSize + 40;
const interval = 30;
const font = new Font('normal', 18);
const pageFont = new Font('normal', 14);
/** 当前页上被选中的存档的posIndex */ /** 当前页上被选中的存档的posIndex */
const selected = ref(0); const selected = ref(0);
@ -169,16 +160,16 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
const grid = computed<IGridLayoutData>(() => const grid = computed<IGridLayoutData>(() =>
adjustGrid( adjustGrid(
width.value, width.value,
height.value - SAVE_DOWN_PAD, height.value - 30,
SAVE_ITEM_SIZE, itemSize,
SAVE_ITEM_HEIGHT, itemHeight,
SAVE_INTERVAL interval
) )
); );
const contentLoc = computed<ElementLocator>(() => { const contentLoc = computed<ElementLocator>(() => {
const cx = width.value / 2; const cx = width.value / 2;
const cy = (height.value - SAVE_DOWN_PAD) / 2; const cy = (height.value - 30) / 2;
return [cx, cy, grid.value.width, grid.value.height, 0.5, 0.5]; return [cx, cy, grid.value.width, grid.value.height, 0.5, 0.5];
}); });
@ -253,8 +244,8 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
`确认要删除存档 ${index + 1}`, `确认要删除存档 ${index + 1}`,
[HALF_WIDTH, HALF_HEIGHT, void 0, void 0, 0.5, 0.5], [420, 240, void 0, void 0, 0.5, 0.5],
POP_BOX_WIDTH, 240,
{ winskin: 'winskin2.png' } { winskin: 'winskin2.png' }
); );
if (confirm) { if (confirm) {
@ -379,9 +370,6 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
); );
//#region 事件监听 //#region 事件监听
// todo: 按住快速切换页码
const wheel = (ev: IWheelEvent) => { const wheel = (ev: IWheelEvent) => {
const delta = Math.sign(ev.wheelY); const delta = Math.sign(ev.wheelY);
if (ev.ctrlKey) { if (ev.ctrlKey) {
@ -396,7 +384,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
<Page <Page
ref={pageRef} ref={pageRef}
loc={[0, 0, width.value, height.value - 10]} loc={[0, 0, width.value, height.value - 10]}
pages={SAVE_PAGES} pages={1000}
font={pageFont} font={pageFont}
v-model:page={now.value} v-model:page={now.value}
onWheel={wheel} onWheel={wheel}

View File

@ -24,8 +24,6 @@ import { openStatistics } from './statistics';
import { saveWithExist } from './save'; import { saveWithExist } from './save';
import { compressToBase64 } from 'lz-string'; import { compressToBase64 } from 'lz-string';
import { ViewMapUI } from './viewmap'; import { ViewMapUI } from './viewmap';
import { CENTER_LOC, FULL_LOC, MAIN_HEIGHT, POP_BOX_WIDTH } from '../shared';
import { useKey } from '../use';
export interface MainSettingsProps export interface MainSettingsProps
extends Partial<ChoicesProps>, extends Partial<ChoicesProps>,
@ -63,9 +61,6 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
[MainChoice.Back, '返回游戏'] [MainChoice.Back, '返回游戏']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case MainChoice.SystemSetting: { case MainChoice.SystemSetting: {
@ -84,9 +79,7 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
break; break;
} }
case MainChoice.ViewMap: { case MainChoice.ViewMap: {
props.controller.open(ViewMapUI, { props.controller.open(ViewMapUI, { loc: [0, 0, 840, 840] });
loc: FULL_LOC
});
break; break;
} }
case MainChoice.Replay: { case MainChoice.Replay: {
@ -105,8 +98,8 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'确认要返回标题吗?', '确认要返回标题吗?',
CENTER_LOC, [420, 240, void 0, void 0, 0.5, 0.5],
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
props.controller.closeAll(); props.controller.closeAll();
@ -125,11 +118,10 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
<Choices <Choices
loc={props.loc} loc={props.loc}
choices={choices} choices={choices}
width={POP_BOX_WIDTH} width={240}
onChoose={choose} onChoose={choose}
maxHeight={MAIN_HEIGHT - 64} maxHeight={400}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -155,9 +147,6 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
[ReplayChoice.Back, '返回游戏'] [ReplayChoice.Back, '返回游戏']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case ReplayChoice.Start: { case ReplayChoice.Start: {
@ -169,7 +158,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break; break;
} }
case ReplayChoice.StartFromSave: { case ReplayChoice.StartFromSave: {
const index = await saveWithExist(props.controller, FULL_LOC); const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break; if (index === -2) break;
if (index === -1) { if (index === -1) {
core.doSL('autoSave', 'replayLoad'); core.doSL('autoSave', 'replayLoad');
@ -180,7 +172,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break; break;
} }
case ReplayChoice.ResumeReplay: { case ReplayChoice.ResumeReplay: {
const index = await saveWithExist(props.controller, FULL_LOC); const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break; if (index === -2) break;
const name = index === -1 ? 'autoSave' : index + 1; const name = index === -1 ? 'autoSave' : index + 1;
const success = core.doSL(name, 'replayRemain'); const success = core.doSL(name, 'replayRemain');
@ -190,11 +185,14 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
} }
await getConfirm( await getConfirm(
props.controller, props.controller,
'[步骤2]请选择第二个存档。\n\\r[yellow]该存档必须是前一个存档的后续。\\r\n将尝试播放到此存档。', '[步骤2]请选择第二个存档。\n\r[yellow]该存档必须是前一个存档的后续。\r\n将尝试播放到此存档。',
CENTER_LOC, [420, 240, void 0, void 0, 0.5, 0.5],
POP_BOX_WIDTH 240
);
const index2 = await saveWithExist(
props.controller,
[0, 0, 840, 480]
); );
const index2 = await saveWithExist(props.controller, FULL_LOC);
if (index2 === -2) break; if (index2 === -2) break;
const name2 = index2 === -1 ? 'autoSave' : index2 + 1; const name2 = index2 === -1 ? 'autoSave' : index2 + 1;
core.doSL(name2, 'replayRemain'); core.doSL(name2, 'replayRemain');
@ -202,7 +200,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
break; break;
} }
case ReplayChoice.ReplayRest: { case ReplayChoice.ReplayRest: {
const index = await saveWithExist(props.controller, FULL_LOC); const index = await saveWithExist(
props.controller,
[0, 0, 840, 480]
);
if (index === -2) break; if (index === -2) break;
if (index === -1) { if (index === -1) {
core.doSL('autoSave', 'replaySince'); core.doSL('autoSave', 'replaySince');
@ -242,10 +243,9 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
<Choices <Choices
loc={props.loc} loc={props.loc}
choices={choice} choices={choice}
width={POP_BOX_WIDTH} width={240}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -269,9 +269,6 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
[GameInfoChoice.Back, '返回主菜单'] [GameInfoChoice.Back, '返回主菜单']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case GameInfoChoice.Statistics: { case GameInfoChoice.Statistics: {
@ -284,8 +281,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'即将离开本游戏,跳转至工程页面,确认跳转?', '即将离开本游戏,跳转至工程页面,确认跳转?',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
window.location.href = 'editor-mobile.html'; window.location.href = 'editor-mobile.html';
@ -302,8 +299,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'即将离开本游戏,跳转至评论页面,确认跳转?', '即将离开本游戏,跳转至评论页面,确认跳转?',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
window.location.href = href; window.location.href = href;
@ -333,10 +330,9 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
<Choices <Choices
loc={props.loc} loc={props.loc}
choices={choices} choices={choices}
width={POP_BOX_WIDTH} width={240}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -364,9 +360,6 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级'] [SyncSaveChoice.Back, '返回上一级']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case SyncSaveChoice.ToServer: { case SyncSaveChoice.ToServer: {
@ -377,8 +370,8 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
const replay = await getInput( const replay = await getInput(
props.controller, props.controller,
'请输入存档编号+密码', '请输入存档编号+密码',
CENTER_LOC, [240, 240, void 0, void 0, 0.5, 0.5],
POP_BOX_WIDTH 240
); );
await syncFromServer(props.controller, replay); await syncFromServer(props.controller, replay);
break; break;
@ -405,11 +398,10 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
return () => ( return () => (
<Choices <Choices
loc={props.loc} loc={props.loc}
width={POP_BOX_WIDTH} width={240}
choices={choices} choices={choices}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -421,9 +413,6 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级'] [SyncSaveChoice.Back, '返回上一级']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case SyncSaveChoice.AllSaves: { case SyncSaveChoice.AllSaves: {
@ -431,8 +420,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'你确定要同步全部存档么?这可能在存档较多的时候比较慢。', '你确定要同步全部存档么?这可能在存档较多的时候比较慢。',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
core.syncSave('all'); core.syncSave('all');
@ -444,8 +433,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'确定要同步当前存档吗?', '确定要同步当前存档吗?',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
core.syncSave(); core.syncSave();
@ -462,11 +451,10 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
return () => ( return () => (
<Choices <Choices
loc={props.loc} loc={props.loc}
width={POP_BOX_WIDTH} width={240}
choices={choices} choices={choices}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -478,23 +466,20 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级'] [SyncSaveChoice.Back, '返回上一级']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case SyncSaveChoice.AllSaves: { case SyncSaveChoice.AllSaves: {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'确认要下载所有存档吗?', '确认要下载所有存档吗?',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
const data = await waitbox( const data = await waitbox(
props.controller, props.controller,
CENTER_LOC, props.loc,
POP_BOX_WIDTH, 240,
getAllSavesData(), getAllSavesData(),
{ text: '请等待处理完毕' } { text: '请等待处理完毕' }
); );
@ -511,8 +496,8 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'确认要下载当前存档吗?', '确认要下载当前存档吗?',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
const data = await getSaveData(core.saves.saveIndex); const data = await getSaveData(core.saves.saveIndex);
@ -535,11 +520,10 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
return () => ( return () => (
<Choices <Choices
loc={props.loc} loc={props.loc}
width={POP_BOX_WIDTH} width={240}
choices={choices} choices={choices}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);
@ -551,23 +535,20 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
[SyncSaveChoice.Back, '返回上一级'] [SyncSaveChoice.Back, '返回上一级']
]; ];
const [key, scope] = useKey();
key.realize('exit', () => props.controller.close(props.instance));
const choose = async (key: ChoiceKey) => { const choose = async (key: ChoiceKey) => {
switch (key) { switch (key) {
case SyncSaveChoice.AllSaves: { case SyncSaveChoice.AllSaves: {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!', '你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
await waitbox( await waitbox(
props.controller, props.controller,
CENTER_LOC, props.loc,
POP_BOX_WIDTH, 240,
new Promise<void>(res => { new Promise<void>(res => {
core.clearLocalForage(() => { core.clearLocalForage(() => {
core.saves.ids = {}; core.saves.ids = {};
@ -590,8 +571,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
await getConfirm( await getConfirm(
props.controller, props.controller,
'所有塔的存档已经全部清空', '所有塔的存档已经全部清空',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
} }
break; break;
@ -600,14 +581,14 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
const confirm = await getConfirm( const confirm = await getConfirm(
props.controller, props.controller,
'你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!', '你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
if (confirm) { if (confirm) {
await waitbox( await waitbox(
props.controller, props.controller,
CENTER_LOC, props.loc,
POP_BOX_WIDTH, 240,
new Promise<void>(res => { new Promise<void>(res => {
Object.keys(core.saves.ids).forEach(function (v) { Object.keys(core.saves.ids).forEach(function (v) {
core.removeLocalForage('save' + v); core.removeLocalForage('save' + v);
@ -632,8 +613,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
await getConfirm( await getConfirm(
props.controller, props.controller,
'当前塔的存档已被清空', '当前塔的存档已被清空',
CENTER_LOC, props.loc,
POP_BOX_WIDTH 240
); );
} }
break; break;
@ -652,7 +633,6 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
choices={choices} choices={choices}
onChoose={choose} onChoose={choose}
interval={8} interval={8}
scope={scope}
/> />
); );
}, mainSettingsProps); }, mainSettingsProps);

View File

@ -91,9 +91,9 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
return num.toString().padStart(2, '0'); return num.toString().padStart(2, '0');
}; };
const font1 = Font.defaults({ size: 18 }); const font1 = new Font('normal', 18);
const font2 = Font.defaults({ size: 18, weight: 700 }); const font2 = new Font('normal', 18, 'px', 700);
const font3 = Font.defaults({ size: 14, weight: 700 }); const font3 = new Font('normal', 14, 'px', 700);
const iconLoc = (n: number): ElementLocator => { const iconLoc = (n: number): ElementLocator => {
return [16, 76 + 44 * n, 32, 32]; return [16, 76 + 44 * n, 32, 32];

View File

@ -5,22 +5,7 @@ import {
UIComponentProps UIComponentProps
} from '@motajs/system-ui'; } from '@motajs/system-ui';
import { defineComponent, nextTick, onMounted, ref } from 'vue'; import { defineComponent, nextTick, onMounted, ref } from 'vue';
import { import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
BUTTONS_HEIGHT,
BUTTONS_WIDTH,
BUTTONS_X,
BUTTONS_Y,
HALF_HEIGHT,
HALF_TITLE_HEIGHT,
HALF_TITLE_WIDTH,
HALF_WIDTH,
MAIN_HEIGHT,
MAIN_WIDTH,
TITLE_HEIGHT,
TITLE_WIDTH,
TITLE_X,
TITLE_Y
} from '../shared';
import { import {
ElementLocator, ElementLocator,
IActionEvent, IActionEvent,
@ -162,8 +147,8 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
let cursorScale = 1; let cursorScale = 1;
const titleFont = Font.defaults({ size: 72 }); const titleFont = new Font('normal', 72).string();
const buttonFont = Font.defaults({ size: 24, weight: 600 }); const buttonFont = new Font('normal', 24, 'px', 600);
//#region 按钮功能 //#region 按钮功能
@ -403,7 +388,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
} }
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.font = titleFont.string(); ctx.font = titleFont;
ctx.fillStyle = titleGradient!; ctx.fillStyle = titleGradient!;
ctx.filter = ` ctx.filter = `
drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5)) drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5))
@ -411,7 +396,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
drop-shadow(12px 12px 4px rgba(0, 0, 0, 0.4)) drop-shadow(12px 12px 4px rgba(0, 0, 0, 0.4))
blur(1px) blur(1px)
`; `;
ctx.fillText(core.firstData.title, HALF_TITLE_WIDTH, HALF_TITLE_HEIGHT); ctx.fillText(core.firstData.title, 320, 50);
}; };
const renderCursor = (canvas: MotaOffscreenCanvas2D) => { const renderCursor = (canvas: MotaOffscreenCanvas2D) => {
@ -435,15 +420,15 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
> >
<image <image
image={bg} image={bg}
loc={[HALF_WIDTH, HALF_HEIGHT, width, height]} loc={[MAIN_WIDTH / 2, MAIN_HEIGHT / 2, width, height]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
filter="brightness(120%)contrast(110%)"
zIndex={0} zIndex={0}
/> />
<shader <shader
ref={imageShader} ref={imageShader}
zIndex={5} zIndex={5}
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]} loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
filter="brightness(120%)contrast(110%)"
/> />
<sprite <sprite
ref={maskSprite} ref={maskSprite}
@ -456,16 +441,17 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
<sprite <sprite
ref={titleSprite} ref={titleSprite}
render={renderTitle} render={renderTitle}
loc={[TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT, 0.5, 0.5]} loc={[MAIN_WIDTH / 2, 120, 640, 100, 0.5, 0.5]}
zIndex={10} zIndex={10}
/> />
<container <container
zIndex={15} zIndex={15}
loc={[BUTTONS_X, BUTTONS_Y, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[50, MAIN_HEIGHT, 200, 160]}
anc={[0, 1]}
> >
<container <container
hidden={selectHard.value} hidden={selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[0, 0, 200, 160]}
alpha={buttonsAlpha.ref.value} alpha={buttonsAlpha.ref.value}
> >
{buttons.map((v, i) => { {buttons.map((v, i) => {
@ -489,7 +475,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
</container> </container>
<container <container
hidden={!selectHard.value} hidden={!selectHard.value}
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]} loc={[0, 0, 200, 160]}
alpha={buttonsAlpha.ref.value} alpha={buttonsAlpha.ref.value}
> >
{hard.map((v, i) => { {hard.map((v, i) => {
@ -542,15 +528,16 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
cursor="pointer" cursor="pointer"
/> />
)} )}
<g-line {!soundOpened.value && (
line={[5, 35, 35, 5]} <g-line
strokeStyle="gray" line={[5, 35, 35, 5]}
lineWidth={3} strokeStyle="gray"
lineCap="round" lineWidth={3}
zIndex={5} lineCap="round"
noevent noevent
hidden={soundOpened.value} zIndex={5}
/> />
)}
</container> </container>
</container> </container>
); );

View File

@ -23,7 +23,7 @@ import { generateBinary } from '@motajs/legacy-common';
import { SetupComponentOptions } from '@motajs/system-ui'; import { SetupComponentOptions } from '@motajs/system-ui';
import { saveSave, saveLoad } from './save'; import { saveSave, saveLoad } from './save';
import { mainUIController } from './controller'; import { mainUIController } from './controller';
import { MAIN_HEIGHT, FULL_LOC, POP_BOX_WIDTH, CENTER_LOC } from '../shared'; import { MAIN_WIDTH, MAIN_HEIGHT } from '../shared';
import { openSettings } from './settings'; import { openSettings } from './settings';
import { openViewMap } from './viewmap'; import { openViewMap } from './viewmap';
@ -90,10 +90,10 @@ export const PlayingToolbar = defineComponent<
const tool = () => core.openToolbox(true); const tool = () => core.openToolbox(true);
const fly = () => core.useFly(true); const fly = () => core.useFly(true);
const save = () => { const save = () => {
saveSave(mainUIController, FULL_LOC); saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}; };
const load = () => { const load = () => {
saveLoad(mainUIController, FULL_LOC); saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}; };
const equip = () => core.openEquipbox(true); const equip = () => core.openEquipbox(true);
const shop = () => core.openQuickShop(true); const shop = () => core.openQuickShop(true);
@ -111,15 +111,12 @@ export const PlayingToolbar = defineComponent<
const redo = () => core.doSL('autoSave', 'reload'); const redo = () => core.doSL('autoSave', 'reload');
const numpad = () => emit('numpad'); const numpad = () => emit('numpad');
const view = () => { const view = () => {
openViewMap(mainUIController, FULL_LOC); openViewMap(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}; };
const danmaku = () => requestAnimationFrame(openDanmakuPoster); const danmaku = () => requestAnimationFrame(openDanmakuPoster);
const replay = () => core.ui._drawReplay(); const replay = () => core.ui._drawReplay();
const settings = () => { const settings = () => {
const loc = CENTER_LOC.slice() as ElementLocator; openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
loc[2] = POP_BOX_WIDTH;
loc[3] = MAIN_HEIGHT - 72;
openSettings(mainUIController, loc);
}; };
return () => ( return () => (
@ -169,7 +166,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
const bookIcon = core.statusBar.icons.book; const bookIcon = core.statusBar.icons.book;
const saveIcon = core.statusBar.icons.save; const saveIcon = core.statusBar.icons.save;
const font1 = Font.defaults({ size: 16 }); const font1 = new Font('normal', 16);
const font2 = new Font('Verdana', 12); const font2 = new Font('Verdana', 12);
const speedText = computed(() => `${status.speed}`); const speedText = computed(() => `${status.speed}`);
@ -186,7 +183,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
const speedUp = () => core.speedUpReplay(); const speedUp = () => core.speedUpReplay();
const book = () => core.openBook(true); const book = () => core.openBook(true);
const save = () => { const save = () => {
saveSave(mainUIController, FULL_LOC); saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
}; };
const view = () => { const view = () => {
if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) { if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) {

View File

@ -38,18 +38,6 @@ import { clamp, mean } from 'lodash-es';
import { calculateStatisticsOne, StatisticsDataOneFloor } from './statistics'; import { calculateStatisticsOne, StatisticsDataOneFloor } from './statistics';
import { Tip, TipExpose } from '../components'; import { Tip, TipExpose } from '../components';
import { useKey } from '../use'; import { useKey } from '../use';
import {
ENABLE_RIGHT_STATUS_BAR,
FULL_LOC,
HALF_MAP_WIDTH,
HALF_STATUS_WIDTH,
MAIN_HEIGHT,
MAP_HEIGHT,
MAP_WIDTH,
RIGHT_STATUS_POS,
STATUS_BAR_HEIGHT,
STATUS_BAR_WIDTH
} from '../shared';
export interface ViewMapProps extends UIComponentProps, BaseProps { export interface ViewMapProps extends UIComponentProps, BaseProps {
loc: ElementLocator; loc: ElementLocator;
@ -71,12 +59,6 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
new LayerGroupAnimate() new LayerGroupAnimate()
]; ];
const restHeight = STATUS_BAR_HEIGHT - 292;
const col = restHeight / 4;
const loc1: ElementLocator = [HALF_STATUS_WIDTH, col * 1 + 292];
const loc2: ElementLocator = [HALF_STATUS_WIDTH, col * 2 + 292];
const loc3: ElementLocator = [HALF_STATUS_WIDTH, col * 3 + 292];
const rightFont = new Font(Font.defaultFamily, 15); const rightFont = new Font(Font.defaultFamily, 15);
const viewableFloor = markRaw( const viewableFloor = markRaw(
@ -112,13 +94,7 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
.realize('@viewMap_book', () => openBook()) .realize('@viewMap_book', () => openBook())
.realize('@viewMap_fly', () => fly()) .realize('@viewMap_fly', () => fly())
.realize('@viewMap_reset', () => resetCamera()) .realize('@viewMap_reset', () => resetCamera())
.realize('confirm', () => close()) .realize('confirm', () => close());
.realize('exit', (_, code, assist) => {
// 如果按键不能触发怪物手册,则关闭界面,因为怪物手册和退出默认使用同一个按键,需要特判
if (!key.willEmit(code, assist, 'book')) {
props.controller.close(props.instance);
}
});
//#region 功能函数 //#region 功能函数
@ -226,28 +202,6 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
group.value?.update(); group.value?.update();
}; };
//#region 事件监听
const clickTop = (ev: IActionEvent) => {
const col = MAP_WIDTH / 3;
if (ev.offsetX < col * 2) {
changeFloor(1);
} else {
resetCamera();
}
};
const clickBottom = (ev: IActionEvent) => {
const col = MAP_WIDTH / 3;
if (ev.offsetX < col) {
openBook();
} else if (ev.offsetX < col * 2) {
changeFloor(-1);
} else {
fly();
}
};
//#region 地图交互 //#region 地图交互
let mouseDown = false; let mouseDown = false;
@ -381,18 +335,12 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
return () => ( return () => (
<container loc={props.loc} nocache> <container loc={props.loc} nocache>
<g-rect fillStyle="black" fill loc={FULL_LOC} /> <g-rect fillStyle="black" fill loc={[0, 0, 840, 480]} />
<g-rect stroke zIndex={100} loc={FULL_LOC} noevent /> <g-rect stroke zIndex={100} loc={[0, 0, 840, 480]} noevent />
<g-line <g-line line={[180, 0, 180, 480]} lineWidth={1} />
line={[STATUS_BAR_WIDTH, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]} <g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
lineWidth={1}
/>
<g-line
line={[RIGHT_STATUS_POS, 0, RIGHT_STATUS_POS, MAIN_HEIGHT]}
lineWidth={1}
/>
<FloorSelector <FloorSelector
loc={[0, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]} loc={[0, 0, 180, 480]}
floors={viewableFloor} floors={viewableFloor}
v-model:now={now.value} v-model:now={now.value}
onClose={close} onClose={close}
@ -400,7 +348,7 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
<layer-group <layer-group
ref={group} ref={group}
ex={layerGroupExtends} ex={layerGroupExtends}
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]} loc={[180, 0, 480, 480]}
onDown={downMap} onDown={downMap}
onMove={moveMap} onMove={moveMap}
onUp={upMap} onUp={upMap}
@ -417,69 +365,45 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
<Tip <Tip
ref={tip} ref={tip}
zIndex={40} zIndex={40}
loc={[STATUS_BAR_WIDTH + 8, 8, 200, 32]} loc={[188, 8, 200, 32]}
pad={[12, 6]} pad={[12, 6]}
corner={16} corner={16}
/> />
<sprite <sprite
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, 64]} loc={[180, 0, 480, 64]}
render={renderTop} render={renderTop}
alpha={topAlpha.value} alpha={topAlpha.value}
zIndex={10} zIndex={10}
cursor="pointer" cursor="pointer"
onEnter={enterTop} onEnter={enterTop}
onLeave={leaveTop} onLeave={leaveTop}
onClick={clickTop} onClick={() => changeFloor(1)}
/> />
<sprite <sprite
loc={[STATUS_BAR_WIDTH, MAP_HEIGHT - 64, MAP_WIDTH, 64]} loc={[180, 416, 480, 64]}
render={renderBottom} render={renderBottom}
alpha={bottomAlpha.value} alpha={bottomAlpha.value}
zIndex={10} zIndex={10}
cursor="pointer" cursor="pointer"
onEnter={enterBottom} onEnter={enterBottom}
onLeave={leaveBottom} onLeave={leaveBottom}
onClick={clickBottom} onClick={() => changeFloor(-1)}
/> />
<text <text
text="上移地图" text="上移地图"
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, 24]} loc={[420, 24]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
zIndex={20} zIndex={20}
noevent noevent
/> />
<text <text
text="下移地图" text="下移地图"
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, MAP_HEIGHT - 24]} loc={[420, 456]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
zIndex={20} zIndex={20}
noevent noevent
/> />
<text <container loc={[660, 0, 180, 480]}>
text="「 怪物手册 」"
loc={[32 + STATUS_BAR_WIDTH, MAP_HEIGHT - 24]}
anc={[0, 0.5]}
zIndex={20}
noevent
/>
<text
text="「 传送至此 」"
loc={[RIGHT_STATUS_POS - 32, MAP_HEIGHT - 24]}
anc={[1, 0.5]}
zIndex={20}
noevent
/>
<text
text="「 重置视角 」"
loc={[RIGHT_STATUS_POS - 32, 24]}
anc={[1, 0.5]}
zIndex={20}
noevent
/>
<container
loc={[RIGHT_STATUS_POS, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
hidden={!ENABLE_RIGHT_STATUS_BAR}
>
<text <text
text="鼠标 / 单指拖动地图" text="鼠标 / 单指拖动地图"
font={rightFont} font={rightFont}
@ -540,21 +464,21 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
<g-line line={[12, 292, 168, 292]} lineWidth={1} /> <g-line line={[12, 292, 168, 292]} lineWidth={1} />
<text <text
text="「 怪物手册 」" text="「 怪物手册 」"
loc={loc1} loc={[90, 330]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={openBook} onClick={openBook}
/> />
<text <text
text="「 传送至此 」" text="「 传送至此 」"
loc={loc2} loc={[90, 380]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={fly} onClick={fly}
/> />
<text <text
text="「 重置视角 」" text="「 重置视角 」"
loc={loc3} loc={[90, 430]}
anc={[0.5, 0.5]} anc={[0.5, 0.5]}
cursor="pointer" cursor="pointer"
onClick={resetCamera} onClick={resetCamera}

View File

@ -73,22 +73,17 @@ type KeyUsing = [Hotkey, symbol];
/** /**
* *
* @param noScope * @param noScope
* @param scope `noScope` `true`
*/ */
export function useKey(noScope: boolean = false, scope?: symbol): KeyUsing { export function useKey(noScope: boolean = false): KeyUsing {
if (noScope) { if (noScope) {
return [gameKey, gameKey.scope]; return [gameKey, gameKey.scope];
} else { } else {
const sym = scope ?? Symbol(); const sym = Symbol();
if (sym === gameKey.scope) { gameKey.use(sym);
return [gameKey, gameKey.scope]; onUnmounted(() => {
} else { gameKey.dispose();
gameKey.use(sym); });
onUnmounted(() => { return [gameKey, sym];
gameKey.dispose();
});
return [gameKey, sym];
}
} }
} }

View File

@ -165,7 +165,7 @@ class GameListener extends EventEmitter<ListenerEvent> {
constructor() { constructor() {
super(); super();
if (main.replayChecking) return; if (main.replayChecking) return;
if (window.core) { if (!!window.core) {
this.init(); this.init();
} else { } else {
loading.once('coreInit', () => { loading.once('coreInit', () => {
@ -176,80 +176,84 @@ class GameListener extends EventEmitter<ListenerEvent> {
private init() { private init() {
// ----- block // ----- block
// const data = core.canvas.data.canvas;
// const getBlockLoc = (px: number, py: number, size: number) => { const data = core.canvas.data.canvas;
// return [
// Math.floor(((px * 32) / size + core.bigmap.offsetX) / 32), const getBlockLoc = (px: number, py: number, size: number) => {
// Math.floor(((py * 32) / size + core.bigmap.offsetY) / 32) return [
// ]; Math.floor(((px * 32) / size + core.bigmap.offsetX) / 32),
// }; Math.floor(((py * 32) / size + core.bigmap.offsetY) / 32)
// // hover & leave & mouseMove ];
// data.addEventListener('mousemove', e => { };
// if (
// core.status.lockControl || // hover & leave & mouseMove
// !core.isPlaying() || data.addEventListener('mousemove', e => {
// !core.status.floorId if (
// ) core.status.lockControl ||
// return; !core.isPlaying() ||
// this.emit('mouseMove', e); !core.status.floorId
// const { )
// x: px, return;
// y: py, this.emit('mouseMove', e);
// size const {
// } = core.actions._getClickLoc(e.offsetX, e.offsetY); x: px,
// const [bx, by] = getBlockLoc(px, py, size); y: py,
// const blocks = core.getMapBlocksObj(); size
// if (this.mouseX !== bx || this.mouseY !== by) { } = core.actions._getClickLoc(e.offsetX, e.offsetY);
// const lastBlock = blocks[`${this.mouseX},${this.mouseY}`]; const [bx, by] = getBlockLoc(px, py, size);
// const block = blocks[`${bx},${by}`]; const blocks = core.getMapBlocksObj();
// if (lastBlock) { if (this.mouseX !== bx || this.mouseY !== by) {
// this.emit('leaveBlock', lastBlock, e, false); const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
// } const block = blocks[`${bx},${by}`];
// if (block) { if (!!lastBlock) {
// this.emit('hoverBlock', block, e); this.emit('leaveBlock', lastBlock, e, false);
// this.mouseX = bx; }
// this.mouseY = by; if (!!block) {
// } else { this.emit('hoverBlock', block, e);
// this.mouseX = -1; this.mouseX = bx;
// this.mouseY = -1; this.mouseY = by;
// } } else {
// } this.mouseX = -1;
// }); this.mouseY = -1;
// data.addEventListener('mouseleave', e => { }
// if ( }
// core.status.lockControl || });
// !core.isPlaying() || data.addEventListener('mouseleave', e => {
// !core.status.floorId if (
// ) core.status.lockControl ||
// return; !core.isPlaying() ||
// const blocks = core.getMapBlocksObj(); !core.status.floorId
// const lastBlock = blocks[`${this.mouseX},${this.mouseY}`]; )
// if (lastBlock) { return;
// this.emit('leaveBlock', lastBlock, e, true); const blocks = core.getMapBlocksObj();
// } const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
// this.mouseX = -1; if (!!lastBlock) {
// this.mouseY = -1; this.emit('leaveBlock', lastBlock, e, true);
// }); }
// // click this.mouseX = -1;
// data.addEventListener('click', e => { this.mouseY = -1;
// if ( });
// core.status.lockControl || // click
// !core.isPlaying() || data.addEventListener('click', e => {
// !core.status.floorId if (
// ) core.status.lockControl ||
// return; !core.isPlaying() ||
// const { !core.status.floorId
// x: px, )
// y: py, return;
// size const {
// } = core.actions._getClickLoc(e.offsetX, e.offsetY); x: px,
// const [bx, by] = getBlockLoc(px, py, size); y: py,
// const blocks = core.getMapBlocksObj(); size
// const block = blocks[`${bx},${by}`]; } = core.actions._getClickLoc(e.offsetX, e.offsetY);
// if (block) { const [bx, by] = getBlockLoc(px, py, size);
// this.emit('clickBlock', block, e); const blocks = core.getMapBlocksObj();
// } const block = blocks[`${bx},${by}`];
// }); if (!!block) {
this.emit('clickBlock', block, e);
}
});
// ----- mouse // ----- mouse
} }
} }

View File

@ -2,16 +2,9 @@
export {}; export {};
interface PortResponse { /* @__PURE__ */ (function () {
server: number;
}
/* @__PURE__ */ (async function () {
if (main.mode !== 'play' || main.replayChecking) return; if (main.mode !== 'play' || main.replayChecking) return;
const res = await fetch('/getPort');
const { server } = (await res.json()) as PortResponse;
/** /**
* css * css
* @param {string} data * @param {string} data
@ -134,7 +127,7 @@ interface PortResponse {
console.log(`Data hot reload: ${data}`); console.log(`Data hot reload: ${data}`);
} }
const ws = new WebSocket(`ws://127.0.0.1:${server}`); const ws = new WebSocket('ws://127.0.0.1:3000');
ws.addEventListener('open', () => { ws.addEventListener('open', () => {
console.log(`Web socket connect successfully`); console.log(`Web socket connect successfully`);
}); });

View File

@ -34,8 +34,9 @@ export function deleteWith<T>(arr: T[], ele: T): T[] {
export function spliceBy<T>(arr: T[], from: T): T[] { export function spliceBy<T>(arr: T[], from: T): T[] {
const index = arr.indexOf(from); const index = arr.indexOf(from);
if (index === -1) return []; if (index === -1) return arr;
return arr.splice(index); arr.splice(index);
return arr;
} }
/** /**

View File

@ -160,18 +160,8 @@ export class Font implements IFontConfig {
/** /**
* *
*/ */
static defaults(config?: Partial<IFontConfig>) { static defaults() {
if (!config) { return new Font();
return new Font();
} else {
return new Font(
config.family,
config.size,
config.sizeUnit,
config.weight,
config.italic
);
}
} }
/** /**

View File

@ -52,7 +52,6 @@ export interface HotkeyData extends Required<RegisterHotkeyData> {
type HotkeyFunc = ( type HotkeyFunc = (
id: string, id: string,
code: KeyCode, code: KeyCode,
assist: number,
ev: KeyboardEvent ev: KeyboardEvent
) => void | '@void'; ) => void | '@void';
@ -180,8 +179,7 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
* @param symbol symbol * @param symbol symbol
*/ */
use(symbol: symbol): void { use(symbol: symbol): void {
if (symbol === this.scope) return; spliceBy(this.scopeStack, symbol);
this.dispose(symbol);
this.scopeStack.push(symbol); this.scopeStack.push(symbol);
this.scope = symbol; this.scope = symbol;
this.conditionMap.set(symbol, () => true); this.conditionMap.set(symbol, () => true);
@ -192,12 +190,10 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
* @param symbol symbol * @param symbol symbol
*/ */
dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()): void { dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()): void {
const disposed = spliceBy(this.scopeStack, symbol); for (const key of Object.values(this.data)) {
disposed.forEach(v => { key.emits.delete(symbol);
for (const key of Object.values(this.data)) { }
key.emits.delete(v); spliceBy(this.scopeStack, symbol);
}
});
this.scope = this.scopeStack.at(-1) ?? Symbol(); this.scope = this.scopeStack.at(-1) ?? Symbol();
} }
@ -224,30 +220,6 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
if (emit) this.emit('set', id, key, assist); if (emit) this.emit('set', id, key, assist);
} }
/**
* id
* @param key
* @param assist
* @param id id
*/
willEmit(key: KeyCode, assist: number, id: string) {
const emittable = this.keyMap.get(key);
if (!emittable) return false;
const { ctrl, shift, alt } = unwarpBinary(assist);
return emittable.some(v => {
if (
v.id === id &&
v.ctrl === ctrl &&
v.shift === shift &&
v.alt === alt
) {
return true;
} else {
return false;
}
});
}
/** /**
* *
* @param key * @param key
@ -281,12 +253,12 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
if (!data) return; if (!data) return;
if (type === 'up' && data.onUp) { if (type === 'up' && data.onUp) {
data.onUp(v.id, key, assist, ev); data.onUp(v.id, key, ev);
emitted = true; emitted = true;
return; return;
} }
if (!this.canEmit(v.id, key, type, data)) return; if (!this.canEmit(v.id, key, type, data)) return;
const res = data.func(v.id, key, assist, ev); const res = data.func(v.id, key, ev);
if (res !== '@void') emitted = true; if (res !== '@void') emitted = true;
} }
}); });
@ -478,12 +450,12 @@ document.addEventListener('keyup', e => {
} }
} else { } else {
// polyfill样板 // polyfill样板
if (main.dom.inputDiv.style.display === 'block') { if (main.dom.inputDiv.style.display == 'block') {
if (e.keyCode === 13) { if (e.keyCode == 13) {
setTimeout(function () { setTimeout(function () {
main.dom.inputYes.click(); main.dom.inputYes.click();
}, 50); }, 50);
} else if (e.keyCode === 27) { } else if (e.keyCode == 27) {
setTimeout(function () { setTimeout(function () {
main.dom.inputNo.click(); main.dom.inputNo.click();
}, 50); }, 50);

View File

@ -402,8 +402,6 @@ importers:
packages-user/types: {} packages-user/types: {}
packages/animate: {}
packages/client: packages/client:
dependencies: dependencies:
'@motajs/client-base': '@motajs/client-base':

View File

@ -876,6 +876,39 @@ control.prototype.setHeroOpacity = function (
////// 设置画布偏移 ////// 设置画布偏移
control.prototype.setGameCanvasTranslate = function (canvas, x, y) { control.prototype.setGameCanvasTranslate = function (canvas, x, y) {
// Deprecated. Use RenderItem.transform instead. // Deprecated. Use RenderItem.transform instead.
var c = core.dom.gameCanvas[canvas];
x = x * core.domStyle.scale;
y = y * core.domStyle.scale;
c.style.transform = 'translate(' + x + 'px,' + y + 'px)';
c.style.webkitTransform = 'translate(' + x + 'px,' + y + 'px)';
c.style.OTransform = 'translate(' + x + 'px,' + y + 'px)';
c.style.MozTransform = 'translate(' + x + 'px,' + y + 'px)';
if (main.mode === 'editor' && editor.isMobile) {
c.style.transform =
'translate(' +
(x / core._PX_) * 96 +
'vw,' +
(y / core._PY_) * 96 +
'vw)';
c.style.webkitTransform =
'translate(' +
(x / core._PX_) * 96 +
'vw,' +
(y / core._PY_) * 96 +
'vw)';
c.style.OTransform =
'translate(' +
(x / core._PX_) * 96 +
'vw,' +
(y / core._PY_) * 96 +
'vw)';
c.style.MozTransform =
'translate(' +
(x / core._PX_) * 96 +
'vw,' +
(y / core._PY_) * 96 +
'vw)';
}
}; };
////// 加减画布偏移 ////// 加减画布偏移
@ -3082,11 +3115,13 @@ control.prototype.resize = function () {
if (!core.domStyle.availableScale.includes(core.domStyle.scale)) { if (!core.domStyle.availableScale.includes(core.domStyle.scale)) {
core.domStyle.scale = 1; core.domStyle.scale = 1;
} }
core.dom.gameDraw.style.top = '0';
} else { } else {
// 竖屏 // 竖屏
core.domStyle.isVertical = true; core.domStyle.isVertical = true;
core.domStyle.scale = window.innerWidth / core._PX_; core.domStyle.scale = window.innerWidth / core._PX_;
core.domStyle.availableScale = []; core.domStyle.availableScale = [];
core.dom.gameDraw.style.top = '10vh';
} }
if (!core.domStyle.isVertical) { if (!core.domStyle.isVertical) {
@ -3097,6 +3132,11 @@ control.prototype.resize = function () {
core.domStyle.scale = target - 0.25; core.domStyle.scale = target - 0.25;
} }
const pw = (480 + 180 * 2) * core.domStyle.scale;
const ph = 480 * core.domStyle.scale;
core.dom.gameDraw.style.width = `${pw}px`;
core.dom.gameDraw.style.height = `${ph}px`;
this._doResize({}); this._doResize({});
this.setToolbarButton(); this.setToolbarButton();
core.updateStatusBar(); core.updateStatusBar();
@ -3107,7 +3147,54 @@ control.prototype._resize_gameGroup = function (obj) {
}; };
control.prototype._resize_canvas = function (obj) { control.prototype._resize_canvas = function (obj) {
// Deprecated. var innerWidth = core._PX_ * core.domStyle.scale + 'px',
innerHeight = core._PY_ * core.domStyle.scale + 'px';
for (var i = 0; i < core.dom.gameCanvas.length; ++i) {
core.dom.gameCanvas[i].style.width = innerWidth;
core.dom.gameCanvas[i].style.height = innerHeight;
var ctx = core.dom.gameCanvas[i].getContext('2d');
core.resizeCanvas(ctx, core._PX_, core._PY_);
}
// resize dynamic canvas
if (!core.isPlaying()) {
for (var name in core.dymCanvas) {
var ctx = core.dymCanvas[name],
canvas = ctx.canvas;
// core.maps._setHDCanvasSize(ctx, parseFloat(canvas.getAttribute('_width')), parseFloat(canvas.getAttribute('_height')));
canvas.style.left =
parseFloat(canvas.getAttribute('_left')) * core.domStyle.scale +
'px';
canvas.style.top =
parseFloat(canvas.getAttribute('_top')) * core.domStyle.scale +
'px';
var scale = canvas.getAttribute('_scale') || 1;
core.resizeCanvas(
canvas,
(canvas.width * scale) / core.domStyle.scale,
(canvas.height * scale) / core.domStyle.scale
);
}
} else {
for (var name in core.dymCanvas) {
var ctx = core.dymCanvas[name],
canvas = ctx.canvas;
canvas.style.width = canvas.width / devicePixelRatio + 'px';
canvas.style.height = canvas.height / devicePixelRatio + 'px';
canvas.style.left =
parseFloat(canvas.getAttribute('_left')) * core.domStyle.scale +
'px';
canvas.style.top =
parseFloat(canvas.getAttribute('_top')) * core.domStyle.scale +
'px';
}
}
// resize next
main.dom.next.style.width = main.dom.next.style.height =
5 * core.domStyle.scale + 'px';
main.dom.next.style.borderBottomWidth =
main.dom.next.style.borderRightWidth = 4 * core.domStyle.scale + 'px';
}; };
control.prototype._resize_toolBar = function (obj) { control.prototype._resize_toolBar = function (obj) {

View File

@ -934,6 +934,8 @@ ui.prototype.closePanel = function () {
ui.prototype.clearUI = function () { ui.prototype.clearUI = function () {
core.status.boxAnimateObjs = []; core.status.boxAnimateObjs = [];
core.deleteCanvas('_selector'); core.deleteCanvas('_selector');
main.dom.next.style.display = 'none';
main.dom.next.style.opacity = 1;
core.clearMap('ui'); core.clearMap('ui');
core.setAlpha('ui', 1); core.setAlpha('ui', 1);
core.setOpacity('ui', 1); core.setOpacity('ui', 1);
@ -1097,7 +1099,7 @@ ui.prototype._getPosition = function (content) {
////// 绘制系统选择光标 ////// 绘制系统选择光标
ui.prototype._drawWindowSelector = function (background, x, y, w, h) { ui.prototype._drawWindowSelector = function (background, x, y, w, h) {
((w = Math.round(w)), (h = Math.round(h))); (w = Math.round(w)), (h = Math.round(h));
var ctx = core.ui.createCanvas('_selector', x, y, w, h, 165); var ctx = core.ui.createCanvas('_selector', x, y, w, h, 165);
this._drawSelector(ctx, background, w, h); this._drawSelector(ctx, background, w, h);
}; };
@ -2054,10 +2056,12 @@ ui.prototype._animateUI = function (type, ctx, callback) {
opacity = 1; opacity = 1;
} }
core.setOpacity(ctx, opacity); core.setOpacity(ctx, opacity);
core.dom.next.style.opacity = opacity;
core.status.event.animateUI = setInterval(function () { core.status.event.animateUI = setInterval(function () {
if (type == 'show') opacity += 0.05; if (type == 'show') opacity += 0.05;
else opacity -= 0.05; else opacity -= 0.05;
core.setOpacity(ctx, opacity); core.setOpacity(ctx, opacity);
core.dom.next.style.opacity = opacity;
if (opacity >= 1 || opacity <= 0) { if (opacity >= 1 || opacity <= 0) {
clearInterval(core.status.event.animateUI); clearInterval(core.status.event.animateUI);
delete core.status.event.animateUI; delete core.status.event.animateUI;
@ -2152,6 +2156,13 @@ ui.prototype.drawTextBox = function (content, config) {
// Step 6: 绘制光标 // Step 6: 绘制光标
if (main.mode == 'play') { if (main.mode == 'play') {
main.dom.next.style.display = 'block';
main.dom.next.style.borderRightColor =
main.dom.next.style.borderBottomColor = core.arrayToRGB(
textAttribute.text
);
main.dom.next.style.top =
(vPos.bottom - 20) * core.domStyle.scale + 'px';
var left = (hPos.left + hPos.right) / 2; var left = (hPos.left + hPos.right) / 2;
if ( if (
posInfo.position == 'up' && posInfo.position == 'up' &&
@ -2160,6 +2171,7 @@ ui.prototype.drawTextBox = function (content, config) {
Math.abs(posInfo.px * 32 + 16 - left) < 50 Math.abs(posInfo.px * 32 + 16 - left) < 50
) )
left = hPos.right - 64; left = hPos.right - 64;
main.dom.next.style.left = left * core.domStyle.scale + 'px';
} }
return config; return config;
}; };

View File

@ -20,12 +20,16 @@ function main() {
this.dom = { this.dom = {
body: document.body, body: document.body,
game: document.getElementById('game'),
gameDraw: document.getElementById('game-draw'), gameDraw: document.getElementById('game-draw'),
gameCanvas: document.getElementsByClassName('gameCanvas'),
data: document.getElementById('data'),
inputDiv: document.getElementById('inputDiv'), inputDiv: document.getElementById('inputDiv'),
inputMessage: document.getElementById('inputMessage'), inputMessage: document.getElementById('inputMessage'),
inputBox: document.getElementById('inputBox'), inputBox: document.getElementById('inputBox'),
inputYes: document.getElementById('inputYes'), inputYes: document.getElementById('inputYes'),
inputNo: document.getElementById('inputNo') inputNo: document.getElementById('inputNo'),
next: document.getElementById('next')
}; };
this.mode = 'play'; this.mode = 'play';
this.loadList = [ this.loadList = [
@ -194,6 +198,10 @@ main.prototype.loadSync = function (mode, callback) {
}; };
main.prototype.loadAsync = async function (mode, callback) { main.prototype.loadAsync = async function (mode, callback) {
for (var i = 0; i < main.dom.gameCanvas.length; i++) {
main.canvas[main.dom.gameCanvas[i].id] =
main.dom.gameCanvas[i].getContext('2d');
}
main.mode = mode; main.mode = mode;
// 加载全塔属性代码 // 加载全塔属性代码

View File

@ -18,11 +18,7 @@ body {
} }
#game-draw { #game-draw {
width: 100%; position: relative;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden; overflow: hidden;
} }

View File

@ -399,7 +399,7 @@ async function buildGame() {
if (level === ClientDataLevel.Error) { if (level === ClientDataLevel.Error) {
logProgress(2, ProgressStatus.Fail); logProgress(2, ProgressStatus.Fail);
process.stderr.write( process.stderr.write(
`数据端似乎引用了客户端内容,请仔细检查后再构建!` `客户端似乎引用了数据端内容,请仔细检查后再构建!`
); );
process.exit(1); process.exit(1);
} }
@ -647,19 +647,6 @@ async function buildGame() {
process.stdout.write( process.stdout.write(
`⚠️ 压缩包大于 100M可能导致发塔困难请考虑降低塔的大小\r\n` `⚠️ 压缩包大于 100M可能导致发塔困难请考虑降低塔的大小\r\n`
); );
const suggections: string[] = [];
if (dataObject.main.bgms.some(v => !v.endsWith('opuw'))) {
suggections.push(`将 BGM 和音效换用 opus 格式`);
}
if (dataObject.main.images.some(v => !v.endsWith('webp'))) {
suggections.push(`将图片换用无损 webp 格式`);
}
if (suggections.length > 0) {
process.stdout.write(`降低压缩包体积的可能方案:\r\n`);
suggections.forEach((v, i) => {
process.stdout.write(`${i + 1}. ${v}\r\n`);
});
}
} }
process.stdout.write(`压缩包大小:${formatSize(zipSize)}\r\n`); process.stdout.write(`压缩包大小:${formatSize(zipSize)}\r\n`);
process.stdout.write(`源码大小:${formatSize(sourceSize)}\r\n`); process.stdout.write(`源码大小:${formatSize(sourceSize)}\r\n`);

View File

@ -1,10 +1,5 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { import { createServer } from 'vite';
createServer,
loadConfigFromFile,
mergeConfig,
UserConfig
} from 'vite';
import { Server } from 'http'; import { Server } from 'http';
import { ensureDir, move, pathExists, remove } from 'fs-extra'; import { ensureDir, move, pathExists, remove } from 'fs-extra';
import { readFile, readdir, writeFile } from 'fs/promises'; import { readFile, readdir, writeFile } from 'fs/promises';
@ -505,14 +500,6 @@ const apiGetEsmFiles = async (req: Request, res: Response) => {
return getEsmFile(req, res, path.resolved); return getEsmFile(req, res, path.resolved);
}; };
const apiGetPort = async (_req: Request, res: Response) => {
const port = {
vite: vitePort,
server: serverPort
};
res.end(JSON.stringify(port));
};
/** /**
* *
* @param {string} type * @param {string} type
@ -690,49 +677,12 @@ async function ensureConfig() {
} }
(async function () { (async function () {
// 1. 加载 vite.config.ts // 1. 启动vite服务
const fsHost = `http://127.0.0.1:${serverPort}`; const vite = await createServer();
const config = await loadConfigFromFile({
command: 'serve',
mode: 'development'
});
if (!config) {
console.error(`Cannot load config file.`);
return;
}
const merged = mergeConfig(config.config, {
server: {
proxy: {
'/readFile': fsHost,
'/writeFile': fsHost,
'/writeMultiFiles': fsHost,
'/listFile': fsHost,
'/makeDir': fsHost,
'/moveFile': fsHost,
'/deleteFile': fsHost,
'/getPort': fsHost,
'^/all/.*': fsHost,
'^/forceTem/.*': {
target: fsHost,
changeOrigin: true,
rewrite(path) {
return path.replace(/^\/forceTem/, '');
}
},
'/danmaku': 'https://h5mota.com/backend/tower/barrage.php'
}
}
} satisfies UserConfig);
// 2. 启动vite服务
const vite = await createServer({
...merged,
configFile: false
});
await vite.listen(vitePort); await vite.listen(vitePort);
console.log(`游戏地址http://localhost:${vitePort}/`); console.log(`游戏地址http://localhost:${vitePort}/`);
// 3. 启动样板http服务 // 2. 启动样板http服务
await ensureConfig(); await ensureConfig();
const app = express(); const app = express();
@ -750,7 +700,6 @@ async function ensureConfig() {
app.get('/all/__all_floors__.js', apiGetAllFloors); app.get('/all/__all_floors__.js', apiGetAllFloors);
app.get('/all/__all_animates__', apiGetAllAnimates); app.get('/all/__all_animates__', apiGetAllAnimates);
app.get('/esm', apiGetEsmFiles); app.get('/esm', apiGetEsmFiles);
app.get('/getPort', apiGetPort);
const server = app.listen(serverPort); const server = app.listen(serverPort);
@ -761,7 +710,7 @@ async function ensureConfig() {
); );
}); });
// 4. 启动样板ws热重载服务 // 3. 启动样板ws热重载服务
startWsServer(server); startWsServer(server);
process.on('SIGTERM', () => { process.on('SIGTERM', () => {

View File

@ -5,6 +5,8 @@ import path from 'path';
import postcssPresetEnv from 'postcss-preset-env'; import postcssPresetEnv from 'postcss-preset-env';
import * as glob from 'glob'; import * as glob from 'glob';
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', 'winskin',
@ -55,6 +57,24 @@ export default defineConfig({
} }
}, },
server: { server: {
proxy: {
'/readFile': FSHOST,
'/writeFile': FSHOST,
'/writeMultiFiles': FSHOST,
'/listFile': FSHOST,
'/makeDir': FSHOST,
'/moveFile': FSHOST,
'/deleteFile': FSHOST,
'^/all/.*': FSHOST,
'^/forceTem/.*': {
target: FSHOST,
changeOrigin: true,
rewrite(path) {
return path.replace(/^\/forceTem/, '');
},
},
'/danmaku': 'https://h5mota.com/backend/tower/barrage.php'
},
watch: { watch: {
ignored: ['**/public/**'] ignored: ['**/public/**']
}, },