feat: 扩展游戏内容和地图系统优化
- MiniMap: 添加缩放/平移功能,优化节点显示样式 - 新增洞穴相关敌人和Boss(洞穴蝙蝠、洞穴领主) - 新增义体类物品(钢制义臂、光学义眼、真皮护甲) - 扩展武器技能系统(剑、斧、钝器、弓箭精通) - 更新商店配置和义体相关功能 - 完善玩家/游戏Store状态管理 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,40 +2,70 @@
|
||||
<view class="mini-map">
|
||||
<!-- 地图容器 -->
|
||||
<view class="mini-map__container">
|
||||
<!-- 连接线层 -->
|
||||
<view class="mini-map__connections">
|
||||
<view
|
||||
v-for="edge in mapData.edges"
|
||||
:key="`${edge.from}-${edge.to}`"
|
||||
class="connection-line"
|
||||
:class="{ 'connection-line--path': isInPath(edge) }"
|
||||
:style="getLineStyle(edge)"
|
||||
></view>
|
||||
</view>
|
||||
|
||||
<!-- 节点层 -->
|
||||
<view
|
||||
v-for="node in mapData.nodes"
|
||||
:key="node.id"
|
||||
class="map-node"
|
||||
:class="getNodeClass(node)"
|
||||
:style="getNodeStyle(node)"
|
||||
@click="handleNodeClick(node)"
|
||||
<scroll-view
|
||||
class="mini-map__scroll"
|
||||
scroll-x
|
||||
scroll-y
|
||||
:scroll-left="scrollLeft"
|
||||
:scroll-top="scrollTop"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<!-- 节点圆点 -->
|
||||
<view class="node__dot"></view>
|
||||
<view
|
||||
class="mini-map__content"
|
||||
:style="contentStyle"
|
||||
>
|
||||
<!-- 连接线层 -->
|
||||
<view class="mini-map__connections">
|
||||
<view
|
||||
v-for="edge in mapData.edges"
|
||||
:key="`${edge.from}-${edge.to}`"
|
||||
class="connection-line"
|
||||
:class="{ 'connection-line--path': isInPath(edge) }"
|
||||
:style="getLineStyle(edge)"
|
||||
></view>
|
||||
</view>
|
||||
|
||||
<!-- 当前位置脉冲效果 -->
|
||||
<view v-if="node.id === currentLocation" class="node__pulse"></view>
|
||||
<!-- 节点层 -->
|
||||
<view
|
||||
v-for="node in mapData.nodes"
|
||||
:key="node.id"
|
||||
class="map-node"
|
||||
:class="getNodeClass(node)"
|
||||
:style="getNodeStyle(node)"
|
||||
@click="handleNodeClick(node)"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<!-- 节点圆点 -->
|
||||
<view class="node__dot"></view>
|
||||
|
||||
<!-- 节点名称 -->
|
||||
<text class="node__name">{{ node.name }}</text>
|
||||
<!-- 当前位置脉冲效果 -->
|
||||
<view v-if="node.id === currentLocation" class="node__pulse"></view>
|
||||
|
||||
<!-- 锁定图标 -->
|
||||
<text v-if="isLocked(node)" class="node__lock">🔒</text>
|
||||
<!-- 节点名称 -->
|
||||
<text class="node__name">{{ node.name }}</text>
|
||||
|
||||
<!-- 距离标记 -->
|
||||
<text v-else-if="getDistance(node) > 0" class="node__distance">{{ getDistance(node) }}</text>
|
||||
<!-- 锁定图标 -->
|
||||
<text v-if="isLocked(node)" class="node__lock">🔒</text>
|
||||
|
||||
<!-- 距离标记 -->
|
||||
<text v-else-if="getDistance(node) > 0" class="node__distance">{{ getDistance(node) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 缩放控制 -->
|
||||
<view class="mini-map__zoom-controls">
|
||||
<view class="zoom-btn" @click="zoomIn">
|
||||
<text class="zoom-icon">+</text>
|
||||
</view>
|
||||
<view class="zoom-btn" @click="resetZoom">
|
||||
<text class="zoom-icon">⟲</text>
|
||||
</view>
|
||||
<view class="zoom-btn" @click="zoomOut">
|
||||
<text class="zoom-icon">−</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -79,28 +109,45 @@ const player = usePlayerStore()
|
||||
const mapData = ref({ nodes: [], edges: [], width: 300, height: 400 })
|
||||
const selectedPath = ref([])
|
||||
|
||||
// 缩放和平移状态
|
||||
const zoom = ref(1)
|
||||
const scrollLeft = ref(0)
|
||||
const scrollTop = ref(0)
|
||||
const currentLocationNode = ref(null)
|
||||
|
||||
// 容器尺寸
|
||||
const containerWidth = 280
|
||||
const containerHeight = 300
|
||||
|
||||
// 触摸拖动状态
|
||||
const touchStartX = ref(0)
|
||||
const touchStartY = ref(0)
|
||||
const isDragging = ref(false)
|
||||
|
||||
const currentLocation = computed(() => player.currentLocation)
|
||||
|
||||
// 内容样式
|
||||
const contentStyle = computed(() => {
|
||||
return {
|
||||
width: (containerWidth * zoom.value) + 'px',
|
||||
height: (containerHeight * zoom.value) + 'px',
|
||||
transformOrigin: 'center center'
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化地图
|
||||
function initMap() {
|
||||
const data = getMapData(player.currentLocation)
|
||||
|
||||
// 缩放坐标以适应容器
|
||||
const containerWidth = 280
|
||||
const containerHeight = 340
|
||||
const scaleX = containerWidth / data.width
|
||||
const scaleY = containerHeight / data.height
|
||||
const scale = Math.min(scaleX, scaleY, 1)
|
||||
|
||||
// 居中偏移
|
||||
const offsetX = (containerWidth - data.width * scale) / 2 + 10
|
||||
const offsetY = (containerHeight - data.height * scale) / 2 + 10
|
||||
// 不再缩放以适应容器,使用原始坐标
|
||||
// 但确保地图足够大
|
||||
const padding = 40
|
||||
|
||||
mapData.value = {
|
||||
nodes: data.nodes.map(n => ({
|
||||
...n,
|
||||
x: n.x * scale + offsetX,
|
||||
y: n.y * scale + offsetY
|
||||
x: n.x,
|
||||
y: n.y
|
||||
})),
|
||||
edges: data.edges,
|
||||
width: data.width,
|
||||
@@ -108,21 +155,74 @@ function initMap() {
|
||||
reachable: data.reachable
|
||||
}
|
||||
|
||||
// 更新缩放比例供样式使用
|
||||
updateNodeStyles()
|
||||
// 找到当前节点并居中
|
||||
centerOnLocation()
|
||||
selectedPath.value = []
|
||||
}
|
||||
|
||||
// 更新节点样式(转换为rpx)
|
||||
function updateNodeStyles() {
|
||||
// 在实际渲染时使用百分比或rpx
|
||||
// 居中到当前位置
|
||||
function centerOnLocation() {
|
||||
const currentNode = mapData.value.nodes.find(n => n.id === currentLocation.value)
|
||||
if (!currentNode) return
|
||||
|
||||
currentLocationNode.value = currentNode
|
||||
|
||||
// 计算需要滚动的位置以居中当前节点
|
||||
// 考虑缩放比例
|
||||
const targetLeft = currentNode.x * zoom.value - containerWidth / 2
|
||||
const targetTop = currentNode.y * zoom.value - containerHeight / 2
|
||||
|
||||
scrollLeft.value = Math.max(0, targetLeft)
|
||||
scrollTop.value = Math.max(0, targetTop)
|
||||
}
|
||||
|
||||
// 缩放控制
|
||||
function zoomIn() {
|
||||
zoom.value = Math.min(2, zoom.value + 0.2)
|
||||
centerOnLocation()
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
zoom.value = Math.max(0.5, zoom.value - 0.2)
|
||||
centerOnLocation()
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
zoom.value = 1
|
||||
centerOnLocation()
|
||||
}
|
||||
|
||||
// 触摸事件处理(用于拖动)
|
||||
function onTouchStart(e) {
|
||||
touchStartX.value = e.touches[0].clientX
|
||||
touchStartY.value = e.touches[0].clientY
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function onTouchMove(e) {
|
||||
const deltaX = e.touches[0].clientX - touchStartX.value
|
||||
const deltaY = e.touches[0].clientY - touchStartY.value
|
||||
|
||||
if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
|
||||
isDragging.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchEnd(e) {
|
||||
// 如果不是拖动,则视为点击
|
||||
// 这里的处理在 handleNodeClick 中完成
|
||||
}
|
||||
|
||||
// 滚动事件
|
||||
function onScroll(e) {
|
||||
// 可以在这里记录滚动位置
|
||||
}
|
||||
|
||||
// 获取节点样式
|
||||
function getNodeStyle(node) {
|
||||
return {
|
||||
left: node.x + 'px',
|
||||
top: node.y + 'px'
|
||||
left: (node.x * zoom.value) + 'px',
|
||||
top: (node.y * zoom.value) + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,14 +280,14 @@ function getLineStyle(edge) {
|
||||
|
||||
if (!fromNode || !toNode) return {}
|
||||
|
||||
const dx = toNode.x - fromNode.x
|
||||
const dy = toNode.y - fromNode.y
|
||||
const dx = (toNode.x - fromNode.x) * zoom.value
|
||||
const dy = (toNode.y - fromNode.y) * zoom.value
|
||||
const length = Math.sqrt(dx * dx + dy * dy)
|
||||
const angle = Math.atan2(dy, dx) * 180 / Math.PI
|
||||
|
||||
return {
|
||||
left: fromNode.x + 'px',
|
||||
top: fromNode.y + 'px',
|
||||
left: (fromNode.x * zoom.value) + 'px',
|
||||
top: (fromNode.y * zoom.value) + 'px',
|
||||
width: length + 'px',
|
||||
transform: `rotate(${angle}deg)`
|
||||
}
|
||||
@@ -252,6 +352,17 @@ onMounted(() => {
|
||||
border: 2rpx solid #4a5568;
|
||||
}
|
||||
|
||||
&__scroll {
|
||||
width: 100%;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
&__connections {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -276,6 +387,38 @@ onMounted(() => {
|
||||
border: 1rpx solid $accent;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
&__zoom-controls {
|
||||
position: absolute;
|
||||
bottom: 12rpx;
|
||||
right: 12rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
z-index: 20;
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-btn {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba($bg-primary, 0.8);
|
||||
border: 1rpx solid rgba($accent, 0.5);
|
||||
border-radius: 8rpx;
|
||||
backdrop-filter: blur(4rpx);
|
||||
|
||||
&:active {
|
||||
background-color: rgba($accent, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.zoom-icon {
|
||||
font-size: 32rpx;
|
||||
color: $accent;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.connection-line {
|
||||
|
||||
Reference in New Issue
Block a user