--- name: dota2-unit description: Dota 2 自定义单位编写指南。适用于编写/修改单位 KV 配置文件(`game/scripts/npc/custom_units/`)。触发词:单位、unit、creature、怪物、召唤物、建筑、塔、npc。 --- 为 DOTA2 自定义游戏项目编写 Dota 2 自定义单位。 ## 核心原则 1. **配置与代码分离**:所有可能与前端共享或需要集中管理的配置、数值、常量(包括但不限于单位属性、技能、物品数据、游戏规则参数等),都必须放在 `game/scripts/npc/` 下的 KV 文件中。禁止在 TSTL 代码或 UI 代码中硬编码任何可能变化的配置值。修改配置后需运行 `npx gulp kv_2_js` 刷新 JSON,保证前后端数据一致 ## 文件结构 | 文件 | 路径 | 用途 | |---|---|---| | 单位 KV | `game/scripts/npc/custom_units/<单位名>.txt` | 单位 KV 键值对(手动创建的自定义单位) | | KV 入口 | `game/scripts/npc/npc_units_custom.txt` | 引用 `#base "custom_units.txt"`,自动生成部分由 Excel 管理 | | Excel 数据 | `excels/单位表.xlsx` | 自动生成 `custom_units.txt`(请勿修改) | | 单位逻辑 | `game/scripts/src/abilities/<模块名>/<关联技能>.ts` | 单位关联的技能逻辑(如召唤物技能),制作技能请使用 dota2-ability skill 的工作流 | | 本地化 | `game/resource/addon.csv` | 单位名称 | ## 工作流 ### 步骤 1:确认单位需求 - 明确单位类型(生物/召唤物/建筑/塔/辅助单位) - 确认模型的来源路径 - 确认单位需要的技能、属性、攻击方式 - 确认单位是否需要自定义 TSTL 逻辑 ### 步骤 2:编写 KV 配置 在 `game/scripts/npc/custom_units/<单位名>.txt` 中创建 KV 文件,或在 `excels/单位表.xlsx` 中添加行(由 Gulp 自动生成到 `custom_units.txt`)。 KV 文件必须在 `npc_units_custom.txt` 中通过 `#base` 引用后生效。 单位名必须以 `npc_dota_` 开头。 **KV 结构规则**:单位名称必须在第一级,所有属性写在第二级的 `{}` 内: ```kv "npc_dota_unit_mymonster" { "BaseClass" "npc_dota_creature" "Model" "models/heroes/earthshaker/earthshaker.vmdl" "MovementCapabilities" "DOTA_UNIT_CAP_MOVE_GROUND" "MovementSpeed" "325" "StatusHealth" "1000" } ``` ### 步骤 2b:编译 KV 到 JSON(必须执行) 修改 KV 文件后,必须运行以下命令将 KV 编译为 JSON,供前后端统一引用: ```bash # 编译所有 KV 文件为 JSON(输出到 panorama 和 src 两个目录) npx gulp kv_2_js # 或快捷命令(同时执行 sheet→kv 和 kv→js) npx gulp jssync ``` 编译后输出到: - `content/panorama/src/json/` — 前端 Panorama 使用 - `game/scripts/src/json/` — 后端 TSTL 使用 **前后端数据分离原则**: - 所有游戏数值配置(属性、伤害、CD、范围等)写在 KV 文件中 - 前后端都从编译后的 JSON 文件中读取同一份数据 - 禁止在前端或后端代码中硬编码数值常量,避免数据不一致 ### 步骤 3:编写关联的技能逻辑(如需要) 如果单位拥有自定义技能,在 `game/scripts/src/abilities/<模块名>/` 中创建技能源码,按 dota2-ability skill 的工作流编写。 ### 步骤 4:添加本地化 在 `game/resource/addon.csv` 中添加: - `npc_dota_unit_<单位名>` → 单位显示名 ### 步骤 5:验证 - 进入游戏测试单位属性、行为、技能 ## KV 键值参考 ### 通用键 | 键名 | 说明 | 示例值 | |---|---|---| | `BaseClass` | 基类 | `npc_dota_creature` / `npc_dota_tower` / `npc_dota_creep_lane` / `npc_dota_building` | | `Model` | 模型路径 | `models/heroes/earthshaker/earthshaker.vmdl` | | `ModelScale` | 模型缩放 | `1.0` | | `SoundSet` | 声音集 | `Hero_EarthShaker` / `Creep_Bad_Melee` / `Tower.Water` | | `Level` | 等级 | `5` | | `IsNeutralUnitType` | 是否中立单位 | `1` | | `ConsideredHero` | 是否视为英雄 | `1` | | `GameSoundsFile` | 游戏声音文件 | `soundevents/game_sounds_heroes/game_sounds_earthshaker.vsndevts` | | `VoiceFile` | 语音文件 | `soundevents/voscripts/game_sounds_vo_earthshaker.vsndevts` | ### 碰撞与体积 | 键名 | 说明 | 示例值 | |---|---|---| | `BoundsHullName` | 碰撞体大小 | `DOTA_HULL_SIZE_HERO` / `DOTA_HULL_SIZE_SMALL` / `DOTA_HULL_SIZE_REGULAR` / `DOTA_HULL_SIZE_LARGE` | | `RingRadius` | 环半径 | `70` | | `HealthBarOffset` | 血条偏移 | `200` | ### 移动 | 键名 | 说明 | 示例值 | |---|---|---| | `MovementCapabilities` | 移动能力 | `DOTA_UNIT_CAP_MOVE_GROUND` / `DOTA_UNIT_CAP_MOVE_FLY` / `DOTA_UNIT_CAP_MOVE_NONE` | | `MovementSpeed` | 移动速度 | `325` | | `MovementTurnRate` | 转身速率 | `0.5` | | `HasAggressiveStance` | 攻击姿态 | `0` | ### 技能 | 键名 | 说明 | 示例值 | |---|---|---| | `Ability1` | 技能槽 1 | `earthshaker_fissure` | | `Ability2` | 技能槽 2 | `` | | `Ability3` | 技能槽 3 | `` | | `Ability4` | 技能槽 4 | `` | | `Ability5` | 技能槽 5 | `` | | `Ability6` | 技能槽 6(额外) | `` | | `Ability7` | 技能槽 7(额外) | `` | | `Ability8` | 技能槽 8(额外) | `` | ### 护甲与抗性 | 键名 | 说明 | 示例值 | |---|---|---| | `ArmorPhysical` | 物理护甲 | `5` | | `MagicalResistance` | 魔法抗性 | `0` / `25` | ### 攻击 | 键名 | 说明 | 示例值 | |---|---|---| | `AttackCapabilities` | 攻击能力 | `DOTA_UNIT_CAP_MELEE_ATTACK` / `DOTA_UNIT_CAP_RANGED_ATTACK` / `DOTA_UNIT_CAP_NO_ATTACK` | | `AttackDamageMin` | 最小攻击力 | `50` | | `AttackDamageMax` | 最大攻击力 | `60` | | `AttackDamageType` | 攻击伤害类型 | `DAMAGE_TYPE_ArmorPhysical` | | `AttackRate` | 攻击速率 | `1.5` | | `AttackAnimationPoint` | 攻击动画点 | `0.3` | | `AttackAcquisitionRange` | 攻击获取范围 | `600` | | `AttackRange` | 攻击范围 | `130` / `500` | | `ProjectileModel` | 弹道模型 | `particles/base_attacks/ranged_badguy.vpcf` | | `ProjectileSpeed` | 弹道速度 | `900` | ### 属性 | 键名 | 说明 | 示例值 | |---|---|---| | `AttributePrimary` | 主属性 | `DOTA_ATTRIBUTE_STRENGTH` / `DOTA_ATTRIBUTE_AGILITY` / `DOTA_ATTRIBUTE_INTELLECT` / `DOTA_ATTRIBUTE_ALL` | | `AttributeBaseStrength` | 基础力量 | `21` | | `AttributeStrengthGain` | 力量成长 | `1.6` | | `AttributeBaseAgility` | 基础敏捷 | `24` | | `AttributeAgilityGain` | 敏捷成长 | `2.8` | | `AttributeBaseIntelligence` | 基础智力 | `12` | | `AttributeIntelligenceGain` | 智力成长 | `1.8` | ### 状态 | 键名 | 说明 | 示例值 | |---|---|---| | `StatusHealth` | 基础生命值 | `500` | | `StatusHealthRegen` | 生命回复 | `2` | | `StatusMana` | 基础魔法值 | `100` | | `StatusManaRegen` | 魔法回复 | `0.75` | | `StatusStartingMana` | 初始魔法 | `0` | ### 赏金 | 键名 | 说明 | 示例值 | |---|---|---| | `BountyXP` | 经验赏金 | `22` | | `BountyGoldMin` | 最小金钱赏金 | `19` | | `BountyGoldMax` | 最大金钱赏金 | `25` | ### 视野 | 键名 | 说明 | 示例值 | |---|---|---| | `VisionDaytimeRange` | 白天视野 | `1800` | | `VisionNighttimeRange` | 夜晚视野 | `800` | ### 队伍 | 键名 | 说明 | 示例值 | |---|---|---| | `TeamName` | 队伍 | `DOTA_TEAM_GOODGUYS` / `DOTA_TEAM_BADGUYS` / `DOTA_TEAM_NEUTRALS` | | `CombatClassAttack` | 攻击战斗类 | `DOTA_COMBAT_CLASS_ATTACK_HERO` / `DOTA_COMBAT_CLASS_ATTACK_BASIC` | | `CombatClassDefend` | 防御战斗类 | `DOTA_COMBAT_CLASS_DEFEND_HERO` / `DOTA_COMBAT_CLASS_DEFEND_BASIC` | | `UnitRelationshipClass` | 单位关系类 | `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT` / `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_HERO` | ### 装饰与外观 | 键名 | 说明 | 示例值 | |---|---|---| | `HasInventory` | 是否有物品栏 | `1` | | `Creature` | 生物配置 | (子键:`AttachWearables`、`DefaultState` 等) | | `AttachWearables` | 装饰品 | (子键:`"1" { "ItemDef" "123" }`) | | `particle_folder` | 粒子文件夹 | `particles/units/heroes/hero_earthshaker` | | `skin` | 皮肤 | `0` | | `wearable` | 装饰 | (子键) | | `OverrideWearableClass` | 覆盖装饰类 | `dota_item_wearable` | ### 其他键 | 键名 | 说明 | 示例值 | |---|---|---| | `SelectionGroup` | 选择组 | `1` | | `SelectOnSpawn` | 生成时选中 | `1` | | `MinimapIcon` | 小地图图标 | `minimap_icon_tower` | | `MinimapIconSize` | 小地图图标大小 | `1000` | | `WakesNeutrals` | 唤醒中立生物 | `1` | | `UnitLabel` | 单位标签 | `custom` | | `UnitToAbilityMap` | 单位到技能映射 | (子键) | | `ScaleSet` | 缩放集 | `0` | | `PathfindingSearchDepthScale` | 寻路深度缩放 | `1` | | `ProjectileCollisionSize` | 弹道碰撞大小 | `50` | | `precache` | 预缓存 | (子键:`particle`、`soundfile`、`model`) | ### AttackCapabilities 常量 | 常量 | 说明 | |---|---| | `DOTA_UNIT_CAP_NO_ATTACK` | 无攻击能力 | | `DOTA_UNIT_CAP_MELEE_ATTACK` | 近战攻击 | | `DOTA_UNIT_CAP_RANGED_ATTACK` | 远程攻击 | ### MovementCapabilities 常量 | 常量 | 说明 | |---|---| | `DOTA_UNIT_CAP_MOVE_NONE` | 不可移动 | | `DOTA_UNIT_CAP_MOVE_GROUND` | 地面移动 | | `DOTA_UNIT_CAP_MOVE_FLY` | 飞行移动 | ### BoundsHullName 常量 | 常量 | 说明 | |---|---| | `DOTA_HULL_SIZE_SMALL` | 小碰撞体(小兵) | | `DOTA_HULL_SIZE_REGULAR` | 普通碰撞体 | | `DOTA_HULL_SIZE_HERO` | 英雄碰撞体 | | `DOTA_HULL_SIZE_LARGE` | 大碰撞体 | | `DOTA_HULL_SIZE_BUILDING` | 建筑碰撞体 | ### TeamName 常量 | 常量 | 说明 | |---|---| | `DOTA_TEAM_GOODGUYS` | 天辉 | | `DOTA_TEAM_BADGUYS` | 夜魇 | | `DOTA_TEAM_NEUTRALS` | 中立 | ### CombatClass 常量 | 常量 | 说明 | |---|---| | `DOTA_COMBAT_CLASS_ATTACK_HERO` | 英雄攻击 | | `DOTA_COMBAT_CLASS_ATTACK_BASIC` | 基础攻击 | | `DOTA_COMBAT_CLASS_ATTACK_PIERCE` | 穿透攻击 | | `DOTA_COMBAT_CLASS_ATTACK_SIEGE` | 攻城攻击 | | `DOTA_COMBAT_CLASS_DEFEND_HERO` | 英雄防御 | | `DOTA_COMBAT_CLASS_DEFEND_BASIC` | 基础防御 | | `DOTA_COMBAT_CLASS_DEFEND_SOFT` | 软甲防御 | | `DOTA_COMBAT_CLASS_DEFEND_STRUCTURE` | 建筑防御 | ### UnitRelationshipClass 常量 | 常量 | 说明 | |---|---| | `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_DEFAULT` | 默认 | | `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_HERO` | 英雄 | | `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_BARRACKS` | 兵营 | | `DOTA_NPC_UNIT_RELATIONSHIP_TYPE_BUILDING` | 建筑 | ## TSTL 编程 如果单位拥有自定义技能,技能逻辑按 dota2-ability skill 工作流编写。 单位可以拥有 vscript 文件,用于规定单位的行为,或者用于制作AI: 具体函数需要使用诸如: ``` Object.assign(getfenv(), { Spawn: callback }); ``` 这样的方式方法注册到单位的脚本文件中 ### 单位创建 API | API | 用途 | |---|---| | `CreateUnitByName(unitName, location, bFindClearSpace, hNpcOwner, hPlayerOwner)` | 创建单位 | | `unit.Kill(hAbility, hAttacker)` | 杀死单位 | | `unit.RemoveSelf()` | 移除单位 | | `unit.SetTeam(teamNumber)` | 设置队伍 | | `unit.SetOwner(hPlayer)` | 设置所有者 | | `unit.SetControllableByPlayer(playerID, bControllable)` | 设置可控制 | | `unit.AddAbility(abilityName)` | 添加技能 | | `unit.RemoveAbility(abilityName)` | 移除技能 | | `unit.SetHealth(value)` | 设置生命值 | | `unit.SetMaxHealth(value)` | 设置最大生命值 | | `unit.SetMana(value)` | 设置魔法值 | | `unit.SetBaseMoveSpeed(speed)` | 设置移动速度 | | `unit.SetAcquisitionRange(range)` | 设置攻击获取范围 | | `unit.SetAttackCapability(capability)` | 设置攻击能力 | | `unit.SetMoveCapability(capability)` | 设置移动能力 | | `unit.AddNewModifier(caster, ability, modifierName, kv)` | 添加 modifier | ### 单位查询 API | API | 用途 | |---|---| | `unit.GetTeamNumber()` | 获取队伍号 | | `unit.GetOrigin()` | 获取位置 | | `unit.GetAbsOrigin()` | 获取绝对位置 | | `unit.GetHealth()` | 获取生命值 | | `unit.GetMaxHealth()` | 获取最大生命值 | | `unit.GetMana()` | 获取魔法值 | | `unit.GetBaseMoveSpeed()` | 获取移动速度 | | `unit.GetAttackDamage()` | 获取攻击力 | | `unit.GetArmor()` | 获取护甲 | | `unit.GetMagicalArmorValue()` | 获取魔抗 | | `unit.IsAlive()` | 是否存活 | | `unit.IsHero()` | 是否英雄 | | `unit.IsCreature()` | 是否生物 | | `unit.IsBuilding()` | 是否建筑 | | `unit.IsAncient()` | 是否远古 | | `unit.IsBoss()` | 是否 Boss | | `unit.IsNeutralUnitType()` | 是否中立 | | `unit.HasAbility(abilityName)` | 是否有技能 | | `unit.FindAbilityByName(abilityName)` | 查找技能 | | `unit.GetUnitName()` | 获取单位名 |