Compare commits

..

4 Commits

Author SHA1 Message Date
AncTe
45412babe0
Merge 2d301cf0d0 into 820dc5bf4c 2025-06-24 07:54:09 +00:00
2d301cf0d0 feat: 存档自适应数量 2025-06-24 15:53:48 +08:00
ShakeFlower
98a0e13aee fix:添加存档的人物属性显示 2025-06-24 11:25:10 +08:00
ShakeFlower
fa327f81b6 fix:添加删除存档功能,并修复标题界面读档爆炸的问题 2025-06-24 10:59:10 +08:00
6 changed files with 297 additions and 163 deletions

View File

@ -20,6 +20,8 @@ export interface PageProps extends DefaultProps {
pages: number;
/** 页码组件的定位 */
loc: ElementLocator;
/** 当前页码 */
page?: number;
/** 页码的字体 */
font?: Font;
/** 只有一页的时候,是否隐藏页码 */
@ -28,6 +30,8 @@ export interface PageProps extends DefaultProps {
export type PageEmits = {
pageChange: (page: number) => void;
'update:page': (page: number) => void;
};
export interface PageExpose {
@ -54,8 +58,8 @@ type PageSlots = SlotsType<{
}>;
const pageProps = {
props: ['pages', 'loc', 'font', 'hideIfSingle'],
emits: ['pageChange']
props: ['pages', 'loc', 'page', 'font', 'hideIfSingle'],
emits: ['pageChange', 'update:page']
} satisfies SetupComponentOptions<
PageProps,
PageEmits,
@ -85,7 +89,7 @@ export const Page = defineComponent<
keyof PageEmits,
PageSlots
>((props, { slots, expose, emit }) => {
const nowPage = ref(0);
const nowPage = ref(props.page ?? 0);
// 五个元素的位置
const leftLoc = ref<ElementLocator>([]);
@ -182,6 +186,7 @@ export const Page = defineComponent<
if (nowPage.value !== target) {
nowPage.value = target;
emit('pageChange', target);
emit('update:page', target);
}
};

View File

@ -43,6 +43,7 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
};
const drawThumbnail = (canvas: MotaOffscreenCanvas2D) => {
if (props.hidden) return;
const ctx = canvas.ctx;
const hero = props.hero;
const options: Partial<DrawThumbnailConfig> = {
@ -73,6 +74,6 @@ export const Thumbnail = defineComponent<ThumbnailProps>(props => {
watch(props, update);
return () => (
<sprite ref={spriteRef} loc={props.loc} render={drawThumbnail} />
<sprite noanti ref={spriteRef} loc={props.loc} render={drawThumbnail} />
);
}, thumbnailProps);

View File

@ -7,22 +7,30 @@ import {
SetupComponentOptions,
UIComponentProps
} from '@motajs/system-ui';
import { defineComponent, ref, computed, watch, nextTick } from 'vue';
import {
defineComponent,
ref,
computed,
onMounted,
shallowReactive
} from 'vue';
import { Page, PageExpose } from '../components';
import { useKey } from '../use';
import { MAP_WIDTH, MAP_HEIGHT } from '../shared';
import { MAP_WIDTH } from '../shared';
import { getSave, SaveData } from '../utils';
import { Thumbnail } from '../components/thumbnail';
import { adjustGrid, IGridLayoutData } from '../utils/layout';
export interface SaveProps extends UIComponentProps, DefaultProps {
loc: ElementLocator;
}
export interface SaveBtnProps extends DefaultProps {
export interface SaveItemProps extends DefaultProps {
loc: ElementLocator;
index: number;
isSelected: boolean;
isDelete: boolean;
selected: boolean;
inDelete: boolean;
data: SaveData | null;
}
export type SaveEmits = {
@ -34,90 +42,88 @@ export type SaveEmits = {
exit: () => void;
};
export type SaveBtnEmits = {
/** 读取数据 */
updateData: (isValid: boolean) => void;
};
const saveProps = {
props: ['loc', 'controller', 'instance'],
emits: ['delete', 'emit', 'exit']
} satisfies SetupComponentOptions<SaveProps, SaveEmits, keyof SaveEmits>;
const saveBtnProps = {
props: ['loc', 'index', 'isSelected', 'isDelete']
} satisfies SetupComponentOptions<SaveBtnProps>;
props: ['loc', 'index', 'selected', 'inDelete', 'data']
} satisfies SetupComponentOptions<SaveItemProps>;
export const SaveBtn = defineComponent<
SaveBtnProps,
SaveBtnEmits,
keyof SaveBtnEmits
>((props, { emit }) => {
const w = props.loc[2] ?? 200;
export const SaveItem = defineComponent<SaveItemProps>(props => {
const font = new Font('normal', 18);
const statusFont = new Font('normal', 14);
const data = ref<SaveData | null>(null);
const mapBlocks = computed(() => {
if (data.value === null) return void 0;
const w = computed(() => props.loc[2] ?? 200);
const lineWidth = computed(() => (props.selected ? 4 : 2));
const imgLoc = computed<ElementLocator>(() => {
const size = w.value - 4;
return [2, 24, size, size];
});
const name = computed(() => {
return props.index === -1 ? '自动存档' : `存档${props.index}`;
});
const statusText = computed(() => {
if (!props.data) return '';
else {
const currData = data.value?.data;
const hero = props.data.data.hero;
return `${hero.hp}/${hero.atk}/${hero.def}`;
}
});
const strokeStyle = computed(() => {
if (props.selected) return props.inDelete ? 'red' : 'gold';
else return 'white';
});
const floorId = computed(() => props.data?.data.floorId ?? 'empty');
const mapBlocks = computed(() => {
if (!props.data) return [];
else {
const currData = props.data.data;
const map = core.maps.loadMap(currData.maps, currData.floorId);
core.extractBlocksForUI(map, currData.hero.flags); // 这一步会向map写入blocks
return map.blocks;
}
});
const text = computed(() =>
props.index === -1 ? '自动存档' : `存档${props.index + 1}`
);
const strokeStyle = computed(() => {
if (props.isSelected) return props.isDelete ? 'red' : 'gold';
else return 'white';
});
const lineWidth = computed(() => (props.isSelected ? 2 : 1));
watch(
() => props.index,
newIndex => {
getSave(newIndex + 1).then(value => {
data.value = value;
emit('updateData', value != null);
});
},
{ immediate: true }
);
return () => (
<container loc={props.loc}>
<text
text={text.value}
text={name.value}
font={font}
loc={[w / 2, 20, void 0, void 0, 0.5, 1]}
loc={[w.value / 2, 20]}
anc={[0.5, 1]}
/>
<g-rect
loc={[lineWidth.value, 24, w - 2 * lineWidth.value, w]}
fill
stroke
loc={imgLoc.value}
strokeAndFill
fillStyle="gray"
strokeStyle={strokeStyle.value}
lineWidth={lineWidth.value}
lineJoin="miter"
cursor="pointer"
/>
<Thumbnail
hidden={data.value === null}
loc={[3, 26, w - 6, w - 4]}
hidden={!props.data}
loc={imgLoc.value}
padStyle="gray"
floorId={data.value?.data.floorId || 'MT0'}
floorId={floorId.value}
map={mapBlocks.value}
hero={data.value?.data.hero as HeroStatus}
hero={props.data?.data.hero}
all
noHD
size={w / MAP_WIDTH}
size={(w.value - 4) / MAP_WIDTH}
noevent
/>
<text
text="placeholder"
text={statusText.value}
fillStyle="yellow"
font={statusFont}
loc={[w / 2, w + 28, void 0, void 0, 0.5, 0]}
loc={[w.value / 2, w.value + 28]}
anc={[0.5, 0]}
/>
</container>
);
@ -125,34 +131,108 @@ export const SaveBtn = defineComponent<
export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
(props, { emit }) => {
const row = 2;
const column = 3;
/** 除自动存档外,每一页容纳的存档数量 */
const pageCap = row * column - 1;
const itemSize = 150;
const itemHeight = itemSize + 40;
const interval = 30;
const font = new Font('normal', 18);
const pageFont = new Font('normal', 14);
/** 各个存档是否有数据 */
const saveValidArr = Array(pageCap).fill(false);
/** 这里参数是存档在当前页的索引posIndex */
const updateValid = (index: number) => (isValid: boolean) => {
saveValidArr[index] = isValid;
};
const isDelete = ref(false);
/** 当前页上被选中的存档的posIndex */
const selected = ref(0);
const now = ref(0);
const inDelete = ref(false);
const pageRef = ref<PageExpose>();
/** 当前页上被选中的存档的序号 只会是0到5 */
const pickIndex = ref(1);
const saveData: Record<number, SaveData | null> = shallowReactive({});
const width = computed(() => props.loc[2] ?? 200);
const height = computed(() => props.loc[3] ?? 200);
const grid = computed<IGridLayoutData>(() =>
adjustGrid(
width.value,
height.value - 30,
itemSize,
itemHeight,
interval
)
);
const contentLoc = computed<ElementLocator>(() => {
const cx = width.value / 2;
const cy = (height.value - 30) / 2;
return [cx, cy, grid.value.width, grid.value.height, 0.5, 0.5];
});
const deleteLoc = computed<ElementLocator>(() => {
const pad = (width.value - grid.value.width) / 2;
return [pad, height.value - 13, void 0, void 0, 0, 1];
});
const exitLoc = computed<ElementLocator>(() => {
const pad = (width.value - grid.value.width) / 2;
const right = width.value - pad;
return [right, height.value - 13, void 0, void 0, 1, 1];
});
/**
* 0 pageCap-1
*/
const getPosIndex = (index: number) => {
if (index === -1) return 0;
return index - pageCap * (pageRef.value?.now() || 0) + 1;
return index % (grid.value.count - 1);
};
/**
* 0
*/
const getIndex = (posIndex: number, page: number) => {
return page * grid.value.count + posIndex - 1;
};
const updateDataList = async (page: number) => {
const promises: Promise<SaveData | null>[] = [];
for (let i = 0; i < grid.value.count; i++) {
const index = getIndex(i, page);
promises.push(getSave(index));
}
const data = await Promise.all(promises);
data.forEach((v, i) => {
if (v) {
saveData[i] = v;
} else {
saveData[i] = null;
}
});
};
const exist = (index: number) => {
return saveData[index] !== null;
};
const deleteData = (index: number) => {
saveData[index] = null;
};
onMounted(() => {
updateDataList(now.value);
});
const emitSave = (index: number) => {
const posIndex = getPosIndex(index);
if (isDelete.value) emit('delete', index, saveValidArr[posIndex]);
else emit('emit', index, saveValidArr[posIndex]);
pickIndex.value = (index % pageCap) + 1;
if (inDelete.value) {
emit('delete', index, exist(posIndex));
deleteData(posIndex);
} else {
emit('emit', index, exist(posIndex));
}
if (index === -1) {
selected.value = 0;
} else {
selected.value = (index % (grid.value.count - 1)) + 1;
}
};
const wheel = (ev: IWheelEvent) => {
@ -165,7 +245,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
};
const toggleDelete = () => {
isDelete.value = !isDelete.value;
inDelete.value = !inDelete.value;
};
const exit = () => {
@ -174,11 +254,14 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
};
// #region 按键实现
const [key] = useKey();
key.realize('confirm', () => {
const currPage = pageRef.value?.now();
if (currPage === void 0) return;
emitSave(pageCap * currPage + pickIndex.value);
if (selected.value === 0) {
emitSave(-1);
} else {
emitSave((grid.value.count - 1) * now.value + selected.value);
}
})
.realize('exit', exit)
.realize('@save_exit', exit)
@ -200,14 +283,16 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
'@save_up',
() => {
if (!pageRef.value) return;
const now = pageRef.value.now();
if (pickIndex.value >= row) {
pickIndex.value -= column;
const cols = grid.value.cols;
const count = grid.value.count;
if (selected.value >= cols) {
selected.value -= cols;
} else {
if (now === 0) {
pickIndex.value = 0;
if (now.value === 0) {
selected.value = 0;
} else {
pickIndex.value += pageCap + 1 - column;
const selectedCol = selected.value % cols;
selected.value = count - (cols - selectedCol);
pageRef.value?.movePage(-1);
}
}
@ -217,10 +302,13 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
.realize(
'@save_down',
() => {
if (pickIndex.value <= pageCap - row) {
pickIndex.value += column;
const cols = grid.value.cols;
const count = grid.value.count;
if (selected.value < count - cols) {
selected.value += cols;
} else {
pickIndex.value += column - pageCap - 1;
const selectedCol = selected.value % cols;
selected.value = selectedCol;
pageRef.value?.movePage(1);
}
},
@ -230,12 +318,12 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
'@save_left',
() => {
if (!pageRef.value) return;
const now = pageRef.value.now();
if (pickIndex.value > 0) {
pickIndex.value--;
const count = grid.value.count;
if (selected.value > 0) {
selected.value--;
} else {
if (now > 0) {
pickIndex.value = pageCap;
if (now.value > 0) {
selected.value = count;
pageRef.value?.movePage(-1);
}
}
@ -245,98 +333,62 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
.realize(
'@save_right',
() => {
if (pickIndex.value < pageCap) {
pickIndex.value++;
const count = grid.value.count;
if (selected.value < count) {
selected.value++;
} else {
pickIndex.value = 0;
selected.value = 0;
pageRef.value?.movePage(1);
}
},
{ type: 'down-repeat' }
);
// #endregion
return () => (
<container loc={props.loc} zIndex={10}>
<Page
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT - 10]}
pages={1000}
onWheel={wheel}
ref={pageRef}
loc={[0, 0, width.value, height.value - 10]}
pages={1000}
font={pageFont}
v-model:page={now.value}
onWheel={wheel}
onPageChange={updateDataList}
>
{(page: number) => (
<container loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}>
<SaveBtn
loc={[30, 50, 120, 170]}
index={-1}
isSelected={pickIndex.value === 0}
isDelete={isDelete.value}
onClick={() => emitSave(-1)}
onUpdateData={updateValid(0)}
cursor="pointer"
/>
<SaveBtn
loc={[180, 50, 120, 170]}
index={page * pageCap}
isSelected={pickIndex.value === 1}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap)}
onUpdateData={updateValid(1)}
cursor="pointer"
/>
<SaveBtn
loc={[330, 50, 120, 170]}
index={page * pageCap + 1}
isSelected={pickIndex.value === 2}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 1)}
onUpdateData={updateValid(2)}
cursor="pointer"
/>
<SaveBtn
loc={[30, 230, 120, 170]}
index={page * pageCap + 2}
isSelected={pickIndex.value === 3}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 2)}
onUpdateData={updateValid(3)}
cursor="pointer"
/>
<SaveBtn
loc={[180, 230, 120, 170]}
index={page * pageCap + 3}
isSelected={pickIndex.value === 4}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 3)}
onUpdateData={updateValid(4)}
cursor="pointer"
/>
<SaveBtn
loc={[330, 230, 120, 170]}
index={page * pageCap + 4}
isSelected={pickIndex.value === 5}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 4)}
onUpdateData={updateValid(5)}
cursor="pointer"
<container loc={contentLoc.value}>
{grid.value.locs.map((v, i) => {
const count = grid.value.count;
const rawIndex = (count - 1) * page + i;
const index = i === 0 ? -1 : rawIndex;
return (
<SaveItem
loc={v}
index={index}
selected={selected.value === i}
inDelete={inDelete.value}
data={saveData[i]}
onClick={() => emitSave(index)}
onEnter={() => (selected.value = i)}
/>
);
})}
</container>
)}
</Page>
<text
text="删除模式"
loc={deleteLoc.value}
font={font}
loc={[30, 450, void 0, void 0, 0, 0]}
zIndex={10}
fillStyle={isDelete.value ? 'red' : 'white'}
fillStyle={inDelete.value ? 'red' : 'white'}
onClick={toggleDelete}
cursor="pointer"
/>
<text
text="返回游戏"
loc={exitLoc.value}
font={font}
loc={[450, 450, void 0, void 0, 1, 0]}
zIndex={10}
onClick={exit}
cursor="pointer"
@ -366,9 +418,9 @@ export type SaveValidationFunction = (
* 使
* ```ts
* const index = await selectSave(props.controller, [0, 0, 416, 416]);
* if (index === -2) {
* if (index === -1) {
* // 如果用户未选择存档,而是关闭了存档。
* } else if (index === -1) {
* } else if (index === 0) {
* // 用户选择了自动存档。
* } else {
* // 用户选择了一个存档。
@ -385,6 +437,14 @@ export function selectSave(
validate?: SaveValidationFunction,
props?: SaveProps
) {
const validateDelete = (index: number, exist: boolean): SaveValidation => {
if (index === -1) {
return { message: '不能删除自动存档!', valid: false };
} else {
return { message: '无法删除该存档!', valid: exist };
}
};
return new Promise<number>(res => {
const instance = controller.open(SaveUI, {
loc,
@ -403,6 +463,15 @@ export function selectSave(
core.drawTip(validation.message);
}
},
onDelete: (index: number, exist: boolean) => {
if (!validate) return;
const validation = validateDelete(index, exist);
if (validation.valid) {
core.removeSave(index);
} else {
core.drawTip(validation.message);
}
},
onExit: () => {
res(-2);
}

View File

@ -0,0 +1,59 @@
import { ElementLocator } from '@motajs/render-core';
export interface IGridLayoutData {
/** 有多少列 */
readonly cols: number;
/** 有多少行 */
readonly rows: number;
/** 去除余留部分后的宽度 */
readonly width: number;
/** 去除预留部分后的高度 */
readonly height: number;
/** 元素总数量 */
readonly count: number;
/** 每个元素的定位,按照从左到右,从左上下的顺序排列,包含所有六个元素 */
readonly locs: readonly ElementLocator[];
}
/**
*
* @param width
* @param height
* @param itemWidth
* @param itemHeight
* @param intervalX
* @param intervalY
* @returns
*/
export function adjustGrid(
width: number,
height: number,
itemWidth: number,
itemHeight: number,
intervalX: number,
intervalY: number = intervalX
): IGridLayoutData {
const cols = Math.floor((width + intervalX) / (itemWidth + intervalX));
const rows = Math.floor((height + intervalY) / (itemHeight + intervalY));
const rawWidth = (itemWidth + intervalX) * cols - intervalX;
const rawHeight = (itemHeight + intervalY) * rows - intervalY;
const locs: ElementLocator[] = [];
for (let y = 0; y < rows; y++) {
const iy = (intervalY + itemHeight) * y;
for (let x = 0; x < cols; x++) {
const ix = (intervalX + itemWidth) * x;
locs.push([ix, iy, itemWidth, itemHeight, 0, 0]);
}
}
return {
cols,
rows,
count: cols * rows,
width: rawWidth,
height: rawHeight,
locs
};
}

View File

@ -25,7 +25,7 @@ export function getSave(index: number) {
const content = {
name: core.firstData.name,
version: core.firstData.version,
data: data
data: data instanceof Array ? data[0] : data
};
res(content);
});

View File

@ -821,7 +821,7 @@ interface Animate {
pitch: any;
}
type Save = DeepReadonly<{
type Save = {
/**
* id
*/
@ -866,7 +866,7 @@ type Save = DeepReadonly<{
*
*/
time: number;
}>;
};
/**
* 使