feat(card): 新增卡牌系统核心组件与配置
- 新增 CardComp 组件用于卡牌视图展示 - 新增 CardSet 配置文件,包含卡牌类型、种类枚举和完整卡池配置 - 重构 HSkillComp 组件,优化技能调试面板布局和交互逻辑 - 更新 MissionCardComp 组件,移除旧卡牌类型依赖 - 调整 GameSet 配置文件,移除 CardType 和 CardKind 枚举 - 更新卡牌预制体结构,优化 UI 布局和组件绑定 - 新增特殊卡牌效果系统,支持抽英雄和重复使用等特殊能力 - 实现卡牌按权重抽取算法和卡池等级管理机制
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
163
assets/script/game/common/config/CardSet.ts
Normal file
163
assets/script/game/common/config/CardSet.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/** 卡牌大类定义 */
|
||||||
|
export enum CardType {
|
||||||
|
Hero = 1,
|
||||||
|
Skill = 2,
|
||||||
|
Potion = 3,
|
||||||
|
Special = 4,
|
||||||
|
Buff = 5,
|
||||||
|
Debuff = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卡池等级定义 */
|
||||||
|
export enum CardKind {
|
||||||
|
LV1 = 1,
|
||||||
|
LV2 = 2,
|
||||||
|
LV3 = 3,
|
||||||
|
LV4 = 4,
|
||||||
|
LV5 = 5,
|
||||||
|
LV6 = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用卡牌配置 */
|
||||||
|
export interface CardConfig {
|
||||||
|
uuid: number
|
||||||
|
type: CardType
|
||||||
|
cost: number
|
||||||
|
weight: number
|
||||||
|
lv: CardKind
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 特殊卡效果类型 */
|
||||||
|
export enum SpecialEffectType {
|
||||||
|
DrawHero = 1,
|
||||||
|
RepeatNextUse = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 特殊卡效果参数 */
|
||||||
|
export interface SpecialCardEffect {
|
||||||
|
type: SpecialEffectType
|
||||||
|
drawHeroCount?: number
|
||||||
|
drawHeroLv?: CardKind
|
||||||
|
repeatNextUseTimes?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 特殊卡完整配置 */
|
||||||
|
export interface SpecialCardConfig extends CardConfig {
|
||||||
|
effect: SpecialCardEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卡池默认初始等级 */
|
||||||
|
export const CARD_POOL_INIT_LEVEL = CardKind.LV1
|
||||||
|
/** 卡池等级上限 */
|
||||||
|
export const CARD_POOL_MAX_LEVEL = CardKind.LV6
|
||||||
|
|
||||||
|
/** 基础卡池(英雄、技能、Buff、Debuff) */
|
||||||
|
export const CardPoolList: CardConfig[] = [
|
||||||
|
{ uuid: 5001, type: CardType.Hero, cost: 3, weight: 20, lv: 1 },
|
||||||
|
{ uuid: 5003, type: CardType.Hero, cost: 3, weight: 20, lv: 1 },
|
||||||
|
{ uuid: 5002, type: CardType.Hero, cost: 3, weight: 25, lv: 2 },
|
||||||
|
{ uuid: 5005, type: CardType.Hero, cost: 3, weight: 25, lv: 2 },
|
||||||
|
{ uuid: 5004, type: CardType.Hero, cost: 3, weight: 30, lv: 3 },
|
||||||
|
{ uuid: 5006, type: CardType.Hero, cost: 3, weight: 35, lv: 4 },
|
||||||
|
{ uuid: 5007, type: CardType.Hero, cost: 3, weight: 40, lv: 5 },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ uuid: 6001, type: CardType.Skill, cost: 1, weight: 20, lv: 1 },
|
||||||
|
{ uuid: 6002, type: CardType.Skill, cost: 1, weight: 20, lv: 1 },
|
||||||
|
{ uuid: 6003, type: CardType.Skill, cost: 2, weight: 25, lv: 2 },
|
||||||
|
{ uuid: 6100, type: CardType.Skill, cost: 4, weight: 25, lv: 2 },
|
||||||
|
{ uuid: 6004, type: CardType.Skill, cost: 3, weight: 30, lv: 3 },
|
||||||
|
{ uuid: 6102, type: CardType.Skill, cost: 4, weight: 35, lv: 4 },
|
||||||
|
{ uuid: 6101, type: CardType.Skill, cost: 5, weight: 40, lv: 5 },
|
||||||
|
{ uuid: 6103, type: CardType.Skill, cost: 6, weight: 45, lv: 6 },
|
||||||
|
|
||||||
|
{ uuid: 10001, type: CardType.Buff, cost: 2, weight: 30, lv: 1 },
|
||||||
|
{ uuid: 10101, type: CardType.Buff, cost: 3, weight: 26, lv: 2 },
|
||||||
|
{ uuid: 10011, type: CardType.Buff, cost: 3, weight: 24, lv: 3 },
|
||||||
|
{ uuid: 10311, type: CardType.Buff, cost: 4, weight: 20, lv: 4 },
|
||||||
|
{ uuid: 10302, type: CardType.Buff, cost: 5, weight: 18, lv: 5 },
|
||||||
|
|
||||||
|
{ uuid: 10201, type: CardType.Debuff, cost: 3, weight: 24, lv: 2 },
|
||||||
|
{ uuid: 10211, type: CardType.Debuff, cost: 4, weight: 20, lv: 3 },
|
||||||
|
{ uuid: 10312, type: CardType.Debuff, cost: 4, weight: 18, lv: 4 },
|
||||||
|
{ uuid: 20001, type: CardType.Debuff, cost: 5, weight: 14, lv: 5 },
|
||||||
|
{ uuid: 20011, type: CardType.Debuff, cost: 6, weight: 12, lv: 6 },
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 特殊卡定义表 */
|
||||||
|
export const SpecialCardList: Record<number, SpecialCardConfig> = {
|
||||||
|
7001: {
|
||||||
|
uuid: 7001,
|
||||||
|
type: CardType.Special,
|
||||||
|
cost: 6,
|
||||||
|
weight: 20,
|
||||||
|
lv: CardKind.LV3,
|
||||||
|
effect: {
|
||||||
|
type: SpecialEffectType.DrawHero,
|
||||||
|
drawHeroCount: 4,
|
||||||
|
drawHeroLv: CardKind.LV3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
7002: {
|
||||||
|
uuid: 7002,
|
||||||
|
type: CardType.Special,
|
||||||
|
cost: 5,
|
||||||
|
weight: 20,
|
||||||
|
lv: CardKind.LV4,
|
||||||
|
effect: {
|
||||||
|
type: SpecialEffectType.RepeatNextUse,
|
||||||
|
repeatNextUseTimes: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** 规范等级到合法区间 [LV1, LV6] */
|
||||||
|
const clampCardLv = (lv: number): CardKind => {
|
||||||
|
const value = Math.floor(lv)
|
||||||
|
if (value < CARD_POOL_INIT_LEVEL) return CARD_POOL_INIT_LEVEL
|
||||||
|
if (value > CARD_POOL_MAX_LEVEL) return CARD_POOL_MAX_LEVEL
|
||||||
|
return value as CardKind
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单次按权重抽取一张卡 */
|
||||||
|
const weightedPick = (cards: CardConfig[]): CardConfig | null => {
|
||||||
|
if (cards.length === 0) return null
|
||||||
|
const totalWeight = cards.reduce((total, card) => total + card.weight, 0)
|
||||||
|
let random = Math.random() * totalWeight
|
||||||
|
for (const card of cards) {
|
||||||
|
random -= card.weight
|
||||||
|
if (random <= 0) return card
|
||||||
|
}
|
||||||
|
return cards[cards.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 连续抽取 count 张卡,允许重复 */
|
||||||
|
const pickCards = (cards: CardConfig[], count: number): CardConfig[] => {
|
||||||
|
if (cards.length === 0 || count <= 0) return []
|
||||||
|
const selected: CardConfig[] = []
|
||||||
|
while (selected.length < count) {
|
||||||
|
const pick = weightedPick(cards)
|
||||||
|
if (!pick) break
|
||||||
|
selected.push(pick)
|
||||||
|
}
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取指定等级可出现的基础卡池(英雄+技能) */
|
||||||
|
export const getCardPoolByLv = (lv: number): CardConfig[] => {
|
||||||
|
const cardLv = clampCardLv(lv)
|
||||||
|
return CardPoolList.filter(card => card.lv <= cardLv)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 常规发牌:前 2 英雄 + 后 2 其他 */
|
||||||
|
export const getCardsByLv = (lv: number): CardConfig[] => {
|
||||||
|
const pool = getCardPoolByLv(lv)
|
||||||
|
const heroPool = pool.filter(card => card.type === CardType.Hero)
|
||||||
|
const otherPool = pool.filter(card => card.type !== CardType.Hero)
|
||||||
|
const heroes = pickCards(heroPool, 2)
|
||||||
|
const others = pickCards(otherPool, 2)
|
||||||
|
return [...heroes, ...others]
|
||||||
|
}
|
||||||
@@ -22,24 +22,6 @@ export enum BoxSet {
|
|||||||
//攻击距离
|
//攻击距离
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CardType {
|
|
||||||
Talent = 1,
|
|
||||||
Skill = 2,
|
|
||||||
Potion = 3,
|
|
||||||
Partner = 4,
|
|
||||||
Attr = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CardKind {
|
|
||||||
Atk = 1,
|
|
||||||
Atted = 2,
|
|
||||||
Buff = 3,
|
|
||||||
Attr = 4,
|
|
||||||
Skill = 5,
|
|
||||||
Hp = 6,
|
|
||||||
Dead = 7,
|
|
||||||
Partner = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export enum FacSet {
|
export enum FacSet {
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export const SkillSet: Record<number, SkillConfig> = {
|
|||||||
ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd,
|
ready:0,EAnm:0,DAnm:9001,RType:RType.fixed,EType:EType.animationEnd,
|
||||||
buffs:[],debuffs:[],info:"对前方目标造成150%攻击的伤害",
|
buffs:[],debuffs:[],info:"对前方目标造成150%攻击的伤害",
|
||||||
},
|
},
|
||||||
// ========== 基础buff ========== 6100-6199
|
//============================= ====== 基础buff ====== ========================== 6100-6199
|
||||||
6100: {
|
6100: {
|
||||||
uuid:6100,name:"治疗",sp_name:"buff_wind",icon:"1292",TGroup:TGroup.Self,TType:TType.LowestHP,act:"atk",DTType:DTType.single,
|
uuid:6100,name:"治疗",sp_name:"buff_wind",icon:"1292",TGroup:TGroup.Self,TType:TType.LowestHP,act:"atk",DTType:DTType.single,
|
||||||
ap:30,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,
|
ap:30,hit_num:1,hit:1,hitcd:0.2,speed:720,with:0,
|
||||||
|
|||||||
103
assets/script/game/map/CardComp.ts
Normal file
103
assets/script/game/map/CardComp.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { mLogger } from "../common/Logger";
|
||||||
|
import { _decorator, Label, Node, tween, Vec3, Color, Sprite, Tween, SpriteAtlas, resources } from "cc";
|
||||||
|
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||||||
|
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
|
||||||
|
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
|
import { CardType } from "../common/config/CardSet";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
interface ICardEvent {
|
||||||
|
type?: CardType;
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 视图层对象 */
|
||||||
|
@ccclass('CardComp')
|
||||||
|
@ecs.register('CardComp', false)
|
||||||
|
export class CardComp extends CCComp {
|
||||||
|
private debugMode: boolean = true;
|
||||||
|
/** 视图层逻辑代码分离演示 */
|
||||||
|
@property(Node)
|
||||||
|
Lock: Node = null!
|
||||||
|
@property(Node)
|
||||||
|
unLock: Node = null!
|
||||||
|
@property(Node)
|
||||||
|
ap_node=null!
|
||||||
|
@property(Node)
|
||||||
|
hp_node=null!
|
||||||
|
@property(Node)
|
||||||
|
name_node=null!
|
||||||
|
@property(Node)
|
||||||
|
icon_node=null!
|
||||||
|
@property(Node)
|
||||||
|
cost_node=null!
|
||||||
|
|
||||||
|
card_cost:number=0
|
||||||
|
card_type:CardType=CardType.Hero
|
||||||
|
card_uuid:number=0
|
||||||
|
// 是否处于锁定状态
|
||||||
|
private isLocked: boolean = true;
|
||||||
|
// 图标图集缓存
|
||||||
|
private uiconsAtlas: SpriteAtlas | null = null;
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
init(){
|
||||||
|
this.onMissionStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 游戏开始初始化 */
|
||||||
|
onMissionStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 游戏结束清理 */
|
||||||
|
onMissionEnd() {
|
||||||
|
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
// 初始隐藏或显示逻辑
|
||||||
|
this.node.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCardInfo(card:Node, data: any){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateIcon(node: Node, iconId: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCardData(index: number, data: any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCard(e: any, index: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭界面
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||||
|
reset() {
|
||||||
|
this.node.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
1
assets/script/game/map/CardComp.ts.meta
Normal file
1
assets/script/game/map/CardComp.ts.meta
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"c4842465-1171-41b1-85ab-66922e63d734","files":[],"subMetas":{},"userData":{}}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import { _decorator, Animation, AnimationClip, Component, instantiate, Label, Node, Prefab, resources, Sprite, SpriteFrame, v3, tween, Vec3, ProgressBar, SpriteAtlas } from 'cc';
|
import { _decorator, instantiate, Label, Node, Prefab, UITransform, v3, Vec3 } from 'cc';
|
||||||
import { oops } from 'db://oops-framework/core/Oops';
|
|
||||||
import { getHeroList, getPreAttr, HeroConf, HeroInfo, HType, HTypeName } from '../common/config/heroSet';
|
|
||||||
import { smc } from '../common/SingletonModuleComp';
|
|
||||||
import { GameEvent } from '../common/config/GameEvent';
|
|
||||||
import { CCComp } from 'db://oops-framework/module/common/CCComp';
|
import { CCComp } from 'db://oops-framework/module/common/CCComp';
|
||||||
import { ecs } from 'db://oops-framework/libs/ecs/ECS';
|
import { ecs } from 'db://oops-framework/libs/ecs/ECS';
|
||||||
import { SkillSet } from '../common/config/SkillSet';
|
import { SkillSet } from '../common/config/SkillSet';
|
||||||
|
import { BoxSet, FacSet } from '../common/config/GameSet';
|
||||||
|
import { smc } from '../common/SingletonModuleComp';
|
||||||
|
import { Skill } from '../skill/Skill';
|
||||||
import { mLogger } from '../common/Logger';
|
import { mLogger } from '../common/Logger';
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
@@ -13,46 +12,185 @@ const { ccclass, property } = _decorator;
|
|||||||
@ecs.register('HSkillComp', false)
|
@ecs.register('HSkillComp', false)
|
||||||
export class HSkillComp extends CCComp {
|
export class HSkillComp extends CCComp {
|
||||||
debugMode: boolean = false;
|
debugMode: boolean = false;
|
||||||
|
private readonly panelName: string = 'skill_debug_panel';
|
||||||
|
private readonly panelWidth: number = 680;
|
||||||
|
private readonly panelHeight: number = 1180;
|
||||||
|
private readonly colCount: number = 4;
|
||||||
|
private readonly cellWidth: number = 160;
|
||||||
|
private readonly cellHeight: number = 68;
|
||||||
|
private readonly startX: number = -240;
|
||||||
|
private readonly startY: number = 560;
|
||||||
|
@property(Prefab)
|
||||||
|
btnPrefab: Prefab | null = null;
|
||||||
|
|
||||||
h_uuid:number=0
|
private panelNode: Node | null = null;
|
||||||
private uiconsAtlas: SpriteAtlas | null = null;
|
private buttonNodes: Node[] = [];
|
||||||
|
private mockCasterNode: Node | null = null;
|
||||||
|
private mockCasterView: any = null;
|
||||||
|
private currentSkill: Skill | null = null;
|
||||||
|
|
||||||
protected onLoad(): void {
|
protected onLoad(): void {
|
||||||
|
this.ensurePanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
this.renderSkillButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
start_test(){
|
start_test() {
|
||||||
this.node.active=true
|
this.node.active = true;
|
||||||
this.node.parent.getChildByName("mission_home").active=false
|
const home = this.node.parent?.getChildByName('mission_home');
|
||||||
|
if (home) {
|
||||||
|
home.active = false;
|
||||||
|
}
|
||||||
|
this.renderSkillButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
end_test(){
|
end_test() {
|
||||||
this.node.parent.getChildByName("mission_home").active=true
|
const home = this.node.parent?.getChildByName('mission_home');
|
||||||
this.node.active=false
|
if (home) {
|
||||||
|
home.active = true;
|
||||||
|
}
|
||||||
|
this.node.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime: number) {
|
update_data(uuid: number) {
|
||||||
|
this.renderSkillButtons();
|
||||||
}
|
|
||||||
update_data(uuid:number){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
load_hui(uuid:number){
|
private ensurePanel() {
|
||||||
var path = "game/gui/hui";
|
let panel = this.node.getChildByName(this.panelName);
|
||||||
var prefab: Prefab = oops.res.get(path, Prefab)!;
|
if (!panel) {
|
||||||
var node = instantiate(prefab);
|
panel = new Node(this.panelName);
|
||||||
// 将节点添加到父节点下
|
panel.parent = this.node;
|
||||||
this.node.addChild(node);
|
const transform = panel.addComponent(UITransform);
|
||||||
// 设置节点位置
|
transform.setContentSize(this.panelWidth, this.panelHeight);
|
||||||
|
panel.setPosition(0, 640, 0);
|
||||||
|
}
|
||||||
|
this.panelNode = panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderSkillButtons() {
|
||||||
|
this.ensurePanel();
|
||||||
|
const prefab = this.getBtnPrefab();
|
||||||
|
if (!prefab || !this.panelNode || !this.panelNode.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clearButtons();
|
||||||
|
const skillIds = Object.keys(SkillSet).map(Number).sort((a, b) => a - b);
|
||||||
|
skillIds.forEach((skillId, index) => {
|
||||||
|
const btnNode = instantiate(prefab);
|
||||||
|
btnNode.parent = this.panelNode;
|
||||||
|
const row = Math.floor(index / this.colCount);
|
||||||
|
const col = index % this.colCount;
|
||||||
|
btnNode.setPosition(
|
||||||
|
this.startX + col * this.cellWidth,
|
||||||
|
this.startY - row * this.cellHeight,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const label = btnNode.getChildByName('Label')?.getComponent(Label);
|
||||||
|
if (label) {
|
||||||
|
const conf = SkillSet[skillId];
|
||||||
|
label.string = `${skillId} ${conf?.name ?? ''}`;
|
||||||
|
}
|
||||||
|
btnNode.on(Node.EventType.TOUCH_END, () => this.playDebugSkill(skillId), this);
|
||||||
|
this.buttonNodes.push(btnNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearButtons() {
|
||||||
|
this.buttonNodes.forEach(node => {
|
||||||
|
if (!node || !node.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.off(Node.EventType.TOUCH_END);
|
||||||
|
node.destroy();
|
||||||
|
});
|
||||||
|
this.buttonNodes.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBtnPrefab(): Prefab | null {
|
||||||
|
if (this.btnPrefab) {
|
||||||
|
return this.btnPrefab;
|
||||||
|
}
|
||||||
|
mLogger.error(this.debugMode, 'HSkillComp', '[HSkillComp] 未绑定 Btn 预制体,请在编辑器中设置 btnPrefab');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSkillParent(): Node {
|
||||||
|
const layer = smc.map?.MapView?.scene?.entityLayer?.node?.getChildByName('SKILL');
|
||||||
|
if (layer && layer.isValid) {
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
return this.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureMockCaster(parent: Node, startPos: Vec3): any {
|
||||||
|
if (!this.mockCasterNode || !this.mockCasterNode.isValid) {
|
||||||
|
this.mockCasterNode = new Node('debug_caster');
|
||||||
|
this.mockCasterNode.parent = parent;
|
||||||
|
this.mockCasterNode.setScale(v3(1, 1, 1));
|
||||||
|
this.mockCasterNode.active = false;
|
||||||
|
} else if (this.mockCasterNode.parent !== parent) {
|
||||||
|
this.mockCasterNode.parent = parent;
|
||||||
|
}
|
||||||
|
this.mockCasterNode.setPosition(startPos);
|
||||||
|
const mockAttrs = {
|
||||||
|
hero_name: '技能调试器',
|
||||||
|
ap: 100,
|
||||||
|
critical: 0,
|
||||||
|
critical_dmg: 50,
|
||||||
|
freeze_chance: 0,
|
||||||
|
stun_chance: 0,
|
||||||
|
back_chance: 0,
|
||||||
|
slow_chance: 0,
|
||||||
|
puncture: 0,
|
||||||
|
puncture_dmg: 0,
|
||||||
|
wfuny: 0,
|
||||||
|
fac: FacSet.HERO
|
||||||
|
};
|
||||||
|
const mockEntity = {
|
||||||
|
eid: -10086,
|
||||||
|
get: () => mockAttrs
|
||||||
|
};
|
||||||
|
this.mockCasterView = {
|
||||||
|
node: this.mockCasterNode,
|
||||||
|
box_group: BoxSet.HERO,
|
||||||
|
ent: mockEntity
|
||||||
|
};
|
||||||
|
return this.mockCasterView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private playDebugSkill(skillId: number) {
|
||||||
|
const conf = SkillSet[skillId];
|
||||||
|
if (!conf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.destroyCurrentSkill();
|
||||||
|
const parent = this.getSkillParent();
|
||||||
|
const startPos = v3(-260, -40, 0);
|
||||||
|
const targetPos = v3(260, -40, 0);
|
||||||
|
const caster = this.ensureMockCaster(parent, startPos);
|
||||||
|
const skill = ecs.getEntity<Skill>(Skill);
|
||||||
|
skill.load(startPos.clone(), parent, skillId, targetPos, caster, 0);
|
||||||
|
this.currentSkill = skill;
|
||||||
|
}
|
||||||
|
|
||||||
|
private destroyCurrentSkill() {
|
||||||
|
if (!this.currentSkill) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentSkill.destroy();
|
||||||
|
this.currentSkill = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.node.destroy()
|
this.destroyCurrentSkill();
|
||||||
|
this.clearButtons();
|
||||||
|
if (this.mockCasterNode && this.mockCasterNode.isValid) {
|
||||||
|
this.mockCasterNode.destroy();
|
||||||
|
this.mockCasterNode = null;
|
||||||
|
}
|
||||||
|
this.node.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/modu
|
|||||||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||||||
import { GameEvent } from "../common/config/GameEvent";
|
import { GameEvent } from "../common/config/GameEvent";
|
||||||
import { smc } from "../common/SingletonModuleComp";
|
import { smc } from "../common/SingletonModuleComp";
|
||||||
import { CardType, FightSet, CardKind } from "../common/config/GameSet";
|
|
||||||
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
import { HeroAttrsComp } from "../hero/HeroAttrsComp";
|
||||||
|
import { CardType } from "../common/config/CardSet";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -32,61 +32,20 @@ export class MissionCardComp extends CCComp {
|
|||||||
@property(Node)
|
@property(Node)
|
||||||
card4:Node = null!
|
card4:Node = null!
|
||||||
|
|
||||||
@property(Node)
|
|
||||||
btnClose: Node = null!
|
|
||||||
|
|
||||||
@property(Node)
|
|
||||||
Lock: Node = null!
|
|
||||||
@property(Node)
|
|
||||||
unLock: Node = null!
|
|
||||||
@property(Node)
|
|
||||||
noStop: Node = null!
|
|
||||||
|
|
||||||
// card1_data: ICardInfo = null!
|
// card1_data: ICardInfo = null!
|
||||||
// card2_data: ICardInfo = null!
|
// card2_data: ICardInfo = null!
|
||||||
// card3_data: ICardInfo = null!
|
// card3_data: ICardInfo = null!
|
||||||
// card4_data: ICardInfo = null!
|
// card4_data: ICardInfo = null!
|
||||||
|
|
||||||
// 当前卡片类型 (用于特殊获取模式)
|
|
||||||
curCardType: CardType | null = null;
|
|
||||||
|
|
||||||
// 是否处于锁定状态
|
|
||||||
private isLocked: boolean = true;
|
|
||||||
// 是否永久解锁(本局)
|
|
||||||
private isAdUnlocked: boolean = false;
|
|
||||||
|
|
||||||
// 图标图集缓存
|
|
||||||
private uiconsAtlas: SpriteAtlas | null = null;
|
private uiconsAtlas: SpriteAtlas | null = null;
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
if (this.btnClose) {
|
|
||||||
this.btnClose.on(Node.EventType.TOUCH_END, this.onGiveUp, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
oops.message.on(GameEvent.TalentSelect, this.onTalentSelect, this);
|
|
||||||
oops.message.on(GameEvent.AttrSelect, this.onAttrSelect, this);
|
|
||||||
oops.message.on(GameEvent.HeroSkillSelect, this.onHeroSkillSelect, this);
|
|
||||||
oops.message.on(GameEvent.ShopOpen, this.onShopOpen, this);
|
|
||||||
oops.message.on(GameEvent.MissionStart, this.onMissionStart, this);
|
|
||||||
oops.message.on(GameEvent.MissionEnd, this.onMissionEnd, this);
|
|
||||||
oops.message.on(GameEvent.ToCallFriend, this.onCallFriend, this);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
if (this.btnClose) {
|
|
||||||
this.btnClose.off(Node.EventType.TOUCH_END, this.onGiveUp, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
oops.message.off(GameEvent.TalentSelect, this.onTalentSelect, this);
|
|
||||||
oops.message.off(GameEvent.AttrSelect, this.onAttrSelect, this);
|
|
||||||
oops.message.off(GameEvent.HeroSkillSelect, this.onHeroSkillSelect, this);
|
|
||||||
oops.message.off(GameEvent.ShopOpen, this.onShopOpen, this);
|
|
||||||
oops.message.off(GameEvent.MissionStart, this.onMissionStart, this);
|
|
||||||
oops.message.off(GameEvent.MissionEnd, this.onMissionEnd, this);
|
|
||||||
oops.message.off(GameEvent.ToCallFriend, this.onCallFriend, this);
|
|
||||||
|
|
||||||
this.ent.destroy();
|
|
||||||
}
|
}
|
||||||
init(){
|
init(){
|
||||||
this.onMissionStart();
|
this.onMissionStart();
|
||||||
@@ -94,569 +53,27 @@ export class MissionCardComp extends CCComp {
|
|||||||
|
|
||||||
/** 游戏开始初始化 */
|
/** 游戏开始初始化 */
|
||||||
onMissionStart() {
|
onMissionStart() {
|
||||||
this.isLocked = true;
|
|
||||||
this.isAdUnlocked = false;
|
|
||||||
this.noStop.active = false;
|
|
||||||
if (this.Lock) this.Lock.active = false; // 初始不显示,等待 showCardType
|
|
||||||
if(this.unLock) this.unLock.active=false
|
|
||||||
this.eventQueue = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 游戏结束清理 */
|
/** 游戏结束清理 */
|
||||||
onMissionEnd() {
|
onMissionEnd() {
|
||||||
this.eventQueue = [];
|
|
||||||
this.node.active = false;
|
|
||||||
this.hasSelected = false;
|
|
||||||
|
|
||||||
// 停止所有卡片动画
|
|
||||||
const cards = [this.card1, this.card2, this.card3, this.card4];
|
|
||||||
cards.forEach(card => {
|
|
||||||
if (card) {
|
|
||||||
Tween.stopAllByTarget(card);
|
|
||||||
const selected = card.getChildByName("selected");
|
|
||||||
if (selected) Tween.stopAllByTarget(selected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
start() {
|
start() {
|
||||||
// 初始隐藏或显示逻辑
|
|
||||||
this.node.active = false;
|
|
||||||
this.resetCardStates();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetCardStates() {
|
private resetCardStates() {
|
||||||
const cards = [this.card1, this.card2, this.card3, this.card4];
|
|
||||||
cards.forEach(card => {
|
|
||||||
if (card) {
|
|
||||||
const selected = card.getChildByName("selected");
|
|
||||||
if (selected) selected.active = false;
|
|
||||||
// 恢复缩放和颜色
|
|
||||||
card.setScale(1, 1, 1);
|
|
||||||
const sprite = card.getComponent(Sprite);
|
|
||||||
if (sprite) sprite.color = new Color(255, 255, 255);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否已经选择了天赋
|
|
||||||
private hasSelected: boolean = false;
|
|
||||||
|
|
||||||
// 事件队列
|
|
||||||
private eventQueue: ICardEvent[] = [];
|
|
||||||
|
|
||||||
private onShopOpen(event: string, args: any) {
|
|
||||||
this.eventQueue.push({ type: CardType.Potion });
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAttrSelect(event: string, args: any) {
|
|
||||||
this.eventQueue.push({ type: CardType.Attr });
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onTalentSelect(event: string, args: any) {
|
|
||||||
this.eventQueue.push({ type: CardType.Talent });
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onHeroSkillSelect(event: string, args: any) {
|
|
||||||
this.eventQueue.push({ type: CardType.Skill });
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private onCallFriend(event: string, args: any) {
|
|
||||||
this.eventQueue.push({ type: CardType.Partner });
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private checkQueue() {
|
|
||||||
if (this.node.active) return;
|
|
||||||
if (this.eventQueue.length === 0) return;
|
|
||||||
|
|
||||||
const event = this.eventQueue.shift();
|
|
||||||
if (event) {
|
|
||||||
if (event.type !== undefined) {
|
|
||||||
this.showCardType(event.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示指定类型的卡牌(特殊获取模式)
|
|
||||||
*/
|
|
||||||
private showCardType(type: CardType) {
|
|
||||||
this.curCardType = type;
|
|
||||||
// 获取当前英雄等级作为参考,或者默认1级
|
|
||||||
const level = smc.vmdata.hero.lv || 1;
|
|
||||||
this.fetchCards(level, type);
|
|
||||||
this.openUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
private openUI() {
|
|
||||||
this.node.active = true;
|
|
||||||
this.hasSelected = false;
|
|
||||||
|
|
||||||
// 根据锁定状态显示 Lock 节点 (仅在特殊模式下可能需要锁定?或者统一逻辑)
|
|
||||||
// 原逻辑:Lock.active = this.isLocked
|
|
||||||
if (this.Lock) {
|
|
||||||
this.Lock.active = this.isLocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示 noStop 节点
|
|
||||||
if (this.noStop) {
|
|
||||||
this.noStop.active = true;
|
|
||||||
this.checkNoStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有开启 noStop,则暂停怪物行动
|
|
||||||
if (!smc.data.noStop) {
|
|
||||||
smc.mission.stop_mon_action = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetCardStates();
|
|
||||||
this.playShowAnimation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNoStop(){
|
|
||||||
this.noStop.getChildByName("no").active=!smc.data.noStop
|
|
||||||
|
|
||||||
// 更新暂停状态
|
|
||||||
if (this.node.active) {
|
|
||||||
smc.mission.stop_mon_action = !smc.data.noStop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switchNoStop(){
|
|
||||||
smc.data.noStop=!smc.data.noStop
|
|
||||||
this.checkNoStop()
|
|
||||||
}
|
|
||||||
private playShowAnimation() {
|
|
||||||
const cards = [this.card1, this.card2, this.card3, this.card4];
|
|
||||||
cards.forEach((card, index) => {
|
|
||||||
if (card) {
|
|
||||||
card.setScale(Vec3.ZERO);
|
|
||||||
tween(card)
|
|
||||||
.delay(index * 0.1)
|
|
||||||
.to(0.4, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' })
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 专门的获取卡牌方法
|
|
||||||
* @param level 等级
|
|
||||||
* @param forcedType 强制类型 (可选)
|
|
||||||
*/
|
|
||||||
fetchCards(level: number, forcedType?: CardType){
|
|
||||||
// 获取主角已有的属性倾向 (已拥有的永久属性Buff)
|
|
||||||
|
|
||||||
|
|
||||||
// 使用 CardSet 的 getCardOptions 获取卡牌
|
|
||||||
// 这里我们要获取 4 张卡牌
|
|
||||||
// const options = getCardOptions(level, 4, [], forcedType, preferredAttrs);
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] 获取到的卡牌选项: ${JSON.stringify(options)}`);
|
|
||||||
// // 更新卡片数据
|
|
||||||
// if (options.length > 0) this.updateCardData(1, options[0]);
|
|
||||||
// if (options.length > 1) this.updateCardData(2, options[1]);
|
|
||||||
// if (options.length > 2) this.updateCardData(3, options[2]);
|
|
||||||
// if (options.length > 3) this.updateCardData(4, options[3]);
|
|
||||||
|
|
||||||
// // 如果获取不足4张,隐藏多余的卡片节点 (UI可能需要处理空数据)
|
|
||||||
// if (options.length < 4 && this.card4) this.card4.active = false;
|
|
||||||
// if (options.length < 3 && this.card3) this.card3.active = false;
|
|
||||||
// if (options.length < 2 && this.card2) this.card2.active = false;
|
|
||||||
// if (options.length < 1 && this.card1) this.card1.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCardInfo(card:Node, data: any){
|
|
||||||
if(!card) return
|
|
||||||
card.active = true;
|
|
||||||
// 隐藏选中状态
|
|
||||||
const selected = card.getChildByName("selected");
|
|
||||||
if(selected) selected.active = false;
|
|
||||||
|
|
||||||
let name = card.getChildByName("name")
|
|
||||||
if(name){
|
|
||||||
name.getComponent(Label)!.string = data.name
|
|
||||||
}
|
|
||||||
let info = card.getChildByName("info")?.getChildByName("Label")
|
|
||||||
if(info){
|
|
||||||
// ICardInfo 已经标准化了 desc,直接使用
|
|
||||||
info.getComponent(Label)!.string = data.desc || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先隐藏所有类型标识
|
|
||||||
const typeNodes = ["Atk", "Atked", "Buff", "Attr", "Skill", "Hp", "Dead", "Partner"];
|
|
||||||
|
|
||||||
// 1. 处理 card 直接子节点
|
|
||||||
typeNodes.forEach(nodeName => {
|
|
||||||
const node = card.getChildByName(nodeName);
|
|
||||||
if (node) node.active = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. 处理 card/type 下的子节点
|
|
||||||
const typeContainer = card.getChildByName("type");
|
|
||||||
if (typeContainer) {
|
|
||||||
typeNodes.forEach(nodeName => {
|
|
||||||
const node = typeContainer.getChildByName(nodeName);
|
|
||||||
if (node) node.active = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据 kind 激活对应节点
|
|
||||||
let activeNodeName = "";
|
|
||||||
switch (data.kind) {
|
|
||||||
case CardKind.Atk:
|
|
||||||
activeNodeName = "Atk";
|
|
||||||
break;
|
|
||||||
case CardKind.Atted:
|
|
||||||
activeNodeName = "Atked";
|
|
||||||
break;
|
|
||||||
case CardKind.Buff:
|
|
||||||
activeNodeName = "Buff";
|
|
||||||
break;
|
|
||||||
case CardKind.Attr:
|
|
||||||
activeNodeName = "Attr";
|
|
||||||
break;
|
|
||||||
case CardKind.Skill:
|
|
||||||
activeNodeName = "Skill";
|
|
||||||
break;
|
|
||||||
case CardKind.Hp:
|
|
||||||
activeNodeName = "Hp";
|
|
||||||
break;
|
|
||||||
case CardKind.Dead:
|
|
||||||
activeNodeName = "Dead";
|
|
||||||
break;
|
|
||||||
case CardKind.Partner:
|
|
||||||
activeNodeName = "Partner";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeNodeName) {
|
|
||||||
// 激活 card 下的节点
|
|
||||||
const activeNode = card.getChildByName(activeNodeName);
|
|
||||||
if (activeNode) activeNode.active = true;
|
|
||||||
|
|
||||||
// 激活 card/type 下的节点
|
|
||||||
if (typeContainer) {
|
|
||||||
const activeTypeNode = typeContainer.getChildByName(activeNodeName);
|
|
||||||
if (activeTypeNode) activeTypeNode.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新图标 (如果存在 icon 节点)
|
|
||||||
// 注意:根据 Prefab 分析,icon 可能在 card/Mask/icon 路径下,也可能在各个分类节点下
|
|
||||||
// 这里尝试统一查找 icon 节点
|
|
||||||
let iconNode: Node | null = null;
|
|
||||||
|
|
||||||
// 1. 尝试查找通用的 mask/icon (根据之前 card.prefab 分析,有个 Mask/icon 节点)
|
|
||||||
const maskNode = card.getChildByName("Mask");
|
|
||||||
if (maskNode) {
|
|
||||||
iconNode = maskNode.getChildByName("icon");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iconNode && data.icon) {
|
|
||||||
this.updateIcon(iconNode, data.icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateIcon(node: Node, iconId: string) {
|
|
||||||
if (!node || !iconId) return;
|
|
||||||
|
|
||||||
const sprite = node.getComponent(Sprite);
|
|
||||||
if (!sprite) return;
|
|
||||||
|
|
||||||
if (this.uiconsAtlas) {
|
|
||||||
const frame = this.uiconsAtlas.getSpriteFrame(iconId);
|
|
||||||
if (frame) {
|
|
||||||
sprite.spriteFrame = frame;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 加载图集
|
|
||||||
resources.load("gui/uicons", SpriteAtlas, (err, atlas) => {
|
|
||||||
if (err) {
|
|
||||||
mLogger.error(this.debugMode, 'MissionCard', "[MissionCardComp] Failed to load uicons atlas", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.uiconsAtlas = atlas;
|
|
||||||
const frame = atlas.getSpriteFrame(iconId);
|
|
||||||
if (frame) {
|
|
||||||
sprite.spriteFrame = frame;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCardData(index: number, data: any) {
|
|
||||||
// 使用动态属性访问
|
|
||||||
(this as any)[`card${index}_data`] = data;
|
|
||||||
this.updateCardInfo((this as any)[`card${index}`], data);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectCard(e: any, index: string) {
|
|
||||||
mLogger.log(this.debugMode, 'MissionCard', "selectCard", index)
|
|
||||||
let _index = parseInt(index);
|
|
||||||
// 如果已经选择过,则不再处理
|
|
||||||
if (this.hasSelected) return;
|
|
||||||
|
|
||||||
// 动态获取数据和节点
|
|
||||||
let selectedData: any;
|
|
||||||
let selectedCardNode: Node | null = (this as any)[`card${_index}`];
|
|
||||||
|
|
||||||
if (selectedData && selectedCardNode) {
|
|
||||||
this.hasSelected = true;
|
|
||||||
mLogger.log(this.debugMode, 'MissionCard', "选择卡片:", selectedData.name, "类型:", selectedData.type);
|
|
||||||
|
|
||||||
// 未选中的卡片缩小
|
|
||||||
const cards = [this.card1, this.card2, this.card3, this.card4];
|
|
||||||
cards.forEach(card => {
|
|
||||||
if (card && card !== selectedCardNode) {
|
|
||||||
tween(card).to(0.2, { scale: Vec3.ZERO }).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 显示当前选中的 selected 节点
|
|
||||||
const selected = selectedCardNode.getChildByName("selected");
|
|
||||||
if(selected) {
|
|
||||||
selected.active = true;
|
|
||||||
selected.setScale(Vec3.ZERO);
|
|
||||||
tween(selected).to(0.2, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' }).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选中卡片动效后触发逻辑
|
|
||||||
tween(selectedCardNode)
|
|
||||||
.to(0.1, { scale: new Vec3(1.1, 1.1, 1.1) })
|
|
||||||
.to(0.1, { scale: new Vec3(1, 1, 1) })
|
|
||||||
.delay(0.5)
|
|
||||||
// .call(() => {
|
|
||||||
// // @ts-ignore
|
|
||||||
// let role = entities.length > 0 ? entities[0] : null;
|
|
||||||
|
|
||||||
// if (!role) {
|
|
||||||
// } else {
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] 成功定位主角实体: ${role.eid}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (role) {
|
|
||||||
// switch (selectedData.type) {
|
|
||||||
// case CardType.Talent:
|
|
||||||
// smc.addTalentRecord(selectedData.uuid);
|
|
||||||
// // 直接调用 TalComp 添加天赋
|
|
||||||
// const talComp = role.get(TalComp);
|
|
||||||
// if (talComp) {
|
|
||||||
// const beforeCount = Object.keys(talComp.Tals).length;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Talent Before: Count=${beforeCount}, Tals=${JSON.stringify(talComp.Tals)}`);
|
|
||||||
// talComp.addTal(selectedData.uuid);
|
|
||||||
// const afterCount = Object.keys(talComp.Tals).length;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Talent After: Count=${afterCount}, Added=${selectedData.uuid}, Tals=${JSON.stringify(talComp.Tals)}`);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case CardType.Skill:
|
|
||||||
// smc.addSkillRecord(selectedData.uuid);
|
|
||||||
// // 直接调用 HeroSkillsComp 添加技能
|
|
||||||
// const skillComp = role.get(HeroSkillsComp);
|
|
||||||
// if (skillComp) {
|
|
||||||
// const beforeCount = Object.keys(skillComp.skills).length;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Skill Before: Count=${beforeCount}, Skills=${JSON.stringify(skillComp.skills)}`);
|
|
||||||
// skillComp.addSkill(selectedData.uuid);
|
|
||||||
// const afterCount = Object.keys(skillComp.skills).length;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Skill After: Count=${afterCount}, Added=${selectedData.uuid}, Skills=${JSON.stringify(skillComp.skills)}`);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case CardType.Partner:
|
|
||||||
// // 伙伴是召唤新实体,依然适合用事件,或者直接调用 summon 方法
|
|
||||||
// oops.message.dispatchEvent(GameEvent.CallFriend, { uuid: selectedData.uuid });
|
|
||||||
// break;
|
|
||||||
// case CardType.Potion:
|
|
||||||
// // 药水直接作用于 HeroAttrsComp
|
|
||||||
// const attrsComp = role.get(HeroAttrsComp);
|
|
||||||
// if (attrsComp) {
|
|
||||||
// const potion = PotionCards[selectedData.uuid];
|
|
||||||
// if (potion) {
|
|
||||||
// const beforeVal = attrsComp.Attrs[potion.attr] || 0;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Potion Before: Attr[${potion.attr}]=${beforeVal}, Attrs=${JSON.stringify(attrsComp.Attrs)}`);
|
|
||||||
|
|
||||||
// const buffConf: BuffConf = {
|
|
||||||
// buff: potion.attr,
|
|
||||||
// value: potion.value,
|
|
||||||
// BType: BType.RATIO,
|
|
||||||
// time: potion.duration,
|
|
||||||
// chance: 1,
|
|
||||||
// };
|
|
||||||
// attrsComp.addBuff(buffConf);
|
|
||||||
// smc.updateHeroInfo(attrsComp);
|
|
||||||
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Potion Applied: ${potion.desc}, Value=${potion.value}, Attrs=${JSON.stringify(attrsComp.Attrs)}`);
|
|
||||||
// oops.gui.toast(potion.desc);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case CardType.Attr:
|
|
||||||
// // 属性卡:使用 addBuff 添加永久属性加成
|
|
||||||
// const attrCard = AttrCards[selectedData.uuid];
|
|
||||||
// if (attrCard) {
|
|
||||||
// const attrsComp = role.get(HeroAttrsComp);
|
|
||||||
// if (attrsComp) {
|
|
||||||
// // 记录变更前状态
|
|
||||||
// const roleBefore = attrsComp.Attrs[attrCard.attr] || 0;
|
|
||||||
|
|
||||||
// // 根据属性类型决定 Buff 类型
|
|
||||||
// // 如果属性本身是 RATIO 型(如暴击率),AttrCards 中的值(如2)应该作为 VALUE 添加(因为 recalculateSingleAttr 会把 VALUE 和 RATIO 相加)
|
|
||||||
// // 但如果属性本身是 VALUE 型(如攻击力),AttrCards 中的值是直接加数值,也应该作为 VALUE 添加
|
|
||||||
// // 结论:无论属性类型如何,AttrCards 中的配置都是"增加的点数",所以统一使用 BType.VALUE
|
|
||||||
// // 修正:虽然 AttrsType 定义了属性本身的类型,但在 addBuff 中,BType.VALUE 表示"加法叠加",BType.RATIO 表示"乘法叠加"
|
|
||||||
// // 对于数值型属性(如攻击力):BType.VALUE 是 +10,BType.RATIO 是 +10%
|
|
||||||
// // 对于百分比型属性(如暴击率):BType.VALUE 是 +2(%),BType.RATIO 是 +2%(即 *1.02,通常不这么用)
|
|
||||||
// // 所以,AttrCards 配置的值应当被视为"绝对值增量",对应 BType.VALUE
|
|
||||||
|
|
||||||
// // 构造永久 Buff (time: 0)
|
|
||||||
// const buffConf: BuffConf = {
|
|
||||||
// buff: attrCard.attr,
|
|
||||||
// value: attrCard.value,
|
|
||||||
// BType: BType.RATIO, // 始终使用 VALUE 类型,代表数值/点数叠加
|
|
||||||
// time: 0,
|
|
||||||
// chance: 1,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Adding Buff: Attr=${attrCard.attr}, Val=${attrCard.value}, Type=VALUE`);
|
|
||||||
// attrsComp.addBuff(buffConf);
|
|
||||||
// // addBuff 内部会自动调用 recalculateSingleAttr 和 updateHeroInfo
|
|
||||||
|
|
||||||
// const roleAfter = attrsComp.Attrs[attrCard.attr] || 0;
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] Attr After: Hero=${roleAfter} (Change: ${roleAfter - roleBefore})`);
|
|
||||||
|
|
||||||
// oops.gui.toast(attrCard.desc);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// mLogger.warn(this.debugMode, 'MissionCard', `[MissionCard] 未找到属性卡配置: UUID=${selectedData.uuid}`);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// mLogger.log(this.debugMode, 'MissionCard', `[MissionCard] 主角实体无效,无法应用卡牌效果`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 记录已获取的卡牌
|
|
||||||
// oops.message.dispatchEvent(GameEvent.UpdateMissionGet, {
|
|
||||||
// uuid: selectedData.uuid,
|
|
||||||
// icon: selectedData.icon,
|
|
||||||
// kind: selectedData.kind
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.close();
|
|
||||||
// })
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 看广告关闭 Lock */
|
|
||||||
watchAdCloseLock() {
|
|
||||||
// TODO: 此处接入 IAA 广告 SDK
|
|
||||||
mLogger.log(this.debugMode, 'MissionCard', "播放激励视频广告...");
|
|
||||||
|
|
||||||
// 模拟广告播放成功回调
|
|
||||||
this.isLocked = false;
|
|
||||||
this.isAdUnlocked = true;
|
|
||||||
if (this.Lock) {
|
|
||||||
this.Lock.active = false;
|
|
||||||
oops.gui.toast("解锁成功");
|
|
||||||
}
|
|
||||||
this.closeUnLock();
|
|
||||||
}
|
|
||||||
coinCloseLock(){
|
|
||||||
let cost = smc.vmdata.mission_data.unlockCoin;
|
|
||||||
if (smc.vmdata.gold >= cost) {
|
|
||||||
// 扣除金币
|
|
||||||
if (smc.updateGold(-cost)) {
|
|
||||||
this.isLocked = false;
|
|
||||||
if (this.Lock) {
|
|
||||||
this.Lock.active = false;
|
|
||||||
}
|
|
||||||
oops.gui.toast("解锁成功");
|
|
||||||
this.closeUnLock();
|
|
||||||
} else {
|
|
||||||
oops.gui.toast("交易失败");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
oops.gui.toast("金币不足");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showUnLock(){
|
|
||||||
if (this.unLock) {
|
|
||||||
this.unLock.active = true;
|
|
||||||
this.unLock.setScale(Vec3.ZERO);
|
|
||||||
tween(this.unLock)
|
|
||||||
.to(0.2, { scale: new Vec3(1, 1, 1) }, { easing: 'backOut' })
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeUnLock(){
|
|
||||||
if (this.unLock && this.unLock.active) {
|
|
||||||
tween(this.unLock)
|
|
||||||
.to(0.2, { scale: Vec3.ZERO }, { easing: 'backIn' })
|
|
||||||
.call(() => {
|
|
||||||
this.unLock.active = false;
|
|
||||||
})
|
|
||||||
.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** 放弃选择 */
|
|
||||||
onGiveUp() {
|
|
||||||
if (this.hasSelected) return;
|
|
||||||
this.hasSelected = true;
|
|
||||||
|
|
||||||
// 隐藏关闭按钮
|
|
||||||
if (this.btnClose) {
|
|
||||||
this.btnClose.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cards = [this.card1, this.card2, this.card3, this.card4];
|
|
||||||
let delayTime = 0.2;
|
|
||||||
|
|
||||||
cards.forEach(card => {
|
|
||||||
if (card && card.active) {
|
|
||||||
tween(card).to(delayTime, { scale: Vec3.ZERO }).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 动画结束后关闭
|
|
||||||
this.scheduleOnce(() => {
|
|
||||||
this.close();
|
|
||||||
}, delayTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭界面
|
* 关闭界面
|
||||||
*/
|
*/
|
||||||
close() {
|
close() {
|
||||||
this.node.active = false;
|
|
||||||
|
|
||||||
// 恢复游戏运行状态(取消暂停)
|
|
||||||
smc.mission.stop_mon_action = false;
|
|
||||||
|
|
||||||
// 关闭时隐藏按钮,避免下次打开其他类型时闪烁
|
|
||||||
if (this.btnClose) {
|
|
||||||
this.btnClose.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭时隐藏 Lock 节点
|
|
||||||
if (this.Lock) {
|
|
||||||
this.Lock.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭时隐藏 noStop 节点
|
|
||||||
if (this.noStop) {
|
|
||||||
this.noStop.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复锁定状态(如果没有永久解锁)
|
|
||||||
if (!this.isAdUnlocked) {
|
|
||||||
this.isLocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checkQueue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { GameEvent } from "../common/config/GameEvent";
|
|||||||
import { HeroViewComp } from "../hero/HeroViewComp";
|
import { HeroViewComp } from "../hero/HeroViewComp";
|
||||||
import { UIID } from "../common/config/GameUIConfig";
|
import { UIID } from "../common/config/GameUIConfig";
|
||||||
import { SkillView } from "../skill/SkillView";
|
import { SkillView } from "../skill/SkillView";
|
||||||
import { FightSet, CardType, FacSet } from "../common/config/GameSet";
|
import { FightSet } from "../common/config/GameSet";
|
||||||
import { mLogger } from "../common/Logger";
|
import { mLogger } from "../common/Logger";
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user