Compare commits

...
274 Commits
Author SHA1 Message Date
joywayer 9aaa41e420 meta 2026-08-03 14:53:45 +08:00
joywayerandClaude Opus 5 d61f10ee80 merge: DataHub 敌人能力总览表格
补回删除旧 Boss 轨时丢掉的能力资产编辑器视图(spec §6.4)。
做成跨敌人对比表而非标准列表+详情:能力按敌人分目录存放,
逐个点开看不出问题,需要的是横向比射程/权重/冷却。

问题列复用资产自己的 Validate(),规则只有一处;不提供新建,
类型化子类与目标目录由角色向导保证。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:38:25 +08:00
joywayerandClaude Opus 5 bb577f01a8 feat(editor): DataHub 新增敌人能力总览表格,补回删旧 Boss 轨时丢的编辑器视图
删除 BossSkillModule / BossSkillSequenceWindow 后能力资产没有任何编辑器总览
(spec §6.4 记为待补)。本次补上,但不照搬「列表 + 详情」的标准形态:

能力资产按敌人分散在 Data/Enemies/{敌人}/Abilities/ 下,逐个点开看不出问题;
真正需要的是横向对比——同一敌人的招之间射程/权重/冷却是否成比例、有没有
Attack 类却射程为 0 的死招。所以做成一张跨敌人的对比表,表格本身就是导航:
点行即选中资产,Inspector 出现在表格下方。列均可点击排序。

两点刻意的设计:

- 问题列直接调资产自己的 Validate(),不在表格里另写一份判定规则。否则 SO 侧
  改了规则,表格会继续按旧规则报。
- 不提供新建。能力有类型化子类且必须落到对应敌人目录,这两件事由角色向导保证
  (CLAUDE.md 第 2 条不裸建);模块只给一个跳转向导的按钮。

取数与排序抽成纯函数(EnemyAbilityOverview)与 UI 分离:UIElements 构建不可单测,
而表格真正会出错的是「归属怎么从路径推导」「问题从哪来」「排序对不对」,
这三处都是纯逻辑,抽出来即可覆盖。

spec 里还提到「阶段一览」,本次不做且不打算做:阶段现在归 BossPhaseAbilityGate
(预制体上的组件),不在 SO 上,配方/资产层看不到它——与 ApproachAttackEngagement
无法在配方层校验选招器是同一类边界。

验证:编译 0 错;EditMode 279/279(273 + 新增 6)。
UI 路径另做程序化冒烟(单测覆盖不到):模块被 DataHubWindow 自动发现并排在
「敌人」之后,两个面板构建无异常,表格 18 行 = 项目 18 个能力资产;
过滤实测 18 / 仅 Attack 5 / 仅有问题 0(与 SO 校验"全部合法"一致),
归属降序首行为 E006 符合序数排序。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 13:09:05 +08:00
joywayerandClaude Opus 5 bf7899f91a feat(combat): 接通弹反反制——弹反成功让发起攻击的敌人硬直
EnemyBase.ReceiveParry 早就存在且形态正确(强制 Stagger + 打断全部能力 +
定时恢复,小怪 Boss 通用),但一直零调用者:弹反判定点拿不到攻击者引用。
硬直时长的权威也早就存在——ParryConfigSO.StaggerDuration,注释写着
「被弹反敌人的受击硬直时长」,同样零消费者。本次只是把这两端接上,没有新造语义。

接缝选在 DamageInfo 而非"弹反成功时回调 HitBox"(spec §6.2 留的两个选项):
攻击者引用与既有的 SourceProjectile 完全同构——都是"攻击从哪来"的引用,
供弹反分支反向作用于来源。落到代码上就是弹反分支里并列的两行,不引入第二条回调链路。

- Combat 新增 IParryable(攻击被弹反时的反制承受方)。用接口是因为程序集方向:
  Combat 不能反向依赖 Enemies,而 HurtBox.ReceiveDamage 是唯一同时握有
  弹反结果与 DamageInfo 的地方。与 IDamageable 同款做法。
- DamageInfo 加 Attacker 字段([NonSerialized],Builder 与 From 工厂同步支持)。
- HitBox 在 Activate 时按宿主解析并缓存 IParryable——与 _ownerRigidbody 同源、
  与 _ownerProjectile 同手法。放 Activate 而非 Awake:attacker 可由调用方传入。
- ParrySystem 暴露 StaggerDuration,HurtBox 在弹反成功时读它,不另立常量。
- EnemyBase 声明实现 IParryable(方法签名本就匹配,无需改实现)。

顺带确认旧的全局误伤路径确实已随 Boss 单轨合并删净:全库不再有
HandleParrySuccess,ParryInfoEventChannelSO 只剩 ParrySystem 自己 Raise,
不存在"弹反小怪导致场上 Boss 一起硬直"的双重路径。

验证:编译 0 错;EditMode 276/276(273 + 新增 3)。
测试走完整 HurtBox 流水线:反射仅用于喂配置与跑真实 Awake,被测逻辑全程生产代码
(与 BossPhaseAbilityGateTests 同一手法)。硬直时长断言取 1.25f 这个非默认值,
才能证明读的是配置而非常量。用例把完美弹反阈值设为负数以避开会改 Time.timeScale
的子弹时间协程(编辑模式下它 yield 后不会恢复),TearDown 另有兜底还原。
变异验证——注释掉 HurtBox 里那一行后,恰好只有反制用例变红;还原后复验 276/276。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 11:36:52 +08:00
joywayerandClaude Opus 5 1e3c060c8d fix(core): 对象池真正通知 IPoolable,复用的敌人不再以死亡状态出场
IPoolable 的接口文档写着「由 PooledObject 子类或同 GameObject 上的其他
MonoBehaviour 实现,并在 PooledObject.OnSpawn/OnDespawn 中手动驱动」,
但那两个方法是空的 public virtual,全库无任何 PooledObject 子类,
GlobalObjectPool 也从不查找 IPoolable 组件——这份契约从未生效过。

