mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-10-31 12:12:58 +08:00 
			
		
		
		
	feat: 浏览地图
This commit is contained in:
		
							parent
							
								
									398de70fee
								
							
						
					
					
						commit
						574d765c69
					
				| @ -434,36 +434,85 @@ gameKey | ||||
|         defaults: KeyCode.PageUp | ||||
|     }) | ||||
|     // #region 存档界面
 | ||||
|     .group('@ui_save', 'save') | ||||
|     .group('@ui_save', '存档界面') | ||||
|     .register({ | ||||
|         id: '@save_pageUp', | ||||
|         name: '存档向后翻页', | ||||
|         name: '向后翻页', | ||||
|         defaults: KeyCode.PageUp | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@save_pageDown', | ||||
|         name: '存档向前翻页', | ||||
|         name: '向前翻页', | ||||
|         defaults: KeyCode.PageDown | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@save_up', | ||||
|         name: '存档选择框向上', | ||||
|         name: '选择框向上', | ||||
|         defaults: KeyCode.UpArrow | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@save_down', | ||||
|         name: '存档选择框向下', | ||||
|         name: '选择框向下', | ||||
|         defaults: KeyCode.DownArrow | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@save_left', | ||||
|         name: '存档选择框向左', | ||||
|         name: '选择框向左', | ||||
|         defaults: KeyCode.LeftArrow | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@save_right', | ||||
|         name: '存档选择框向右', | ||||
|         name: '选择框向右', | ||||
|         defaults: KeyCode.RightArrow | ||||
|     }) | ||||
|     //#region 浏览地图
 | ||||
|     .group('@ui_viewMap', '浏览地图') | ||||
|     .register({ | ||||
|         id: '@viewMap_up_1', | ||||
|         name: '下一层地图_1', | ||||
|         defaults: KeyCode.UpArrow | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_up_2', | ||||
|         name: '下一层地图_2', | ||||
|         defaults: KeyCode.PageUp | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_down_1', | ||||
|         name: '上一层地图_1', | ||||
|         defaults: KeyCode.DownArrow | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_down_2', | ||||
|         name: '上一层地图_2', | ||||
|         defaults: KeyCode.PageDown | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_up_ten', | ||||
|         name: '下十层地图', | ||||
|         defaults: KeyCode.UpArrow, | ||||
|         ctrl: true | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_down_ten', | ||||
|         name: '上十层地图', | ||||
|         defaults: KeyCode.DownArrow, | ||||
|         ctrl: true | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_book', | ||||
|         name: '怪物手册', | ||||
|         defaults: KeyCode.KeyX | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_fly', | ||||
|         name: '传送至', | ||||
|         defaults: KeyCode.KeyG | ||||
|     }) | ||||
|     .register({ | ||||
|         id: '@viewMap_reset', | ||||
|         name: '重置视角', | ||||
|         defaults: KeyCode.KeyR | ||||
|     }); | ||||
| // #endregion
 | ||||
| 
 | ||||
| @ -492,9 +541,6 @@ gameKey | ||||
|     .realize('shop', () => { | ||||
|         core.openQuickShop(true); | ||||
|     }) | ||||
|     .realize('viewMap', () => { | ||||
|         core.ui._drawViewMaps(); | ||||
|     }) | ||||
|     .realize('skillTree', () => { | ||||
|         core.useItem('skill1', true); | ||||
|     }) | ||||
|  | ||||
| @ -6,7 +6,8 @@ import { | ||||
|     openStatistics, | ||||
|     saveLoad, | ||||
|     openSettings, | ||||
|     ReplaySettingsUI | ||||
|     ReplaySettingsUI, | ||||
|     openViewMap | ||||
| } from './ui'; | ||||
| 
 | ||||
