feat: 分离小地图绘制

This commit is contained in:
unanmed 2024-04-21 14:39:58 +08:00
parent 1279dc9f8f
commit 0fa07e0605
7 changed files with 344 additions and 263 deletions

View File

@ -97,7 +97,7 @@ dam4.png ---- 存档 59
[] 技能树允许自动升级 [] 技能树允许自动升级
[] 重构装备系统 [] 重构装备系统
[] 弹幕系统 [] 弹幕系统
[] 优化各种 ui [x] 优化各种 ui
[] 怪物脚下加入阴影 [] 怪物脚下加入阴影
[x] 着色器特效 [x] 着色器特效
[x] 完全删除 core.plugin采用 Plugin.register 的形式进行插件编写 [x] 完全删除 core.plugin采用 Plugin.register 的形式进行插件编写

View File

@ -37,7 +37,7 @@ main.floors.MT50=
"左边两个机关门在打完左下角区域的boss后开启右边同理。" "左边两个机关门在打完左下角区域的boss后开启右边同理。"
], ],
"9,1": [ "9,1": [
"左下角和右下角两个区域打完之后必须要点开学习技能,不然会卡关。", "建议优先点出学习技能,对于特定场景将会非常有帮助",
"本区域可以使用跳跃技能,不要忘记了。" "本区域可以使用跳跃技能,不要忘记了。"
] ]
}, },

View File

