#[derive(Clone, Debug, Default)] pub(crate) struct SceneImagePromptProfile<'a> { pub name: &'a str, pub subtitle: &'a str, pub tone: &'a str, pub player_goal: &'a str, pub summary: &'a str, pub setting_text: &'a str, } #[derive(Clone, Debug, Default)] pub(crate) struct SceneImagePromptLandmark<'a> { pub name: &'a str, pub description: &'a str, } #[derive(Clone, Debug)] pub(crate) struct SceneImagePromptParams<'a> { pub profile: SceneImagePromptProfile<'a>, pub landmark: SceneImagePromptLandmark<'a>, pub user_prompt: &'a str, pub has_reference_image: bool, pub fallback_landmark_name: Option<&'a str>, pub fallback_world_name: &'a str, } #[derive(Clone, Debug)] pub(crate) struct SceneActBackgroundPromptParams<'a> { pub world_name: &'a str, pub world_tone: &'a str, pub scene_name: &'a str, pub title: &'a str, pub summary: &'a str, pub act_goal: &'a str, pub transition_hook: &'a str, pub primary_role_name: &'a str, pub support_role_names: Vec, pub prompt_text: &'a str, } pub(crate) const DEFAULT_CUSTOM_WORLD_SCENE_IMAGE_NEGATIVE_PROMPT: &str = "文字,水印,logo,UI界面,对话框,边框,人物近景特写,多人合照,模糊,低清晰度,畸形建筑,现代车辆,监控摄像头"; pub(crate) fn build_custom_world_scene_image_prompt(params: SceneImagePromptParams<'_>) -> String { let world_name = clamp_scene_image_text( if params.profile.name.trim().is_empty() { params.fallback_world_name } else { params.profile.name }, 18, ); let world_subtitle = clamp_scene_image_text(params.profile.subtitle, 18); let world_tone = clamp_scene_image_text(params.profile.tone, 48); let world_goal = clamp_scene_image_text(params.profile.player_goal, 48); let world_summary = clamp_scene_image_text(params.profile.summary, 72); let world_setting = clamp_scene_image_text(params.profile.setting_text, 72); let landmark_name = clamp_scene_image_text( if params.landmark.name.trim().is_empty() { params.fallback_landmark_name.unwrap_or("未命名场景") } else { params.landmark.name }, 18, ); let landmark_description = clamp_scene_image_text(params.landmark.description, 96); let requested_visual = clamp_scene_image_text(params.user_prompt, 120); vec![ "为横版 16:9 2D RPG 生成高完成度像素风场景背景,适合作为剧情探索与战斗底图。".to_string(), "画面构图必须严格按上下 1:1 分区:上半部分严格控制在整张图的 1/2 高度内,只描绘场景远景与中远景轮廓,不要让背景内容向下侵占超过半屏。".to_string(), "下半部分严格占据整张图的 1/2 高度,用于玩家角色站位与展示,必须是模拟 3D 游戏视角的地面近景,有明确的透视延伸和近大远小关系,不是平铺的 2D 侧视地面。".to_string(), "下半部分的内容必须是明确可站立的地面本体,例如道路、石板、平台、广场、甲板、沙地或草地,要有连续、稳定、可落脚的站位逻辑,不能只是装饰性前景、坑洞、障碍堆、栏杆带或不可通行的景物。".to_string(), "下半部分地面近景要保持相对简洁、低细节、轮廓清楚、便于角色站立,不要堆满道具、植被、碎石、栏杆或复杂装饰。".to_string(), if params.has_reference_image { "已提供一张自定义参考图,请沿用其构图、镜头或氛围线索,同时继续满足本次场景需求。".to_string() } else { String::new() }, format!( "世界:{}{}。", if world_name.is_empty() { "未命名世界" } else { world_name.as_str() }, if world_subtitle.is_empty() { String::new() } else { format!(",{world_subtitle}") } ), conditional_prompt_line("玩家设定", world_setting.as_str()), conditional_prompt_line("世界概述", world_summary.as_str()), conditional_prompt_line("整体基调", world_tone.as_str()), conditional_prompt_line("玩家目标关联", world_goal.as_str()), format!( "场景名称:{}。", if landmark_name.is_empty() { "未命名场景" } else { landmark_name.as_str() } ), conditional_prompt_line("场景描述", landmark_description.as_str()), conditional_prompt_line("本次想要生成的画面内容", requested_visual.as_str()), "不要出现 UI、字幕、文字、水印、logo 或装饰边框,人物仅可作为很小的远景剪影,画面重点放在场景本身,不要遮挡下半部分的角色展示区域。".to_string(), ] .into_iter() .filter(|line| !line.is_empty()) .collect::>() .join("") } pub(crate) fn build_scene_act_background_image_prompt( params: SceneActBackgroundPromptParams<'_>, ) -> String { // 幕背景图不是普通地点图,必须把世界、幕目标、过渡钩子和角色关系一起写入图像提示词, // 同时明确禁止角色立绘和 UI 元素进入背景资产。 [ format!("这是世界《{}》中的场景幕背景图。", params.world_name), format!("场景:{}", params.scene_name), format!("幕标题:{}", params.title), format!("幕摘要:{}", params.summary), format!("幕目标:{}", params.act_goal), format!("过渡钩子:{}", params.transition_hook), format!( "主角色:{}", if params.primary_role_name.trim().is_empty() { "待补主角色" } else { params.primary_role_name.trim() } ), if params.support_role_names.is_empty() { String::new() } else { format!("辅助角色:{}", params.support_role_names.join("、")) }, format!("世界气质:{}", params.world_tone), format!("背景描述:{}", params.prompt_text), "要求:只生成环境背景,不出现角色立绘、站位 UI、对白框、按钮或文字。".to_string(), ] .into_iter() .filter(|line| !line.trim().is_empty()) .collect::>() .join("\n") } fn clamp_scene_image_text(value: &str, max_length: usize) -> String { value .trim() .replace(char::is_whitespace, " ") .chars() .take(max_length) .collect::() .trim() .to_string() } fn conditional_prompt_line(prefix: &str, value: &str) -> String { if value.is_empty() { String::new() } else { format!("{prefix}:{value}。") } }