feat: 前端改为通过签名地址读取生成资源

This commit is contained in:
2026-04-21 16:45:05 +08:00
parent fcaf7bdb38
commit 78dcad1222
26 changed files with 779 additions and 76 deletions

View File

@@ -15,6 +15,7 @@ import {
import {HostileNpcAnimator} from '../HostileNpcAnimator';
import {MedievalNpcAnimator} from '../MedievalNpcAnimator';
import {getRenderableNpcFacing} from '../npcRenderUtils';
import {ResolvedAssetImage} from '../ResolvedAssetImage';
import {NpcAffinityEffectBadge} from './NpcAffinityEffectBadge';
import {
DialogueBubbleIcon,
@@ -408,7 +409,7 @@ export function GameCanvasEntityLayer({
<div className={ROLE_CHARACTER_FRAME_CLASS}>
{encounter.kind === 'treasure' ? (
<div className="flex h-20 w-20 items-center justify-center rounded-2xl border border-amber-400/30 bg-amber-500/15 shadow-[0_0_20px_rgba(255,255,255,0.12)]">
<img
<ResolvedAssetImage
src={encounter.npcAvatar || '/Icons/47_treasure.png'}
alt={encounter.npcName}
className="h-12 w-12 object-contain"

View File

@@ -1,5 +1,6 @@
import {AnimatePresence, motion} from 'motion/react';
import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl';
import {type ScenePresetInfo, WorldType} from '../../types';
import {CHROME_ICONS, getNineSliceStyle, UI_CHROME} from '../../uiAssets';
import {PixelIcon} from '../PixelIcon';
@@ -24,11 +25,19 @@ export function GameCanvasSceneLayer({
onSceneNameClick = null,
onBackgroundLoadError,
}: GameCanvasSceneLayerProps) {
const {
resolvedUrl: resolvedBackgroundSrc,
shouldResolve: shouldResolveBackground,
} = useResolvedAssetReadUrl(backgroundSrc);
// 签名地址未返回前先显示渐变底色,避免浏览器直接访问私有原图触发 403。
const displayBackgroundSrc =
resolvedBackgroundSrc || (!shouldResolveBackground ? backgroundSrc : '');
return (
<>
{!backgroundLoadFailed ? (
{!backgroundLoadFailed && displayBackgroundSrc ? (
<img
src={backgroundSrc}
src={displayBackgroundSrc}
alt={currentScenePreset?.name || 'Scene background'}
className="absolute inset-0 h-full w-full object-cover"
style={{imageRendering: 'pixelated'}}

View File

@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react';
import {getCharacterById} from '../../data/characterPresets';
import {METERS_TO_PIXELS} from '../../data/hostileNpcs';
import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl';
import {
buildMedievalNpcVisual,
buildMedievalNpcVisualFromCustomWorldVisual,
@@ -266,6 +267,15 @@ export function SceneEncounterNpcSprite({
facing: 'left' | 'right';
className?: string;
}) {
const rawEncounterImageSrc = encounter.imageSrc?.trim() ?? '';
const {
resolvedUrl: resolvedEncounterImageSrc,
shouldResolve: shouldResolveEncounterImage,
} = useResolvedAssetReadUrl(rawEncounterImageSrc);
const displayEncounterImageSrc =
resolvedEncounterImageSrc
|| (!shouldResolveEncounterImage ? rawEncounterImageSrc : '');
if (encounter.visual) {
return (
<MedievalNpcAnimator
@@ -277,10 +287,14 @@ export function SceneEncounterNpcSprite({
);
}
if (encounter.imageSrc?.trim()) {
if (rawEncounterImageSrc && shouldResolveEncounterImage && !displayEncounterImageSrc) {
return <div className={`h-full w-full ${className ?? ''}`.trim()} />;
}
if (displayEncounterImageSrc) {
return (
<img
src={encounter.imageSrc.trim()}
src={displayEncounterImageSrc}
alt={encounter.npcName}
className={`h-full w-full object-contain ${className ?? ''}`.trim()}
style={{