mirror of
https://github.com/motajs/template.git
synced 2026-11-09 19:54:30 +08:00
feat(09-01): migrate statistics.tsx and viewmap.tsx to src/ui (D-10)
- Move render/ui/statistics.tsx -> src/ui/statistics.tsx (verbatim copy) - Move render/ui/viewmap.tsx -> src/ui/viewmap.tsx; rewrite ../use -> ../render/use, ../../shared -> ../shared; keep ./statistics - Add export * from './statistics' and './viewmap' to src/ui/index.ts - Delete old render/ui/statistics.tsx and render/ui/viewmap.tsx
This commit is contained in:
1 parent
3f5c323897
commit
24ee08cc2d
3 files changed
+4
-2
No files matched your search
@@ -1,2 +1,4 @@
|
||||
export * from './types';
|
||||
export * from './save';
|
||||
export * from './statistics';
|
||||
export * from './viewmap';
|
||||
@@ -0,0 +1,302 @@
|
||||
import {
|
||||
GameUI,
|
||||
IUIController,
|
||||
SetupComponentOptions,
|
||||
IUIDefaultPropsBase
|
||||
} from '@motajs/system';
|
||||
import { defineComponent } from 'vue';
|
||||
import { waitbox, ListPage, TextContent } from '@user/client-base';
|
||||
import { DefaultProps } from '@motajs/render-vue';
|
||||
// @ts-expect-error 需要重构
|
||||
import { ItemState } from '@user/data-state';
|
||||
|
||||
// 未完工
|
||||
|
||||
export interface StatisticsDataOneFloor {
|
||||
enemyCount: number;
|
||||
potionCount: number;
|
||||
gemCount: number;
|
||||
potionValue: number;
|
||||
atkValue: number;
|
||||
defValue: number;
|
||||
mdefValue: number;
|
||||
atkGemCount: number;
|
||||
defGemCount: number;
|
||||
mdefGemCount: number;
|
||||
}
|
||||
|
||||
export interface StatisticsDataPotionGem {
|
||||
atkGemCount: number;
|
||||
defGemCount: number;
|
||||
mdefGemCount: number;
|
||||
potionCount: number;
|
||||
}
|
||||
|
||||
export interface StatisticsData {
|
||||
total: StatisticsDataOneFloor;
|
||||
floors: Map<FloorIds, StatisticsDataOneFloor>;
|
||||
potionGem: StatisticsDataPotionGem;
|
||||
}
|
||||
|
||||
export interface StatisticsProps extends IUIDefaultPropsBase, DefaultProps {
|
||||
data: StatisticsData;
|
||||
}
|
||||
|
||||
const statisticsProps = {
|
||||
props: ['data', 'controller', 'instance']
|
||||
} satisfies SetupComponentOptions<StatisticsProps>;
|
||||
|
||||
export const Statistics = defineComponent<StatisticsProps>(props => {
|
||||
const list: [string, string][] = [
|
||||
['total', '总览'],
|
||||
['floor', '楼层'],
|
||||
['enemy', '怪物'],
|
||||
['potion', '血瓶宝石']
|
||||
];
|
||||
|
||||
const close = () => {
|
||||
props.controller.close(props.instance);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<ListPage
|
||||
list={list}
|
||||
selected="total"
|
||||
loc={[180, 0, 480, 480]}
|
||||
height={470}
|
||||
close
|
||||
onClose={close}
|
||||
lineHeight={24}
|
||||
closeLoc={[10, 470, void 0, void 0, 0, 1]}
|
||||
>
|
||||
{{
|
||||
total: () => <TotalStatistics data={props.data} />,
|
||||
floor: () => <FloorStatistics data={props.data} />,
|
||||
enemy: () => <EnemyStatistics data={props.data} />,
|
||||
potion: () => <PotionStatistics data={props.data} />
|
||||
}}
|
||||
</ListPage>
|
||||
);
|
||||
}, statisticsProps);
|
||||
|
||||
interface StatisticsPanelProps extends DefaultProps {
|
||||
data: StatisticsData;
|
||||
}
|
||||
|
||||
const statisticsPanelProps = {
|
||||
props: ['data']
|
||||
} satisfies SetupComponentOptions<StatisticsPanelProps>;
|
||||
|
||||
const TotalStatistics = defineComponent<StatisticsPanelProps>(props => {
|
||||
return () => {
|
||||
const total = props.data.total;
|
||||
|
||||
const text1 = `全塔地图中,共有怪物${total.enemyCount}个。`;
|
||||
const text2 = `共有宝石${total.gemCount}个,共加攻击力${total.atkValue}点、防御力${total.defValue}点,魔防${total.mdefValue}点。`;
|
||||
const text3 = `共有血瓶${total.potionCount}个,共加生命值${total.potionValue}点。`;
|
||||
|
||||
return (
|
||||
<TextContent
|
||||
text={`${text1}${text2}${text3}`}
|
||||
width={330}
|
||||
loc={[0, 5, 330, 470]}
|
||||
height={470}
|
||||
lineHeight={9}
|
||||
></TextContent>
|
||||
);
|
||||
};
|
||||
}, statisticsPanelProps);
|
||||
|
||||
const FloorStatistics = defineComponent<StatisticsPanelProps>(props => {
|
||||
return () => <container></container>;
|
||||
}, statisticsPanelProps);
|
||||
|
||||
const EnemyStatistics = defineComponent<StatisticsPanelProps>(props => {
|
||||
return () => <container></container>;
|
||||
}, statisticsPanelProps);
|
||||
|
||||
const PotionStatistics = defineComponent<StatisticsPanelProps>(props => {
|
||||
return () => {
|
||||
const gemPotion = props.data.potionGem;
|
||||
|
||||
const text1 = `全塔地图中,共有红宝石${gemPotion.atkGemCount}个,共有蓝宝石${gemPotion.defGemCount}个,共有缘宝石${gemPotion.mdefGemCount}个。`;
|
||||
const text2 = `共有血瓶${gemPotion.potionCount}个。`;
|
||||
return (
|
||||
<TextContent
|
||||
text={`${text1}${text2}`}
|
||||
width={330}
|
||||
loc={[0, 5, 330, 470]}
|
||||
height={470}
|
||||
lineHeight={9}
|
||||
></TextContent>
|
||||
);
|
||||
};
|
||||
}, statisticsPanelProps);
|
||||
|
||||
export function calculateStatisticsOne(
|
||||
floorId: FloorIds,
|
||||
diff?: Map<string, number>
|
||||
) {
|
||||
core.setFlag('__statistics__', true);
|
||||
const hasDiff = !!diff;
|
||||
if (!hasDiff) {
|
||||
diff = new Map();
|
||||
const hero = core.status.hero;
|
||||
const handler: ProxyHandler<HeroStatus> = {
|
||||
set(target, p, newValue) {
|
||||
if (typeof p !== 'string') return true;
|
||||
if (typeof newValue === 'number') {
|
||||
const value = diff!.get(p) ?? 0;
|
||||
const delta =
|
||||
newValue - (target[p as keyof HeroStatus] as number);
|
||||
diff!.set(p, value + delta);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const proxy = new Proxy(hero, handler);
|
||||
core.status.hero = proxy;
|
||||
}
|
||||
core.extractBlocks(floorId);
|
||||
const statistics: StatisticsDataOneFloor = {
|
||||
enemyCount: 0,
|
||||
potionCount: 0,
|
||||
gemCount: 0,
|
||||
potionValue: 0,
|
||||
atkValue: 0,
|
||||
defValue: 0,
|
||||
mdefValue: 0,
|
||||
atkGemCount: 0,
|
||||
defGemCount: 0,
|
||||
mdefGemCount: 0
|
||||
};
|
||||
if (!diff) return statistics;
|
||||
core.status.maps[floorId].blocks.forEach(v => {
|
||||
if (v.event.cls === 'enemys' || v.event.cls === 'enemy48') {
|
||||
statistics.enemyCount++;
|
||||
} else if (v.event.cls === 'items') {
|
||||
const item = ItemState.items.get(v.event.id as AllIdsOf<'items'>);
|
||||
if (!item) return;
|
||||
if (item.cls === 'items') {
|
||||
try {
|
||||
item.itemEffectFn?.();
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
const hp = diff.get('hp') ?? 0;
|
||||
const atk = diff.get('atk') ?? 0;
|
||||
const def = diff.get('def') ?? 0;
|
||||
const mdef = diff.get('mdef') ?? 0;
|
||||
if (hp > 0) {
|
||||
statistics.potionCount++;
|
||||
statistics.potionValue += hp;
|
||||
}
|
||||
if (atk > 0 || def > 0 || mdef > 0) {
|
||||
statistics.gemCount++;
|
||||
}
|
||||
if (atk > 0) {
|
||||
statistics.atkValue += atk;
|
||||
statistics.atkGemCount++;
|
||||
}
|
||||
if (def > 0) {
|
||||
statistics.defValue += def;
|
||||
statistics.defGemCount++;
|
||||
}
|
||||
if (mdef > 0) {
|
||||
statistics.mdefValue += mdef;
|
||||
statistics.mdefGemCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
diff.clear();
|
||||
});
|
||||
|
||||
if (!hasDiff) {
|
||||
core.status.hero = hero;
|
||||
window.hero = hero;
|
||||
window.flags = core.status.hero.flags;
|
||||
}
|
||||
core.removeFlag('__statistics__');
|
||||
return statistics;
|
||||
}
|
||||
|
||||
export function calculateStatistics(): StatisticsData {
|
||||
core.setFlag('__statistics__', true);
|
||||
const hero = core.status.hero;
|
||||
const diff = new Map<string, number>();
|
||||
const handler: ProxyHandler<HeroStatus> = {
|
||||
set(target, p, newValue) {
|
||||
if (typeof p !== 'string') return true;
|
||||
if (typeof newValue === 'number') {
|
||||
const value = diff!.get(p) ?? 0;
|
||||
const delta =
|
||||
newValue - (target[p as keyof HeroStatus] as number);
|
||||
diff!.set(p, value + delta);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const proxy = new Proxy(hero, handler);
|
||||
core.status.hero = proxy;
|
||||
|
||||
const floors = new Map<FloorIds, StatisticsDataOneFloor>();
|
||||
core.floorIds.forEach(v => {
|
||||
const statistics = calculateStatisticsOne(v, diff);
|
||||
floors.set(v, statistics);
|
||||
});
|
||||
|
||||
core.status.hero = hero;
|
||||
window.hero = hero;
|
||||
window.flags = core.status.hero.flags;
|
||||
core.removeFlag('__statistics__');
|
||||
|
||||
const total = floors.values().reduce((prev, curr) => {
|
||||
prev.atkValue += curr.atkValue;
|
||||
prev.defValue += curr.defValue;
|
||||
prev.enemyCount += curr.enemyCount;
|
||||
prev.gemCount += curr.gemCount;
|
||||
prev.mdefValue += curr.mdefValue;
|
||||
prev.potionCount += curr.potionCount;
|
||||
prev.potionValue += curr.potionValue;
|
||||
return prev;
|
||||
});
|
||||
|
||||
const potionGem = floors.values().reduce((prev, curr) => {
|
||||
prev.atkGemCount += curr.atkGemCount;
|
||||
prev.defGemCount += curr.defGemCount;
|
||||
prev.mdefGemCount += curr.mdefGemCount;
|
||||
prev.potionCount += curr.potionCount;
|
||||
return prev;
|
||||
});
|
||||
|
||||
return {
|
||||
total,
|
||||
floors,
|
||||
potionGem
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开数据统计界面
|
||||
* @param controller 要在哪个 UI 控制器上打开
|
||||
*/
|
||||
export async function openStatistics(controller: IUIController) {
|
||||
const cal = Promise.resolve().then<StatisticsData>(() => {
|
||||
return new Promise(res => {
|
||||
const data = calculateStatistics();
|
||||
res(data);
|
||||
});
|
||||
});
|
||||
const data = await waitbox(
|
||||
controller,
|
||||
[240 + 180, 240, void 0, void 0, 0.5, 0.5],
|
||||
240,
|
||||
cal,
|
||||
{
|
||||
text: '正在统计...'
|
||||
}
|
||||
);
|
||||
controller.open(StatisticsUI, { data: data });
|
||||
}
|
||||
|
||||
export const StatisticsUI = new GameUI('statistics', Statistics);
|
||||
@@ -0,0 +1,568 @@
|
||||
import {
|
||||
ElementLocator,
|
||||
IActionEvent,
|
||||
IActionEventBase,
|
||||
IWheelEvent,
|
||||
MotaOffscreenCanvas2D,
|
||||
Font
|
||||
} from '@motajs/render';
|
||||
import { BaseProps } from '@motajs/render-vue';
|
||||
import {
|
||||
GameUI,
|
||||
IUIController,
|
||||
SetupComponentOptions,
|
||||
IUIDefaultPropsBase
|
||||
} from '@motajs/system';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
markRaw,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch
|
||||
} from 'vue';
|
||||
import { FloorSelector } from '@user/client-base';
|
||||
import { clamp, mean } from 'lodash-es';
|
||||
import { StatisticsDataOneFloor } from './statistics';
|
||||
import { Tip, TipExpose } from '@user/client-base';
|
||||
import { useKey } from '../render/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 IUIDefaultPropsBase, BaseProps {
|
||||
loc: ElementLocator;
|
||||
floorId?: FloorIds;
|
||||
}
|
||||
|
||||
const viewMapProps = {
|
||||
props: ['loc', 'floorId', 'controller', 'instance']
|
||||
} satisfies SetupComponentOptions<ViewMapProps>;
|
||||
|
||||
export const ViewMap = defineComponent<ViewMapProps>(props => {
|
||||
const nowFloorId = core.status.floorId;
|
||||
|
||||
// const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
// new FloorDamageExtends(),
|
||||
// new FloorItemDetail(),
|
||||
// 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(
|
||||
core.floorIds.filter(v => {
|
||||
return (
|
||||
!core.floors[v].cannotViewMap &&
|
||||
!core.status?.hero?.flags?.__removed__?.includes(v)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// const group = ref<LayerGroup>();
|
||||
const tip = ref<TipExpose>();
|
||||
const statistics = shallowRef<StatisticsDataOneFloor>();
|
||||
|
||||
const now = ref(0);
|
||||
if (props.floorId) {
|
||||
const index = viewableFloor.indexOf(props.floorId);
|
||||
if (index !== -1) now.value = index;
|
||||
}
|
||||
|
||||
const floorId = computed(() => viewableFloor[now.value]);
|
||||
|
||||
//#region 按键实现
|
||||
|
||||
const [key] = useKey();
|
||||
key.realize('@viewMap_up', () => changeFloor(1), { type: 'down-repeat' })
|
||||
.realize('@viewMap_down', () => changeFloor(-1), {
|
||||
type: 'down-repeat'
|
||||
})
|
||||
.realize('@viewMap_up_ten', () => changeFloor(10))
|
||||
.realize('@viewMap_down_ten', () => changeFloor(-10))
|
||||
.realize('@viewMap_book', () => openBook())
|
||||
.realize('@viewMap_fly', () => fly())
|
||||
// .realize('@viewMap_reset', () => resetCamera())
|
||||
.realize('confirm', () => close())
|
||||
.realize('exit', (_, code, assist) => {
|
||||
// 如果按键不能触发怪物手册,则关闭界面,因为怪物手册和退出默认使用同一个按键,需要特判
|
||||
if (!key.willEmit(code, assist, 'book')) {
|
||||
props.controller.close(props.instance);
|
||||
}
|
||||
});
|
||||
|
||||
//#region 功能函数
|
||||
|
||||
const close = () => {
|
||||
props.controller.close(props.instance);
|
||||
};
|
||||
|
||||
const format = (num?: number) => {
|
||||
return core.formatBigNumber(num ?? 0, 6);
|
||||
};
|
||||
|
||||
const changeTo = (index: number) => {
|
||||
const res = clamp(index, 0, viewableFloor.length - 1);
|
||||
now.value = res;
|
||||
};
|
||||
|
||||
const changeFloor = (delta: number) => {
|
||||
changeTo(now.value + delta);
|
||||
};
|
||||
|
||||
const openBook = () => core.openBook(true);
|
||||
|
||||
const fly = () => {
|
||||
if (!core.hasItem('fly')) return;
|
||||
const id = viewableFloor[now.value];
|
||||
const success = core.flyTo(id);
|
||||
if (success) close();
|
||||
else tip.value?.drawTip(`无法飞往${core.floors[id].title}`);
|
||||
};
|
||||
|
||||
// const resetCamera = () => {
|
||||
// group.value?.camera.reset();
|
||||
// group.value?.update();
|
||||
// };
|
||||
|
||||
//#region 渐变渲染
|
||||
|
||||
const topAlpha = ref(0.7);
|
||||
const bottomAlpha = ref(0.7);
|
||||
|
||||
let topGradient: CanvasGradient | null = null;
|
||||
let bottomGradient: CanvasGradient | null = null;
|
||||
|
||||
const getTopGradient = (ctx: CanvasRenderingContext2D) => {
|
||||
if (topGradient) return topGradient;
|
||||
topGradient = ctx.createLinearGradient(0, 0, 0, 64);
|
||||
topGradient.addColorStop(0, 'rgba(0,0,0,1)');
|
||||
topGradient.addColorStop(0.75, 'rgba(0,0,0,0.5)');
|
||||
topGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
return topGradient;
|
||||
};
|
||||
|
||||
const getBottomGradient = (ctx: CanvasRenderingContext2D) => {
|
||||
if (bottomGradient) return bottomGradient;
|
||||
bottomGradient = ctx.createLinearGradient(0, 64, 0, 0);
|
||||
bottomGradient.addColorStop(0, 'rgba(0,0,0,1)');
|
||||
bottomGradient.addColorStop(0.75, 'rgba(0,0,0,0.5)');
|
||||
bottomGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
return bottomGradient;
|
||||
};
|
||||
|
||||
const renderTop = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
ctx.fillStyle = getTopGradient(ctx);
|
||||
ctx.fillRect(0, 0, MAP_WIDTH, 64);
|
||||
};
|
||||
|
||||
const renderBottom = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
ctx.fillStyle = getBottomGradient(ctx);
|
||||
ctx.fillRect(0, 0, MAP_HEIGHT, 64);
|
||||
};
|
||||
|
||||
const enterTop = () => (topAlpha.value = 0.9);
|
||||
const enterBottom = () => (bottomAlpha.value = 0.9);
|
||||
const leaveTop = () => (topAlpha.value = 0.7);
|
||||
const leaveBottom = () => (bottomAlpha.value = 0.7);
|
||||
|
||||
//#region 地图渲染
|
||||
|
||||
// const renderLayer = (floorId: FloorIds) => {
|
||||
// const binder = group.value?.getExtends(
|
||||
// 'floor-binder'
|
||||
// ) as LayerGroupFloorBinder;
|
||||
// binder.bindFloor(floorId);
|
||||
// group.value?.camera.reset();
|
||||
// core.status.floorId = floorId;
|
||||
// core.status.thisMap = core.status.maps[floorId];
|
||||
// statistics.value = calculateStatisticsOne(floorId);
|
||||
// };
|
||||
|
||||
// const moveCamera = (dx: number, dy: number) => {
|
||||
// const camera = group.value?.camera;
|
||||
// if (!camera) return;
|
||||
// camera.translate(dx / camera.scaleX, dy / camera.scaleX);
|
||||
// group.value?.update();
|
||||
// };
|
||||
|
||||
// const scaleCamera = (scale: number, x: number, y: number) => {
|
||||
// const camera = group.value?.camera;
|
||||
// if (!camera) return;
|
||||
// const [cx, cy] = camera.untransformed(x, y);
|
||||
// camera.translate(cx, cy);
|
||||
// camera.scale(scale);
|
||||
// camera.translate(-cx, -cy);
|
||||
// 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;
|
||||
let moved = false;
|
||||
let scaled = false;
|
||||
let lastMoveX = 0;
|
||||
let lastMoveY = 0;
|
||||
let lastDis = 0;
|
||||
let movement = 0;
|
||||
|
||||
const touches = new Map<number, IActionEvent>();
|
||||
|
||||
const downMap = (ev: IActionEvent) => {
|
||||
moved = false;
|
||||
lastMoveX = ev.offsetX;
|
||||
lastMoveY = ev.offsetY;
|
||||
movement = 0;
|
||||
|
||||
if (ev.touch) {
|
||||
touches.set(ev.identifier, ev);
|
||||
if (touches.size >= 2) {
|
||||
const [touch1, touch2] = touches.values();
|
||||
lastDis = Math.hypot(
|
||||
touch1.offsetX - touch2.offsetX,
|
||||
touch1.offsetY - touch2.offsetY
|
||||
);
|
||||
}
|
||||
} else {
|
||||
mouseDown = true;
|
||||
}
|
||||
};
|
||||
|
||||
const upMap = (ev: IActionEvent) => {
|
||||
if (ev.touch) {
|
||||
touches.delete(ev.identifier);
|
||||
} else {
|
||||
mouseDown = false;
|
||||
}
|
||||
if (touches.size === 0) {
|
||||
scaled = false;
|
||||
}
|
||||
};
|
||||
|
||||
const move = (ev: IActionEvent) => {
|
||||
if (moved) {
|
||||
const dx = ev.offsetX - lastMoveX;
|
||||
const dy = ev.offsetY - lastMoveY;
|
||||
movement += Math.hypot(dx, dy);
|
||||
// moveCamera(dx, dy);
|
||||
}
|
||||
moved = true;
|
||||
lastMoveX = ev.offsetX;
|
||||
lastMoveY = ev.offsetY;
|
||||
};
|
||||
|
||||
const moveMap = (ev: IActionEvent) => {
|
||||
if (ev.touch) {
|
||||
if (touches.size === 0) return;
|
||||
else if (touches.size === 1) {
|
||||
// 移动
|
||||
if (scaled) return;
|
||||
move(ev);
|
||||
} else {
|
||||
// 缩放
|
||||
const [touch1, touch2] = touches.values();
|
||||
const cx = mean([touch1.offsetX, touch2.offsetX]);
|
||||
const cy = mean([touch1.offsetY, touch2.offsetY]);
|
||||
const dis = Math.hypot(
|
||||
touch1.offsetX - touch2.offsetX,
|
||||
touch1.offsetY - touch2.offsetY
|
||||
);
|
||||
const scale = dis / lastDis;
|
||||
if (!scaled) {
|
||||
lastDis = dis;
|
||||
return;
|
||||
}
|
||||
if (!isFinite(scale) || scale === 0) return;
|
||||
// scaleCamera(scale, cx, cy);
|
||||
}
|
||||
} else {
|
||||
if (mouseDown) {
|
||||
move(ev);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const leaveMap = (ev: IActionEventBase) => {
|
||||
if (ev.touch) {
|
||||
touches.delete(ev.identifier);
|
||||
} else {
|
||||
mouseDown = false;
|
||||
}
|
||||
};
|
||||
|
||||
const wheelMap = (ev: IWheelEvent) => {
|
||||
if (ev.altKey) {
|
||||
// const scale = ev.wheelY < 0 ? 1.1 : 0.9;
|
||||
// scaleCamera(scale, ev.offsetX, ev.offsetY);
|
||||
} else if (ev.ctrlKey) {
|
||||
changeFloor(-Math.sign(ev.wheelY) * 10);
|
||||
} else {
|
||||
changeFloor(-Math.sign(ev.wheelY));
|
||||
}
|
||||
};
|
||||
|
||||
const clickMap = (ev: IActionEvent) => {
|
||||
if (movement > 5) return;
|
||||
if (ev.touch) {
|
||||
if (touches.size === 0) {
|
||||
close();
|
||||
}
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// renderLayer(floorId.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
core.status.floorId = nowFloorId;
|
||||
core.status.thisMap = core.status.maps[nowFloorId];
|
||||
});
|
||||
|
||||
watch(floorId, value => {
|
||||
// renderLayer(value);
|
||||
});
|
||||
|
||||
//#region 组件树
|
||||
|
||||
return () => (
|
||||
<container loc={props.loc} nocache>
|
||||
<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, STATUS_BAR_WIDTH, MAIN_HEIGHT]}
|
||||
floors={viewableFloor}
|
||||
v-model:now={now.value}
|
||||
onClose={close}
|
||||
/>
|
||||
{/* <layer-group
|
||||
ref={group}
|
||||
ex={layerGroupExtends}
|
||||
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
onDown={downMap}
|
||||
onMove={moveMap}
|
||||
onUp={upMap}
|
||||
onLeave={leaveMap}
|
||||
onWheel={wheelMap}
|
||||
onClick={clickMap}
|
||||
>
|
||||
<layer layer="bg" zIndex={10}></layer>
|
||||
<layer layer="bg2" zIndex={20}></layer>
|
||||
<layer layer="event" zIndex={30}></layer>
|
||||
<layer layer="fg" zIndex={40}></layer>
|
||||
<layer layer="fg2" zIndex={50}></layer>
|
||||
</layer-group> */}
|
||||
<Tip
|
||||
ref={tip}
|
||||
zIndex={40}
|
||||
loc={[STATUS_BAR_WIDTH + 8, 8, 200, 32]}
|
||||
pad={[12, 6]}
|
||||
corner={16}
|
||||
/>
|
||||
<custom
|
||||
loc={[STATUS_BAR_WIDTH, 0, MAP_WIDTH, 64]}
|
||||
render={renderTop}
|
||||
alpha={topAlpha.value}
|
||||
zIndex={10}
|
||||
cursor="pointer"
|
||||
onEnter={enterTop}
|
||||
onLeave={leaveTop}
|
||||
onClick={clickTop}
|
||||
/>
|
||||
<custom
|
||||
loc={[STATUS_BAR_WIDTH, MAP_HEIGHT - 64, MAP_WIDTH, 64]}
|
||||
render={renderBottom}
|
||||
alpha={bottomAlpha.value}
|
||||
zIndex={10}
|
||||
cursor="pointer"
|
||||
onEnter={enterBottom}
|
||||
onLeave={leaveBottom}
|
||||
onClick={clickBottom}
|
||||
/>
|
||||
<text
|
||||
text="上移地图"
|
||||
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
zIndex={20}
|
||||
noevent
|
||||
/>
|
||||
<text
|
||||
text="下移地图"
|
||||
loc={[HALF_MAP_WIDTH + STATUS_BAR_WIDTH, MAP_HEIGHT - 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
zIndex={20}
|
||||
noevent
|
||||
/>
|
||||
<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}
|
||||
loc={[90, 24]}
|
||||
anc={[0.5, 0.5]}
|
||||
fillStyle="yellow"
|
||||
/>
|
||||
<text
|
||||
text="Alt+滚轮 / 双指缩放地图"
|
||||
font={rightFont}
|
||||
loc={[90, 48]}
|
||||
anc={[0.5, 0.5]}
|
||||
fillStyle="yellow"
|
||||
/>
|
||||
<text
|
||||
text="Ctrl+滚轮 / 滚轮切换地图"
|
||||
font={rightFont}
|
||||
loc={[90, 72]}
|
||||
anc={[0.5, 0.5]}
|
||||
fillStyle="yellow"
|
||||
/>
|
||||
<g-line line={[12, 96, 168, 96]} lineWidth={1} />
|
||||
<text
|
||||
text={`怪物数量:${statistics.value?.enemyCount}`}
|
||||
loc={[20, 120]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`血瓶数量:${statistics.value?.potionCount}`}
|
||||
loc={[20, 144]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`宝石数量:${statistics.value?.gemCount}`}
|
||||
loc={[20, 168]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`血瓶数值:${format(statistics.value?.potionValue)}`}
|
||||
loc={[20, 192]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`攻击数值:${format(statistics.value?.atkValue)}`}
|
||||
loc={[20, 216]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`防御数值:${format(statistics.value?.defValue)}`}
|
||||
loc={[20, 240]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<text
|
||||
text={`智慧数值:${format(statistics.value?.mdefValue)}`}
|
||||
loc={[20, 264]}
|
||||
anc={[0, 0.5]}
|
||||
/>
|
||||
<g-line line={[12, 292, 168, 292]} lineWidth={1} />
|
||||
<text
|
||||
text="「 怪物手册 」"
|
||||
loc={loc1}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={openBook}
|
||||
/>
|
||||
<text
|
||||
text="「 传送至此 」"
|
||||
loc={loc2}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
onClick={fly}
|
||||
/>
|
||||
<text
|
||||
text="「 重置视角 」"
|
||||
loc={loc3}
|
||||
anc={[0.5, 0.5]}
|
||||
cursor="pointer"
|
||||
// onClick={resetCamera}
|
||||
/>
|
||||
</container>
|
||||
</container>
|
||||
);
|
||||
}, viewMapProps);
|
||||
|
||||
export const ViewMapUI = new GameUI('view-map', ViewMap);
|
||||
|
||||
export function openViewMap(
|
||||
controller: IUIController,
|
||||
loc: ElementLocator,
|
||||
props?: ViewMapProps
|
||||
) {
|
||||
controller.open(ViewMapUI, {
|
||||
...props,
|
||||
loc,
|
||||
floorId: core.status.floorId
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user