Files
heros/assets/script/game/hero/HeroViewComp.ts
walkpan 187153ac9e feat(英雄系统): 添加怒气值属性及相关功能
top 血条样式调整
- 在HeroAttrsComp中添加pow属性表示当前怒气值
- 在HeroAttrs枚举中添加POW_MAX和POW_REGEN属性
- 修改HeroViewComp根据英雄类型显示不同资源条
- 调整boss血条位置偏移量
- 注释掉物理系统调试绘制代码
2025-11-02 13:34:04 +08:00

400 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Vec3, _decorator , v3,Collider2D,Contact2DType,Label ,Node,Prefab,instantiate,ProgressBar, Component, Material, Sprite, math, clamp, Game, tween, Color, BoxCollider2D, UITransform} 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 { HeroSpine } from "./HeroSpine";
import { BoxSet, FacSet } from "../common/config/GameSet";
import { smc } from "../common/SingletonModuleComp";
import { EAnmConf, SkillSet,} from "../common/config/SkillSet";
import { oops } from "db://oops-framework/core/Oops";
import { GameEvent } from "../common/config/GameEvent";
import { TooltipTypes } from "../common/config/GameSet";
import { Attrs, } from "../common/config/HeroAttrs";
import { HeroAttrsComp } from "./HeroAttrsComp";
import { Tooltip } from "../skill/Tooltip";
import { timedCom } from "../skill/timedCom";
import { HeroInfo, HType } from "../common/config/heroSet";
const { ccclass, property } = _decorator;
/** 角色显示组件 */
export interface BuffInfo {
value: number;
remainTime?: number;
}
@ccclass('HeroViewComp') // 定义Cocos Creator 组件
@ecs.register('HeroView', false) // 定义ECS 组件
export class HeroViewComp extends CCComp {
// ==================== View 层属性(表现相关)====================
as: HeroSpine = null!
status:String = "idle"
scale: number = 1; // 显示方向
box_group:number = BoxSet.HERO; // 碰撞组
usePower:boolean = false;
useMp:boolean = false;
// ==================== UI 节点引用 ====================
private top_node: Node = null!;
// ==================== 直接访问 HeroAttrsComp ====================
get model() {
return this.ent.get(HeroAttrsComp);
}
private damageQueue: Array<{
damage: number,
isCrit: boolean,
delay: number,
anm:string,
}> = [];
private isProcessingDamage: boolean = false;
private damageInterval: number = 0.01; // 伤害数字显示间隔
onLoad() {
this.as = this.getComponent(HeroSpine);
//console.log("[HeroViewComp]:hero view comp ",this.FIGHTCON)
this.on(GameEvent.FightEnd,this.do_fight_end,this)
const collider = this.node.getComponent(BoxCollider2D);
this.scheduleOnce(()=>{
if (collider) collider.enabled = true; // 先禁
},0.1)
// let anm = this.node.getChildByName("anm")
// anm.setScale(anm.scale.x*0.8,anm.scale.y*0.8);
}
/** 视图层逻辑代码分离演示 */
start () {
this.as.idle()
// 初始化 UI 节点
this.initUINodes();
/** 方向 */
this.node.setScale(this.scale,1);
this.top_node.setScale(this.scale,1);
if(this.model && this.model.is_boss){
this.top_node.position=v3(this.node.position.x,this.node.position.y+70,0)
}
/* 显示角色血*/
this.top_node.getChildByName("hp").active = true;
this.usePower=HeroInfo[this.model.hero_uuid].type==HType.warrior||HeroInfo[this.model.hero_uuid].type==HType.assassin;
this.useMp=HeroInfo[this.model.hero_uuid].type==HType.mage||HeroInfo[this.model.hero_uuid].type==HType.remote||HeroInfo[this.model.hero_uuid].type==HType.support;
this.top_node.getChildByName("pow").active = this.usePower;
this.top_node.getChildByName("mp").active = this.useMp;
}
/** 初始化 UI 节点引用 */
private initUINodes() {
this.top_node = this.node.getChildByName("top");
let hp_y = this.node.getComponent(UITransform).height;
this.top_node.setPosition(0, hp_y, 0);
}
/**
* View 层每帧更新
* 注意:数据更新逻辑已移到 HeroAttrSystem这里只负责显示
*/
update(dt: number){
if(!smc.mission.play || smc.mission.pause) return;
// 添加安全检查防止在实体销毁过程中访问null的model
if (!this.model) return;
if(this.model.is_dead) return;
// ✅ View 层职责:处理表现相关的逻辑
this.processDamageQueue(); // 伤害数字显示队列
// ✅ 更新 UI 显示(数据由 HeroAttrSystem 更新)
this.hp_show(this.model.hp, this.model.Attrs[Attrs.HP_MAX]);
if(this.useMp) this.mp_show(this.model.mp, this.model.Attrs[Attrs.MP_MAX]);
if(this.usePower) this.pow_show(this.model.pow, this.model.Attrs[Attrs.POW_MAX]);
this.show_shield(this.model.shield, this.model.Attrs[Attrs.SHIELD_MAX]);
}
/** 显示护盾 */
private show_shield(shield: number = 0, shield_max: number = 0) {
let shield_progress = shield / shield_max;
this.node.getChildByName("shielded").active = shield > 0;
this.top_node.getChildByName("shield").active = shield > 0;
this.top_node.getChildByName("shield").getComponent(ProgressBar).progress = shield_progress;
this.scheduleOnce(() => {
this.top_node.getChildByName("shield").getChildByName("pb").getComponent(ProgressBar).progress = shield_progress;
}, 0.15);
}
/** 显示血量 */
private hp_show(hp: number, hp_max: number) {
let hp_progress = hp / hp_max;
this.top_node.getChildByName("hp").getComponent(ProgressBar).progress = hp_progress;
this.scheduleOnce(() => {
this.top_node.getChildByName("hp").getChildByName("hpb").getComponent(ProgressBar).progress = hp_progress;
}, 0.15);
}
/** 显示魔法值 */
private mp_show(mp: number, mp_max: number) {
this.top_node.getChildByName("mp").getComponent(ProgressBar).progress = mp / mp_max;
this.scheduleOnce(() => {
this.top_node.getChildByName("mp").getChildByName("mpb").getComponent(ProgressBar).progress = mp / mp_max;
}, 0.15);
}
private pow_show(pow: number, pow_max: number) {
this.top_node.getChildByName("pow").getComponent(ProgressBar).progress = pow / pow_max;
this.scheduleOnce(() => {
this.top_node.getChildByName("pow").getChildByName("mpb").getComponent(ProgressBar).progress = pow / pow_max;
}, 0.15);
}
/** 升级特效 */
private lv_up() {
var path = "game/skill/buff/buff_lvup";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
/** 攻击力提升特效 */
private ap_up() {
var path = "game/skill/buff/buff_apup";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
/** 显示 Buff 特效 */
private show_do_buff(name: string) {
var path = "game/skill/buff/" + name;
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
let pos = v3(this.node.position.x, this.node.position.y + 20, this.node.position.z);
node.parent = this.node.parent;
node.setPosition(pos);
}
/** 受击特效 */
private in_atked(anm: string = "atked", scale: number = 1) {
var path = "game/skill/end/" + anm;
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.setScale(node.scale.x * scale, node.scale.y);
node.setPosition(this.node.position.x, this.node.position.y+8, this.node.position.z);
node.parent = this.node.parent;
}
/** 冰冻特效 */
private in_iced(t: number = 1, ap: number = 0) {
var path = "game/skill/buff/buff_iced";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.getComponent(timedCom).time = t;
node.getComponent(timedCom).ap = ap;
node.parent = this.node;
}
/** 眩晕特效 */
private in_yun(t: number = 1, ap: number = 0) {
var path = "game/skill/buff/buff_yun";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
let height = this.node.getComponent(UITransform).height;
node.setPosition(v3(0, height));
node.getComponent(timedCom).time = t;
node.getComponent(timedCom).ap = ap;
node.parent = this.node;
}
/** 技能提示 */
private tooltip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 90) {
let tip = ecs.getEntity<Tooltip>(Tooltip);
let pos = v3(0, 60);
pos.y = pos.y + y;
tip.load(pos, type, value, s_uuid, this.node);
}
/** 血量提示(伤害数字) */
private hp_tip(type: number = 1, value: string = "", s_uuid: number = 1001, y: number = 90) {
let tip = ecs.getEntity<Tooltip>(Tooltip);
let x = this.node.position.x;
let ny = this.node.getComponent(UITransform).height + 60;
let pos = v3(x, ny, 0);
tip.load(pos, type, value, s_uuid, this.node.parent);
}
/** 治疗特效 */
private heathed() {
var path = "game/skill/buff/heathed";
var prefab: Prefab = oops.res.get(path, Prefab)!;
var node = instantiate(prefab);
node.parent = this.node;
}
// 注意BaseUp 逻辑已移到 HeroAttrSystem.update()
// 注意updateTemporaryBuffsDebuffs 逻辑已移到 HeroAttrSystem.update()
do_fight_end(){
this.as.do_buff()
}
get isActive() {
return this.ent.has(HeroViewComp) && this.node?.isValid;
}
/** 状态切换(动画) */
status_change(type:string){
this.status = type;
if(type === "idle"){
this.as.idle();
} else if(type === "move"){
this.as.move();
}
}
add_shield(shield:number){
// 护盾数据更新由 Model 层处理,这里只负责视图表现
if(this.model && this.model.shield>0) this.show_shield(this.model.shield, this.model.Attrs[Attrs.SHIELD_MAX]);
}
health(hp: number = 0, is_num:boolean=true) {
// 生命值更新由 Model 层处理,这里只负责视图表现
this.heathed();
if(this.model) {
this.hp_show(hp, this.model.Attrs[Attrs.HP_MAX]);
}
}
/**
* 死亡视图表现
* 由 HeroAtkSystem 调用,只负责视觉效果和事件通知
*/
do_dead(){
// 添加安全检查
if (!this.model) return;
// 防止重复触发
if(this.model.is_count_dead) return;
this.model.is_count_dead = true; // 防止重复触发,必须存在防止重复调用
// 播放死亡特效
this.as.dead();
// 根据阵营触发不同事件
if(this.model.fac === FacSet.MON){
// 怪物死亡:延迟触发掉落(暂时不发事件,等待实现)
// this.scheduleOnce(() => {
// oops.message.dispatchEvent(GameEvent.MonsterDead, {
// hero_uuid: this.model.hero_uuid,
// position: this.node.position
// });
// }, 0.2);
} else if(this.model.fac === FacSet.HERO){
// 英雄死亡:延迟触发死亡事件
this.scheduleOnce(() => {
oops.message.dispatchEvent(GameEvent.HeroDead, {
hero_uuid: this.model.hero_uuid
});
}, 0.2);
}
}
do_atked(damage:number,isCrit:boolean,s_uuid:number){
if (damage <= 0) return;
// 视图层表现
let SConf=SkillSet[s_uuid]
this.back()
this.showDamage(damage, isCrit, SConf.DAnm); // 暴击状态由战斗系统内部处理, DAnm和EAnm共用设定数组
}
//后退
back(){
if(this.model.fac==FacSet.MON) {
let tx=this.node.position.x+5
if(tx > 320) tx=320
tween(this.node).to(0.1, { position:v3(tx,this.node.position.y,0)}).start()
}
if(this.model.fac==FacSet.HERO) {
let tx=this.node.position.x-5
if(tx < -320) tx=-320
tween(this.node).to(0.1, { position:v3(tx,this.node.position.y,0)}).start()
}
}
// 伤害计算和战斗逻辑已迁移到 HeroBattleSystem
/** 死亡触发器(预留,用于天赋/特殊效果) */
do_dead_trigger(){
if(!this.model || this.model.is_dead || this.model.fac === FacSet.MON) return;
// 预留:天赋触发、复活检查等
}
/** 受击触发器(预留,用于天赋/特殊效果) */
do_atked_trigger(){
if(!this.model || this.model.is_dead || this.model.fac === FacSet.MON) return;
// 预留:反击、护盾触发等
}
/** 调试日志(已禁用) */
to_console(value:any, value2:any=null, value3:any=null){
// 调试用,生产环境已禁用
}
reset() {
const collider = this.getComponent(Collider2D);
if (collider) {
collider.off(Contact2DType.BEGIN_CONTACT);
}
this.scheduleOnce(() => {
this.node.destroy();
}, 0.1);
}
playSkillEffect(skill_id:number) {
let skill = SkillSet[skill_id]
switch(skill.act){
case "max":
this.as.max()
this.tooltip(TooltipTypes.skill, skill.name, skill_id)
break
case "atk":
this.as.atk()
break
}
}
/** 显示伤害数字 */
showDamage(damage: number, isCrit: boolean,DAnm:number) {
let anm=EAnmConf[DAnm].path // DAnm和EAnm共用设定数组
this.damageQueue.push({
damage,
isCrit,
delay: this.damageInterval,
anm
});
}
/** 处理伤害队列 */
private processDamageQueue() {
if (this.isProcessingDamage || this.damageQueue.length === 0) return;
this.isProcessingDamage = true;
const damageInfo = this.damageQueue.shift()!;
this.showDamageImmediate(damageInfo.damage, damageInfo.isCrit,damageInfo.anm);
// 设置延时处理下一个伤
this.scheduleOnce(() => {
this.isProcessingDamage = false;
}, this.damageInterval);
}
/** 立即显示伤害效果 */
private showDamageImmediate(damage: number, isCrit: boolean, anm:string="atked") {
if (!this.model) return;
this.hp_show(this.model.hp, this.model.Attrs[Attrs.HP_MAX]);
this.in_atked(anm, this.model.fac==FacSet.HERO?1:-1);
if (isCrit) {
this.hp_tip(TooltipTypes.crit, damage.toFixed(0), damage);
} else {
this.hp_tip(TooltipTypes.life, damage.toFixed(0), damage);
}
}
}