@ -33,21 +33,17 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
} }
addMaterialLoaded() { addMaterialLoaded() {
this.once('coreInit', () => { this.materialsLoaded++;
this.materialsLoaded++; if (this.materialsLoaded === this.materialsNum) {
if (this.materialsLoaded === this.materialsNum) { this.emit('materialLoaded');
this.emit('materialLoaded'); }
}
});
} }
addAutotileLoaded() { addAutotileLoaded() {
this.once('coreInit', () => { this.autotileLoaded++;
this.autotileLoaded++; if (this.autotileLoaded === this.autotileNum) {
if (this.autotileLoaded === this.autotileNum) { this.emit('autotileLoaded');
this.emit('autotileLoaded'); }
}
});
} }
/** /**

View File

@ -1,3 +1,5 @@
import { hook } from '@/game/game';
export {}; export {};
const potionItems: AllIdsOf<'items'>[] = [ const potionItems: AllIdsOf<'items'>[] = [
@ -11,8 +13,6 @@ const potionItems: AllIdsOf<'items'>[] = [
'I491' 'I491'
]; ];
const hook = Mota.require('var', 'hook');
hook.on('afterGetItem', (itemId, x, y, isGentleClick) => { hook.on('afterGetItem', (itemId, x, y, isGentleClick) => {
// 获得一个道具后触发的事件 // 获得一个道具后触发的事件
// itemId获得的道具IDx和y是该道具所在的坐标 // itemId获得的道具IDx和y是该道具所在的坐标

View File

@ -17,6 +17,7 @@ import * as utils from './utils';
import * as chase from './chase'; import * as chase from './chase';
import * as remainEnemy from './enemy/remainEnemy'; import * as remainEnemy from './enemy/remainEnemy';
import * as checkBlock from './enemy/checkblock'; import * as checkBlock from './enemy/checkblock';
import './hook';
Mota.Plugin.register('utils_g', utils); Mota.Plugin.register('utils_g', utils);
Mota.Plugin.register('loopMap_g', loopMap, loopMap.init); Mota.Plugin.register('loopMap_g', loopMap, loopMap.init);

View File

@ -1,4 +1,4 @@
import { has } from '../utils'; import { downloadCanvasImage, has, tip } from '../utils';
type BFSFromString = `${FloorIds},${number},${number},${Dir}`; type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
type BFSToString = `${FloorIds},${number},${number}`; type BFSToString = `${FloorIds},${number},${number}`;
@ -200,3 +200,294 @@ export function getMapData(
return (bfsCache[floorId] = res); return (bfsCache[floorId] = res);
} }
type Loc2 = [number, number, number, number];
export class MinimapDrawer {
ctx: CanvasRenderingContext2D;
canvas: HTMLCanvasElement;
scale: number = 1;
nowFloor: FloorIds = core.status.floorId;
nowArea: string = '';
// position & config
ox: number = 0;
oy: number = 0;
noBorder: boolean = false;
// cache
drawedThumbnail: Partial<Record<FloorIds, boolean>> = {};
thumbnailLoc: Partial<Record<FloorIds, Loc2>> = {};
// temp
private tempCanvas: HTMLCanvasElement = document.createElement('canvas');
private tempCtx: CanvasRenderingContext2D;
private downloadMode: boolean = false;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
this.tempCtx = this.tempCanvas.getContext('2d')!;
}
link(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
}
clearCache() {
this.drawedThumbnail = {};
this.thumbnailLoc = {};
}
/**
*
* @param noCache 使
*/
drawMap(noCache: boolean = false) {
const border = this.noBorder ? 0.5 : 1;
const data = getMapDrawData(
this.nowFloor,
this.noBorder ? 0 : 5,
border,
noCache
);
const temp = this.tempCanvas;
const ctx = this.tempCtx;
const s = this.scale * devicePixelRatio;
temp.width = data.width * s;
temp.height = data.height * s;
ctx.lineWidth = (border * devicePixelRatio) / 2;
ctx.strokeStyle = '#fff';
ctx.scale(s, s);
ctx.translate(5, 5);
if (!this.noBorder) {
// 绘制连线
data.line.forEach(([x1, y1, x2, y2]) => {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
});
}
// 绘制地图及缩略图
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (!this.noBorder) this.drawBorder(id, x, y);
this.drawThumbnail(id, x, y);
}
this.drawToTarget();
}
/**
*
*/
drawBorder(id: FloorIds, x: number, y: number) {
const border = this.noBorder ? 0.5 : 1;
const ctx = this.tempCtx;
ctx.lineWidth = border * devicePixelRatio;
const map = core.status.maps[id];
if (!core.hasVisitedFloor(id)) {
ctx.fillStyle = '#d0d';
} else {
ctx.fillStyle = '#000';
}
if (id === this.nowFloor) {
ctx.strokeStyle = 'gold';
} else {
ctx.strokeStyle = '#fff';
}
ctx.strokeRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
ctx.fillRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
if (id === this.nowFloor) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
}
}
/**
*
*/
drawToTarget() {
const mapCtx = this.ctx;
const map = this.canvas;
const temp = this.tempCanvas;
mapCtx.clearRect(0, 0, map.width, map.height);
mapCtx.drawImage(
temp,
0,
0,
temp.width,
temp.height,
this.ox * devicePixelRatio + (map.width - temp.width) / 2,
this.oy * devicePixelRatio + (map.height - temp.height) / 2,
temp.width,
temp.height
);
}
/**
*
*/
checkThumbnail(floorId: FloorIds, x: number, y: number) {
const scale = this.scale;
const ox = this.ox;
const oy = this.oy;
const map = this.canvas;
const temp = this.tempCanvas;
const floor = core.status.maps[floorId];
const s = scale * devicePixelRatio;
const px = ox * devicePixelRatio + (map.width - temp.width) / 2 + 5 * s;
const py =
oy * devicePixelRatio + (map.height - temp.height) / 2 + 5 * s;
const left = px + (x - floor.width / 2) * s;
const top = py + (y - floor.height / 2) * s;
const right = left + floor.width * s;
const bottom = top + floor.height * s;
this.thumbnailLoc[floorId] = [left, top, right, bottom];
if (
this.drawedThumbnail[floorId] ||
(!this.noBorder && scale <= 4) ||
right < 0 ||
bottom < 0 ||
left > map.width ||
top > map.height
)
return false;
return true;
}
/**
*
*/
drawThumbnail(
floorId: FloorIds,
x: number,
y: number,
noCheck: boolean = false
) {
if (
!this.downloadMode &&
!noCheck &&
!this.checkThumbnail(floorId, x, y)
)
return;
const floor = core.status.maps[floorId];
this.drawedThumbnail[floorId] = true;
// 绘制缩略图
const ctx = this.tempCtx;
core.drawThumbnail(floorId, void 0, {
all: true,
inFlyMap: true,
x: x - floor.width / 2,
y: y - floor.height / 2,
w: floor.width,
h: floor.height,
ctx,
damage: this.scale > 7
});
if (!this.downloadMode) {
if (!core.hasVisitedFloor(floorId)) {
ctx.fillStyle = '#d0d6';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
if (this.nowFloor === floorId) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
}
}
/**
*
*/
checkMoveThumbnail() {
const border = this.noBorder ? 0.5 : 1;
const data = getMapDrawData(
this.nowFloor,
this.noBorder ? 0 : 5,
border
);
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (this.checkThumbnail(id, x, y)) {
this.drawThumbnail(id, x, y, true);
}
}
}
download() {
if (this.nowArea === '') {
tip('error', '当前地图不在任意一个区域内!');
return;
}
this.downloadMode = true;
const before = this.scale;
this.scale = 32;
this.drawMap();
downloadCanvasImage(this.tempCanvas, this.nowArea);
this.scale = before;
this.downloadMode = false;
tip('success', '图片下载成功!');
}
/**
*
* @param id id
*/
locateMap(id: FloorIds) {
const data = getMapDrawData(
id,
this.noBorder ? 0 : 5, // 可恶的0和5写反了找一个多小时
this.noBorder ? 0.5 : 1
);
if (!data.locs[id]) return;
const [x, y] = data.locs[id]!;
this.ox = (-x + data.width / 2 - 5) * this.scale;
this.oy = (-y + data.height / 2 - 5) * this.scale;
}
}

