🎯 幽灵射手系列 · 全目录
文章目录
-
-
- [🎯 幽灵射手系列 · 全目录](#🎯 幽灵射手系列 · 全目录)
- 概述
- [3.1 GameManager(游戏管理)](#3.1 GameManager(游戏管理))
-
- [3.1.1 功能定位](#3.1.1 功能定位)
- [3.1.2 静态属性(全局访问)](#3.1.2 静态属性(全局访问))
- [3.1.3 核心方法](#3.1.3 核心方法)
-
- [3.1.3.1 游戏初始化](#3.1.3.1 游戏初始化)
- [3.1.3.2 关卡刷新](#3.1.3.2 关卡刷新)
- [3.1.3.3 创建玩家](#3.1.3.3 创建玩家)
- [3.1.3.4 怪物管理](#3.1.3.4 怪物管理)
- [3.1.3.5 资源回收](#3.1.3.5 资源回收)
- [3.1.3.6 游戏状态控制](#3.1.3.6 游戏状态控制)
- [3.2 Player(玩家控制)](#3.2 Player(玩家控制))
-
- [3.2.1 组件结构](#3.2.1 组件结构)
- [3.2.2 初始化逻辑](#3.2.2 初始化逻辑)
- [3.2.3 技能解析](#3.2.3 技能解析)
- [3.2.4 攻击逻辑](#3.2.4 攻击逻辑)
- [3.2.5 移动控制](#3.2.5 移动控制)
- [3.2.6 血量管理](#3.2.6 血量管理)
- [3.3 Monster(怪物控制)](#3.3 Monster(怪物控制))
-
- [3.3.1 组件结构](#3.3.1 组件结构)
- [3.3.2 初始化逻辑](#3.3.2 初始化逻辑)
- [3.3.3 AI逻辑](#3.3.3 AI逻辑)
- [3.3.4 攻击逻辑](#3.3.4 攻击逻辑)
- [3.3.5 受击与死亡处理](#3.3.5 受击与死亡处理)
- [3.4 MapManager(地图管理)](#3.4 MapManager(地图管理))
-
- [3.4.1 功能定位](#3.4.1 功能定位)
- [3.4.2 核心数据结构](#3.4.2 核心数据结构)
- [3.4.3 地图构建流程](#3.4.3 地图构建流程)
- [3.4.4 传送门管理](#3.4.4 传送门管理)
- [3.5 Arrow(箭矢控制)](#3.5 Arrow(箭矢控制))
-
- [3.5.1 组件结构](#3.5.1 组件结构)
- [3.5.2 核心逻辑](#3.5.2 核心逻辑)
- [3.6 战斗流程总结](#3.6 战斗流程总结)
- 总结
-
概述
战斗系统是游戏的核心玩法部分,包括玩家控制、怪物AI、地图管理、箭矢系统等。本章详细讲解战斗系统的设计和实现。
3.1 GameManager(游戏管理)
3.1.1 功能定位
GameManager 是战斗系统的核心控制器,负责:
- 游戏状态管理(开始/暂停/结束)
- 关卡加载和切换
- 玩家和怪物的全局管理
- 资源回收和清理
3.1.2 静态属性(全局访问)
typescript
// 游戏状态
public static isGameStart: boolean = false; // 是否开始
public static isGamePause: boolean = false; // 是否暂停
public static isGameOver: boolean = false; // 是否结束
public static isWin: boolean = false; // 是否胜利
// 场景节点
public static ndPlayer: Node; // 玩家节点
public static ndBoss: Node; // Boss节点
public static arrMonster: Node[] = []; // 怪物数组
public static ndGameManager: Node; // 游戏管理器节点
// 属性加成
public static attackAddition: number = 1; // 敌人攻击加成
public static hpAddition: number = 1; // 敌人生命加成
public static defenseAddition: number = 1; // 敌人防御加成
// 调试模式
public static isTesting: boolean = true; // 是否调试模式
3.1.3 核心方法
3.1.3.1 游戏初始化
typescript
private _onGameInit(finishCb: Function) {
// 获取当前关卡配置
let level = PlayerData.instance.playerInfo.level;
this.mapInfo = LocalConfig.instance.queryByID("checkpoint", String(level));
// 设置敌人属性加成
GameManager.attackAddition = this.mapInfo.attackAddition;
GameManager.hpAddition = this.mapInfo.hpAddition;
GameManager.defenseAddition = this.mapInfo.defenseAddition;
// 重置游戏状态
GameManager.isGameStart = false;
GameManager.isGamePause = false;
GameManager.isGameOver = false;
GameManager.isWin = false;
// 清理怪物数组
GameManager.arrMonster = [];
GameManager.ndBoss = null;
GameManager.scriptBoss = null;
// 显示加载界面并刷新关卡
UIManager.instance.showDialog("loading/loadingPanel", [], () => {
this._refreshLevel();
finishCb && finishCb();
});
}
3.1.3.2 关卡刷新
typescript
private _refreshLevel() {
// 回收所有资源
this._recycleAll();
// 获取关卡配置
let level = PlayerData.instance.playerInfo.level;
let mapName = LocalConfig.instance.queryByID("checkpoint", String(level)).mapName;
let arrMap = mapName.split("#");
// 设置当前地图索引
this.curMapIndex = 0;
this.totalMapNum = arrMap.length;
// 加载第一个地图
this._loadMap(arrMap[0], () => {
// 地图加载完成
UIManager.instance.hideDialog("loading/loadingPanel");
// 创建玩家
this._createPlayer();
// 显示战斗界面
UIManager.instance.showDialog("fight/fightPanel");
});
}
private _loadMap(mapName: string, completeCb: Function) {
// 构建地图
this.scriptMapManager.buildMap(mapName,
(progress: number) => {
// 更新加载进度
let panel = this._dictPanel.get("loading/loadingPanel");
if (panel && panel.scriptLoading) {
panel.scriptLoading.setProgress(progress);
}
},
() => {
// 地图构建完成
completeCb && completeCb();
}
);
}
3.1.3.3 创建玩家
typescript
private _createPlayer() {
ResourceUtil.loadModelRes(`player/${Constant.PLAYER_RES_NAME}`).then((prefab: Prefab) => {
GameManager.ndPlayer = instantiate(prefab);
// 设置初始位置(根据地图配置)
let playerInfo = LocalConfig.instance.queryByID("base", Constant.PLAYER_ID);
let pos = Util.parseVector3(playerInfo.position);
GameManager.ndPlayer.setPosition(pos);
// 添加到场景
this.node.addChild(GameManager.ndPlayer);
// 获取玩家脚本并初始化
GameManager.scriptPlayer = GameManager.ndPlayer.getComponent(Player);
GameManager.scriptPlayer.init();
// 显示玩家血条
UIManager.instance.showPlayerBloodBar(
GameManager.scriptPlayer,
GameManager.scriptPlayer.playerBaseInfo.hp,
GameManager.scriptPlayer.curHp
);
});
}
3.1.3.4 怪物管理
typescript
/**
* 获取最近的怪物
*/
public static getNearestMonster(ndSelf: Node): Node {
let minLength = Number.MAX_VALUE;
let ndTarget: Node = null;
for (let ndMonster of GameManager.arrMonster) {
if (!ndMonster || !isValid(ndMonster)) continue;
let scriptMonster = ndMonster.getComponent(Monster);
if (!scriptMonster || scriptMonster.isDie) continue;
let length = Util.getTwoNodeXZLength(ndSelf, ndMonster);
if (length < minLength) {
minLength = length;
ndTarget = ndMonster;
}
}
return ndTarget;
}
/**
* 获取附近的怪物(用于闪电链等技能)
*/
public static getNearbyMonster(ndSelf: Node, maxDistance: number): Node[] {
let arrResult: Node[] = [];
for (let ndMonster of GameManager.arrMonster) {
if (!ndMonster || !isValid(ndMonster)) continue;
let scriptMonster = ndMonster.getComponent(Monster);
if (!scriptMonster || scriptMonster.isDie) continue;
let length = Util.getTwoNodeXZLength(ndSelf, ndMonster);
if (length <= maxDistance * maxDistance) {
arrResult.push(ndMonster);
}
}
return arrResult;
}
3.1.3.5 资源回收
typescript
private _recycleAll() {
// 回收玩家
if (GameManager.ndPlayer && isValid(GameManager.ndPlayer)) {
PoolManager.instance.putNode(GameManager.ndPlayer);
GameManager.ndPlayer = null;
GameManager.scriptPlayer = null;
}
// 回收所有怪物
for (let ndMonster of GameManager.arrMonster) {
if (ndMonster && isValid(ndMonster)) {
PoolManager.instance.putNode(ndMonster);
}
}
GameManager.arrMonster = [];
GameManager.ndBoss = null;
GameManager.scriptBoss = null;
// 回收地图
if (this.scriptMapManager) {
this.scriptMapManager.recycle();
}
// 回收特效和UI
EffectManager.instance.recycleAll();
UIManager.instance.hideDialog("fight/fightPanel");
}
3.1.3.6 游戏状态控制
typescript
/**
* 开始游戏
*/
public startGame() {
GameManager.isGameStart = true;
GameManager.isGamePause = false;
// 开始玩家自动攻击
GameManager.scriptPlayer.startAttack();
// 开始怪物AI
for (let ndMonster of GameManager.arrMonster) {
let script = ndMonster.getComponent(Monster);
if (script) {
script.startAI();
}
}
}
/**
* 暂停游戏
*/
public pauseGame() {
GameManager.isGamePause = true;
// 暂停玩家攻击
GameManager.scriptPlayer.pauseAttack();
// 暂停怪物AI
for (let ndMonster of GameManager.arrMonster) {
let script = ndMonster.getComponent(Monster);
if (script) {
script.pauseAI();
}
}
// 显示暂停界面
UIManager.instance.showDialog("pause/pausePanel");
}
/**
* 继续游戏
*/
public resumeGame() {
GameManager.isGamePause = false;
// 继续玩家攻击
GameManager.scriptPlayer.resumeAttack();
// 继续怪物AI
for (let ndMonster of GameManager.arrMonster) {
let script = ndMonster.getComponent(Monster);
if (script) {
script.resumeAI();
}
}
// 隐藏暂停界面
UIManager.instance.hideDialog("pause/pausePanel");
}
/**
* 游戏结束
*/
public gameOver(isWin: boolean) {
GameManager.isGameOver = true;
GameManager.isWin = isWin;
GameManager.isGameStart = false;
// 停止玩家攻击
GameManager.scriptPlayer.stopAttack();
// 停止怪物AI
for (let ndMonster of GameManager.arrMonster) {
let script = ndMonster.getComponent(Monster);
if (script) {
script.stopAI();
}
}
// 分发游戏结束事件
ClientEvent.dispatchEvent(Constant.EVENT_TYPE.ON_GAME_OVER, isWin);
// 显示结算界面
UIManager.instance.showDialog("settlement/settlementPanel", [isWin]);
}
3.2 Player(玩家控制)
3.2.1 组件结构
Player (脚本)
├─ 引用组件:
│ ├─ scriptPlayerModel: PlayerModel // 模型控制
│ └─ rigidBody: CharacterRigid // 物理控制
│
├─ 属性:
│ ├─ playerBaseInfo // 基础配置
│ ├─ curAttackPower // 当前攻击力
│ ├─ curDefensePower // 当前防御力
│ ├─ curAttackSpeed // 当前攻击速度
│ ├─ curMoveSpeed // 当前移动速度
│ ├─ curHp // 当前血量
│ ├─ curHpLimit // 生命上限
│ └─ 技能状态标志(isArrowDouble等)
│
└─ 方法:
├─ init() // 初始化
├─ playAction() // 执行动作
├─ throwArrowToEnemy() // 射箭
├─ addBlood() // 增加血量
├─ reduceBlood() // 减少血量
└─ _parsePlayerSkill() // 解析技能
3.2.2 初始化逻辑
typescript
public init() {
// 获取基础配置
this.playerBaseInfo = LocalConfig.instance.queryByID("base", Constant.PLAYER_ID);
// 初始化属性(应用数值技能效果)
this._parsePlayerSkill();
// 设置初始血量
this.curHp = this.playerBaseInfo.hp;
// 初始化模型
this.scriptPlayerModel = this.node.getComponent(PlayerModel);
this.scriptPlayerModel.init(this.playerBaseInfo);
// 初始化物理
this.rigidBody = this.node.getComponent(CharacterRigid);
this.rigidBody.maxSpeed = this.curMoveSpeed;
// 开始自动攻击
this._startAttackSchedule();
// 监听事件
ClientEvent.on(Constant.EVENT_TYPE.PARSE_PLAYER_SKILL, this._parsePlayerSkill, this);
}
3.2.3 技能解析
typescript
private _parsePlayerSkill(isCoverSkill: boolean = false) {
let arrSkill = PlayerData.instance.playerInfo.arrSkill;
// 分类技能
let arrFormChangeSkill = arrSkill.filter(s => s.startsWith("1")); // 形态技能
let arrValueChangeSkill = arrSkill.filter(s => s.startsWith("2")); // 数值技能
let arrBuffSkill = arrSkill.filter(s => s.startsWith("3")); // Buff技能
let arrTriggerSkill = arrSkill.filter(s => s.startsWith("4")); // 触发技能
// 保存技能数组(用于显示)
this._arrFormChangeSkill = arrFormChangeSkill;
this._arrValueChangeSkill = arrValueChangeSkill;
this._arrBuffSkill = arrBuffSkill;
this._arrTriggerSkill = arrTriggerSkill;
// 设置技能标志
this.isArrowDouble = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_DOUBLE);
this.isArrowContinuous = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_CONTINUOUS);
this.isArrowUmbrella = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_UMBRELLA);
this.isArrowReverse = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_REVERSE);
this.isArrowSide = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_SIDE);
this.isArrowPenetrate = arrFormChangeSkill.includes(Constant.PLAYER_SKILL.ARROW_PENETRATE);
// Buff技能
this.isArrowIce = arrBuffSkill.includes(Constant.PLAYER_SKILL.ARROW_ICE);
this.isArrowFire = arrBuffSkill.includes(Constant.PLAYER_SKILL.ARROW_FIRE);
// 触发技能
this.isBloodthirsty = arrTriggerSkill.includes(Constant.PLAYER_SKILL.BLOODTHIRSTY);
this.isArrowLightning = arrTriggerSkill.includes(Constant.PLAYER_SKILL.ARROW_LIGHTNING);
this.isArrowLaunch = arrTriggerSkill.includes(Constant.PLAYER_SKILL.ARROW_LAUNCH);
// 应用数值技能效果
this._applyValueSkill();
}
private _applyValueSkill() {
// 攻击力提升
let attackRate = this._getValueSkillRate(Constant.PLAYER_SKILL.RAISE_ATTACK_01)
+ this._getValueSkillRate(Constant.PLAYER_SKILL.RAISE_ATTACK_02);
this.curAttackPower = this.playerBaseInfo.attackPower * (1 + attackRate);
// 攻击速度提升
let speedRate = this._getValueSkillRate(Constant.PLAYER_SKILL.RAISE_ATTACK_SPEED_01)
+ this._getValueSkillRate(Constant.PLAYER_SKILL.RAISE_ATTACK_SPEED_02);
this.curAttackSpeed = this.playerBaseInfo.attackSpeed * (1 + speedRate);
// 移动速度提升
let moveRate = this._getValueSkillRate(Constant.PLAYER_SKILL.MOVE_SPEED);
this.curMoveSpeed = this.playerBaseInfo.moveSpeed * (1 + moveRate);
// 生命上限提升
let hpRate = this._getValueSkillRate(Constant.PLAYER_SKILL.RAISE_HP_LIMIT);
this.curHpLimit = this.playerBaseInfo.hp * (1 + hpRate);
// 更新物理组件速度
if (this.rigidBody) {
this.rigidBody.maxSpeed = this.curMoveSpeed;
}
// 更新攻击计时器
this._updateAttackSchedule();
}
private _getValueSkillRate(skillId: string): number {
let skill = LocalConfig.instance.queryByID("playerSkill", skillId);
if (skill && this._arrValueChangeSkill.includes(skillId)) {
return Number(skill.value);
}
return 0;
}
3.2.4 攻击逻辑
typescript
private _startAttackSchedule() {
// 计算攻击间隔(毫秒)
let interval = 1000 / this.curAttackSpeed;
// 设置攻击计时器
this._attackSchedule = schedule(() => {
if (GameManager.isGameStart && !GameManager.isGamePause && !this.isDie) {
this._attackMonster();
}
}, interval / 1000);
}
private _updateAttackSchedule() {
// 清除旧计时器
if (this._attackSchedule) {
unschedule(this._attackSchedule);
}
// 重新启动计时器
this._startAttackSchedule();
}
private _attackMonster() {
// 获取最近的怪物
this._ndTarget = GameManager.getNearestMonster(this.node);
if (!this._ndTarget || this.isDie) return;
// 转向目标
this._moveToTargetWorPos(this._ndTarget.worldPosition);
// 播放攻击动画
this.scriptPlayerModel.playAni(Constant.PLAYER_ANI_TYPE.ATTACK, false, () => {
// 动画完成后自动射箭
this.throwArrowToEnemy();
// 如果不是连续攻击,继续寻找下一个目标
if (!this.scriptPlayerModel.isRunning) {
this._attackMonster();
}
});
}
public throwArrowToEnemy() {
if (!this._ndTarget || !isValid(this._ndTarget)) return;
// 设置朝向
Vec3.subtract(this._forWard, this._ndTarget.worldPosition, this.node.worldPosition);
this._forWard.y = 0;
this._forWard.normalize();
this.node.forward = this._forWard;
// 根据技能状态选择箭矢类型
let arrowType = this._getArrowType();
// 创建箭矢
ResourceUtil.loadModelRes(`weapon/arrow/${arrowType}`).then((prefab: Prefab) => {
let ndArrow = PoolManager.instance.getNode(prefab, this.node.parent);
// 设置箭矢初始位置(玩家前方)
let arrowStartPos = new Vec3();
Vec3.add(arrowStartPos, this.node.worldPosition,
new Vec3(this._forWard.x * 0.5, 0.2, this._forWard.z * 0.5));
ndArrow.setWorldPosition(arrowStartPos);
ndArrow.forward = this._forWard;
// 初始化箭矢组件
let scriptArrow = ndArrow.getComponent(Arrow);
scriptArrow.init({
speed: Constant.ARROW_SPEED,
damage: this.curAttackPower,
isPenetrate: this.isArrowPenetrate,
isIce: this.isArrowIce,
isFire: this.isArrowFire,
isLightning: this.isArrowLightning,
isLaunch: this.isArrowLaunch,
});
});
// 播放射箭音效
AudioManager.instance.playSound(Constant.SOUND_LOOSE);
}
private _getArrowType(): string {
// 根据技能组合选择箭矢类型
if (this.isArrowDouble) {
if (this.isArrowContinuous) {
return "arrowDoubleContinuous";
}
return "arrowDouble";
}
if (this.isArrowContinuous) {
return "arrowSingleContinuous";
}
return "arrowSingle";
}
3.2.5 移动控制
typescript
public playAction(obj: { x: number; z: number }) {
if (this.isDie) return;
// 设置移动方向
let direction = new Vec3(obj.x, 0, obj.z);
// 如果方向为0,停止移动
if (direction.length() < 0.01) {
this._stopMove();
return;
}
// 归一化方向
direction.normalize();
// 设置物理速度
this.rigidBody.setVelocity(direction.multiplyScalar(this.curMoveSpeed));
// 转向移动方向
this.node.forward = direction;
// 播放跑步动画
this.scriptPlayerModel.playRunAni(true);
}
private _stopMove() {
// 停止物理移动
this.rigidBody.setVelocity(Vec3.ZERO);
// 播放待机动画
this.scriptPlayerModel.playRunAni(false);
}
private _moveToTargetWorPos(targetPos: Vec3) {
// 计算方向
Vec3.subtract(this._offsetPos, targetPos, this.node.worldPosition);
this._offsetPos.y = 0;
// 如果距离小于攻击距离,不需要移动
if (this._offsetPos.length() < this._attackDistance) {
return;
}
// 设置朝向(不移动,只转向)
this._offsetPos.normalize();
this.node.forward = this._offsetPos;
}
3.2.6 血量管理
typescript
public addBlood(num: number, isLimit: boolean = true) {
let oldHp = this.curHp;
if (isLimit) {
this.curHp = Math.min(this.curHp + num, this.curHpLimit);
} else {
this.curHp += num;
}
// 通知UI更新
ClientEvent.dispatchEvent(Constant.EVENT_TYPE.PLAYER_ADD_BLOOD, this.curHp - oldHp);
// 更新血条
if (this.scriptBloodBar) {
this.scriptBloodBar.changeHp(this.curHp);
}
}
public reduceBlood(baseInfo: any, isShowTips: boolean = true) {
if (this.isDie) return;
// 计算伤害(攻击力 - 防御力)
let damage = Math.max(1, baseInfo.attackPower * GameManager.attackAddition - this.curDefensePower);
// 暴击检测
let isCritical = Math.random() < this.playerBaseInfo.criticalHitRate;
if (isCritical) {
damage *= this.playerBaseInfo.criticalHitDamage;
}
// 闪避检测
let isDodge = Math.random() < this.playerBaseInfo.dodgeRate;
if (isDodge) {
// 显示闪避提示
if (isShowTips) {
UIManager.instance.showBloodTips(Constant.BLOOD_TIPS_TYPE.DODGE, 0, this.node.worldPosition);
}
return;
}
// 减少血量
this.curHp -= damage;
// 显示伤害提示
if (isShowTips) {
UIManager.instance.showBloodTips(
isCritical ? Constant.BLOOD_TIPS_TYPE.CRITICAL_HIT : Constant.BLOOD_TIPS_TYPE.HIT_PLAYER,
damage,
this.node.worldPosition
);
}
// 更新血条
if (this.scriptBloodBar) {
this.scriptBloodBar.changeHp(this.curHp);
}
// 播放受击动画
this.scriptPlayerModel.playHitAni();
// 播放受击音效
AudioManager.instance.playSound(Constant.SOUND_HIT_PLAYER);
// 检查是否死亡
if (this.curHp <= 0) {
this.showDie();
}
}
public showDie() {
if (this.isDie) return;
this.isDie = true;
GameManager.isGameStart = false;
// 停止攻击
this.stopAttack();
// 播放死亡动画
this.scriptPlayerModel.playAni(Constant.PLAYER_ANI_TYPE.DIE, false);
// 播放死亡音效
AudioManager.instance.playSound(Constant.SOUND_DIE);
// 通知游戏结束
ClientEvent.dispatchEvent(Constant.EVENT_TYPE.PLAYER_DIE);
}
3.3 Monster(怪物控制)
3.3.1 组件结构
Monster (脚本)
├─ 引用组件:
│ ├─ scriptMonsterModel: MonsterModel // 模型控制
│ └─ rigidBody: CharacterRigid // 物理控制
│
├─ 属性:
│ ├─ baseInfo // 基础配置
│ ├─ layerInfo // 关卡配置
│ ├─ curHp // 当前血量
│ ├─ curMoveSpeed // 当前移动速度
│ ├─ curAttackSpeed // 当前攻击速度
│ ├─ isDie // 是否死亡
│ ├─ isMoving // 是否移动
│ ├─ _movePattern // 移动模式
│ ├─ _skillInfo // 当前技能
│ └─ _allSkillInfo // 所有技能
│
└─ 方法:
├─ init() // 初始化
├─ playAction() // 执行动作
├─ playHit() // 受击处理
├─ releaseSkillToPlayer() // 释放技能
└─ showDie() // 死亡处理
3.3.2 初始化逻辑
typescript
public init(baseInfo: any, layerInfo: any) {
// 保存配置
this.baseInfo = baseInfo;
this.layerInfo = layerInfo;
// 初始化属性(应用关卡加成)
this.curHp = baseInfo.hp * GameManager.hpAddition;
this.curMoveSpeed = baseInfo.moveSpeed * GameManager.moveSpeedAddition;
this.curAttackSpeed = baseInfo.attackSpeed * GameManager.attackSpeedAddition;
// 设置移动模式(优先使用关卡配置)
this._movePattern = layerInfo.movePattern || baseInfo.movePattern;
// 初始化技能
this._initSkill();
// 初始化模型
this.scriptMonsterModel = this.node.getComponent(MonsterModel);
this.scriptMonsterModel.init(baseInfo);
// 初始化物理
this.rigidBody = this.node.getComponent(CharacterRigid);
this.rigidBody.maxSpeed = this.curMoveSpeed;
// 显示血条(Boss和精英怪)
if (baseInfo.type === Constant.BASE_TYPE.BOSS || this.curHp > 500) {
UIManager.instance.showMonsterBloodBar(this, this.baseInfo.hp, GameManager.hpAddition);
}
// 开始AI
this._startAI();
}
private _initSkill() {
// 解析技能配置
if (this.layerInfo.skill && this.layerInfo.skill !== "") {
this._allSkillInfo = this.layerInfo.skill.split("#");
} else {
this._allSkillInfo = [];
}
// 选择初始技能
if (this._allSkillInfo.length > 0) {
this._skillIndex = 0;
this._skillInfo = LocalConfig.instance.queryByID("monsterSkill", this._allSkillInfo[0]);
}
}
3.3.3 AI逻辑
typescript
private _startAI() {
// 移动计时器
this._moveSchedule = schedule(() => {
if (GameManager.isGameStart && !GameManager.isGamePause && !this.isDie) {
this._monsterMove();
}
}, 0.5);
// 攻击计时器
let attackInterval = 1000 / this.curAttackSpeed;
this._attackSchedule = schedule(() => {
if (GameManager.isGameStart && !GameManager.isGamePause && !this.isDie) {
this._attackPlayer();
}
}, attackInterval / 1000);
}
private _monsterMove() {
if (!GameManager.ndPlayer || !isValid(GameManager.ndPlayer)) return;
switch (this._movePattern) {
case Constant.MONSTER_MOVE_PATTERN.RANDOM:
this._moveToRandomPos();
break;
case Constant.MONSTER_MOVE_PATTERN.FORWARD_PLAYER:
this._moveToTargetWorPos(GameManager.ndPlayer.worldPosition);
break;
case Constant.MONSTER_MOVE_PATTERN.NO_MOVE:
// 不移动,原地攻击
break;
}
}
private _moveToRandomPos() {
// 随机生成目标位置
let range = 5; // 移动范围
let randomX = (Math.random() - 0.5) * range * 2;
let randomZ = (Math.random() - 0.5) * range * 2;
let targetPos = new Vec3(
this._bornPos.x + randomX,
this._bornPos.y,
this._bornPos.z + randomZ
);
this._moveToTargetWorPos(targetPos);
}
private _moveToTargetWorPos(targetPos: Vec3) {
Vec3.subtract(this._offsetPos, targetPos, this.node.worldPosition);
this._offsetPos.y = 0;
// 如果距离太近,不移动
if (this._offsetPos.length() < 0.5) {
if (this.isMoving) {
this._stopMove();
}
return;
}
// 设置移动方向
this._offsetPos.normalize();
// 更新朝向
this.node.forward = this._offsetPos;
// 设置物理速度
this.rigidBody.setVelocity(this._offsetPos.multiplyScalar(this.curMoveSpeed));
// 播放跑步动画
this.scriptMonsterModel.playRunAni(true);
this.isMoving = true;
}
private _stopMove() {
this.rigidBody.setVelocity(Vec3.ZERO);
this.scriptMonsterModel.playRunAni(false);
this.isMoving = false;
}
3.3.4 攻击逻辑
typescript
private _attackPlayer() {
if (!GameManager.ndPlayer || GameManager.scriptPlayer.isDie) return;
// 计算与玩家的距离
Vec3.subtract(this._offsetPos_2, GameManager.ndPlayer.worldPosition, this.node.worldPosition);
this._offsetPos_2.y = 0;
let distance = this._offsetPos_2.length();
// 如果距离太远,不攻击
if (distance > this.baseInfo.attackRange) {
return;
}
// 转向玩家
this._offsetPos_2.normalize();
this.node.forward = this._offsetPos_2;
// 停止移动
this._stopMove();
// 播放攻击动画
this.scriptMonsterModel.playAni(Constant.MONSTER_ANI_TYPE.ATTACK, false, () => {
// 动画完成后释放技能
this.releaseSkillToPlayer();
// 切换到下一个技能
this._switchSkill();
});
}
public releaseSkillToPlayer() {
// 近战攻击(无技能时)
if (!this._allSkillInfo.length) {
let offsetLength = Util.getTwoNodeXZLength(this.node, GameManager.ndPlayer);
if (offsetLength <= this.baseInfo.attackRange * this.baseInfo.attackRange) {
GameManager.scriptPlayer.reduceBlood(this.baseInfo);
}
return;
}
// 远程技能攻击
ResourceUtil.loadEffectRes(`${this._skillInfo.resName}/${this._skillInfo.resName}`).then((prefab: Prefab) => {
let ndSkill = PoolManager.instance.getNode(prefab, GameManager.ndGameManager);
// 设置技能位置和方向
ndSkill.setWorldPosition(this.node.worldPosition);
ndSkill.forward = this.node.forward;
// 根据技能类型初始化
let scriptSkill = ndSkill.getComponent(MonsterSkillBase);
if (scriptSkill) {
scriptSkill.init(this._skillInfo, this.baseInfo, this);
}
});
}
private _switchSkill() {
// 循环切换技能
if (this._allSkillInfo.length > 1) {
this._skillIndex = (this._skillIndex + 1) % this._allSkillInfo.length;
this._skillInfo = LocalConfig.instance.queryByID("monsterSkill", this._allSkillInfo[this._skillIndex]);
}
}
3.3.5 受击与死亡处理
typescript
public playHit(isArrowLaunch: boolean = false) {
if (this.isDie) return;
// 播放受击动画
this.scriptMonsterModel.playHitAni();
// 如果是弹射箭矢,击退怪物
if (isArrowLaunch) {
let direction = Vec3.subtract(new Vec3(), this.node.worldPosition, GameManager.ndPlayer.worldPosition);
direction.y = 0;
direction.normalize();
this.rigidBody.setVelocity(direction.multiplyScalar(2));
scheduleOnce(() => {
this._stopMove();
}, 0.3);
}
}
public reduceBlood(damage: number, isShowTips: boolean = true) {
if (this.isDie) return;
this.curHp -= damage;
// 显示伤害提示
if (isShowTips) {
UIManager.instance.showBloodTips(Constant.BLOOD_TIPS_TYPE.HIT_MONSTER, damage, this.node.worldPosition);
}
// 更新血条
if (this.scriptBloodBar) {
this.scriptBloodBar.changeHp(this.curHp);
}
// 检查是否死亡
if (this.curHp <= 0) {
this.showDie();
}
}
public showDie() {
if (this.isDie) return;
this.isDie = true;
// 停止AI
this.stopAI();
// 停止移动
this._stopMove();
// 播放死亡动画
this.scriptMonsterModel.playAni(Constant.MONSTER_ANI_TYPE.DIE, false, () => {
// 动画完成后回收
this._recycleSelf();
});
// 播放死亡音效
AudioManager.instance.playSound(Constant.SOUND_MONSTER_DIE);
// 掉落奖励
this._dropReward();
// 从怪物数组中移除
let idx = GameManager.arrMonster.indexOf(this.node);
if (idx >= 0) {
GameManager.arrMonster.splice(idx, 1);
}
// 检查是否胜利
this._checkWin();
}
private _dropReward() {
// 掉落金币
let goldNum = Math.floor(this.baseInfo.goldNum * (0.8 + Math.random() * 0.4));
if (goldNum > 0) {
// 创建金币奖励
ResourceUtil.loadModelRes("reward/gold").then((prefab: Prefab) => {
let ndGold = PoolManager.instance.getNode(prefab, GameManager.ndGameManager);
ndGold.setWorldPosition(this.node.worldPosition);
let scriptGold = ndGold.getComponent(Gold);
scriptGold.init(goldNum);
});
// 更新玩家金币
PlayerData.instance.updatePlayerInfo('gold', goldNum);
}
// 掉落爱心(概率)
if (Math.random() < this.baseInfo.heartDropRate) {
ResourceUtil.loadModelRes("reward/heart").then((prefab: Prefab) => {
let ndHeart = PoolManager.instance.getNode(prefab, GameManager.ndGameManager);
ndHeart.setWorldPosition(this.node.worldPosition);
let scriptHeart = ndHeart.getComponent(Heart);
scriptHeart.init();
});
}
}
private _checkWin() {
// 检查是否所有怪物都已死亡
if (GameManager.arrMonster.length === 0) {
GameManager.gameOver(true);
}
}
private _recycleSelf() {
// 回收血条
if (this.scriptBloodBar) {
PoolManager.instance.putNode(this.scriptBloodBar.node);
this.scriptBloodBar = null;
}
// 回收怪物节点
PoolManager.instance.putNode(this.node);
}
3.4 MapManager(地图管理)
3.4.1 功能定位
MapManager 负责加载和管理关卡地图,包括:
- 解析地图配置文件
- 创建怪物、NPC、道具等实体
- 管理传送门(关卡切换)
3.4.2 核心数据结构
typescript
private _dictModuleType: { [key: string]: any[] } = {}; // 按类型分组的实体配置
private _arrMap: any[]; // 地图配置数组
3.4.3 地图构建流程
typescript
public buildMap(mapName: string, progressCb?: Function, completeCb?: Function) {
// 清空之前的配置
this._dictModuleType = {};
// 获取地图配置
this._arrMap = LocalConfig.instance.getTableArr(mapName);
// 按类型分组
for (let item of this._arrMap) {
let baseInfo = LocalConfig.instance.queryByID("base", item.ID);
if (!baseInfo) continue;
let type = baseInfo.type;
if (!this._dictModuleType[type]) {
this._dictModuleType[type] = [];
}
this._dictModuleType[type].push(item);
}
// 批量创建各类型实体
let arrPromise: Promise<void>[] = [];
for (const type in this._dictModuleType) {
if (this._dictModuleType[type].length > 0) {
arrPromise.push(this._buildModel(type));
}
}
// 等待所有实体创建完成
Promise.all(arrPromise).then(() => {
// 显示传送门(最后一关不显示)
let level = PlayerData.instance.playerInfo.level;
if (level < Constant.MAX_LEVEL) {
this._showWarpGate();
}
progressCb && progressCb(1);
completeCb && completeCb();
});
}
private _buildModel(type: string): Promise<void> {
return new Promise((resolve) => {
let objItems = this._dictModuleType[type];
let createCount = 0;
for (let layerInfo of objItems) {
let baseInfo = LocalConfig.instance.queryByID("base", layerInfo.ID);
let modelPath = `${type}/${baseInfo.resName}`;
ResourceUtil.loadModelRes(modelPath).then((prefab: Prefab) => {
// 创建父节点(按类型分组)
let parentName = type + 'Group';
let ndParent = this.node.getChildByName(parentName);
if (!ndParent) {
ndParent = new Node(parentName);
this.node.addChild(ndParent);
}
// 创建实体
let ndChild = PoolManager.instance.getNode(prefab, ndParent);
// 设置位置(优先使用关卡配置,否则使用基础配置)
let pos = layerInfo.position ?
Util.parseVector3(layerInfo.position) :
Util.parseVector3(baseInfo.position);
ndChild.setPosition(pos);
// 设置角度
let angle = layerInfo.angle ?
Util.parseVector3(layerInfo.angle) :
Util.parseVector3(baseInfo.angle);
ndChild.eulerAngles = angle;
// 设置缩放
let scale = layerInfo.scale ?
Util.parseVector3(layerInfo.scale) :
Util.parseVector3(baseInfo.scale);
ndChild.setScale(scale);
// 保存出生位置(用于怪物随机移动)
if (type === Constant.BASE_TYPE.MONSTER || type === Constant.BASE_TYPE.BOSS) {
ndChild.setUserData('bornPos', ndChild.worldPosition.clone());
}
// 初始化组件
this._initComponent(ndChild, type, baseInfo, layerInfo);
createCount++;
if (createCount >= objItems.length) {
resolve();
}
});
}
});
}
private _initComponent(ndChild: Node, type: string, baseInfo: any, layerInfo: any) {
switch (type) {
case Constant.BASE_TYPE.MONSTER:
let scriptMonster = ndChild.getComponent(Monster);
scriptMonster.init(baseInfo, layerInfo);
GameManager.arrMonster.push(ndChild);
break;
case Constant.BASE_TYPE.BOSS:
let scriptBoss = ndChild.getComponent(Boss);
scriptBoss.init(baseInfo, layerInfo);
GameManager.arrMonster.push(ndChild);
GameManager.ndBoss = ndChild;
GameManager.scriptBoss = scriptBoss;
break;
case Constant.BASE_TYPE.HEART:
let scriptHeart = ndChild.getComponent(Heart);
scriptHeart.init();
break;
case Constant.BASE_TYPE.NPC:
let scriptNPC = ndChild.getComponent(NPC);
scriptNPC.init(baseInfo);
break;
}
}
3.4.4 传送门管理
typescript
private _showWarpGate() {
ResourceUtil.loadModelRes("map/warpGate").then((prefab: Prefab) => {
let ndWarpGate = PoolManager.instance.getNode(prefab, this.node);
// 设置传送门位置(地图中心偏右)
ndWarpGate.setPosition(8, 0, 0);
// 初始化传送门组件
let scriptWarpGate = ndWarpGate.getComponent(WarpGate);
scriptWarpGate.init();
// 保存传送门引用
this._ndWarpGate = ndWarpGate;
});
}
public enterNextLevel() {
// 隐藏传送门
if (this._ndWarpGate && isValid(this._ndWarpGate)) {
PoolManager.instance.putNode(this._ndWarpGate);
this._ndWarpGate = null;
}
// 进入下一关
let level = PlayerData.instance.playerInfo.level;
let mapName = LocalConfig.instance.queryByID("checkpoint", String(level)).mapName;
let arrMap = mapName.split("#");
// 更新当前地图索引
GameManager.scriptGameManager.curMapIndex++;
// 如果还有地图,加载下一个
if (GameManager.scriptGameManager.curMapIndex < arrMap.length) {
UIManager.instance.showDialog("loading/loadingPanel", [], () => {
this.recycle();
this.buildMap(arrMap[GameManager.scriptGameManager.curMapIndex], () => {
UIManager.instance.hideDialog("loading/loadingPanel");
});
});
} else {
// 所有地图完成,进入下一关
PlayerData.instance.updatePlayerInfo('level', 1);
PlayerData.instance.updatePlayerInfo('gold', 100); // 通关奖励
// 重新初始化游戏
ClientEvent.dispatchEvent(Constant.EVENT_TYPE.ON_GAME_INIT);
}
}
public recycle() {
// 回收所有子节点
for (let i = this.node.children.length - 1; i >= 0; i--) {
let child = this.node.children[i];
if (child.name !== "mapGroup") { // 保留地图场景
PoolManager.instance.putNode(child);
}
}
// 清空分组
this._dictModuleType = {};
}
3.5 Arrow(箭矢控制)
3.5.1 组件结构
Arrow (脚本)
├─ 引用组件:
│ └─ collider: ColliderComponent // 碰撞组件
│
├─ 属性:
│ ├─ _speed // 飞行速度
│ ├─ _damage // 伤害值
│ ├─ _isPenetrate // 是否穿透
│ ├─ _isIce // 是否冰冻
│ ├─ _isFire // 是否灼烧
│ ├─ _isLightning // 是否闪电
│ └─ _isLaunch // 是否弹射
│
└─ 方法:
├─ init() // 初始化
├─ _update() // 更新飞行
├─ _onCollisionEnter() // 碰撞检测
└─ _destroyArrow() // 销毁箭矢
3.5.2 核心逻辑
typescript
public init(data: {
speed: number;
damage: number;
isPenetrate?: boolean;
isIce?: boolean;
isFire?: boolean;
isLightning?: boolean;
isLaunch?: boolean;
}) {
// 设置属性
this._speed = data.speed;
this._damage = data.damage;
this._isPenetrate = data.isPenetrate || false;
this._isIce = data.isIce || false;
this._isFire = data.isFire || false;
this._isLightning = data.isLightning || false;
this._isLaunch = data.isLaunch || false;
// 设置飞行时间(防止无限飞行)
this._flyTime = 0;
// 启用碰撞检测
this.colliderCom = this.node.getComponent(ColliderComponent);
this.colliderCom.on('onCollisionEnter', this._onCollisionEnter, this);
// 开始飞行
this._isFlying = true;
}
protected update(deltaTime: number) {
if (!this._isFlying) return;
// 更新飞行时间
this._flyTime += deltaTime;
// 飞行超时检测
if (this._flyTime > Constant.ARROW_MAX_FLY_TIME) {
this._destroyArrow();
return;
}
// 向前移动
let moveDistance = this._speed * deltaTime;
this.node.translate(new Vec3(0, 0, moveDistance));
}
private _onCollisionEnter(other: ColliderComponent) {
let group = other.getGroup();
// 击中怪物
if (group === Constant.PHY_GROUP.MONSTER || group === Constant.PHY_GROUP.BOSS) {
let scriptMonster = other.node.getComponent(Monster) || other.node.getComponent(Boss);
// 造成伤害
scriptMonster.reduceBlood(this._damage);
// 应用技能效果
this._applySkillEffect(scriptMonster);
// 播放击中特效
this._playHitEffect();
// 如果不是穿透,销毁箭矢
if (!this._isPenetrate) {
this._destroyArrow();
return;
}
}
// 击中障碍物
if (group === Constant.PHY_GROUP.OBSTACLE) {
// 播放撞击特效
this._playHitEffect();
if (!this._isPenetrate) {
this._destroyArrow();
}
}
// 击中玩家(箭矢反弹)
if (group === Constant.PHY_GROUP.PLAYER) {
// 玩家箭矢不应击中自己
return;
}
}
private _applySkillEffect(scriptMonster: Monster | Boss) {
// 冰冻效果
if (this._isIce) {
scriptMonster.addBuff(Constant.BUFF_TYPE.ICE, 2);
}
// 灼烧效果
if (this._isFire) {
scriptMonster.addBuff(Constant.BUFF_TYPE.FIRE, 3);
}
// 闪电链效果
if (this._isLightning) {
this._triggerLightning(scriptMonster);
}
// 弹射效果(已在Monster.playHit中处理)
}
private _triggerLightning(scriptMonster: Monster | Boss) {
// 获取附近的怪物
let arrNearby = GameManager.getNearbyMonster(scriptMonster.node, 5);
// 对每个附近怪物造成伤害
for (let ndMonster of arrNearby) {
if (ndMonster === scriptMonster.node) continue;
let targetScript = ndMonster.getComponent(Monster) || ndMonster.getComponent(Boss);
if (targetScript && !targetScript.isDie) {
// 闪电伤害为原伤害的50%
targetScript.reduceBlood(this._damage * 0.5);
// 播放闪电特效
EffectManager.instance.showLightning(scriptMonster.node.worldPosition, ndMonster.worldPosition);
}
}
}
private _playHitEffect() {
// 播放击中特效
ResourceUtil.loadEffectRes("hit/hit").then((prefab: Prefab) => {
let ndEffect = PoolManager.instance.getNode(prefab, this.node.parent);
ndEffect.setWorldPosition(this.node.worldPosition);
// 3秒后回收特效
scheduleOnce(() => {
PoolManager.instance.putNode(ndEffect);
}, 3);
});
}
private _destroyArrow() {
this._isFlying = false;
// 移除碰撞监听
this.colliderCom.off('onCollisionEnter', this._onCollisionEnter, this);
// 回收箭矢
PoolManager.instance.putNode(this.node);
}
3.6 战斗流程总结
┌─────────────────────────────────────────────────────────────────┐
│ 战斗流程 │
└─────────────────────────────────────────────────────────────────┘
│
▼
游戏初始化 (_onGameInit)
│
▼
加载关卡配置 (checkpoint.csv)
│
▼
设置敌人属性加成
│
▼
┌───────────────┴───────────────┐
▼ ▼
创建玩家节点 构建地图 (MapManager)
│ │
▼ ▼
初始化玩家属性 创建怪物/NPC/道具
│ │
└───────────────┬───────────────┘
▼
开始游戏 (startGame)
│
┌────────────────┼────────────────┐
▼ ▼ ▼
玩家自动攻击 怪物AI启动 显示战斗UI
│ │
▼ ▼
射箭 → 碰撞检测 移动 → 攻击 → 技能释放
│ │
▼ ▼
怪物受伤/死亡 玩家受伤/死亡
│ │
▼ ▼
掉落奖励 游戏结束判断
│ │
└────────────────┼────────────────┘
▼
检查胜利条件(所有怪物死亡)
│
┌───────────────┴───────────────┐
▼ ▼
胜利 失败
│ │
▼ ▼
进入下一关 显示复活界面
总结
战斗系统是游戏的核心玩法,主要包含:
- GameManager:全局游戏状态管理
- Player:玩家角色控制(移动、攻击、技能)
- Monster:怪物AI控制(移动、攻击、技能)
- MapManager:地图加载和实体管理
- Arrow:箭矢飞行和碰撞检测
这些模块协同工作,实现了完整的战斗逻辑。理解战斗系统对于扩展游戏玩法至关重要。