接入大鱼玩法运行态美术资产
This commit is contained in:
@@ -17,6 +17,9 @@
|
|||||||
7. 发布为可运行玩法
|
7. 发布为可运行玩法
|
||||||
8. 进入竖屏全屏实时玩法运行态
|
8. 进入竖屏全屏实时玩法运行态
|
||||||
|
|
||||||
|
补充说明:
|
||||||
|
当前工程实现中,运行态页面需要直接消费结果页已经生成的等级主图、动作图与场地背景图,不能只在结果页预览资产、进入玩法后退回圆点占位表现。
|
||||||
|
|
||||||
本稿必须满足两个硬要求:
|
本稿必须满足两个硬要求:
|
||||||
|
|
||||||
1. 不能沿用 RPG 创作链里的旧命名和旧数据口径,把实时吞噬玩法硬塞进 `rpg` 或旧 `customWorld` 语义里。
|
1. 不能沿用 RPG 创作链里的旧命名和旧数据口径,把实时吞噬玩法硬塞进 `rpg` 或旧 `customWorld` 语义里。
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { ArrowLeft, Loader2 } from 'lucide-react';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
BigFishAssetSlotResponse,
|
||||||
BigFishRuntimeEntityResponse,
|
BigFishRuntimeEntityResponse,
|
||||||
BigFishRuntimeSnapshotResponse,
|
BigFishRuntimeSnapshotResponse,
|
||||||
SubmitBigFishInputRequest,
|
SubmitBigFishInputRequest,
|
||||||
} from '../../../packages/shared/src/contracts/bigFish';
|
} from '../../../packages/shared/src/contracts/bigFish';
|
||||||
|
import { ResolvedAssetImage } from '../ResolvedAssetImage';
|
||||||
|
|
||||||
type BigFishRuntimeShellProps = {
|
type BigFishRuntimeShellProps = {
|
||||||
run: BigFishRuntimeSnapshotResponse | null;
|
run: BigFishRuntimeSnapshotResponse | null;
|
||||||
|
assetSlots?: BigFishAssetSlotResponse[];
|
||||||
isBusy?: boolean;
|
isBusy?: boolean;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
@@ -54,32 +57,87 @@ function projectEntity(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findBigFishAssetSlot(
|
||||||
|
slots: BigFishAssetSlotResponse[],
|
||||||
|
assetKind: string,
|
||||||
|
level?: number,
|
||||||
|
motionKey?: string,
|
||||||
|
) {
|
||||||
|
return slots.find((slot) => {
|
||||||
|
if (slot.assetKind !== assetKind || slot.status !== 'ready') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (level !== undefined && slot.level !== level) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (motionKey !== undefined && slot.motionKey !== motionKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRuntimeEntityAsset(
|
||||||
|
entity: BigFishRuntimeEntityResponse,
|
||||||
|
assetSlots: BigFishAssetSlotResponse[],
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
findBigFishAssetSlot(assetSlots, 'level_motion', entity.level, 'move_swim') ??
|
||||||
|
findBigFishAssetSlot(assetSlots, 'level_motion', entity.level, 'idle_float') ??
|
||||||
|
findBigFishAssetSlot(assetSlots, 'level_main_image', entity.level)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function BigFishEntityDot({
|
function BigFishEntityDot({
|
||||||
entity,
|
entity,
|
||||||
run,
|
run,
|
||||||
owned,
|
owned,
|
||||||
|
assetSlots,
|
||||||
}: {
|
}: {
|
||||||
entity: BigFishRuntimeEntityResponse;
|
entity: BigFishRuntimeEntityResponse;
|
||||||
run: BigFishRuntimeSnapshotResponse;
|
run: BigFishRuntimeSnapshotResponse;
|
||||||
owned: boolean;
|
owned: boolean;
|
||||||
|
assetSlots: BigFishAssetSlotResponse[];
|
||||||
}) {
|
}) {
|
||||||
const projected = projectEntity(entity, run);
|
const projected = projectEntity(entity, run);
|
||||||
const isLeader = run.leaderEntityId === entity.entityId;
|
const isLeader = run.leaderEntityId === entity.entityId;
|
||||||
|
const assetSlot = resolveRuntimeEntityAsset(entity, assetSlots);
|
||||||
|
const entityImageSrc = assetSlot?.assetUrl?.trim() || null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute -translate-x-1/2 -translate-y-1/2 rounded-full border shadow-lg transition-all ${
|
className={`absolute -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-full border shadow-lg transition-all ${
|
||||||
owned
|
entityImageSrc
|
||||||
? isLeader
|
? owned
|
||||||
? 'border-cyan-100 bg-cyan-300 shadow-cyan-950/30'
|
? isLeader
|
||||||
: 'border-cyan-100/70 bg-cyan-500/88 shadow-cyan-950/24'
|
? 'border-cyan-50 shadow-cyan-950/40'
|
||||||
: entity.level > run.playerLevel
|
: 'border-cyan-100/80 shadow-cyan-950/28'
|
||||||
? 'border-rose-100/70 bg-rose-500/88 shadow-rose-950/24'
|
: entity.level > run.playerLevel
|
||||||
: 'border-emerald-100/70 bg-emerald-400/88 shadow-emerald-950/20'
|
? 'border-rose-100/80 shadow-rose-950/28'
|
||||||
|
: 'border-emerald-100/80 shadow-emerald-950/24'
|
||||||
|
: owned
|
||||||
|
? isLeader
|
||||||
|
? 'border-cyan-100 bg-cyan-300 shadow-cyan-950/30'
|
||||||
|
: 'border-cyan-100/70 bg-cyan-500/88 shadow-cyan-950/24'
|
||||||
|
: entity.level > run.playerLevel
|
||||||
|
? 'border-rose-100/70 bg-rose-500/88 shadow-rose-950/24'
|
||||||
|
: 'border-emerald-100/70 bg-emerald-400/88 shadow-emerald-950/20'
|
||||||
}`}
|
}`}
|
||||||
style={projected}
|
style={projected}
|
||||||
>
|
>
|
||||||
<span className="absolute inset-0 flex items-center justify-center text-[0.62rem] font-black text-slate-950">
|
{entityImageSrc ? (
|
||||||
|
<>
|
||||||
|
<ResolvedAssetImage
|
||||||
|
src={entityImageSrc}
|
||||||
|
alt={`Lv.${entity.level} 实体`}
|
||||||
|
className={`h-full w-full object-cover ${
|
||||||
|
owned && isLeader ? 'scale-110' : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,transparent_32%,rgba(2,6,23,0.18)_72%,rgba(2,6,23,0.36)_100%)]" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<span className="absolute inset-0 flex items-center justify-center text-[0.62rem] font-black text-white [text-shadow:0_1px_2px_rgba(2,6,23,0.9)]">
|
||||||
{entity.level}
|
{entity.level}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,6 +146,7 @@ function BigFishEntityDot({
|
|||||||
|
|
||||||
export function BigFishRuntimeShell({
|
export function BigFishRuntimeShell({
|
||||||
run,
|
run,
|
||||||
|
assetSlots = [],
|
||||||
isBusy = false,
|
isBusy = false,
|
||||||
error = null,
|
error = null,
|
||||||
onBack,
|
onBack,
|
||||||
@@ -142,10 +201,20 @@ export function BigFishRuntimeShell({
|
|||||||
|
|
||||||
const statusLabel =
|
const statusLabel =
|
||||||
run.status === 'won' ? '通关' : run.status === 'failed' ? '失败' : '进行中';
|
run.status === 'won' ? '通关' : run.status === 'failed' ? '失败' : '进行中';
|
||||||
|
const backgroundAsset =
|
||||||
|
findBigFishAssetSlot(assetSlots, 'stage_background')?.assetUrl?.trim() || null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[100] flex justify-center bg-slate-950 text-white">
|
<div className="fixed inset-0 z-[100] flex justify-center bg-slate-950 text-white">
|
||||||
<div className="relative h-full w-full max-w-[430px] overflow-hidden bg-[radial-gradient(circle_at_50%_20%,rgba(34,211,238,0.2),transparent_28%),radial-gradient(circle_at_20%_80%,rgba(16,185,129,0.18),transparent_26%),linear-gradient(180deg,#082f49,#020617)]">
|
<div className="relative h-full w-full max-w-[430px] overflow-hidden bg-[radial-gradient(circle_at_50%_20%,rgba(34,211,238,0.2),transparent_28%),radial-gradient(circle_at_20%_80%,rgba(16,185,129,0.18),transparent_26%),linear-gradient(180deg,#082f49,#020617)]">
|
||||||
|
{backgroundAsset ? (
|
||||||
|
<ResolvedAssetImage
|
||||||
|
src={backgroundAsset}
|
||||||
|
alt="大鱼吃小鱼场地背景"
|
||||||
|
className="absolute inset-0 h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(8,47,73,0.2),rgba(2,6,23,0.6))]" />
|
||||||
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:32px_32px] opacity-30" />
|
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[length:32px_32px] opacity-30" />
|
||||||
|
|
||||||
<div className="absolute left-0 top-0 z-20 flex w-full items-center justify-between px-4 py-4">
|
<div className="absolute left-0 top-0 z-20 flex w-full items-center justify-between px-4 py-4">
|
||||||
@@ -168,6 +237,7 @@ export function BigFishRuntimeShell({
|
|||||||
entity={entity}
|
entity={entity}
|
||||||
run={run}
|
run={run}
|
||||||
owned={false}
|
owned={false}
|
||||||
|
assetSlots={assetSlots}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{run.ownedEntities.map((entity) => (
|
{run.ownedEntities.map((entity) => (
|
||||||
@@ -176,6 +246,7 @@ export function BigFishRuntimeShell({
|
|||||||
entity={entity}
|
entity={entity}
|
||||||
run={run}
|
run={run}
|
||||||
owned
|
owned
|
||||||
|
assetSlots={assetSlots}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1791,6 +1791,7 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
>
|
>
|
||||||
<BigFishRuntimeShell
|
<BigFishRuntimeShell
|
||||||
run={bigFishRun}
|
run={bigFishRun}
|
||||||
|
assetSlots={bigFishSession?.assetSlots ?? []}
|
||||||
isBusy={isBigFishBusy}
|
isBusy={isBigFishBusy}
|
||||||
error={bigFishError}
|
error={bigFishError}
|
||||||
onBack={() => {
|
onBack={() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user