| export function createAction() { | ||||
| @ -27,5 +28,8 @@ export function createAction() { | ||||
|             mainUIController.open(ReplaySettingsUI, { | ||||
|                 loc: [420, 240, void 0, void 0, 0.5, 0.5] | ||||
|             }); | ||||
|         }) | ||||
|         .realize('viewMap', () => { | ||||
|             openViewMap(mainUIController, [0, 0, MAIN_WIDTH, MAIN_HEIGHT]); | ||||
|         }); | ||||
| } | ||||
|  | ||||
| @ -37,6 +37,8 @@ export class BlockCacher< | ||||
|     width: number; | ||||
|     /** 区域高度 */ | ||||
|     height: number; | ||||
|     /** 区域面积 */ | ||||
|     area: number = 0; | ||||
|     /** 分块大小 */ | ||||
|     blockSize: number; | ||||
|     /** 分块信息 */ | ||||
| @ -116,6 +118,7 @@ export class BlockCacher< | ||||
|             restWidth: this.width % this.blockSize, | ||||
|             restHeight: this.height % this.blockSize | ||||
|         }; | ||||
|         this.area = this.blockData.width * this.blockData.height; | ||||
|         this.emit('split'); | ||||
|     } | ||||
| 
 | ||||
| @ -303,7 +306,10 @@ export interface ICanvasCacheItem extends IBlockCacheable { | ||||
| } | ||||
| 
 | ||||