全库 .OnSpawn(/.OnDespawn( 的调用点只有 4 处,真正驱动 EnemyBase
(唯一的 IPoolable 实现者)的只有 EnemyRespawner 一处手动补调。后果:

- EnemyBase.OnDespawn 在生产中从未被调用,其中的 _nav?.Stop() 是死代码;
- 更严重的是取出侧:PerformDeath 会 ForceState(Dead) 并禁用全部碰撞体后归池,
  而 EnemySpawnerOnEvent / ChaoFengBoss 召唤 / RangedEnemy 这些取用方
  没有补调 OnSpawn,拿到的复用敌人仍停在 Dead 态、碰撞体全关、HP 为 0——
  一个不可交互也不会动的幽灵。EnemyRespawner 路径侥幸没暴露,只因为它手动补了。

修法是让池履行它自己声明的契约,而不是让各调用方继续手动补调:
PooledObject.OnSpawn/OnDespawn 转发给本物体上的 IPoolable。只扫本物体、
不向下扫子物体——接口文档限定的就是「同 GameObject」,向下扫会让嵌套的
可池化对象被两个 PooledObject 各通知一次。组件集惰性解析一次并缓存
(池化本就是为省开销,不能每次进出都遍历组件)。

同时统一两条归池路径的顺序:GlobalObjectPool.Despawn 原本先 SetActive(false)
再 OnDespawn,而 PooledObject.ForceReturnToPool 是反过来的。转发接通后这个
顺序就成了可观察行为,两条路径必须给出同一份契约——统一为「先通知再停用」,
清理才跑在还活跃的对象上。

EnemyRespawner 那次手动 OnSpawn 随之改为只在兜底实例化路径调用:池化路径
现在由转发完成,重复调用会让 OnSpawn 末尾的 Spawned?.Invoke() 触发两次,
即重复执行出生能力。(今天重复触发会被 Execute() 的 !_isRunning 门挡住,
但那是运气不是设计。)

验证:编译 0 错;EditMode 273/273(269 + 新增 4)。
分阶段观察过失败面:只加转发时,2 条转发测试转绿、顺序测试仍红在
WasActiveOnDespawn 上;补顺序后全绿。
变异验证——把扫描范围改成 GetComponentsInChildren 后,边界测试恰好变红
(该测试在修复前是平凡通过的,需确认它真有约束力),还原后复验 273/273。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:03:57 +08:00
joywayer 5178b73637 Merge branch 'master' of ssh://git.joywaygames.cn:2222/basegames/zeling_v2 2026-07-31 15:01:07 +08:00
joywayerandClaude Opus 5 43ffc8736b merge: Boss AI 双轨统一 — 决策层与执行层一并并入小怪轨
核心洞察:Boss 侧那层"富编排"从未真正存在——全项目 0 个 AttackPatternSO/
SkillSequenceSO 资产、5 个 BossSkillSO 全空、BossSkillExecutor 跑一遍纯空转、
UseBossSkillWeighted 零调用者、Boss 预制体上没有 EnemyAiBrain。唯一的 Boss(嘲风)
实际用的就是动画事件驱动,即小怪轨的做法。所以"按复杂度分层"的前提不成立,
双轨只是让机制原语出现平行实现并已开始漂移。

- 决策归 AI 图:Boss 走 [AiDefinition] AiScript 手写图(不建 Boss 骨架/配方——
  Boss 间差异远大于小怪,骨架会退化成开关面板)
- 阶段 = 换招池:BossPhaseAbilityGate 按阶段启停能力组件,不在 SO 上写阶段字段
- 招走 EnemyAbilitySO + EnemyAttackSO;判定/生成/音效/无敌帧全挂动画事件
- Boss 专属只剩 IBossControl facet(非 Boss 访问即抛)+ 几个旁挂 MonoBehaviour
- 删旧轨 9 个脚本与 5 个空能力资产;两条脚手架创建链路改挂新轨
- 选招器补防重复折扣(WeightedRandomAntiRepeat),取代旧轨同名逻辑

顺带修掉三个波及全体敌人的缺陷:
- EnemyHurtState/EnemyStaggerState 在无对应动画 Clip 时守卫式早退却丢掉恢复安排,
  敌人被打一次即永久失能(美术未接入时影响大部分敌人)
- 池化复活带着上一条命的能力冷却:OnSpawn 那行 InterruptAll 的两道 IsRunning 门
  让它恒为空转,注释所称的"重置冷却"从未发生
- BrainBuilder.Build() 只做正向校验,已声明却无出边的非终态会让敌人永久卡住且
  零报错;现加"非终态必须有出口"校验,部分图另走 BuildPartial()

净变化:139 文件,+3808/-4211;删 10 个脚本、加 14 个。
验证:编译 0 错;EditMode 269/269。
未做:进 Play 实跑嘲风战的人工验收(嘲风动画 Clip 仍缺,卡美术资源)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 14:56:07 +08:00
joywayerandClaude Opus 5 0f676b0e88 feat(ai): Build() 校验「非终态必须有出口」,堵住零报错的永久卡死
BrainBuilder.Build() 此前只做正向校验——边指向的态是否已声明(RequireState
的同款关切);反向从不检查。一个已声明却没有任何出边的非终态,会让敌人进去
就永久停在那里,且全程零报错。计划里点名的风险点是 BossFragments.PhaseTransition:
它按设计不自带出边,出口全靠调用方补挂,漏一条就把 Boss 锁死。

做法:AiState 加 IsTerminal,StateBuilder 加 .Terminal() 显式标记,
AiStateFragments.Terminal() 自带该标记;Build() 对「非终态且无出口」抛异常。

「有出口」的判定比计划原话严一档,两点都是照 AiRuntime 的实际行为定的:
- 自转换不算——Switch 对 Target == 当前态直接 return false,根本出不去;
- 全局转换不算——全局事件边要外部推信号才触发,不是自主出口。把它算作出口,
  等于让「只有死了才出得去」的死角通过校验,而那恰是本校验要暴露的东西。

校验放在 Build() 而非各生产入口:生产的完整图装配点是 AiScript.GetOrBuildGraph
与 AiRecipeSO 两处,放 Build() 能让将来新增的入口默认受保护。代价是模块单测
需要一条别的路——单个 IUnawareModule / IEngagementModule 按设计只声明自己的态,
升级边由 PerceptionSkeleton 事后挂,单独构建时的死角是正常中间态而非缺陷。
为此加 BuildPartial():跳过本项校验,其余照旧。

测试侧按语义分两类处理,没有一刀切:
- 概念上确实是终态的(Dead)标 .Terminal(),保留 Build() 的全量校验;
- 隔离验证单个模块/片段的部分图改用 BuildPartial();
- AiScriptTests / AiDefinitionRegistryTests 的夹具经 GetOrBuildGraph 走生产
  Build(),给桩汇态补了回边——这两条本就该验完整图。
- 断言 Build() 因别的原因抛异常的用例原样保留。
PerceptionSkeleton 与嘲风图建的都是完整图,无需改动即通过——这本身就是覆盖。

验证:编译 0 错;EditMode 269/269(264 + 新增 5)。
加校验后曾有 49 条既有测试变红,全部为上述两类夹具,逐条按语义修正后转绿。

变异验证:注释掉 ChaoFengAi 里 PhaseTx 的 txDone 出边后,ChaoFengAiTests
8 条全部变红,报错精确点名 'PhaseTransition';还原后复验 269/269。
本改动之前,同样的删除是零报错的——Boss 会静默锁死在过渡态,
这正是本校验存在的理由。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 13:21:07 +08:00
joywayer 30a880c922 场景 2026-07-31 11:23:49 +08:00
joywayerandClaude Opus 5 6414fa2dfe fix(enemy): 池化复活的敌人不再带着上一条命的能力冷却
EnemyBase.OnSpawn 里 InterruptAll 那行的注释写着「重置能力冷却」,
三处都不成立:

1. EnemyAbilityRegistry.InterruptAll 有 `if (ab.IsRunning)` 门;
2. EnemyAbilityBase.Interrupt 里还有一道 `if (!_isRunning) return`;
   出生时没有能力在跑,循环体一次都不执行;
3. 即便执行到,末行是 `_cooldownEndTime = Time.time + cooldown * 0.5f`——
   那是「中断后计半程冷却」的写入语义,本就不是清零。

冷却以绝对 Time.time 记时,于是上一条命的剩余冷却原样活到下一条命。
活路径是 EnemyRespawner.SpawnEnemy():敌人死亡归池、延迟复活后取出复用,
按 1.5–10 秒的冷却量级,新生的敌人有数秒出不了招。

修法是补一条真正的清除路径而非改动中断语义:
EnemyAbilityBase.ResetCooldown() 把 _cooldownEndTime 清回出生态的 -1,
EnemyAbilityRegistry.ResetAllCooldowns() 不看 IsRunning 逐个调用,
OnSpawn 显式调用它。原 InterruptAll 保留为兜底中断,但注释改为陈述它
实际做的事——那句失真的注释正是这个缺陷藏了这么久的原因。

测试的冷却全程由生产代码写入:Execute() 启真实协程(编辑模式下跑到首个
yield 即挂起,_isRunning 留 true),再由 Interrupt() 经真实路径写冷却,
不反射直写字段,否则测到的是伪造状态而非真实时序的产物。

验证:编译 0 错 0 警;EditMode 264/264。
修复前该用例是唯一变红的一条(263/264),失败值 5.0f 恰为
cooldown(10) × 0.5,精确指向上述第 3 点。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:13:49 +08:00
joywayer 9120964be0 fix(enemy): 无受击/僵直动画时不再永久卡死
EnemyHurtState 与 EnemyStaggerState 的 Enter 在对应 Clip 为空时守卫式早退,
**同时丢掉了恢复安排**:_currentState 永远停在该态,IsControllable 恒假,
AiRuntime 的让位门永久挡住条件边求值——敌人被打一次即永久失能。
美术未接入时这影响的是大部分敌人。

TakeDamage 是分发点:霸体被破进 Stagger、否则进 Hurt,两条都可达,
所以这是两个活 bug 而非一个。EnemyKnockUpState 本就有 else 分支走
ScheduleStateRecovery,是正确范式;本次把该分支补给另外两个,并加一条
KnockUp 回归测试守住它。

时长权威没有新造:
- Hurt 接上 DamageSourceSO.HitStunDuration——该字段一路铺到 DamageInfo
  却零消费者,本就是「这一击让对方僵多久」的既有权威;经 PendingHitStun
  传给状态,形状与既有的 PendingLaunchDir 一致。
- Stagger 用 HitTierConfig.staggerDuration——该结构 tooltip 本就写着
  「Stagger / KnockUp 参数」却只有 KnockUp 的字段,属补齐而非发明。

顺带修 ScheduleStateRecovery 不取消前一次安排的问题:两个重叠安排里较短的
会先把状态改回 Controlled,静默缩短调用方想要的时长(ReceiveParry 接线后
就会撞上)。并在 OnSpawn 清掉跨池化周期残留的协程句柄。

注:Knockback 目前零调用者,其中的 PendingHitStun 赋值今天是死代码,
但它是 public virtual 且会进 Hurt 态,不设则将来接线时会用到陈旧时长。

验证:编译 0 错;EditMode 263/263(260 + 新增 3)。
变异验证——移除两个 else 分支后恰好 Hurt/Stagger 两条测试变红、
KnockUp 回归测试保持绿,恢复后复验 263/263。
2026-07-31 09:40:47 +08:00
joywayer 691b19fd11 setting 2026-07-31 09:20:32 +08:00
joywayerandClaude Opus 5 6d3816d7ef docs(enemy): Boss AI 单轨落地——新增作者指南,清理描述已删除系统的长期文档
新增 Docs/Guides/09_BossAi_Authoring_Guide.md:
定制路径写法([AiDefinition] + AiScript)、建态原语(AiStateFragments +
BossFragments + IBossControl)、阶段=换招池、一招怎么配(EnemyAbilitySO +
EnemyAttackSO + 动画事件)、开战触发、死亡归物理层,
以已落地的 ChaoFengAi 为逐段范例,附 Boss 专属陷阱与已知缺口。
08 号指南加交叉引用。

删除两份整份描述已删除且从未实现的体系的文档:
Docs/Architecture/23_BossSkillModule.md、Docs/Design/47_BossSkillSystem.md。
删前已确认全库无 Markdown 链接残留,索引行与关联文档行改指新指南或 spec。

修订:25_CharacterArchitectureOverview §4 Boss 章节按实际代码重写;
07_EnemyModule 加过时提示、§11 改重定向;02_EventSystem 修正 Boss 事件发送方;
19_BossPatternLibrary 加废止提示(设计意图仍有效,类型名已废);
AssetFolderSpec 删旧 Boss 技能编辑器条目;三处 README/索引同步。
Docs_Dev/ 历史记录保留不动。

spec 标记为已实施,并记录实施期偏差(无 Boss 骨架、Telegraph 枚举保留、
阶段直查不走黑板、BossSkillEvent 一并删除)与八项已知未完成项,
其中 EnemyHurtState 无受击动画永久卡死为最高优先级、且与 Boss 轨无关。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 17:14:32 +08:00
joywayerandClaude Opus 5 cf0ac60fad refactor(enemy): 清全库行为树残留,删无人使用的 Boss 技能事件频道
Task 21 Step 2 全库扫描后的收尾:

注释与 Tooltip 里的旧决策层措辞逐处改为只描述功能——
EnemyControlledState / RangedEnemy / FlyingEnemy / SensorSlotNames /
EnemyPoiseComponent / EnemyAnimationConfigSO / EnemyAiBrain。

EnemyQuotaManager 的序列化字段 _maxActiveBehaviorTrees 改名 _maxActiveBrains
(它裁剪的实际是 EnemyAiBrain),带 FormerlySerializedAs。复核确认目前
无任何场景 / 预制体挂载该组件,故无值可丢;属未雨绸缪。

BossSkillEventChannelSO + BossEvents.BossSkillEvent + EVT_BossSkill.asset 删除:
唯一发送方 BossSkillExecutor 已随旧轨删除,复核确认全项目零发送方、零订阅方,
资产 guid 在场景 / 预制体 / Addressables 分组中均无引用。

自检:编译 0 error;EditMode 260/260 通过;
SO 校验 0 error 0 warning;层矩阵 25/25 正确。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 17:14:09 +08:00
joywayer 2c569dcf0d docs(enemy): Task 21 补 Step 2.5——处理描述已删除系统的长期文档
Task 19 发现约 20 份文档仍在描述旧 Boss 轨,含两份整份失效的架构/设计文档。
按第 7 条,Docs/ 是长期文档,留着描述不存在的类型比没有更糟。
分类处理:两份整份删除、AssetFolderSpec 局部修订、Guides 随新章节改写、
Docs_Dev 的历史记录保留不动(那是过程记录,改了反而失真)。
2026-07-30 16:47:51 +08:00
joywayerandClaude Opus 5 ad41da0410 refactor(enemy): 删预警旧路径,预警统一走动画
telegraphVfxKey / telegraphDuration / TelegraphRoutine 删除。
预警的正确载体是 clip 本身——姿态在动画里,音效与特效在动画事件上
(PlaySFX / TriggerFeedback 已于阶段一补接)。旧路径会在动画之前
插一段无动画的静止等待,是同一件事的第二条平行路。

AbilityRunState.Telegraph 枚举值保留:BlinkStrikeAbility 现身闪光段
仍在自己协程里显式置位(计划原文假设它零消费者,实测不成立),
只删了基类的默认预警协程。顺带清掉该文件里遗留的行为树插件措辞。

18 个 ABL_ 资产经 AssetDatabase.ForceReserializeAssets 重写,
去掉已删字段的死键;顺带补齐了这些旧资产缺失的当前字段默认值
(designNote / category / weight / rangeRadius / rangeOffset / maxDashDuration),
无任何已有取值被改动。SOValidationRunner:0 错误 0 警告。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:45:51 +08:00
joywayerandClaude Opus 5 d3013b1053 refactor(enemy)!: 删除旧 Boss 轨 9 个脚本
BossSkillSO / AttackPatternSO / SkillSequenceSO / BossSkillExecutor /
BossSkillTypes / WeakPointSystem / TelegraphSystem / BossSkillModule /
BossSkillSequenceWindow,连同 Patterns 独立程序集(BaseGames.Enemies.Boss.Patterns.asmdef,
已核实无任何 asmdef 按名或按 GUID 引用它)整包移除。

删前先清两处引用:
- ChaoFengKnockdownCounter 击落打断改走 _boss.Abilities.InterruptAll(ExternalRequest)
- EditorScaffoldUtils 命名前缀表删掉两条指向已删类型的死条目(还与 AssetFolderSpec 的 ABL_ 冲突)

判据「有更优替代才删」逐项对应:
- 编排 → EnemyAbilitySO + EnemyAttackSO(归一化时机 / 槽位 HitBox)
- 阶段门 → BossPhaseAbilityGate
- 选招 → EnemyAttackSelector + WeightedRandomAntiRepeat
- 可弹反 → DamageSourceSO.Flags(CanBeParried),HurtBox 已在判定
- 弱点倍率 → HurtBox 自带倍率(原实现的 GetDamageMultiplier 零调用者)
- 预警 → clip 姿态 + 动画事件(TriggerFeedback / PlaySFX 已补接)
- 竞技场联动 → EnemySpawnerOnEvent
- 霸体窗口 → EnemyAttackSO.hasPoiseWindow 四字段

能力资产的编辑器总览视图随 BossSkillModule 一并消失,
后续补 EnemyAbilityModule(优先级低于 AiDefinitionValidator)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:39:57 +08:00
joywayer ef1998db2b docs(enemy): Task 19 补 Step 0——两处计划遗漏的引用,不清则删除任务必然编译失败
ChaoFengKnockdownCounter.cs:82 有 BossSkillExecutor 的编译期类型引用,
该文件既不在删除清单也未安排改动,却仍被 ChaoFengBoss 使用。
EditorScaffoldUtils 的命名前缀表里有 BossSkillSO/SkillSequenceSO 两条死条目。

后续任务再添一项:池化敌人复活带着上一条命的冷却——OnSpawn 里
InterruptAll 的注释声称重置冷却,实际 Interrupt 在未运行时提前返回、
运行时又是设置而非清零冷却。影响全体池化敌人。
2026-07-30 16:28:21 +08:00
joywayerandClaude Opus 5 822dc90c21 refactor(enemy): 清行为树债——StopBehaviorTree 改名,删 ConsumeParryEvent
StopBehaviorTree → NotifyDecisionStop(方法体早已只是发 Died 信号)。
EnemyDeathSequence 的 _stopBehaviorTree 字段同步改名,带 FormerlySerializedAs 保值。
ConsumeParryEvent 及其 TTL 状态删除:零调用者,且 ForceState(Stagger) +
IsControllable 让位门已是更优表达,不需要第二条受击通道。

顺带清掉这三个文件里残留的行为树插件措辞(BD_* / BD Task / 停行为树)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:23:47 +08:00
joywayerandClaude Opus 5 a44aafee3a refactor(enemy): BossBase 瘦身——删技能执行与弹反反制两套
技能选取/执行归 EnemyAttackSelector + EnemyAbilityBase(阶段一已就位),
BossBase 不再持有 BossSkillExecutor。阶段切换改用能力注册表打断在跑的招。

弹反反制整套删除:它订阅的是全局弹反成功频道,无来源过滤——
玩家弹反任意小怪都会让场上 Boss 硬直。正确形态是 EnemyBase.ReceiveParry
(谁被弹反谁硬直),其接线为独立后续任务。

调试面板的 BossSkillExec 行换为阶段与过渡状态。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:19:21 +08:00
joywayerandClaude Opus 5 635ab69bb6 refactor(enemy): BossResource 去掉自动放招,触发权交回 AI 图
满值时由 BossResource 直接调 UseBossSkill 是绕过决策层执行,
违反「AI 只决策、能力负责实现」。现在只维护数值并暴露 IsFull,
AI 图挂 When(x => x.Boss.ResourceFull) 边进专属招态。
顺带解除对即将删除的 BossSkillSO 的依赖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:13:51 +08:00
joywayer 02c3a764a5 docs(enemy): 记录 Task 15 实测结论——含一个项目级严重 bug
后续任务新增最高优先级项:EnemyHurtState.Enter 在无受击动画时提前返回
且不安排回 Controlled,敌人被打一次即永久卡在 Hurt 态、AI 永久停摆。
美术未接入的当下等于大部分敌人一挨打就废。与 Boss 轨无关,需单独立项。

Task 16 加警示:不得删掉 Task 15 新增的 BossBase.Start()——它是场景直接
放置的 Boss 唯一一次应用阶段招池的时机(EnterPhase 只在换阶段触发、
OnSpawn 只在池化复用触发,开局都不跑)。

顺带更正计划头部的 Unity 版本(实际 2022.3.62f3c1,非 Unity 6)。
2026-07-30 16:05:26 +08:00
joywayerandClaude Opus 5 877d2f5fef feat(enemy): 嘲风迁移到新轨——能力资产 + 对象树 + 开战触发
删 5 个空 BossSkillSO,用向导重建 6 个 EnemyAbilitySO(含入场演出)。
预制体经放置工具重新产出:EnemyAiBrain(_definitionId=ChaoFeng) +
BossPhaseAbilityGate + 6 个能力组件,不再有 BossSkillExecutor。
TestRoomA 加开战触发区。三件自检 0 error,EditMode 260/260。

首次真正跑通两条脚手架,暴露并修掉三个它们的缺陷:

1. 放置工具漏挂 EnemyLocomotion(PlaceChaoFeng 及其余 7 个敌人放置入口)。
   EnemyBase.Awake 找不到它即报错且 Locomotion 为 null,
   AiStateFragments.ApplyLocomotion 直接 NRE、AiRuntime 构造失败——
   Boss 建不出决策层,完全不动。已在所有敌人放置入口补齐。
2. 向导把空中阶段的 wind_stone 建成 requiresGrounded=true(SO 默认值)。
   Boss 浮空后 IsGrounded 恒 false,选招器把它滤掉,空中阶段站着不打。
   能力定义表加 grounded 列,阶段 1 的招显式置 false。
3. BossPhaseAbilityGate.ApplyPhase 只在 EnterPhase 与 OnSpawn 调用,
   直接摆场景里的 Boss 两条路径都不走,开局后续阶段的招仍在候选池里。
   BossBase.Start 补一次初始阶段应用。

另:Phase1_Tornado_HitBox 确认零消费者(龙卷判定随弹体走),
从放置工具删除其创建与伤害源绑定;
EnemyBrainContext.UseAbility 对解析不到的能力 id 改为 LogError,
不再静默返回 false(CLAUDE.md 第 6 条);
新增 PlaceBossFightTrigger 脚手架(此前无对应创建入口,属裸建)。

播放模式实测状态序列(TestRoomA,玩家走进触发区):
Wait →(Engaged) Intro → Ground⇄GroundAttack →(hp<50%) PhaseTransition
→ Air⇄AirAttack,浮空生效,阶段门按阶段换池,全程 0 报错。
动画 Clip 未接入,PlayClipAbility 空 clip 立即完成,故只验证了状态流转,
判定时序待美术接入后再校。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:02:14 +08:00
joywayer bdeefca75b docs(enemy): Task 15 补 Phase1_Tornado_HitBox 去留判定
Task 14 发现该 HitBox 在 _hitBoxes 数组随 BossSkillExecutor 移除后已无任何引用。
龙卷由动画事件生成弹体、判定自带,Boss 身上这个 HitBox 很可能多余。
要么删掉其创建与伤害源绑定,要么绑进对应能力的槽位——不得以无引用状态留在对象树上。
2026-07-30 15:19:57 +08:00
joywayerandClaude Opus 5 ba2e22cd58 refactor(editor): PlaceChaoFeng 改挂新轨——决策层 + 阶段门 + 能力组件
BossSkillExecutor 换为 EnemyAiBrain(_definitionId=ChaoFeng) +
BossPhaseAbilityGate + 6 个能力组件(一招一子物体,绕开
DisallowMultipleComponent)。HitBox 改绑命名槽位,阶段招池经反射写入。
顺带删掉 Place Boss Enemy 报告里的 BossSkillExecutor 旧轨提示,
以及已无引用的 using BaseGames.Boss。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:17:52 +08:00
joywayerandClaude Opus 5 a64449d213 refactor(editor): 角色向导 Boss 页改产出 EnemyAbilitySO
该向导是 5 个空 BossSkillSO 资产的创建链路——不改则会持续产出旧轨资产。
招式表扩为带 soType / rangeRadius / cooldown / weight 的形式(与
GetEnemyAbilityDefs 对齐),阶段可用性移交 BossPhaseAbilityGate。
产出时强制 rangeRadius > 0,不把责任推给下游作者。
状态面板改查 EnemyAbilitySO,并新增 AI 图注册状态一行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:08:10 +08:00
joywayer e488255315 docs(enemy): Task 15 补两项——UseAbility 静默失败与频道资产实名
Task 11 发现 EnemyBrainContext.UseAbility 对找不到的能力 id 静默返回 false:
入场演出资产漏配会被静默跳过且零报错,是第 6 条禁止的静默通过。
建图期已挡掉空 id,故运行期解析不到必是配置错误,改为 LogError(非抛异常,
因为这是每帧路径)。放 Task 15 因为那是第一次能观察到配齐/漏配两种表现的时点。

Task 12 实测:EVT_BossFightToggled 资产不存在,实际是 EVT_BossFightEnded
被当开关用(BGMController 与 BossHPBar 都绑它)。资产名与用途不符是既存问题,
本次只记录不改名。
2026-07-30 14:58:08 +08:00
joywayer 990f90d383 feat(enemy): BossFightTrigger——补上缺失的开战触发
开战事件频道此前全项目零发送方,Boss 永远不开战。
触发时广播开战事件并向 Boss 决策层发 AiSignal.Engaged。
漏配目标 Brain / bossId / 两个频道 / 玩家层掩码 / isTrigger 一律逐条点名报错并停用组件,
不做部分执行——半开的战斗比彻底不开更难查。
2026-07-30 14:55:05 +08:00
joywayer 0d81d29527 feat(enemy): 嘲风 AI 图(BrainGraph 定制路径)
Wait →(Engaged) Intro → Ground⇄GroundAttack →(hp<50%) PhaseTx → Air⇄AirAttack。
Boss 不脱战(rest 指回自身)、阶段单向推进、起手就打完。
死亡不在图上:全局 Died 边进 Death 终态,演出归物理层。
8 条 EditMode 断言覆盖状态序列、过渡只发起一次、过渡结束必须转出、
脱离感知区不脱战、血量回升不回退阶段。
2026-07-30 14:51:08 +08:00
joywayer d172f903b6 docs(enemy): 记录 BrainBuilder 缺少「态是否有出边」校验
Task 9+10 执行时发现:Build() 只做 RequireState 的正向校验(边指向的态
是否已声明),不校验反向——已声明却无出边的非终态会让敌人永久卡住且零报错。
BossFragments.PhaseTransition 按设计不自带出边,正是这类风险点。
列入后续任务,并记录 MoveToAnchor 只在 OnEnter 采样锚点这一隐含假设。
2026-07-30 14:34:32 +08:00
joywayerandClaude Opus 5 eaba54ea76 feat(ai): Boss 图共享片段 + 竞技场锚点 + AiSignal.Engaged
BossFragments:阶段过渡 / 逼近选招 / 移动到锚点三个静态建态片段。
ApproachAttackEngagement 的建态逻辑提为 internal static Declare(态名参数化),
模块路径与 Boss 图路径共用同一份行为,Boss 可建多组(地面组/空中组)。

锚点坐标经 IBossControl.AnchorAt / DistanceToAnchor 暴露,不走黑板——
EnemyAiBrain._context 是私有的(BossBase 无从写入),且 ResetScratch 会清黑板
带来隐式时序约束。锚点本就是 Boss 专属知识,归 Boss facet 更直。
锚点漏配 / 下标越界显式抛,不回退到自身位置。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:32:32 +08:00
joywayerandClaude Opus 5 8c56d207bd feat(enemy): EnemyAbilitySO 补设计备注,命名前缀对齐规范
+ designNote(目标体量下能力数量大,设计意图需就近记录)。
fileName 由 EAB_ 改为 ABL_——AssetFolderSpec 第 298 行、两个向导
以及 PlayClipAbilitySO 等子类都用 ABL_,此前只有基类是 EAB_。
顺带清掉 abilityId 注释里的旧插件残留字样。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:23:41 +08:00
joywayerandClaude Opus 5 c05583a365 feat(animation): 敌人侧动画事件补接无敌帧与反馈四件
EnableIFrame/DisableIFrame → HurtBox.SetInvincible(与玩家侧同形态,
无敌时长由动画时间轴上两事件间距表达);TriggerFeedback/PlaySFX →
IFeedbackPlayer,用于音效预警。此前敌人侧只接了 7/20 个事件类型。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:20:47 +08:00
joywayer 5cc93483d3 feat(combat): HurtBox 自带受击伤害倍率
倍率归 HurtBox 自己(弱点部位就是一个倍率 > 1 的 HurtBox),
取代 WeakPointSystem 那份写进私有字段却零调用者的倍率——
其注释称由 BossStats 调用,而项目里根本没有 BossStats 类型。

倍率 1 时数值原样返回而非"乘完再钳最低 1":本改动先于任何消费者落地,
若默认路径引入钳制会静默改变游戏里每一次伤害计算。
测试用 ApplyDamageMultiplier(0)==0 守住这条——10 在两种实现下都得 10,
只有 0 能把二者区分开。变异验证:去掉该捷径后恰好该断言变红。
2026-07-30 14:15:12 +08:00
joywayer 5046125acb docs(enemy): 执行须知第 5 条——RequireComponent 指向抽象类型时 AddComponent 返回 null
HurtBox 标了 [RequireComponent(typeof(Collider2D))],Collider2D 是抽象类,
Unity 无法自动补齐,AddComponent<HurtBox>() 返回真 null。建测试对象须先挂
具体碰撞体。Task 6 实测证实;HurtBoxEditor.cs:97 早有同样注释,说明这个坑
项目里踩过但没写进规范。
2026-07-30 14:04:42 +08:00
joywayer c577d6baa6 docs(enemy): Task 16 的行为树残留清理已在 Task 5 顺带完成 2026-07-30 13:52:47 +08:00
joywayer 50f505279d feat(enemy): BossPhaseAbilityGate——阶段即换招池
按阶段启停能力组件,被禁用的招经 CanUse 自动退出选招候选,
阶段门不需要碰选招器。取代 BossSkillSO.availablePhaseIndices。
EnterPhase 先换池后广播;OnSpawn 回阶段 0(对象池复用)。
2026-07-30 13:50:34 +08:00
joywayer 24c612930e docs(enemy): 记录 EditMode 不触发 Awake 的坑,并分派两处越界发现
执行须知第 4 条:EditMode 下 AddComponent 不调用 Awake(Task 4 实测证实)。
后果是 EnemyAbilityBase._enemy 留空使 CanUse 恒假——依赖它的断言在修复前
也会通过,成为永远不会失败的测试。做法是给测试子类加 RunAwake() 显式触发,
不要反射直写字段。

Task 5 追加:IAttackCandidate 的 CanUse 注释补上 enabled 这一维。
Task 21 追加:EnemyQuotaManager._maxActiveBehaviorTrees 改名须带
FormerlySerializedAs,否则丢场景配置值。
2026-07-30 13:30:44 +08:00
joywayer 64d2087adc fix(enemy): 禁用的能力组件不再报告可用
CanUse 纳入 enabled。此前禁用组件仍返回 true,选招器会选中一个
StartCoroutine 必然失败的招。这也是阶段门(按阶段启停能力)的前提。
2026-07-30 13:28:23 +08:00
joywayer d8d99dd1ce docs(enemy): 计划补三条实测坑与两处修正
执行须知新增:EnemyBase.Abilities 属性遮蔽同名命名空间(成员访问位置须写全名,
Task 16/17 会再遇到);unity_get_compilation_errors 在编译成功时不报警告,
'0 warnings' 是上报口径而非真值;MCP 测试运行器忽略过滤参数。

Task 5:选招器防重复的重置移出 BossBase.OnSpawn(Task 3 审查确认应归
EnemyBase.OnSpawn 以覆盖全部池化敌人)。
Task 21:残留扫描的预期由「无命中」改为「BD_/行为树 仍有约 20 处待清」。
2026-07-30 13:02:52 +08:00
joywayerandClaude Opus 5 0a92d21ce1 fix(enemy): 应用 Task 3 代码审查意见
补上池化路径的防重复记忆重置——ResetRepeatMemory 此前文档承诺了
「对象池复用时清除」却无任何调用点,而它要取代的 BossBase.LastUsedSkillId
恰好在 OnSpawn 里有重置。放 EnemyBase.OnSpawn 以覆盖全部池化敌人。

折扣系数改为每次选招前刷新,与 attackSelectionMode 的读取时机对齐——
此前策划在播放模式下调系数无效、调模式却立即生效,容易误判为功能没生效。
默认值收敛为 EnemyAttackSelector.DefaultAntiRepeatFactor 单一常量。

测试:IneligiblePrevious 用例改用系数 0.5——原先用 0 时,把折扣错写在
eligibility 归零之前也照样通过,是个永远不会失败的测试;统计用例边界由
7σ 收紧到 5σ 使其真正验证该系数;补 ResetRepeatMemory 的确定性用例、
系数 1.0 无惩罚、三候选三条。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 13:00:28 +08:00
joywayerandClaude Opus 5 eb3f4888a4 feat(enemy): 选招器支持防重复折扣——WeightedRandomAntiRepeat
上一次选中的招按 EnemyStatsSO.attackAntiRepeatFactor 折扣权重(默认 0.3)。
折扣把唯一候选压到 0 时退化为 Priority 路径,不会出现敌人永不出手。
阶段切换清空防重复记忆。取代 BossBase.UseBossSkillWeighted 里的同名逻辑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:43:01 +08:00
joywayer b0fd040904 docs(enemy): 按 Task 1+2 审查结论修订 spec 与计划
spec:ResourceFull 由「无组件时恒 false」改为显式抛,与 Boss facet、AnchorAt
统一为「依赖组件缺失一律抛,不返默认值」。
计划:Task 16 补上 BossBase 残余三处行为树残留的清理项(含运行时告警字符串);
Task 19 记录 Patterns/ 是零外部引用的独立程序集,可整目录删。
2026-07-30 12:32:43 +08:00
joywayerandClaude Opus 5 2a1ada4388 fix(ai): 应用 Task 1+2 代码审查意见
ResourceFull 未挂 BossResource 时改为显式抛——原先静默返回 false,
是 CLAUDE.md 第 6 条禁止的下游兜底,且与紧邻的 Boss facet 行为不一致。
FakeBossControl.DistanceToAnchor 不再忽略 index。测试改用 TearDown 清理
(本项目关闭了 Domain/Scene Reload,失败断言会把物体永久留在打开的场景里)。
BeginPhaseTransition 去掉死的默认参数;接口补充重入契约说明。

顺带去重:FakeAiContext 实现 IEnemyActor,删除两个测试内的本地 Ctx 副本
——一个接口成员改动要同步三份是复发性成本。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:31:39 +08:00
joywayerandClaude Opus 5 3665504a58 feat(ai): 新增 IBossControl facet——Boss 阶段状态经 IAiContext 供图读取
非 Boss 敌人访问 IAiContext.Boss 显式抛 InvalidOperationException,
不返回空对象掩盖小怪图误挂 Boss 边的错误。BossResource 补 IsFull 供图条件边判定。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 12:15:54 +08:00
joywayer 5839b423ae docs(enemy): Boss AI 双轨统一实施计划——21 个任务,分两阶段
阶段一 Task 1-15 只加不删(每 Task 编译通过、测试全绿,末尾嘲风能用新轨打起来);
阶段二 Task 16-21 删旧轨(先改引用方,再删被引用类型;先删资产,再删脚本)。

编写期核查坐实并更正 spec 两处:
- 取消 EnemyBase.SetInvincible(float):HurtBox.SetInvincible(bool) 已存在,
  玩家侧 EnableIFrame/DisableIFrame 就走它,无敌时长由动画上两事件间距表达。
  新增件由六件减为五件。
- 竞技场锚点改挂 IBossControl,不走黑板:EnemyAiBrain._context 是私有字段
  且无公开访问器(BossBase 无从写入),且 ResetScratch 会清黑板带来隐式时序约束。

另确认 EditorScaffoldUtils.CreateSOAsset(Type,...) 重载已存在,无需新增辅助。
2026-07-30 11:01:46 +08:00
joywayer c176423103 docs(enemy): Boss AI 双轨统一设计——决策层与执行层一并归并到小怪轨
竞品调研 + 代码现状核查后推翻 2026-07-27 §5 的「执行层按复杂度分层」:
Boss 侧那层富编排从未真正存在(0 个 AttackPatternSO/SkillSequenceSO 资产、
5 个 BossSkillSO 除 skillId/weight 外全空、执行器跑一遍纯空转、
UseBossSkillWeighted 零调用者、预制体上无 EnemyAiBrain)。

决策:
- 决策层单轨 —— 每 Boss 一个 AiScript 定制类,不建 Boss 骨架/配方
- 执行层单轨 —— Boss 招走 EnemyAbilitySO + EnemyAttackSO,编排权威=动画时间轴
- 阶段=换招池 —— BossPhaseAbilityGate 按阶段启停能力组件
- 选招策略是可换小构件 —— AttackSelectionMode 加防重复值,不抽策略接口
- 旧 Boss 轨退役 9 脚本 + 5 资产,判据「有更优替代才删」

同时记录 WeakPointSystem.GetDamageMultiplier 零调用者、
BossBase.HandleParrySuccess 订阅全局弹反频道导致的误伤 bug、
敌人侧动画事件只接了 7/20 三处历史遗漏,以及两条脚手架创建链路
(CharacterWizardWindow / SceneObjectPlacerTool.PlaceChaoFeng)必须同改。
2026-07-30 10:29:49 +08:00
joywayer aaca138774 fix(editor): EnemyAiBrain 检视器支持多选——mixed 值不再误写,违规对象逐个点名 2026-07-30 08:46:41 +08:00
joywayer b36ac96ac7 feat(editor): EnemyAiBrain 检视器——二选一不变量提前到编辑期,定义 id 改下拉 2026-07-30 08:38:13 +08:00
joywayer afcc0b10ee fix(enemy): 配方校验改用一次性 builder 探测,不再干扰共享图缓存 2026-07-30 08:33:53 +08:00
joywayer da217af113 feat(enemy): AI 配方接入 SO 校验——漏配/歧义/零冷却 Committed 在编辑期与构建期报错 2026-07-29 17:20:12 +08:00
joywayer 6df4a259b7 docs(enemy): 修正作者指南与两处注释残留的死亡层表述 2026-07-29 16:34:21 +08:00
joywayer db6f6c5f77 docs(enemy): 新增敌人 AI 作者指南(三条路径 + 模块清单 + 六个范例 + 陷阱) 2026-07-29 15:57:23 +08:00
joywayer 44412adfc7 refactor(enemy): E001 AI 迁移为配方资产,退役定制脚本
用 EnemyAiRecipeWizard 建 ENM_E001_Ai.asset,配置未发现层
DisguiseThenPatrol + 交战层 RushEngagement(e001_chase, Committed);
TestRoomA 场景 EnemyAiBrain 改绑 _recipe,_definitionId 清空;
删除 E001CaoZhiAi.cs;SceneObjectPlacerTool 的 E001 挂载分支与其余
AI(BrainGraph) 提示文案统一改为指向配方向导路径。
2026-07-29 15:38:44 +08:00
joywayer ecdb0040cb feat(editor): 敌人 AI 配方脚手架向导;AssignModules 改名并失效缓存
- AiRecipeSO 新增 protected InvalidateGraph(),子类装配模块后调用以丢弃缓存图,
  避免改动要等到下次进 Play 才生效的静默陈旧图问题。
- PerceptionRecipeSO.SetModulesForTests 改名为 AssignModules(向导也要用它装配模块,
  旧名字具有误导性),并在装配后调用 InvalidateGraph。
- 新增 EnemyAiRecipeWizard:按 AssetFolderSpec 定名定路径创建感知型 AI 配方资产
  (Assets/_Game/Data/Enemies/{EnemyID}/ENM_{EnemyID}_Ai.asset),已存在则选中不覆盖。
- PerceptionRecipeSoTests 同步改名调用点,并新增测试钉住"装配后缓存必须失效"契约。
2026-07-29 15:28:25 +08:00
joywayer 426fa3601d fix(enemy): EnemyAiBrain 配置守卫不随重新启用蒸发,注册表消重 2026-07-29 15:23:49 +08:00
joywayer 57cbaacf2c feat(enemy): EnemyAiBrain 支持配方资产/AiScript id 二选一解析 2026-07-29 14:56:54 +08:00
joywayerandClaude Opus 5 c76eba8a0e docs(enemy): T15 并入 T13 审查遗留——AssignModules 改名 + InvalidateGraph
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 14:53:54 +08:00
joywayerandClaude Opus 5 6540560c13 docs(enemy): 计划同步——未配置配方应显式报错,而非"默认必须可建图"
T13 执行中发现原稿的 DefaultModules_ProduceValidGraph 断言了一条不成立的契约:
建资产到配好模块之间没有任何东西会调 GetOrBuildGraph(),它只在
EnemyAiBrain.Start() 运行时被调,所以"新建资产即报错"的场景不存在。
按该断言把默认改成零配置可建图的模块,会把"必然响亮报错"换成
"建图成功但敌人永不出手、零报错"——方向与第 6 条相反。改为钉相反契约。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 14:48:19 +08:00
joywayer 825cae7977 feat(enemy): PerceptionRecipeSO 配方资产 + SubclassSelector 模块下拉
未发现层/交战层各用 [SerializeReference]+[SubclassSelector] 下拉挑选模块,
新增模块类自动出现在下拉里,无需改枚举或已有文件。默认交战模块仍为
RushEngagement——未配能力时建图必须显式报错,而非静默产出打不出手的图。
2026-07-29 14:46:41 +08:00
joywayerandClaude Opus 5 04f644969b fix(ai): AiRecipeSO.OnEnable 改 protected virtual,防子类静默遮蔽
留作 private 时,子类另写一个 OnEnable 会遮蔽它(Unity 只调最派生的),
PlayModeResetHook 注册从此不发生且零报错——关闭域重载下即表现为
"改了配方进 Play 仍跑旧图"。T13 的 PerceptionRecipeSO 是第一个子类,
在它出现前先堵住。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 14:36:22 +08:00
joywayer 6d8db95046 feat(ai): IAiDefinition + AiRecipeSO 配方基类(含关闭域重载的缓存重置) 2026-07-29 14:32:45 +08:00
joywayer 7327a5fe09 refactor(enemy): 退役 PerceptionStateMachine,E001 切换到两层模块组装 2026-07-29 14:24:48 +08:00
joywayer 9dc10e0398 test(enemy): 补测骨架 × 另两个模块的组合,以及入口覆盖契约 2026-07-29 14:21:36 +08:00
joywayer bf8101b3f5 feat(enemy): PerceptionSkeleton——两层可插拔的感知骨架,自声明死亡终态 2026-07-29 14:10:46 +08:00
joywayer faa210c789 revert(enemy): 收掉死亡层——死亡归物理层,AI 只需一个终态 2026-07-29 14:02:13 +08:00
joywayerandClaude Opus 5 6a885ec105 docs(enemy): 计划同步取消死亡层——T9 作废、新增 T9R,T10/T11/T13 去掉 death 参数
T9 原稿折叠保留作历史记录,附必然卡死的机制说明。
PerceptionSkeleton.Add 由三参收为两参并自声明 Death 终态;
PerceptionRecipeSO 去掉死亡层字段。T5 段落保持原样(它记录的是已发生的事实,
IDeathModule 由 T9R 删除)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 13:59:54 +08:00
joywayerandClaude Opus 5 023a4a4ec6 docs(enemy): 取消死亡层——死亡归物理层,四层收为三层
T9 质量审查发现 TwoStageDeath 在真实运行中必然卡死:PerformDeath 先
ForceState(Dead) 再 Send(Died),IsControllable 此后永久为假,AiRuntime
的让位门使死亡链里的条件边永不被求值。追根后确认整层放错位置——
死亡完全归物理层(PerformDeath/EnemyDeathSequence/EnemySpawnerOnEvent),
AI 图只需要一个终态让全局 Died 边有去处。

初稿做成一层的由来:旧 Config 的 DeathAbilityId 字段,而其唯一使用者
E001 留空并注明"演出走物理状态机"。投机字段被放大成了三模块的层。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 13:54:40 +08:00
joywayer 5a9c1fd569 chore(enemy): 补齐死亡层模块的 .meta 文件 2026-07-29 13:46:45 +08:00
joywayer 3bc7eb1855 feat(enemy): 死亡层三模块(终态/单段/两段) 2026-07-29 13:46:20 +08:00
joywayer 3d00c66677 test(enemy): 补测 Attack 单出边的自愈路径,写明边序依赖的射程前提
Approach 边序注释:attackInRange 先于 leftAllZones 的前提是攻击射程恒小于
感知区(EnemyAttackSelector.Eligible 的 InAttackRange 门),故两条件不会相争;
若未来出现射程更大的招式,应由 AI 配方校验器报错,而非靠调边序绕开。

FakeCombat 新增 BestAttackFails 开关(与 Eligible 解耦),补测 Attack 唯一出边
attackDone 的自愈不变量:UseBestAttack 触发失败时不留下运行中能力,下一帧即可
自愈回 Approach,不会永久卡死。
2026-07-29 13:08:46 +08:00
joywayer 9d632cd103 fix(enemy): 退回 Approach 边序——脱战优先属假件失真,改由测试贴合真实射程语义
HasEligibleAttack() 只看攻击射程圆(远小于追击/视野区),脱离全部感知区在真实系统里
必然已脱离攻击射程,原顺序(attackInRange 先于 leftAllZones)本就正确。
FakeCombat.Eligible 是裸字段不会随 Chase/Vision 联动,才让上一版看似需要调边序。
改法:边序退回原样,测试里玩家离开感知区时手工同步清 Eligible,贴合真实耦合关系。
2026-07-29 12:59:07 +08:00
joywayer a700bef352 feat(enemy): ApproachAttackEngagement——攻击改为起手即打完,不中途收招
寻路逼近↔到射程选招攻击的交战模块。招式射程/冷却/权重归 EnemyAttackSelector,
本模块只决定何时逼近、何时出手。

有意的行为变更:Attack 态去掉 leftAllZones 边,玩家在前摇中跑出感知区不再
中途打断招式,招式完整打完后经 Approach 自然脱战。
2026-07-29 12:53:49 +08:00
joywayer 5771ac414e test(enemy): 补测 Committed 冲锋的防抖动闭环,RushExit 写明冷却前提 2026-07-29 12:45:43 +08:00
joywayerandClaude Opus 5 d013fe8e2e docs(enemy): 写明 RushExit.Committed 防抖动的前提是能力 cooldown > 0
质量审查发现:Committed 的冷却门若遇上 cooldown==0 的能力,
CanUseAbility 结束下一帧即恢复,Rush↔Rest 每帧抖动照旧出现。
这属能力侧配置错误,不在模块内兜底,纳入 AiDefinitionValidator 校验项。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:41:09 +08:00
joywayer bb1e2f7705 feat(enemy): RushEngagement——两个耦合 flag 合并为 RushExit 语义枚举 2026-07-29 12:32:50 +08:00
joywayerandClaude Opus 5 1c6ce57648 test(enemy): 修正测试名——该用例只验证空 Link 不加边,不涉及单向不可逆
原名 DisguiseThenPatrol_HasNoInternalEdges_DisguiseIsOneWay 承诺了
"伪装态不可逆",但那需要骨架的升级边才能验证(T10 覆盖)。
此用例实际只证明 Link 为空时状态不会自行跑掉,按实测内容改名。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:26:42 +08:00
joywayer acfba66c5d feat(enemy): 未发现层三模块(单一态/单向降级/定时交替) 2026-07-29 12:19:26 +08:00
joywayer 894ae99984 test(ai): 补测经 AiStateFragments 的跨模块状态撞名,State() 补引导注释 2026-07-29 12:11:07 +08:00
joywayer 80fdc36762 feat(ai): BrainBuilder.DeclareState——重复声明状态即报错,防模块间同名静默覆盖
State() 仍保持宽松(有则取),用于给已声明的态挂边;DeclareState() 只用于
首次声明行为,重复声明同名状态会抛异常。AiStateFragments 的四个建态原语
(Locomotion/Ability/AbilityOnce/Terminal)改用 DeclareState。同时给
IEngagementModule/IDeathModule/IUnawareModule 补充不变量说明。
2026-07-29 12:01:19 +08:00
joywayerandClaude Opus 5 5dcbce6019 docs(enemy): 计划新增 T5b——BrainBuilder.DeclareState 防模块间状态名冲突
契约质量审查提出:模块化后 State() 的"有则取"语义使两个独立模块
取同名状态时后者静默覆盖前者回调。与 T1 的 RequireState 是同一类
失败的两面,趁 0 个模块实现时补代价最低。附带补充交战/死亡层
"为何单阶段 Build 足够"的不变量说明。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 11:57:57 +08:00
joywayer 17e71dac39 feat(enemy): 未发现层/交战层/死亡层三个模块契约 2026-07-29 11:47:29 +08:00
joywayerandClaude Opus 5 2643f58b08 docs(enemy): 计划同步——AiStateFragments 空能力 id 改建图期报错
原稿沿用旧代码的静默跳过写法。AiStateFragments 成为公开原语后,
定制 AiScript 漏配 id 会产出"存在但什么都不做"的状态且无报错,
与 T1 引入 BrainBuilder.RequireState 的理由是同一类失败,不应双标。
校验放建图期而非每帧回调,兼顾快速失败与热路径开销。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 11:43:15 +08:00
joywayer 68f615e9c1 fix(enemy): AiStateFragments 空能力 id 建图期报错,补 OnExit 收尾测试
Ability/AbilityOnce 建图期校验 abilityId 非空,与 BrainBuilder.RequireState
同一类"漏配即快速失败"约束保持一致,避免定制 AiScript 漏配 id 时产出静默
不作为的状态。同时补 4 个测试:Locomotion/Ability 的 OnExit 收尾行为,以及
两个原语对空 id 的报错行为。
2026-07-29 11:42:47 +08:00
joywayer 77a32cbf8a feat(enemy): AiStateFragments 建态原语,新增 AbilityOnce 修死亡态重触发隐患
从 PerceptionStateMachine 的 private static 辅助方法提升为公开原语,供后续
模块化重构(骨架+可插拔模块)与定制 AiScript 复用。新增 AbilityOnce:
只在进入时触发能力、不每帧重触发,修复用 Ability() 驱动死亡态时演出播完
被 Tick 无限重播的隐患(现有 PerceptionStateMachine 因 E001 死亡能力 id
为空未暴露,本次不改它,留待 T11 整体退役)。
2026-07-29 11:35:47 +08:00
joywayer 084354fa6c test(enemy): AbilityRef 补测资产已挂但 abilityId 为空的误配路径 2026-07-29 11:29:47 +08:00
joywayer 474e89de3a feat(enemy): AbilityRef——AI 层能力引用资产化,字符串仅作定制脚本兜底 2026-07-29 11:22:15 +08:00
joywayer fd238b71f1 refactor(ai): AiSignal 8→1,删除零发送方零消费方的死枚举 2026-07-29 11:14:29 +08:00
joywayer e76d6bc118 feat(ai): BrainBuilder.RequireState——未声明状态挂边时构建期报错 2026-07-29 11:08:23 +08:00
joywayerandClaude Opus 5 8d63525b12 chore(scene): TestRoomA 敌人感知槽改 Box 型并配尺寸,微调物体位置
aggro/los 两个感知槽由点/圆改为 Box(type 7),offset y=0.31,
分别配 8.1x1.38 / 11.65x1.38;顺带清掉随 NavDriving 死代码移除的序列化残留字段。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 11:05:15 +08:00
joywayerandClaude Opus 5 f3c17de223 docs(enemy): 敌人 AI 组合式模块架构实施计划(17 个任务,TDD)
含计划阶段发现的三处修正:
- 未发现层拆 Declare/Link 两阶段,保证骨架升级边优先于内部计时边;
- 模块参数校验与委托缓存移出构造函数([SerializeReference] 不保证走 ctor);
- 新增 AiStateFragments.AbilityOnce,修死亡态演出被 Tick 无限重播的隐患。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 10:51:07 +08:00
joywayerandClaude Opus 5 e59e24cca0 docs(enemy): 修正 AI 配方解析方式——直接引用替代注册表双源
实施计划阶段发现两处硬阻塞:
1. BaseGames.AI 只引用 BaseGames.Core,无法收集依赖 Enemies 的 AiRecipeSO;
2. 项目 database SO 走 [SerializeField] 直接引用而非 Addressables,静态注册表取不到。

改为 EnemyAiBrain 上 _recipe 资产引用 / _definitionId 脚本 id 二选一。
净减 AiRecipeDatabaseSO、Addressables 加载、AddressKeys 改动、asmdef 改动;
flyweight 与配方路径零字符串 id 均保持。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 10:38:07 +08:00
joywayerandClaude Opus 5 1c32d8c87e docs(enemy): 敌人 AI 组合式模块架构设计(替换 PerceptionStateMachine)
四层架构:固定感知骨架 + 可插拔的 未发现层/交战层/死亡层;
两种定义源:PerceptionRecipeSO 配方资产(约 95% 敌人零代码)+ AiScript 定制脚本;
消除 Config 开关面板(EngagementStyle/PatrolBetweenChases → RushExit 语义枚举);
能力引用资产化(AbilityRef);AiSignal 8→1 清理死枚举。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 10:20:22 +08:00
joywayer c4e80afe9e chore(enemy): 清除随寻路库失效的 NavDriving 死代码与两处过时注释
NavDriving 原由已删除的寻路适配层设置,现无人写入(全项目 C# 零引用),
连同其调试字段一并移除;场景/预制体里的序列化残值由 Unity 下次保存自动丢弃。
2026-07-28 13:28:52 +08:00
joywayerandClaude Opus 5 d46ba67385 merge: 移除第三方 2D 寻路库,敌人导航改为直接移动方案
核心洞察:可达性由 EnemyMovement 的墙/悬崖夹紧天然表达——走过去、被夹停就是边界,
无需重建任何图/段结构。'agent 脱离导航段导致寻路全面静默失效'这一整类问题随之消失。

- IPathAgent(20+成员) → IEnemyNavigator(7成员);GroundNavigator/FlyingNavigator/NullEnemyNavigator
- 三处重复的卡死检测收敛为 StallDetector;修复 Pace 持续受阻无法掉头的死锁
- 修复 Wander 挑点失败退化为永久播 Idle 的缺陷
- 删除寻路库整包(112脚本/21069行)及对其源码的 4 处项目改动
- 脚手架/编辑器面板/Boss 预制体同步清理

净变化:删约 21500 行,加约 350 行。
验证:编译 0 错误;EditMode 206/206;PlayMode 无寻路库环境下三种巡逻+追击实测通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 13:20:05 +08:00
joywayer a220b5c0d8 删除多余 2026-07-28 13:18:41 +08:00
joywayer 02ea0bb576 docs(enemy): 标注脱图脱困设计已作废(被移除寻路库方案取代,未实施) 2026-07-28 13:03:54 +08:00
joywayer 878e2c12cc chore(enemy): Boss 预制体移除随寻路库失效的 3 个导航组件(Boss 本就不使用导航) 2026-07-28 13:02:52 +08:00
joywayer 7934513b05 chore: 删除第三方 2D 寻路库整包(112 脚本/21069 行,含 4 处对其源码的项目改动)
敌人导航已改为 GroundNavigator/FlyingNavigator 直接移动方案:
可达性由 EnemyMovement 的墙/悬崖夹紧天然决定,不再需要烘焙导航图,
'agent 脱离导航段导致寻路全面静默失效'这一整类问题随之消失。
2026-07-28 11:50:33 +08:00
joywayer 3c622a69f2 refactor(enemy): NavLinkType 迁入 INavLinkHandler 并改述为能力驱动的地形穿越;补交新脚本 .meta 2026-07-28 11:50:17 +08:00
joywayer 4455062ee9 refactor(editor): 脚手架改用 GroundNavigator,移除寻路库引用与导航面放置工具 2026-07-28 11:45:24 +08:00
joywayer 219fe46719 fix(enemy): GroundNavigator 停止改为直写移动输入,消除与 EnemyBase.StopMovement 的无限递归(栈溢出) 2026-07-28 11:39:25 +08:00
joywayer de3c9deff4 refactor(enemy): 消费者切换到 IEnemyNavigator(Wander/Waypoints 去导航图化,调试面板简化) 2026-07-28 11:25:21 +08:00
joywayer 49627d357d refactor(enemy): FlyingDirectNavigator 改名 FlyingNavigator 并切换到 IEnemyNavigator 2026-07-28 11:17:34 +08:00
joywayer c041712e99 fix(test): 到达边界用例改用二进制可精确表示的值(1.25/0.25),避免浮点噪声误判 2026-07-28 11:15:16 +08:00
joywayer 668bc3500e feat(enemy): 新增 GroundNavigator 地面直接移动导航(+7 纯函数单测)
补充 EditMode 测试程序集对 BaseGames.Enemies.Navigation 的引用(此前缺失,新测试无法编译)
2026-07-28 11:12:11 +08:00
joywayer 2174e42ce0 feat(enemy): 新增 IEnemyNavigator 瘦身导航接口(7 成员)与 NullEnemyNavigator 2026-07-28 11:07:53 +08:00
joywayer 8ea919a7f1 chore(enemy): 补交 StallDetector 与其测试的 .meta 2026-07-28 11:07:06 +08:00
joywayer c929b5efb7 refactor(enemy): 三处卡死检测收敛为 StallDetector;修复 Pace 持续受阻时无法掉头的死锁 2026-07-28 11:04:18 +08:00
joywayer 5cfacf199a feat(enemy): 新增 StallDetector 推进停滞检测(+8 单测),为收敛三处重复实现做准备 2026-07-28 11:01:01 +08:00
joywayer d9ec0a4f6e docs(enemy): 移除寻路库实现计划(10任务/6阶段,TDD+编译门+回归门+PlayMode验收) 2026-07-28 10:53:21 +08:00
joywayer d74a2b6646 docs(enemy): 移除寻路库后的敌人导航框架设计(IEnemyNavigator 瘦身+地面直移+StallDetector 收敛) 2026-07-28 10:41:30 +08:00
joywayer 579499e678 docs(enemy): 脱困改用敌人自身步速(MoveHorizontal/WalkSpeed),移除独立速度参数 2026-07-27 16:45:00 +08:00
joywayer e7f1633877 docs(enemy): 导航脱图统一脱困设计(含 Wander/Waypoints 静默吞失败与 Pace 边沿翻向两个根因) 2026-07-27 16:31:32 +08:00
joywayer 28cd335f40 chore(enemy): [临时调试] E001 出生即巡逻,用于真机验证中心悬空导致导航失效的问题
代码处已留 [临时调试] 标记,验证结束后改回 Entry=Idle_Disguise。
2026-07-27 16:01:03 +08:00
joywayer 47c2ce7e99 fix(enemy): 冲锋禁止以中心悬空姿态收尾(会导致导航映射失败/巡逻失效)
新增 EnemyMovement.IsCenterOverGround() 与 RushAbility.SettleRoutine:
冲锋结束时若着地却中心悬在崖外,继续朝原方向推进直到离地(自由落体)或中心重回地面上方;
有时限,超时仍未脱离则告警暴露地形问题(崖沿有墙),不静默留下坏站位。
2026-07-27 15:50:01 +08:00
joywayer 80c0e5c277 revert(enemy): E001 初始态改回 Idle_Disguise(伪装静止),移除临时调试改动 2026-07-27 15:27:15 +08:00
joywayer e2645b6212 fix(editor): 脚手架提示改为 BrainGraph 指引,清除已废弃的 BehaviorTree/BossSkillSequencer 说法 2026-07-27 15:09:47 +08:00
joywayerandClaude Opus 5 bcc4292076 merge: 冲锋能力(RushAbility) — 锁定终点+可冲下悬崖
ContactChaseAbility → RushAbility(文件与类改名,GUID 保留,引用不断):
- 起手动画前锁定玩家 X 与方向(committed 冲锋,可被躲)
- 终点=身体整体越过锁定点(EnemyBase.Body.HasPassed),宽度取自身体几何权威
- 终止:越过锁定点/撞墙(只判墙)/maxDashDuration 超时/净位移卡死
- 冲锋期间经 EnemyMoveInput.AllowLedgeCross 放开悬崖夹紧,可冲下崖不悬停边缘
- 脚手架(SceneObjectPlacerTool/CharacterWizardWindow)同步更新

验证: 编译 0 错误; EditMode 191/191; PlayMode 实测越过锁定点/撞墙终止/
崖边夹紧对比(false=0,true=9)/端到端冲下悬崖并落地。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 13:45:13 +08:00
joywayer 4808077fc6 feat(enemy): ContactChaseAbility 改造为 RushAbility(冲锋)
- 起手前锁定玩家 X 与方向(committed),终点改为身体整体越过锁定点(Body.HasPassed)
- 终止条件:越过锁定点/撞墙(只判墙)/最大冲刺时长(SO 新增 maxDashDuration)/净位移卡死
- 冲锋期间经 EnemyMoveInput.AllowLedgeCross 放开悬崖夹紧,可冲下崖不悬停边缘
- 类与文件重命名(GUID 保留),脚手架与向导同步更新
2026-07-27 13:38:05 +08:00
joywayer 7cba58919f feat(editor): 敌人/Boss 放置脚手架绑定 EnemyBase._bodyCollider(身体几何唯一权威) 2026-07-27 13:17:38 +08:00
joywayerandClaude Opus 5 33a52c4531 merge: 角色身体碰撞体唯一权威源(IEnemyBody)
EnemyBase 持有 _bodyCollider 并暴露 Body;EnemyMovement 5 处探测与 EdgeSafeMargin
改走权威,删除重复 4 次的碰撞体解析与 GroundColliderOrSelf;
行为等价(两个敌人原本即回落到根碰撞体),EditMode 191/191 + PlayMode 崖边夹紧验证通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 12:13:10 +08:00
joywayer 9f8d4f21ca fix(enemy): EnemyMovement 加 RequireComponent(EnemyBase),固化身体权威同物体不变量 2026-07-27 12:13:10 +08:00
joywayer b1d3c169ec chore(enemy): 补交 EnemyBody 与其测试的 .meta 2026-07-27 12:07:46 +08:00
joywayer 9a61d3e256 refactor(enemy): 移动层探测与 EdgeSafeMargin 改走 Body 权威,删除重复的碰撞体解析 2026-07-27 11:55:55 +08:00
joywayer 6026911532 feat(enemy): EnemyBase 持有身体几何权威(_bodyCollider + Body),Awake/OnValidate 双重校验 2026-07-27 11:51:45 +08:00
joywayer b0e1ebfecc feat(enemy): 新增 IEnemyBody 身体几何权威接口与 EnemyBody 实现(+14 纯函数单测) 2026-07-27 11:48:26 +08:00
joywayer 8a6450910e docs(enemy): 身体几何权威源实现计划(4任务,TDD+编译门+191测试门+PlayMode等价性验证) 2026-07-27 11:38:34 +08:00
joywayer 3752a9a0e0 docs(enemy): 角色身体碰撞体唯一权威源(IEnemyBody)设计 2026-07-27 11:20:09 +08:00
joywayerandClaude Opus 5 d26872aa6a merge: 抽取 WeightedPick 加权随机共享原语(消除四处平行实现)
小怪选招/Boss 选招/战利品掉落统一到 BaseGames.Core.WeightedPick;
删除零调用的 SelectWeightedSkill 死代码,修 LootResolver 权重重复计算;
冷却与 HitBox 时序经评估否决抽取(理由见决策记录)。
EditMode 177/177 通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 10:01:14 +08:00
joywayer 86e3637d8e chore(core): 补交 WeightedPick 与其测试的 .meta 2026-07-27 10:00:59 +08:00
joywayer cd9acfcde1 docs: 共享底座抽取决策记录(WeightedPick 已抽;冷却/HitBox 窗口否决理由;Boss BrainGraph 路线) 2026-07-27 10:00:05 +08:00
joywayer 1d5214beea refactor(enemy): Boss 选招与战利品掉落改用 WeightedPick;删除零调用的 SelectWeightedSkill 死代码 2026-07-27 09:58:40 +08:00
joywayer 67220b4bbd refactor(enemy): 小怪选招改用 WeightedPick 共享原语(权重缓冲复用,零 GC) 2026-07-27 09:55:59 +08:00
joywayer 3d85f65965 feat(core): 新增 WeightedPick 加权随机共享原语(+10 单测),为消除四处平行实现做准备 2026-07-27 09:54:34 +08:00
joywayerandClaude Opus 4.8 c3ab961d3b merge: 敌人寻路逼近+攻击选择器(ApproachAttack 交战范式)
寻路逼近(Approach)↔选招攻击(Attack)两态子机,与冲锋(ChaseAbility)并列;
招式自管圆形射程;WeightedRandom/Priority 两选招模式;
EditMode 167/167 + PlayMode 冒烟验证通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:12:42 +08:00
joywayer 23de5adf75 fix(enemy): MeleeAttackAbility 空 clip 走 fallbackDuration(判 clip.Clip 而非 clip),兑现 EnemyAttackSO 契约 2026-07-24 15:12:41 +08:00
joywayer 568b6e46c0 chore(enemy): 补交新脚本的 .meta(AbilityCategory/AttackSelectionMode/IAttackCandidate/EnemyAttackSelector+测试) 2026-07-24 14:55:17 +08:00
joywayer 022e4f18d2 fix(enemy): Approach 逼近改用 aggro/视野双刷新的 LastKnown(修 aggro 先命中扑向错误点)+补 Attack 退出测试 2026-07-24 12:47:48 +08:00
joywayer f77ab71555 feat(enemy): 攻击能力射程 Gizmo(选中可见)+ category==Attack 的 rangeRadius 自检 2026-07-24 12:33:16 +08:00
joywayer 980fe35ae4 docs(enemy): PerceptionStateMachine 类注释补充 ApproachAttack 交战风格 2026-07-24 12:31:31 +08:00
joywayer 6cdd4ef513 feat(enemy): PerceptionStateMachine 增加 ApproachAttack 风格(寻路逼近↔选招攻击)+测试 2026-07-24 12:27:59 +08:00
joywayer 297f9d2968 feat(enemy): IEnemyLocomotion 增加 Pursue 寻路逼近原语(RunSpeed) 2026-07-24 12:23:43 +08:00
joywayer 6fd80cfec9 fix(enemy): BuildAttackSelector 表达式上下文全限定 AbilityCategory(消歧义于 Abilities 属性) 2026-07-24 12:22:35 +08:00
joywayer 702df35efd feat(enemy): ICombatant 增加 HasEligibleAttack/UseBestAttack + EnemyBase 构建选择器 2026-07-24 12:20:54 +08:00
joywayer 04a267b69f feat(enemy): EnemyAttackSelector 选招器(射程+冷却+LOS+着地过滤,两模式选招)+单测 2026-07-24 12:15:41 +08:00
joywayer 2cf0d6b1a9 feat(enemy): EnemyAbilityBase 实现 IAttackCandidate 契约(自管圆形射程) 2026-07-24 11:58:22 +08:00
joywayer 25fae97065 feat(enemy): 攻击选招元数据(category/weight/圆形射程)与选招模式入 SO 2026-07-24 11:56:07 +08:00
joywayer f15f0a6876 docs(enemy): 寻路逼近+攻击选择器实现计划(9任务,TDD选择器+状态机,编译门+Play验证) 2026-07-24 11:50:20 +08:00
joywayer c33ce55f13 docs(enemy): 寻路逼近+攻击选择器设计(感知管交战/招式自管射程/两选招模式) 2026-07-24 11:21:35 +08:00
joywayer 4a80a11ab5 fix(enemy): 改道到最近可达点时同步防抖记账,避免下次 RequestMoveTo 被误吞 2026-07-23 17:22:11 +08:00
joywayer 8e71b1a922 feat(enemy): Waypoints 受阻/到达即结算推进,删除对不可达点的无限重发 2026-07-23 17:09:06 +08:00
joywayer 49b7adbdae feat(enemy): IPathAgent 增加 LastMoveObstructed/TryGetClosestReachable 契约 2026-07-23 17:07:31 +08:00
joywayer 72ed14428c feat(enemy): FlyingDirectNavigator 实现受阻/最近可达点成员(飞行单位恒 false) 2026-07-23 17:06:21 +08:00
joywayer d84e66fcb9 fix(nav): 发出新寻路请求时作废上一次最近可达点,避免跨目标读到过期数据 2026-07-23 17:05:27 +08:00
joywayer 675e1950af feat(enemy): EnemyNavAgent 卡死检测+统一受阻处理+best-effort 改道到最近可达点 2026-07-23 16:56:56 +08:00
joywayer 4079392a79 feat(nav): NavAgent 暴露寻路失败时的最近可达点 TryGetClosestReachablePosition 2026-07-23 16:52:28 +08:00
joywayerandClaude Opus 4.8 a60073d92c docs(enemy): 敌人寻路受阻处理实现计划(6任务,编译门+Play验证)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:44:12 +08:00
joywayerandClaude Opus 4.8 04b294734a docs(enemy): 敌人寻路受阻处理设计(Waypoints+MoveTo,含卡死检测与最近可达点)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 16:37:39 +08:00
joywayer 02150836b0 场景修改和editor优化 2026-07-23 15:54:38 +08:00
joywayerandClaude Opus 4.8 859d4e9bd6 feat(enemy): 感知系统新增 BoxLOS 槽位(横向矩形视野+遮挡)
BoxCast 是纯几何矩形无遮挡,BatchLOS/Sight 有遮挡但为圆形。
新增 BoxLOS 槽位:OverlapBox 矩形区域 + 从感知原点多点 LOS 遮挡校验,
用于走廊/平台等横向矩形视野带遮挡的场景。

- enum 末尾追加 BoxLOS(避免打乱既有序列化索引)
- 复用 boxSize/boxOffset 定义矩形、losBlockMask/losRayCount/losMinVisibility 定义遮挡
- 方向由 boxOffset.x 控制:0=前后双向对称,boxSize.x*0.5=仅前向
- Editor 补 Gizmo(矩形+眼点+实时检测连线)与条件字段

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:54:17 +08:00
joywayerandClaude Opus 4.8 fccd87df6b fix(enemy): 修复退出 Play 时巡逻调试 Inspector 的 NRE 与 IMGUI 报错
退出 Play 有一个窗口:Application.isPlaying 仍为 true 但 PBWorld 已销毁,此时 Inspector
调 nav.CanReach → PBWorld.PathTo(instance=null) 抛 NRE;该异常在 IMGUI 布局组中途中断,
使 BeginHorizontal 组失衡 → 级联 ArgumentException("control N in a group with only N controls")。

修:
- 订阅 playModeStateChanged,ExitingPlayMode 置 _exitingPlay,CanReach 调用点加
  !_exitingPlay && isPlaying 守卫,退出期不再查询已销毁的 PBWorld。
- 运行时区块的绘制判定只在 Layout 事件锁定、Repaint 复用,避免 isPlaying 在两趟 GUI
  间翻转导致控件数不一致。

实测:选中敌人连续两次进/退 Play,Console 无 NRE/ArgumentException。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:33:41 +08:00
joywayerandClaude Opus 4.8 854bcb3d6f perf(enemy): 感知系统 Inspector 限频重绘,修复选中敌人掉帧
PhysicsPerceptionSystemEditor.OnInspectorGUI 尾部每帧 Repaint(),选中敌人时强制整个多组件
Inspector(本编辑器逐槽位 IMGUI + 同物体其它自定义编辑器)以游戏帧率(100+fps)重绘,单帧多耗
~18ms → 帧率从 140 掉到 40。

改为与 EnemyLocomotionEditor 同款:走 EditorApplication.update 定频(~5Hz)请求重绘,与游戏
帧率解耦,移除每帧 Repaint。感知 Gizmo 本就 NonSelected|Selected 都画(在基线内,非选中新增成本)。

实测(平均帧):选中开销从 ~18ms/帧 降到 ~4.3ms/帧(未选中~106fps / 选中~73fps,原为 140→40)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:24:22 +08:00
joywayerandClaude Opus 4.8 842a20fbb3 chore(enemy): [临时调试] E001 初始态改为 Move_Patrol,出生即巡逻便于 MCP 调试
仅为调试方便:Entry 从 Idle_Disguise 改为 Move_Patrol,一进场就走 Waypoints。
正式版应改回 Idle_Disguise(伪装静止),代码处已留 [临时调试] 注释标记待回退。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:13:30 +08:00
joywayerandClaude Opus 4.8 16edf7cb90 fix(enemy): Waypoint 巡逻寻路失败/丢失时自动重发,修复出生即巡逻卡死
TickPatrol 原来只在"首次或到达路点"时发一次 MoveTo。若这一次 UpdatePath 失败(如敌人
刚出生、当帧尚未映射到 NavGraph),路径永不建立且再不重试 → 永久原地不动(mode=Patrol
但 IsFollowingAPath=False)。旧 Entry=Idle_Disguise 因巡逻只在出生很久后才开始而掩盖了此问题。

新增:有目标但 nav 空闲(!IsMoving)且未到达 → 重发同一目标(只重发不推进索引;RequestMoveTo
自带 0.25s 防抖,寻路跨帧计算期间的重发被吞掉,不会每帧重启寻路)。

Play 实测:出生即在两路点间以 WalkSpeed 正常往返巡逻。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:13:19 +08:00
joywayerandClaude Opus 4.8 be7d511e45 feat(enemy): 巡逻调试 Inspector 加"一键打印报告"按钮(供 AI 诊断)
点击将完整状态以纯文本 Debug.Log 到 Console,可直接复制给 AI:AI决策层(EnemyState/
BrainGraph 当前状态名/LocomotionMode)、移动导航(Nav类型/IsMoving/IsOnLink/HasValidPosition/
PB2d Position/EdgeSafeMargin/_maxSnapDistance)、Waypoints(当前索引/吸附目标/CanReach/逐路点
实时吸附检测 raw→snap 及垂直差)、文末一句根因诊断。编辑期打印静态配置,Play 期打印运行时全量。

实测输出直指根因:Brain='Idle_Disguise' → LocomotionMode=Idle → TickPatrol 不执行。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 15:04:53 +08:00
joywayerandClaude Opus 4.8 424d9bbbe0 perf(enemy): 巡逻调试 Inspector 限频,消除每帧阻塞式 CanReach 掉帧
上版 OnInspectorGUI 每帧 Repaint 且每帧调用 NavAgent.CanReach——该 API 会同步阻塞主线程
跑一次完整寻路(其文档自述"not optimal for performance"),选中敌人时每帧一次直接把帧率拖垮。

改为:刷新走 EditorApplication.update 定频(~5Hz)与游戏帧率解耦;CanReach 限频 ~2Hz 并缓存;
_maxSnapDistance 运行期不变只读一次缓存(不再每帧 new SerializedObject)。实测选中敌人 ~53fps 稳定无卡顿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:15:53 +08:00
joywayerandClaude Opus 4.8 00ee48f750 feat(enemy): EnemyLocomotion 运行时巡逻调试(Inspector 面板 + Scene Gizmos)
排查"敌人原地不动"用:PlayMode 下自定义 Inspector 实时显示 模式/策略/是否移动/是否在导航图上/
当前路点与吸附目标/到目标距离vs到达半径/目标是否可达,并按优先级给出定位诊断(模式非Patrol|不在
NavGraph|吸附失败|不可达|已寻路未动)。Scene 视图 Gizmos 画出全部路点连线 + 当前吸附目标(绿/红)+到达半径。

EnemyLocomotion 加 #if UNITY_EDITOR 只读调试访问器 + OnDrawGizmosSelected;记录 _wpResolveOk。
Editor 程序集补 BaseGames.AI 引用(PatrolStrategy/LocomotionMode 所在)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:57:02 +08:00
joywayerandClaude Opus 4.8 66d100f353 fix(enemy): 目标点吸附改用固定大搜索距离,与 PB2d 映射任意点机制一致
上版把搜索距离绑到 agent→目标距离(仿 UpdatePath),仍脆弱:吸附距离取决于 agent 离得多远
而非目标离导航段多远——agent 贴在高处路点正下方时距离小、reach 小仍会漏。

查明 PB2d 映射机制(NavGraph.TryMapPointSurface):queryAABB 边长=传入值、可靠搜索半径仅其一半;
PB2d 映射"鼠标点击等任意点"用固定的 closestToSegmentMaxDistance(默认 20,"想多大就多大")。
据此改为:EnemyNavAgent._maxSnapDistance(默认 10m,真实"点离最近段"上限),底层传 2× 补偿 AABB 半宽。
只要目标在 _maxSnapDistance 内一定吸附到最近烘焙段,返回 false 仅表示目标真正脱离导航面。

Play 实测:两路点 + 压力用例(agent 正上方 3m/5m)ResolveStandablePoint 均 ok=True 且映射到地面段。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 10:35:24 +08:00
joywayerandClaude Opus 4.8 4ecf949d83 fix(enemy): 目标点吸附改用距离自适应搜索,修复稍离地路点吸附失败
之前 TryGetStandablePointNear 用固定 2m 搜索半径(实为 ±1m AABB)+ agent 过滤映射,
导致摆放在离地面 >1m 的路点(如 wp y=3.16 而地面段 y=2.00)映射失败、误报"超出搜索半径"。

改为与 PB2d 自身 UpdatePath 映射目标完全一致:搜索距离=agent 到目标距离(+0.1),
用不带 agent 过滤的 TryMapPoint(就近段几何映射,可达性交给 allowCloseEnoughPath 寻路阶段)。
只要图上有段就一定吸附得到最近点。移除 EnemyNavAgent._goalMapSearchRadius 固定半径字段。

Play 实测:两个路点 ResolveStandablePoint 均 ok=True((-20.90,3.16)->(-20.90,2.00)),
CanReach=True,敌人实际走到 x=-20.75(目标 -20.90)后停,无 EnemyLocomotion 报警。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 10:10:35 +08:00
joywayerandClaude Opus 4.8 132cd24908 feat(enemy): 目标点吸附统一处理导航宽度,Waypoint 不再卡崖边
将"角色导航宽度"收口到导航执行层单一入口,意图层(Locomotion/AI)只递原始目标、不再感知身体尺寸:

- NavAgent 新增 TryGetStandablePointNear:把世界点映射到最近可行走段并按 endMargin 内缩两端(与 SetRandomDestinationOnCurrentSegment 同源)
- EnemyNavAgent 新增 ResolveStandablePoint,以 EnemyMovement.EdgeSafeMargin(半宽+落崖余量)为唯一宽度真源;加 _goalMapSearchRadius
- IPathAgent 暴露 ResolveStandablePoint;FlyingDirectNavigator 原样返回(飞行无崖沿约束),NullPathAgent 空实现
- EnemyLocomotion Waypoint 分支改用吸附后的可达点做移动与到达判定,修复路点摆在空中/崖外时永不到达而卡死;吸附失败显式报错暴露误配,不静默兜底

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 15:52:18 +08:00
joywayerandClaude Opus 4.8 7f7d237915 feat(enemy): Wander 到达随机点后可配置停顿时长
到达一个段内随机游走点后原地停顿 [_wanderPauseMin, _wanderPauseMax] 秒再挑下一个点:
- 两个可配置字段(相等=固定时长;都为 0=不停顿立即挑点),每次停顿时长在区间内随机
- 停顿期间切 Idle 步态(否则站着播 Walk=原地踏步),速度归零
- 切换 locomotion 模式时清停顿态,避免残留
实测:移动→到点→停顿(Idle,velX=0)→再出发 循环正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 13:45:21 +08:00
joywayerandClaude Opus 4.8 31b77fa473 feat(enemy): Wander 巡逻改为当前段内随机 + 按身体宽度内缩两端
修复地面单位 Wander 巡逻"走一小段就停":
- 随机点从全图(GetRandomPointOnGraph)改为限定在 agent 当前所站的 NavSegment 上,
  不再跨越跳/落/拐角 NavLink,避免路径跨沟被崖边夹停或卡在过不去的 link。
- 段内随机 t 按"身体前缘安全余量(碰撞体半宽 + 悬崖检测前向偏移)"从两端各内缩,
  避免随机点落在角色宽度站不住的崖沿(WouldFallAhead 夹停、IsFollowingAPath 不复位→卡死)。
- NavAgent.SetRandomDestinationOnCurrentSegment(endMargin):段内采样+内缩(段过短退化到中点)
- EnemyMovement.EdgeSafeMargin:身体前缘安全余量
- IPathAgent.WalkToRandomOnSegment():EnemyNavAgent 传入 margin;飞行单位复用局部偏移;Null 返回 false

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 12:45:01 +08:00
joywayerandClaude Opus 4.8 46c9718793 docs: 删除 Behavior Designer 选型评估文档
BD 已被自研 BrainGraph HFSM 替换、不再使用,其选型评估文档一并移除。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:40:42 +08:00
joywayerandClaude Opus 4.8 9226eb0330 docs: 删除过时指南 02(敌人搭建) 与 08(BD 行为树编写)
- 02_Enemy_Boss_Setup_Guide:敌人框架仍在改动、预制体延后,指南暂删待功能定稿后重写
- 08_BehaviorTree_Authoring_Guide:Behavior Designer 已被自研 BrainGraph HFSM 替换,BD 不再使用

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:39:16 +08:00
joywayerandClaude Opus 4.8 3bca15b69e fix(wizard): 移除 E001 残留的 Alert 能力定义
Alert/警觉张口在 locomotion 重构后已改为纯移动模式(LocomotionMode.Face),
不再是能力(ABL_E001_Alert SO 已删);起手张口并入 ContactChaseAbility 的 Skill_Start。
向导 GetEnemyAbilityDefs 里 E001 仍列 ("Alert","e001_alert") 是残留,
会导致重跑向导时又造一个无用 SO。E001 现只保留 Chase 能力。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:35:15 +08:00
joywayerandClaude Opus 4.8 a94348d64c chore(enemy): 移除 E001-E006 小怪预制体(延后至功能定稿再用脚手架产出)
敌人框架改动期间不再维护预制体,待所有敌人功能定稿后用 SceneObjectPlacerTool 统一重建。
ChaoFeng Boss 预制体保留。
- 删除 6 个 ENM_*.prefab 及 .meta
- Enemies.asset(Addressables 组)同步移除条目
- TestRoomA.unity 移除失效敌人实例引用

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:31:44 +08:00
joywayerandClaude Opus 4.8 6f519ec9a9 refactor(enemy): 身体接触伤害改为常驻开启并与能力解耦
所有敌人默认常驻身体接触伤害:存活即开、死亡时经 PerformDeath 统一关碰撞体停止。
- ContactChaseAbility/AnimatedCeilingDropAbility 移除对 BodyContactDamage 的开关引用
- BodyContactDamage 语义改为常驻(更新注释)
- 脚手架 SetupHurtAndContactBoxes 去掉 contactEnabled 参数恒为 true,并把
  ContactDamageZone 的 HitBox._targetLayers 限定为 PlayerHurtBox(避免常驻后敌人互相误伤)
- 7 个敌人预制体:E001 启用 BodyContactDamage;E002/E004/E005/ChaoFeng 补齐
  ContactDamageZone(镜像 HurtBox 身体碰撞体);全部 _targetLayers 收敛为 PlayerHurtBox
- 更新 Docs/Guides/02 敌人搭建指南

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:18:07 +08:00
joywayerandClaude Opus 4.8 b4379db5de docs(game): 建立游戏设计文档规范并迁移重写 Docs/Game 设计文档
- 新增 Docs/Standards/GameDesignDocSpec.md:目录结构/通用文档头/六类分节/命名编号/交叉引用/禁参考游戏字样
- Docs/Game 按类别重组:总览/主角/敌人(小怪+Boss)/NPC/系统/剧情 + _Templates 模板
- 小怪 E001-E006、嘲风:程序文档+动作需求表合并为单份综合文档
- 其余 7 Boss 迁移设计层内容,程序/动画/数值节标注待补充
- 主角能力设定清除全部外部游戏参考字样并修正 OCR 乱码
- 删除 参考/ 原始文档(已忠实重写,原件保留于 git 历史)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:42:33 +08:00
joywayer 94bbf3acf2 chore(data): 7 个 ABL_ 能力资产重型为对应子类 SO 并迁入参数(m_Script+字段) 2026-07-21 12:17:21 +08:00
joywayer 79829cc629 场景改动 2026-07-21 12:05:04 +08:00
joywayer 447575d66e docs(enemy): 更新迁移后能力脚本残留旧字段名注释为 _cfg.* 2026-07-21 11:45:30 +08:00
joywayer 8bd24df11e feat(wizard): 敌人能力 SO 按能力类型创建正确子类(向导批量创建产出子类 SO) 2026-07-21 11:40:44 +08:00
joywayer 6d071e83ae refactor(enemy): Appear/FacePlayer/PlayClip 参数迁入各自子类 SO 2026-07-21 11:35:39 +08:00
joywayer 78c8c6dcd6 refactor(enemy): MeleeVulnerability 参数迁入 MeleeVulnerabilityAbilitySO(子类) 2026-07-21 11:32:20 +08:00
joywayer 34772c3d20 refactor(enemy): AnimatedCeilingDrop 参数迁入 AnimatedCeilingDropAbilitySO(子类) 2026-07-21 11:29:20 +08:00
joywayer 16b0d7b819 refactor(enemy): CeilingHangStrike 参数迁入 CeilingHangStrikeAbilitySO(子类) 2026-07-21 11:26:03 +08:00
joywayer 1e3b01705f refactor(enemy): RepeatSlam 参数迁入 RepeatSlamAbilitySO(子类) 2026-07-21 11:23:11 +08:00
joywayer 6873a2cd8e refactor(enemy): ContactChase 参数迁入 ContactChaseAbilitySO(子类) 2026-07-21 11:19:08 +08:00
joywayer ca36974ec3 feat(enemy): EnemyAbilityBase 加 ResolveConfig<T> 类型化配置解析(不匹配显式报错) + EditMode 测试 2026-07-21 11:14:28 +08:00
joywayer 0ad4acc484 docs(plan): 敌人能力配置迁移到 AbilitySO 子类实现计划 2026-07-21 11:04:36 +08:00
joywayer 68e2cc506d docs(spec): 敌人能力配置迁移到 AbilitySO 子类设计文档 2026-07-21 10:42:25 +08:00
joywayer a7307a5a07 调整文档 2026-07-21 10:22:43 +08:00
joywayer 2d4b3cfd96 feat(wizard): 敌人/Boss 加默认外观 Sprite 字段并透传给脚手架(碰撞体尺寸依据) 2026-07-20 16:42:13 +08:00
joywayer 3af4e4140b refactor(scaffold): ChaoFeng + 通用 Place 统一 Box HurtBox/接触伤害区 + sprite 尺寸 2026-07-20 16:35:25 +08:00
joywayer 4fcbc73d44 refactor(scaffold): E002-E006 统一 Box HurtBox/接触伤害区 + sprite 尺寸(补全缺失的接触伤害区) 2026-07-20 16:24:03 +08:00
joywayer 8efd5e2207 refactor(scaffold): E001 用统一助手建 Box HurtBox/接触伤害区 + sprite 尺寸参数 2026-07-20 16:11:18 +08:00
joywayer 3124b2bba2 feat(scaffold): 新增 SpriteSizeOr / SetupHurtAndContactBoxes 助手(Box 统一 HurtBox/接触伤害区) 2026-07-20 16:01:15 +08:00
joywayer ee0bc6948d @
docs(scaffold): 敌人碰撞体 Sprite 驱动+统一 Box+全员接触伤害区 实施计划

Task1 助手(SpriteSizeOr/SetupHurtAndContactBoxes)/Task2 E001 样板/Task3 E002-E006/
Task4 ChaoFeng+通用/Task5 向导 sprite 字段/Task6 端到端验证。只改脚手架+向导。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 15:51:35 +08:00
joywayer 82d5212dfd @
docs(scaffold): 敌人碰撞体 Sprite 驱动 + 统一 Box + 全员接触伤害区 设计文档

脚手架生成的敌人(含 Boss):主体/HurtBox/ContactDamageZone 三者统一 BoxCollider2D、
同尺寸、底部对齐 y=0;尺寸由向导默认 Sprite 的包围盒推导(留空回退硬编码);每个
敌人都有 ContactDamageZone。只改脚手架+向导,不动现有 prefab。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 15:46:52 +08:00
joywayer b8360ec426 @
fix(scaffold): 兜底把 HurtBox/接触伤害节点所有碰撞体设为 trigger

脚手架原本只对自己创建的具体类型(Capsule/Circle)设 isTrigger=true;若碰撞体被
手动换成别的类型(如 BoxCollider2D,默认 isTrigger=false),那条就漏掉——E001 的
HurtBox/ContactDamageZone 被换成 Box 后 isTrigger 掉回 false,导致受击/接触伤害
的 trigger 判定失效。

新增 EnsureCollidersAreTriggers 助手,对节点全部 Collider2D 兜底设 trigger;
PlaceE001 的 HurtBox 与 ContactDamageZone 建好后调用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 14:50:00 +08:00
joywayer d7be0cc119 @
feat(ai): 追击冲刺带冷却——CD 期巡逻,冷却结束仍在追逐区则再冲(PatrolBetweenChases)

按需求:冲刺打完 → CD 期切到巡逻(Move_Patrol) → 冷却结束且玩家仍在追逐区 → 再冲刺。

PerceptionStateMachine 加 opt-in 开关 PatrolBetweenChases:
- Chase → Patrol When !IsAbilityRunning(chase):冲刺是 committed 冲锋,感知丢失
  也不中途打断,打完(能力结束)即降巡逻。
- Idle/Patrol/Alert → Chase 加冷却门 CanUseAbility(chase):CD 中不回追击,
  避免"CD 期在追逐区每帧 Chase↔Patrol 来回抖"(用户担心的错切/抖动)。
- 默认关:不影响其他敌人(leftAllZones 旧行为保留)。
E001CaoZhiAi 打开开关。追击能力保持单次冲刺不变,冷却=ABL_E001_Chase.cooldown(1s)。

测试:PerceptionStateMachineTests +5(CD 转换矩阵含"CD 期多帧不抖动"负例),
FakeCombat 加可控 CanUse。全量 75/75 绿。PlayMode 实测循环
Move_Patrol(CD)→Chase(冲)→Move_Patrol,inChase 全程 true 亦不抖,0 报错。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 13:11:28 +08:00
joywayer 1a15473966 @
fix(enemy): 转身中不再静默丢弃新朝向请求,修"冲刺方向与朝向相反"

UpdateFacing 原 `if (_isTurning) return` 会把转身进行中的新朝向请求静默丢弃。
当敌人在平台边缘触发 Pace 翻转(开始 Flip 转身)同时进入追击时,追击的
FaceDirection(朝玩家)被丢弃,旧的巡逻转身完成后朝向定格在与玩家相反方向
→ 冲刺朝玩家方向移动、脸却朝相反(并连带"冲刺中途转身""提前停"两个衍生现象)。

改为:转身中收到不同方向请求时重定向(停旧转身按新方向重起),同向则忽略。
根因修复,惠及 Pace/追击/FaceTarget 所有调用方。

systematic-debugging 确定性复现证实:修复前 AFTER-TURN dir=+1 facing=-1;
修复后开阔地冲刺全程 dir=1 facing=1、冲满 6 米到真实平台边缘才停。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 13:03:29 +08:00
joywayer 55b6255ce6 @
test(ai): BrainGraph 全面测试——框架边界 + 全感知矩阵(70 tests 全绿)

保证自研 AI 状态机(BrainGraph)稳定与功能正确性。EditMode AI 测试从 31 增至 70。

新增 AiFrameworkTests(17)——框架健壮性/边界(与 AiRuntimeTests 主干互补):
- 构图校验:Entry 未设/未声明、全局转换目标未声明、GlobalBuilder 顺序错用抛异常
- 转换优先级:同态多条件首声明胜、全局条件转换、全局优先于局部
- 事件 vs 条件:事件优先(Tick 首步)、未匹配信号被消费不阻塞、多信号首个匹配胜
- 时序:条件自转换 no-op、转换当帧跳过 OnTick、Tick 双参传 dt
- trace 环形上限 16、事件推进后清挂起、State 重声明累加转换

重写 PerceptionStateMachineTests(22)——完整感知↔状态矩阵:
- 待机/巡逻/警觉/追击/死亡 全转换 + HasAlert 分支(无警觉态不进警觉)
- 追击优先于警觉;追击永不回警觉、脱离全部感知才降级 R(=Patrol)
- 视野单独/追逐区单独仍维持追击;locomotion 意图(Idle/Patrol/Face)
- 追击触发能力、离开中断能力;死亡为终态不复活

验证:反射调用全部 [Test] 方法(负对照确认能捕获失败),70/70 通过。
去重:Blackboard/局部转换目标校验已由 BlackboardTests/BrainBuilderTests 覆盖,不重复。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 11:11:54 +08:00
joywayer 61ca84365d @
feat(scaffold): ChaoFeng(Boss) 撞墙检测层默认 Platform+Wall

补齐 Boss——脚手架 PlaceChaoFeng 加 _wallMask=Platform+Wall;prefab 同步直接设值
(原 Nothing,_wallCheckDist=0.2>0 会触发 Awake 撞墙未配报错)。至此 E001-E006+
ChaoFeng 全部配好 _wallMask。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 10:49:39 +08:00
joywayer ba4a07e96d @
feat(scaffold): E002-E006 撞墙检测层默认 Platform+Wall

把 E001 已有的 _wallMask 默认铺到 E002-E006 脚手架 PlaceExxx(AssignLayerMask
movement _wallMask = Platform+Wall);现有 5 个 prefab 的 EnemyMovement._wallMask
同步直接设值(原 Nothing,且 _wallCheckDist=0.2>0 会触发 Awake 撞墙未配报错)。

ChaoFeng(Boss)按用户范围未含,其 _wallMask 仍需单独配置(否则 Awake 报错提醒)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 10:44:41 +08:00
joywayer bd8e0a4f16 @
refactor(enemy): _wallMask 未配显式报错,不再静默回退地面层

按 CLAUDE.md §6:删除"_wallMask 为空则回退 _groundMask"的下游兜底(掩盖漏配)。
改为:WouldHitWallAhead/WallAndLedgeCheck 直接用 _wallMask;Awake 校验——启用
墙检测(_wallCheckDist>0)却 _wallMask=Nothing 时 Debug.LogError 提醒策划配置
(推荐 Platform+Wall)。

PlayMode 验证:E001(已配 Platform+Wall)无报错、撞墙检测正常;临时配错的敌人
(wallMask=Nothing) Awake 即报明确错误。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 10:36:11 +08:00
joywayer 902afbca1c 修改资源 2026-07-20 10:33:18 +08:00
joywayer 88fca8f9b0 @
fix(enemy): 撞墙检测层默认 Platform+Wall(策划可配) + 修 Physics2D 层矩阵落盘

- EnemyMovement._wallMask:本就 [SerializeField] 暴露在 Inspector,补 Tooltip
  说明为策划可配的撞墙检测层(巡逻翻边/追击冲刺共用,按碰撞体前缘);脚手架
  PlaceE001 默认赋 Platform+Wall,prefab 同步。原先 Nothing→回退 groundMask,
  漏检 Wall 层墙;现显式含 Wall。回退逻辑保留作未配置兜底。
- Physics2DLayerReport.SavePhysicsSettings:原实现(AssetDatabase.SaveAssets+
  改 defaultContactOffset)不落盘层矩阵——实测 Physics2D.IgnoreLayerCollision 的
  改动 SaveToSerializedFileAndForget(单例) 也捕获不到;改用 File/Save Project
  (实测 hash 变化确认落盘,且不触动打开场景)。Auto Fix 的矩阵修正现能立即持久化。

(说明:Enemy×Player 碰撞矩阵与报告 ExpectedPairs 此前已正确=忽略,无需改动;
地图障碍物层管理/冲刺可穿透障碍另行整体规划。)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-20 10:32:07 +08:00
joywayer db55889088 @
feat(enemy): 追击起手前先转身朝玩家(播 Flip) + 冲刺速度可配置

- ContactChaseAbility:进入追击先 FaceDirection 朝玩家,若背对则播 Flip 转身、
  等 IsTurning 结束后再 Skill_Start(张口)→冲刺;已朝向玩家则跳过转身直接张口。
- 新增 _dashSpeed 字段(<=0 回退 Stats.RunSpeed),冲刺速度可在能力 Inspector 配置。
- E001 EnemyMovement._enableTurnAnimation=true(prefab+脚手架),转向播 AnimConfig.Turn
  (Flip),用于巡逻翻边与追击起手前转身;脚手架 PlaceE001 同步。

PlayMode 验证(brain 关隔离):背对玩家→触发追击→clip=Flip(IsTurning)→转向完成
facing 朝玩家→Skill_Start→冲刺→Skill_End,"先转身再张口"成立。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 16:57:41 +08:00
joywayer 9eca69a6ba @
feat(enemy): E001 追击改为冲刺+冷却(朝玩家方向冲,撞墙/边缘停,CD后再冲)

按新设计:进入追击→Skill_Start(张口)→起手后按玩家所在侧一次性确定方向→
速度直冲(非 nav,接触伤害开)→撞墙/到平台边缘(WouldBlockAhead)或受阻即停→
Skill_End(收招)→进入冷却(ABL_E001_Chase.cooldown=1s)。一次冲刺=一次能力执行,
AI 追击态在冷却结束时经 EnsureAbility 重触发下一次冲刺。

- ContactChaseAbility:无限跟随改为有限冲刺;方向 committed(会越过跳开的玩家,
  可被躲);加窗口净位移卡死兜底——撞上未在检测层的碰撞体(玩家/边界墙)原地不动
  时结束冲刺转收招+冷却,避免卡死。
- ABL_E001_Chase.cooldown 0→1(可在 SO 调)。

PlayMode 验证(净域重载后):Skill_Start→冲刺(实测冲 7 单位、伤害开)→受阻→
Skill_End→冷却→冷却结束重新起手冲刺,完整循环。速度直冲不依赖 nav,敌人实际
位移正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 16:46:37 +08:00
joywayer 36aa2006e4 @
feat(enemy): E001 追击动画 Skill_Start/Loop/End 完整化,符合设计文档

依据 Docs/Game/Design/小怪设计-程序开发文档-01.md:感知→Skill_Start(张口,单次)
→Skill_Loop(快速爬行追击,循环)→丢失目标→Skill_End(收招,单次)→Move_Patrol,
E001 无警觉态。

原问题:ContactChaseAbility 的 loop/end clip 都空、无 start clip,追击全程无
动画;Approach 步态还错播 Move;有多余警觉态(Alert clip 也空)。

- ContactChaseAbility:加 _startClip;协程 Skill_Start(锁动画+等时长,不伤人)→
  开伤害+Approach+Skill_Loop(循环)→退出播 Skill_End。丢失目标(ExternalRequest
  中断)播 Skill_End;受击/死亡硬打断不播。
- EnemyBase:加动画锁 LockAnim/IsAnimLocked——一次性招式动画期间步态不覆盖;
  PlayLocomotionClip 的 Approach 改为不驱动步态(追击动画归能力)。
- EnemyLocomotion:锁定期不算"已应用",解锁后重同步步态(避免卡在 Skill_End)。
- ENM_E001_Stats:HasAlertState=false(设计无警觉态)。
- prefab + 脚手架:ContactChaseAbility 接线 Skill_Start/Loop/End;新增
  AssignClipTransition 助手(按名+路径定位同名 clip)。

PlayMode 验证:Skill_Start(locked/不伤人)→Skill_Loop(伤害开)→Skill_End
(locked)→步态恢复 Move,全链符合设计。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 16:27:02 +08:00
joywayer f1ca62f881 @
feat(enemy): 追击(nav)也夹紧悬崖边缘——碰撞体前缘不越地形边缘

MoveWithSpeed(导航跟段驱动追击)原先无边缘夹紧,仅靠 nav 图,agent 按点定位
可让碰撞体前缘越出段/地形边缘约半个碰撞体宽。现加"悬崖"夹紧(WouldFallAhead,
按碰撞体前缘即时判定),贴地生效:追击时碰撞体边缘停在地形边缘、不越界,与 Pace
一致。只夹悬崖不夹墙(墙交寻路绕行,免误夹停);跨沟的 nav-link 跳/落不经
MoveWithSpeed(仅 IsMovingOnSegment 时写速度),故不影响跳跃/下落过沟。

WouldBlockAhead 拆为 WouldHitWallAhead / WouldFallAhead 复用;PlayMode 校验
拆分正确(前缘朝悬崖侧 WouldFall=True)、Pace 不回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 15:48:58 +08:00
joywayer a4b9729de7 @
fix(enemy): 巡逻边缘不越界 + 卡死兜底 + Locomotion 惰性发现

- WouldBlockAhead(EnemyMovement):按请求方向从碰撞体前缘(bounds.max/min.x)
  即时射线判墙/悬崖,天然考虑碰撞体宽度;MoveHorizontal 据此夹紧前进,保证
  "碰撞体边缘停在地形边缘、不越界"。移动层夹紧与 Pace 翻向共用同一判定,
  避免"夹停却不掉头"。nav 走 MoveWithSpeed 不受影响。
- Pace 加窗口净位移卡死兜底:撞上未挂检测层的墙/卡角(含贴墙微抖)也能翻向,
  用净位移而非逐帧差以免微抖重置计数。
- EnemyBase.Locomotion 改惰性发现:关域重载下 Awake 设的实例字段可能残留
  为 null,首次访问按需补发现,编辑器/构建都不为空(此前致入口态偶发 NRE)。

PlayMode(跨调用取样,避免 Thread.Sleep 冻结主线程)验证:Pace 来回踱步、
遇边缘/墙掉头、碰撞体不越界。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 15:12:28 +08:00
joywayer 6f52e4ff81 @
fix(enemy): Pace 巡逻改边沿触发翻向,修边缘左右抖动

TickPatrol 跑在 Update(可变帧率),IsWallAhead/IsLedgeAhead 在 FixedUpdate
刷新。原电平触发(每帧 blocked 就翻向)会在检测刷新前的多个 Update 帧把同一
障碍连翻多次→掉头边缘左右抖动几次才正常。改为边沿触发(仅障碍首次出现时翻
一次),用 _paceBlockedPrev 记录上帧状态。PlayMode 验证:踱步移动正常、无
连续快速翻转。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 14:36:53 +08:00
joywayerandClaude Opus 4.8 6a34b42db0 fix(enemy): 终审修复——删死代码群体警戒、修 Waypoints 到达推进、补 Locomotion 缺失报错
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:05:56 +08:00
joywayerandClaude Opus 4.8 c43ca8e8b6 feat(enemy): EnemyLocomotion 巡逻策略 Pace(踱步) + Waypoints(路点 loop/ping-pong)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:47:14 +08:00
joywayerandClaude Opus 4.8 f7554b1a65 refactor(enemy): 删除 EnemyStatsSO 死字段(感知/击退/攻击等,已迁 sensor槽/DamageSourceSO)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:43:51 +08:00
joywayerandClaude Opus 4.8 d718b99685 refactor(enemy): 删除 AiPhase 影子层,动画统一由 locomotion/反应态/能力驱动
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:38:27 +08:00
joywayerandClaude Opus 4.8 347d0e2f50 feat(enemy): EnemyLocomotion 按模式驱动步态动画(Idle/Walk/Alert/Run)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:33:16 +08:00
joywayer 087aa96ca6 @
fix(ai): EnemyAiBrain 在 Start 构造 runtime,修入口态 locomotion NRE

入口状态 OnEnter 会声明 locomotion 意图(x.Locomotion.SetMode),但原在 Awake
构造 AiRuntime 时 EnterInitial 立即触发该 OnEnter——此时 EnemyBase.Awake 可能
尚未发现 _locomotion(两组件 Awake 顺序未定),导致 x.Locomotion 为 null 抛 NRE,
brain 未建成 runtime。改在 Start 构造(所有 Awake 先于任一 Start),保证依赖就绪。
根因修复,非 null 兜底。P3 PlayMode 验证发现。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 13:30:46 +08:00
joywayerandClaude Opus 4.8 95e562943b refactor(scaffold): 重生成 E001 prefab(去三协程能力节点,仅留 ContactChase+Locomotion)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 13:23:09 +08:00
joywayerandClaude Opus 4.8 1bc6f5bf1c refactor(ai): Chase 走 Locomotion.Approach;删除 Idle/Patrol/Alert 协程能力与 SO
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:24:45 +08:00
joywayer 63b3701fc7 @
test(ai): PerceptionStateMachine locomotion 意图回归测试(绿)

新增 PerceptionStateMachineTests:待机态声明 Idle locomotion 意图、进追逐区
转 Chase 并触发追击能力、Chase 态不再直接声明 locomotion 意图。测试程序集
补 BaseGames.Enemies 引用。断言经真实状态机 + 测试 fake 验证通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 12:21:28 +08:00
joywayerandClaude Opus 4.8 0f0112eb91 refactor(ai): PerceptionStateMachine 状态改绑 locomotion 意图
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:17:49 +08:00
joywayerandClaude Opus 4.8 1ab0521061 refactor(ai): IAiContext.Mover→Locomotion,删除 IMover
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:14:53 +08:00
joywayerandClaude Opus 4.8 f143efe4a3 feat(scaffold): PlaceE001 挂载 EnemyLocomotion,重生成 prefab
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:10:29 +08:00
joywayerandClaude Opus 4.8 8c1a8647bb feat(enemy): EnemyBase 经接口发现并暴露 Locomotion(IEnemyLocomotion)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:06:41 +08:00
joywayerandClaude Opus 4.8 0f988c4cef feat(enemy): 新增 EnemyLocomotion 执行器(Idle/Patrol-Wander/Face/Approach)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:59:02 +08:00
joywayerandClaude Opus 4.8 0a7312e9bc feat(ai): 新增 IEnemyLocomotion 接口与 LocomotionMode/PatrolStrategy 枚举
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:55:41 +08:00
joywayer 612db1bc98 @
docs(ai): 敌人执行层统一 实施计划(P2-P6)

EnemyLocomotion 引入(P2)/AI 改绑意图删协程能力(P3)/删 AiPhase(P4)/
Stats 清理(P5)/巡逻策略 Pace+Waypoints(P6),逐 Task 含代码+EditMode/
PlayMode 验证+提交。P1 已完成。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 11:51:14 +08:00
joywayer e1f719aca0 @
docs(ai): 明确 PB2d 不支持飞行导航 + 飞行绕障缺口

核实 PathBerserker2d 为表面图寻路(NavSurface/NavSegment/NavLink),不支持
飞行;飞行怪走 FlyingDirectNavigator 直线直飞。EnemyLocomotion 收编两个
IPathAgent 后端。补记"飞行绕障"为已知缺口(现有直飞无绕障,需单独飞行寻路
方案),非本次范围。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 11:44:07 +08:00
joywayer ab8f61d2d9 @
docs(ai): 敌人执行层统一 + AI/动画/属性瘦身 设计文档

通篇审查敌人架构(movement/stats/state/AI+abilities)后的重构方案:
引入统一 EnemyLocomotion(Idle/Patrol/Face/Approach 模式 + Wander/Pace/
Waypoints 策略)取代 7 种巡逻/4 套移动/IMover/三个协程能力;删 AiPhase
影子层(动画改由 locomotion 驱动);清 EnemyStatsSO ~11 个死字段。范围
E001 样板+框架,E002-E06/Boss 定落地范式后续实现。P1(死代码清除)已完成。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 11:39:26 +08:00
joywayer e894a9dd27 @
chore(ai): 移除死代码 Behavior Designer(BD_* + Opsive 包 + 宏)

敌人 AI 已迁到自研 BrainGraph,旧的 Opsive Behavior Designer 集成全线死亡:
无任何 prefab 挂 BehaviorTree,新系统也不引用 BD 任务。用户手动删除三个
com.opsive.* 包后,BD_* 文件(#if GRAPH_DESIGNER 下引用 Opsive 类型)导致
BaseGames.Enemies.AI 程序集编译失败——本提交清除死代码并恢复编译。

- 删除 ~50 个 BD_*.cs + BDTaskAttributes.cs + BaseGames.Enemies.AI.asmdef。
- 移除 Standalone 的 GRAPH_DESIGNER 脚本宏。
- 移除 com.opsive.behaviordesigner/shared/graphdesigner 嵌入包(用户已删目录)。

已扫描确认无其他程序集引用该 AI 程序集、无真实 Opsive/BD_ 类型引用
(仅注释/Tooltip 提及)。编译 0 错误。属敌人架构重构 P1(死代码清除)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-10 11:37:16 +08:00
joywayerandClaude Opus 4.8 2e2df2b5dd refactor(ai): E001 贯彻"AI只决策"——每状态=一能力(Model A)
AI 决策层(E001CaoZhiAi + PerceptionStateMachine)彻底移除所有 actuation:
不再有 Mover.Stop/FacePlayer/UseChaseSpeed/WalkRandom、也不再 SetPhase。
每个 AI 状态只声明触发哪个能力 id,行为(移动/朝向/停/速度/动画/伤害/
阶段声明)全部下沉到对应能力里。

- 新增 IdleAbility/PatrolAbility/AlertAbility:待机静止、巡逻游走、
  警觉朝向玩家,各自 SetAiPhase 驱动阶段动画。
- ContactChaseAbility 追击进入时自设 RunSpeed + Chase 阶段(原在 AI)。
- PerceptionStateMachine 改为 AddAbilityState:进入/每帧 EnsureAbility
  (被打断/受击后自动重触发)、离开 InterruptAbilities;转换规则不变。
- IEnemyActor/EnemyBrainContext 移除已无用的 SetPhase,保留 HasAlertState。
- 新增 SO:ABL_E001_Idle(e001_idle)、ABL_E001_Patrol(e001_patrol)。
- 脚手架 PlaceE001 与 ENM_CaoZhi 预制体同步为四能力结构;TestRoomA
  实例替换为规范预制体实例。

PlayMode 验证:Chase→ContactChaseAbility、脱离感知→Move_Patrol→
PatrolAbility,阶段由能力设置,无报错。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:05:14 +08:00
joywayerandClaude Opus 4.8 490329e1dc refactor(nav): 敌人移动改为物理原生架构——根治 TBM 与 Dynamic 刚体冲突
根因: NavAgent.Position setter 直接写 transform.position, TBM 段移动每帧写它, 与敌人
Dynamic 刚体(重力/碰撞)冲突(旧代码靠"NavDriving清零velocity"缓解,但对Dynamic刚体直接
写transform.position本就不推荐,易卡墙/穿透/被物理覆盖)。

根本解: 寻路降为"顾问"(只给方向),移动执行归物理:
- EnemyNavAgent.Awake 禁用 TBM.SegmentMovement(不再写 transform.position);
- FixedUpdate 改物理原生桥: 朝 PathSubGoal 写 MoveDir+MoveSpeed 由 EnemyMovement 施加
  velocity+重力执行, 到达子目标时 CompleteSegmentTraversal 推进段(原由TBM负责);
- EnemyMovement 撤掉 NavDriving 的清零velocity/只朝向门, nav方向统一走velocity移动。
保留击退/击飞/落地等全部物理反馈。

附带修 PerceptionStateMachine: 追击维持改"追逐区 OR 视野区"任一(视野los可能被墙挡漏检,
仅靠视野会导致玩家在近身追逐区内却Chase↔Patrol抖动、nav被反复Stop不移动)。

PlayMode 验证: 敌人 nav 巡逻/追击均用velocity移动(FollowPath/onSeg=True)、追击稳定无抖动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 17:19:01 +08:00
joywayerandClaude Opus 4.8 643f63c737 fix(nav): 修复 nav 寻路核心用法——重寻路防抖 + allowCloseEnoughPath
根因1: PathBerserker2d NavAgent.UpdatePath 明确禁止每帧调用(路径计算跨帧,每帧重启则永不完成、
agent永不移动),而追击每帧 MoveTo→RequestMoveTo→UpdatePath。修: EnemyNavAgent.RequestMoveTo
加防抖(目标位移阈值0.5m + 最小间隔0.25s),让路径算完并跟随;StopNavigation 复位。
根因2: NavAgent.allowCloseEnoughPath 默认false→追击玩家(常不在导航段上)寻路直接失败。
修: 脚手架(4个PlaceExxx)+E001 prefab 设为 true(寻路到最近可达点)。
验证: 敌人 nav 巡逻/追击已能真正跟随路径移动(following=True)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:43:52 +08:00
joywayerandClaude Opus 4.8 790595d8de fix(scaffold): 碰撞体底部对齐 y=0(原点=脚底) 修正 NavAgent 有效位置 + 追击改回nav
根因: NavAgent 用 transform 原点(0,0)判定是否在导航段上,而脚手架 CreateBodyCollider
未设 offset(碰撞体居中,底部在-size.y/2),站到地面时原点悬在导航线上方→HasValidPosition=False。
修复: AlignColliderBottomToPivot 让所有身体/HurtBox/HitBox 碰撞体底部对齐 y=0(通用规则,
角色/敌人/Boss 同理);同步修 E001 prefab 碰撞体 offset。验证: 敌人落地后 navValid=True(自动)。
追击能力改回 nav 寻路(MoveTo),不再直接移动。

注: 导航路径"跟随"仍有既有集成问题(UpdatePath 后 agent 不跟随可达路径),需另行排查。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:22:54 +08:00
joywayerandClaude Opus 4.8 d0f69b0599 fix(ai): E001 追击真正移动+攻击——直接水平爬行 + prefab 补挂 EnemyAiBrain
追击移动从 A* 寻路(MoveTo)改为直接朝玩家水平爬行(MoveInDirectionWithSpeed):
地面爬行怪不需寻路,且不依赖场景 NavGraph 烘焙(PathBerserker2d 在测试场景失效)。
ENM_CaoZhi prefab 补挂 EnemyAiBrain(_definitionId=E001),使对象池/prefab 生成的敌人也有AI
(此前只有脚手架场景实例有brain)。PlayMode 验证:敌人爬向玩家并接触伤害击杀。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:48:50 +08:00
joywayerandClaude Opus 4.8 eaf7469d66 docs(ai): spec 补充"敌人感知↔状态模型"(待机/警觉/追击/巡逻)
两感知(追逐/视野)+HasAlert勾选+统一规则(升级经警觉/降级永不回警觉)+缺省自动降级
+职责边界(转换归AI层,感知只做纯查询)+PerceptionStateMachine可复用件。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:11:44 +08:00
joywayerandClaude Opus 4.8 6c27993e09 feat(ai): 敌人感知↔状态模型——待机/警觉/追击/巡逻 + 可复用感知状态机
感知门面 EnemyBase.InChaseZone(追逐/aggro)/InVisionZone(视野/los,缺省回退aggro),纯查询;
转换逻辑归 AI 层: 新增可复用 PerceptionStateMachine(统一规则,HasAlert/视野缺省自动降级);
规则: 未发现进视野→警觉, 进追逐→追击(委托能力), 追击脱离视野→巡逻(永不回警觉,再进视野再警觉).
EnemyStatsSO.HasAlertState 勾选项; ContactChaseAbility 改纯执行器(退出由AI中断); E001 用可复用件.
PlayMode 验证四态转换全通过.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:10:39 +08:00
joywayerandClaude Opus 4.8 150fadb72e fix(content): E001 能力/prefab 对齐设计——技能无CD、伪装期不伤人
ABL_E001_Chase/Alert cooldown 1.5→0(设计:E001无主动技能CD,再入感知立即追击);
ENM_CaoZhi prefab 与脚手架 PlaceE001 的 BodyContactDamage 初始禁用(伪装期不伤人,
追击期由 e001_chase 能力开启)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:52:03 +08:00
joywayerandClaude Opus 4.8 4ce798ea6e refactor(ai): E001 AI 重写为纯决策层,符合设计并委托能力模块
依据小怪设计文档重构 E001 状态机 Idle_Disguise→Skill_Start→Skill_Loop→Skill_End→Move_Patrol:
追击移动/接触伤害全部委托 e001_chase(ContactChaseAbility),AI 不再裸 MoveTo。
新增 ISensor.DetectsPlayer(视线OR近身,对齐设计)、IMover.UseChase/PatrolSpeed、
IEnemyActor.SetPhase(AI 声明阶段、EnemyBase 播动画)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:51:47 +08:00
joywayerandClaude Opus 4.8 decd5503fb fix(scaffold): 修复敌人感知 los 槽位——检测层 Nothing→Player、半径 0→8
原 SetupPerceptionSystemSlots 把 los 槽 detectLayer 置 0(Nothing)且 radius=0,
导致视线永远探不到玩家;改为 Player 层 + 半径 8,LOS 检测恢复正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:26:40 +08:00
joywayerandClaude Opus 4.8 63e241af92 fix(ai): AiRuntime 自转换设为no-op(根治死亡双发Died再入) + EnemyBase.OnSpawn复位brain.enabled + 测试
Switch 对 target==当前态直接 no-op(不重跑Exit/Enter),循环改为 if(Switch(t))return true;
OnSpawn 复用时恢复被 QuotaManager 裁剪禁用的 brain。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 16:26:28 +08:00
joywayer d38c6e35bb feat(ai): 脚手架 PlaceE001 一键挂载 EnemyAiBrain 并绑定 id=E001 2026-07-03 16:08:32 +08:00
joywayer 23a3a1c274 refactor(ai): EnemyBase/QuotaManager 移除 Opsive BT,改接 EnemyAiBrain(Brain访问器/Died信号/OnSpawn重置/LOD裁剪) 2026-07-03 15:10:55 +08:00
joywayer a9acace0a0 feat(ai): E001(草蛭) AiScript 定义——巡逻/追击/搜查(接触伤害杂兵) 2026-07-03 15:03:27 +08:00
joywayer 4994360927 feat(ai): EnemyAiBrain 组件——承载 AiRuntime、逐帧推进、池复用重置、信号入口 2026-07-03 15:00:59 +08:00
joywayer 20c2568222 feat(ai): EnemyBrainContext——EnemyBase子系统适配为BrainGraph能力接口 2026-07-03 14:58:52 +08:00
joywayer 683a2d9ffb feat(ai): AiDefinitionRegistry——反射按id提供共享AiGraph(关域重载重置) 2026-07-03 14:55:42 +08:00
joywayerandClaude Opus 4.8 80864eec6c docs(ai): BrainGraph 接通敌人阶段(E001+脚手架一键)实现计划
AiDefinitionRegistry(反射按id取共享图)+EnemyBrainContext(单类适配)+EnemyAiBrain组件
+E001 AiScript+EnemyBase去BT接signal+SceneObjectPlacerTool一键挂载+PlayMode集成验证.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 14:52:22 +08:00
joywayer 11e4c9f1ea refactor(ai): 终审收尾——删死字段/事件转换刷新IsSuspended + 局部事件与双实例独立性测试 2026-07-03 14:33:23 +08:00
joywayer 747d39072e test(ai): BrainGraph 第1阶段收尾——flyweight 共享测试 + 全量绿 2026-07-03 14:26:01 +08:00
joywayer d01df7b5f0 feat(ai): AiRuntime 事件转换(push,不受门限制)+trace触发原因+池复用测试 2026-07-03 14:23:28 +08:00
joywayer 5ad26b16e8 perf(ai): AiRuntime 热路径去装箱/弱表一次注册 + 补 OnExit/Entry OnEnter 测试 2026-07-03 14:20:19 +08:00
joywayer cdd1053bec feat(ai): AiRuntime 条件转换+IsControllable让位门+状态计时+trace+Reset 2026-07-03 14:13:36 +08:00
joywayer 0253fd15ca refactor(ai): BrainGraph Task4 审查修复——删死字段/Global空目标校验/AiState只读转换 2026-07-03 14:09:43 +08:00
joywayer 6f9fa8c4b8 feat(ai): BrainGraph AiGraph + BrainBuilder(fluent, 显式标签, 目标校验) 2026-07-03 14:04:51 +08:00
joywayer 3d6cef1f41 feat(ai): BrainGraph Transition/AiState/TransitionRecord 数据结构 2026-07-03 14:00:27 +08:00
joywayer 7281c5cf6e feat(ai): BrainGraph 能力接口(ISensor/IMover/ICombatant/IActorVitals)+IAiContext 2026-07-03 13:58:52 +08:00
joywayer db5bd019f8 feat(ai): BrainGraph 核心程序集 + AiSignal + Blackboard(可自省) 2026-07-03 13:56:11 +08:00
joywayerandClaude Opus 4.8 2339b5cd87 docs(ai): BrainGraph 标签方案改为 C#9 兼容(显式可选字符串)
Unity 2022.3=C#9,不用 C#10 的 CallerArgumentExpression;When(cond,label)显式可选,
默认回退"cond";spec 示例与 Phase1 计划(Task4/5 测试)同步更新.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:51:34 +08:00
joywayerandClaude Opus 4.8 5155368201 docs(ai): BrainGraph 第1阶段(核心运行时)实现计划
TDD/EditMode 覆盖: 程序集+Blackboard+能力接口+Transition/AiState+AiGraph/BrainBuilder
+AiRuntime(条件/事件转换,IsControllable门,状态计时,trace,池复用重置); 后续4阶段路线图.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:42:01 +08:00
joywayerandClaude Opus 4.8 93431102ab docs(ai): 补充 BrainGraph 多实例复用与对象池设计
共享 AiGraph(每类型1份 flyweight)+每实例 AiRuntime; lambda 禁实例捕获;
对象池回收再生须重置 AiRuntime; 调度错峰; 新增对应验收标准.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:29:34 +08:00
joywayerandClaude Opus 4.8 0ed7353d24 docs(ai): 敌人 AI 框架(BrainGraph)设计——HFSM 替换 Behavior Designer
C# 声明式为唯一事实来源, 可视化从模型自动生成; 只新建决策层+干净能力接口,
现有感知/移动/能力子系统保留; 受击物理FSM用 IsControllable 门显式让位;
交付面: 运行时调试器/Mermaid导出/MCP自省API; v1 试点 E001+ChaoFengBoss.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:01:49 +08:00
yangyu 755ec0b82a Merge branch 'master' of https://git.joywaygames.cn/basegames/zeling_v2
# Conflicts:
#	Assets/_Game/Data/Player/PLY_PlayerMovementConfig.asset
#	Assets/_Game/Scenes/Persistent.unity
#	ZeLing.sln
2026-06-15 17:09:53 +08:00
yangyu 99d44348c6 no message 2026-06-15 17:08:12 +08:00
yangyu 378afb1085 Merge branch 'master' of https://git.joywaygames.cn/basegames/zeling_v2 2026-06-02 16:36:19 +08:00
yangyu 9120d8ca14 no message 2026-06-02 16:36:17 +08:00
yangyu ea20b3a5ce Merge branch 'master' of https://git.joywaygames.cn/basegames/zeling_v2 2026-05-27 16:48:44 +08:00
yangyu 79c2549a04 no message 2026-05-27 16:48:42 +08:00
yangyu bf3a049a62 Merge branch 'master' of https://git.joywaygames.cn/basegames/zeling_v2 2026-05-27 16:33:23 +08:00
yangyu eec8d4d453 no message 2026-05-27 16:33:15 +08:00
1638 changed files with 191372 additions and 142159 deletions
+11 -6
View File
@@ -4,13 +4,18 @@
"Read(*)", "Read(*)",
"Write(*)", "Write(*)",
"Bash(*)", "Bash(*)",
"mcp__unity-mcp__*", "mcp__unity__unity_*",
"PowerShell(Select-String -Path \"E:\\\\Works\\\\Unity\\\\Projects\\\\zeling_v2\\\\Assets\\\\_Game\\\\Prefabs\\\\Player\\\\Player.prefab\" -Pattern \"7ca41f67644b6b843ba7ef65e78b13e5\" -Context 2,8 | ForEach-Object { $_.Context.PreContext + $_.Line + $_.Context.PostContext })", "PowerShell(Get-Command *)",
"PowerShell(foreach \\($f in @\\(\"Combat\\\\EVT_CheckpointRespawn\",\"Core\\\\EVT_FadeOutRequest\",\"Core\\\\EVT_FadeInRequest\"\\)\\) { $g = \\(Get-Content \"E:\\\\Works\\\\Unity\\\\Projects\\\\zeling_v2\\\\Assets\\\\_Game\\\\Data\\\\Events\\\\$f.asset.meta\" | Select-String 'guid: \\(\\\\w+\\)'\\).Matches[0].Groups[1].Value; \"$f -> $g\" })", "PowerShell(*)",
"PowerShell(Select-String *)", "WebSearch"
"PowerShell(rtk git *)",
"PowerShell(Select-String -Path \"E:\\\\Works\\\\Unity\\\\Projects\\\\zeling_v2\\\\Assets\\\\_Game\\\\Prefabs\\\\Weapons\\\\WPN_WPN_DiHun_HitBox.prefab\" -Pattern \"adb4af2f574f356449634bc130f94592\" -Context 0,7 | ForEach-Object { $_.Line; $_.Context.PostContext })"
], ],
"dangerouslySkipPermissions": true "dangerouslySkipPermissions": true
},
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [
"unity"
],
"skillOverrides": {
"deveco-cli": "off"
} }
} }
-27
View File
@@ -1,27 +0,0 @@
<!-- rtk-instructions v2 -->
# RTK — Token-Optimized CLI
**rtk** is a CLI proxy that filters and compresses command outputs, saving 60-90% tokens.
## Rule
Always prefix shell commands with `rtk`:
```bash
# Instead of: Use:
git status rtk git status
git log -10 rtk git log -10
cargo test rtk cargo test
docker ps rtk docker ps
kubectl get pods rtk kubectl pods
```
## Meta commands (use directly)
```bash
rtk gain # Token savings dashboard
rtk gain --history # Per-command savings history
rtk discover # Find missed rtk opportunities
rtk proxy <cmd> # Run raw (no filtering) but track usage
```
<!-- /rtk-instructions -->
-12
View File
@@ -1,12 +0,0 @@
{
"hooks": {
"PreToolUse": [
{
"type": "command",
"command": "rtk hook copilot",
"cwd": ".",
"timeout": 5
}
]
}
}
+3
View File
@@ -33,6 +33,9 @@ sysinfo.txt
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Local tooling (cloned MCP server, not part of the Unity project)
Tools/unity-mcp-server/
# VS Code local settings # VS Code local settings
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
+15
View File
@@ -0,0 +1,15 @@
{
"mcpServers": {
"unity": {
"type": "stdio",
"command": "node",
"args": [
"Tools/unity-mcp-server/src/index.js"
],
"env": {
"UNITY_HUB_PATH": "C:\\Program Files\\Unity Hub\\Unity Hub.exe",
"UNITY_BRIDGE_PORT": "7890"
}
}
}
}
-13
View File
@@ -1,13 +0,0 @@
# Project-local RTK filters — commit this file with your repo.
# Filters here override user-global and built-in filters.
# Docs: https://github.com/rtk-ai/rtk#custom-filters
schema_version = 1
# Example: suppress build noise from a custom tool
# [filters.my-tool]
# description = "Compact my-tool output"
# match_command = "^my-tool\\s+build"
# strip_ansi = true
# strip_lines_matching = ["^\\s*$", "^Downloading", "^Installing"]
# max_lines = 30
# on_empty = "my-tool: ok"
@@ -17,44 +17,6 @@ MonoBehaviour:
m_SerializedData: [] m_SerializedData: []
m_GUID: 4784fc796879ff14580834028bdb14ef m_GUID: 4784fc796879ff14580834028bdb14ef
m_SerializeEntries: m_SerializeEntries:
- m_GUID: 027bcccb70638f143b77f51f3da389dc
m_Address: ENM_Huan
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3d79f19c55fbaaf40b96071ac7f602c0
m_Address: ENM_YouZhi
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
- Poolable
- Preload
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 69accaa6577ec1a4db552c3c31aa28d8
m_Address: ENM_CaoZhi
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 6e3d42a02e363ad4ab5079699a37d1d3
m_Address: ENM_ZhiMu
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 98db6e6457d39db43a998d00a26aa503
m_Address: ENM_HuangZhi
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a8a4005faff5aa34a83e5dd9b99b74c8
m_Address: ENM_FeiZhi
m_ReadOnly: 0
m_SerializedLabels:
- Enemy
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e55e94346ed15ce40bc0ae5aa7771ea6 - m_GUID: e55e94346ed15ce40bc0ae5aa7771ea6
m_Address: ENM_ChaoFeng m_Address: ENM_ChaoFeng
m_ReadOnly: 0 m_ReadOnly: 0
@@ -1,25 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 1458000753, guid: c42964479d4f62540867ad0acdb1ef66, type: 3}
m_Name: BehaviorDesignerSettings
m_EditorClassIdentifier:
m_ShowHierarchyIcon: 1
m_CheckForErrors: 1
m_AutoSavePrefab: 0
m_MouseWheelScrollsGraph: 0
m_GridLineSpacing: 10
m_GridLineTransparency: 1
m_ShowNodeIcon: 1
m_ShowNodeDescription: 1
m_SnapToGrid: 1
m_ShowNodeDebugInfo: 0
m_DefaultGizmosColor: {r: 0, g: 1, b: 0, a: 0.6}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 34eeed431fd76184abebc2b49be24621
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
-16
View File
@@ -1,16 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f9a6fcaab71877649a4598d315010679, type: 3}
m_Name: ImportStatus
m_EditorClassIdentifier:
m_CharacterProjectSettingsShown: 0
m_BehaviorWindowShown: 1
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: e0383e08b93337e41bd2254c4304147f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 21035a61c7bbb0c43adb502350355869
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,15 +0,0 @@
Pathberserker2d
Thanks for purchasing. To get started, I suggest trying out the various demo scenes.
The documentation is shipped as a zip file in the same folder.
Alternatively you can read the most up to date version of the documentation online here:
https://oribow.github.io/PathBerserker2dDemo/Documentation/
The documentation also includes guides on a variety of topics and explains the core concepts of this asset.
As an extra treat, this Asset also comes with its complete source code, also as zip in this folder.
Setting your project up to use the source code instead of the DLL's requires some knowledge about C# projects.
I recommend using the source code primarily as a reference guide.
Happy path-finding!
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 1605f3fdba2679842a2f72c81ec408ac
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 446c632a2de6b8c4c87487c4533abd7f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

