feat: 存档自适应数量

This commit is contained in:
unanmed 2025-06-24 15:53:48 +08:00
parent 98a0e13aee
commit 2d301cf0d0
6 changed files with 261 additions and 167 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

@ -74,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,29 @@ import {
SetupComponentOptions,
UIComponentProps
} from '@motajs/system-ui';
import { defineComponent, ref, computed, onMounted } 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;
}
@ -35,26 +42,45 @@ 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', 'data']
} satisfies SetupComponentOptions<SaveBtnProps>;
props: ['loc', 'index', 'selected', 'inDelete', 'data']
} satisfies SetupComponentOptions<SaveItemProps>;
export const SaveBtn = defineComponent<SaveBtnProps>(props => {
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 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 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 === null || props.data === undefined) return void 0;
if (!props.data) return [];
else {
const currData = props.data.data;
const map = core.maps.loadMap(currData.maps, currData.floorId);
@ -62,54 +88,42 @@ export const SaveBtn = defineComponent<SaveBtnProps>(props => {
return map.blocks;
}
});
const name = computed(() =>
props.index === -1 ? '自动存档' : `存档${props.index + 1}`
);
const statusText = computed(() => {
if (props.data === null || props.data === undefined) return '';
else {
const hero = props.data.data.hero;
return `${hero.hp}/${hero.atk}/${hero.def}`;
}
});
const strokeStyle = computed(() => {
if (props.isSelected) return props.isDelete ? 'red' : 'gold';
else return 'white';
});
const lineWidth = computed(() => (props.isSelected ? 2 : 1));
return () => (
<container loc={props.loc}>
<text
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={props.data === null}
loc={[3, 26, w - 6, w - 4]}
hidden={!props.data}
loc={imgLoc.value}
padStyle="gray"
floorId={props.data?.data.floorId || 'MT0'}
floorId={floorId.value}
map={mapBlocks.value}
hero={props.data?.data.hero as HeroStatus}
hero={props.data?.data.hero}
all
noHD
size={w / MAP_WIDTH}
size={(w.value - 4) / MAP_WIDTH}
noevent
/>
<text
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>
);
@ -117,62 +131,108 @@ export const SaveBtn = defineComponent<SaveBtnProps>(props => {
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 isDelete = ref(false);
/** 当前页上被选中的存档的posIndex */
const selected = ref(0);
const now = ref(0);
const inDelete = ref(false);
const pageRef = ref<PageExpose>();
/** posIndex 存档在当前页的序号 范围为0到pageCap-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;
};
/** index 存档的总序号 从0开始 用于数据交互 */
const getIndex = (posIndex: number, page: number) => {
return page * pageCap + posIndex - 1;
return index % (grid.value.count - 1);
};
/** 存档数据的列表 */
const dataList = ref<(SaveData | null)[]>(
Array(pageCap + 1).fill(null)
);
const updateDataList = (page: number) => {
dataList.value.forEach((_ele, i) => {
getSave(getIndex(i, page)).then(saveValue => {
dataList.value[i] = saveValue;
});
/**
* 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 hasData = (posIndex: number) => {
return dataList.value[posIndex] !== null;
const exist = (index: number) => {
return saveData[index] !== null;
};
const getData = (posIndex: number) => dataList.value[posIndex];
const deleteData = (posIndex: number) => {
dataList.value[posIndex] = null;
const deleteData = (index: number) => {
saveData[index] = null;
};
onMounted(() => {
const currPage = pageRef.value?.now() || 0;
updateDataList(currPage);
updateDataList(now.value);
});
/** 当前页上被选中的存档的posIndex */
const pickIndex = ref(1);
const emitSave = (index: number) => {
const posIndex = getPosIndex(index);
if (isDelete.value) {
emit('delete', index, hasData(posIndex));
if (inDelete.value) {
emit('delete', index, exist(posIndex));
deleteData(posIndex);
} else {
emit('emit', index, hasData(posIndex));
emit('emit', index, exist(posIndex));
}
if (index === -1) {
selected.value = 0;
} else {
selected.value = (index % (grid.value.count - 1)) + 1;
}
pickIndex.value = (index % pageCap) + 1;
};
const wheel = (ev: IWheelEvent) => {
@ -185,7 +245,7 @@ export const Save = defineComponent<SaveProps, SaveEmits, keyof SaveEmits>(
};
const toggleDelete = () => {
isDelete.value = !isDelete.value;
inDelete.value = !inDelete.value;
};
const exit = () => {
@ -194,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)
@ -220,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);
}
}
@ -237,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);
}
},
@ -250,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);
}
}
@ -265,99 +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]}
ref={pageRef}
loc={[0, 0, width.value, height.value - 10]}
pages={1000}
font={pageFont}
v-model:page={now.value}
onWheel={wheel}
onPageChange={updateDataList}
ref={pageRef}
font={pageFont}
>
{(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)}
cursor="pointer"
data={getData(0)}
/>
<SaveBtn
loc={[180, 50, 120, 170]}
index={page * pageCap}
isSelected={pickIndex.value === 1}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap)}
cursor="pointer"
data={getData(1)}
/>
<SaveBtn
loc={[330, 50, 120, 170]}
index={page * pageCap + 1}
isSelected={pickIndex.value === 2}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 1)}
cursor="pointer"
data={getData(2)}
/>
<SaveBtn
loc={[30, 230, 120, 170]}
index={page * pageCap + 2}
isSelected={pickIndex.value === 3}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 2)}
cursor="pointer"
data={getData(3)}
/>
<SaveBtn
loc={[180, 230, 120, 170]}
index={page * pageCap + 3}
isSelected={pickIndex.value === 4}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 3)}
cursor="pointer"
data={getData(4)}
/>
<SaveBtn
loc={[330, 230, 120, 170]}
index={page * pageCap + 4}
isSelected={pickIndex.value === 5}
isDelete={isDelete.value}
onClick={() => emitSave(page * pageCap + 4)}
cursor="pointer"
data={getData(5)}
/>
<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"
@ -387,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 {
* // 用户选择了一个存档。
@ -462,7 +493,6 @@ export async function saveSave(
};
const index = await selectSave(controller, loc, validate, props);
if (index === -2) return false;
// 由于样板存档编号从1开始这里需要+1
core.doSL(index + 1, 'save');
return true;
}

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;
}>;
};
/**
* 使