refactor(common prompt,hero box): 优化弹窗绑定逻辑与英雄出售限制

1. 重构CommonPrompt的编辑器属性绑定,添加按钮节点序列化引用并优化兜底查找逻辑
2. 为英雄出售操作添加存活数量校验,场上仅1个英雄时禁止出售
3. 新增统一的存活英雄统计工具方法
4. 补充Claude工具命令配置
This commit is contained in:
panFD
2026-08-02 09:42:30 +08:00
parent 53fa0d7dc0
commit 401a5daf77
6 changed files with 7914 additions and 1745 deletions

View File

@@ -16,7 +16,9 @@
"Bash(git add *)",
"Bash(git commit *)",
"Bash(npm install *)",
"Bash(npx tsc *)"
"Bash(npx tsc *)",
"Bash(d:/game/pixelheros/extensions/pixelhero-config-editor/node_modules/.bin/tsx --version)",
"Bash(d:/game/pixelheros/extensions/pixelhero-config-editor/node_modules/.bin/tsx --tsconfig scripts/tsconfig.eval.json scripts/eval-heroes.ts --lv=1 --base=5011)"
],
"deny": [
"Bash(rm -rf *)",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -59,40 +59,47 @@ export interface WindowPromptParams {
@ccclass("CommonPrompt")
export class CommonPrompt extends Component {
/** 窗口标题 */
@property(Label)
@property({ type: Label, displayName: "标题文本" })
private lab_title: Label | null = null;
/** 窗口内容(富文本,需在 prefab 中将 Label 换为 RichText 后重新拖入) */
@property(RichText)
@property({ type: RichText, displayName: "内容富文本" })
private lab_content: RichText | null = null;
/** 确定按钮文本 */
@property(Label)
@property({ type: Label, displayName: "确定按钮文本" })
private lab_ok: Label | null = null;
/** 取消按钮文本 */
@property(Label)
@property({ type: Label, displayName: "取消按钮文本" })
private lab_cancel: Label | null = null;
/** 确定按钮节点 */
@property({ type: Node, displayName: "确定按钮" })
private btn_ok: Node | null = null;
/** 取消按钮节点 */
@property({ type: Node, displayName: "取消按钮" })
private btn_cancel: Node | null = null;
/** 运行时参数 */
private config: WindowPromptParams = {};
/** 弹窗打开动画已播放标记(防止 onAdded 重复触发) */
private animPlayed: boolean = false;
/** 代码动态绑定的确定按钮prefab clickEvents 失效时的兜底) */
private btnOk: Node | null = null;
/** 代码动态绑定的取消按钮 */
private btnCancel: Node | null = null;
onLoad() {
// 兜底prefab 中 clickEvents 引用的旧插件脚本被移除后,代码动态绑定保证按钮可用
this.btnOk = this.node.getChildByName("Node")?.getChildByName("btn_ok") ?? null;
this.btnCancel = this.node.getChildByName("Node")?.getChildByName("btn_cancel") ?? null;
this.btnOk?.on(Button.EventType.CLICK, this.onOk, this);
this.btnCancel?.on(Button.EventType.CLICK, this.onCancel, this);
// 优先使用编辑器绑定的按钮节点;未绑定时按节点名查找兜底
if (!this.btn_ok) {
this.btn_ok = this.node.getChildByName("Node")?.getChildByName("btn_ok") ?? null;
}
if (!this.btn_cancel) {
this.btn_cancel = this.node.getChildByName("Node")?.getChildByName("btn_cancel") ?? null;
}
this.btn_ok?.on(Button.EventType.CLICK, this.onOk, this);
this.btn_cancel?.on(Button.EventType.CLICK, this.onCancel, this);
// 遮罩点击关闭btn_sky 为 btn_ok 内嵌子节点,无 Button 组件,直接监听触摸事件)
this.btnOk?.getChildByName("btn_sky")?.on(NodeEventType.TOUCH_END, this.onMaskClick, this);
this.btn_ok?.getChildByName("btn_sky")?.on(NodeEventType.TOUCH_END, this.onMaskClick, this);
// 序列化属性槽引用的是旧插件 LanguageLabel 组件实例,脚本替换后已失效;
// 这里按节点名查找 Label/RichText 作为兜底,保证文字一定写入
@@ -100,10 +107,10 @@ export class CommonPrompt extends Component {
this.lab_title = this.node.getChildByName("lab_title")?.getComponent(Label) ?? null;
}
if (!this.lab_ok) {
this.lab_ok = this.btnOk?.getChildByName("lab_ok")?.getComponent(Label) ?? null;
this.lab_ok = this.btn_ok?.getChildByName("lab_ok")?.getComponent(Label) ?? null;
}
if (!this.lab_cancel) {
this.lab_cancel = this.btnCancel?.getChildByName("lab_cancel")?.getComponent(Label) ?? null;
this.lab_cancel = this.btn_cancel?.getChildByName("lab_cancel")?.getComponent(Label) ?? null;
}
if (!this.lab_content) {
this.lab_content = this.node.getChildByName("lab_content")?.getComponent(RichText) ?? null;
@@ -127,8 +134,8 @@ export class CommonPrompt extends Component {
}
onDestroy() {
this.btnOk?.off(Button.EventType.CLICK, this.onOk, this);
this.btnCancel?.off(Button.EventType.CLICK, this.onCancel, this);
this.btn_ok?.off(Button.EventType.CLICK, this.onOk, this);
this.btn_cancel?.off(Button.EventType.CLICK, this.onCancel, this);
Tween.stopAllByTarget(this.node);
this.config = {};
}

View File

@@ -20,7 +20,7 @@ import { GameEvent } from "../common/config/GameEvent";
import { HeroInfo } from "../common/config/heroSet";
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { Hero } from "../hero/Hero";
import { getLvColor } from "../common/config/GameSet";
import { FacSet, getLvColor } from "../common/config/GameSet";
import { buildSkillDescRich } from "../common/config/HeroSkillDesc";
import { MissionEconomy } from "./MissionEconomy";
import { UIID } from "../common/config/GameUIConfig";
@@ -189,6 +189,11 @@ export class HeroBoxComp extends CCComp {
*/
private onSellHeroClick() {
if (!this.eid || !this.model) return;
// 场上仅剩 1 个英雄时不允许出售(至少保留一个作战单位)
if (HeroBoxComp.getAliveHeroCount() <= 1) {
oops.gui.toast("至少保留 1 个英雄");
return;
}
oops.audio.playEffect("music/button");
const heroName = this.model.hero_name ?? "";
@@ -453,6 +458,20 @@ export class HeroBoxComp extends CCComp {
// ======================== 内部工具 ========================
/**
* 统计场上存活英雄数量(与 MissionCardComp.getAliveHeroCount 口径一致)。
*/
private static getAliveHeroCount(): number {
let count = 0;
ecs.query(ecs.allOf(HeroAttrsComp)).forEach((entity: ecs.Entity) => {
const model = entity.get(HeroAttrsComp);
if (model && model.fac === FacSet.HERO) {
count++;
}
});
return count;
}
/**
* 写入一行战斗信息(总值 + 附加值)。
* 总值始终显示0 不隐藏);附加值为 0 时隐藏,正/负分别显示 (+x) / (x)。