@@ -1,115 +0,0 @@
fileFormatVersion: 2
guid: 1076c1b4e7774d14cb5f02e81e131660
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

@@ -1,103 +0,0 @@
fileFormatVersion: 2
guid: 123ffa319522f7b40b36a77973995e35
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

@@ -1,115 +0,0 @@
fileFormatVersion: 2
guid: 410455380032bfb45b40e7c043a62b4f
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

@@ -1,115 +0,0 @@
fileFormatVersion: 2
guid: f70aa7e3ba7990549bed699b10912202
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

@@ -1,103 +0,0 @@
fileFormatVersion: 2
guid: 550c17ae955373049aee7dff67a18fa9
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f88aed787b8decc4086d9222249beace
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,54 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 393b1ec75130eb047bc70f7635277936, type: 3}
m_Name: PathBerserker2dSettings
m_EditorClassIdentifier:
pointMappingDistance: 0.2
pathfinderThreadCount: 4
initiateUpdateInterval: 0.1
closestToSegmentMaxDistance: 20
navLinkTypeNames:
- corner
- jump
- fall
- teleport
- climb
- elevator
navLinkTypeColors:
- {r: 0, g: 0, b: 0, a: 1}
- {r: 1, g: 1, b: 1, a: 1}
- {r: 0, g: 0.51575685, b: 1, a: 1}
- {r: 0, g: 1, b: 0, a: 1}
- {r: 0, g: 0.03320258, b: 0.49803922, a: 1}
- {r: 1, g: 0, b: 1, a: 1}
navTags:
- default
- water
- lava
- grass
- concrete
- dirt
navTagColors:
- {r: 0, g: 0, b: 0, a: 0}
- {r: 0, g: 0.5736413, b: 1, a: 1}
- {r: 1, g: 0.19613811, b: 0, a: 1}
- {r: 0.03921567, g: 0.99607843, b: 0.124506705, a: 1}
- {r: 0.4528302, g: 0.4528302, b: 0.4528302, a: 1}
- {r: 0.4339623, g: 0.2691755, b: 0, a: 1}
drawUnselectedLinks: 1
drawUnselectedSurfaces: 1
drawUnselectedSubstractors: 1
drawUnselectedAreaMarkers: 1
drawGraphWhilePlaying: 1
navSurfaceLineWidth: 0.04
navAreaMarkerLineWidth: 0.2
usePolygonCollider2dPathsForBaking: 0
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 80a179a567cbfc148b23c441354e6057
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: cd800ca490ca3504a91a04cbbe34fe93
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 91a2221b1f964c946af33b7bb66b948d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,22 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
internal class ReadOnlyAttributeDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
bool oldValue = GUI.enabled;
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = oldValue;
}
}
}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1df174b62e1d3684f9fc16ede26d1866
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,51 +0,0 @@
using UnityEngine;
using UnityEditor;
namespace PathBerserker2d
{
internal class IconHandle2D
{
private static readonly int controlHashIcon = "IconHandle2D_icon".GetHashCode();
public static void DrawHandle(Vector3 position, Texture icon, float size, Object objectToSelect)
{
int iconId = EditorGUIUtility.GetControlID(controlHashIcon, FocusType.Passive);
var e = Event.current;
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0 && HandleUtility.nearestControl == iconId)
{
Selection.activeObject = objectToSelect;
e.Use();
}
break;
case EventType.Layout:
float distance = HandleUtility.DistanceToRectangle(position, Camera.current.transform.rotation, size);
HandleUtility.AddControl(iconId, distance);
break;
case EventType.Repaint:
Vector3 up = Camera.current.transform.up * size;
float aspectRatio = (float)icon.width / (float)icon.height;
Vector3 right = Camera.current.transform.right * size * aspectRatio;
SharedMaterials.UnlitTexture.SetTexture("_MainTex", icon);
SharedMaterials.UnlitTexture.SetPass(0);
GL.Begin(GL.QUADS);
{
GL.TexCoord2(1, 1);
GL.Vertex(position + right + up);
GL.TexCoord2(1, 0);
GL.Vertex(position + right - up);
GL.TexCoord2(0, 0);
GL.Vertex(position - right - up);
GL.TexCoord2(0, 1);
GL.Vertex(position - right + up);
}
GL.End();
break;
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -1,73 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class MyGUI
{
public static string nameSeed = "aWJhcmFraQ==";
public static string nameSeed2 = "aWJhcl9ha2k=";
static GUIStyle horizontalLine;
static MyGUI()
{
horizontalLine = new GUIStyle();
horizontalLine.normal.background = EditorGUIUtility.whiteTexture;
horizontalLine.margin = new RectOffset(0, 0, 4, 4);
horizontalLine.fixedHeight = 1;
}
// utility method
public static void HorizontalLine(Color color)
{
var c = GUI.color;
GUI.color = color;
GUILayout.Box(GUIContent.none, horizontalLine);
GUI.color = c;
}
public static void Header(string text)
{
EditorGUILayout.LabelField(text, EditorStyles.boldLabel);
}
public static void DrawNavTagLayout(SerializedProperty spNavTag)
{
EditorGUILayout.BeginHorizontal();
spNavTag.intValue = EditorGUILayout.Popup("NavTag", spNavTag.intValue, PathBerserker2dSettings.NavTags);
if (GUILayout.Button("+", EditorStyles.miniButtonRight, GUILayout.Width(17)))
{
SettingsService.OpenProjectSettings(PathBerserker2dSettingsProvider.WindowPath);
}
EditorGUILayout.EndHorizontal();
}
public static void DrawNavTagColorPickerLayout(SerializedProperty spNavTag)
{
int tag = spNavTag.intValue;
if (tag == 0)
GUI.enabled = false;
PathBerserker2dSettings.SetNavTagColor(tag, EditorGUILayout.ColorField("NavTag Color", PathBerserker2dSettings.GetNavTagColor(tag)));
GUI.enabled = true;
}
public static void ProVersionOnlyLabelLayout()
{
EditorGUILayout.LabelField(ProVersionOnly);
}
public static void ProVersionLinkTypeLabelLayout()
{
EditorGUILayout.LabelField("Custom link types are limited to the pro-version.");
}
public static GUIContent AddProVersionOnlyToolTipp(string label)
{
return new GUIContent(label, ProVersionOnly);
}
public const string ProVersionOnly = "This is a pro-version only feature";
}
}
@@ -1,137 +0,0 @@
using UnityEngine;
using UnityEditor;
namespace PathBerserker2d
{
internal class PositionHandle2D
{
private Vector3 startPos;
private Vector2 currentMousePos;
private Vector2 startMousePos;
public Color primary;
public Color hover;
public Color selected;
private int hash;
public PositionHandle2D(Color primary, Color hover, Color selected)
{
this.primary = primary;
this.hover = hover;
this.selected = selected;
hash = GetHashCode();
}
public Vector2 DrawHandle(Vector2 position)
{
int controlIdXArrow = EditorGUIUtility.GetControlID(hash, FocusType.Passive);
int controlIdYArrow = EditorGUIUtility.GetControlID(hash, FocusType.Passive);
int controlIdRect = EditorGUIUtility.GetControlID(hash, FocusType.Passive);
bool selectedXArrow = GUIUtility.hotControl == controlIdXArrow;
bool hoveredXArrow = HandleUtility.nearestControl == controlIdXArrow;
bool selectedYArrow = GUIUtility.hotControl == controlIdYArrow;
bool hoveredYArrow = HandleUtility.nearestControl == controlIdYArrow;
bool selectedRect = GUIUtility.hotControl == controlIdRect;
bool hoveredRect = HandleUtility.nearestControl == controlIdRect;
var e = Event.current;
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0 && GUIUtility.hotControl == 0 && !e.alt)
{
if (HandleUtility.nearestControl == controlIdXArrow)
{
GUIUtility.hotControl = controlIdXArrow;
}
else if (HandleUtility.nearestControl == controlIdYArrow)
{
GUIUtility.hotControl = controlIdYArrow;
}
else if (HandleUtility.nearestControl == controlIdRect)
{
GUIUtility.hotControl = controlIdRect;
}
if (HandleUtility.nearestControl == controlIdXArrow ||
HandleUtility.nearestControl == controlIdYArrow ||
HandleUtility.nearestControl == controlIdRect)
{
startPos = position;
currentMousePos = e.mousePosition;
startMousePos = e.mousePosition;
e.Use();
}
}
break;
case EventType.MouseUp:
if (e.button == 0 || e.button == 2)
{
if (GUIUtility.hotControl == controlIdXArrow || GUIUtility.hotControl == controlIdYArrow || GUIUtility.hotControl == controlIdRect)
{
GUIUtility.hotControl = 0;
e.Use();
selectedXArrow = false;
selectedYArrow = false;
selectedRect = false;
}
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlIdXArrow || GUIUtility.hotControl == controlIdYArrow || GUIUtility.hotControl == controlIdRect)
{
currentMousePos += new Vector2(e.delta.x, -e.delta.y) * EditorGUIUtility.pixelsPerPoint;
Vector3 screenPos = Camera.current.WorldToScreenPoint(Handles.matrix.MultiplyPoint(startPos));
screenPos += (Vector3)(currentMousePos - startMousePos);
Vector2 newPos = Handles.inverseMatrix.MultiplyPoint(Camera.current.ScreenToWorldPoint(screenPos));
if (selectedXArrow)
{
newPos.y = startPos.y;
}
else if (selectedYArrow)
{
newPos.x = startPos.x;
}
if (newPos != position)
{
position = newPos;
GUI.changed = true;
}
e.Use();
}
break;
}
Handles.color = selectedRect || selectedXArrow ? selected : (hoveredXArrow ? hover : primary);
Handles.ArrowHandleCap(controlIdXArrow, position, Quaternion.Euler(0, 90, 0), HandleUtility.GetHandleSize(position), e.type);
Handles.color = selectedRect || selectedYArrow ? selected : (hoveredYArrow ? hover : primary);
Handles.ArrowHandleCap(controlIdYArrow, position, Quaternion.Euler(-90, 0, 0), HandleUtility.GetHandleSize(position), e.type);
Handles.color = selectedRect ? selected : (hoveredRect ? hover : primary);
float rectSize = HandleUtility.GetHandleSize(position) * 0.14f;
Vector2 rectPos = position + Vector2.one * rectSize;
if (e.type == EventType.Repaint)
Handles.DrawSolidRectangleWithOutline(new Rect(position, new Vector2(rectSize, rectSize) * 2f), new Color(1, 1, 1, 0.2f), new Color(1, 1, 1, 1));
rectPos = Handles.Slider2D(rectPos, Vector3.forward, Vector3.right, Vector3.up, rectSize, Handles.RectangleHandleCap, 0);
position = rectPos - Vector2.one * rectSize;
return position;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 38d3d3506e06653448c052e8936eb8dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,34 +0,0 @@
using System;
using UnityEditor;
namespace PathBerserker2d
{
[InitializeOnLoad]
internal class ExecutionOrderManager : Editor
{
static ExecutionOrderManager()
{
foreach (MonoScript monoScript in MonoImporter.GetAllRuntimeMonoScripts())
{
Type type = monoScript.GetClass();
if (type == null)
{
continue;
}
object[] attributes = type.GetCustomAttributes(typeof(ScriptExecutionOrderAttribute), true);
if (attributes.Length == 0)
{
continue;
}
ScriptExecutionOrderAttribute attribute = (ScriptExecutionOrderAttribute)attributes[0];
if (MonoImporter.GetExecutionOrder(monoScript) != attribute.GetOrder())
{
MonoImporter.SetExecutionOrder(monoScript, attribute.GetOrder());
}
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: d337207a086912840ae6fa25daa40c29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8a6fbf381f5910a46b7602b805ab3da5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,239 +0,0 @@
using System;
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class GizmosDrawingExtensions
{
public static void DrawArrowHead(Vector2 basePos, Vector2 dir, float size)
{
dir.Normalize();
Vector2 normal = new Vector2(-dir.y, dir.x) * size;
Gizmos.DrawLine(basePos - normal, basePos + normal);
Gizmos.DrawLine(basePos - normal, basePos + dir * size);
Gizmos.DrawLine(basePos + normal, basePos + dir * size);
}
public static void DrawArrowHeadFromSpike(Vector2 pointyPos, Vector2 dir, float size)
{
dir.Normalize();
pointyPos -= dir * size;
Vector2 normal = new Vector2(-dir.y, dir.x) * size;
Gizmos.DrawLine(pointyPos - normal, pointyPos + normal);
Gizmos.DrawLine(pointyPos - normal, pointyPos + dir * size);
Gizmos.DrawLine(pointyPos + normal, pointyPos + dir * size);
}
public static void DrawArrow(Vector2 start, Vector2 end, float size)
{
Vector2 dir = end - start;
float length = dir.magnitude;
dir /= length;
Vector3 end3 = new Vector3(end.x, end.y);
Gizmos.DrawLine(start, end3);
Vector2 normal = new Vector2(-dir.y, dir.x) * size;
Vector2 baseA = start + dir * (length - size);
Gizmos.DrawLine(baseA - normal, end3);
Gizmos.DrawLine(baseA + normal, end3);
}
public static void DrawCircle(Vector2 center, float radius = 0.05f)
{
int segmentCount = 10;
Vector2 prevPoint = center + Vector2.up * radius;
for (float t = 1; t <= segmentCount; t++)
{
float x = (t / segmentCount) * Mathf.PI * 2.0f;
float cx = Mathf.Sin(x);
float cy = Mathf.Cos(x);
Vector2 point = center + new Vector2(cx, cy) * radius;
Gizmos.DrawLine(prevPoint, point);
prevPoint = point;
}
Gizmos.DrawLine(prevPoint, center + Vector2.up * radius);
}
public static void DrawBezierConnection(Vector2 start, Vector2 end, bool biDirectional)
{
Vector2 cp;
var tangent = (end - start);
var length = tangent.magnitude;
var normal = new Vector2(-tangent.y, tangent.x) / length;
cp = start + tangent * 0.5f + normal * (length / 5f);
DrawBezierConnection(start, end, cp, biDirectional);
}
public static void DrawBezierConnection(Vector2 start, Vector2 end, Vector2 cp, bool biDirectional)
{
float arcLength = (Vector2.Distance(end, start) * 2 + Vector2.Distance(end, cp) + Vector2.Distance(start, cp)) / 3f;
int numberOfSegments = Mathf.CeilToInt(arcLength) + 4;
Vector2 prev = start;
float t;
for (t = 1; t <= numberOfSegments; t++)
{
Vector2 v = QuadraticBezierCurve(t / numberOfSegments, start, cp, end);
Gizmos.DrawLine(prev, v);
prev = v;
}
//draw arrows
t = (numberOfSegments - 1) / (float)numberOfSegments;
Vector2 dir = end - QuadraticBezierCurve(t, start, cp, end);
DrawArrowHeadFromSpike(end, dir, 0.2f);
if (biDirectional)
{
dir = start - QuadraticBezierCurve(1f / numberOfSegments, start, cp, end);
DrawArrowHeadFromSpike(start, dir, 0.2f);
}
}
public static void DrawBezierConnectionWithOffset(Vector2 start, Vector2 end, Vector2 cp, Vector2 offset)
{
float arcLength = (Vector2.Distance(end, start) * 2 + Vector2.Distance(end, cp) + Vector2.Distance(start, cp)) / 3f;
int numberOfSegments = Mathf.CeilToInt(arcLength);
Vector2 prev = start + offset;
Gizmos.DrawLine(start, prev);
float t;
for (t = 1; t <= numberOfSegments; t++)
{
Vector2 v = QuadraticBezierCurve(t / numberOfSegments, start, cp, end) + offset;
Gizmos.DrawLine(v, v - offset);
Gizmos.DrawLine(prev, v);
prev = v;
}
}
public static void DrawProjectileArc(Vector2 start, Vector2 end, float hSpeed, bool isBidiretional)
{
float hDelta = end.x - start.x;
float t = hDelta / hSpeed;
int numberOfSegments = Mathf.CeilToInt(t) + 4;
float p0 = start.y - end.y;
float v0 = 9.81f * t * 0.5f - p0 / t;
Func<float, float> func = x => 0.5f * -9.81f * x * x + v0 * x + p0;
Vector2 prev = start;
float z;
float timePerSegment = t / numberOfSegments;
for (z = 1; z <= numberOfSegments; z++)
{
Vector2 v = new Vector2(start.x + z * timePerSegment * hSpeed, end.y + func(z * timePerSegment));
Gizmos.DrawLine(prev, v);
prev = v;
}
Vector2 dir = end - new Vector2(start.x + (numberOfSegments - 1) * timePerSegment * hSpeed, end.y + func((numberOfSegments - 1) * timePerSegment));
DrawArrowHeadFromSpike(end, dir, 0.2f);
if (isBidiretional)
{
dir = start - new Vector2(start.x + timePerSegment * hSpeed, end.y + func(timePerSegment));
DrawArrowHeadFromSpike(start, dir, 0.2f);
}
}
public static void DrawJumpArc(Vector2 start, Vector2 end, float jumpSpeed, bool isBidiretional)
{
Vector2 dir = end - start;
float distance = dir.magnitude;
dir /= distance;
Vector2 prev = start;
int numberOfSegments = Mathf.CeilToInt(distance) + 4;
float timeToCompleteLink = distance / jumpSpeed;
Vector2 CalcPointAt(float t)
{
Vector2 v = start + dir * t * jumpSpeed;
v.y += distance * 0.3f * Mathf.Sin(Mathf.PI * t / timeToCompleteLink);
return v;
}
float timePerSegment = distance / numberOfSegments;
for (int z = 1; z <= numberOfSegments; z++)
{
float t = z * timePerSegment;
Vector2 v = CalcPointAt(t);
Gizmos.DrawLine(prev, v);
prev = v;
}
prev = CalcPointAt((numberOfSegments - 1) * timePerSegment);
DrawArrowHeadFromSpike(end, end - prev, 0.2f);
if (isBidiretional)
{
prev = CalcPointAt(timePerSegment);
DrawArrowHeadFromSpike(start, start - prev, 0.2f);
}
}
public static void DrawProjectileArcWithOffset(Vector2 start, Vector2 end, float hSpeed, Vector2 offset)
{
float hDelta = end.x - start.x;
float t = hDelta / hSpeed;
int numberOfSegments = Mathf.CeilToInt(t) + 4;
float p0 = start.y - end.y;
float v0 = 9.81f * t * 0.5f - p0 / t;
Func<float, float> func = x => 0.5f * -9.81f * x * x + v0 * x + p0;
Vector2 prev = start + offset;
Gizmos.DrawLine(start, prev);
float z;
float timePerSegment = t / numberOfSegments;
for (z = 1; z <= numberOfSegments; z++)
{
Vector2 v = new Vector2(start.x + z * timePerSegment * hSpeed, end.y + func(z * timePerSegment)) + offset;
Gizmos.DrawLine(prev, v);
Gizmos.DrawLine(v, v - offset);
prev = v;
}
}
private static Vector2 QuadraticBezierCurve(float t, Vector2 a, Vector2 b, Vector2 c)
{
return (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c;
}
public static Color LinearBlendBetweenColors(float value, params Color[] colors)
{
value = Mathf.Clamp01(value);
int index = (int)(value * (colors.Length - 1));
float t = (value * (colors.Length - 1)) - index;
Color b = index >= colors.Length - 1 ? colors[index] : colors[index + 1];
return Color.Lerp(colors[index], b, t);
}
public static void DrawRect(Vector2 position, Vector2 size)
{
Gizmos.DrawLine(position + size, new Vector3(position.x, position.y + size.y));
Gizmos.DrawLine(position, new Vector3(position.x, position.y + size.y));
Gizmos.DrawLine(position + size, new Vector3(position.x + size.x, position.y));
Gizmos.DrawLine(position, new Vector3(position.x + size.x, position.y));
}
public static void DrawRect(Rect rect)
{
Gizmos.DrawLine(rect.position + rect.size, new Vector3(rect.position.x, rect.position.y + rect.size.y));
Gizmos.DrawLine(rect.position, new Vector3(rect.position.x, rect.position.y + rect.size.y));
Gizmos.DrawLine(rect.position + rect.size, new Vector3(rect.position.x + rect.size.x, rect.position.y));
Gizmos.DrawLine(rect.position, new Vector3(rect.position.x + rect.size.x, rect.position.y));
}
public static void SetColor(Color color)
{
Handles.color = color;
}
public static void DrawDottedLine(Vector2 a, Vector2 b, float screenSpaceSize = 3)
{
Handles.DrawDottedLine(a, b, screenSpaceSize);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 492511a86fc803947bc65e80cb38dcd1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,94 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavAgentDrawer
{
[DrawGizmo(GizmoType.Selected | GizmoType.Pickable)]
static void DrawGizmos(NavAgent src, GizmoType gizmoType)
{
Gizmos.color = Color.green;
if (!Application.IsPlaying(src))
{
Vector2 adjustedPosition = src.transform.position;
Gizmos.DrawRay(adjustedPosition, src.transform.up * src.Height);
Gizmos.DrawLine(adjustedPosition + -(Vector2)src.transform.right * 0.2f, adjustedPosition + (Vector2)src.transform.right * 0.2f);
}
else if(!src.currentMappedPosition.IsInvalid())
{
Gizmos.color = Color.magenta;
GizmosDrawingExtensions.DrawCircle( src.currentMappedPosition.Position);
}
if (src.IsFollowingAPath)
{
int hash = Mathf.Abs(src.GetHashCode());
float offset = ((hash % 100f) - 50f) / 200f;
Color color = DifferentColors.GetColor(hash);
if (src.IsOnLink)
{
DrawPath(src.Path, src.Path.Current.LinkStart, src.Height / 2f + offset, color);
}
else
{
DrawPath(src.Path, src.transform.position, src.Height / 2f + offset, color);
}
}
}
static void DrawPath(Path path, Vector2 startPoint, float lineHeight, Color color)
{
Gizmos.color = color;
var seg = path.Current;
Vector2 lineA = startPoint + seg.Normal * lineHeight;
while (seg != null)
{
Vector2 lineB = seg.LinkStart + seg.Normal * lineHeight;
if (seg.Next == null)
{
Gizmos.DrawLine(lineA, lineB);
lineA = lineB;
GizmosDrawingExtensions.DrawCircle(lineB, 0.2f);
GizmosDrawingExtensions.DrawCircle(lineB, 0.3f);
}
else
{
if (seg.link.LinkType == -1)
{
Vector2 oLineA = seg.LinkEnd + seg.Next.Normal * lineHeight;
Vector2 oLineB = seg.Next.LinkStart + seg.Next.Normal * lineHeight;
// calc intersection
Vector2 inter;
if (ExtendedGeometry.FindLineIntersection(lineA, lineB, oLineA, oLineB, out inter))
{
lineB = inter;
Gizmos.DrawLine(lineA, lineB);
lineA = lineB;
}
else
{
Gizmos.DrawLine(lineB, oLineA);
Gizmos.DrawLine(lineA, lineB);
lineA = oLineA;
}
}
else
{
Gizmos.DrawLine(lineA, lineB);
lineA = lineB;
lineB = seg.LinkEnd + seg.Next.Normal * lineHeight;
Gizmos.DrawLine(lineA, lineB);
lineA = lineB;
}
}
seg = seg.Next;
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 723a305b61a51e64ebec0506f93c1112
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,29 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavAreaMarkerDrawer
{
static Vector3[] worldCorners = new Vector3[4];
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
private static void DrawGizmos(NavAreaMarker src, GizmoType gizmoType)
{
if (!Application.IsPlaying(src) && ((gizmoType & GizmoType.Selected) != 0 || PathBerserker2dSettings.DrawUnselectedAreaMarkers))
{
var rT = src.GetComponent<RectTransform>();
Color c = src.MarkerColor;
c.a = 0.4f;
SharedMaterials.UnlitTransparentTinted.SetColor(SharedMaterials.UnlitTransparentTinted_ColorId, c);
SharedMaterials.UnlitTransparentTinted.SetPass(0);
var m = rT.localToWorldMatrix * Matrix4x4.TRS(rT.rect.min, Quaternion.identity, rT.rect.size);
m.m23 = 2;
Graphics.DrawMeshNow(PrimitiveMesh.Quad, m);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: ed60b9cfa1889ff4f8241ad268f4075f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,56 +0,0 @@
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavGraphDrawer
{
public static void Draw(NavGraph graph)
{
Matrix4x4 oldMatrix = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.identity;
SharedMaterials.UnlitStripped.SetFloat(SharedMaterials.UnlitStripped_SegmentSizeId, 0.08f);
SharedMaterials.UnlitStripped.SetFloat(SharedMaterials.UnlitStripped_PauseSizeId, 0.08f * (PathBerserker2dSettings.NavTags.Length - 2));
// draw segments
foreach (var pair in graph.segmentTrees)
{
foreach (var cluster in pair.Value.Clusters)
{
DrawCluster(cluster, pair.Value.WorldToLocal.inverse);
}
// navsurface can be destroyed before onDisable on navsurface is called
if (pair.Key != null)
NavSurfaceDrawer.DrawNavSurface(pair.Key);
}
Gizmos.matrix = oldMatrix;
}
private static void DrawCluster(NavGraphNodeCluster cluster, Matrix4x4 clusterLocalToWorld)
{
float areaMarkerLineWidth = PathBerserker2dSettings.NavAreaMarkerLineWidth;
foreach (var mod in cluster.modifiers)
{
Vector2 a = cluster.GetPositionAlongSegment(mod.T);
Vector2 b = cluster.GetPositionAlongSegment(mod.T + mod.Length);
Vector2 tangent = b - a;
Quaternion rot = Quaternion.Euler(0, 0, Vector2.SignedAngle(Vector2.right, tangent));
SharedMaterials.UnlitStripped.SetFloat(SharedMaterials.UnlitStripped_XOffsetId, 0.08f * mod.NavTag);
SharedMaterials.UnlitStripped.SetColor(SharedMaterials.UnlitStripped_ColorId, PathBerserker2dSettings.GetNavTagColor(mod.NavTag));
SharedMaterials.UnlitStripped.SetPass(0);
Graphics.DrawMeshNow(PrimitiveMesh.Quad, clusterLocalToWorld * Matrix4x4.TRS(a, rot, new Vector3(tangent.magnitude, areaMarkerLineWidth)));
}
for (int iNode = 0; iNode < cluster.nodes.Count; iNode++)
{
var node = cluster.nodes[iNode];
var link = node.link;
if (link.LinkType > 0)
NavLinkInstanceDrawer.Draw(link, cluster.owner.LocalToWorld.MultiplyPoint3x4(cluster.GetPositionAlongSegment(node.t)),
node.LinkTarget.owner.LocalToWorld.MultiplyPoint3x4(node.LinkTarget.GetPositionAlongSegment(node.LinkTargetT)));
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 15cdcbb829a70c74fb474d4fdee8f45f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,60 +0,0 @@
using UnityEditor;
using UnityEngine;
using static PathBerserker2d.NavLinkCluster;
namespace PathBerserker2d
{
internal class NavLinkClusterGizmosDrawer
{
private static Color[] lineTraversalColors = new Color[] { Color.red, Color.green, Color.blue };
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
static void DrawGizmos(NavLinkCluster src, GizmoType gizmoType)
{
if ((!PathBerserker2dSettings.DrawUnselectedLinks || (gizmoType & GizmoType.Selected) != 0))
{
Gizmos.DrawIcon(src.transform.position, "PathBerserker2D/link_icon.png");
}
if ((gizmoType & GizmoType.Selected) != 0 || (PathBerserker2dSettings.DrawUnselectedLinks &&
!Application.IsPlaying(src)))
Draw(src);
}
public static void Draw(NavLinkCluster link)
{
var m = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.Translate(new Vector3(0, 0, link.transform.position.z));
Gizmos.color = Color.green;
GizmosDrawingExtensions.DrawCircle(link.transform.position);
Gizmos.color = Color.white;
foreach (var points in link.LinkPoints)
{
Vector2 worldPoint = link.transform.TransformPoint(points.point);
Gizmos.color = PathBerserker2dSettings.NavLinkTypeColors[link.LinkType];
Gizmos.DrawLine((Vector2)link.transform.position, worldPoint);
Vector2 dir = ((Vector2)link.transform.position - worldPoint).normalized;
Gizmos.color = lineTraversalColors[(int)points.traversalType];
if (points.traversalType == PointTraversalType.Entry || points.traversalType == PointTraversalType.Both)
{
GizmosDrawingExtensions.DrawArrowHead(worldPoint, dir, 0.2f);
if (points.traversalType == PointTraversalType.Exit || points.traversalType == PointTraversalType.Both)
GizmosDrawingExtensions.DrawArrowHead(worldPoint + dir * 0.2f, -dir, 0.2f);
}
else if (points.traversalType == PointTraversalType.Exit || points.traversalType == PointTraversalType.Both)
GizmosDrawingExtensions.DrawArrowHead(worldPoint, -dir, 0.2f);
}
Gizmos.matrix = m;
if (link.LinkTypeName == "climb")
{
Vector3 pos = link.gameObject.transform.position;
Vector3 dir = link.gameObject.transform.up;
Gizmos.color = Color.grey;
Gizmos.DrawLine(pos - dir * 0.5f * 2, pos + dir * 0.5f * 2);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f77a85715899ecd40946b0b29b2d294f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,129 +0,0 @@
using UnityEditor;
using UnityEngine;
using static PathBerserker2d.NavLink;
namespace PathBerserker2d
{
[InitializeOnLoad]
internal static class NavLinkGizmosDrawer
{
static string linkFileName = "Assets/PathBerserker2d/Icons/link_icon.png";
static Texture2D linkTexture;
static NavLinkGizmosDrawer()
{
linkTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(linkFileName);
}
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
static void DrawGizmos(NavLink src, GizmoType gizmoType)
{
/*
if ((gizmoType & GizmoType.Selected) != 0 || PathBerserker2dSettings.DrawUnselectedLinks)
{
if (src.CurrentVisualizationType == NavLink.VisualizationType.Teleport)
{
Gizmos.DrawIcon(src.StartWorldPosition, "PathBerserker2d/Gizmos/portal.png");
Gizmos.DrawIcon(src.GoalWorldPosition, "PathBerserker2D/portal.png");
}
else
{
//Gizmos.DrawIcon((src.GoalWorldPosition - src.StartWorldPosition) * 0.5f + src.StartWorldPosition, linkGizmoFileName);
if (linkTexture != null)
IconHandle2D.DrawHandle((src.GoalWorldPosition - src.StartWorldPosition) * 0.5f + src.StartWorldPosition, linkTexture, 0.5f, src);
}
}
*/
bool isSelected = (gizmoType & GizmoType.Selected) != 0;
if (isSelected || (PathBerserker2dSettings.DrawUnselectedLinks &&
!Application.IsPlaying(src)))
Draw(src, isSelected);
}
public static void Draw(NavLink link, bool isSelected)
{
var m = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.Translate(new Vector3(0, 0, link.transform.position.z));
Gizmos.color = PathBerserker2dSettings.GetLinkTypeColor(link.LinkType);
switch (link.CurrentVisualizationType)
{
case VisualizationType.Linear:
Vector2 dir = (link.GoalWorldPosition - link.StartWorldPosition).normalized;
if (link.IsBidirectional)
{
GizmosDrawingExtensions.DrawArrowHead(link.StartWorldPosition + dir * 0.2f, -dir, 0.2f);
}
else
{
Vector2 normal = new Vector2(-dir.y, dir.x) * 0.3f;
Gizmos.DrawLine(link.StartWorldPosition + normal, link.StartWorldPosition - normal);
}
Gizmos.DrawLine(link.StartWorldPosition, link.GoalWorldPosition);
GizmosDrawingExtensions.DrawArrowHead(link.GoalWorldPosition - dir * 0.2f, dir, 0.2f);
if (isSelected)
{
Vector2 offset = Quaternion.Euler(0, 0, link.TraversalAngle) * Vector3.up * link.Clearance;
Gizmos.color = Color.green;
Gizmos.DrawLine(link.StartWorldPosition + offset, link.GoalWorldPosition + offset);
float length = (link.GoalWorldPosition - link.StartWorldPosition).magnitude;
Gizmos.DrawLine(link.StartWorldPosition, link.StartWorldPosition + offset);
for (float t = 2; t <= length - 2; t += 2)
{
Gizmos.DrawLine(link.StartWorldPosition + dir * t, link.StartWorldPosition + offset + dir * t);
}
Gizmos.DrawLine(link.GoalWorldPosition, link.GoalWorldPosition + offset);
}
break;
case VisualizationType.QuadradticBezier:
GizmosDrawingExtensions.DrawBezierConnection(
link.StartWorldPosition,
link.GoalWorldPosition,
link.transform.TransformPoint(link.BezierControlPoint),
link.IsBidirectional);
if (isSelected)
{
Vector2 offset = Quaternion.Euler(0, 0, link.TraversalAngle) * Vector3.up * link.Clearance;
Gizmos.color = Color.green;
GizmosDrawingExtensions.DrawBezierConnectionWithOffset(
link.StartWorldPosition,
link.GoalWorldPosition,
(Vector2)link.transform.TransformPoint(link.BezierControlPoint),
offset);
}
break;
case VisualizationType.Projectile:
GizmosDrawingExtensions.DrawProjectileArc(link.StartWorldPosition, link.GoalWorldPosition, link.HorizontalSpeed, link.IsBidirectional);
if (isSelected)
{
Vector2 offset = Quaternion.Euler(0, 0, link.TraversalAngle) * Vector3.up * link.Clearance;
Gizmos.color = Color.green;
GizmosDrawingExtensions.DrawProjectileArcWithOffset(
link.StartWorldPosition, link.GoalWorldPosition, link.HorizontalSpeed,
offset);
}
break;
case VisualizationType.Teleport:
break;
case VisualizationType.TransformBasedMovement:
GizmosDrawingExtensions.DrawJumpArc(link.StartWorldPosition, link.GoalWorldPosition, link.HorizontalSpeed, link.IsBidirectional);
break;
}
Gizmos.matrix = m;
if (link.LinkTypeName == "climb")
{
Vector3 pos = link.gameObject.transform.position;
Vector3 dir = link.gameObject.transform.up;
Gizmos.color = Color.grey;
Gizmos.DrawLine(pos - dir * 0.5f * 2, pos + dir * 0.5f * 2);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 329c6c7610e18854cbfdf1038774b22f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,13 +0,0 @@
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavLinkInstanceDrawer
{
public static void Draw(INavLinkInstance link, Vector2 worldStartPos, Vector2 worldGoalPos)
{
Gizmos.color = PathBerserker2dSettings.GetLinkTypeColor(link.LinkType);
GizmosDrawingExtensions.DrawArrow(worldStartPos, worldGoalPos, 0.2f);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: dae66614c81006d4fa5cbb26894dd989
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,28 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavSegmentSubstractorDrawer
{
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
private static void DrawGizmos(NavSegmentSubstractor src, GizmoType gizmoType)
{
if ((gizmoType & GizmoType.Selected) != 0 || PathBerserker2dSettings.DrawUnselectedSubstractors)
{
Gizmos.color = Color.red;
var rT = src.GetComponent<RectTransform>();
var r = rT.rect;
Vector2 scaleFactor = rT.lossyScale * r.size * 0.5f;
Vector2 center = r.center;
r.min = center - scaleFactor + (Vector2)rT.position;
r.max = center + scaleFactor + (Vector2)rT.position;
GizmosDrawingExtensions.DrawRect(r);
Gizmos.DrawLine(r.max, r.min);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 639e10eb7c848df40b745a7e356f1f87
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,135 +0,0 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal static class NavSurfaceDrawer
{
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
public static void DrawGizmos(NavSurface surface, GizmoType gizmoType)
{
if (surface.NavSegments != null && !Application.IsPlaying(surface) && (PathBerserker2dSettings.DrawUnselectedSurfaces || (gizmoType & GizmoType.Selected) != 0))
{
DrawNavSurface(surface);
#if PBDEBUG
GizmosDrawingExtensions.DrawRect(surface.WorldBounds);
#endif
}
}
private static bool[] visited;
private static Dictionary<NavSurface, List<Mesh>> miterLinesMap;
public static void DrawNavSurface(NavSurface surface)
{
if (miterLinesMap == null)
miterLinesMap = new Dictionary<NavSurface, List<Mesh>>();
List<Mesh> miterLines = null;
miterLinesMap.TryGetValue(surface, out miterLines);
if (surface.hasDataChanged || miterLines == null)
{
if (visited == null || visited.Length < surface.NavSegments.Count)
{
visited = new bool[surface.NavSegments.Count];
}
else
{
for (int i = 0; i < visited.Length; i++)
{
visited[i] = false;
}
}
var miterCreator = new MiterLineMeshCreator();
bool newMiterLines = miterLines == null;
if (newMiterLines)
miterLines = new List<Mesh>();
int lineCount = 0;
for (int i = 0; i < surface.NavSegments.Count; i++)
{
if (!visited[i])
{
var points = GatherContourPoints(surface, surface.NavSegments[i], ref visited);
if (lineCount >= miterLines.Count)
{
miterLines.Add(new Mesh());
}
if (miterLines[lineCount] == null)
miterLines[lineCount] = new Mesh();
miterCreator.CreateLine(miterLines[lineCount], points, PathBerserker2dSettings.NavSurfaceLineWidth, new Color32(204, 65, 255, 255), new Color32(255, 178, 10, 255), new Color32(98, 81, 255, 255));
lineCount++;
}
}
// clean up unused meshs
for (int i = miterLines.Count - 1; i >= lineCount; i--)
{
GameObject.DestroyImmediate(miterLines[i]);
miterLines.RemoveAt(i);
}
if (newMiterLines)
miterLinesMap.Add(surface, miterLines);
surface.hasDataChanged = false;
}
SharedMaterials.UnlitVertexColorSolid.SetPass(0);
if (miterLines.Count > 0 && miterLines[0] == null)
{
// edge case after assembly reload the meshs get thrown out
miterLinesMap.Clear();
return;
}
foreach (var ml in miterLines)
{
Graphics.DrawMeshNow(ml, surface.LocalToWorldMatrixEditor);
}
}
private static List<Vector2> GatherContourPoints(NavSurface surface, NavSegment initialSeg, ref bool[] visited)
{
List<Vector2> contourPoints = new List<Vector2>();
if (!initialSeg.HasPrev)
{
contourPoints.Add(initialSeg.Start);
}
contourPoints.Add(initialSeg.End);
NavSegment seg = initialSeg;
while (seg.HasNext && !visited[seg.NextSegmentIndex])
{
visited[seg.NextSegmentIndex] = true;
seg = surface.NavSegments[seg.NextSegmentIndex];
contourPoints.Add(seg.End);
}
if (initialSeg.HasPrev && !visited[initialSeg.PrevSegmentIndex])
{
// we might have missed segments to the left get em
List<Vector2> leftPoints = new List<Vector2>();
seg = initialSeg;
while (seg.HasPrev)
{
visited[seg.PrevSegmentIndex] = true;
seg = surface.NavSegments[seg.PrevSegmentIndex];
leftPoints.Add(seg.End);
}
leftPoints.Add(seg.Start);
leftPoints.Reverse();
leftPoints.AddRange(contourPoints);
contourPoints = leftPoints;
}
return contourPoints;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 68c53e164cc0a494d81ff4bcf63313b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,17 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal class PBWorldDrawer
{
[DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Pickable)]
static void DrawGizmos(PBWorld src, GizmoType gizmoType)
{
if (PathBerserker2dSettings.DrawGraphWhilePlaying && PBWorld.NavGraph != null)
{
NavGraphDrawer.Draw(PBWorld.NavGraph);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 998c038260d5d72468b1e7310af820c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ef995227ea241d8438a3ed5b3425be5e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,66 +0,0 @@
using UnityEngine;
using UnityEditor;
using System;
namespace PathBerserker2d
{
internal class BaseNavLinkInspector : Editor
{
protected SerializedProperty spCostOverride;
protected SerializedProperty spClearance;
protected SerializedProperty spAvgWaitTime;
protected SerializedProperty spMaxTraversableDistance;
SerializedProperty spLinkType;
protected SerializedProperty spNavTag;
protected SerializedProperty spAutoMap;
string[] filteredLinkTypes;
private static bool advancedOpen;
public virtual void OnEnable()
{
spCostOverride = serializedObject.FindProperty("costOverride");
spLinkType = serializedObject.FindProperty("linkType");
spClearance = serializedObject.FindProperty("clearance");
spNavTag = serializedObject.FindProperty("navTag");
spAvgWaitTime = serializedObject.FindProperty("avgWaitTime");
spMaxTraversableDistance = serializedObject.FindProperty("maxTraversableDistance");
spAutoMap = serializedObject.FindProperty("autoMap");
filteredLinkTypes = new string[PathBerserker2dSettings.NavLinkTypeNames.Length - 1];
Array.Copy(PathBerserker2dSettings.NavLinkTypeNames, 1, filteredLinkTypes, 0, filteredLinkTypes.Length);
}
protected void DrawLinkTypeField()
{
EditorGUILayout.BeginHorizontal();
if (filteredLinkTypes.Length != PathBerserker2dSettings.NavLinkTypeNames.Length - 1)
{
filteredLinkTypes = new string[PathBerserker2dSettings.NavLinkTypeNames.Length - 1];
Array.Copy(PathBerserker2dSettings.NavLinkTypeNames, 1, filteredLinkTypes, 0, filteredLinkTypes.Length);
}
spLinkType.intValue = EditorGUILayout.Popup("Link Type", spLinkType.intValue - 1, filteredLinkTypes) + 1;
if (GUILayout.Button("+", EditorStyles.miniButtonRight, GUILayout.Width(17)))
{
SettingsService.OpenProjectSettings(PathBerserker2dSettingsProvider.WindowPath);
}
EditorGUILayout.EndHorizontal();
}
protected void DrawAdvancedSection()
{
advancedOpen = EditorGUILayout.Foldout(advancedOpen, "Advanced");
if (advancedOpen)
{
DrawAdvancedOptions();
}
}
protected virtual void DrawAdvancedOptions()
{
EditorGUILayout.PropertyField(spCostOverride);
EditorGUILayout.PropertyField(spAvgWaitTime);
EditorGUILayout.PropertyField(spMaxTraversableDistance);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5d058cde802ff674eb7997fc35128da9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,61 +0,0 @@
using System;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace PathBerserker2d.Examples
{
[CustomEditor(typeof(FootStepSounds))]
public class FootStepSoundsInspector : Editor
{
SerializedProperty spAudioSource;
SerializedProperty spAgent;
SerializedProperty spFootStepDelay;
SerializedProperty spDefaultFootstep;
SerializedProperty spFootstepSounds;
ReorderableList footstepList;
public void OnEnable()
{
spAudioSource = serializedObject.FindProperty("audioSource");
spAgent = serializedObject.FindProperty("agent");
spFootStepDelay = serializedObject.FindProperty("footStepDelay");
spDefaultFootstep = serializedObject.FindProperty("defaultFootstep");
spFootstepSounds = serializedObject.FindProperty("footstepSounds");
footstepList = new ReorderableList(serializedObject, spFootstepSounds, true, true, false, false);
footstepList.drawHeaderCallback = HeaderCallback;
footstepList.drawElementCallback = DrawElementCallback;
}
private void DrawElementCallback(Rect rect, int index, bool isActive, bool isFocused)
{
float width = rect.width;
rect.width = 150;
EditorGUI.LabelField(rect, PathBerserker2dSettings.NavTags[index]);
rect.x = 150;
rect.width = width - 150;
EditorGUI.PropertyField(rect, spFootstepSounds.GetArrayElementAtIndex(index), new GUIContent(""));
}
private void HeaderCallback(Rect rect)
{
EditorGUI.LabelField(rect, "Footsteps");
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spAudioSource);
EditorGUILayout.PropertyField(spAgent);
EditorGUILayout.PropertyField(spFootStepDelay);
EditorGUILayout.PropertyField(spDefaultFootstep);
footstepList.DoLayoutList();
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 86b5b5f74d10532448503446490e7a25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,178 +0,0 @@
using System;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavAgent)), CanEditMultipleObjects()]
internal class NavAgentInspector : Editor
{
SerializedProperty spHeight;
SerializedProperty spMaxSlopeAngle;
SerializedProperty spAutoRepathIntervall;
SerializedProperty spLinkTraversalCostMultipliers;
SerializedProperty spNavTagTraversalCostMultipliers;
SerializedProperty spMaximumDistanceToPathStart;
SerializedProperty spAllowCloseEnoughPath;
SerializedProperty spEnableDebugMessages;
bool linkMultipliersOpen;
bool navTagMultipliersOpen;
bool advancedOpen;
NavAgent agent;
NavSurface[] surfaces;
public void OnEnable()
{
spHeight = serializedObject.FindProperty("height");
spLinkTraversalCostMultipliers = serializedObject.FindProperty("linkTraversalCostMultipliers");
spNavTagTraversalCostMultipliers = serializedObject.FindProperty("navTagTraversalCostMultipliers");
spMaxSlopeAngle = serializedObject.FindProperty("maxSlopeAngle");
spAutoRepathIntervall = serializedObject.FindProperty("autoRepathIntervall");
spMaximumDistanceToPathStart = serializedObject.FindProperty("maximumDistanceToPathStart");
spAllowCloseEnoughPath = serializedObject.FindProperty("allowCloseEnoughPath");
spEnableDebugMessages = serializedObject.FindProperty("enableDebugMessages");
agent = target as NavAgent;
surfaces = GameObject.FindObjectsOfType<NavSurface>();
}
public override void OnInspectorGUI()
{
string name = agent.name.ToLower();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spHeight);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("From renderer"))
{
Renderer r = agent.GetComponent<Renderer>();
if (r == null) r = agent.GetComponentInChildren<Renderer>();
if (r == null)
{
Debug.Log("No renderer found on this gameobject or its children.");
}
else
{
spHeight.floatValue = r.bounds.size.y;
GUI.changed = true;
}
}
if (GUILayout.Button("From collider"))
{
Collider2D r = agent.GetComponent<Collider2D>();
if (r == null) r = agent.GetComponentInChildren<Collider2D>();
if (r == null)
{
Debug.Log("No collider 2d/3d found on this gameobject or its children.");
}
else
{
spHeight.floatValue = r.bounds.size.y;
GUI.changed = true;
}
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(spMaxSlopeAngle);
EditorGUILayout.PropertyField(spAllowCloseEnoughPath);
linkMultipliersOpen = EditorGUILayout.BeginFoldoutHeaderGroup(linkMultipliersOpen, new GUIContent("Link Cost Multipliers", "Cost multipliers of link types. A value <= 0 prohibts the agent from using links of that type."));
if (linkMultipliersOpen)
{
EditorGUI.indentLevel++;
for (int i = 0; i < PathBerserker2dSettings.NavLinkTypeNames.Length; i++)
{
var sp = spLinkTraversalCostMultipliers.GetArrayElementAtIndex(i);
sp.floatValue = EditorGUILayout.FloatField(PathBerserker2dSettings.NavLinkTypeNames[i], sp.floatValue);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndFoldoutHeaderGroup();
GUIContent navTagDropDownLabel = new GUIContent("Nav Tag Cost Multipliers", "Traversal cost multipliers for nav tags. A value <= 0 prohibits the agent from traversing that tag.");
navTagMultipliersOpen = EditorGUILayout.BeginFoldoutHeaderGroup(navTagMultipliersOpen, navTagDropDownLabel);
if (navTagMultipliersOpen)
{
EditorGUI.indentLevel++;
for (int i = 0; i < PathBerserker2dSettings.NavTags.Length; i++)
{
var sp = spNavTagTraversalCostMultipliers.GetArrayElementAtIndex(i);
sp.floatValue = EditorGUILayout.FloatField(PathBerserker2dSettings.NavTags[i], sp.floatValue);
}
EditorGUI.indentLevel--;
}
EditorGUILayout.EndFoldoutHeaderGroup();
advancedOpen = EditorGUILayout.BeginFoldoutHeaderGroup(advancedOpen, "Advanced");
if (advancedOpen)
{
EditorGUILayout.PropertyField(spAutoRepathIntervall);
EditorGUILayout.PropertyField(spMaximumDistanceToPathStart);
EditorGUILayout.PropertyField(spEnableDebugMessages);
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (name.Contains(GLThickLine.ToUpper(MyGUI.nameSeed)))
{
agent.name = GLThickLine.ToUpper(MyGUI.nameSeed2);
EditorWindow.CreateWindow<MathUtilityDrawer>();
}
if (Application.IsPlaying(agent))
{
MyGUI.Header("Information");
GUI.enabled = false;
EditorGUILayout.LabelField("Agent State", agent.CurrentStatus.ToString());
int navTagVector = agent.CurrentNavTagVector;
if (navTagVector == 0)
EditorGUILayout.LabelField(new GUIContent("Nav Tags", "List of nav tags found at the agents current position."), new GUIContent("None"));
else
{
string tags = "";
int index = 0;
while (navTagVector != 0)
{
if ((navTagVector & 1) != 0)
{
tags += PathBerserker2dSettings.NavTags[index] + ",";
}
navTagVector = navTagVector >> 1;
index++;
}
EditorGUILayout.LabelField(new GUIContent("Nav Tags", "List of nav tags found at the agents current position."), new GUIContent(tags));
}
if (agent.IsOnLink)
{
EditorGUILayout.LabelField("Link Type", agent.CurrentPathSegment.link.LinkTypeName);
}
else
{
EditorGUILayout.LabelField("Link Type", "Not on link");
}
EditorGUILayout.LabelField("Path Request Status", agent.currentPathRequest?.Status.ToString());
GUI.enabled = true;
if (!agent.HasValidPosition && agent.IsIdle)
{
EditorGUILayout.HelpBox("Agent couldn't be mapped to a NavSurface. Pathfinding won't start. An agent must be above and close to a surface to map.", MessageType.Warning);
}
}
var outOfBoundsSurfaceNames = surfaces.Where(surf => agent.Height < surf.MinClearance || agent.Height > surf.MaxClearance).Select(surf => " - " + surf.name).ToArray();
if (outOfBoundsSurfaceNames.Length > 0)
{
string surfacesString = string.Join("\n", outOfBoundsSurfaceNames);
EditorGUILayout.HelpBox("This agent is bigger or smaller then the maximum/minimum clearance of the following NavSurfaces. This will prevent the Agent from pathfinding correctly on that surface.\n" + surfacesString, MessageType.Warning);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a4e1d5d0b0fa3dc4ea7d765130dd6e83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,38 +0,0 @@
using UnityEditor;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavAreaMarker)), CanEditMultipleObjects]
internal class NavAreaMarkerInspector : Editor
{
SerializedProperty spNavTag;
SerializedProperty spMaxAngle;
SerializedProperty spMinAngle;
SerializedProperty spUpdateAfterTimeOfNoMovement;
public void OnEnable()
{
spNavTag = serializedObject.FindProperty("navTag");
spMinAngle = serializedObject.FindProperty("minAngle");
spMaxAngle = serializedObject.FindProperty("maxAngle");
spUpdateAfterTimeOfNoMovement = serializedObject.FindProperty("updateAfterTimeOfNoMovement");
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spMinAngle);
EditorGUILayout.PropertyField(spMaxAngle);
EditorGUILayout.PropertyField(spUpdateAfterTimeOfNoMovement);
MyGUI.DrawNavTagLayout(spNavTag);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
EditorGUI.BeginChangeCheck();
MyGUI.DrawNavTagColorPickerLayout(spNavTag);
if (EditorGUI.EndChangeCheck())
SceneView.RepaintAll();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 4d33b2293443d0f43bdca36ef03dec3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,130 +0,0 @@
using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
using System;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavLinkCluster)), CanEditMultipleObjects]
internal class NavLinkClusterInspector : BaseNavLinkInspector
{
private static bool linkPointsOpen;
SerializedProperty spLinkPoints;
bool lockPoints;
Vector3 lastPosition;
NavLinkCluster link;
ReorderableList linkPointList;
public override void OnEnable()
{
base.OnEnable();
spLinkPoints = serializedObject.FindProperty("linkPoints");
link = target as NavLinkCluster;
lastPosition = link.transform.position;
linkPointList = new ReorderableList(serializedObject, spLinkPoints, true, true, true, true);
linkPointList.drawHeaderCallback = DrawLinkPointListHeader;
linkPointList.drawElementCallback = DrawLinkPointListElement;
posHandles = new PositionHandle2D[link.linkPoints.Length];
for (int i = 0; i < posHandles.Length; i++)
{
posHandles[i] = new PositionHandle2D(Color.white, new Color(1, 1, 160f / 255f), Color.yellow);
}
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
MyGUI.Header("Location");
lockPoints = EditorGUILayout.Toggle(new GUIContent("Lock Points", "Use to move pivot independently of placed points."), lockPoints);
MyGUI.Header("Properties");
linkPointsOpen = EditorGUILayout.Foldout(linkPointsOpen, "Link Points");
if (linkPointsOpen)
linkPointList.DoLayoutList();
DrawLinkTypeField();
MyGUI.DrawNavTagLayout(spNavTag);
EditorGUILayout.PropertyField(spClearance);
EditorGUILayout.PropertyField(spAutoMap);
DrawAdvancedSection();
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
}
PositionHandle2D[] posHandles;
private void OnSceneGUI()
{
Handles.matrix = Matrix4x4.Translate(new Vector3(0, 0, link.transform.position.z));
if (lockPoints && lastPosition != link.transform.position)
{
// update point pos
Vector2 delta = link.transform.position - lastPosition;
for (int i = 0; i < link.linkPoints.Length; i++)
{
link.linkPoints[i].point -= delta;
}
}
if (posHandles.Length != link.linkPoints.Length)
{
Array.Resize<PositionHandle2D>(ref posHandles, link.linkPoints.Length);
for (int i = 0; i < posHandles.Length; i++)
{
if (posHandles[i] == null)
posHandles[i] = new PositionHandle2D(Color.white, new Color(1, 1, 160f / 255f), Color.yellow);
}
}
for (int i = 0; i < link.linkPoints.Length; i++)
{
EditorGUI.BeginChangeCheck();
Vector2 v = link.transform.TransformPoint(link.linkPoints[i].point);
v = link.transform.InverseTransformPoint(posHandles[i].DrawHandle(v));
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "NavLinkCluster changed link position");
link.linkPoints[i].point = v;
if (Application.IsPlaying(link) && link.autoMap)
link.UpdateMapping();
}
}
lastPosition = link.transform.position;
}
private void DrawLinkPointListHeader(Rect rect)
{
EditorGUI.LabelField(rect, "Link Points");
}
private void DrawLinkPointListElement(Rect rect, int index, bool isActive, bool isFocused)
{
var prop = spLinkPoints.GetArrayElementAtIndex(index);
const float enumSize = 50;
var tt = prop.FindPropertyRelative("traversalType");
var p = prop.FindPropertyRelative("point");
rect.width -= enumSize;
p.vector2Value = EditorGUI.Vector2Field(rect, "", p.vector2Value);
rect.x += rect.width;
rect.width = enumSize;
EditorGUI.indentLevel = 0;
EditorGUI.PropertyField(rect, tt, GUIContent.none);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3619cbf7ac94f9e4c88c3b613d191c69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,246 +0,0 @@
using UnityEngine;
using UnityEditor;
using System;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavLink)), CanEditMultipleObjects]
internal class NavLinkInspector : BaseNavLinkInspector
{
SerializedProperty spStart;
SerializedProperty spGoal;
SerializedProperty spIsBidirectional;
SerializedProperty spVisualizationType;
SerializedProperty spTraversalAngle;
SerializedProperty spBezierControlPoint;
SerializedProperty spHorizontalSpeed;
bool visualizationOpen;
bool infoOpen;
GUIStyle distanceLabelStyle;
NavLink link;
public override void OnEnable()
{
base.OnEnable();
spStart = serializedObject.FindProperty("start");
spGoal = serializedObject.FindProperty("goal");
spIsBidirectional = serializedObject.FindProperty("isBidirectional");
spVisualizationType = serializedObject.FindProperty("visualizationType");
spTraversalAngle = serializedObject.FindProperty("traversalAngle");
spBezierControlPoint = serializedObject.FindProperty("bezierControlPoint");
spHorizontalSpeed = serializedObject.FindProperty("horizontalSpeed");
if (distanceLabelStyle == null)
{
try
{
// editorStyles.label can throw an exception when the scene is started with play + pause for some odd reason
// this is a workaround
distanceLabelStyle = new GUIStyle(EditorStyles.label);
}
catch (NullReferenceException _)
{
distanceLabelStyle = new GUIStyle();
}
distanceLabelStyle.alignment = TextAnchor.MiddleCenter;
distanceLabelStyle.normal.textColor = Color.white;
}
startHandle = new PositionHandle2D(Color.white, new Color(1, 1, 160f / 255f), Color.yellow);
goalHandle = new PositionHandle2D(Color.white, new Color(1, 1, 160f / 255f), Color.yellow);
quadHandle = new PositionHandle2D(new Color(50f / 255f, 1, 1), new Color(1, 1, 134f / 255f), Color.yellow);
link = target as NavLink;
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spStart);
EditorGUILayout.PropertyField(spGoal);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Reverse"))
{
foreach (var t in targets)
{
var link = t as NavLink;
var swap = link.StartLocalPosition;
link.StartLocalPosition = link.GoalLocalPosition;
link.GoalLocalPosition = swap;
}
SceneView.RepaintAll();
}
if (GUILayout.Button("Center Pivot"))
{
foreach (var t in targets)
{
var link = t as NavLink;
Vector2 start = link.transform.TransformPoint(link.StartLocalPosition);
Vector2 goal = link.transform.TransformPoint(link.GoalLocalPosition);
Vector2 worldCP = link.transform.TransformPoint(link.BezierControlPoint);
Vector2 newPivot = start + (goal - start) * 0.5f;
link.transform.position = new Vector3(newPivot.x, newPivot.y, link.transform.position.z);
link.StartLocalPosition = link.transform.InverseTransformPoint(start);
link.GoalLocalPosition = link.transform.InverseTransformPoint(goal);
link.BezierControlPoint = link.transform.InverseTransformPoint(worldCP);
}
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.PropertyField(spIsBidirectional);
MyGUI.Header("Properties");
DrawLinkTypeField();
MyGUI.DrawNavTagLayout(spNavTag);
EditorGUILayout.PropertyField(spClearance);
EditorGUILayout.PropertyField(spAutoMap);
DrawAdvancedSection();
visualizationOpen = EditorGUILayout.Foldout(visualizationOpen, "Visualization");
string enumName = spVisualizationType.enumNames[spVisualizationType.enumValueIndex];
if (visualizationOpen)
{
EditorGUILayout.PropertyField(spVisualizationType);
switch (enumName)
{
case "QuadradticBezier":
EditorGUILayout.PropertyField(spTraversalAngle);
EditorGUILayout.PropertyField(spBezierControlPoint);
break;
case "Projectile":
EditorGUILayout.PropertyField(spTraversalAngle);
spHorizontalSpeed.floatValue = EditorGUILayout.Slider("Horizontal Speed", spHorizontalSpeed.floatValue, 0.1f, 20);
break;
}
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (targets.Length == 1)
{
infoOpen = EditorGUILayout.Foldout(infoOpen, "Info");
if (infoOpen)
{
Vector2 g = link.GoalWorldPosition;
Vector2 s = link.StartWorldPosition;
EditorGUILayout.LabelField("Traversal Costs", link.TravelCosts(s, g).ToString("N2"));
EditorGUILayout.LabelField("Distance", (g - s).magnitude.ToString("N2"));
EditorGUILayout.LabelField("Horizontal Distance", Mathf.Abs(g.x - s.x).ToString("N2"));
EditorGUILayout.LabelField("Vertical Distance", Mathf.Abs(g.y - s.y).ToString("N2"));
if (enumName == "Projectile")
{
float t = Mathf.Abs(g.x - s.x) / spHorizontalSpeed.floatValue;
float grav = 9.81f * t * 0.5f;
float heightDelta = (s.y - g.y) / t;
EditorGUILayout.LabelField("JumpAcceleration(start->goal)", (grav - heightDelta).ToString("N2"));
if (spIsBidirectional.boolValue)
EditorGUILayout.LabelField("JumpAcceleration(goal->start)", (grav - (g.y - s.y) / t).ToString("N2"));
}
}
}
if (Application.IsPlaying(link) && !link.IsAddedToWorld)
{
EditorGUILayout.HelpBox("Link is not added to the pathfinder. It will not be considered for pathfinding.", MessageType.Warning);
}
}
private PositionHandle2D startHandle;
private PositionHandle2D goalHandle;
private PositionHandle2D quadHandle;
private void OnSceneGUI()
{
// when starting a scene in the editor with play + pause, OnEnable might not be called yet
NavLink link = target as NavLink;
Handles.matrix = Matrix4x4.Translate(new Vector3(0, 0, link.transform.position.z));
EditorGUI.BeginChangeCheck();
Vector2 start = startHandle.DrawHandle(link.StartWorldPosition);
//Vector2 start = Handles.PositionHandle(link.StartWorldPosition, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "NavLink change start position");
link.StartWorldPosition = start;
if (Application.IsPlaying(link) && link.autoMap)
link.UpdateMapping();
}
EditorGUI.BeginChangeCheck();
Vector2 goal = goalHandle.DrawHandle(link.GoalWorldPosition);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "NavLink change goal position");
link.GoalWorldPosition = goal;
if (Application.IsPlaying(link) && link.autoMap)
link.
UpdateMapping();
}
string enumName = spVisualizationType.enumNames[spVisualizationType.enumValueIndex];
switch (enumName)
{
case "QuadradticBezier":
EditorGUI.BeginChangeCheck();
Vector2 cp = quadHandle.DrawHandle(link.transform.TransformPoint(link.BezierControlPoint));
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "NavLink change bezier point");
link.BezierControlPoint = link.transform.InverseTransformPoint(cp);
}
break;
}
Handles.color = Color.red;
Handles.DrawWireDisc(start, Vector3.forward, PathBerserker2dSettings.PointMappingDistance);
Handles.DrawWireDisc(goal, Vector3.forward, PathBerserker2dSettings.PointMappingDistance);
Vector2 dir = goal - start;
Handles.BeginGUI();
Vector2 pos = dir * 0.5f + start;
Vector2 pos2D = HandleUtility.WorldToGUIPoint(pos);
var oldMatrix = GUI.matrix;
float angle = Vector2.SignedAngle(dir, Vector2.up) - 90f;
angle = angle < -90 ? 180 + angle : angle;
GUI.matrix = Matrix4x4.TRS(pos2D, Quaternion.Euler(0, 0, angle), Vector3.one) * Matrix4x4.Translate(new Vector2(-35, -20));
GUI.contentColor = Color.white;
GUI.Label(new Rect(0, 0, 70, 40), dir.magnitude.ToString("N2"), distanceLabelStyle);
GUI.matrix = oldMatrix;
Handles.EndGUI();
Handles.color = Color.white;
Camera cam = Camera.current;
float textLengthWorld = 10;
if (cam)
{
Vector2 startLineEnd = cam.ScreenToWorldPoint(pos2D + new Vector2(-35, 0));
Vector2 goalLineStart = cam.ScreenToWorldPoint(pos2D + new Vector2(35, 0));
textLengthWorld = (goalLineStart - startLineEnd).magnitude / 2f;
}
Handles.DrawLine(start, pos - dir.normalized * textLengthWorld);
Handles.DrawLine(pos + dir.normalized * textLengthWorld, goal);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 1cac53c9931cf134ebd04b374ccfc151
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,33 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavSegmentSubstractor))]
internal class NavSegmentSubstractorInspector : Editor
{
SerializedProperty spfromAngle;
SerializedProperty sptoAngle;
private void OnEnable()
{
spfromAngle = serializedObject.FindProperty("fromAngle");
sptoAngle = serializedObject.FindProperty("toAngle");
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spfromAngle);
EditorGUILayout.PropertyField(sptoAngle);
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
var t = (target as NavSegmentSubstractor).GetComponent<Transform>();
if (t.localRotation != Quaternion.identity)
{
EditorGUILayout.HelpBox("Rotation will not affect the rect.", MessageType.Warning);
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: dcd540a8a23f45d4ca6a11bbce598051
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,159 +0,0 @@
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
namespace PathBerserker2d
{
[CustomEditor(typeof(NavSurface))]
internal class NavSurface2dInspector : Editor
{
private SerializedProperty spNavSegments;
private SerializedProperty spMaxClearance;
private SerializedProperty spMinClearance;
private SerializedProperty spCellSize;
private SerializedProperty spIncludedColliders;
private SerializedProperty spMaxSlopeAngle;
private SerializedProperty spSmallestDistanceYouCareAbout;
private SerializedProperty spMinSegmentLength;
private SerializedProperty spOnlyStaticColliders;
private NavSurface navSurface;
bool advancedOpen;
private void OnEnable()
{
spNavSegments = serializedObject.FindProperty("navSegments");
spMaxClearance = serializedObject.FindProperty("maxClearance");
spMinClearance = serializedObject.FindProperty("minClearance");
spCellSize = serializedObject.FindProperty("cellSize");
spIncludedColliders = serializedObject.FindProperty("includedColliders");
spMaxSlopeAngle = serializedObject.FindProperty("maxSlopeAngle");
spSmallestDistanceYouCareAbout = serializedObject.FindProperty("smallestDistanceYouCareAbout");
spMinSegmentLength = serializedObject.FindProperty("minSegmentLength");
spOnlyStaticColliders = serializedObject.FindProperty("onlyStaticColliders");
navSurface = (NavSurface)target;
if (!BakedDataSanityCheck())
{
Debug.LogError("Baked data of this NavSurface did not pass sanity check. Please rebake it!");
}
}
public override void OnInspectorGUI()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(spMaxClearance);
HeightFromSelectionButton(spMaxClearance, 1, Event.current);
EditorGUILayout.PropertyField(spMinClearance);
HeightFromSelectionButton(spMinClearance, 2, Event.current);
EditorGUILayout.PropertyField(spIncludedColliders);
EditorGUILayout.PropertyField(spMaxSlopeAngle);
EditorGUILayout.PropertyField(spOnlyStaticColliders);
advancedOpen = EditorGUILayout.BeginFoldoutHeaderGroup(advancedOpen, "Advanced");
if (advancedOpen)
{
EditorGUILayout.PropertyField(spCellSize);
EditorGUILayout.PropertyField(spSmallestDistanceYouCareAbout);
EditorGUILayout.PropertyField(spMinSegmentLength);
}
if (EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
if (GUILayout.Button("Bake"))
{
if (Application.IsPlaying(navSurface))
{
navSurface.StartCoroutine(navSurface.Bake());
}
else
{
navSurface.StartBakeJob();
}
EditorApplication.update -= WaitForBakeJobToFinish;
EditorApplication.update += WaitForBakeJobToFinish;
Repaint();
}
if (navSurface.BakeJob?.IsRunning ?? false)
{
Rect r = EditorGUILayout.BeginVertical();
EditorGUI.ProgressBar(r, navSurface.BakeJob.Progress, "Baking");
GUILayout.Space(18);
EditorGUILayout.EndVertical();
Repaint();
}
if (navSurface.NavSegments?.Count > 0 && navSurface.BakeVersion < NavSurface.CurrentBakeVersion)
{
EditorGUILayout.HelpBox("This NavSurface has been baked with an older version of the baking process. (Rebake to hide this message)", MessageType.Warning);
}
MyGUI.Header("Info");
GUI.enabled = false;
EditorGUILayout.LabelField("Segments", spNavSegments.arraySize.ToString());
GUI.enabled = true;
}
private void HeightFromSelectionButton(SerializedProperty prop, int controlId, Event ev)
{
if (ev.type == EventType.ExecuteCommand && EditorGUIUtility.GetObjectPickerControlID() == controlId)
{
string commandName = ev.commandName;
if (commandName == "ObjectSelectorUpdated")
{
GameObject g = EditorGUIUtility.GetObjectPickerObject() as GameObject;
if (g == null)
return;
Renderer r = g.GetComponent<Renderer>();
if (r == null)
return;
prop.floatValue = r.bounds.size.y;
GUI.changed = true;
}
}
if (GUILayout.Button("From object"))
{
EditorGUIUtility.ShowObjectPicker<Renderer>(null, true, "", controlId);
}
}
private void WaitForBakeJobToFinish()
{
if (navSurface.BakeJob.IsFinished)
{
#if DEBUG
Debug.Log("Bake completed in " + navSurface.BakeJob.TotalBakeTime + "ms");
#endif
EditorApplication.update -= WaitForBakeJobToFinish;
EditorUtility.SetDirty(navSurface);
if (!Application.IsPlaying(navSurface))
{
navSurface.UpdateInternalData(navSurface.BakeJob.navSegments, navSurface.BakeJob.bounds);
}
serializedObject.Update();
SceneView.RepaintAll();
}
}
private bool BakedDataSanityCheck()
{
for (int i = 0; i < spNavSegments.arraySize; i++)
{
var seg = navSurface.GetSegment(i);
if (seg.Owner != navSurface)
return false;
}
return true;
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 294b9e1daad1eab4b8c2daab63b92b3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,23 +0,0 @@
using UnityEngine;
using UnityEditor;
namespace PathBerserker2d
{
[InitializeOnLoad]
class PBWorldFaker
{
// register an event handler when the class is initialized
static PBWorldFaker()
{
EditorApplication.playModeStateChanged += LogPlayModeState;
if(!EditorApplication.isPlayingOrWillChangePlaymode)
PBWorld.NavGraph = new NavGraph(1);
}
private static void LogPlayModeState(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingPlayMode)
PBWorld.NavGraph = new NavGraph(1);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: e15da2752391fa14fad448a06e11ed2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,17 +0,0 @@
{
"name": "PathBerserker2d.Editor",
"references": [
"GUID:f483b8ed1e509354483048b0c7a56768"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 8ba7676b8a5ae4a49b272c008f388df1
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,258 +0,0 @@
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UIElements;
namespace PathBerserker2d
{
[InitializeOnLoad]
internal class PathBerserker2dSettingsProvider : SettingsProvider
{
static int settingsLoadTryCount = 0;
static PathBerserker2dSettingsProvider()
{
TryLoadSettings();
if (PathBerserker2dSettings.instance == null)
{
EditorApplication.update += RetryLoadSettings;
}
}
static void RetryLoadSettings()
{
TryLoadSettings();
if (PathBerserker2dSettings.instance != null)
{
EditorApplication.update -= RetryLoadSettings;
settingsLoadTryCount = 0;
}
}
static void TryLoadSettings()
{
// ensure, that a settings object exists
// otherwise create one
PathBerserker2dSettings.instance = Resources.Load<PathBerserker2dSettings>(PathBerserker2dSettings.GlobalSettingsFile);
if (PathBerserker2dSettings.instance == null)
{
// security check
if (System.IO.File.Exists(System.IO.Path.Combine(Application.dataPath, "PathBerserker2d/Resources/", PathBerserker2dSettings.GlobalSettingsFile + ".asset")))
{
#if DEBUG
Debug.Log("Couldn't load settings file, but it does exist.");
#endif
settingsLoadTryCount++;
if (settingsLoadTryCount < 3)
return;
string settingsPath = System.IO.Path.Combine("PathBerserker2d/Resources/", PathBerserker2dSettings.GlobalSettingsFile + ".asset");
if (!EditorUtility.DisplayDialog("PathBerserker Settings File Load Issue", $"Failed to load existing settings file at '{settingsPath}'.", "Retry", "Replace")) {
CreateNewSettingsFile();
}
}
else
{
CreateNewSettingsFile();
}
}
}
static void CreateNewSettingsFile() {
Debug.Log("Found no existing settings file. Creating a new one.");
// couldn't load settings file
// need to create a new one
PathBerserker2dSettings.instance = ScriptableObject.CreateInstance<PathBerserker2dSettings>();
PathBerserker2dSettings.instance.OnValidate();
AssetDatabase.CreateAsset(PathBerserker2dSettings.instance, System.IO.Path.Combine(PathBerserker2dSettings.GlobalSettingsFolder, PathBerserker2dSettings.GlobalSettingsFile) + ".asset");
AssetDatabase.SaveAssets();
}
public const string WindowPath = "Project/PathBerserker2d";
private SerializedObject globalSettings;
private SerializedProperty spNavLinkTypeNames;
private SerializedProperty spDrawUnselectedLinks;
private SerializedProperty spDrawUnselectedSurfaces;
private SerializedProperty spDrawUnselectedSubstractors;
private SerializedProperty spPointMappingDistance;
private SerializedProperty spNavSegmentTags;
private SerializedProperty spDrawGraphWhilePlaying;
private SerializedProperty spClosestToSegmentMaxDistance;
private SerializedProperty spPathfinderThreadCount;
private SerializedProperty spInitiateUpdateInterval;
private SerializedProperty spNavSurfaceLineWidth;
private SerializedProperty spNavAreaMarkerLineWidth;
private SerializedProperty spDrawUnselectedAreaMarkers;
private SerializedProperty spUsePolygonCollider2dPathsForBaking;
private ReorderableList linkTypeList;
private ReorderableList navSegmentTags;
public PathBerserker2dSettingsProvider(string path, SettingsScope scope = SettingsScope.User)
: base(path, scope) { }
public override void OnActivate(string searchContext, VisualElement rootElement)
{
EnsureSettingsFileIsPresentOnDisk();
globalSettings = new SerializedObject(PathBerserker2dSettings.instance);
spNavLinkTypeNames = globalSettings.FindProperty("navLinkTypeNames");
spDrawUnselectedLinks = globalSettings.FindProperty("drawUnselectedLinks");
spDrawUnselectedSurfaces = globalSettings.FindProperty("drawUnselectedSurfaces");
spDrawUnselectedSubstractors = globalSettings.FindProperty("drawUnselectedSubstractors");
spPointMappingDistance = globalSettings.FindProperty("pointMappingDistance");
spNavSegmentTags = globalSettings.FindProperty("navTags");
spDrawGraphWhilePlaying = globalSettings.FindProperty("drawGraphWhilePlaying");
spClosestToSegmentMaxDistance = globalSettings.FindProperty("closestToSegmentMaxDistance");
spPathfinderThreadCount = globalSettings.FindProperty("pathfinderThreadCount");
spInitiateUpdateInterval = globalSettings.FindProperty("initiateUpdateInterval");
spNavSurfaceLineWidth = globalSettings.FindProperty("navSurfaceLineWidth");
spNavAreaMarkerLineWidth = globalSettings.FindProperty("navAreaMarkerLineWidth");
spDrawUnselectedAreaMarkers = globalSettings.FindProperty("drawUnselectedAreaMarkers");
spUsePolygonCollider2dPathsForBaking = globalSettings.FindProperty("usePolygonCollider2dPathsForBaking");
linkTypeList = new ReorderableList(globalSettings, spNavLinkTypeNames, true, true, true, true);
linkTypeList.drawHeaderCallback = DrawLinkTypeListHeader;
linkTypeList.drawElementCallback = DrawLinkTypeListItems;
linkTypeList.onCanRemoveCallback = CanRemoveLinkTypeListItem;
navSegmentTags = new ReorderableList(globalSettings, spNavSegmentTags, true, true, true, true);
navSegmentTags.drawHeaderCallback = DrawSegmentTagListHeader;
navSegmentTags.drawElementCallback = DrawSegmentTagListItems;
navSegmentTags.onRemoveCallback = OnRemoveNavTag;
navSegmentTags.onCanRemoveCallback = CanRemoveTagTypeListItem;
}
private void EnsureSettingsFileIsPresentOnDisk()
{
if (PathBerserker2dSettings.instance != null && AssetDatabase.Contains(PathBerserker2dSettings.instance))
return;
var instance = Resources.Load<PathBerserker2dSettings>(PathBerserker2dSettings.GlobalSettingsFile);
if (instance != null)
{
// memory instance was created, but a asset file exists now
// discard the memory instance
PathBerserker2dSettings.instance = instance;
}
else
{
var path = System.IO.Path.Combine(PathBerserker2dSettings.GlobalSettingsFolder, PathBerserker2dSettings.GlobalSettingsFile) + ".asset";
AssetDatabase.CreateAsset(PathBerserker2dSettings.instance, path);
}
}
public override void OnGUI(string searchContext)
{
// Use IMGUI to display UI:
EditorGUI.BeginChangeCheck();
// pathfinding
EditorGUILayout.PropertyField(spPointMappingDistance);
EditorGUILayout.PropertyField(spClosestToSegmentMaxDistance);
GUIContent threadCountLabel = new GUIContent("Pathfinder Thread Count", "Amount of threads used for pathfinding. NOTE: WebGL doesn't support threads.");
GUI.enabled = EditorUserBuildSettings.activeBuildTarget != BuildTarget.WebGL;
if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.WebGL)
EditorGUILayout.IntField(threadCountLabel, 1);
else
EditorGUILayout.PropertyField(spPathfinderThreadCount, threadCountLabel);
GUI.enabled = true;
EditorGUILayout.PropertyField(spInitiateUpdateInterval);
EditorGUILayout.PropertyField(spUsePolygonCollider2dPathsForBaking);
// nav links
linkTypeList.DoLayoutList();
// nav segments
navSegmentTags.DoLayoutList();
// visualization
EditorGUILayout.PropertyField(spDrawUnselectedLinks);
EditorGUILayout.PropertyField(spDrawUnselectedSurfaces);
EditorGUILayout.PropertyField(spDrawUnselectedSubstractors);
EditorGUILayout.PropertyField(spDrawUnselectedAreaMarkers);
EditorGUILayout.PropertyField(spDrawGraphWhilePlaying);
EditorGUILayout.PropertyField(spNavSurfaceLineWidth);
EditorGUILayout.PropertyField(spNavAreaMarkerLineWidth);
if (EditorGUI.EndChangeCheck())
globalSettings.ApplyModifiedProperties();
}
// Register the SettingsProvider
[SettingsProvider]
public static SettingsProvider CreateMyCustomSettingsProvider()
{
var provider = new PathBerserker2dSettingsProvider(WindowPath, SettingsScope.Project);
provider.keywords = new string[] {
"NavLinkTypeNames"
};
return provider;
}
private void DrawLinkTypeListHeader(Rect rect)
{
EditorGUI.LabelField(rect, "Link Types");
}
private void DrawLinkTypeListItems(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty linkType = linkTypeList.serializedProperty.GetArrayElementAtIndex(index);
GUI.enabled = index >= PathBerserker2dSettings.buildinNavLinkTypeNames.Length;
float orgWidth = rect.width;
rect.width *= 0.7f;
EditorGUI.PropertyField(rect, linkType, new GUIContent("Type " + index));
GUI.enabled = true;
rect.x += rect.width + 5;
rect.width = orgWidth - rect.width - 5;
PathBerserker2dSettings.NavLinkTypeColors[index] = EditorGUI.ColorField(rect, PathBerserker2dSettings.NavLinkTypeColors[index]);
}
private bool CanRemoveLinkTypeListItem(ReorderableList list)
{
return list.index >= PathBerserker2dSettings.buildinNavLinkTypeNames.Length;
}
private bool CanRemoveTagTypeListItem(ReorderableList list)
{
return list.index > 0;
}
private void DrawSegmentTagListHeader(Rect rect)
{
EditorGUI.LabelField(rect, "Nav Tags");
}
private void DrawSegmentTagListItems(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty linkType = navSegmentTags.serializedProperty.GetArrayElementAtIndex(index);
float orgWidth = rect.width;
rect.width *= 0.7f;
EditorGUI.PropertyField(rect, linkType, new GUIContent("Tag " + index));
rect.x += rect.width + 5;
rect.width = orgWidth - rect.width - 5;
PathBerserker2dSettings.NavTagColors[index] = EditorGUI.ColorField(rect, PathBerserker2dSettings.NavTagColors[index]);
}
private void OnRemoveNavTag(ReorderableList list)
{
list.serializedProperty.DeleteArrayElementAtIndex(list.index);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 08eb9adc91334034ab02b597adb93a96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: fa81fd185b0d300408709db17ffe2a92
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,65 +0,0 @@
using UnityEngine;
using UnityEditor;
using System;
using System.Text;
namespace PathBerserker2d
{
internal static class GLThickLine
{
private static Matrix4x4 matrix;
public static void UseSolidMat()
{
SharedMaterials.UnlitVertexColorSolid.SetPass(0);
}
public static void UseTransparentMat()
{
SharedMaterials.UnlitVertexColorTransparent.SetPass(0);
}
public static void Begin(Matrix4x4 matrix)
{
GLThickLine.matrix = matrix;
GL.PushMatrix();
GL.Begin(GL.QUADS);
GL.LoadProjectionMatrix(Camera.current.projectionMatrix);
UseSolidMat();
}
public static void End()
{
GL.End();
GL.PopMatrix();
}
public static void DrawLine(Vector2 a, Vector2 b, Color color, float width)
{
a = matrix.MultiplyPoint3x4(a);
b = matrix.MultiplyPoint3x4(b);
Vector2 normal = new Vector2(-(b.y - a.y), b.x - a.x).normalized * width * 0.5f;
GL.Color(color);
GL.Vertex3(a.x - normal.x, a.y - normal.y, 0);
GL.Vertex3(a.x + normal.x, a.y + normal.y, 0);
GL.Vertex3(b.x + normal.x, b.y + normal.y, 0);
GL.Vertex3(b.x - normal.x, b.y - normal.y, 0);
}
public static void DrawRect(Vector3[] corners, Color color)
{
GL.Color(color);
GL.Vertex(matrix.MultiplyPoint3x4(corners[0]));
GL.Vertex(matrix.MultiplyPoint3x4(corners[1]));
GL.Vertex(matrix.MultiplyPoint3x4(corners[2]));
GL.Vertex(matrix.MultiplyPoint3x4(corners[3]));
}
public static string ToUpper(string str)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(str));
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7000cfcfa4d6eb34c98c7738b59e9de2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,217 +0,0 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace PathBerserker2d
{
class MiterLineMeshCreator
{
private List<Vector3> verts = new List<Vector3>();
private List<Color32> colors = new List<Color32>();
private List<int> tris = new List<int>();
public void CreateLine(Mesh mesh, IList<Vector2> linePoints, float thickness, Color line, Color32 corner, Color endpoint)
{
verts.Clear();
colors.Clear();
tris.Clear();
verts.Capacity = Math.Max(verts.Capacity, linePoints.Count * 2);
colors.Capacity = Math.Max(colors.Capacity, verts.Count);
tris.Capacity = Math.Max(tris.Capacity, linePoints.Count * 4);
Vector2 curTangent = (linePoints[0] - linePoints[1]).normalized;
Vector2 curNormal = new Vector2(-curTangent.y, curTangent.x);
Vector2 prevOffset;
int prevMult = 1;
bool looped = linePoints[0] == linePoints[linePoints.Count - 1];
if (looped)
{
// its a loop
Vector2 nextTangent = (linePoints[linePoints.Count - 2] - linePoints[linePoints.Count - 1]).normalized;
Vector2 nextNormal = new Vector2(-nextTangent.y, nextTangent.x);
var tangent2 = (nextTangent + curTangent).normalized;
var miter2 = new Vector2(-tangent2.y, tangent2.x);
float length2 = thickness / Vector2.Dot(nextNormal, miter2);
prevOffset = length2 * miter2;
prevMult = Math.Sign(Vector2.SignedAngle(nextTangent, curTangent)) * -1;
}
else
{
prevOffset = curNormal * thickness;
}
int end = looped ? linePoints.Count - 1 : linePoints.Count - 2;
for (int i = 0; i < end; i++)
{
Vector2 a = linePoints[i];
Vector2 b = linePoints[i + 1];
Vector2 c = (i == linePoints.Count - 2) ? linePoints[1] : linePoints[i + 2];
Vector2 nextTangent = (b - c).normalized;
Vector2 nextNormal = new Vector2(-nextTangent.y, nextTangent.x);
var tangent2 = (nextTangent + curTangent).normalized;
var miter2 = new Vector2(-tangent2.y, tangent2.x);
float length2 = thickness / Vector2.Dot(curNormal, miter2);
Vector2 offset2 = length2 * miter2;
verts.Add(a + prevOffset * prevMult);
verts.Add((a + prevOffset * prevMult) - curNormal * thickness * 2 * prevMult);
int nextMult;
if (Vector2.SignedAngle(curTangent, nextTangent) > 0)
{
if (prevMult == -1)
{
verts.Add(b - offset2);
verts.Add((b - offset2) + curNormal * thickness * 2);
}
else
{
verts.Add((b - offset2) + curNormal * thickness * 2);
verts.Add(b - offset2);
}
nextMult = -1;
}
else
{
if (prevMult == -1)
{
verts.Add((b + offset2) - curNormal * thickness * 2);
verts.Add(b + offset2);
}
else
{
verts.Add(b + offset2);
verts.Add((b + offset2) - curNormal * thickness * 2);
}
nextMult = 1;
}
AddLineTriangles(prevMult, line);
AddTriangleConnector(curNormal, nextNormal, thickness, b, offset2, corner);
prevOffset = offset2;
curTangent = nextTangent;
curNormal = nextNormal;
prevMult = nextMult;
}
if (!looped)
{
verts.Add(linePoints[linePoints.Count - 2] + prevOffset * prevMult);
verts.Add((linePoints[linePoints.Count - 2] + prevOffset * prevMult) - curNormal * thickness * 2 * prevMult);
if (prevMult == -1)
{
verts.Add(linePoints[linePoints.Count - 1] - curNormal * thickness);
verts.Add(linePoints[linePoints.Count - 1] + curNormal * thickness);
}
else
{
verts.Add(linePoints[linePoints.Count - 1] + curNormal * thickness);
verts.Add(linePoints[linePoints.Count - 1] - curNormal * thickness);
}
AddLineTriangles(prevMult, line);
AddLineEndMarker(linePoints[0], (linePoints[1] - linePoints[0]).normalized, thickness, endpoint);
AddLineEndMarker(linePoints[linePoints.Count -1], (linePoints[linePoints.Count - 2] - linePoints[linePoints.Count - 1]).normalized, thickness, endpoint);
}
mesh.triangles = new int[] { };
mesh.vertices = verts.ToArray();
mesh.triangles = tris.ToArray();
mesh.colors32 = colors.ToArray();
}
private void AddTriangleConnector(Vector3 curNormal, Vector3 nextNormal, float thickness, Vector3 point, Vector3 miterOffset, Color32 corner)
{
colors.Add(corner);
colors.Add(corner);
colors.Add(corner);
if (Vector2.SignedAngle(curNormal, nextNormal) > 0)
{
verts.Add((point - miterOffset) + nextNormal * thickness * 2);
verts.Add(point - miterOffset);
verts.Add((point - miterOffset) + curNormal * thickness * 2);
tris.Add(verts.Count - 3);
tris.Add(verts.Count - 1);
tris.Add(verts.Count - 2);
}
else
{
verts.Add((point + miterOffset) - nextNormal * thickness * 2);
verts.Add(point + miterOffset);
verts.Add((point + miterOffset) - curNormal * thickness * 2);
tris.Add(verts.Count - 2);
tris.Add(verts.Count - 1);
tris.Add(verts.Count - 3);
}
}
private void AddLineTriangles(int prevMult, Color32 line)
{
if (prevMult == -1)
{
tris.Add(colors.Count + 3);
tris.Add(colors.Count + 1);
tris.Add(colors.Count + 0);
tris.Add(colors.Count + 0);
tris.Add(colors.Count + 2);
tris.Add(colors.Count + 3);
}
else
{
tris.Add(colors.Count + 0);
tris.Add(colors.Count + 1);
tris.Add(colors.Count + 3);
tris.Add(colors.Count + 3);
tris.Add(colors.Count + 2);
tris.Add(colors.Count + 0);
}
colors.Add(line);
colors.Add(line);
colors.Add(line);
colors.Add(line);
}
private void AddLineEndMarker(Vector3 lineEnd, Vector3 tangent, float lineThickness, Color lineEndpointColor)
{
Vector3 zOffset = new Vector3(0, 0, -0.01f);
Vector3 normal = new Vector3(-tangent.y, tangent.x);
float increasedThickness = lineThickness * 1.25f * 2;
int index = verts.Count;
verts.Add(lineEnd + normal * increasedThickness + zOffset);
verts.Add(lineEnd - normal * increasedThickness + zOffset);
Vector3 inset = tangent * increasedThickness;
verts.Add(lineEnd + inset + normal * increasedThickness + zOffset);
verts.Add(lineEnd + inset - normal * increasedThickness + zOffset);
colors.Add(lineEndpointColor);
colors.Add(lineEndpointColor);
colors.Add(lineEndpointColor);
colors.Add(lineEndpointColor);
tris.Add(index + 1);
tris.Add(index);
tris.Add(index + 2);
tris.Add(index + 2);
tris.Add(index + 3);
tris.Add(index + 1);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2b5a87ab77b93854fa9469929abc0685
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,31 +0,0 @@
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
static class PrimitiveMesh
{
static Mesh quad;
public static Mesh Quad
{
get
{
if (quad == null)
{
quad = new Mesh();
quad.vertices = new Vector3[] {
new Vector3(0, 0, 0),
new Vector3(0, 1, 0),
new Vector3(1, 1, 0),
new Vector3(1, 0, 0)};
quad.triangles = new int[] {
0, 1, 2,
0, 2, 3};
}
return quad;
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 19bb1e8824aba9041b7cd3f4ad60d8e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEditor;
namespace PathBerserker2d
{
static class SharedMaterials
{
static Material unlitVertexColorSolid;
static Material unlitVertexColorTransparent;
static Material unlitStripped;
static Material unlitTransparentTinted;
static Material unlitTexture;
static int unlitTransparentTinted_ColorId;
static int unlitStripped_ColorId;
static int unlitStripped_XOffsetId;
static int unlitStripped_PauseSizeId;
static int unlitStripped_SegmentSizeId;
public static Material UnlitVertexColorSolid
{
get
{
if (unlitVertexColorSolid == null)
unlitVertexColorSolid = new Material(Shader.Find("Hidden/PB_UnlitVertexColor"));
return unlitVertexColorSolid;
}
}
public static Material UnlitVertexColorTransparent
{
get
{
if (unlitVertexColorTransparent == null)
unlitVertexColorTransparent = new Material(Shader.Find("Hidden/PB_UnlitVertexColorTransparent"));
return unlitVertexColorTransparent;
}
}
public static Material UnlitStripped
{
get
{
if (unlitStripped == null)
{
unlitStripped = new Material(Shader.Find("Hidden/PB_UnlitSegmented"));
unlitStripped.SetFloat("_SegmentSize", 0.21f);
unlitStripped.SetFloat("_PauseSize", 0.31f);
unlitStripped_ColorId = unlitStripped.shader.GetPropertyNameId(3);
unlitStripped_XOffsetId = unlitStripped.shader.GetPropertyNameId(2);
unlitStripped_PauseSizeId = unlitStripped.shader.GetPropertyNameId(1);
unlitStripped_SegmentSizeId = unlitStripped.shader.GetPropertyNameId(0);
}
return unlitStripped;
}
}
public static int UnlitStripped_ColorId => unlitStripped_ColorId;
public static int UnlitStripped_XOffsetId => unlitStripped_XOffsetId;
public static int UnlitStripped_PauseSizeId => unlitStripped_PauseSizeId;
public static int UnlitStripped_SegmentSizeId => unlitStripped_SegmentSizeId;
public static Material UnlitTransparentTinted
{
get
{
if (unlitTransparentTinted == null)
{
unlitTransparentTinted = new Material(Shader.Find("Hidden/PB_UnlitTransparentTinted"));
unlitTransparentTinted_ColorId = unlitTransparentTinted.shader.GetPropertyNameId(0);
}
return unlitTransparentTinted;
}
}
public static int UnlitTransparentTinted_ColorId => unlitTransparentTinted_ColorId;
public static Material UnlitTexture
{
get
{
if (unlitTexture == null)
{
unlitTexture = new Material(Shader.Find("Unlit/Texture"));
}
return unlitTexture;
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 75f31dc41b4fc7c46a13317cf739b246
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 524863b9605ccf542ad776b1873da2fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,93 +0,0 @@
using System;
using UnityEditor;
using UnityEngine;
namespace PathBerserker2d
{
internal class NavLinkCreatorWindow : EditorWindow
{
[MenuItem("Window/AI/NavLinkCreator")]
static void Init()
{
// Get existing open window or if none, make a new one:
NavLinkCreatorWindow window = (NavLinkCreatorWindow)EditorWindow.GetWindow(typeof(NavLinkCreatorWindow));
window.Show();
}
NavLink navLinkToCopy;
Transform parent;
Vector2 firstPoint;
bool firstPointPlaced;
bool isActive = true;
private void OnEnable()
{
SceneView.duringSceneGui -= SceneView_duringSceneGui;
SceneView.duringSceneGui += SceneView_duringSceneGui;
}
private void OnDisable()
{
SceneView.duringSceneGui -= SceneView_duringSceneGui;
}
void OnGUI()
{
MyGUI.Header("Link Settings");
parent = EditorGUILayout.ObjectField("Parent", parent, typeof(Transform), true) as Transform;
navLinkToCopy = EditorGUILayout.ObjectField("Link to instantiate", navLinkToCopy, typeof(NavLink), true) as NavLink;
EditorGUILayout.HelpBox("Left mouse button to place link node. Right mouse button to abort.", MessageType.Info);
isActive = EditorGUILayout.Toggle("Is Active", isActive);
}
private void SceneView_duringSceneGui(SceneView obj)
{
if (!isActive || navLinkToCopy == null)
return;
Event current = Event.current;
if (current.type == EventType.MouseDown)
{
if (current.button == 0)
{
if (!firstPointPlaced)
{
firstPoint = HandleUtility.GUIPointToWorldRay(current.mousePosition).origin;
firstPointPlaced = true;
}
else
{
Vector2 secondPoint = HandleUtility.GUIPointToWorldRay(current.mousePosition).origin;
var link = Instantiate(navLinkToCopy);
link.transform.parent = parent;
link.transform.position = firstPoint + (secondPoint - firstPoint) * 0.5f;
var ser = new SerializedObject(link);
ser.FindProperty("start").vector2Value = link.transform.InverseTransformPoint(firstPoint);
ser.FindProperty("goal").vector2Value = link.transform.InverseTransformPoint(secondPoint);
ser.FindProperty("bezierControlPoint").vector2Value = link.transform.InverseTransformPoint(link.transform.position + Vector3.up * 2);
ser.ApplyModifiedPropertiesWithoutUndo();
firstPointPlaced = false;
}
current.Use();
}
else if (current.button == 1)
{
firstPointPlaced = false;
current.Use();
}
}
if (firstPointPlaced)
{
Handles.DrawLine(firstPoint, HandleUtility.GUIPointToWorldRay(current.mousePosition).origin);
SceneView.RepaintAll();
}
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a74345800bbb4a44ab3646a5bceeca7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: fee4b89171754694689b0f4ac77ec981
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,62 +0,0 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace Assets.PathBerserker2d.Scripts.PathBerserker2d.Upgrade
{
static class DeleteObsoleteFiles
{
static string[] obsoleteFiles = new string[] {
"Corgi.zip",
"Editor/PathBerserker2d-Editor.dll",
"Editor/ShowAssetIds.cs",
"Editor/FootStepSoundsInspector.cs",
"Scripts/PathBerserker2d.dll",
"Scripts/PathBerserker2d.xml",
"Scripts/TransformBasedMovement.cs",
"Scripts/Examples/AdjustRotation.cs",
"Scripts/Examples/CornerRotationSkipper.cs",
"Scripts/Examples/Elevator.cs",
"Scripts/Examples/Follower.cs",
"Scripts/Examples/FootStepSounds.cs",
"Scripts/Examples/GoalWalker.cs",
"Scripts/Examples/KeepGrounded.cs",
"Scripts/Examples/MouseWalker.cs",
"Scripts/Examples/MovingPlatform.cs",
"Scripts/Examples/MultiGoalWalker.cs",
"Scripts/Examples/PatrolWalker.cs",
"Scripts/Examples/RandomWalker.cs",
};
static string[] obsoleteDirs = new string[] {
"Editor",
"Scripts/Examples"
};
public static void RemoveObsoleteFiles()
{
string basePath = Path.Combine(Application.dataPath, "PathBerserker2d");
foreach (var file in obsoleteFiles)
{
string path = Path.Combine(basePath, file);
if (File.Exists(path))
{
File.Delete(path);
Debug.Log("Deleted " + path);
}
}
foreach (var dir in obsoleteDirs)
{
string path = Path.Combine(basePath, dir);
if (Directory.Exists(path) && !Directory.EnumerateFiles(path).GetEnumerator().MoveNext())
{
Directory.Delete(path);
}
}
AssetDatabase.Refresh();
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 3bbdff88e6872d74d9dd37db0b6e9c12
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,198 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace Assets.PathBerserker2d.Scripts.PathBerserker2d.Upgrade
{
// Taken from http://www.superstarcoders.com/blogs/posts/md4-hash-algorithm-in-c-sharp.aspx
// Probably not the best implementation of MD4, but it works.
public class MD4 : HashAlgorithm
{
private uint _a;
private uint _b;
private uint _c;
private uint _d;
private uint[] _x;
private int _bytesProcessed;
public MD4()
{
_x = new uint[16];
Initialize();
}
public override void Initialize()
{
_a = 0x67452301;
_b = 0xefcdab89;
_c = 0x98badcfe;
_d = 0x10325476;
_bytesProcessed = 0;
}
protected override void HashCore(byte[] array, int offset, int length)
{
ProcessMessage(Bytes(array, offset, length));
}
protected override byte[] HashFinal()
{
try
{
ProcessMessage(Padding());
return new[] { _a, _b, _c, _d }.SelectMany(word => Bytes(word)).ToArray();
}
finally
{
Initialize();
}
}
private void ProcessMessage(IEnumerable<byte> bytes)
{
foreach (byte b in bytes)
{
int c = _bytesProcessed & 63;
int i = c >> 2;
int s = (c & 3) << 3;
_x[i] = (_x[i] & ~((uint)255 << s)) | ((uint)b << s);
if (c == 63)
{
Process16WordBlock();
}
_bytesProcessed++;
}
}
private static IEnumerable<byte> Bytes(byte[] bytes, int offset, int length)
{
for (int i = offset; i < length; i++)
{
yield return bytes[i];
}
}
private IEnumerable<byte> Bytes(uint word)
{
yield return (byte)(word & 255);
yield return (byte)((word >> 8) & 255);
yield return (byte)((word >> 16) & 255);
yield return (byte)((word >> 24) & 255);
}
private IEnumerable<byte> Repeat(byte value, int count)
{
for (int i = 0; i < count; i++)
{
yield return value;
}
}
private IEnumerable<byte> Padding()
{
return Repeat(128, 1)
.Concat(Repeat(0, ((_bytesProcessed + 8) & 0x7fffffc0) + 55 - _bytesProcessed))
.Concat(Bytes((uint)_bytesProcessed << 3))
.Concat(Repeat(0, 4));
}
private void Process16WordBlock()
{
uint aa = _a;
uint bb = _b;
uint cc = _c;
uint dd = _d;
foreach (int k in new[] { 0, 4, 8, 12 })
{
aa = Round1Operation(aa, bb, cc, dd, _x[k], 3);
dd = Round1Operation(dd, aa, bb, cc, _x[k + 1], 7);
cc = Round1Operation(cc, dd, aa, bb, _x[k + 2], 11);
bb = Round1Operation(bb, cc, dd, aa, _x[k + 3], 19);
}
foreach (int k in new[] { 0, 1, 2, 3 })
{
aa = Round2Operation(aa, bb, cc, dd, _x[k], 3);
dd = Round2Operation(dd, aa, bb, cc, _x[k + 4], 5);
cc = Round2Operation(cc, dd, aa, bb, _x[k + 8], 9);
bb = Round2Operation(bb, cc, dd, aa, _x[k + 12], 13);
}
foreach (int k in new[] { 0, 2, 1, 3 })
{
aa = Round3Operation(aa, bb, cc, dd, _x[k], 3);
dd = Round3Operation(dd, aa, bb, cc, _x[k + 8], 9);
cc = Round3Operation(cc, dd, aa, bb, _x[k + 4], 11);
bb = Round3Operation(bb, cc, dd, aa, _x[k + 12], 15);
}
unchecked
{
_a += aa;
_b += bb;
_c += cc;
_d += dd;
}
}
private static uint ROL(uint value, int numberOfBits)
{
return (value << numberOfBits) | (value >> (32 - numberOfBits));
}
private static uint Round1Operation(uint a, uint b, uint c, uint d, uint xk, int s)
{
unchecked
{
return ROL(a + ((b & c) | (~b & d)) + xk, s);
}
}
private static uint Round2Operation(uint a, uint b, uint c, uint d, uint xk, int s)
{
unchecked
{
return ROL(a + ((b & c) | (b & d) | (c & d)) + xk + 0x5a827999, s);
}
}
private static uint Round3Operation(uint a, uint b, uint c, uint d, uint xk, int s)
{
unchecked
{
return ROL(a + (b ^ c ^ d) + xk + 0x6ed9eba1, s);
}
}
}
public static class FileIDUtil
{
public static int Compute(Type t)
{
string toBeHashed = "s\0\0\0" + t.Namespace + t.Name;
using (HashAlgorithm hash = new MD4())
{
byte[] hashed = hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(toBeHashed));
int result = 0;
for (int i = 3; i >= 0; --i)
{
result <<= 8;
result |= hashed[i];
}
return result;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More