chore: initial commit
This commit is contained in:
8
Assets/Scripts/Enemies/AI.meta
Normal file
8
Assets/Scripts/Enemies/AI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 458bf9c7d1eae52438922d9630862656
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
0
Assets/Scripts/Enemies/AI/.gitkeep
Normal file
0
Assets/Scripts/Enemies/AI/.gitkeep
Normal file
31
Assets/Scripts/Enemies/AI/BD_Attack.cs
Normal file
31
Assets/Scripts/Enemies/AI/BD_Attack.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:发动攻击。
|
||||
/// CanAttack() 检查通过后调用 BeginAttack,立即返回 Success。
|
||||
/// </summary>
|
||||
public class BD_Attack : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || !_enemy.CanAttack())
|
||||
return TaskStatus.Failure;
|
||||
|
||||
_enemy.BeginAttack(AttackType.Melee);
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_Attack.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_Attack.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6129e08d8bc28a24ebfb02c246e2e744
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Assets/Scripts/Enemies/AI/BD_IsPlayerInRange.cs
Normal file
31
Assets/Scripts/Enemies/AI/BD_IsPlayerInRange.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:检查玩家是否在指定范围内。
|
||||
/// 成功/失败直接驱动 BT 分支选择(Selector / Sequence 节点)。
|
||||
/// </summary>
|
||||
public class BD_IsPlayerInRange : Conditional
|
||||
{
|
||||
/// <summary>检测范围(Inspector 可配置,默认 6 米)。</summary>
|
||||
public float Range = 6f;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
return _enemy.IsPlayerInRange(Range) ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsPlayerInRange.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsPlayerInRange.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d176aa20179c6b43a6798432eb073a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/Scripts/Enemies/AI/BD_MoveToPlayer.cs
Normal file
41
Assets/Scripts/Enemies/AI/BD_MoveToPlayer.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:移向玩家。
|
||||
/// OnStart 缓存玩家 Transform,OnUpdate 每帧更新导航目标。
|
||||
/// </summary>
|
||||
public class BD_MoveToPlayer : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
private Transform _playerTransform;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
var playerGO = GameObject.FindWithTag("Player");
|
||||
if (playerGO != null) _playerTransform = playerGO.transform;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
if (_playerTransform == null) return TaskStatus.Failure;
|
||||
|
||||
_enemy.MoveTo(_playerTransform.position);
|
||||
_enemy.FacePlayer();
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
_enemy?.StopMovement();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_MoveToPlayer.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_MoveToPlayer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67b2f5400ef71aa4fae183cf1ce1e649
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
35
Assets/Scripts/Enemies/AI/BD_Patrol.cs
Normal file
35
Assets/Scripts/Enemies/AI/BD_Patrol.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:敌人巡逻行为(Phase 1 简单实现)。
|
||||
/// 挂在 BT 节点上,持续令敌人向当前朝向移动。
|
||||
/// </summary>
|
||||
public class BD_Patrol : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
// Phase 1:固定向右巡逻;Phase 2 改为往返检测
|
||||
_enemy.MoveInDirection(1f);
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
_enemy?.StopMovement();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_Patrol.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_Patrol.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0fd85d7c7ce18f4193bb3b70707d055
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Scripts/Enemies/AI/BaseGames.Enemies.AI.asmdef
Normal file
17
Assets/Scripts/Enemies/AI/BaseGames.Enemies.AI.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Enemies.AI",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Enemies.AI",
|
||||
"references": [
|
||||
"BaseGames.Enemies",
|
||||
"Opsive.BehaviorDesigner.Runtime"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2f7ab9cf613d0c4fb44cdd94fea551f
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Scripts/Enemies/AI/_Placeholder.cs
Normal file
3
Assets/Scripts/Enemies/AI/_Placeholder.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
// Placeholder to prevent asmdef-no-scripts warning.
|
||||
namespace BaseGames.Enemies.AI { }
|
||||
|
||||
11
Assets/Scripts/Enemies/AI/_Placeholder.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/_Placeholder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e18876511cb4b9e4ab43cbce1215c72c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
Assets/Scripts/Enemies/BaseGames.Enemies.asmdef
Normal file
21
Assets/Scripts/Enemies/BaseGames.Enemies.asmdef
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Enemies",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Enemies",
|
||||
"references": [
|
||||
"BaseGames.Core",
|
||||
"BaseGames.Core.Events",
|
||||
"BaseGames.Combat",
|
||||
"BaseGames.Feedback",
|
||||
"MoreMountains.Tools",
|
||||
"Kybernetik.Animancer"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
7
Assets/Scripts/Enemies/BaseGames.Enemies.asmdef.meta
Normal file
7
Assets/Scripts/Enemies/BaseGames.Enemies.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ecc7c40216aa154f81faf49c6d655de
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Enemies/Boss.meta
Normal file
8
Assets/Scripts/Enemies/Boss.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe0b3fb5ffe55d84a86db3085bd868f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Enemies/Boss/Patterns.meta
Normal file
8
Assets/Scripts/Enemies/Boss/Patterns.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d8f5f23ee1dde046b1a7361ac1b6386
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
0
Assets/Scripts/Enemies/Boss/Patterns/.gitkeep
Normal file
0
Assets/Scripts/Enemies/Boss/Patterns/.gitkeep
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Enemies.Boss.Patterns",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Enemies.Boss.Patterns",
|
||||
"references": [
|
||||
"BaseGames.Enemies",
|
||||
"BaseGames.Combat"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bc3529e552a34a45998814c7cd056e6
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Scripts/Enemies/Boss/Patterns/_Placeholder.cs
Normal file
3
Assets/Scripts/Enemies/Boss/Patterns/_Placeholder.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
// Placeholder to prevent asmdef-no-scripts warning.
|
||||
namespace BaseGames.Enemies.Boss.Patterns { }
|
||||
|
||||
11
Assets/Scripts/Enemies/Boss/Patterns/_Placeholder.cs.meta
Normal file
11
Assets/Scripts/Enemies/Boss/Patterns/_Placeholder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17b436090127a5d4b88e51b077f2cdb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/Scripts/Enemies/EnemyAnimationConfigSO.cs
Normal file
24
Assets/Scripts/Enemies/EnemyAnimationConfigSO.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人动画配置 SO(架构 07_EnemyModule §5)。
|
||||
/// 所有字段为 AnimationClip,由 Animancer 直接播放。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Enemies/AnimationConfig")]
|
||||
public class EnemyAnimationConfigSO : ScriptableObject
|
||||
{
|
||||
[Header("基础")]
|
||||
public AnimationClip Idle;
|
||||
public AnimationClip Walk;
|
||||
public AnimationClip Run;
|
||||
|
||||
[Header("战斗")]
|
||||
public AnimationClip Attack;
|
||||
|
||||
[Header("受击")]
|
||||
public AnimationClip Hurt;
|
||||
public AnimationClip Dead;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyAnimationConfigSO.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyAnimationConfigSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7dd720bca19fcc49b22106fb65f7652
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
164
Assets/Scripts/Enemies/EnemyBase.cs
Normal file
164
Assets/Scripts/Enemies/EnemyBase.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using UnityEngine;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人基类(架构 07_EnemyModule §1)。
|
||||
/// 实现 IDamageable,为 Behavior Designer 任务提供统一虚方法接口。
|
||||
/// Phase 1 实现:完整骨架,BD 接口、受击、死亡流程。
|
||||
/// ⚠️ _nav 字段类型为 IPathAgent(在 BaseGames.Enemies.Navigation 中实现具体类)。
|
||||
/// </summary>
|
||||
public class EnemyBase : MonoBehaviour, IDamageable
|
||||
{
|
||||
[Header("配置 SO")]
|
||||
[SerializeField] protected EnemyStatsSO _statsSO;
|
||||
[SerializeField] protected EnemyAnimationConfigSO _animConfig;
|
||||
|
||||
[Header("子组件(Prefab Inspector 绑定)")]
|
||||
[SerializeField] protected EnemyStats _stats;
|
||||
[SerializeField] protected EnemyMovement _movement;
|
||||
[SerializeField] protected EnemyCombat _combat;
|
||||
[SerializeField] protected AnimancerComponent _animancer;
|
||||
[SerializeField] protected EnemyFeedback _feedback;
|
||||
[SerializeField] protected HurtBox _hurtBox;
|
||||
|
||||
[Header("事件频道")]
|
||||
[SerializeField] private BaseGames.Core.Events.TransformEventChannelSO _onEnemyDied;
|
||||
|
||||
// ── 导航代理(IPathAgent;由 EnemyNavAgent 实现)───────────────────
|
||||
// Phase 1:通过接口引用,避免对 Navigation 程序集的直接依赖。
|
||||
// 由子类 / Inspector 注入,或者运行时 GetComponent<IPathAgent>() 获取。
|
||||
protected IPathAgent _nav;
|
||||
|
||||
// ── 状态 ──────────────────────────────────────────────────────────
|
||||
private EnemyStateType _currentState;
|
||||
public EnemyStateType CurrentState => _currentState;
|
||||
|
||||
// ── IDamageable ───────────────────────────────────────────────────
|
||||
public bool IsInvincible => _currentState == EnemyStateType.Dead;
|
||||
public int Defense => _stats != null ? _stats.Defense : 0;
|
||||
|
||||
public void TakeDamage(DamageInfo info)
|
||||
{
|
||||
if (_currentState == EnemyStateType.Dead) return;
|
||||
|
||||
_stats?.TakeDamage(info.FinalDamage);
|
||||
_feedback?.OnHit(info);
|
||||
|
||||
if (_stats != null && _stats.CurrentHP <= 0)
|
||||
{
|
||||
Die();
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 2:根据霸体结果选 Stagger / Hurt
|
||||
ForceState(EnemyStateType.Hurt);
|
||||
}
|
||||
|
||||
// ── BD 行为树接口(虚方法)────────────────────────────────────────
|
||||
|
||||
public virtual void MoveTo(Vector2 target)
|
||||
=> _nav?.RequestMoveTo(target);
|
||||
|
||||
public virtual void MoveInDirection(float dir)
|
||||
=> _movement?.MoveHorizontal(dir);
|
||||
|
||||
public virtual void StopMovement()
|
||||
{
|
||||
_nav?.StopNavigation();
|
||||
_movement?.StopHorizontal();
|
||||
}
|
||||
|
||||
public virtual void BeginAttack(AttackType type)
|
||||
{
|
||||
_combat?.StartAttack(type);
|
||||
_stats?.ResetAttackCooldown();
|
||||
}
|
||||
|
||||
public virtual bool CanAttack()
|
||||
=> _stats != null && _stats.AttackCooldownTimer <= 0f;
|
||||
|
||||
public virtual bool IsPlayerInRange(float range)
|
||||
=> _stats != null && _stats.DistanceToPlayer <= range;
|
||||
|
||||
public virtual void FacePlayer()
|
||||
{
|
||||
if (_playerTransform != null)
|
||||
_movement?.FaceTarget(_playerTransform.position);
|
||||
}
|
||||
|
||||
public virtual void Knockback(DamageInfo info)
|
||||
{
|
||||
if (info.Flags.HasFlag(DamageFlags.NoKnockback)) return;
|
||||
_movement?.ApplyKnockback(info.KnockbackDirection, info.KnockbackForce);
|
||||
}
|
||||
|
||||
// ── 状态控制 ──────────────────────────────────────────────────────
|
||||
public void ForceState(EnemyStateType newState)
|
||||
{
|
||||
_currentState = newState;
|
||||
// Phase 2:根据状态播放对应动画 / 触发硬直计时
|
||||
}
|
||||
|
||||
// ── Unity 生命周期 ────────────────────────────────────────────────
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_nav = GetComponent<IPathAgent>() ?? new NullPathAgent();
|
||||
|
||||
if (_stats != null && _statsSO != null)
|
||||
_stats.Initialize(_statsSO);
|
||||
|
||||
// Phase 1:简单查找玩家;Phase 2 改为事件频道订阅
|
||||
var playerGO = GameObject.FindWithTag("Player");
|
||||
if (playerGO != null) _playerTransform = playerGO.transform;
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
_stats?.TickAttackCooldown(Time.deltaTime);
|
||||
|
||||
if (_playerTransform != null && _stats != null)
|
||||
_stats.DistanceToPlayer = Vector2.Distance(transform.position, _playerTransform.position);
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
// 播放 Idle 动画(若 Animancer 和配置都就绪)
|
||||
if (_animancer != null && _animConfig != null && _animConfig.Idle != null)
|
||||
_animancer.Play(_animConfig.Idle);
|
||||
}
|
||||
|
||||
// ── 内部 ──────────────────────────────────────────────────────────
|
||||
private Transform _playerTransform;
|
||||
|
||||
protected virtual void Die()
|
||||
{
|
||||
if (_currentState == EnemyStateType.Dead) return;
|
||||
ForceState(EnemyStateType.Dead);
|
||||
|
||||
// 禁用所有碰撞体
|
||||
foreach (var col in GetComponentsInChildren<Collider2D>())
|
||||
col.enabled = false;
|
||||
|
||||
// 播放死亡动画
|
||||
if (_animancer != null && _animConfig != null && _animConfig.Dead != null)
|
||||
{
|
||||
var state = _animancer.Play(_animConfig.Dead);
|
||||
state.Events(this).OnEnd = () => Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject, 1.5f);
|
||||
}
|
||||
|
||||
_feedback?.OnDeath();
|
||||
_onEnemyDied?.Raise(transform);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 枚举(架构 07 §1)────────────────────────────────────────────────
|
||||
public enum EnemyStateType { Controlled, Hurt, Stagger, Dead }
|
||||
public enum AttackType { Melee, Ranged, Special }
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyBase.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyBase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a2dbfbcc31a4c34cbd3ac893f02e07d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Assets/Scripts/Enemies/EnemyCombat.cs
Normal file
39
Assets/Scripts/Enemies/EnemyCombat.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Combat;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人战斗组件(Phase 1 桩,架构 07_EnemyModule §4)。
|
||||
/// Phase 2 实现:HitBox 按 AttackType 索引管理、伤害来源 SO 注入。
|
||||
/// </summary>
|
||||
public class EnemyCombat : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private HitBox[] _hitBoxes; // Inspector 按 AttackType 索引绑定
|
||||
|
||||
public void StartAttack(AttackType type)
|
||||
{
|
||||
// Phase 1 桩:Phase 2 播放攻击动画,由 AnimationEvent 触发 HitBox On/Off
|
||||
int idx = (int)type;
|
||||
EnableHitBox(idx);
|
||||
}
|
||||
|
||||
public void EnableHitBox(int index)
|
||||
{
|
||||
if (_hitBoxes == null || index >= _hitBoxes.Length) return;
|
||||
_hitBoxes[index]?.Activate();
|
||||
}
|
||||
|
||||
public void DisableHitBox(int index)
|
||||
{
|
||||
if (_hitBoxes == null || index >= _hitBoxes.Length) return;
|
||||
_hitBoxes[index]?.Deactivate();
|
||||
}
|
||||
|
||||
public void DisableAllHitBoxes()
|
||||
{
|
||||
if (_hitBoxes == null) return;
|
||||
foreach (var hb in _hitBoxes) hb?.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyCombat.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyCombat.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b092975777446a24ba295c6d30470935
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/Scripts/Enemies/EnemyFeedback.cs
Normal file
62
Assets/Scripts/Enemies/EnemyFeedback.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
using MoreMountains.Feedbacks;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Feedback;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人反馈播放器:实现 IFeedbackPlayer,将行为语义映射到 MMF_Player 实例。
|
||||
/// 同时保留 OnHit/OnDeath 语义方法供 EnemyBase 直接调用(转发给接口方法)。
|
||||
/// </summary>
|
||||
public class EnemyFeedback : MonoBehaviour, IFeedbackPlayer
|
||||
{
|
||||
[Header("命中反馈")]
|
||||
[SerializeField] private MMF_Player _onHitLight;
|
||||
[SerializeField] private MMF_Player _onHitMedium;
|
||||
[SerializeField] private MMF_Player _onHitHeavy;
|
||||
|
||||
[Header("受伤 / 死亡反馈")]
|
||||
[SerializeField] private MMF_Player _onTakeHit;
|
||||
[SerializeField] private MMF_Player _onDeath;
|
||||
|
||||
// ── IFeedbackPlayer ──────────────────────────────────────────────────────
|
||||
public void PlayHit(HitWeight weight)
|
||||
{
|
||||
var player = weight switch
|
||||
{
|
||||
HitWeight.Light => _onHitLight,
|
||||
HitWeight.Medium => _onHitMedium,
|
||||
HitWeight.Heavy => _onHitHeavy,
|
||||
_ => _onHitLight,
|
||||
};
|
||||
player?.PlayFeedbacks();
|
||||
}
|
||||
|
||||
public void PlayTakeHit() => _onTakeHit?.PlayFeedbacks();
|
||||
public void PlayDeath() => _onDeath?.PlayFeedbacks();
|
||||
|
||||
// 以下方法对敌人无意义,提供空实现保持接口完整
|
||||
public void PlayParrySuccess() { }
|
||||
public void PlayHeal() { }
|
||||
public void PlayLandImpact() { }
|
||||
public void PlayAttackWhoosh() { }
|
||||
public void PlayJumpLaunch() { }
|
||||
public void TriggerPreset(string presetId) { }
|
||||
public void PlaySFXById(string sfxId) { }
|
||||
|
||||
// ── EnemyBase 语义方法(向前兼容)────────────────────────────────────────
|
||||
/// <summary>受到伤害时调用(由 EnemyBase 触发)。</summary>
|
||||
public void OnHit(DamageInfo info)
|
||||
{
|
||||
PlayTakeHit();
|
||||
}
|
||||
|
||||
/// <summary>死亡时调用(由 EnemyBase 触发)。</summary>
|
||||
public void OnDeath()
|
||||
{
|
||||
PlayDeath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Scripts/Enemies/EnemyFeedback.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyFeedback.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9892874f77e34964092168ab0642a47c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Assets/Scripts/Enemies/EnemyMovement.cs
Normal file
67
Assets/Scripts/Enemies/EnemyMovement.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人移动组件(架构 07_EnemyModule §3)。
|
||||
/// Phase 1 实现:水平移动、面向目标、击退。
|
||||
/// ⚠️ 使用 Rigidbody2D.velocity(Unity 2022 LTS)。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
public class EnemyMovement : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private EnemyStatsSO _config;
|
||||
|
||||
private Rigidbody2D _rb;
|
||||
private int _facingDir = 1;
|
||||
|
||||
public bool IsGrounded { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_rb = GetComponent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
/// <summary>按 SO 配置速度水平移动。dir: +1 右 / -1 左 / 0 停止。</summary>
|
||||
public void MoveHorizontal(float dir)
|
||||
{
|
||||
if (_config == null) return;
|
||||
float speed = _config.WalkSpeed;
|
||||
_rb.velocity = new Vector2(dir * speed, _rb.velocity.y);
|
||||
UpdateFacing(dir);
|
||||
}
|
||||
|
||||
/// <summary>显式指定速度(BD 追击任务调用)。</summary>
|
||||
public void MoveWithSpeed(float dir, float speed)
|
||||
{
|
||||
_rb.velocity = new Vector2(dir * speed, _rb.velocity.y);
|
||||
UpdateFacing(dir);
|
||||
}
|
||||
|
||||
public void FaceTarget(Vector2 targetPos)
|
||||
{
|
||||
float dir = targetPos.x < transform.position.x ? -1f : 1f;
|
||||
UpdateFacing(dir);
|
||||
}
|
||||
|
||||
public void ApplyKnockback(Vector2 dir, float force)
|
||||
{
|
||||
_rb.velocity = dir.normalized * force;
|
||||
}
|
||||
|
||||
public void StopHorizontal()
|
||||
{
|
||||
_rb.velocity = new Vector2(0f, _rb.velocity.y);
|
||||
}
|
||||
|
||||
private void UpdateFacing(float dir)
|
||||
{
|
||||
if (Mathf.Approximately(dir, 0f)) return;
|
||||
int newDir = dir > 0f ? 1 : -1;
|
||||
if (newDir == _facingDir) return;
|
||||
_facingDir = newDir;
|
||||
Vector3 s = transform.localScale;
|
||||
transform.localScale = new Vector3(Mathf.Abs(s.x) * newDir, s.y, s.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyMovement.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyMovement.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20bd45717dc17a94581eee24814fe60c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/Scripts/Enemies/EnemyStats.cs
Normal file
45
Assets/Scripts/Enemies/EnemyStats.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人运行时数值组件(架构 07_EnemyModule §2)。
|
||||
/// 由 EnemyBase.Awake() 通过 Initialize(EnemyStatsSO) 注入配置。
|
||||
/// </summary>
|
||||
public class EnemyStats : MonoBehaviour
|
||||
{
|
||||
private EnemyStatsSO _config;
|
||||
|
||||
public int MaxHP { get; private set; }
|
||||
public int CurrentHP { get; private set; }
|
||||
public int Defense { get; private set; }
|
||||
public float AttackCooldownTimer { get; private set; }
|
||||
|
||||
/// <summary>每帧由 EnemyBase 更新(读取玩家位置后写入)。</summary>
|
||||
public float DistanceToPlayer { get; set; }
|
||||
|
||||
public void Initialize(EnemyStatsSO so)
|
||||
{
|
||||
_config = so;
|
||||
MaxHP = so.MaxHP;
|
||||
CurrentHP = so.MaxHP;
|
||||
Defense = so.Defense;
|
||||
}
|
||||
|
||||
public void TakeDamage(int amount)
|
||||
{
|
||||
CurrentHP = Mathf.Max(0, CurrentHP - amount);
|
||||
}
|
||||
|
||||
public void TickAttackCooldown(float dt)
|
||||
{
|
||||
if (AttackCooldownTimer > 0f)
|
||||
AttackCooldownTimer -= dt;
|
||||
}
|
||||
|
||||
public void ResetAttackCooldown()
|
||||
{
|
||||
AttackCooldownTimer = _config != null ? _config.AttackCooldown : 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyStats.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyStats.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48bc7c82cd2c1df4ba7103160db48a11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Assets/Scripts/Enemies/EnemyStatsSO.cs
Normal file
31
Assets/Scripts/Enemies/EnemyStatsSO.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人属性配置 SO(架构 07_EnemyModule §2)。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Enemies/EnemyStats")]
|
||||
public class EnemyStatsSO : ScriptableObject
|
||||
{
|
||||
[Header("生命")]
|
||||
public int MaxHP = 50;
|
||||
|
||||
[Header("防御")]
|
||||
public int Defense = 0;
|
||||
|
||||
[Header("移动")]
|
||||
public float WalkSpeed = 2f;
|
||||
public float RunSpeed = 4f;
|
||||
|
||||
[Header("战斗")]
|
||||
public int AttackDamage = 10;
|
||||
public float AttackRange = 1.5f;
|
||||
public float AttackCooldown = 1f;
|
||||
public float DetectRange = 6f;
|
||||
|
||||
[Header("击退(作为来源时)")]
|
||||
public float KnockbackForce = 5f;
|
||||
public float HitStunDuration = 0.3f;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/EnemyStatsSO.cs.meta
Normal file
11
Assets/Scripts/Enemies/EnemyStatsSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed4391dfa14c0304c8932f1ef9f8ce63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/Scripts/Enemies/IPathAgent.cs
Normal file
42
Assets/Scripts/Enemies/IPathAgent.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// 导航代理抽象接口(架构 07_EnemyModule §5)。
|
||||
/// EnemyBase 和 BD Task 只依赖此接口,不依赖具体导航库。
|
||||
/// 实现:EnemyNavAgent(PathBerserker2d);测试用 NullPathAgent。
|
||||
/// </summary>
|
||||
public interface IPathAgent
|
||||
{
|
||||
/// <summary>请求移动到世界坐标 target。</summary>
|
||||
void RequestMoveTo(Vector2 target);
|
||||
|
||||
/// <summary>立即停止导航(清除路径)。</summary>
|
||||
void StopNavigation();
|
||||
|
||||
/// <summary>是否已到达目标(距离 ≤ stoppingDistance)。</summary>
|
||||
bool IsAtDestination();
|
||||
|
||||
/// <summary>运行时覆盖移动速度。</summary>
|
||||
void SetSpeed(float speed);
|
||||
|
||||
/// <summary>当前帧是否在移动(速度 > 0.01 且有有效路径)。</summary>
|
||||
bool IsMoving { get; }
|
||||
|
||||
/// <summary>路径寻路失败事件(目标不可达时触发)。</summary>
|
||||
event Action OnNavPathFailed;
|
||||
}
|
||||
|
||||
// ── 无导航 / 测试用空实现 ─────────────────────────────────────────────
|
||||
public sealed class NullPathAgent : IPathAgent
|
||||
{
|
||||
public void RequestMoveTo(Vector2 _) { }
|
||||
public void StopNavigation() { }
|
||||
public bool IsAtDestination() => true;
|
||||
public void SetSpeed(float _) { }
|
||||
public bool IsMoving => false;
|
||||
public event Action OnNavPathFailed { add { } remove { } }
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/IPathAgent.cs.meta
Normal file
11
Assets/Scripts/Enemies/IPathAgent.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99f648f6975de20469192afe7fcc4e0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Enemies/Navigation.meta
Normal file
8
Assets/Scripts/Enemies/Navigation.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f941b7883c1fd624387991a1d0edd709
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
0
Assets/Scripts/Enemies/Navigation/.gitkeep
Normal file
0
Assets/Scripts/Enemies/Navigation/.gitkeep
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Enemies.Navigation",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Enemies.Navigation",
|
||||
"references": [
|
||||
"BaseGames.Enemies",
|
||||
"PathBerserker2d"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9c0925f2c3786343b319ae4706ebf20
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/Scripts/Enemies/Navigation/EnemyNavAgent.cs
Normal file
53
Assets/Scripts/Enemies/Navigation/EnemyNavAgent.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
using PathBerserker2d;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// PathBerserker2d 导航代理包装器(架构 07_EnemyModule §5)。
|
||||
/// 实现 IPathAgent 接口,使 EnemyBase 和 BD Task 无需直接依赖 PB2d 类型。
|
||||
/// PB2d API:UpdatePath(Vector2)、Stop()、TransformBasedMovement.movementSpeed、IsFollowingAPath。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(NavAgent))]
|
||||
[RequireComponent(typeof(TransformBasedMovement))]
|
||||
public class EnemyNavAgent : MonoBehaviour, IPathAgent
|
||||
{
|
||||
private NavAgent _navAgent;
|
||||
private TransformBasedMovement _movement;
|
||||
|
||||
/// <summary>正在沿路径移动时为 true。</summary>
|
||||
public bool IsMoving => _navAgent != null && _navAgent.IsFollowingAPath;
|
||||
|
||||
public event System.Action OnNavPathFailed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_navAgent = GetComponent<NavAgent>();
|
||||
_movement = GetComponent<TransformBasedMovement>();
|
||||
}
|
||||
|
||||
public void RequestMoveTo(Vector2 target)
|
||||
{
|
||||
_navAgent?.UpdatePath(target);
|
||||
}
|
||||
|
||||
public void StopNavigation()
|
||||
{
|
||||
_navAgent?.Stop();
|
||||
}
|
||||
|
||||
public bool IsAtDestination()
|
||||
{
|
||||
if (_navAgent == null) return true;
|
||||
// 已停止 OR 在目标线段上且不再跟随路径
|
||||
return _navAgent.IsIdle;
|
||||
}
|
||||
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
if (_movement != null) _movement.movementSpeed = speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Scripts/Enemies/Navigation/EnemyNavAgent.cs.meta
Normal file
11
Assets/Scripts/Enemies/Navigation/EnemyNavAgent.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44871319d7318de40b9ac21757b69c78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Scripts/Enemies/Navigation/_Placeholder.cs
Normal file
3
Assets/Scripts/Enemies/Navigation/_Placeholder.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
// Placeholder to prevent asmdef-no-scripts warning.
|
||||
namespace BaseGames.Enemies.Navigation { }
|
||||
|
||||
11
Assets/Scripts/Enemies/Navigation/_Placeholder.cs.meta
Normal file
11
Assets/Scripts/Enemies/Navigation/_Placeholder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d27858cf5a8d193489516e0cc5bcc39a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Scripts/Enemies/_Placeholder.cs
Normal file
3
Assets/Scripts/Enemies/_Placeholder.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
// Placeholder to prevent asmdef-no-scripts warning.
|
||||
namespace BaseGames.Enemies { }
|
||||
|
||||
11
Assets/Scripts/Enemies/_Placeholder.cs.meta
Normal file
11
Assets/Scripts/Enemies/_Placeholder.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2f632c03ad8b734e953d5fce3d5b048
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user