| export class CanvasCacheItem implements ICanvasCacheItem { | ||||
|     constructor(public canvas: MotaOffscreenCanvas2D, public symbol: number) {} | ||||
|     constructor( | ||||
|         public canvas: MotaOffscreenCanvas2D, | ||||
|         public symbol: number | ||||
|     ) {} | ||||
| 
 | ||||
|     destroy(): void {} | ||||
| } | ||||
|  | ||||
| @ -1132,6 +1132,7 @@ export class Layer extends Container<ELayerEvent> { | ||||
|             const length = this.backImage.length; | ||||
|             const img = this.backImage[frame % length]; | ||||
|             need.forEach(index => { | ||||
|                 if (index >= this.block.area || index < 0) return; | ||||
|                 const x = index % width; | ||||
|                 const y = Math.floor(index / width); | ||||
|                 const sx = x * blockSize; | ||||
|  | ||||
| @ -11,3 +11,4 @@ export * from './settings'; | ||||
| export * from './statistics'; | ||||
| export * from './statusBar'; | ||||
| export * from './toolbar'; | ||||
| export * from './viewmap'; | ||||
|  | ||||
| @ -19,6 +19,8 @@ import { | ||||
|     ReplayingToolbar | ||||
| } from './toolbar'; | ||||
| import { HeroSkill } from '@user/data-state'; | ||||
| import { openViewMap } from './viewmap'; | ||||
| import { mainUIController } from './controller'; | ||||
| 
 | ||||
| export interface ILeftHeroStatus { | ||||
|     hp: number; | ||||
| @ -98,6 +100,10 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>( | ||||
|             return [per * (n + 1), keyY, void 0, void 0, 0.5, 0.5]; | ||||
|         }; | ||||
| 
 | ||||
|         const viewMap = () => { | ||||
|             openViewMap(mainUIController, [0, 0, 840, 480]); | ||||
|         }; | ||||
| 
 | ||||
|         return () => { | ||||
|             return ( | ||||
|                 <container loc={p.loc} hidden={p.hidden}> | ||||
| @ -106,6 +112,7 @@ export const LeftStatusBar = defineComponent<StatusBarProps<ILeftHeroStatus>>( | ||||
|                         loc={central(24)} | ||||
|                         font={font1} | ||||
|                         cursor="pointer" | ||||
|                         onClick={viewMap} | ||||
|                     ></text> | ||||
|                     <text text={s.lv} loc={central(54)} font={font1}></text> | ||||
|                     <image image={hpIcon} loc={iconLoc(0)}></image> | ||||
|  | ||||
| @ -332,8 +332,8 @@ export const GameTitle = defineComponent<GameTitleProps>(props => { | ||||
| 
 | ||||
|     const createMaskGradient = (ctx: CanvasRenderingContext2D) => { | ||||
|         maskGradient = ctx.createLinearGradient(100, 100, 200, 0); | ||||
|         maskGradient.addColorStop(0, '#fff'); | ||||
|         maskGradient.addColorStop(1, '#000'); | ||||
|         maskGradient.addColorStop(0, 'rgba(255,255,255,0)'); | ||||
|         maskGradient.addColorStop(1, 'rgba(0,0,0,1)'); | ||||
|     }; | ||||
| 
 | ||||
|     const createTitleGradient = (ctx: CanvasRenderingContext2D) => { | ||||
|  | ||||
							
								
								
									
										503
									
								
								packages-user/client-modules/src/render/ui/viewmap.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										503
									
								
								packages-user/client-modules/src/render/ui/viewmap.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,503 @@ | ||||
| import { | ||||
|     ElementLocator, | ||||
|     IActionEvent, | ||||
|     IActionEventBase, | ||||
|     IWheelEvent, | ||||
|     MotaOffscreenCanvas2D | ||||
| } from '@motajs/render-core'; | ||||
| import { BaseProps } from '@motajs/render-vue'; | ||||
| import { | ||||
|     GameUI, | ||||
|     IUIMountable, | ||||
|     SetupComponentOptions, | ||||
|     UIComponentProps | ||||
| } from '@motajs/system-ui'; | ||||
| import { | ||||
|     computed, | ||||
|     defineComponent, | ||||
|     markRaw, | ||||
|     onMounted, | ||||
|     onUnmounted, | ||||
|     ref, | ||||
|     shallowRef, | ||||
|     watch | ||||
| } from 'vue'; | ||||
| import { FloorSelector } from '../components/floorSelect'; | ||||
| import { | ||||
|     ILayerGroupRenderExtends, | ||||
|     FloorDamageExtends, | ||||
|     FloorItemDetail, | ||||
|     LayerGroupAnimate, | ||||
|     LayerGroup, | ||||
|     LayerGroupFloorBinder | ||||
| } from '../elements'; | ||||
| import { LayerGroupHalo } from '../legacy/halo'; | ||||
| import { LayerGroupPortal } from '../legacy/portal'; | ||||
| import { Font } from '@motajs/render-style'; | ||||
| import { clamp, mean } from 'lodash-es'; | ||||
| import { calculateStatisticsOne, StatisticsDataOneFloor } from './statistics'; | ||||
| import { Tip, TipExpose } from '../components'; | ||||
| import { useKey } from '../use'; | ||||
| 
 | ||||
| export interface ViewMapProps extends UIComponentProps, 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 LayerGroupPortal(), | ||||
|         new LayerGroupHalo(), | ||||
|         new LayerGroupAnimate() | ||||
|     ]; | ||||
| 
 | ||||
|     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()); | ||||
| 
 | ||||
|     //#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 = () => { | ||||
|         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, 480, 64); | ||||
|     }; | ||||
| 
 | ||||
|     const renderBottom = (canvas: MotaOffscreenCanvas2D) => { | ||||
|         const ctx = canvas.ctx; | ||||
|         ctx.fillStyle = getBottomGradient(ctx); | ||||
|         ctx.fillRect(0, 0, 480, 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 地图交互
 | ||||
| 
 | ||||
|     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={[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} /> | ||||
|             <FloorSelector | ||||
|                 loc={[0, 0, 180, 480]} | ||||
|                 floors={viewableFloor} | ||||
|                 v-model:now={now.value} | ||||
|                 onClose={close} | ||||
|             /> | ||||
|             <layer-group | ||||
|                 ref={group} | ||||
|                 ex={layerGroupExtends} | ||||
|                 loc={[180, 0, 480, 480]} | ||||
|                 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={[188, 8, 200, 32]} | ||||
|                 pad={[12, 6]} | ||||
|                 corner={16} | ||||
|             /> | ||||
|             <sprite | ||||
|                 loc={[180, 0, 480, 64]} | ||||
|                 render={renderTop} | ||||
|                 alpha={topAlpha.value} | ||||
|                 zIndex={10} | ||||
|                 cursor="pointer" | ||||
|                 onEnter={enterTop} | ||||
|                 onLeave={leaveTop} | ||||
|                 onClick={() => changeFloor(1)} | ||||
|             /> | ||||
|             <sprite | ||||
|                 loc={[180, 416, 480, 64]} | ||||
|                 render={renderBottom} | ||||
|                 alpha={bottomAlpha.value} | ||||
|                 zIndex={10} | ||||
|                 cursor="pointer" | ||||
|                 onEnter={enterBottom} | ||||
|                 onLeave={leaveBottom} | ||||
|                 onClick={() => changeFloor(-1)} | ||||
|             /> | ||||
|             <text | ||||
|                 text="上移地图" | ||||
|                 loc={[420, 24]} | ||||
|                 anc={[0.5, 0.5]} | ||||
|                 zIndex={20} | ||||
|                 noevent | ||||
|             /> | ||||
|             <text | ||||
|                 text="下移地图" | ||||
|                 loc={[420, 456]} | ||||
|                 anc={[0.5, 0.5]} | ||||
|                 zIndex={20} | ||||
|                 noevent | ||||
|             /> | ||||
|             <container loc={[660, 0, 180, 480]}> | ||||
|                 <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={[90, 330]} | ||||
|                     anc={[0.5, 0.5]} | ||||
|                     cursor="pointer" | ||||
|                     onClick={openBook} | ||||
|                 /> | ||||
|                 <text | ||||
|                     text="「 传送至此 」" | ||||
|                     loc={[90, 380]} | ||||
|                     anc={[0.5, 0.5]} | ||||
|                     cursor="pointer" | ||||
|                     onClick={fly} | ||||
|                 /> | ||||
|                 <text | ||||
|                     text="「 重置视角 」" | ||||
|                     loc={[90, 430]} | ||||
|                     anc={[0.5, 0.5]} | ||||
|                     cursor="pointer" | ||||
|                     onClick={resetCamera} | ||||
|                 /> | ||||
|             </container> | ||||
|         </container> | ||||
|     ); | ||||
| }, viewMapProps); | ||||
| 
 | ||||
| export const ViewMapUI = new GameUI('view-map', ViewMap); | ||||
| 
 | ||||
| export function openViewMap( | ||||
|     controller: IUIMountable, | ||||
|     loc: ElementLocator, | ||||
|     props?: ViewMapProps | ||||
| ) { | ||||
|     controller.open(ViewMapUI, { | ||||
|         ...props, | ||||
|         loc, | ||||
|         floorId: core.status.floorId | ||||
|     }); | ||||
| } | ||||
| @ -101,7 +101,6 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> { | ||||
|     /** | ||||
|      * 复制一个离屏Canvas2D对象,一般用于缓存等操作 | ||||
|      * @param canvas 被复制的MotaOffscreenCanvas2D对象 | ||||
|      * @returns 复制结果,注意复制结果是被冻结的,无法进行大小等的修改,但是可以继续绘制 | ||||
|      */ | ||||
|     static clone(canvas: MotaOffscreenCanvas2D): MotaOffscreenCanvas2D { | ||||
|         const newCanvas = new MotaOffscreenCanvas2D(); | ||||
|  | ||||
| @ -140,7 +140,7 @@ export class Container<E extends EContainerEvent = EContainerEvent> | ||||
|     destroy(): void { | ||||
|         super.destroy(); | ||||
|         this.children.forEach(v => { | ||||
|             v.remove(); | ||||
|             v.destroy(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -193,6 +193,8 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { | ||||
|             this.createTouchAction(ev, ActionType.Up).forEach(v => { | ||||
|                 this.captureEvent(ActionType.Up, v); | ||||
|                 this.captureEvent(ActionType.Click, v); | ||||
|             }); | ||||
|             [...ev.touches].forEach(v => { | ||||
|                 this.touchInfo.delete(v.identifier); | ||||
|             }); | ||||
|         }); | ||||
| @ -200,14 +202,18 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { | ||||
|             ev.preventDefault(); | ||||
|             this.createTouchAction(ev, ActionType.Up).forEach(v => { | ||||
|                 this.captureEvent(ActionType.Up, v); | ||||
|             }); | ||||
|             [...ev.touches].forEach(v => { | ||||
|                 this.touchInfo.delete(v.identifier); | ||||
|             }); | ||||
|         }); | ||||
|         document.addEventListener('touchmove', ev => { | ||||
|             ev.preventDefault(); | ||||
|             this.createTouchAction(ev, ActionType.Move).forEach(v => { | ||||
|                 const touch = this.touchInfo.get(v.identifier); | ||||
|                 if (!touch) return; | ||||
|                 const list = this.touchInfo.values(); | ||||
|                 if (!list.some(vv => v.identifier === vv.identifier)) { | ||||
|                     return; | ||||
|                 } | ||||
|                 const temp = this.beforeHovered; | ||||
|                 temp.clear(); | ||||
|                 this.beforeHovered = this.hoveredElement; | ||||
|  | ||||
| @ -163,7 +163,7 @@ export class Image extends RenderItem<EImageEvent> { | ||||
|     image: CanvasImageSource; | ||||
| 
 | ||||
|     constructor(image: CanvasImageSource, type: RenderItemPosition = 'static') { | ||||
|         super(type); | ||||
|         super(type, false); | ||||
|         this.image = image; | ||||
|         if (image instanceof VideoFrame || image instanceof SVGElement) { | ||||
|             this.size(200, 200); | ||||
| @ -177,7 +177,7 @@ export class Image extends RenderItem<EImageEvent> { | ||||
|         _transform: Transform | ||||
|     ): void { | ||||
|         const ctx = canvas.ctx; | ||||
|         ctx.drawImage(this.image, 0, 0, canvas.width, canvas.height); | ||||
|         ctx.drawImage(this.image, 0, 0, this.width, this.height); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -30,7 +30,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({ | ||||
|     }, | ||||
| 
 | ||||
|     remove: function (el: RenderItem<ERenderItemEvent>): void { | ||||
|         el.remove(); | ||||
|         el.destroy(); | ||||
|     }, | ||||
| 
 | ||||
|     createElement: function ( | ||||
|  | ||||
| @ -436,6 +436,13 @@ export const gameKey = new Hotkey('gameKey', '游戏按键'); | ||||
| document.addEventListener('keyup', e => { | ||||
|     const assist = generateBinary([e.ctrlKey, e.shiftKey, e.altKey]); | ||||
|     const code = keycode(e.keyCode); | ||||
|     if ( | ||||
|         code === KeyCode.Alt || | ||||
|         code === KeyCode.Shift || | ||||
|         code === KeyCode.Ctrl | ||||
|     ) { | ||||
|         e.preventDefault(); | ||||
|     } | ||||
|     if (gameKey.emitKey(code, assist, 'up', e)) { | ||||
|         e.preventDefault(); | ||||
|         if (core.status.holdingKeys) { | ||||
|  | ||||
| @ -40,8 +40,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "小绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(20 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(20 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(20 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(20 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -626,8 +626,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "中绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(40 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(40 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(40 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(40 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -729,8 +729,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "大绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(80 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(80 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(80 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(80 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -896,8 +896,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "超大绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(160 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(160 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(160 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(160 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -1019,8 +1019,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "璀璨绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(320 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(320 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(320 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(320 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -1050,8 +1050,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "传奇绿宝石", | ||||
| 		"text": ",防御+${core.values.blueGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(640 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(640 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.def += core.values.blueGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
| @ -1071,8 +1071,8 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a = | ||||
| 		"cls": "items", | ||||
| 		"name": "史诗绿宝石", | ||||
| 		"text": ",护盾+${core.values.greenGem}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(1280 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(1280 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"itemEffect": "core.status.hero.mdef += Math.round(1280 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))", | ||||
| 		"itemEffectTip": ",智慧+${Math.round(1280 * core.status.thisMap.ratio / (core.getFlag(\"hard\") + 1) * (Mota.require('@user/data-state').getSkillLevel(12) / 20 + 1))}", | ||||
| 		"useItemEffect": "core.status.hero.mdef += core.values.greenGem", | ||||
| 		"canUseItemEffect": "true" | ||||
| 	}, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user