View File

@ -89,7 +89,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import Scroll from '../components/scroll.vue'; import Scroll from '../components/scroll.vue';
import { getArea, getMapDrawData, getMapData } from '../plugin/ui/fly'; import {
getArea,
getMapDrawData,
getMapData,
MinimapDrawer
} from '../plugin/ui/fly';
import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use'; import { cancelGlobalDrag, isMobile, useDrag, useWheel } from '../plugin/use';
import { import {
LeftOutlined, LeftOutlined,
@ -121,10 +126,6 @@ const noBorder = ref(true);
const tradition = ref(false); const tradition = ref(false);
let scale = let scale =
((isMobile ? 1.5 : 3) * mainSetting.getValue('ui.mapScale', 100)) / 100; ((isMobile ? 1.5 : 3) * mainSetting.getValue('ui.mapScale', 100)) / 100;
let ox = 0;
let oy = 0;
let drawedThumbnail: Partial<Record<FloorIds, boolean>> = {};
let thumbnailLoc: Partial<Record<FloorIds, Loc2>> = {};
noBorder.value = core.getLocalStorage('noBorder', true); noBorder.value = core.getLocalStorage('noBorder', true);
tradition.value = core.getLocalStorage('flyTradition', false); tradition.value = core.getLocalStorage('flyTradition', false);
@ -133,32 +134,34 @@ const floor = computed(() => {
return core.status.maps[nowFloor.value]; return core.status.maps[nowFloor.value];
}); });
watch(nowFloor, draw); watch(nowFloor, n => {
drawer.nowFloor = n;
draw();
});
watch(nowArea, n => { watch(nowArea, n => {
ox = 0;
oy = 0;
scale = 3; scale = 3;
lastScale = 3; lastScale = 3;
drawer.nowArea = n;
drawer.scale = 3;
drawer.ox = 0;
drawer.oy = 0;
if (area[n] && !area[n].includes(nowFloor.value)) if (area[n] && !area[n].includes(nowFloor.value))
nowFloor.value = nowFloor.value =
area[n].find(v => v === core.status.floorId) ?? area[n][0]; area[n].find(v => v === core.status.floorId) ?? area[n][0];
}); });
watch(noBorder, n => { watch(noBorder, n => {
core.setLocalStorage('noBorder', n); core.setLocalStorage('noBorder', n);
drawedThumbnail = {}; drawer.noBorder = true;
drawMap(); drawer.drawedThumbnail = {};
drawer.drawMap();
}); });
watch(tradition, n => { watch(tradition, n => {
core.setLocalStorage('flyTradition', n); core.setLocalStorage('flyTradition', n);
}); });
const temp = document.createElement('canvas');
const tempCtx = temp.getContext('2d')!;
let map: HTMLCanvasElement; let map: HTMLCanvasElement;
let mapCtx: CanvasRenderingContext2D;
let thumb: HTMLCanvasElement; let thumb: HTMLCanvasElement;
let thumbCtx: CanvasRenderingContext2D; let thumbCtx: CanvasRenderingContext2D;
let downloadMode = false;
function exit() { function exit() {
mainUi.close(props.num); mainUi.close(props.num);
@ -169,192 +172,7 @@ const title = computed(() => {
}); });
const titleChange = createChangable(title).change; const titleChange = createChangable(title).change;
/** let drawer: MinimapDrawer;
* 绘制小地图
* @param noCache 是否不使用缓存
*/
function drawMap(noCache: boolean = false) {
const border = noBorder.value ? 0.5 : 1;
const data = getMapDrawData(
nowFloor.value,
noBorder.value ? 0 : 5,
border,
noCache
);
const ctx = tempCtx;
const s = scale * devicePixelRatio;
temp.width = data.width * s;
temp.height = data.height * s;
ctx.lineWidth = (border * devicePixelRatio) / 2;
ctx.strokeStyle = '#fff';
ctx.scale(s, s);
ctx.translate(5, 5);
if (!noBorder.value) {
// 线
data.line.forEach(([x1, y1, x2, y2]) => {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
});
}
//
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (!noBorder.value) drawBorder(id, x, y);
drawThumbnail(id, x, y);
}
drawToTarget();
}
function drawBorder(id: FloorIds, x: number, y: number) {
const border = noBorder.value ? 0.5 : 1;
const ctx = tempCtx;
ctx.lineWidth = border * devicePixelRatio;
const map = core.status.maps[id];
if (!core.hasVisitedFloor(id)) {
ctx.fillStyle = '#d0d';
} else {
ctx.fillStyle = '#000';
}
if (id === nowFloor.value) {
ctx.strokeStyle = 'gold';
} else {
ctx.strokeStyle = '#fff';
}
ctx.strokeRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
ctx.fillRect(x - map.width / 2, y - map.height / 2, map.width, map.height);
if (id === nowFloor.value) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - map.width / 2,
y - map.height / 2,
map.width,
map.height
);
}
}
/**
* 绘制小地图至目标画布
*/
function drawToTarget(s: number = 1) {
mapCtx.clearRect(0, 0, map.width, map.height);
mapCtx.drawImage(
temp,
0,
0,
temp.width,
temp.height,
ox * devicePixelRatio + (map.width - temp.width) / 2,
oy * devicePixelRatio + (map.height - temp.height) / 2,
temp.width,
temp.height
);
}
/**
* 检查是否应该绘制缩略图
*/
function checkThumbnail(floorId: FloorIds, x: number, y: number) {
const floor = core.status.maps[floorId];
const s = scale * devicePixelRatio;
const px = ox * devicePixelRatio + (map.width - temp.width) / 2 + 5 * s;
const py = oy * devicePixelRatio + (map.height - temp.height) / 2 + 5 * s;
const left = px + (x - floor.width / 2) * s;
const top = py + (y - floor.height / 2) * s;
const right = left + floor.width * s;
const bottom = top + floor.height * s;
thumbnailLoc[floorId] = [left, top, right, bottom];
if (
drawedThumbnail[floorId] ||
(!noBorder.value && scale <= 4) ||
right < 0 ||
bottom < 0 ||
left > map.width ||
top > map.height
)
return false;
return true;
}
/**
* 绘制缩略图
*/
function drawThumbnail(
floorId: FloorIds,
x: number,
y: number,
noCheck: boolean = false
) {
if (!downloadMode && !noCheck && !checkThumbnail(floorId, x, y)) return;
const floor = core.status.maps[floorId];
drawedThumbnail[floorId] = true;
//
const ctx = tempCtx;
core.drawThumbnail(floorId, void 0, {
all: true,
inFlyMap: true,
x: x - floor.width / 2,
y: y - floor.height / 2,
w: floor.width,
h: floor.height,
ctx,
damage: scale > 7
});
if (!downloadMode) {
if (!core.hasVisitedFloor(floorId)) {
ctx.fillStyle = '#d0d6';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
if (nowFloor.value === floorId) {
ctx.fillStyle = '#ff04';
ctx.fillRect(
x - floor.width / 2,
y - floor.height / 2,
floor.width,
floor.height
);
ctx.fillStyle = '#000';
}
}
}
/**
* 当移动时检查是否应该绘制缩略图
*/
function checkMoveThumbnail() {
const border = noBorder.value ? 0.5 : 1;
const data = getMapDrawData(nowFloor.value, noBorder.value ? 0 : 5, border);
for (const [id, [x, y]] of Object.entries(data.locs) as [
FloorIds,
LocArr
][]) {
if (checkThumbnail(id, x, y)) drawThumbnail(id, x, y, true);
}
}
function drawRight() { function drawRight() {
let w = thumb.width; let w = thumb.width;
@ -388,26 +206,14 @@ function drawRight() {
* 绘制所有内容 * 绘制所有内容
*/ */
function draw() { function draw() {
drawedThumbnail = {}; drawer.clearCache();
thumbnailLoc = {}; drawer.drawMap();
drawMap();
drawRight(); drawRight();
} }
function download() { function download() {
if (nowArea.value === '') { drawer.download();
tip('error', '当前地图不在任意一个区域内!');
return;
}
downloadMode = true;
const before = scale;
scale = 32;
drawMap();
downloadCanvasImage(temp, nowArea.value);
scale = before;
downloadMode = false;
draw(); draw();
tip('success', '图片下载成功!');
} }
function fly() { function fly() {
@ -418,18 +224,19 @@ function fly() {
let lastScale = scale; let lastScale = scale;
const changeScale = debounce((s: number) => { const changeScale = debounce((s: number) => {
map.style.transform = ''; map.style.transform = '';
drawedThumbnail = {}; drawer.drawedThumbnail = {};
drawMap(); drawer.scale = s;
drawer.drawMap();
lastScale = s; lastScale = s;
}, 200); }, 200);
function resize(delta: number) { function resize(delta: number) {
ox *= delta; drawer.ox *= delta;
oy *= delta; drawer.oy *= delta;
scale = delta * scale; scale = delta * scale;
changeScale(scale); changeScale(scale);
map.style.transform = `scale(${scale / lastScale})`; map.style.transform = `scale(${scale / lastScale})`;
thumbnailLoc = {}; drawer.thumbnailLoc = {};
} }
let lastX = 0; let lastX = 0;
@ -445,12 +252,12 @@ function drag(x: number, y: number) {
if (touchScale) return; if (touchScale) return;
const dx = x - lastX; const dx = x - lastX;
const dy = y - lastY; const dy = y - lastY;
ox += dx; drawer.ox += dx;
oy += dy; drawer.oy += dy;
lastX = x; lastX = x;
lastY = y; lastY = y;
checkMoveThumbnail(); drawer.checkMoveThumbnail();
drawToTarget(); drawer.drawToTarget();
if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) moved = true; if (Math.abs(x - startX) > 10 || Math.abs(y - startY) > 10) moved = true;
} }
@ -460,7 +267,7 @@ function click(e: MouseEvent) {
const x = e.offsetX * devicePixelRatio; const x = e.offsetX * devicePixelRatio;
const y = e.offsetY * devicePixelRatio; const y = e.offsetY * devicePixelRatio;
for (const [id, [left, top, right, bottom]] of Object.entries( for (const [id, [left, top, right, bottom]] of Object.entries(
thumbnailLoc drawer.thumbnailLoc
) as [FloorIds, Loc2][]) { ) as [FloorIds, Loc2][]) {
if (x >= left && x <= right && y >= top && y <= bottom) { if (x >= left && x <= right && y >= top && y <= bottom) {
if (id === nowFloor.value) { if (id === nowFloor.value) {
@ -497,7 +304,7 @@ function changeFloorByDelta(delta: number) {
} }
nowFloor.value = core.floorIds[to]; nowFloor.value = core.floorIds[to];
changeAreaByFloor(nowFloor.value); changeAreaByFloor(nowFloor.value);
locateMap(nowFloor.value); drawer.locateMap(nowFloor.value);
} }
function changeFloorByDir(dir: Dir) { function changeFloorByDir(dir: Dir) {
@ -509,30 +316,13 @@ function changeFloorByDir(dir: Dir) {
if (d === dir) { if (d === dir) {
const target = to.split(',')[0] as FloorIds; const target = to.split(',')[0] as FloorIds;
locateMap(target); drawer.locateMap(target);
nowFloor.value = target; nowFloor.value = target;
return; return;
} }
} }
} }
/**
* 居中地图
* @param id 楼层id
*/
function locateMap(id: FloorIds) {
const data = getMapDrawData(
id,
noBorder.value ? 0 : 5, // 05
noBorder.value ? 0.5 : 1
);
if (!data.locs[id]) return;
const [x, y] = data.locs[id]!;
ox = (-x + data.width / 2 - 5) * scale;
oy = (-y + data.height / 2 - 5) * scale;
}
// -------------------- // --------------------
gameKey.use(props.ui.symbol); gameKey.use(props.ui.symbol);
@ -607,10 +397,13 @@ function touchmove(e: TouchEvent) {
onMounted(async () => { onMounted(async () => {
map = document.getElementById('fly-map') as HTMLCanvasElement; map = document.getElementById('fly-map') as HTMLCanvasElement;
mapCtx = map.getContext('2d')!;
thumb = document.getElementById('fly-thumbnail') as HTMLCanvasElement; thumb = document.getElementById('fly-thumbnail') as HTMLCanvasElement;
thumbCtx = thumb.getContext('2d')!; thumbCtx = thumb.getContext('2d')!;
drawer = new MinimapDrawer(map);
drawer.scale = scale;
drawer.noBorder = noBorder.value;
const mapStyle = getComputedStyle(map); const mapStyle = getComputedStyle(map);
const thumbStyle = getComputedStyle(thumb); const thumbStyle = getComputedStyle(thumb);
map.width = parseFloat(mapStyle.width) * devicePixelRatio; map.width = parseFloat(mapStyle.width) * devicePixelRatio;
@ -622,7 +415,7 @@ onMounted(async () => {
v.addEventListener('click', e => (v as HTMLElement).blur()); v.addEventListener('click', e => (v as HTMLElement).blur());
}); });
locateMap(nowFloor.value); drawer.locateMap(nowFloor.value);
draw(); draw();
useDrag( useDrag(