mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-10-09 04:11:46 +08:00
Compare commits
10 Commits
7cbe35e246
...
608cd15f76
Author | SHA1 | Date | |
---|---|---|---|
608cd15f76 | |||
f9bd3fc3b4 | |||
5c49f6617b | |||
82d58756b7 | |||
e666b85ba3 | |||
4c6343227f | |||
721edefaba | |||
a89bf40e50 | |||
9a175e9afe | |||
![]() |
7fef0df1e6 |
10
index.html
10
index.html
@ -20,14 +20,8 @@
|
||||
|
||||
<body>
|
||||
<!-- injection -->
|
||||
<div id="game">
|
||||
<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 id="game-draw">
|
||||
<canvas id="render-main"></canvas>
|
||||
</div>
|
||||
<div id='inputDiv'>
|
||||
<div id='inputDialog'>
|
||||
|
@ -44,6 +44,7 @@ export class HeroKeyMover {
|
||||
down: data[config?.down ?? 'moveDown']
|
||||
};
|
||||
|
||||
// 静止时尝试启动移动
|
||||
this.ticker.add(() => {
|
||||
if (!this.moving) {
|
||||
if (this.pressedKey.size > 0) {
|
||||
@ -56,6 +57,10 @@ export class HeroKeyMover {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 按键移动
|
||||
* @param code 按键码
|
||||
*/
|
||||
private onPressKey = (code: KeyCode) => {
|
||||
if (core.isReplaying() || !core.isPlaying()) return;
|
||||
core.waitHeroToStop();
|
||||
@ -65,6 +70,10 @@ export class HeroKeyMover {
|
||||
else if (code === this.hotkeyData.down.key) this.press('down');
|
||||
};
|
||||
|
||||
/**
|
||||
* 释放按键
|
||||
* @param code 按键码
|
||||
*/
|
||||
private onReleaseKey = (code: KeyCode) => {
|
||||
if (code === this.hotkeyData.left.key) this.release('left');
|
||||
else if (code === this.hotkeyData.right.key) this.release('right');
|
||||
@ -135,18 +144,26 @@ export class HeroKeyMover {
|
||||
this.controller?.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动结束
|
||||
*/
|
||||
private onStepEnd = () => {
|
||||
const con = this.controller;
|
||||
if (!con) return;
|
||||
|
||||
// 被禁止操作时
|
||||
if (core.status.lockControl) {
|
||||
con.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// 未移动时
|
||||
if (!this.moving) {
|
||||
con.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试移动
|
||||
if (this.pressedKey.size > 0) {
|
||||
if (con.queue.length === 0) {
|
||||
con.push({ type: 'dir', value: this.moveDir });
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { MAIN_WIDTH, MAIN_HEIGHT } from './shared';
|
||||
import { MAIN_WIDTH, MAIN_HEIGHT, POP_BOX_WIDTH, CENTER_LOC } from './shared';
|
||||
import {
|
||||
saveSave,
|
||||
mainUIController,
|
||||
@ -9,6 +9,7 @@ import {
|
||||
ReplaySettingsUI,
|
||||
openViewMap
|
||||
} from './ui';
|
||||
import { ElementLocator } from '@motajs/render-core';
|
||||
|
||||
export function createAction() {
|
||||
gameKey
|
||||
@ -22,11 +23,13 @@ export function createAction() {
|
||||
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
})
|
||||
.realize('menu', () => {
|
||||
openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
|
||||
const loc = CENTER_LOC.slice() as ElementLocator;
|
||||
loc[2] = POP_BOX_WIDTH;
|
||||
openSettings(mainUIController, loc);
|
||||
})
|
||||
.realize('replay', () => {
|
||||
mainUIController.open(ReplaySettingsUI, {
|
||||
loc: [420, 240, void 0, void 0, 0.5, 0.5]
|
||||
loc: CENTER_LOC
|
||||
});
|
||||
})
|
||||
.realize('viewMap', () => {
|
||||
|
@ -9,18 +9,32 @@ import { useKey } from '../use';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
export interface ConfirmBoxProps extends DefaultProps, TextContentProps {
|
||||
/** 确认框的提示文本内容 */
|
||||
text: string;
|
||||
/** 确认框对话框的宽度 */
|
||||
width: number;
|
||||
/** 确认框对话框的位置 */
|
||||
loc: ElementLocator;
|
||||
/** 确认/取消按钮的字体样式 */
|
||||
selFont?: Font;
|
||||
/** 确认/取消按钮的文本颜色 */
|
||||
selFill?: CanvasStyle;
|
||||
/** 对话框内部所有元素的内边距 */
|
||||
pad?: number;
|
||||
/** 确认按钮的显示文本,默认为"确认" */
|
||||
yesText?: string;
|
||||
/** 取消按钮的显示文本,默认为"取消" */
|
||||
noText?: string;
|
||||
/** 窗口皮肤图片ID,用于对话框背景绘制 */
|
||||
winskin?: ImageIds;
|
||||
/** 是否默认选中确认按钮 */
|
||||
defaultYes?: boolean;
|
||||
/** 对话框背景颜色,当未设置 winskin 时生效 */
|
||||
color?: CanvasStyle;
|
||||
/** 对话框边框颜色,当未设置 winskin 时生效 */
|
||||
border?: CanvasStyle;
|
||||
/** 按键作用域,如果需要同作用域按键,那么需要传入 */
|
||||
scope?: symbol;
|
||||
}
|
||||
|
||||
export type ConfirmBoxEmits = {
|
||||
@ -41,7 +55,8 @@ const confirmBoxProps = {
|
||||
'winskin',
|
||||
'defaultYes',
|
||||
'color',
|
||||
'border'
|
||||
'border',
|
||||
'scope'
|
||||
],
|
||||
emits: ['no', 'yes']
|
||||
} satisfies SetupComponentOptions<
|
||||
@ -131,7 +146,7 @@ export const ConfirmBox = defineComponent<
|
||||
noSize.value = [width, height];
|
||||
};
|
||||
|
||||
const [key] = useKey();
|
||||
const [key] = useKey(false, props.scope);
|
||||
key.realize('confirm', () => {
|
||||
if (selected.value) emit('yes');
|
||||
else emit('no');
|
||||
@ -198,22 +213,40 @@ export type ChoiceItem<T extends ChoiceKey = ChoiceKey> = [
|
||||
];
|
||||
|
||||
export interface ChoicesProps extends DefaultProps, TextContentProps {
|
||||
/** 选项数组 */
|
||||
choices: ChoiceItem[];
|
||||
/** 选择框对话框的位置 */
|
||||
loc: ElementLocator;
|
||||
/** 选择框对话框的宽度 */
|
||||
width: number;
|
||||
/** 选择框的最大高度,超过时将分页显示 */
|
||||
maxHeight?: number;
|
||||
/** 选择框的提示文本内容 */
|
||||
text?: string;
|
||||
/** 选择框的标题文本 */
|
||||
title?: string;
|
||||
/** 窗口皮肤图片ID,用于对话框背景绘制 */
|
||||
winskin?: ImageIds;
|
||||
/** 对话框背景颜色,当未设置 winskin 时生效 */
|
||||
color?: CanvasStyle;
|
||||
/** 对话框边框颜色,当未设置 winskin 时生效 */
|
||||
border?: CanvasStyle;
|
||||
/** 选项文本的字体样式 */
|
||||
selFont?: Font;
|
||||
/** 选项文本的颜色 */
|
||||
selFill?: CanvasStyle;
|
||||
/** 标题文本的字体样式 */
|
||||
titleFont?: Font;
|
||||
/** 标题文本的颜色 */
|
||||
titleFill?: CanvasStyle;
|
||||
/** 对话框内部所有元素的内边距 */
|
||||
pad?: number;
|
||||
/** 选项之间的垂直间隔 */
|
||||
interval?: number;
|
||||
/** 默认选中的选项索引 */
|
||||
selected?: number;
|
||||
/** 按键作用域,如果需要同作用域按键,那么需要传入,例如系统设置 UI */
|
||||
scope?: symbol;
|
||||
}
|
||||
|
||||
export type ChoicesEmits = {
|
||||
@ -237,7 +270,8 @@ const choicesProps = {
|
||||
'titleFill',
|
||||
'pad',
|
||||
'interval',
|
||||
'selected'
|
||||
'selected',
|
||||
'scope'
|
||||
],
|
||||
emits: ['choose']
|
||||
} satisfies SetupComponentOptions<
|
||||
@ -436,7 +470,7 @@ export const Choices = defineComponent<
|
||||
selected.value = 0;
|
||||
};
|
||||
|
||||
const [key] = useKey();
|
||||
const [key] = useKey(false, props.scope);
|
||||
key.realize('moveUp', () => {
|
||||
if (selected.value === 0) {
|
||||
if (pageCom.value?.now() !== 0) {
|
||||
@ -477,27 +511,25 @@ export const Choices = defineComponent<
|
||||
color={props.color ?? '#333'}
|
||||
border={props.border}
|
||||
/>
|
||||
{hasTitle.value && (
|
||||
<text
|
||||
loc={titleLoc.value}
|
||||
text={props.title}
|
||||
font={props.titleFont ?? new Font(void 0, 18)}
|
||||
fillStyle={props.titleFill ?? 'gold'}
|
||||
zIndex={5}
|
||||
onSetText={updateTitleHeight}
|
||||
/>
|
||||
)}
|
||||
{hasText.value && (
|
||||
<TextContent
|
||||
{...attrs}
|
||||
text={props.text}
|
||||
loc={contentLoc.value}
|
||||
width={contentWidth.value}
|
||||
zIndex={5}
|
||||
autoHeight
|
||||
onUpdateHeight={updateContentHeight}
|
||||
/>
|
||||
)}
|
||||
<text
|
||||
hidden={!hasTitle.value}
|
||||
loc={titleLoc.value}
|
||||
text={props.title}
|
||||
font={props.titleFont ?? new Font(void 0, 18)}
|
||||
fillStyle={props.titleFill ?? 'gold'}
|
||||
zIndex={5}
|
||||
onSetText={updateTitleHeight}
|
||||
/>
|
||||
<TextContent
|
||||
{...attrs}
|
||||
hidden={!hasText.value}
|
||||
text={props.text}
|
||||
loc={contentLoc.value}
|
||||
width={contentWidth.value}
|
||||
zIndex={5}
|
||||
autoHeight
|
||||
onUpdateHeight={updateContentHeight}
|
||||
/>
|
||||
<Page
|
||||
ref={pageCom}
|
||||
loc={choiceLoc.value}
|
||||
|
@ -5,6 +5,14 @@ import { computed, defineComponent, onMounted, ref, watch } from 'vue';
|
||||
import { Scroll, ScrollExpose } from './scroll';
|
||||
import { Font } from '@motajs/render-style';
|
||||
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 {
|
||||
floors: FloorIds[];
|
||||
@ -67,7 +75,7 @@ export const FloorSelector = defineComponent<
|
||||
|
||||
const getGradient = (ctx: CanvasRenderingContext2D) => {
|
||||
if (gradient) return gradient;
|
||||
gradient = ctx.createLinearGradient(0, 0, 0, 200);
|
||||
gradient = ctx.createLinearGradient(0, 0, 0, SCROLL_HEIGHT);
|
||||
gradient.addColorStop(0, 'rgba(255,255,255,0)');
|
||||
gradient.addColorStop(0.2, 'rgba(255,255,255,1)');
|
||||
gradient.addColorStop(0.8, 'rgba(255,255,255,1)');
|
||||
@ -112,12 +120,24 @@ export const FloorSelector = defineComponent<
|
||||
|
||||
return () => (
|
||||
<container>
|
||||
<text text={floorName.value} loc={[90, 24]} anc={[0.5, 0.5]} />
|
||||
<g-line line={[48, 40, 132, 40]} lineWidth={1} />
|
||||
<g-line line={[48, 440, 132, 440]} lineWidth={1} />
|
||||
<text
|
||||
text={floorName.value}
|
||||
loc={[HALF_STATUS_WIDTH, 24]}
|
||||
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="退出"
|
||||
loc={[90, 456]}
|
||||
loc={[90, STATUS_BAR_HEIGHT - 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={close}
|
||||
@ -138,25 +158,25 @@ export const FloorSelector = defineComponent<
|
||||
/>
|
||||
<text
|
||||
text="「 下移一层 」"
|
||||
loc={[90, 370]}
|
||||
loc={[90, STATUS_BAR_HEIGHT - 110]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(-1)}
|
||||
/>
|
||||
<text
|
||||
text="「 下移十层 」"
|
||||
loc={[90, 410]}
|
||||
loc={[90, STATUS_BAR_HEIGHT - 70]}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={() => changeFloor(-10)}
|
||||
/>
|
||||
<container loc={[0, 140, 144, 200]}>
|
||||
<container loc={[0, 140, 144, SCROLL_HEIGHT]}>
|
||||
<Scroll
|
||||
ref={scrollRef}
|
||||
loc={[0, 0, 144, 200]}
|
||||
loc={[0, 0, 144, SCROLL_HEIGHT]}
|
||||
noscroll
|
||||
zIndex={10}
|
||||
padEnd={88}
|
||||
padEnd={HALF_HEIGHT - 12}
|
||||
>
|
||||
{floors.value.map((v, i, a) => {
|
||||
const floor = core.floors[v];
|
||||
@ -168,7 +188,7 @@ export const FloorSelector = defineComponent<
|
||||
return (
|
||||
<container
|
||||
nocache
|
||||
loc={[0, i * 24 + 88, 144, 24]}
|
||||
loc={[0, i * 24 + HALF_HEIGHT - 12, 144, 24]}
|
||||
key={v}
|
||||
>
|
||||
<text
|
||||
@ -195,14 +215,14 @@ export const FloorSelector = defineComponent<
|
||||
})}
|
||||
</Scroll>
|
||||
<g-line
|
||||
line={[130, 0, 130, 200]}
|
||||
line={[130, 0, 130, SCROLL_HEIGHT]}
|
||||
zIndex={5}
|
||||
lineWidth={1}
|
||||
strokeStyle="#aaa"
|
||||
/>
|
||||
<sprite
|
||||
zIndex={20}
|
||||
loc={[0, 0, 144, 200]}
|
||||
loc={[0, 0, 144, SCROLL_HEIGHT]}
|
||||
nocache
|
||||
noevent
|
||||
render={renderMask}
|
||||
|
@ -146,6 +146,7 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
|
||||
const renderer = MotaRenderer.get('render-main');
|
||||
const canvas = renderer?.getCanvas();
|
||||
if (!canvas) return;
|
||||
|
||||
const chain: RenderItem[] = [];
|
||||
let now: RenderItem | undefined = root.value;
|
||||
if (!now) return;
|
||||
@ -153,6 +154,8 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
|
||||
chain.unshift(now);
|
||||
now = now.parent;
|
||||
}
|
||||
|
||||
// 应用内边距偏移
|
||||
const { clientLeft, clientTop } = canvas;
|
||||
const trans = new Transform();
|
||||
trans.translate(clientLeft, clientTop);
|
||||
@ -163,8 +166,11 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
|
||||
trans.multiply(item.transform);
|
||||
}
|
||||
trans.translate(padding.value, padding.value);
|
||||
|
||||
// 构建CSS transform的matrix字符串
|
||||
const [a, b, , c, d, , e, f] = trans.mat;
|
||||
const str = `matrix(${a},${b},${c},${d},${e},${f})`;
|
||||
|
||||
const w = width.value * core.domStyle.scale;
|
||||
const h = height.value * core.domStyle.scale;
|
||||
const font = props.font ?? Font.defaults();
|
||||
@ -237,17 +243,29 @@ export const Input = defineComponent<InputProps, InputEmits, keyof InputEmits>(
|
||||
);
|
||||
|
||||
export interface InputBoxProps extends TextContentProps {
|
||||
/** 输入框对话框的位置 */
|
||||
loc: ElementLocator;
|
||||
/** 传递给内部 Input 组件的配置参数,用于自定义输入行为 */
|
||||
input?: InputProps;
|
||||
/** 窗口皮肤图片ID,用于对话框背景绘制 */
|
||||
winskin?: ImageIds;
|
||||
/** 对话框背景颜色,当未设置 winskin 时生效 */
|
||||
color?: CanvasStyle;
|
||||
/** 对话框边框颜色,当未设置 winskin 时生效 */
|
||||
border?: CanvasStyle;
|
||||
/** 对话框内部所有元素的内边距 */
|
||||
pad?: number;
|
||||
/** 内部输入框区域的高度 */
|
||||
inputHeight?: number;
|
||||
/** 对话框顶部的提示文本 */
|
||||
text?: string;
|
||||
/** 确认按钮的显示文本,默认为"确认" */
|
||||
yesText?: string;
|
||||
/** 取消按钮的显示文本,默认为"取消" */
|
||||
noText?: string;
|
||||
/** 确认/取消按钮的字体样式 */
|
||||
selFont?: Font;
|
||||
/** 确认/取消按钮的文本颜色 */
|
||||
selFill?: CanvasStyle;
|
||||
}
|
||||
|
||||
@ -505,6 +523,11 @@ export function getInput(
|
||||
|
||||
/**
|
||||
* 与 `getInput` 类似,不过会将结果转为数字。用法参考 {@link getInput}
|
||||
* @param controller UI 控制器
|
||||
* @param text 确认文本内容
|
||||
* @param loc 确认框的位置
|
||||
* @param width 确认框的宽度
|
||||
* @param props 额外的 props,参考 {@link ConfirmBoxProps}
|
||||
*/
|
||||
export async function getInputNumber(
|
||||
controller: IUIMountable,
|
||||
|
@ -118,6 +118,16 @@ export const Page = defineComponent<
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const round = computed(() => font.value.size / 4);
|
||||
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'));
|
||||
@ -135,11 +145,12 @@ export const Page = defineComponent<
|
||||
pageLoc.value = [0, height.value - pageH, width.value, pageH];
|
||||
const center = width.value / 2;
|
||||
const size = font.value.size * 1.5;
|
||||
const int = size + interval.value;
|
||||
nowPageLoc.value = [center, 0, size, size, 0.5, 0];
|
||||
leftPageLoc.value = [center - size * 1.5, 0, size, size, 0.5, 0];
|
||||
leftLoc.value = [center - size * 3, 0, size, size, 0.5, 0];
|
||||
rightPageLoc.value = [center + size * 1.5, 0, size, size, 0.5, 0];
|
||||
rightLoc.value = [center + size * 3, 0, size, size, 0.5, 0];
|
||||
leftPageLoc.value = [center - int, 0, size, size, 0.5, 0];
|
||||
leftLoc.value = [center - int * 2, 0, size, size, 0.5, 0];
|
||||
rightPageLoc.value = [center + int, 0, size, size, 0.5, 0];
|
||||
rightLoc.value = [center + int * 2, 0, size, size, 0.5, 0];
|
||||
};
|
||||
|
||||
const updateArrowPath = () => {
|
||||
|
@ -54,6 +54,7 @@ export interface ScrollExpose {
|
||||
export interface ScrollProps extends DefaultProps {
|
||||
loc: ElementLocator;
|
||||
hor?: boolean;
|
||||
/** 是否不允许滚动 */
|
||||
noscroll?: boolean;
|
||||
/**
|
||||
* 滚动到最下方(最右方)时的填充大小,如果默认的高度计算方式有误,
|
||||
@ -360,6 +361,8 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
||||
|
||||
//#region 事件监听
|
||||
|
||||
// todo: 滑动操作时的滚动惯性
|
||||
|
||||
const customPropagate = <T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
|
@ -130,7 +130,7 @@ export const TextContent = defineComponent<
|
||||
let needUpdate = false;
|
||||
|
||||
const retype = () => {
|
||||
if (needUpdate) return;
|
||||
if (needUpdate || props.hidden) return;
|
||||
needUpdate = true;
|
||||
if (!spriteElement.value) {
|
||||
needUpdate = false;
|
||||
@ -500,18 +500,28 @@ export const Textbox = defineComponent<
|
||||
}, textboxOptions);
|
||||
|
||||
interface TextboxStoreEmits {
|
||||
/** 结束打字机动画的回调函数 */
|
||||
endType: () => void;
|
||||
/** 隐藏文本框的回调函数 */
|
||||
hide: () => void;
|
||||
/** 显示文本框的回调函数 */
|
||||
show: () => void;
|
||||
/** 更新文本框配置的回调函数 */
|
||||
update: (value: TextboxProps) => void;
|
||||
/** 设置显示文本的回调函数 */
|
||||
setText: (text: string) => void;
|
||||
}
|
||||
|
||||
interface TextboxStoreEvent {
|
||||
/** 文本框配置更新事件,传递更新后的配置值 */
|
||||
update: [value: TextboxProps];
|
||||
/** 文本框显示事件 */
|
||||
show: [];
|
||||
/** 文本框隐藏事件 */
|
||||
hide: [];
|
||||
/** 打字机开始打字事件 */
|
||||
typeStart: [];
|
||||
/** 打字机结束打字事件 */
|
||||
typeEnd: [];
|
||||
}
|
||||
|
||||
|
@ -75,10 +75,15 @@ interface TyperConfig extends ITextContentConfig {
|
||||
}
|
||||
|
||||
interface ParserStatus {
|
||||
/** 画布填充描边样式 */
|
||||
fillStyle: CanvasStyle;
|
||||
/** 描边样式 */
|
||||
fontFamily: string;
|
||||
/** 字体大小 */
|
||||
fontSize: number;
|
||||
/** 是否斜体 */
|
||||
fontItalic: boolean;
|
||||
/** 字体粗细 */
|
||||
fontWeight: number;
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,15 @@ import { defineComponent, ref, watch } from 'vue';
|
||||
import { SetupComponentOptions } from '@motajs/system-ui';
|
||||
|
||||
export interface ThumbnailProps extends SpriteProps {
|
||||
/** 缩略图的位置 */
|
||||
loc: ElementLocator;
|
||||
/** 楼层 ID */
|
||||
floorId: FloorIds;
|
||||
/** 缩略图填充样式 */
|
||||
padStyle?: CanvasStyle;
|
||||
/** 楼层信息 */
|
||||
map?: Block[];
|
||||
/** 角色信息 */
|
||||
hero?: HeroStatus;
|
||||
// configs
|
||||
damage?: boolean;
|
||||
|
@ -8,9 +8,13 @@ import { texture } from '../elements';
|
||||
import { SetupComponentOptions } from '@motajs/system-ui';
|
||||
|
||||
export interface TipProps extends DefaultProps {
|
||||
/** 显示的位置 */
|
||||
loc: ElementLocator;
|
||||
/** 边距 */
|
||||
pad?: [number, number];
|
||||
/** 圆角 */
|
||||
corner?: number;
|
||||
/** 显示的图标 */
|
||||
id?: string;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,12 @@ import { RenderableData, texture } from './cache';
|
||||
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
|
||||
import { IAnimateFrame, renderEmits } from './frame';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import {
|
||||
MAP_BLOCK_HEIGHT,
|
||||
MAP_BLOCK_WIDTH,
|
||||
MAP_HEIGHT,
|
||||
MAP_WIDTH
|
||||
} from '../shared';
|
||||
|
||||
export interface ILayerGroupRenderExtends {
|
||||
/** 拓展的唯一标识符 */
|
||||
@ -104,7 +110,7 @@ export class LayerGroup
|
||||
// static list: Set<LayerGroup> = new Set();
|
||||
|
||||
cellSize: number = 32;
|
||||
blockSize: number = core._WIDTH_;
|
||||
blockSize: number = MAP_BLOCK_WIDTH;
|
||||
|
||||
/** 当前楼层 */
|
||||
floorId?: FloorIds;
|
||||
@ -126,7 +132,7 @@ export class LayerGroup
|
||||
|
||||
this.setHD(true);
|
||||
this.setAntiAliasing(false);
|
||||
this.size(core._PX_, core._PY_);
|
||||
this.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
|
||||
this.on('afterRender', () => {
|
||||
this.releaseNeedRender();
|
||||
@ -378,8 +384,8 @@ export function calNeedRenderOf(
|
||||
cell: number,
|
||||
block: BlockCacher<any>
|
||||
): Set<number> {
|
||||
const w = core._WIDTH_ * cell;
|
||||
const h = core._HEIGHT_ * cell;
|
||||
const w = MAP_BLOCK_WIDTH * cell;
|
||||
const h = MAP_BLOCK_HEIGHT * cell;
|
||||
const size = block.blockSize;
|
||||
const width = block.blockData.width;
|
||||
|
||||
@ -604,7 +610,7 @@ export class Layer extends Container<ELayerEvent> {
|
||||
block: BlockCacher<ICanvasCacheItem> = new BlockCacher(
|
||||
0,
|
||||
0,
|
||||
core._WIDTH_,
|
||||
MAP_BLOCK_WIDTH,
|
||||
4
|
||||
);
|
||||
|
||||
@ -625,20 +631,20 @@ export class Layer extends Container<ELayerEvent> {
|
||||
|
||||
// this.setHD(false);
|
||||
this.setAntiAliasing(false);
|
||||
this.size(core._PX_, core._PY_);
|
||||
this.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
|
||||
this.staticMap.setHD(false);
|
||||
this.staticMap.setAntiAliasing(false);
|
||||
this.staticMap.size(core._PX_, core._PY_);
|
||||
this.staticMap.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
this.movingMap.setHD(false);
|
||||
this.movingMap.setAntiAliasing(false);
|
||||
this.movingMap.size(core._PX_, core._PY_);
|
||||
this.movingMap.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
this.backMap.setHD(false);
|
||||
this.backMap.setAntiAliasing(false);
|
||||
this.backMap.size(core._PX_, core._PY_);
|
||||
this.backMap.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
this.main.setAntiAliasing(false);
|
||||
this.main.setHD(false);
|
||||
this.main.size(core._PX_, core._PY_);
|
||||
this.main.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
|
||||
this.appendChild(this.main);
|
||||
this.main.setRenderFn((canvas, transform) => {
|
||||
@ -789,7 +795,7 @@ export class Layer extends Container<ELayerEvent> {
|
||||
const [sx, sy, w, h] = data.render[i];
|
||||
canvas.setHD(false);
|
||||
canvas.setAntiAliasing(false);
|
||||
canvas.size(core._PX_, core._PY_);
|
||||
canvas.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
temp.size(w, h);
|
||||
|
||||
const img = data.autotile ? data.image[0b11111111] : data.image;
|
||||
@ -1204,7 +1210,7 @@ export class Layer extends Container<ELayerEvent> {
|
||||
const temp = this.requireCanvas(true, false);
|
||||
temp.setAntiAliasing(false);
|
||||
temp.setHD(false);
|
||||
temp.size(core._PX_, core._PY_);
|
||||
temp.size(MAP_WIDTH, MAP_HEIGHT);
|
||||
|
||||
// 先画到临时画布,用于缓存
|
||||
for (let nx = sx; nx < ex; nx++) {
|
||||
@ -1261,7 +1267,7 @@ export class Layer extends Container<ELayerEvent> {
|
||||
const [a, b, , c, d, , e, f] = mat;
|
||||
ctx.setTransform(a, b, c, d, e, f);
|
||||
const max1 = 1 / Math.min(a, b, c, d) ** 2;
|
||||
const max2 = Math.max(core._PX_, core._PY_) * 2;
|
||||
const max2 = Math.max(MAP_WIDTH, MAP_HEIGHT) * 2;
|
||||
const r = (max1 * max2) ** 2;
|
||||
|
||||
this.movingRenderable.forEach(v => {
|
||||
|
@ -1,10 +1,103 @@
|
||||
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_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 STATUS_BAR_COUNT = ENABLE_RIGHT_STATUS_BAR ? 2 : 1;
|
||||
/** 状态栏宽度的一半 */
|
||||
export const HALF_STATUS_WIDTH = STATUS_BAR_WIDTH / 2;
|
||||
|
||||
export const MAP_WIDTH = 480;
|
||||
export const MAP_HEIGHT = 480;
|
||||
//#region 游戏画面
|
||||
|
||||
export const MAIN_WIDTH = 480 + 180 * 2;
|
||||
export const MAIN_HEIGHT = 480;
|
||||
/** 游戏画面像素宽度,宽=地图宽度+状态栏宽度*状态栏数量 */
|
||||
export const MAIN_WIDTH = MAP_WIDTH + STATUS_BAR_WIDTH * STATUS_BAR_COUNT;
|
||||
/** 游戏画面像素高度 */
|
||||
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;
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
IActionEvent,
|
||||
MotaOffscreenCanvas2D,
|
||||
Sprite,
|
||||
onTick
|
||||
onTick,
|
||||
transformCanvas
|
||||
} from '@motajs/render';
|
||||
import { WeatherController } from '../weather';
|
||||
import {
|
||||
@ -19,8 +20,12 @@ import {
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI } from '@motajs/system-ui';
|
||||
import {
|
||||
ENABLE_RIGHT_STATUS_BAR,
|
||||
MAIN_HEIGHT,
|
||||
MAIN_WIDTH,
|
||||
MAP_HEIGHT,
|
||||
MAP_WIDTH,
|
||||
RIGHT_STATUS_POS,
|
||||
STATUS_BAR_HEIGHT,
|
||||
STATUS_BAR_WIDTH
|
||||
} from '../shared';
|
||||
@ -71,7 +76,7 @@ const MainScene = defineComponent(() => {
|
||||
const mainTextboxProps: Props<typeof Textbox> = {
|
||||
text: '',
|
||||
hidden: true,
|
||||
loc: [0, 330, 480, 150],
|
||||
loc: [0, MAP_HEIGHT - 150, MAIN_WIDTH, 150],
|
||||
zIndex: 30,
|
||||
fillStyle: '#fff',
|
||||
titleFill: 'gold',
|
||||
@ -80,7 +85,7 @@ const MainScene = defineComponent(() => {
|
||||
winskin: 'winskin2.png',
|
||||
interval: 100,
|
||||
lineHeight: 4,
|
||||
width: 480
|
||||
width: MAP_WIDTH
|
||||
};
|
||||
|
||||
const map = shallowRef<LayerGroup>();
|
||||
@ -207,7 +212,9 @@ const MainScene = defineComponent(() => {
|
||||
|
||||
const renderMapMisc = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const step = core.status.stepPostfix;
|
||||
if (!step) return;
|
||||
const camera = map.value?.camera;
|
||||
if (!step || !camera) return;
|
||||
transformCanvas(canvas, camera);
|
||||
const ctx = canvas.ctx;
|
||||
ctx.fillStyle = '#fff';
|
||||
step.forEach(({ x, y, direction }) => {
|
||||
@ -267,10 +274,13 @@ const MainScene = defineComponent(() => {
|
||||
status={leftStatus}
|
||||
hidden={hideStatus.value}
|
||||
></LeftStatusBar>
|
||||
<g-line line={[180, 0, 180, 480]} lineWidth={1} />
|
||||
<g-line
|
||||
line={[STATUS_BAR_WIDTH, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
|
||||
lineWidth={1}
|
||||
/>
|
||||
<container
|
||||
id="map-draw"
|
||||
loc={[180, 0, 480, 480]}
|
||||
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
zIndex={10}
|
||||
onClick={clickMap}
|
||||
onDown={downMap}
|
||||
@ -295,17 +305,20 @@ const MainScene = defineComponent(() => {
|
||||
/>
|
||||
<sprite
|
||||
noevent
|
||||
loc={[0, 0, 480, 480]}
|
||||
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
ref={mapMiscSprite}
|
||||
zIndex={170}
|
||||
render={renderMapMisc}
|
||||
/>
|
||||
</container>
|
||||
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
|
||||
<g-line
|
||||
line={[RIGHT_STATUS_POS, 0, RIGHT_STATUS_POS, MAP_HEIGHT]}
|
||||
lineWidth={1}
|
||||
/>
|
||||
<RightStatusBar
|
||||
loc={[480 + 180, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||
loc={[RIGHT_STATUS_POS, 0, STATUS_BAR_WIDTH, STATUS_BAR_HEIGHT]}
|
||||
status={rightStatus}
|
||||
hidden={hideStatus.value}
|
||||
hidden={hideStatus.value && ENABLE_RIGHT_STATUS_BAR}
|
||||
></RightStatusBar>
|
||||
<container
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
@ -322,12 +335,17 @@ const MainScene = defineComponent(() => {
|
||||
noevent
|
||||
></g-rect>
|
||||
<g-line
|
||||
line={[180, 0, 480 + 180, 0]}
|
||||
line={[STATUS_BAR_WIDTH, 0, RIGHT_STATUS_POS, 0]}
|
||||
hidden={!hideStatus.value}
|
||||
zIndex={100}
|
||||
/>
|
||||
<g-line
|
||||
line={[180, 480, 480 + 180, 480]}
|
||||
line={[
|
||||
STATUS_BAR_WIDTH,
|
||||
MAP_HEIGHT,
|
||||
RIGHT_STATUS_POS,
|
||||
MAP_HEIGHT
|
||||
]}
|
||||
hidden={!hideStatus.value}
|
||||
zIndex={100}
|
||||
/>
|
||||
|
@ -16,7 +16,19 @@ import {
|
||||
} from 'vue';
|
||||
import { getConfirm, Page, PageExpose, Thumbnail } from '../components';
|
||||
import { useKey } from '../use';
|
||||
import { MAP_WIDTH } from '../shared';
|
||||
import {
|
||||
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';
|
||||
|
||||
export const enum SaveMode {
|
||||
@ -61,10 +73,11 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
|
||||
const statusFont = new Font('normal', 14);
|
||||
|
||||
const w = computed(() => props.loc[2] ?? 200);
|
||||
const h = computed(() => props.loc[3] ?? 200);
|
||||
const lineWidth = computed(() => (props.selected ? 4 : 2));
|
||||
const imgLoc = computed<ElementLocator>(() => {
|
||||
const size = w.value - 4;
|
||||
return [2, 24, size, size];
|
||||
return [2, SAVE_ITEM_TOP, size, size];
|
||||
});
|
||||
|
||||
const name = computed(() => {
|
||||
@ -102,7 +115,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
|
||||
<text
|
||||
text={name.value}
|
||||
font={font}
|
||||
loc={[w.value / 2, 20]}
|
||||
loc={[w.value / 2, SAVE_ITEM_TOP - 4]}
|
||||
anc={[0.5, 1]}
|
||||
/>
|
||||
<g-rect
|
||||
@ -130,7 +143,7 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
|
||||
text={statusText.value}
|
||||
fillStyle="yellow"
|
||||
font={statusFont}
|
||||
loc={[w.value / 2, w.value + 28]}
|
||||
loc={[w.value / 2, h.value - SAVE_ITEM_DOWN + 2]}
|
||||
anc={[0.5, 0]}
|
||||
/>
|
||||
</container>
|
||||
@ -139,12 +152,8 @@ export const SaveItem = defineComponent<SaveItemProps>(props => {
|
||||
|
||||
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||||
(props, { emit }) => {
|
||||
const itemSize = 150;
|
||||
const itemHeight = itemSize + 40;
|
||||
const interval = 30;
|
||||
|
||||
const font = new Font('normal', 18);
|
||||
const pageFont = new Font('normal', 14);
|
||||
const font = Font.defaults({ size: 18 });
|
||||
const pageFont = Font.defaults({ size: 14 });
|
||||
|
||||
/** 当前页上被选中的存档的posIndex */
|
||||
const selected = ref(0);
|
||||
@ -160,16 +169,16 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||||
const grid = computed<IGridLayoutData>(() =>
|
||||
adjustGrid(
|
||||
width.value,
|
||||
height.value - 30,
|
||||
itemSize,
|
||||
itemHeight,
|
||||
interval
|
||||
height.value - SAVE_DOWN_PAD,
|
||||
SAVE_ITEM_SIZE,
|
||||
SAVE_ITEM_HEIGHT,
|
||||
SAVE_INTERVAL
|
||||
)
|
||||
);
|
||||
|
||||
const contentLoc = computed<ElementLocator>(() => {
|
||||
const cx = width.value / 2;
|
||||
const cy = (height.value - 30) / 2;
|
||||
const cy = (height.value - SAVE_DOWN_PAD) / 2;
|
||||
return [cx, cy, grid.value.width, grid.value.height, 0.5, 0.5];
|
||||
});
|
||||
|
||||
@ -244,8 +253,8 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
`确认要删除存档 ${index + 1}?`,
|
||||
[420, 240, void 0, void 0, 0.5, 0.5],
|
||||
240,
|
||||
[HALF_WIDTH, HALF_HEIGHT, void 0, void 0, 0.5, 0.5],
|
||||
POP_BOX_WIDTH,
|
||||
{ winskin: 'winskin2.png' }
|
||||
);
|
||||
if (confirm) {
|
||||
@ -370,6 +379,9 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||||
);
|
||||
|
||||
//#region 事件监听
|
||||
|
||||
// todo: 按住快速切换页码
|
||||
|
||||
const wheel = (ev: IWheelEvent) => {
|
||||
const delta = Math.sign(ev.wheelY);
|
||||
if (ev.ctrlKey) {
|
||||
@ -384,7 +396,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
|
||||
<Page
|
||||
ref={pageRef}
|
||||
loc={[0, 0, width.value, height.value - 10]}
|
||||
pages={1000}
|
||||
pages={SAVE_PAGES}
|
||||
font={pageFont}
|
||||
v-model:page={now.value}
|
||||
onWheel={wheel}
|
||||
|
@ -24,6 +24,8 @@ import { openStatistics } from './statistics';
|
||||
import { saveWithExist } from './save';
|
||||
import { compressToBase64 } from 'lz-string';
|
||||
import { ViewMapUI } from './viewmap';
|
||||
import { CENTER_LOC, FULL_LOC, MAIN_HEIGHT, POP_BOX_WIDTH } from '../shared';
|
||||
import { useKey } from '../use';
|
||||
|
||||
export interface MainSettingsProps
|
||||
extends Partial<ChoicesProps>,
|
||||
@ -61,6 +63,9 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
|
||||
[MainChoice.Back, '返回游戏']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case MainChoice.SystemSetting: {
|
||||
@ -79,7 +84,9 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
|
||||
break;
|
||||
}
|
||||
case MainChoice.ViewMap: {
|
||||
props.controller.open(ViewMapUI, { loc: [0, 0, 840, 840] });
|
||||
props.controller.open(ViewMapUI, {
|
||||
loc: FULL_LOC
|
||||
});
|
||||
break;
|
||||
}
|
||||
case MainChoice.Replay: {
|
||||
@ -98,8 +105,8 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'确认要返回标题吗?',
|
||||
[420, 240, void 0, void 0, 0.5, 0.5],
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
props.controller.closeAll();
|
||||
@ -118,10 +125,11 @@ export const MainSettings = defineComponent<MainSettingsProps>(props => {
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
choices={choices}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
onChoose={choose}
|
||||
maxHeight={400}
|
||||
maxHeight={MAIN_HEIGHT - 64}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -147,6 +155,9 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
[ReplayChoice.Back, '返回游戏']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case ReplayChoice.Start: {
|
||||
@ -158,10 +169,7 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
break;
|
||||
}
|
||||
case ReplayChoice.StartFromSave: {
|
||||
const index = await saveWithExist(
|
||||
props.controller,
|
||||
[0, 0, 840, 480]
|
||||
);
|
||||
const index = await saveWithExist(props.controller, FULL_LOC);
|
||||
if (index === -2) break;
|
||||
if (index === -1) {
|
||||
core.doSL('autoSave', 'replayLoad');
|
||||
@ -172,10 +180,7 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
break;
|
||||
}
|
||||
case ReplayChoice.ResumeReplay: {
|
||||
const index = await saveWithExist(
|
||||
props.controller,
|
||||
[0, 0, 840, 480]
|
||||
);
|
||||
const index = await saveWithExist(props.controller, FULL_LOC);
|
||||
if (index === -2) break;
|
||||
const name = index === -1 ? 'autoSave' : index + 1;
|
||||
const success = core.doSL(name, 'replayRemain');
|
||||
@ -185,14 +190,11 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
}
|
||||
await getConfirm(
|
||||
props.controller,
|
||||
'[步骤2]请选择第二个存档。\n\r[yellow]该存档必须是前一个存档的后续。\r\n将尝试播放到此存档。',
|
||||
[420, 240, void 0, void 0, 0.5, 0.5],
|
||||
240
|
||||
);
|
||||
const index2 = await saveWithExist(
|
||||
props.controller,
|
||||
[0, 0, 840, 480]
|
||||
'[步骤2]请选择第二个存档。\n\\r[yellow]该存档必须是前一个存档的后续。\\r\n将尝试播放到此存档。',
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
const index2 = await saveWithExist(props.controller, FULL_LOC);
|
||||
if (index2 === -2) break;
|
||||
const name2 = index2 === -1 ? 'autoSave' : index2 + 1;
|
||||
core.doSL(name2, 'replayRemain');
|
||||
@ -200,10 +202,7 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
break;
|
||||
}
|
||||
case ReplayChoice.ReplayRest: {
|
||||
const index = await saveWithExist(
|
||||
props.controller,
|
||||
[0, 0, 840, 480]
|
||||
);
|
||||
const index = await saveWithExist(props.controller, FULL_LOC);
|
||||
if (index === -2) break;
|
||||
if (index === -1) {
|
||||
core.doSL('autoSave', 'replaySince');
|
||||
@ -243,9 +242,10 @@ export const ReplaySettings = defineComponent<MainSettingsProps>(props => {
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
choices={choice}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -269,6 +269,9 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
|
||||
[GameInfoChoice.Back, '返回主菜单']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case GameInfoChoice.Statistics: {
|
||||
@ -281,8 +284,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'即将离开本游戏,跳转至工程页面,确认跳转?',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
window.location.href = 'editor-mobile.html';
|
||||
@ -299,8 +302,8 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'即将离开本游戏,跳转至评论页面,确认跳转?',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
window.location.href = href;
|
||||
@ -330,9 +333,10 @@ export const GameInfo = defineComponent<MainSettingsProps>(props => {
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
choices={choices}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -360,6 +364,9 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
|
||||
[SyncSaveChoice.Back, '返回上一级']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case SyncSaveChoice.ToServer: {
|
||||
@ -370,8 +377,8 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
|
||||
const replay = await getInput(
|
||||
props.controller,
|
||||
'请输入存档编号+密码',
|
||||
[240, 240, void 0, void 0, 0.5, 0.5],
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
await syncFromServer(props.controller, replay);
|
||||
break;
|
||||
@ -398,10 +405,11 @@ export const SyncSave = defineComponent<MainSettingsProps>(props => {
|
||||
return () => (
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
choices={choices}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -413,6 +421,9 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
[SyncSaveChoice.Back, '返回上一级']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case SyncSaveChoice.AllSaves: {
|
||||
@ -420,8 +431,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'你确定要同步全部存档么?这可能在存档较多的时候比较慢。',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
core.syncSave('all');
|
||||
@ -433,8 +444,8 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'确定要同步当前存档吗?',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
core.syncSave();
|
||||
@ -451,10 +462,11 @@ export const SyncSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
return () => (
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
choices={choices}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -466,20 +478,23 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
[SyncSaveChoice.Back, '返回上一级']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case SyncSaveChoice.AllSaves: {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'确认要下载所有存档吗?',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
const data = await waitbox(
|
||||
props.controller,
|
||||
props.loc,
|
||||
240,
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH,
|
||||
getAllSavesData(),
|
||||
{ text: '请等待处理完毕' }
|
||||
);
|
||||
@ -496,8 +511,8 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'确认要下载当前存档吗?',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
const data = await getSaveData(core.saves.saveIndex);
|
||||
@ -520,10 +535,11 @@ export const DownloadSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
return () => (
|
||||
<Choices
|
||||
loc={props.loc}
|
||||
width={240}
|
||||
width={POP_BOX_WIDTH}
|
||||
choices={choices}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
@ -535,20 +551,23 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
[SyncSaveChoice.Back, '返回上一级']
|
||||
];
|
||||
|
||||
const [key, scope] = useKey();
|
||||
key.realize('exit', () => props.controller.close(props.instance));
|
||||
|
||||
const choose = async (key: ChoiceKey) => {
|
||||
switch (key) {
|
||||
case SyncSaveChoice.AllSaves: {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'你确定要清除【全部游戏】的所有本地存档?此行为不可逆!!!',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
await waitbox(
|
||||
props.controller,
|
||||
props.loc,
|
||||
240,
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH,
|
||||
new Promise<void>(res => {
|
||||
core.clearLocalForage(() => {
|
||||
core.saves.ids = {};
|
||||
@ -571,8 +590,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
await getConfirm(
|
||||
props.controller,
|
||||
'所有塔的存档已经全部清空',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -581,14 +600,14 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
const confirm = await getConfirm(
|
||||
props.controller,
|
||||
'你确定要清除【当前游戏】的所有本地存档?此行为不可逆!!!',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
if (confirm) {
|
||||
await waitbox(
|
||||
props.controller,
|
||||
props.loc,
|
||||
240,
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH,
|
||||
new Promise<void>(res => {
|
||||
Object.keys(core.saves.ids).forEach(function (v) {
|
||||
core.removeLocalForage('save' + v);
|
||||
@ -613,8 +632,8 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
await getConfirm(
|
||||
props.controller,
|
||||
'当前塔的存档已被清空',
|
||||
props.loc,
|
||||
240
|
||||
CENTER_LOC,
|
||||
POP_BOX_WIDTH
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -633,6 +652,7 @@ export const ClearSaveSelect = defineComponent<MainSettingsProps>(props => {
|
||||
choices={choices}
|
||||
onChoose={choose}
|
||||
interval={8}
|
||||
scope={scope}
|
||||
/>
|
||||
);
|
||||
}, mainSettingsProps);
|
||||
|
@ -91,9 +91,9 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>(
|
||||
return num.toString().padStart(2, '0');
|
||||
};
|
||||
|
||||
const font1 = new Font('normal', 18);
|
||||
const font2 = new Font('normal', 18, 'px', 700);
|
||||
const font3 = new Font('normal', 14, 'px', 700);
|
||||
const font1 = Font.defaults({ size: 18 });
|
||||
const font2 = Font.defaults({ size: 18, weight: 700 });
|
||||
const font3 = Font.defaults({ size: 14, weight: 700 });
|
||||
|
||||
const iconLoc = (n: number): ElementLocator => {
|
||||
return [16, 76 + 44 * n, 32, 32];
|
||||
|
@ -5,7 +5,22 @@ import {
|
||||
UIComponentProps
|
||||
} from '@motajs/system-ui';
|
||||
import { defineComponent, nextTick, onMounted, ref } from 'vue';
|
||||
import { MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
|
||||
import {
|
||||
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 {
|
||||
ElementLocator,
|
||||
IActionEvent,
|
||||
@ -147,8 +162,8 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
|
||||
let cursorScale = 1;
|
||||
|
||||
const titleFont = new Font('normal', 72).string();
|
||||
const buttonFont = new Font('normal', 24, 'px', 600);
|
||||
const titleFont = Font.defaults({ size: 72 });
|
||||
const buttonFont = Font.defaults({ size: 24, weight: 600 });
|
||||
|
||||
//#region 按钮功能
|
||||
|
||||
@ -388,7 +403,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
}
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = titleFont;
|
||||
ctx.font = titleFont.string();
|
||||
ctx.fillStyle = titleGradient!;
|
||||
ctx.filter = `
|
||||
drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.5))
|
||||
@ -396,7 +411,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
drop-shadow(12px 12px 4px rgba(0, 0, 0, 0.4))
|
||||
blur(1px)
|
||||
`;
|
||||
ctx.fillText(core.firstData.title, 320, 50);
|
||||
ctx.fillText(core.firstData.title, HALF_TITLE_WIDTH, HALF_TITLE_HEIGHT);
|
||||
};
|
||||
|
||||
const renderCursor = (canvas: MotaOffscreenCanvas2D) => {
|
||||
@ -420,15 +435,15 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
>
|
||||
<image
|
||||
image={bg}
|
||||
loc={[MAIN_WIDTH / 2, MAIN_HEIGHT / 2, width, height]}
|
||||
loc={[HALF_WIDTH, HALF_HEIGHT, width, height]}
|
||||
anc={[0.5, 0.5]}
|
||||
filter="brightness(120%)contrast(110%)"
|
||||
zIndex={0}
|
||||
/>
|
||||
<shader
|
||||
ref={imageShader}
|
||||
zIndex={5}
|
||||
loc={[0, 0, MAIN_WIDTH, MAIN_HEIGHT]}
|
||||
filter="brightness(120%)contrast(110%)"
|
||||
/>
|
||||
<sprite
|
||||
ref={maskSprite}
|
||||
@ -441,17 +456,16 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
<sprite
|
||||
ref={titleSprite}
|
||||
render={renderTitle}
|
||||
loc={[MAIN_WIDTH / 2, 120, 640, 100, 0.5, 0.5]}
|
||||
loc={[TITLE_X, TITLE_Y, TITLE_WIDTH, TITLE_HEIGHT, 0.5, 0.5]}
|
||||
zIndex={10}
|
||||
/>
|
||||
<container
|
||||
zIndex={15}
|
||||
loc={[50, MAIN_HEIGHT, 200, 160]}
|
||||
anc={[0, 1]}
|
||||
loc={[BUTTONS_X, BUTTONS_Y, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
|
||||
>
|
||||
<container
|
||||
hidden={selectHard.value}
|
||||
loc={[0, 0, 200, 160]}
|
||||
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
|
||||
alpha={buttonsAlpha.ref.value}
|
||||
>
|
||||
{buttons.map((v, i) => {
|
||||
@ -475,7 +489,7 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
</container>
|
||||
<container
|
||||
hidden={!selectHard.value}
|
||||
loc={[0, 0, 200, 160]}
|
||||
loc={[0, 0, BUTTONS_WIDTH, BUTTONS_HEIGHT]}
|
||||
alpha={buttonsAlpha.ref.value}
|
||||
>
|
||||
{hard.map((v, i) => {
|
||||
@ -528,16 +542,15 @@ export const GameTitle = defineComponent<GameTitleProps>(props => {
|
||||
cursor="pointer"
|
||||
/>
|
||||
)}
|
||||
{!soundOpened.value && (
|
||||
<g-line
|
||||
line={[5, 35, 35, 5]}
|
||||
strokeStyle="gray"
|
||||
lineWidth={3}
|
||||
lineCap="round"
|
||||
noevent
|
||||
zIndex={5}
|
||||
/>
|
||||
)}
|
||||
<g-line
|
||||
line={[5, 35, 35, 5]}
|
||||
strokeStyle="gray"
|
||||
lineWidth={3}
|
||||
lineCap="round"
|
||||
zIndex={5}
|
||||
noevent
|
||||
hidden={soundOpened.value}
|
||||
/>
|
||||
</container>
|
||||
</container>
|
||||
);
|
||||
|
@ -23,7 +23,7 @@ import { generateBinary } from '@motajs/legacy-common';
|
||||
import { SetupComponentOptions } from '@motajs/system-ui';
|
||||
import { saveSave, saveLoad } from './save';
|
||||
import { mainUIController } from './controller';
|
||||
import { MAIN_WIDTH, MAIN_HEIGHT } from '../shared';
|
||||
import { MAIN_HEIGHT, FULL_LOC, POP_BOX_WIDTH, CENTER_LOC } from '../shared';
|
||||
import { openSettings } from './settings';
|
||||
import { openViewMap } from './viewmap';
|
||||
|
||||
@ -90,10 +90,10 @@ export const PlayingToolbar = defineComponent<
|
||||
const tool = () => core.openToolbox(true);
|
||||
const fly = () => core.useFly(true);
|
||||
const save = () => {
|
||||
saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
saveSave(mainUIController, FULL_LOC);
|
||||
};
|
||||
const load = () => {
|
||||
saveLoad(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
saveLoad(mainUIController, FULL_LOC);
|
||||
};
|
||||
const equip = () => core.openEquipbox(true);
|
||||
const shop = () => core.openQuickShop(true);
|
||||
@ -111,12 +111,15 @@ export const PlayingToolbar = defineComponent<
|
||||
const redo = () => core.doSL('autoSave', 'reload');
|
||||
const numpad = () => emit('numpad');
|
||||
const view = () => {
|
||||
openViewMap(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
openViewMap(mainUIController, FULL_LOC);
|
||||
};
|
||||
const danmaku = () => requestAnimationFrame(openDanmakuPoster);
|
||||
const replay = () => core.ui._drawReplay();
|
||||
const settings = () => {
|
||||
openSettings(mainUIController, [420, 240, 240, 400, 0.5, 0.5]);
|
||||
const loc = CENTER_LOC.slice() as ElementLocator;
|
||||
loc[2] = POP_BOX_WIDTH;
|
||||
loc[3] = MAIN_HEIGHT - 72;
|
||||
openSettings(mainUIController, loc);
|
||||
};
|
||||
|
||||
return () => (
|
||||
@ -166,7 +169,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
|
||||
|
||||
const bookIcon = core.statusBar.icons.book;
|
||||
const saveIcon = core.statusBar.icons.save;
|
||||
const font1 = new Font('normal', 16);
|
||||
const font1 = Font.defaults({ size: 16 });
|
||||
const font2 = new Font('Verdana', 12);
|
||||
|
||||
const speedText = computed(() => `${status.speed}速`);
|
||||
@ -183,7 +186,7 @@ export const ReplayingToolbar = defineComponent<ReplayingProps>(props => {
|
||||
const speedUp = () => core.speedUpReplay();
|
||||
const book = () => core.openBook(true);
|
||||
const save = () => {
|
||||
saveSave(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]);
|
||||
saveSave(mainUIController, FULL_LOC);
|
||||
};
|
||||
const view = () => {
|
||||
if (core.isPlaying() && !core.isMoving() && !core.status.lockControl) {
|
||||
|
@ -38,6 +38,18 @@ import { clamp, mean } from 'lodash-es';
|
||||
import { calculateStatisticsOne, StatisticsDataOneFloor } from './statistics';
|
||||
import { Tip, TipExpose } from '../components';
|
||||
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 {
|
||||
loc: ElementLocator;
|
||||
@ -59,6 +71,12 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
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 viewableFloor = markRaw(
|
||||
@ -94,7 +112,13 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
.realize('@viewMap_book', () => openBook())
|
||||
.realize('@viewMap_fly', () => fly())
|
||||
.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 功能函数
|
||||
|
||||
@ -202,6 +226,28 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
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 地图交互
|
||||
|
||||
let mouseDown = false;
|
||||
@ -335,12 +381,18 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
|
||||
return () => (
|
||||
<container loc={props.loc} nocache>
|
||||
<g-rect fillStyle="black" fill loc={[0, 0, 840, 480]} />
|
||||
<g-rect stroke zIndex={100} loc={[0, 0, 840, 480]} noevent />
|
||||
<g-line line={[180, 0, 180, 480]} lineWidth={1} />
|
||||
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
|
||||
<g-rect fillStyle="black" fill loc={FULL_LOC} />
|
||||
<g-rect stroke zIndex={100} loc={FULL_LOC} noevent />
|
||||
<g-line
|
||||
line={[STATUS_BAR_WIDTH, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
|
||||
lineWidth={1}
|
||||
/>
|
||||
<g-line
|
||||
line={[RIGHT_STATUS_POS, 0, RIGHT_STATUS_POS, MAIN_HEIGHT]}
|
||||
lineWidth={1}
|
||||
/>
|
||||
<FloorSelector
|
||||
loc={[0, 0, 180, 480]}
|
||||
loc={[0, 0, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
|
||||
floors={viewableFloor}
|
||||
v-model:now={now.value}
|
||||
onClose={close}
|
||||
@ -348,7 +400,7 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
<layer-group
|
||||
ref={group}
|
||||
ex={layerGroupExtends}
|
||||
loc={[180, 0, 480, 480]}
|
||||
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
onDown={downMap}
|
||||
onMove={moveMap}
|
||||
onUp={upMap}
|
||||
@ -365,45 +417,69 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
<Tip
|
||||
ref={tip}
|
||||
zIndex={40}
|
||||
loc={[188, 8, 200, 32]}
|
||||
loc={[STATUS_BAR_WIDTH + 8, 8, 200, 32]}
|
||||
pad={[12, 6]}
|
||||
corner={16}
|
||||
/>
|
||||
<sprite
|
||||
loc={[180, 0, 480, 64]}
|
||||
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, 64]}
|
||||
render={renderTop}
|
||||
alpha={topAlpha.value}
|
||||
zIndex={10}
|
||||
cursor="pointer"
|
||||
onEnter={enterTop}
|
||||
onLeave={leaveTop}
|
||||
onClick={() => changeFloor(1)}
|
||||
onClick={clickTop}
|
||||
/>
|
||||
<sprite
|
||||
loc={[180, 416, 480, 64]}
|
||||
loc={[STATUS_BAR_WIDTH, MAP_HEIGHT - 64, MAP_WIDTH, 64]}
|
||||
render={renderBottom}
|
||||
alpha={bottomAlpha.value}
|
||||
zIndex={10}
|
||||
cursor="pointer"
|
||||
onEnter={enterBottom}
|
||||
onLeave={leaveBottom}
|
||||
onClick={() => changeFloor(-1)}
|
||||
onClick={clickBottom}
|
||||
/>
|
||||
<text
|
||||
text="上移地图"
|
||||
loc={[420, 24]}
|
||||
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
zIndex={20}
|
||||
noevent
|
||||
/>
|
||||
<text
|
||||
text="下移地图"
|
||||
loc={[420, 456]}
|
||||
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, MAP_HEIGHT - 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
zIndex={20}
|
||||
noevent
|
||||
/>
|
||||
<container loc={[660, 0, 180, 480]}>
|
||||
<text
|
||||
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="鼠标 / 单指拖动地图"
|
||||
font={rightFont}
|
||||
@ -464,21 +540,21 @@ export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
<g-line line={[12, 292, 168, 292]} lineWidth={1} />
|
||||
<text
|
||||
text="「 怪物手册 」"
|
||||
loc={[90, 330]}
|
||||
loc={loc1}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={openBook}
|
||||
/>
|
||||
<text
|
||||
text="「 传送至此 」"
|
||||
loc={[90, 380]}
|
||||
loc={loc2}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={fly}
|
||||
/>
|
||||
<text
|
||||
text="「 重置视角 」"
|
||||
loc={[90, 430]}
|
||||
loc={loc3}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={resetCamera}
|
||||
|
@ -73,17 +73,22 @@ type KeyUsing = [Hotkey, symbol];
|
||||
/**
|
||||
* 在组件中定义按键操作
|
||||
* @param noScope 是否不创建新作用域
|
||||
* @param scope 指定作用域,如果 `noScope` 为 `true`,则此项无效
|
||||
*/
|
||||
export function useKey(noScope: boolean = false): KeyUsing {
|
||||
export function useKey(noScope: boolean = false, scope?: symbol): KeyUsing {
|
||||
if (noScope) {
|
||||
return [gameKey, gameKey.scope];
|
||||
} else {
|
||||
const sym = Symbol();
|
||||
gameKey.use(sym);
|
||||
onUnmounted(() => {
|
||||
gameKey.dispose();
|
||||
});
|
||||
return [gameKey, sym];
|
||||
const sym = scope ?? Symbol();
|
||||
if (sym === gameKey.scope) {
|
||||
return [gameKey, gameKey.scope];
|
||||
} else {
|
||||
gameKey.use(sym);
|
||||
onUnmounted(() => {
|
||||
gameKey.dispose();
|
||||
});
|
||||
return [gameKey, sym];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ class GameListener extends EventEmitter<ListenerEvent> {
|
||||
constructor() {
|
||||
super();
|
||||
if (main.replayChecking) return;
|
||||
if (!!window.core) {
|
||||
if (window.core) {
|
||||
this.init();
|
||||
} else {
|
||||
loading.once('coreInit', () => {
|
||||
@ -176,84 +176,80 @@ class GameListener extends EventEmitter<ListenerEvent> {
|
||||
|
||||
private init() {
|
||||
// ----- block
|
||||
|
||||
const data = core.canvas.data.canvas;
|
||||
|
||||
const getBlockLoc = (px: number, py: number, size: number) => {
|
||||
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 ||
|
||||
!core.isPlaying() ||
|
||||
!core.status.floorId
|
||||
)
|
||||
return;
|
||||
this.emit('mouseMove', e);
|
||||
const {
|
||||
x: px,
|
||||
y: py,
|
||||
size
|
||||
} = core.actions._getClickLoc(e.offsetX, e.offsetY);
|
||||
const [bx, by] = getBlockLoc(px, py, size);
|
||||
const blocks = core.getMapBlocksObj();
|
||||
if (this.mouseX !== bx || this.mouseY !== by) {
|
||||
const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
|
||||
const block = blocks[`${bx},${by}`];
|
||||
if (!!lastBlock) {
|
||||
this.emit('leaveBlock', lastBlock, e, false);
|
||||
}
|
||||
if (!!block) {
|
||||
this.emit('hoverBlock', block, e);
|
||||
this.mouseX = bx;
|
||||
this.mouseY = by;
|
||||
} else {
|
||||
this.mouseX = -1;
|
||||
this.mouseY = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
data.addEventListener('mouseleave', e => {
|
||||
if (
|
||||
core.status.lockControl ||
|
||||
!core.isPlaying() ||
|
||||
!core.status.floorId
|
||||
)
|
||||
return;
|
||||
const blocks = core.getMapBlocksObj();
|
||||
const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
|
||||
if (!!lastBlock) {
|
||||
this.emit('leaveBlock', lastBlock, e, true);
|
||||
}
|
||||
this.mouseX = -1;
|
||||
this.mouseY = -1;
|
||||
});
|
||||
// click
|
||||
data.addEventListener('click', e => {
|
||||
if (
|
||||
core.status.lockControl ||
|
||||
!core.isPlaying() ||
|
||||
!core.status.floorId
|
||||
)
|
||||
return;
|
||||
const {
|
||||
x: px,
|
||||
y: py,
|
||||
size
|
||||
} = core.actions._getClickLoc(e.offsetX, e.offsetY);
|
||||
const [bx, by] = getBlockLoc(px, py, size);
|
||||
const blocks = core.getMapBlocksObj();
|
||||
const block = blocks[`${bx},${by}`];
|
||||
if (!!block) {
|
||||
this.emit('clickBlock', block, e);
|
||||
}
|
||||
});
|
||||
|
||||
// const data = core.canvas.data.canvas;
|
||||
// const getBlockLoc = (px: number, py: number, size: number) => {
|
||||
// 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 ||
|
||||
// !core.isPlaying() ||
|
||||
// !core.status.floorId
|
||||
// )
|
||||
// return;
|
||||
// this.emit('mouseMove', e);
|
||||
// const {
|
||||
// x: px,
|
||||
// y: py,
|
||||
// size
|
||||
// } = core.actions._getClickLoc(e.offsetX, e.offsetY);
|
||||
// const [bx, by] = getBlockLoc(px, py, size);
|
||||
// const blocks = core.getMapBlocksObj();
|
||||
// if (this.mouseX !== bx || this.mouseY !== by) {
|
||||
// const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
|
||||
// const block = blocks[`${bx},${by}`];
|
||||
// if (lastBlock) {
|
||||
// this.emit('leaveBlock', lastBlock, e, false);
|
||||
// }
|
||||
// if (block) {
|
||||
// this.emit('hoverBlock', block, e);
|
||||
// this.mouseX = bx;
|
||||
// this.mouseY = by;
|
||||
// } else {
|
||||
// this.mouseX = -1;
|
||||
// this.mouseY = -1;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// data.addEventListener('mouseleave', e => {
|
||||
// if (
|
||||
// core.status.lockControl ||
|
||||
// !core.isPlaying() ||
|
||||
// !core.status.floorId
|
||||
// )
|
||||
// return;
|
||||
// const blocks = core.getMapBlocksObj();
|
||||
// const lastBlock = blocks[`${this.mouseX},${this.mouseY}`];
|
||||
// if (lastBlock) {
|
||||
// this.emit('leaveBlock', lastBlock, e, true);
|
||||
// }
|
||||
// this.mouseX = -1;
|
||||
// this.mouseY = -1;
|
||||
// });
|
||||
// // click
|
||||
// data.addEventListener('click', e => {
|
||||
// if (
|
||||
// core.status.lockControl ||
|
||||
// !core.isPlaying() ||
|
||||
// !core.status.floorId
|
||||
// )
|
||||
// return;
|
||||
// const {
|
||||
// x: px,
|
||||
// y: py,
|
||||
// size
|
||||
// } = core.actions._getClickLoc(e.offsetX, e.offsetY);
|
||||
// const [bx, by] = getBlockLoc(px, py, size);
|
||||
// const blocks = core.getMapBlocksObj();
|
||||
// const block = blocks[`${bx},${by}`];
|
||||
// if (block) {
|
||||
// this.emit('clickBlock', block, e);
|
||||
// }
|
||||
// });
|
||||
// ----- mouse
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,16 @@
|
||||
|
||||
export {};
|
||||
|
||||
/* @__PURE__ */ (function () {
|
||||
interface PortResponse {
|
||||
server: number;
|
||||
}
|
||||
|
||||
/* @__PURE__ */ (async function () {
|
||||
if (main.mode !== 'play' || main.replayChecking) return;
|
||||
|
||||
const res = await fetch('/getPort');
|
||||
const { server } = (await res.json()) as PortResponse;
|
||||
|
||||
/**
|
||||
* 热重载css
|
||||
* @param {string} data
|
||||
@ -127,7 +134,7 @@ export {};
|
||||
console.log(`Data hot reload: ${data}`);
|
||||
}
|
||||
|
||||
const ws = new WebSocket('ws://127.0.0.1:3000');
|
||||
const ws = new WebSocket(`ws://127.0.0.1:${server}`);
|
||||
ws.addEventListener('open', () => {
|
||||
console.log(`Web socket connect successfully`);
|
||||
});
|
||||
|
@ -34,9 +34,8 @@ export function deleteWith<T>(arr: T[], ele: T): T[] {
|
||||
|
||||
export function spliceBy<T>(arr: T[], from: T): T[] {
|
||||
const index = arr.indexOf(from);
|
||||
if (index === -1) return arr;
|
||||
arr.splice(index);
|
||||
return arr;
|
||||
if (index === -1) return [];
|
||||
return arr.splice(index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,8 +160,18 @@ export class Font implements IFontConfig {
|
||||
/**
|
||||
* 获取默认字体
|
||||
*/
|
||||
static defaults() {
|
||||
return new Font();
|
||||
static defaults(config?: Partial<IFontConfig>) {
|
||||
if (!config) {
|
||||
return new Font();
|
||||
} else {
|
||||
return new Font(
|
||||
config.family,
|
||||
config.size,
|
||||
config.sizeUnit,
|
||||
config.weight,
|
||||
config.italic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,6 +52,7 @@ export interface HotkeyData extends Required<RegisterHotkeyData> {
|
||||
type HotkeyFunc = (
|
||||
id: string,
|
||||
code: KeyCode,
|
||||
assist: number,
|
||||
ev: KeyboardEvent
|
||||
) => void | '@void';
|
||||
|
||||
@ -179,7 +180,8 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
* @param symbol 当前作用域的symbol
|
||||
*/
|
||||
use(symbol: symbol): void {
|
||||
spliceBy(this.scopeStack, symbol);
|
||||
if (symbol === this.scope) return;
|
||||
this.dispose(symbol);
|
||||
this.scopeStack.push(symbol);
|
||||
this.scope = symbol;
|
||||
this.conditionMap.set(symbol, () => true);
|
||||
@ -190,10 +192,12 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
* @param symbol 要释放的作用域的symbol
|
||||
*/
|
||||
dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()): void {
|
||||
for (const key of Object.values(this.data)) {
|
||||
key.emits.delete(symbol);
|
||||
}
|
||||
spliceBy(this.scopeStack, symbol);
|
||||
const disposed = spliceBy(this.scopeStack, symbol);
|
||||
disposed.forEach(v => {
|
||||
for (const key of Object.values(this.data)) {
|
||||
key.emits.delete(v);
|
||||
}
|
||||
});
|
||||
this.scope = this.scopeStack.at(-1) ?? Symbol();
|
||||
}
|
||||
|
||||
@ -220,6 +224,30 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
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 要触发的按键
|
||||
@ -253,12 +281,12 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
|
||||
if (!data) return;
|
||||
|
||||
if (type === 'up' && data.onUp) {
|
||||
data.onUp(v.id, key, ev);
|
||||
data.onUp(v.id, key, assist, ev);
|
||||
emitted = true;
|
||||
return;
|
||||
}
|
||||
if (!this.canEmit(v.id, key, type, data)) return;
|
||||
const res = data.func(v.id, key, ev);
|
||||
const res = data.func(v.id, key, assist, ev);
|
||||
if (res !== '@void') emitted = true;
|
||||
}
|
||||
});
|
||||
@ -450,12 +478,12 @@ document.addEventListener('keyup', e => {
|
||||
}
|
||||
} else {
|
||||
// polyfill样板
|
||||
if (main.dom.inputDiv.style.display == 'block') {
|
||||
if (e.keyCode == 13) {
|
||||
if (main.dom.inputDiv.style.display === 'block') {
|
||||
if (e.keyCode === 13) {
|
||||
setTimeout(function () {
|
||||
main.dom.inputYes.click();
|
||||
}, 50);
|
||||
} else if (e.keyCode == 27) {
|
||||
} else if (e.keyCode === 27) {
|
||||
setTimeout(function () {
|
||||
main.dom.inputNo.click();
|
||||
}, 50);
|
||||
|
@ -402,6 +402,8 @@ importers:
|
||||
|
||||
packages-user/types: {}
|
||||
|
||||
packages/animate: {}
|
||||
|
||||
packages/client:
|
||||
dependencies:
|
||||
'@motajs/client-base':
|
||||
|
@ -876,39 +876,6 @@ control.prototype.setHeroOpacity = function (
|
||||
////// 设置画布偏移
|
||||
control.prototype.setGameCanvasTranslate = function (canvas, x, y) {
|
||||
// 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)';
|
||||
}
|
||||
};
|
||||
|
||||
////// 加减画布偏移
|
||||
@ -3115,13 +3082,11 @@ control.prototype.resize = function () {
|
||||
if (!core.domStyle.availableScale.includes(core.domStyle.scale)) {
|
||||
core.domStyle.scale = 1;
|
||||
}
|
||||
core.dom.gameDraw.style.top = '0';
|
||||
} else {
|
||||
// 竖屏
|
||||
core.domStyle.isVertical = true;
|
||||
core.domStyle.scale = window.innerWidth / core._PX_;
|
||||
core.domStyle.availableScale = [];
|
||||
core.dom.gameDraw.style.top = '10vh';
|
||||
}
|
||||
|
||||
if (!core.domStyle.isVertical) {
|
||||
@ -3132,11 +3097,6 @@ control.prototype.resize = function () {
|
||||
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.setToolbarButton();
|
||||
core.updateStatusBar();
|
||||
@ -3147,54 +3107,7 @@ control.prototype._resize_gameGroup = function (obj) {
|
||||
};
|
||||
|
||||
control.prototype._resize_canvas = function (obj) {
|
||||
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';
|
||||
// Deprecated.
|
||||
};
|
||||
|
||||
control.prototype._resize_toolBar = function (obj) {
|
||||
|
@ -934,8 +934,6 @@ ui.prototype.closePanel = function () {
|
||||
ui.prototype.clearUI = function () {
|
||||
core.status.boxAnimateObjs = [];
|
||||
core.deleteCanvas('_selector');
|
||||
main.dom.next.style.display = 'none';
|
||||
main.dom.next.style.opacity = 1;
|
||||
core.clearMap('ui');
|
||||
core.setAlpha('ui', 1);
|
||||
core.setOpacity('ui', 1);
|
||||
@ -1099,7 +1097,7 @@ ui.prototype._getPosition = function (content) {
|
||||
|
||||
////// 绘制系统选择光标
|
||||
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);
|
||||
this._drawSelector(ctx, background, w, h);
|
||||
};
|
||||
@ -2056,12 +2054,10 @@ ui.prototype._animateUI = function (type, ctx, callback) {
|
||||
opacity = 1;
|
||||
}
|
||||
core.setOpacity(ctx, opacity);
|
||||
core.dom.next.style.opacity = opacity;
|
||||
core.status.event.animateUI = setInterval(function () {
|
||||
if (type == 'show') opacity += 0.05;
|
||||
else opacity -= 0.05;
|
||||
core.setOpacity(ctx, opacity);
|
||||
core.dom.next.style.opacity = opacity;
|
||||
if (opacity >= 1 || opacity <= 0) {
|
||||
clearInterval(core.status.event.animateUI);
|
||||
delete core.status.event.animateUI;
|
||||
@ -2156,13 +2152,6 @@ ui.prototype.drawTextBox = function (content, config) {
|
||||
|
||||
// Step 6: 绘制光标
|
||||
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;
|
||||
if (
|
||||
posInfo.position == 'up' &&
|
||||
@ -2171,7 +2160,6 @@ ui.prototype.drawTextBox = function (content, config) {
|
||||
Math.abs(posInfo.px * 32 + 16 - left) < 50
|
||||
)
|
||||
left = hPos.right - 64;
|
||||
main.dom.next.style.left = left * core.domStyle.scale + 'px';
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
@ -20,16 +20,12 @@ function main() {
|
||||
|
||||
this.dom = {
|
||||
body: document.body,
|
||||
game: document.getElementById('game'),
|
||||
gameDraw: document.getElementById('game-draw'),
|
||||
gameCanvas: document.getElementsByClassName('gameCanvas'),
|
||||
data: document.getElementById('data'),
|
||||
inputDiv: document.getElementById('inputDiv'),
|
||||
inputMessage: document.getElementById('inputMessage'),
|
||||
inputBox: document.getElementById('inputBox'),
|
||||
inputYes: document.getElementById('inputYes'),
|
||||
inputNo: document.getElementById('inputNo'),
|
||||
next: document.getElementById('next')
|
||||
inputNo: document.getElementById('inputNo')
|
||||
};
|
||||
this.mode = 'play';
|
||||
this.loadList = [
|
||||
@ -198,10 +194,6 @@ main.prototype.loadSync = 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;
|
||||
|
||||
// 加载全塔属性代码
|
||||
|
@ -18,7 +18,11 @@ body {
|
||||
}
|
||||
|
||||
#game-draw {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
@ -399,7 +399,7 @@ async function buildGame() {
|
||||
if (level === ClientDataLevel.Error) {
|
||||
logProgress(2, ProgressStatus.Fail);
|
||||
process.stderr.write(
|
||||
`客户端似乎引用了数据端内容,请仔细检查后再构建!`
|
||||
`数据端似乎引用了客户端内容,请仔细检查后再构建!`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@ -647,6 +647,19 @@ async function buildGame() {
|
||||
process.stdout.write(
|
||||
`⚠️ 压缩包大于 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(sourceSize)}\r\n`);
|
||||
|
@ -1,5 +1,10 @@
|
||||
/* eslint-disable no-console */
|
||||
import { createServer } from 'vite';
|
||||
import {
|
||||
createServer,
|
||||
loadConfigFromFile,
|
||||
mergeConfig,
|
||||
UserConfig
|
||||
} from 'vite';
|
||||
import { Server } from 'http';
|
||||
import { ensureDir, move, pathExists, remove } from 'fs-extra';
|
||||
import { readFile, readdir, writeFile } from 'fs/promises';
|
||||
@ -500,6 +505,14 @@ const apiGetEsmFiles = async (req: Request, res: Response) => {
|
||||
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 类型
|
||||
@ -677,12 +690,49 @@ async function ensureConfig() {
|
||||
}
|
||||
|
||||
(async function () {
|
||||
// 1. 启动vite服务
|
||||
const vite = await createServer();
|
||||
// 1. 加载 vite.config.ts
|
||||
const fsHost = `http://127.0.0.1:${serverPort}`;
|
||||
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);
|
||||
console.log(`游戏地址:http://localhost:${vitePort}/`);
|
||||
|
||||
// 2. 启动样板http服务
|
||||
// 3. 启动样板http服务
|
||||
await ensureConfig();
|
||||
|
||||
const app = express();
|
||||
@ -700,6 +750,7 @@ async function ensureConfig() {
|
||||
app.get('/all/__all_floors__.js', apiGetAllFloors);
|
||||
app.get('/all/__all_animates__', apiGetAllAnimates);
|
||||
app.get('/esm', apiGetEsmFiles);
|
||||
app.get('/getPort', apiGetPort);
|
||||
|
||||
const server = app.listen(serverPort);
|
||||
|
||||
@ -710,7 +761,7 @@ async function ensureConfig() {
|
||||
);
|
||||
});
|
||||
|
||||
// 3. 启动样板ws热重载服务
|
||||
// 4. 启动样板ws热重载服务
|
||||
startWsServer(server);
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
|
@ -5,8 +5,6 @@ import path from 'path';
|
||||
import postcssPresetEnv from 'postcss-preset-env';
|
||||
import * as glob from 'glob';
|
||||
|
||||
const FSHOST = 'http://127.0.0.1:3000/';
|
||||
|
||||
const custom = [
|
||||
'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom',
|
||||
'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon', 'winskin',
|
||||
@ -57,24 +55,6 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
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: {
|
||||
ignored: ['**/public/**']
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user