第一批重构,主要是物理迁移脚本和部分命名空间的调整
- 结构上迁移文件,将脚本分成 Base/Runtime/Presentation/Procedure/Editor 五大文件夹 - 调整 DataTable 和 Definition 下脚本的命名空间 - 依据 UI-5层架构设计规范 拆分原先的 UI 到 Runtime 和 Presentation 下
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58efe0589d7e90f498c8bcbbf94c6be4
|
||||
folderAsset: yes
|
||||
timeCreated: 1528026151
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
using StarForce;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class AccessoryObjectData : EntityDataBase
|
||||
{
|
||||
[SerializeField]
|
||||
private int m_OwnerId = 0;
|
||||
|
||||
[SerializeField]
|
||||
private CampType m_OwnerCamp = CampType.Unknown;
|
||||
|
||||
public AccessoryObjectData(int entityId, int typeId, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, typeId)
|
||||
{
|
||||
m_OwnerId = ownerId;
|
||||
m_OwnerCamp = ownerCamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拥有者编号。
|
||||
/// </summary>
|
||||
public int OwnerId
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_OwnerId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拥有者阵营。
|
||||
/// </summary>
|
||||
public CampType OwnerCamp
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_OwnerCamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83cb841b7411a844581691d5547bf866
|
||||
timeCreated: 1528026151
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
public class CoinData : EntityDataBase
|
||||
{
|
||||
private int _value;
|
||||
|
||||
public CoinData(int value, int entityId, int typeId) : base(entityId, typeId)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get => _value;
|
||||
set => _value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9caee22d67a42018113acc77155d6ba
|
||||
timeCreated: 1770292597
|
||||
@@ -0,0 +1,33 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class EffectData : EntityDataBase
|
||||
{
|
||||
[SerializeField]
|
||||
private float m_KeepTime = 0f;
|
||||
|
||||
public EffectData(int entityId, int typeId)
|
||||
: base(entityId, typeId)
|
||||
{
|
||||
m_KeepTime = 3f;
|
||||
}
|
||||
|
||||
public float KeepTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_KeepTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5b93dcbe30421a429c196bda0af31ed
|
||||
timeCreated: 1528026152
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 611437bd292819b439512f684e13c7e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.DataTable;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class EnemyData : TargetableObjectData
|
||||
{
|
||||
[SerializeField] private DREnemy _drEnemy;
|
||||
|
||||
public EnemyData(int entityId, EnemyType enemyType, int level) : base(
|
||||
entityId, (int)enemyType, CampType.Enemy)
|
||||
{
|
||||
DREnemy enemyRow = GameEntry.DataTable.GetDataTableRow<DREnemy>((int)enemyType);
|
||||
|
||||
if (enemyRow == null)
|
||||
{
|
||||
throw new Exception($"Enemy data table row is missing, EnemyType='{enemyType}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_drEnemy = enemyRow;
|
||||
}
|
||||
|
||||
int effectiveLevel = Mathf.Max(1, level);
|
||||
MaxHealthBase = enemyRow.MaxHealth + enemyRow.HpAddPerLevel * (effectiveLevel - 1);
|
||||
}
|
||||
|
||||
public EnemyType EnemyType => (EnemyType)_drEnemy.Id;
|
||||
|
||||
public int EntityTypeId => _drEnemy.EntityTypeId;
|
||||
|
||||
public override int MaxHealthBase { get; }
|
||||
|
||||
public int AttackDamage => _drEnemy.AttackDamage;
|
||||
|
||||
public float AttackCooldown => _drEnemy.AttackCooldown;
|
||||
|
||||
public float AttackRange => _drEnemy.AttackRange;
|
||||
|
||||
public float SpeedBase => _drEnemy.Speed;
|
||||
|
||||
public int DropCoin => _drEnemy.DropCoin;
|
||||
|
||||
public int DropExp => _drEnemy.DropExp;
|
||||
|
||||
public float DropPercent => _drEnemy.DropPercent;
|
||||
|
||||
public IReadOnlyDictionary<string, string> Params => _drEnemy.Params;
|
||||
|
||||
public bool TryGetParam(string key, out string value)
|
||||
{
|
||||
value = null;
|
||||
if (string.IsNullOrEmpty(key) || _drEnemy?.Params == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _drEnemy.Params.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b802c30cc274e04929b8659ec631e84
|
||||
timeCreated: 1768890974
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class EnemyProjectileData : EntityDataBase
|
||||
{
|
||||
public EnemyProjectileData(int entityId, int ownerEntityId, CampType ownerCamp, int attackDamage,
|
||||
float speed, float lifeTime, Vector3 direction)
|
||||
: base(entityId, 0)
|
||||
{
|
||||
OwnerEntityId = ownerEntityId;
|
||||
OwnerCamp = ownerCamp;
|
||||
AttackDamage = attackDamage;
|
||||
Speed = speed;
|
||||
LifeTime = lifeTime;
|
||||
Direction = direction;
|
||||
}
|
||||
|
||||
public int OwnerEntityId { get; }
|
||||
public CampType OwnerCamp { get; }
|
||||
public int AttackDamage { get; }
|
||||
public float Speed { get; }
|
||||
public float LifeTime { get; }
|
||||
public Vector3 Direction { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52b38f83ab6c4029803d40c189db47c7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class EntityDataBase
|
||||
{
|
||||
[SerializeField]
|
||||
private int m_Id = 0;
|
||||
|
||||
[SerializeField]
|
||||
private int m_TypeId = 0;
|
||||
|
||||
[SerializeField]
|
||||
private Vector3 m_Position = Vector3.zero;
|
||||
|
||||
[SerializeField]
|
||||
private Quaternion m_Rotation = Quaternion.identity;
|
||||
|
||||
public EntityDataBase(int entityId, int typeId)
|
||||
{
|
||||
m_Id = entityId;
|
||||
m_TypeId = typeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体编号。
|
||||
/// </summary>
|
||||
public int Id => m_Id;
|
||||
|
||||
/// <summary>
|
||||
/// 实体类型编号。
|
||||
/// </summary>
|
||||
public int TypeId => m_TypeId;
|
||||
|
||||
/// <summary>
|
||||
/// 实体位置。
|
||||
/// </summary>
|
||||
public Vector3 Position
|
||||
{
|
||||
get => m_Position;
|
||||
set => m_Position = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体朝向。
|
||||
/// </summary>
|
||||
public Quaternion Rotation
|
||||
{
|
||||
get => m_Rotation;
|
||||
set => m_Rotation = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afb91fe3ed8105e4a96b7bd5917ecc2b
|
||||
timeCreated: 1528026152
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
public class ExpData : EntityDataBase
|
||||
{
|
||||
private int _value;
|
||||
|
||||
public ExpData(int value, int entityId, int typeId) : base(entityId, typeId)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get => _value;
|
||||
set => _value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0f6c8e117da49f1ad04fd8810882043
|
||||
timeCreated: 1770292932
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using SepCore.DataTable;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class PlayerData : TargetableObjectData
|
||||
{
|
||||
private int _maxExp;
|
||||
|
||||
public PlayerData(int entityId, int typeId) : base(entityId, typeId, CampType.Player)
|
||||
{
|
||||
MaxHealthBase = MaxHealthBase;
|
||||
}
|
||||
|
||||
public override int MaxHealthBase { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b69d3168ac4f442994f8dc4d906daa60
|
||||
timeCreated: 1768890638
|
||||
@@ -0,0 +1,40 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
using StarForce;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class TargetableObjectData : EntityDataBase
|
||||
{
|
||||
[SerializeField]
|
||||
private CampType _camp = CampType.Unknown;
|
||||
|
||||
public TargetableObjectData(int entityId, int typeId, CampType camp)
|
||||
: base(entityId, typeId)
|
||||
{
|
||||
_camp = camp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色阵营。
|
||||
/// </summary>
|
||||
public CampType Camp => _camp;
|
||||
|
||||
/// <summary>
|
||||
/// 最大生命。
|
||||
/// </summary>
|
||||
public abstract int MaxHealthBase
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f866c15cf3f2a45429251d4a66f70c77
|
||||
timeCreated: 1528026152
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8b2591453b7f0841940c8e45528f465
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.DataTable;
|
||||
using SepCore.Definition;
|
||||
using GameFramework;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public class WeaponData : AccessoryObjectData
|
||||
{
|
||||
private DRWeapon _drWeapon;
|
||||
|
||||
private int _entityTypeId = 0;
|
||||
|
||||
public WeaponData(int entityId, WeaponType weaponType, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, (int)weaponType, ownerId, ownerCamp)
|
||||
{
|
||||
_drWeapon = GameEntry.DataTable.GetDataTableRow<DRWeapon>((int)weaponType);
|
||||
|
||||
if (_drWeapon == null)
|
||||
{
|
||||
throw new Exception($"Weapon data table row is missing, WeaponType='{weaponType}'.");
|
||||
}
|
||||
|
||||
_entityTypeId = _drWeapon.EntityTypeId;
|
||||
}
|
||||
|
||||
public WeaponType WeaponType => (WeaponType)_drWeapon.Id;
|
||||
|
||||
public bool TryGetParam(string key, out string value)
|
||||
{
|
||||
value = null;
|
||||
if (string.IsNullOrEmpty(key) || Params == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Params.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
protected TParams ParseParams<TParams>() where TParams : new()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_drWeapon.ParamsJson))
|
||||
{
|
||||
return new TParams();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TParams parsed = Utility.Json.ToObject<TParams>(_drWeapon.ParamsJson);
|
||||
return parsed ?? new TParams();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Failed to parse weapon params, WeaponType='{WeaponType}', Json='{_drWeapon.ParamsJson}'.",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 攻击力。
|
||||
/// </summary>
|
||||
public int Attack => _drWeapon.Attack;
|
||||
|
||||
public int EntityTypeId => _entityTypeId;
|
||||
|
||||
/// <summary>
|
||||
/// 武器名称。
|
||||
/// </summary>
|
||||
public string Title => _drWeapon.Title;
|
||||
|
||||
/// <summary>
|
||||
/// 图标资源名称。
|
||||
/// </summary>
|
||||
public string IconAssetName => _drWeapon.IconAssetName;
|
||||
|
||||
public ItemRarity Rarity => _drWeapon.Rarity;
|
||||
|
||||
public int Price => _drWeapon.Price;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击间隔。
|
||||
/// </summary>
|
||||
public float Cooldown => _drWeapon.Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击范围。
|
||||
/// </summary>
|
||||
public float AttackRange => _drWeapon.AttackRange;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击音效。
|
||||
/// </summary>
|
||||
public int AttackSoundId => _drWeapon.AttackSoundId;
|
||||
|
||||
/// <summary>
|
||||
/// 额外参数。
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Params => _drWeapon.Pramas;
|
||||
|
||||
public string ParamsJson => _drWeapon.ParamsJson;
|
||||
|
||||
/// <summary>
|
||||
/// 额外属性。
|
||||
/// </summary>
|
||||
public StatModifier[] Modifiers => _drWeapon.Modifiers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e813e3219579f834d8e619c17a8083d4
|
||||
timeCreated: 1528026153
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponHandgunParamsData
|
||||
{
|
||||
}
|
||||
|
||||
public class WeaponHandgunData : WeaponData
|
||||
{
|
||||
public WeaponHandgunParamsData ParamsData { get; }
|
||||
|
||||
public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp)
|
||||
{
|
||||
ParamsData = ParseParams<WeaponHandgunParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 574d105bf89bf7546a682ed407708034
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponKnifeParamsData
|
||||
{
|
||||
public float HitRadius { get; set; }
|
||||
}
|
||||
|
||||
public class WeaponKnifeData : WeaponData
|
||||
{
|
||||
public WeaponKnifeParamsData ParamsData { get; }
|
||||
|
||||
public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife,
|
||||
ownerId, ownerCamp)
|
||||
{
|
||||
ParamsData = ParseParams<WeaponKnifeParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da25d8e0669f0194d8f56f4283c67b38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponLanceParamsData
|
||||
{
|
||||
/// <summary>
|
||||
/// 横向半宽,表示前戳矩形判定的一半宽度。
|
||||
/// </summary>
|
||||
public float HitHalfWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 旧字段兼容,未配置 HitHalfWidth 时回退使用。
|
||||
/// </summary>
|
||||
public float HitRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前戳判定盒体的总高度。
|
||||
/// </summary>
|
||||
public float HitHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定盒体中心相对战斗平面的高度偏移。
|
||||
/// </summary>
|
||||
public float HitCenterYOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前刺距离,同时驱动武器位移和命中长度。
|
||||
/// </summary>
|
||||
public float PierceLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 旧字段兼容,未配置 PierceLength 时回退使用。
|
||||
/// </summary>
|
||||
public float ThrustDistance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 判定起点相对武器当前位置的前置偏移。
|
||||
/// </summary>
|
||||
public float ForwardOffset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 追踪目标时的转向速度。
|
||||
/// </summary>
|
||||
public float RotateSpeed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 向前突刺阶段耗时。
|
||||
/// </summary>
|
||||
public float AttackDuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 收枪返回阶段耗时。
|
||||
/// </summary>
|
||||
public float ReturnDuration { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WeaponLanceData : WeaponData
|
||||
{
|
||||
public WeaponLanceParamsData ParamsData { get; }
|
||||
|
||||
public WeaponLanceData(int entityId, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, WeaponType.WeaponLance, ownerId, ownerCamp)
|
||||
{
|
||||
ParamsData = ParseParams<WeaponLanceParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d1a821e8ee1a9a4b912b70b0a1616eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponLightningParamsData
|
||||
{
|
||||
public float HitRadius { get; set; }
|
||||
public float HoverHeight { get; set; }
|
||||
}
|
||||
|
||||
public class WeaponLightningData : WeaponData
|
||||
{
|
||||
public WeaponLightningParamsData ParamsData { get; }
|
||||
|
||||
public WeaponLightningData(int entityId, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, WeaponType.WeaponLightning, ownerId, ownerCamp)
|
||||
{
|
||||
ParamsData = ParseParams<WeaponLightningParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25b0006918fd46959c7f6b8ec1bbc8ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace Entity.EntityData
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class WeaponSlashParamsData
|
||||
{
|
||||
public float SectorAngle { get; set; }
|
||||
}
|
||||
|
||||
public class WeaponSlashData : WeaponData
|
||||
{
|
||||
public WeaponSlashParamsData ParamsData { get; }
|
||||
|
||||
public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp)
|
||||
: base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp)
|
||||
{
|
||||
ParamsData = ParseParams<WeaponSlashParamsData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4aa6473525dc4c62aeee02ff562ec453
|
||||
timeCreated: 1771484661
|
||||
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using UnityGameFramework.Runtime;
|
||||
using SepCore.DataTable;
|
||||
using CustomUtility;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public static class EntityExtension
|
||||
{
|
||||
private static int s_SerialId = -10;
|
||||
|
||||
private const string EntityNamespace = "Entity.";
|
||||
private static readonly Dictionary<string, Type> _typeDict;
|
||||
private static readonly Dictionary<int, string> _assetNameDict;
|
||||
|
||||
static EntityExtension()
|
||||
{
|
||||
_typeDict = new Dictionary<string, Type>();
|
||||
_assetNameDict = new Dictionary<int, string>();
|
||||
}
|
||||
|
||||
public static EntityBase GetGameEntity(this EntityComponent entityComponent, int entityId)
|
||||
{
|
||||
UnityGameFramework.Runtime.Entity entity = entityComponent.GetEntity(entityId);
|
||||
if (entity == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (EntityBase)entity.Logic;
|
||||
}
|
||||
|
||||
public static void HideEntity(this EntityComponent entityComponent, EntityBase entity)
|
||||
{
|
||||
entityComponent.HideEntity(entity.Entity);
|
||||
}
|
||||
|
||||
public static void AttachEntity(this EntityComponent entityComponent, EntityBase entityBase, int ownerId,
|
||||
string parentTransformPath = null, object userData = null)
|
||||
{
|
||||
entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData);
|
||||
}
|
||||
|
||||
public static void ShowPlayer(this EntityComponent entityComponent, PlayerData data)
|
||||
{
|
||||
entityComponent.ShowEntity(typeof(Player), "Player", Constant.AssetPriority.PlayerAsset, data);
|
||||
}
|
||||
|
||||
public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Log.Warning("Enemy data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var enemyType = TryGetType(data.EnemyType.ToString());
|
||||
var assetName = TryGetAssetName(data.EntityTypeId);
|
||||
|
||||
entityComponent.ShowEntity(
|
||||
entityId: data.Id,
|
||||
entityLogicType: enemyType,
|
||||
entityAssetName: AssetUtility.GetEntityAsset(assetName),
|
||||
entityGroupName: "Enemy",
|
||||
priority: Constant.AssetPriority.BulletAsset,
|
||||
userData: data);
|
||||
}
|
||||
|
||||
public static void ShowWeapon(this EntityComponent entityComponent, WeaponData data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Log.Warning("Weapon data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var weaponType = TryGetType("Weapon." + data.WeaponType);
|
||||
var assetName = TryGetAssetName(data.EntityTypeId);
|
||||
|
||||
entityComponent.ShowEntity(
|
||||
entityId: data.Id,
|
||||
entityLogicType: weaponType,
|
||||
entityAssetName: AssetUtility.GetEntityAsset(assetName),
|
||||
entityGroupName: "Weapon",
|
||||
priority: Constant.AssetPriority.BulletAsset,
|
||||
userData: data);
|
||||
}
|
||||
|
||||
// public static void ShowBullet(this EntityComponent entityComponent, BulletData data)
|
||||
// {
|
||||
// if (data == null)
|
||||
// {
|
||||
// Log.Warning("Bullet data is invalid.");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// var bulletType = TryGetType(data.BulletType.ToString());
|
||||
// string assetName = TryGetAssetName(data.EntityTypeId);
|
||||
//
|
||||
// entityComponent.ShowEntity(
|
||||
// entityId: data.Id,
|
||||
// entityLogicType: bulletType,
|
||||
// entityAssetName: AssetUtility.GetEntityAsset(assetName),
|
||||
// entityGroupName: "Bullet",
|
||||
// priority: Constant.AssetPriority.BulletAsset,
|
||||
// userData: data);
|
||||
// }
|
||||
|
||||
public static void ShowEffect(this EntityComponent entityComponent, EffectData data)
|
||||
{
|
||||
entityComponent.ShowEntity(typeof(Effect), "Effect", Constant.AssetPriority.EffectAsset, data);
|
||||
}
|
||||
|
||||
public static void ShowCoin(this EntityComponent entityComponent, CoinData data)
|
||||
{
|
||||
entityComponent.ShowEntity(typeof(CoinEntity), "Drop", Constant.AssetPriority.EffectAsset, data);
|
||||
}
|
||||
|
||||
public static void ShowExp(this EntityComponent entityComponent, ExpData data)
|
||||
{
|
||||
entityComponent.ShowEntity(typeof(ExpEntity), "Drop", Constant.AssetPriority.EffectAsset, data);
|
||||
}
|
||||
|
||||
private static void ShowEntity(this EntityComponent entityComponent, Type logicType, string entityGroup,
|
||||
int priority, EntityDataBase data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
Log.Warning("Data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
DREntity drEntity = GameEntry.DataTable.GetDataTableRow<DREntity>(data.TypeId);
|
||||
if (drEntity == null)
|
||||
{
|
||||
Log.Warning("Can not load entity id '{0}' from data table.", data.TypeId.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
entityComponent.ShowEntity(data.Id, logicType, AssetUtility.GetEntityAsset(drEntity.AssetName), entityGroup,
|
||||
priority, data);
|
||||
}
|
||||
|
||||
private static Type TryGetType(string rawTypeName)
|
||||
{
|
||||
string typeName = EntityNamespace + rawTypeName;
|
||||
if (!_typeDict.TryGetValue(typeName, out Type type))
|
||||
{
|
||||
type = Type.GetType(typeName);
|
||||
if (type == null)
|
||||
{
|
||||
Log.Warning("Can not load entity type '{0}'.", typeName);
|
||||
return null;
|
||||
}
|
||||
|
||||
_typeDict.Add(typeName, type);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static string TryGetAssetName(int entityId)
|
||||
{
|
||||
if (!_assetNameDict.TryGetValue(entityId, out string assetName))
|
||||
{
|
||||
DREntity drEntity = GameEntry.DataTable.GetDataTableRow<DREntity>(entityId);
|
||||
if (drEntity == null)
|
||||
{
|
||||
Log.Warning("Can not load bullet entity id '{0}' from data table.", entityId.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
assetName = drEntity.AssetName;
|
||||
_assetNameDict.Add(entityId, assetName);
|
||||
}
|
||||
|
||||
return assetName;
|
||||
}
|
||||
|
||||
public static int GenerateSerialId(this EntityComponent entityComponent)
|
||||
{
|
||||
return --s_SerialId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 626632079980ad34595c3b43a062a664
|
||||
timeCreated: 1528026153
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74fbdf043ed3bc141a0a0b09756bc7d2
|
||||
folderAsset: yes
|
||||
timeCreated: 1528026153
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class CoinEntity : EntityBase
|
||||
{
|
||||
private CoinData _coinData;
|
||||
private bool _isCollected;
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
_isCollected = false;
|
||||
|
||||
if (userData is CoinData coinData)
|
||||
{
|
||||
_coinData = coinData;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("CoinEntity.OnShow() called with wrong type.");
|
||||
}
|
||||
|
||||
this.CachedTransform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
|
||||
public bool TryCollect(Player player, float collectDistance = 0f)
|
||||
{
|
||||
if (_isCollected || player == null || !player.Available || _coinData == null) return false;
|
||||
|
||||
if (collectDistance > 0f)
|
||||
{
|
||||
float distance = Vector3.Distance(player.CachedTransform.position, CachedTransform.position);
|
||||
if (distance > collectDistance) return false;
|
||||
}
|
||||
|
||||
_isCollected = true;
|
||||
player.Coin += _coinData.Value;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
TryCollect(other.GetComponent<Player>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0878ab0829c34f44a83d5e5a1eb586ea
|
||||
timeCreated: 1770292173
|
||||
@@ -0,0 +1,58 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using Entity;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 特效类。
|
||||
/// </summary>
|
||||
public class Effect : EntityBase
|
||||
{
|
||||
[SerializeField]
|
||||
private EffectData m_EffectData = null;
|
||||
|
||||
private float m_ElapseSeconds = 0f;
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnShow(object userData)
|
||||
#else
|
||||
protected internal override void OnShow(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
m_EffectData = userData as EffectData;
|
||||
if (m_EffectData == null)
|
||||
{
|
||||
Log.Error("Effect data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_ElapseSeconds = 0f;
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#else
|
||||
protected internal override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#endif
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
m_ElapseSeconds += elapseSeconds;
|
||||
if (m_ElapseSeconds >= m_EffectData.KeepTime)
|
||||
{
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3e408851e9ea064bb8bc08fbb10b33f
|
||||
timeCreated: 1528026154
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81c9b48d9ff1bce45b91ffa040b1db2a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using SepCore.Definition;
|
||||
using Entity;
|
||||
using UnityEngine;
|
||||
|
||||
public abstract class EnemyBase : TargetableObject
|
||||
{
|
||||
protected Transform _target;
|
||||
|
||||
public abstract override ImpactData GetImpactData();
|
||||
public virtual float AttackRange => 1f;
|
||||
|
||||
public virtual void SetTarget(Transform target) => _target = target;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d33d6ac2d1874d4418780e3354e6b4e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,107 @@
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class EnemyProjectile : EntityBase
|
||||
{
|
||||
private EnemyProjectileData _projectileData;
|
||||
private Vector3 _direction = Vector3.forward;
|
||||
private bool _isActive;
|
||||
private ImpactData _impactData;
|
||||
private Collider[] _cachedColliders;
|
||||
|
||||
public bool IsActive => _isActive;
|
||||
public ImpactData GetImpactData() => _impactData;
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_projectileData = userData as EnemyProjectileData;
|
||||
if (_projectileData == null)
|
||||
{
|
||||
Log.Error("Enemy projectile data is invalid.");
|
||||
_isActive = false;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
return;
|
||||
}
|
||||
|
||||
_isActive = true;
|
||||
_impactData = new ImpactData(_projectileData.OwnerCamp, _projectileData.AttackDamage);
|
||||
|
||||
_direction = _projectileData.Direction;
|
||||
_direction.y = 0f;
|
||||
if (_direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
_direction = CachedTransform.forward;
|
||||
_direction.y = 0f;
|
||||
}
|
||||
|
||||
if (_direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
_direction = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
_direction.Normalize();
|
||||
}
|
||||
|
||||
CachedTransform.rotation = Quaternion.LookRotation(_direction, Vector3.up);
|
||||
|
||||
if (_projectileData.OwnerCamp == CampType.Player)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("PlayerWeapon");
|
||||
}
|
||||
else if (_projectileData.OwnerCamp == CampType.Enemy)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("EnemyWeapon");
|
||||
}
|
||||
|
||||
SetColliderEnabled(false);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (!_isActive || _projectileData == null) return;
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_isActive = false;
|
||||
_projectileData = null;
|
||||
_impactData = default;
|
||||
_direction = Vector3.forward;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void Expire()
|
||||
{
|
||||
if (!_isActive) return;
|
||||
_isActive = false;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
}
|
||||
|
||||
private void SetColliderEnabled(bool enabled)
|
||||
{
|
||||
_cachedColliders ??= GetComponentsInChildren<Collider>(true);
|
||||
if (_cachedColliders == null) return;
|
||||
|
||||
for (int i = 0; i < _cachedColliders.Length; i++)
|
||||
{
|
||||
Collider collider = _cachedColliders[i];
|
||||
if (collider == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collider.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22624f81b9364c8681b32d993f5e618f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,264 @@
|
||||
using Components;
|
||||
using CustomUtility;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class MeleeEnemy : EnemyBase
|
||||
{
|
||||
private enum AttackStateType
|
||||
{
|
||||
Idle,
|
||||
Check_OutRange,
|
||||
Check_InRange,
|
||||
Attack
|
||||
}
|
||||
|
||||
private MovementComponent _movementComponent;
|
||||
|
||||
private float _attackRange = 1f;
|
||||
private float _attackCooldown = 1f;
|
||||
private int _attackDamage = 1;
|
||||
|
||||
private float _sqrAttackRange;
|
||||
private float _currAttackTimer;
|
||||
|
||||
private AttackStateType _attackState = AttackStateType.Idle;
|
||||
private EnemyData _meleeEnemyData;
|
||||
private TargetableObject _targetableTarget;
|
||||
|
||||
protected override TargetableObjectData _targetableObjectData => _meleeEnemyData;
|
||||
public override float AttackRange => _attackRange;
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_meleeEnemyData.Camp, _attackDamage);
|
||||
}
|
||||
|
||||
#region FSM
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_movementComponent = GetComponent<MovementComponent>();
|
||||
_healthComponent = GetComponent<HealthComponent>();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (userData is EnemyData enemyData)
|
||||
{
|
||||
_meleeEnemyData = enemyData;
|
||||
_healthComponent.OnInit(enemyData.MaxHealthBase);
|
||||
_movementComponent.OnInit(_meleeEnemyData.SpeedBase, this.CachedTransform, null, true);
|
||||
_movementComponent.SetMove(true);
|
||||
|
||||
_attackRange = Mathf.Max(0.1f, _meleeEnemyData.AttackRange);
|
||||
_attackCooldown = Mathf.Max(0.01f, _meleeEnemyData.AttackCooldown);
|
||||
_attackDamage = Mathf.Max(1, _meleeEnemyData.AttackDamage);
|
||||
_sqrAttackRange = _attackRange * _attackRange;
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
_attackState = AttackStateType.Idle;
|
||||
_targetableTarget = null;
|
||||
|
||||
this.CachedTransform.position = enemyData.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
UpdateAttackState(elapseSeconds);
|
||||
}
|
||||
|
||||
protected override void OnDead(EntityBase attacker)
|
||||
{
|
||||
if (Random.value < _meleeEnemyData.DropPercent)
|
||||
{
|
||||
var data = new CoinData(_meleeEnemyData.DropCoin, GameEntry.Entity.GenerateSerialId(), 10001)
|
||||
{
|
||||
Position = this.CachedTransform.position
|
||||
};
|
||||
GameEntry.Entity.ShowCoin(data);
|
||||
}
|
||||
|
||||
if (Random.value < _meleeEnemyData.DropPercent)
|
||||
{
|
||||
var data = new ExpData(_meleeEnemyData.DropExp, GameEntry.Entity.GenerateSerialId(), 10002)
|
||||
{
|
||||
Position = this.CachedTransform.position
|
||||
};
|
||||
GameEntry.Entity.ShowExp(data);
|
||||
}
|
||||
|
||||
base.OnDead(attacker);
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_movementComponent.OnReset();
|
||||
_healthComponent.OnReset();
|
||||
_targetableTarget = null;
|
||||
_currAttackTimer = 0f;
|
||||
_attackState = AttackStateType.Idle;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void SetTarget(Transform target)
|
||||
{
|
||||
base.SetTarget(target);
|
||||
_targetableTarget = target != null ? target.GetComponent<TargetableObject>() : null;
|
||||
}
|
||||
|
||||
private void UpdateAttackState(float elapseSeconds)
|
||||
{
|
||||
_currAttackTimer += elapseSeconds;
|
||||
|
||||
switch (_attackState)
|
||||
{
|
||||
case AttackStateType.Idle:
|
||||
SetMove(false);
|
||||
if (HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_OutRange);
|
||||
}
|
||||
|
||||
break;
|
||||
case AttackStateType.Check_OutRange:
|
||||
if (!HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsTargetInRange())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_InRange);
|
||||
return;
|
||||
}
|
||||
|
||||
SetMove(true);
|
||||
_movementComponent.SetDirection(GetTargetDirection());
|
||||
break;
|
||||
case AttackStateType.Check_InRange:
|
||||
if (!HasValidTarget())
|
||||
{
|
||||
TransitionTo(AttackStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
SetMove(false);
|
||||
if (!IsTargetInRange())
|
||||
{
|
||||
TransitionTo(AttackStateType.Check_OutRange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currAttackTimer >= _attackCooldown)
|
||||
{
|
||||
TransitionTo(AttackStateType.Attack);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionTo(AttackStateType newState)
|
||||
{
|
||||
_attackState = newState;
|
||||
|
||||
if (_attackState == AttackStateType.Check_InRange)
|
||||
{
|
||||
SetMove(false);
|
||||
}
|
||||
|
||||
if (_attackState == AttackStateType.Check_InRange && _currAttackTimer >= _attackCooldown)
|
||||
{
|
||||
TransitionTo(AttackStateType.Attack);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_attackState == AttackStateType.Attack)
|
||||
{
|
||||
SetMove(false);
|
||||
ExecuteAttack();
|
||||
_currAttackTimer = 0f;
|
||||
TransitionTo(AttackStateType.Check_InRange);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasValidTarget()
|
||||
{
|
||||
if (_target == null) return false;
|
||||
|
||||
if (_targetableTarget == null || _targetableTarget.CachedTransform != _target)
|
||||
{
|
||||
_targetableTarget = _target.GetComponent<TargetableObject>();
|
||||
}
|
||||
|
||||
return _targetableTarget != null && _targetableTarget.Available && !_targetableTarget.IsDead;
|
||||
}
|
||||
|
||||
private bool IsTargetInRange()
|
||||
{
|
||||
if (_target == null) return false;
|
||||
|
||||
Vector3 delta = _target.position - this.CachedTransform.position;
|
||||
delta.y = 0f;
|
||||
return delta.sqrMagnitude <= _sqrAttackRange;
|
||||
}
|
||||
|
||||
private void ExecuteAttack()
|
||||
{
|
||||
if (!HasValidTarget() || !IsTargetInRange()) return;
|
||||
|
||||
ImpactData targetImpactData = _targetableTarget.GetImpactData();
|
||||
ImpactData selfImpactData = GetImpactData();
|
||||
if (AIUtility.GetRelation(selfImpactData.Camp, targetImpactData.Camp) == RelationType.Friendly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int damage = AIUtility.CalcDamageHP(_attackDamage, null, targetImpactData.DefenseStat,
|
||||
targetImpactData.DodgeStat);
|
||||
_targetableTarget.ApplyDamage(this, damage);
|
||||
}
|
||||
|
||||
private void SetMove(bool value)
|
||||
{
|
||||
if (_movementComponent != null)
|
||||
{
|
||||
_movementComponent.SetMove(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetTargetDirection()
|
||||
{
|
||||
if (_target == null)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
return new Vector3(
|
||||
_target.position.x - this.CachedTransform.position.x,
|
||||
0f,
|
||||
_target.position.z - this.CachedTransform.position.z
|
||||
).normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 112f2a17ab82438484782c9a763fdee1
|
||||
timeCreated: 1768892870
|
||||
@@ -0,0 +1,232 @@
|
||||
using Components;
|
||||
using CustomUtility;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class RemoteEnemy : EnemyBase
|
||||
{
|
||||
private const string EnemyProjectileGroupName = "Bullet";
|
||||
private const float EnemyProjectileGroupAutoReleaseInterval = 60f;
|
||||
private const int EnemyProjectileGroupCapacity = 64;
|
||||
private const float EnemyProjectileGroupExpireTime = 60f;
|
||||
private const int EnemyProjectileGroupPriority = 0;
|
||||
|
||||
private const string ProjectileSpeedParamKey = "ProjectileSpeed";
|
||||
private const string ProjectileLifeTimeParamKey = "ProjectileLifeTime";
|
||||
private const string ProjectileSpawnForwardOffsetParamKey = "ProjectileSpawnForwardOffset";
|
||||
private const string ProjectileSpawnHeightOffsetParamKey = "ProjectileSpawnHeightOffset";
|
||||
private const string ProjectileAssetNameParamKey = "ProjectileAssetName";
|
||||
|
||||
private MovementComponent _movementComponent;
|
||||
private float _attackRange = 1f;
|
||||
private float _attackRangeSquared;
|
||||
private float _attackCooldown = 1f;
|
||||
private int _attackDamage = 1;
|
||||
private float _currAttackTimer;
|
||||
|
||||
[SerializeField] private float _projectileSpeed = 12f;
|
||||
[SerializeField] private float _projectileLifeTime = 3f;
|
||||
[SerializeField] private float _projectileSpawnForwardOffset = 0.7f;
|
||||
[SerializeField] private float _projectileSpawnHeightOffset = 0.6f;
|
||||
[SerializeField] private string _projectileAssetName = "BulletHandgun";
|
||||
|
||||
private EnemyData _remoteEnemyData;
|
||||
|
||||
protected override TargetableObjectData _targetableObjectData => _remoteEnemyData;
|
||||
public override float AttackRange => _attackRange;
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_remoteEnemyData.Camp, _attackDamage);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_movementComponent = GetComponent<MovementComponent>();
|
||||
_healthComponent = GetComponent<HealthComponent>();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (userData is EnemyData enemyData)
|
||||
{
|
||||
_remoteEnemyData = enemyData;
|
||||
_healthComponent.OnInit(enemyData.MaxHealthBase);
|
||||
_movementComponent.OnInit(_remoteEnemyData.SpeedBase, this.CachedTransform, null, true);
|
||||
_movementComponent.SetMove(true);
|
||||
|
||||
_attackRange = Mathf.Max(0.1f, _remoteEnemyData.AttackRange);
|
||||
_attackRangeSquared = _attackRange * _attackRange;
|
||||
_attackCooldown = Mathf.Max(0.01f, _remoteEnemyData.AttackCooldown);
|
||||
_attackDamage = Mathf.Max(1, _remoteEnemyData.AttackDamage);
|
||||
|
||||
_projectileSpeed = ReadPositiveParam(ProjectileSpeedParamKey, _projectileSpeed);
|
||||
_projectileLifeTime = ReadPositiveParam(ProjectileLifeTimeParamKey, _projectileLifeTime);
|
||||
_projectileSpawnForwardOffset = ReadPositiveParam(ProjectileSpawnForwardOffsetParamKey,
|
||||
_projectileSpawnForwardOffset);
|
||||
_projectileSpawnHeightOffset = ReadPositiveParam(ProjectileSpawnHeightOffsetParamKey,
|
||||
_projectileSpawnHeightOffset);
|
||||
_projectileAssetName = ReadStringParam(ProjectileAssetNameParamKey, _projectileAssetName);
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
this.CachedTransform.position = enemyData.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_currAttackTimer += elapseSeconds;
|
||||
|
||||
if (_target == null)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 toTarget = _target.position - this.CachedTransform.position;
|
||||
toTarget.y = 0f;
|
||||
float distanceSquared = toTarget.sqrMagnitude;
|
||||
if (distanceSquared <= _attackRangeSquared)
|
||||
{
|
||||
_movementComponent.SetMove(false);
|
||||
TryFireProjectile();
|
||||
}
|
||||
else
|
||||
{
|
||||
_movementComponent.SetMove(true);
|
||||
_movementComponent.SetDirection(GetTargetDirection());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_movementComponent.OnReset();
|
||||
_healthComponent.OnReset();
|
||||
_currAttackTimer = 0f;
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void TryFireProjectile()
|
||||
{
|
||||
if (_currAttackTimer < _attackCooldown || _target == null) return;
|
||||
if (!EnsureEnemyProjectileGroup()) return;
|
||||
|
||||
Vector3 spawnPosition = this.CachedTransform.position +
|
||||
this.CachedTransform.forward * _projectileSpawnForwardOffset +
|
||||
Vector3.up * _projectileSpawnHeightOffset;
|
||||
Vector3 direction = _target.position - spawnPosition;
|
||||
direction.y = 0f;
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
direction = this.CachedTransform.forward;
|
||||
direction.y = 0f;
|
||||
}
|
||||
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
direction = Vector3.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
direction.Normalize();
|
||||
}
|
||||
|
||||
int projectileEntityId = GameEntry.Entity.GenerateSerialId();
|
||||
var projectileData = new EnemyProjectileData(projectileEntityId, Id, _remoteEnemyData.Camp,
|
||||
_attackDamage, _projectileSpeed, _projectileLifeTime, direction)
|
||||
{
|
||||
Position = spawnPosition,
|
||||
Rotation = Quaternion.LookRotation(direction, Vector3.up)
|
||||
};
|
||||
|
||||
GameEntry.Entity.ShowEntity(
|
||||
entityId: projectileEntityId,
|
||||
entityLogicType: typeof(EnemyProjectile),
|
||||
entityAssetName: AssetUtility.GetEntityAsset(_projectileAssetName),
|
||||
entityGroupName: EnemyProjectileGroupName,
|
||||
priority: Constant.AssetPriority.BulletAsset,
|
||||
userData: projectileData);
|
||||
|
||||
_currAttackTimer = 0f;
|
||||
}
|
||||
|
||||
private static bool EnsureEnemyProjectileGroup()
|
||||
{
|
||||
var entityComponent = GameEntry.Entity;
|
||||
if (entityComponent == null) return false;
|
||||
|
||||
if (entityComponent.HasEntityGroup(EnemyProjectileGroupName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addResult = entityComponent.AddEntityGroup(
|
||||
EnemyProjectileGroupName,
|
||||
EnemyProjectileGroupAutoReleaseInterval,
|
||||
EnemyProjectileGroupCapacity,
|
||||
EnemyProjectileGroupExpireTime,
|
||||
EnemyProjectileGroupPriority);
|
||||
|
||||
if (!addResult)
|
||||
{
|
||||
Log.Warning("Can not create entity group '{0}'.", EnemyProjectileGroupName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float ReadPositiveParam(string paramName, float defaultValue)
|
||||
{
|
||||
if (_remoteEnemyData != null &&
|
||||
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
|
||||
float.TryParse(rawValue, out float parsedValue))
|
||||
{
|
||||
return Mathf.Max(0.01f, parsedValue);
|
||||
}
|
||||
|
||||
return Mathf.Max(0.01f, defaultValue);
|
||||
}
|
||||
|
||||
private string ReadStringParam(string paramName, string defaultValue)
|
||||
{
|
||||
if (_remoteEnemyData != null &&
|
||||
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
|
||||
!string.IsNullOrWhiteSpace(rawValue))
|
||||
{
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private Vector3 GetTargetDirection()
|
||||
{
|
||||
if (_target == null)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
return new Vector3(
|
||||
_target.position.x - this.CachedTransform.position.x,
|
||||
0f,
|
||||
_target.position.z - this.CachedTransform.position.z
|
||||
).normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b90e05d1e7a5475bb928b2445e113767
|
||||
timeCreated: 1770209676
|
||||
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using Entity.EntityData;
|
||||
using GameFramework;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public abstract class EntityBase : EntityLogic
|
||||
{
|
||||
[SerializeField] private EntityDataBase _entityData = null;
|
||||
|
||||
public int Id => Entity.Id;
|
||||
|
||||
public Animation CachedAnimation { get; private set; }
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
CachedAnimation = GetComponent<Animation>();
|
||||
}
|
||||
|
||||
protected override void OnRecycle()
|
||||
{
|
||||
base.OnRecycle();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
_entityData = userData as EntityDataBase;
|
||||
if (_entityData == null)
|
||||
{
|
||||
Log.Error("Entity data is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
Name = Utility.Text.Format("[Entity {0}]", Id);
|
||||
CachedTransform.localPosition = _entityData.Position;
|
||||
CachedTransform.localRotation = _entityData.Rotation;
|
||||
CachedTransform.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
protected override void OnAttached(EntityLogic childEntity, Transform parentTransform, object userData)
|
||||
{
|
||||
base.OnAttached(childEntity, parentTransform, userData);
|
||||
}
|
||||
|
||||
protected override void OnDetached(EntityLogic childEntity, object userData)
|
||||
{
|
||||
base.OnDetached(childEntity, userData);
|
||||
}
|
||||
|
||||
protected override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
{
|
||||
base.OnAttachTo(parentEntity, parentTransform, userData);
|
||||
}
|
||||
|
||||
protected override void OnDetachFrom(EntityLogic parentEntity, object userData)
|
||||
{
|
||||
base.OnDetachFrom(parentEntity, userData);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 878fbbe82b4bde944812bcb7c964cff8
|
||||
timeCreated: 1528026154
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using Entity.EntityData;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class ExpEntity:EntityBase
|
||||
{
|
||||
private ExpData _expData;
|
||||
private bool _isCollected;
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
_isCollected = false;
|
||||
|
||||
if (userData is ExpData expData)
|
||||
{
|
||||
_expData = expData;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("expEntity.OnShow() called with wrong type.");
|
||||
}
|
||||
this.CachedTransform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
|
||||
}
|
||||
|
||||
public bool TryCollect(Player player, float collectDistance = 0f)
|
||||
{
|
||||
if (_isCollected || player == null || !player.Available || _expData == null) return false;
|
||||
|
||||
if (collectDistance > 0f)
|
||||
{
|
||||
float distance = Vector3.Distance(player.CachedTransform.position, CachedTransform.position);
|
||||
if (distance > collectDistance) return false;
|
||||
}
|
||||
|
||||
_isCollected = true;
|
||||
player.Exp += _expData.Value;
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
TryCollect(other.GetComponent<Player>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec4ae4b0a327442a88463eafd59c5b6e
|
||||
timeCreated: 1770293001
|
||||
@@ -0,0 +1,245 @@
|
||||
using System.Collections.Generic;
|
||||
using Components;
|
||||
using CustomEvent;
|
||||
using SepCore.DataTable;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using GameFramework.Event;
|
||||
using UnityGameFramework.Runtime;
|
||||
using Entity.Weapon;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class Player : TargetableObject
|
||||
{
|
||||
protected override TargetableObjectData _targetableObjectData => _playerData;
|
||||
|
||||
private InputComponent _inputComponent;
|
||||
private MovementComponent _movementComponent;
|
||||
private AbsorbComponent _absorbComponent;
|
||||
private StatComponent _statComponent;
|
||||
private BackpackComponent _backpackComponent;
|
||||
private PlayerData _playerData;
|
||||
|
||||
/// <summary>
|
||||
/// 当前金币。
|
||||
/// </summary>
|
||||
private int _coin;
|
||||
|
||||
/// <summary>
|
||||
/// 当前经验。
|
||||
/// </summary>
|
||||
private int _exp;
|
||||
|
||||
/// <summary>
|
||||
/// 每级所需的经验。
|
||||
/// tip: _expRequires[1] 表示 Lv 0 -> Lv 1 需要的经验
|
||||
/// </summary>
|
||||
private int[] _expRequires;
|
||||
|
||||
/// <summary>
|
||||
/// 当前的角色等级。
|
||||
/// </summary>
|
||||
private int _currentLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家是否启用。
|
||||
/// </summary>
|
||||
private bool _enable;
|
||||
|
||||
private bool _weaponEnabled = true;
|
||||
|
||||
public int Coin
|
||||
{
|
||||
get => _coin;
|
||||
set
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerCoinChangeEventArgs.Create(value - _coin, value));
|
||||
_coin = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Exp
|
||||
{
|
||||
get => _exp;
|
||||
set
|
||||
{
|
||||
if (value >= _expRequires[_currentLevel + 1])
|
||||
{
|
||||
value -= _expRequires[_currentLevel + 1];
|
||||
_currentLevel++;
|
||||
PendingLevelPoints++;
|
||||
GameEntry.Event.Fire(this, PlayerLevelUpEventArgs.Create());
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, PlayerExpChangeEventArgs.Create(value, _expRequires[_currentLevel + 1]));
|
||||
_exp = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int CurrentLevel => _currentLevel;
|
||||
|
||||
public int PendingLevelPoints { get; set; }
|
||||
|
||||
public IReadOnlyList<WeaponBase> Weapons => _backpackComponent != null ? _backpackComponent.Weapons : null;
|
||||
public IReadOnlyList<PropItem> Props => _backpackComponent != null ? _backpackComponent.Props : null;
|
||||
public int WeaponCapacity => _backpackComponent != null ? _backpackComponent.WeaponCapacity : 0;
|
||||
|
||||
public bool AddProp(PropItem prop)
|
||||
{
|
||||
if (prop == null || _backpackComponent == null) return false;
|
||||
return _backpackComponent.AttachProp(prop);
|
||||
}
|
||||
|
||||
public bool RemoveWeapon(WeaponBase weapon)
|
||||
{
|
||||
if (weapon == null || _backpackComponent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_backpackComponent.DetachWeapon(weapon))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameEntry.Entity.HideEntity(weapon);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Enable
|
||||
{
|
||||
get => _enable;
|
||||
set
|
||||
{
|
||||
if (value == _enable) return;
|
||||
_enable = value;
|
||||
_movementComponent.SetMove(value);
|
||||
_backpackComponent.SetWeaponState(value && _weaponEnabled);
|
||||
_inputComponent.SetListening(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool WeaponEnabled => _weaponEnabled;
|
||||
|
||||
public void SetWeaponEnabled(bool enabled)
|
||||
{
|
||||
_weaponEnabled = enabled;
|
||||
_backpackComponent?.SetWeaponState(_enable && _weaponEnabled);
|
||||
}
|
||||
|
||||
public void InitRole(DRRole role)
|
||||
{
|
||||
// MovementComponent
|
||||
_movementComponent.OnInit(role.Speed, this.CachedTransform, _statComponent);
|
||||
_movementComponent.SetMove(true);
|
||||
|
||||
// HealthComponent
|
||||
_healthComponent.OnInit(role.MaxHealth, _statComponent);
|
||||
|
||||
// BackpackComponent
|
||||
Coin = role.Coin;
|
||||
_backpackComponent.OnInit(this, role.WeaponCapacity);
|
||||
GameEntry.Entity.ShowWeapon(new WeaponKnifeData(GameEntry.Entity.GenerateSerialId(), Id, _playerData.Camp));
|
||||
|
||||
// StatComponent
|
||||
foreach (var modifier in role.InitialProperties)
|
||||
{
|
||||
_statComponent.AddModifier(modifier);
|
||||
}
|
||||
|
||||
_expRequires = role.ExpRequires;
|
||||
|
||||
// Fire Events
|
||||
GameEntry.Event.Fire(this,
|
||||
PlayerHealthChangeEventArgs.Create(0, _healthComponent.CurrentHealth, _healthComponent.MaxHealth));
|
||||
|
||||
GameEntry.Event.Fire(this, PlayerExpChangeEventArgs.Create(0, 100));
|
||||
|
||||
GameEntry.Event.Fire(this,
|
||||
PlayerCoinChangeEventArgs.Create(Coin, Coin));
|
||||
}
|
||||
|
||||
#region FSM
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_playerData.Camp, 0, null, _healthComponent.DefenseStat, _healthComponent.DodgeStat);
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_inputComponent = GetComponent<InputComponent>();
|
||||
_movementComponent = GetComponent<MovementComponent>();
|
||||
_absorbComponent = GetComponent<AbsorbComponent>();
|
||||
_statComponent = GetComponent<StatComponent>();
|
||||
_healthComponent = GetComponent<HealthComponent>();
|
||||
_backpackComponent = GetComponent<BackpackComponent>();
|
||||
|
||||
if (_absorbComponent == null)
|
||||
{
|
||||
_absorbComponent = gameObject.AddComponent<AbsorbComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);
|
||||
|
||||
_statComponent.OnInit();
|
||||
|
||||
if (userData is PlayerData playerData)
|
||||
{
|
||||
_playerData = playerData;
|
||||
}
|
||||
|
||||
_inputComponent.OnInit();
|
||||
_inputComponent.SetListening(true);
|
||||
_absorbComponent.OnInit(this, _statComponent);
|
||||
_weaponEnabled = true;
|
||||
|
||||
_currentLevel = 0;
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_inputComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
_movementComponent.SetDirection(_inputComponent.Direction);
|
||||
_absorbComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
protected override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
_inputComponent.OnReset();
|
||||
_movementComponent.OnReset();
|
||||
_healthComponent.OnReset();
|
||||
_statComponent.OnReset();
|
||||
_backpackComponent.OnReset();
|
||||
_absorbComponent.OnReset();
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void ShowEntitySuccess(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is ShowEntitySuccessEventArgs args)) return;
|
||||
if (args.Entity.Logic is WeaponBase weapon)
|
||||
{
|
||||
_backpackComponent.AttachWeapon(weapon);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3979cb10abff0944aad09de04ec7813a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using Components;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using CustomUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 可作为目标的实体类。
|
||||
/// </summary>
|
||||
public abstract class TargetableObject : EntityBase
|
||||
{
|
||||
protected abstract TargetableObjectData _targetableObjectData { get; }
|
||||
|
||||
public bool IsDead => _healthComponent.CurrentHealth <= 0;
|
||||
|
||||
protected HealthComponent _healthComponent;
|
||||
|
||||
public abstract ImpactData GetImpactData();
|
||||
|
||||
public void ApplyDamage(EntityBase attacker, int damageHP)
|
||||
{
|
||||
float fromHPRatio = _healthComponent.HealthRatio;
|
||||
_healthComponent.TakeDamage(damageHP);
|
||||
float toHPRatio = _healthComponent.HealthRatio;
|
||||
if (fromHPRatio > toHPRatio)
|
||||
{
|
||||
GameEntry.HPBar.ShowHPBar(this, fromHPRatio, toHPRatio);
|
||||
}
|
||||
|
||||
if (_healthComponent.CurrentHealth <= 0)
|
||||
{
|
||||
OnDead(attacker);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_healthComponent = GetComponent<HealthComponent>();
|
||||
}
|
||||
|
||||
protected override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
}
|
||||
|
||||
protected virtual void OnDead(EntityBase attacker)
|
||||
{
|
||||
GameEntry.Entity.HideEntity(this);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
EntityBase entity = other.GetComponentInParent<EntityBase>();
|
||||
if (entity == null || entity == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity is TargetableObject && entity.Id < Id)
|
||||
{
|
||||
// 碰撞事件由 Id 大的一方处理
|
||||
// 在这里约定 Enemy 的 Id 为非负数(通常从 0 开始)
|
||||
// 而其他的 Entity (Player, Weapon, Bullet) 的 Id 均小于 0
|
||||
return;
|
||||
}
|
||||
|
||||
AIUtility.PerformCollision(this, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a1fd351b05d994459122be1a39507b1
|
||||
timeCreated: 1528026154
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b5f40df44fe005498fc03555871b8ad
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abdf25a9782ebf844a65b2348f8f566d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private const string PoolName = "Weapon.HandgunHitMarker";
|
||||
private const float PoolAutoReleaseInterval = 60f;
|
||||
private const int PoolCapacity = 128;
|
||||
private const float PoolExpireTime = 120f;
|
||||
private const int PoolPriority = 0;
|
||||
|
||||
private static IObjectPool<HandgunHitMarkerPoolObject> s_Pool;
|
||||
|
||||
private readonly float _size;
|
||||
private readonly float _yOffset;
|
||||
private readonly float _duration;
|
||||
private readonly Color _color;
|
||||
private Material _sharedMaterial;
|
||||
|
||||
public HandgunHitMarkerAttackEffect(float size, float yOffset, float duration, Color color)
|
||||
{
|
||||
_size = size;
|
||||
_yOffset = yOffset;
|
||||
_duration = duration;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
if (target == null) return;
|
||||
if (!TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform targetTransform = target.CachedTransform;
|
||||
Vector3 worldPosition = targetTransform != null ? targetTransform.position : position;
|
||||
markerInstance.transform.SetParent(null, false);
|
||||
markerInstance.transform.position = worldPosition + Vector3.up * _yOffset;
|
||||
markerInstance.transform.localScale = Vector3.one * Mathf.Max(0.01f, _size);
|
||||
markerInstance.ApplyMaterial(GetSharedMaterial());
|
||||
markerInstance.Activate(Mathf.Max(0.01f, _duration), s_Pool);
|
||||
}
|
||||
|
||||
private bool TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance)
|
||||
{
|
||||
markerInstance = null;
|
||||
IObjectPool<HandgunHitMarkerPoolObject> pool = EnsurePool();
|
||||
if (pool == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HandgunHitMarkerPoolObject pooledObject = pool.Spawn();
|
||||
if (pooledObject != null)
|
||||
{
|
||||
markerInstance = pooledObject.Target as HandgunHitMarkerPooledInstance;
|
||||
if (markerInstance != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GameObject markerGameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
markerGameObject.name = "HandgunHitMarker";
|
||||
Collider collider = markerGameObject.GetComponent<Collider>();
|
||||
if (collider != null)
|
||||
{
|
||||
Object.Destroy(collider);
|
||||
}
|
||||
|
||||
markerInstance = markerGameObject.AddComponent<HandgunHitMarkerPooledInstance>();
|
||||
markerGameObject.SetActive(false);
|
||||
pool.Register(HandgunHitMarkerPoolObject.Create(markerInstance), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IObjectPool<HandgunHitMarkerPoolObject> EnsurePool()
|
||||
{
|
||||
var poolComponent = GameEntry.ObjectPool;
|
||||
if (poolComponent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (s_Pool != null && poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName))
|
||||
{
|
||||
return s_Pool;
|
||||
}
|
||||
|
||||
s_Pool = poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName)
|
||||
? poolComponent.GetObjectPool<HandgunHitMarkerPoolObject>(PoolName)
|
||||
: poolComponent.CreateSingleSpawnObjectPool<HandgunHitMarkerPoolObject>(
|
||||
PoolName,
|
||||
PoolAutoReleaseInterval,
|
||||
PoolCapacity,
|
||||
PoolExpireTime,
|
||||
PoolPriority);
|
||||
return s_Pool;
|
||||
}
|
||||
|
||||
private Material GetSharedMaterial()
|
||||
{
|
||||
if (_sharedMaterial != null)
|
||||
{
|
||||
return _sharedMaterial;
|
||||
}
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
_sharedMaterial = new Material(shader)
|
||||
{
|
||||
color = _color
|
||||
};
|
||||
return _sharedMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d944f33383dc4c1ebe850a12d4211afa
|
||||
timeCreated: 1771481130
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using GameFramework;
|
||||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerPoolObject : ObjectBase
|
||||
{
|
||||
public static HandgunHitMarkerPoolObject Create(object target)
|
||||
{
|
||||
HandgunHitMarkerPoolObject pooledObject = ReferencePool.Acquire<HandgunHitMarkerPoolObject>();
|
||||
pooledObject.Initialize(target);
|
||||
return pooledObject;
|
||||
}
|
||||
|
||||
protected override void Release(bool isShutdown)
|
||||
{
|
||||
HandgunHitMarkerPooledInstance markerInstance = Target as HandgunHitMarkerPooledInstance;
|
||||
if (markerInstance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Object.Destroy(markerInstance.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 966d34a18f5d00c49bddea3c7c5ec13d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
using GameFramework.ObjectPool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HandgunHitMarkerPooledInstance : MonoBehaviour
|
||||
{
|
||||
private Renderer _renderer;
|
||||
private IObjectPool<HandgunHitMarkerPoolObject> _ownerPool;
|
||||
private float _expireTime;
|
||||
private bool _isActive;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.time < _expireTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
public void ApplyMaterial(Material material)
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
_renderer = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
if (_renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_renderer.sharedMaterial = material;
|
||||
}
|
||||
|
||||
public void Activate(float duration, IObjectPool<HandgunHitMarkerPoolObject> pool)
|
||||
{
|
||||
_ownerPool = pool;
|
||||
_expireTime = Time.time + Mathf.Max(0.01f, duration);
|
||||
_isActive = true;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_isActive = false;
|
||||
_ownerPool = null;
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
_isActive = false;
|
||||
IObjectPool<HandgunHitMarkerPoolObject> pool = _ownerPool;
|
||||
_ownerPool = null;
|
||||
transform.SetParent(null, false);
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
pool.Unspawn(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ff8a67466d8f0643845c41fd5952f50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public interface IWeaponAttackEffect
|
||||
{
|
||||
void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius);
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 231a935a484c4351b707b3e359074a47
|
||||
timeCreated: 1771481068
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class KnifeRangeAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.2f;
|
||||
private readonly float _yOffset = 0.05f;
|
||||
private readonly float _lineWidth = 0.05f;
|
||||
private readonly int _segments = 48;
|
||||
private readonly Color _color = new(0.1f, 1f, 0.1f, 0.9f);
|
||||
|
||||
public KnifeRangeAttackEffect()
|
||||
{
|
||||
}
|
||||
|
||||
public KnifeRangeAttackEffect(float duration, float yOffset, float lineWidth, int segments, Color color)
|
||||
{
|
||||
_duration = duration;
|
||||
_yOffset = yOffset;
|
||||
_lineWidth = lineWidth;
|
||||
_segments = Mathf.Max(8, segments);
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
float safeRadius = Mathf.Max(0.1f, radius);
|
||||
|
||||
GameObject indicator = new GameObject("KnifeRangeIndicator");
|
||||
indicator.transform.position = new Vector3(position.x, position.y + _yOffset, position.z);
|
||||
|
||||
LineRenderer line = indicator.AddComponent<LineRenderer>();
|
||||
line.loop = true;
|
||||
line.useWorldSpace = true;
|
||||
line.positionCount = _segments;
|
||||
line.startWidth = _lineWidth;
|
||||
line.endWidth = _lineWidth;
|
||||
line.startColor = _color;
|
||||
line.endColor = _color;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material material = new Material(shader);
|
||||
material.color = _color;
|
||||
line.material = material;
|
||||
|
||||
float step = Mathf.PI * 2f / _segments;
|
||||
for (int i = 0; i < _segments; i++)
|
||||
{
|
||||
float angle = i * step;
|
||||
Vector3 offset = new Vector3(Mathf.Cos(angle) * safeRadius, 0f, Mathf.Sin(angle) * safeRadius);
|
||||
line.SetPosition(i, indicator.transform.position + offset);
|
||||
}
|
||||
|
||||
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f48328040644546bf0a866cc337d3b2
|
||||
timeCreated: 1771481100
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class LanceThrustAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.18f;
|
||||
private readonly float _yOffset = 0.06f;
|
||||
private readonly float _lineWidth = 0.05f;
|
||||
private readonly Color _color = new(0.2f, 0.95f, 0.7f, 0.92f);
|
||||
|
||||
public LanceThrustAttackEffect()
|
||||
{
|
||||
}
|
||||
|
||||
public LanceThrustAttackEffect(float duration, float yOffset, float lineWidth, Color color)
|
||||
{
|
||||
_duration = duration;
|
||||
_yOffset = yOffset;
|
||||
_lineWidth = lineWidth;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
if (weapon is not WeaponLance lance) return;
|
||||
|
||||
Vector3 forward = lance.StrikeDirection;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
forward = Vector3.forward;
|
||||
}
|
||||
|
||||
forward.Normalize();
|
||||
Vector3 right = Vector3.Cross(Vector3.up, forward);
|
||||
float halfWidth = Mathf.Max(0.1f, lance.HitHalfWidth);
|
||||
float halfLength = Mathf.Max(0.1f, lance.PierceLength * 0.5f);
|
||||
Vector3 center = position + Vector3.up * _yOffset;
|
||||
|
||||
Vector3 frontCenter = center + forward * halfLength;
|
||||
Vector3 backCenter = center - forward * halfLength;
|
||||
|
||||
Vector3[] corners =
|
||||
{
|
||||
frontCenter - right * halfWidth,
|
||||
frontCenter + right * halfWidth,
|
||||
backCenter + right * halfWidth,
|
||||
backCenter - right * halfWidth,
|
||||
};
|
||||
|
||||
GameObject indicator = new GameObject("LanceThrustIndicator");
|
||||
indicator.transform.position = center;
|
||||
|
||||
LineRenderer line = indicator.AddComponent<LineRenderer>();
|
||||
line.loop = true;
|
||||
line.useWorldSpace = true;
|
||||
line.positionCount = corners.Length;
|
||||
line.startWidth = _lineWidth;
|
||||
line.endWidth = _lineWidth;
|
||||
line.startColor = _color;
|
||||
line.endColor = _color;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material material = new Material(shader);
|
||||
material.color = _color;
|
||||
line.material = material;
|
||||
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
line.SetPosition(i, corners[i]);
|
||||
}
|
||||
|
||||
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c77c590c7233e544a9295ed41ff8827
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class LightningStrikeAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.22f;
|
||||
private readonly float _yOffset = 0.2f;
|
||||
private readonly float _ringLineWidth = 0.045f;
|
||||
private readonly float _boltLineWidth = 0.08f;
|
||||
private readonly int _ringSegments = 32;
|
||||
private readonly float _boltHeight = 3f;
|
||||
private readonly Color _ringColor = new(0.35f, 0.82f, 1f, 0.92f);
|
||||
private readonly Color _boltColor = new(0.85f, 0.96f, 1f, 0.97f);
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
float safeRadius = Mathf.Max(0.1f, radius);
|
||||
Vector3 center = new Vector3(position.x, position.y + _yOffset, position.z);
|
||||
|
||||
GameObject root = new GameObject("LightningStrikeEffect");
|
||||
root.transform.position = center;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material ringMaterial = new Material(shader);
|
||||
ringMaterial.color = _ringColor;
|
||||
|
||||
LineRenderer ring = root.AddComponent<LineRenderer>();
|
||||
ring.loop = true;
|
||||
ring.useWorldSpace = true;
|
||||
ring.positionCount = _ringSegments;
|
||||
ring.startWidth = _ringLineWidth;
|
||||
ring.endWidth = _ringLineWidth;
|
||||
ring.startColor = _ringColor;
|
||||
ring.endColor = _ringColor;
|
||||
ring.material = ringMaterial;
|
||||
|
||||
float step = Mathf.PI * 2f / _ringSegments;
|
||||
for (int i = 0; i < _ringSegments; i++)
|
||||
{
|
||||
float angle = i * step;
|
||||
Vector3 offset = new Vector3(Mathf.Cos(angle) * safeRadius, 0f, Mathf.Sin(angle) * safeRadius);
|
||||
ring.SetPosition(i, center + offset);
|
||||
}
|
||||
|
||||
GameObject bolt = new GameObject("Bolt");
|
||||
bolt.transform.SetParent(root.transform, false);
|
||||
|
||||
Material boltMaterial = new Material(shader);
|
||||
boltMaterial.color = _boltColor;
|
||||
|
||||
LineRenderer boltLine = bolt.AddComponent<LineRenderer>();
|
||||
boltLine.loop = false;
|
||||
boltLine.useWorldSpace = true;
|
||||
boltLine.positionCount = 4;
|
||||
boltLine.startWidth = _boltLineWidth;
|
||||
boltLine.endWidth = _boltLineWidth * 0.55f;
|
||||
boltLine.startColor = _boltColor;
|
||||
boltLine.endColor = _boltColor;
|
||||
boltLine.material = boltMaterial;
|
||||
|
||||
Vector3 top = center + Vector3.up * _boltHeight;
|
||||
Vector3 middleA = center + Vector3.up * (_boltHeight * 0.66f) +
|
||||
new Vector3(Random.Range(-0.18f, 0.18f), 0f, Random.Range(-0.18f, 0.18f));
|
||||
Vector3 middleB = center + Vector3.up * (_boltHeight * 0.3f) +
|
||||
new Vector3(Random.Range(-0.12f, 0.12f), 0f, Random.Range(-0.12f, 0.12f));
|
||||
|
||||
boltLine.SetPosition(0, top);
|
||||
boltLine.SetPosition(1, middleA);
|
||||
boltLine.SetPosition(2, middleB);
|
||||
boltLine.SetPosition(3, center);
|
||||
|
||||
Object.Destroy(root, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81cbdf8961ad419c91b989d56ca782d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class SlashSectorAttackEffect : IWeaponAttackEffect
|
||||
{
|
||||
private readonly float _duration = 0.2f;
|
||||
private readonly float _yOffset = 0.06f;
|
||||
private readonly float _lineWidth = 0.05f;
|
||||
private readonly int _segments = 24;
|
||||
private readonly Color _color = new(1f, 0.45f, 0.2f, 0.9f);
|
||||
|
||||
public SlashSectorAttackEffect()
|
||||
{
|
||||
}
|
||||
|
||||
public SlashSectorAttackEffect(float duration, float yOffset, float lineWidth, int segments, Color color)
|
||||
{
|
||||
_duration = duration;
|
||||
_yOffset = yOffset;
|
||||
_lineWidth = lineWidth;
|
||||
_segments = Mathf.Max(6, segments);
|
||||
_color = color;
|
||||
}
|
||||
|
||||
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
|
||||
{
|
||||
if (weapon is not WeaponSlash slash) return;
|
||||
|
||||
float safeRadius = Mathf.Max(0.1f, radius);
|
||||
float sectorAngle = Mathf.Clamp(slash.SectorAngle, 1f, 360f);
|
||||
|
||||
GameObject indicator = new GameObject("SlashSectorIndicator");
|
||||
Vector3 center = new Vector3(position.x, position.y + _yOffset, position.z);
|
||||
indicator.transform.position = center;
|
||||
|
||||
LineRenderer line = indicator.AddComponent<LineRenderer>();
|
||||
line.loop = false;
|
||||
line.useWorldSpace = true;
|
||||
line.startWidth = _lineWidth;
|
||||
line.endWidth = _lineWidth;
|
||||
line.startColor = _color;
|
||||
line.endColor = _color;
|
||||
|
||||
Shader shader = Shader.Find("Sprites/Default");
|
||||
if (shader == null)
|
||||
{
|
||||
shader = Shader.Find("Unlit/Color");
|
||||
}
|
||||
|
||||
Material material = new Material(shader);
|
||||
material.color = _color;
|
||||
line.material = material;
|
||||
|
||||
float half = sectorAngle * 0.5f;
|
||||
int arcPoints = _segments + 1;
|
||||
int positionCount = arcPoints + 2;
|
||||
line.positionCount = positionCount;
|
||||
|
||||
Vector3 forward = weapon.CachedTransform.forward;
|
||||
forward.y = 0f;
|
||||
if (forward.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
forward = Vector3.forward;
|
||||
}
|
||||
|
||||
forward.Normalize();
|
||||
|
||||
Quaternion baseRotation = Quaternion.LookRotation(forward, Vector3.up);
|
||||
|
||||
line.SetPosition(0, center);
|
||||
for (int i = 0; i < arcPoints; i++)
|
||||
{
|
||||
float t = arcPoints <= 1 ? 0f : i / (float)(arcPoints - 1);
|
||||
float angle = Mathf.Lerp(-half, half, t);
|
||||
Vector3 dir = Quaternion.Euler(0f, angle, 0f) * Vector3.forward;
|
||||
Vector3 worldDir = baseRotation * dir;
|
||||
line.SetPosition(i + 1, center + worldDir * safeRadius);
|
||||
}
|
||||
|
||||
line.SetPosition(positionCount - 1, center);
|
||||
|
||||
UnityEngine.Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 329af2c156b345f28a7465ef29c943da
|
||||
timeCreated: 1771485834
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 371bc24a18a3447686043843fdd6fbe5
|
||||
timeCreated: 1771427642
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using Components;
|
||||
using CustomUtility;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class HighestHealthTargetSelector : ITargetSelector
|
||||
{
|
||||
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
|
||||
{
|
||||
if (weapon == null || candidates == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityBase target = null;
|
||||
float maxHp = float.MinValue;
|
||||
float maxSqr = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (candidate == null || !candidate.Available) continue;
|
||||
|
||||
if (maxSqrRange > 0f && AIUtility.GetSqrMagnitudeXZ(weapon, candidate) >= maxSqr) continue;
|
||||
|
||||
if (!TryGetCurrentHealth(candidate, out var hp)) continue;
|
||||
|
||||
if (hp <= maxHp) continue;
|
||||
|
||||
maxHp = hp;
|
||||
target = candidate;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static bool TryGetCurrentHealth(EntityBase target, out float health)
|
||||
{
|
||||
health = 0f;
|
||||
if (target == null) return false;
|
||||
|
||||
var healthComponent = target.GetComponent<HealthComponent>();
|
||||
if (healthComponent == null) return false;
|
||||
|
||||
health = healthComponent.CurrentHealth;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54932952c9fa3fd4dbe6d21f2a484b4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public interface ITargetSelector
|
||||
{
|
||||
EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ed801e4816c03b4597756dc36d6aba8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using Components;
|
||||
using SepCore.Definition;
|
||||
using CustomUtility;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class LowestHealthTargetSelector : ITargetSelector
|
||||
{
|
||||
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
|
||||
{
|
||||
if (weapon == null || candidates == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
EntityBase target = null;
|
||||
float minHp = float.MaxValue;
|
||||
float maxSqr = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (candidate == null || !candidate.Available) continue;
|
||||
|
||||
if (maxSqrRange > 0f && AIUtility.GetSqrMagnitudeXZ(weapon, candidate) >= maxSqr) continue;
|
||||
|
||||
if (!TryGetHealth(candidate, out var hp)) continue;
|
||||
|
||||
if (hp >= minHp) continue;
|
||||
|
||||
minHp = hp;
|
||||
target = candidate;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static bool TryGetHealth(EntityBase target, out float health)
|
||||
{
|
||||
health = 0f;
|
||||
if (target == null) return false;
|
||||
|
||||
var statComponent = target.GetComponent<StatComponent>();
|
||||
if (statComponent == null) return false;
|
||||
|
||||
var hpStat = statComponent.GetStat(StatType.MaxHealth);
|
||||
if (hpStat == null) return false;
|
||||
|
||||
health = hpStat.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 081d67a491f5792479306d88091168e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using CustomUtility;
|
||||
using CustomComponent;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public sealed class NearestTargetSelector : ITargetSelector
|
||||
{
|
||||
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
|
||||
{
|
||||
if (weapon == null || candidates == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TrySelectFromSpatialIndex(weapon, maxSqrRange, out EntityBase indexedTarget))
|
||||
{
|
||||
return indexedTarget;
|
||||
}
|
||||
|
||||
EntityBase target = null;
|
||||
float minSqrMagnitude = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (candidate == null || !candidate.Available) continue;
|
||||
|
||||
float sqrMagnitude = AIUtility.GetSqrMagnitudeXZ(weapon, candidate);
|
||||
if (sqrMagnitude >= minSqrMagnitude) continue;
|
||||
|
||||
minSqrMagnitude = sqrMagnitude;
|
||||
target = candidate;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static bool TrySelectFromSpatialIndex(WeaponBase weapon, float maxSqrRange, out EntityBase target)
|
||||
{
|
||||
target = null;
|
||||
if (weapon == null || maxSqrRange <= 0f || weapon.CachedTransform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!simulationWorld.TryGetNearestEnemyEntityId(weapon.CachedTransform.position, maxSqrRange,
|
||||
out int entityId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
|
||||
if (enemyManager == null || !enemyManager.TryGetEnemy(entityId, out EntityBase enemy))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
target = enemy;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a70602535b318e74396a6c2ea282834d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public enum TargetSelectorType
|
||||
{
|
||||
Nearest = 0,
|
||||
HighestHealth = 1,
|
||||
LowestHealth = 2
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4eb1e0b99139fdd4280c8616804a8f77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,320 @@
|
||||
using System.Collections.Generic;
|
||||
using Components;
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using GameFramework;
|
||||
using CustomUtility;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public enum WeaponStateType
|
||||
{
|
||||
Disabled,
|
||||
Attack,
|
||||
Idle,
|
||||
Check_OutRange,
|
||||
Check_InRange,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 武器类。
|
||||
/// </summary>
|
||||
public abstract class WeaponBase : EntityBase
|
||||
{
|
||||
protected const string AttachPoint = "Weapon Point";
|
||||
|
||||
protected bool _isAttacking = false;
|
||||
|
||||
[SerializeField] protected bool _isEnabled = false;
|
||||
|
||||
public WeaponData WeaponData { get; protected set; }
|
||||
|
||||
public bool IsAttacking => _isAttacking;
|
||||
|
||||
protected ITargetSelector TargetSelector { get; set; }
|
||||
|
||||
protected Dictionary<WeaponStateType, WeaponStateBase> _states;
|
||||
|
||||
protected WeaponStateBase _currentState;
|
||||
|
||||
protected EntityBase _target;
|
||||
|
||||
protected float _currAttackTimer;
|
||||
|
||||
protected float _sqrRange;
|
||||
|
||||
protected StatProperty AttackStat { get; private set; } = new();
|
||||
|
||||
private StatComponent _attackStatComponent;
|
||||
private System.Action<StatModifier, bool> _attackStatCallback;
|
||||
|
||||
private static readonly List<EntityBase> s_EmptyCandidates = new();
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
if (TargetSelector == null)
|
||||
{
|
||||
TargetSelector = CreateSelector(DefaultTargetSelectorType);
|
||||
}
|
||||
}
|
||||
|
||||
protected sealed override void OnShow(object userData)
|
||||
{
|
||||
base.OnShow(userData);
|
||||
|
||||
if (!OnWeaponShow(userData)) return;
|
||||
|
||||
EnsureStatesBuilt();
|
||||
TransitionTo(InitialState);
|
||||
}
|
||||
|
||||
protected sealed override void OnHide(bool isShutdown, object userData)
|
||||
{
|
||||
ReleaseAttackStatSubscription();
|
||||
OnWeaponHide(userData);
|
||||
base.OnHide(isShutdown, userData);
|
||||
}
|
||||
|
||||
protected sealed override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
{
|
||||
base.OnAttachTo(parentEntity, parentTransform, userData);
|
||||
|
||||
Name = Utility.Text.Format("Weapon of {0}", parentEntity.Name);
|
||||
CachedTransform.localPosition = Vector3.zero;
|
||||
|
||||
OnWeaponAttach(parentEntity, parentTransform, userData);
|
||||
}
|
||||
|
||||
protected sealed override void OnDetachFrom(EntityLogic parentEntity, object userData)
|
||||
{
|
||||
OnWeaponDetach(parentEntity, userData);
|
||||
base.OnDetachFrom(parentEntity, userData);
|
||||
}
|
||||
|
||||
protected virtual bool OnWeaponShow(object userData) => true;
|
||||
|
||||
protected virtual void OnWeaponHide(object userData)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void OnWeaponDetach(EntityLogic parentEntity, object userData)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
||||
if (!_isEnabled) return;
|
||||
_currentState?.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public abstract ImpactData GetImpactData();
|
||||
protected abstract void Attack();
|
||||
protected abstract void Check();
|
||||
protected abstract void BuildStates();
|
||||
|
||||
protected virtual WeaponStateType InitialState => WeaponStateType.Idle;
|
||||
protected virtual TargetSelectorType DefaultTargetSelectorType => TargetSelectorType.Nearest;
|
||||
|
||||
protected void RegisterState(WeaponStateBase state)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
Log.Error("Weapon state is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
state.OnInit(this);
|
||||
_states[state.State] = state;
|
||||
}
|
||||
|
||||
protected void EnsureStatesBuilt()
|
||||
{
|
||||
if (_states != null) return;
|
||||
|
||||
_states = new Dictionary<WeaponStateType, WeaponStateBase>();
|
||||
BuildStates();
|
||||
}
|
||||
|
||||
protected virtual void TransitionTo(WeaponStateType newState)
|
||||
{
|
||||
if (_states == null || !_states.TryGetValue(newState, out var nextState))
|
||||
{
|
||||
Log.Error("Weapon state not found: {0}", newState);
|
||||
return;
|
||||
}
|
||||
|
||||
_currentState?.OnLeave();
|
||||
_currentState = nextState;
|
||||
_currentState.OnEnter();
|
||||
}
|
||||
|
||||
public void SetEnabled(bool value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
|
||||
OnEnabledChanged(value);
|
||||
|
||||
if (_states == null || _states.Count == 0) return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
TransitionTo(InitialState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_states.ContainsKey(WeaponStateType.Disabled))
|
||||
{
|
||||
TransitionTo(WeaponStateType.Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnabledChanged(bool enabled)
|
||||
{
|
||||
}
|
||||
|
||||
protected T RequireWeaponData<T>(object userData) where T : WeaponData
|
||||
{
|
||||
var data = userData as T;
|
||||
if (data == null)
|
||||
{
|
||||
Log.Error("{0} data is invalid.", typeof(T).Name);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<EntityBase> GetCandidates()
|
||||
{
|
||||
var enemyManager = GameEntry.EnemyManager;
|
||||
return enemyManager != null ? enemyManager.Enemies : s_EmptyCandidates;
|
||||
}
|
||||
|
||||
protected EntityBase SelectTarget(float maxSqrRange)
|
||||
{
|
||||
return TargetSelector != null ? TargetSelector.SelectTarget(this, GetCandidates(), maxSqrRange) : null;
|
||||
}
|
||||
|
||||
protected float GetSqrDistance(EntityBase target)
|
||||
{
|
||||
return target != null ? AIUtility.GetSqrMagnitudeXZ(this, target) : float.MaxValue;
|
||||
}
|
||||
|
||||
protected bool IsInRange(EntityBase target, float sqrRange)
|
||||
{
|
||||
if (target == null) return false;
|
||||
return AIUtility.GetSqrMagnitudeXZ(this, target) < sqrRange;
|
||||
}
|
||||
|
||||
protected bool TryQueueAreaCollisionQuery(in Vector3 center, float radius, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestAreaCollision(Id, ownerEntityId, in center, radius, maxTargets);
|
||||
}
|
||||
|
||||
protected bool TryQueueSectorCollisionQuery(in Vector3 center, float radius, in Vector3 direction,
|
||||
float halfAngleDeg, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestSectorCollision(Id, ownerEntityId, in center, radius, in direction,
|
||||
halfAngleDeg, maxTargets);
|
||||
}
|
||||
|
||||
protected bool TryQueueRectangleCollisionQuery(in Vector3 center, float halfWidth, float halfLength,
|
||||
in Vector3 direction, int maxTargets = 16)
|
||||
{
|
||||
var simulationWorld = GameEntry.SimulationWorld;
|
||||
if (simulationWorld == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
|
||||
return simulationWorld.TryRequestRectangleCollision(Id, ownerEntityId, in center, halfWidth, halfLength,
|
||||
in direction, maxTargets);
|
||||
}
|
||||
|
||||
protected void SetTargetSelector(TargetSelectorType selectorType)
|
||||
{
|
||||
TargetSelector = CreateSelector(selectorType);
|
||||
}
|
||||
|
||||
protected static ITargetSelector CreateSelector(TargetSelectorType selectorType)
|
||||
{
|
||||
switch (selectorType)
|
||||
{
|
||||
case TargetSelectorType.HighestHealth:
|
||||
return new HighestHealthTargetSelector();
|
||||
case TargetSelectorType.LowestHealth:
|
||||
return new LowestHealthTargetSelector();
|
||||
case TargetSelectorType.Nearest:
|
||||
default:
|
||||
return new NearestTargetSelector();
|
||||
}
|
||||
}
|
||||
|
||||
protected void BindAttackStatFromOwner(EntityLogic parentEntity)
|
||||
{
|
||||
ReleaseAttackStatSubscription();
|
||||
AttackStat = new StatProperty();
|
||||
|
||||
if (parentEntity is not Player player) return;
|
||||
|
||||
var statComponent = player.GetComponent<StatComponent>();
|
||||
if (statComponent == null) return;
|
||||
|
||||
AttackStat = statComponent.GetStat(StatType.Attack);
|
||||
_attackStatComponent = statComponent;
|
||||
_attackStatCallback = (modifier, isApply) =>
|
||||
statComponent.UpdateStat(AttackStat, modifier, isApply);
|
||||
statComponent.Subscribe(StatType.Attack, _attackStatCallback);
|
||||
}
|
||||
|
||||
protected void ReleaseAttackStatSubscription()
|
||||
{
|
||||
if (_attackStatComponent != null && _attackStatCallback != null)
|
||||
{
|
||||
_attackStatComponent.Unsubscribe(StatType.Attack, _attackStatCallback);
|
||||
}
|
||||
|
||||
_attackStatComponent = null;
|
||||
_attackStatCallback = null;
|
||||
AttackStat = new StatProperty();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class WeaponStateBase
|
||||
{
|
||||
public abstract WeaponStateType State { get; }
|
||||
public abstract void OnInit(WeaponBase weapon);
|
||||
public abstract void OnEnter();
|
||||
public abstract void OnUpdate(float elapseSeconds, float realElapseSeconds);
|
||||
public abstract void OnLeave();
|
||||
public override string ToString() => State.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc18f1a1232bb9644be1191168741922
|
||||
timeCreated: 1528026154
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b289e88dc5243aeb564df941485c5cf
|
||||
timeCreated: 1771579168
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponHandgun
|
||||
{
|
||||
private class AttackState : WeaponStateBase
|
||||
{
|
||||
private WeaponHandgun _weapon;
|
||||
public override WeaponStateType State => WeaponStateType.Attack;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
_weapon._currAttackTimer = 0f;
|
||||
_weapon.Attack();
|
||||
}
|
||||
|
||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Check_InRange);
|
||||
}
|
||||
|
||||
public override void OnLeave() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e72df067466464fa3be92bfd049dd0d
|
||||
timeCreated: 1771579220
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponHandgun
|
||||
{
|
||||
private class CheckInRangeState : WeaponStateBase
|
||||
{
|
||||
private WeaponHandgun _weapon;
|
||||
public override WeaponStateType State => WeaponStateType.Check_InRange;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
|
||||
|
||||
public override void OnEnter()
|
||||
{
|
||||
if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Attack);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
_weapon.Check();
|
||||
_weapon.RotateToTarget(elapseSeconds);
|
||||
_weapon._currAttackTimer += elapseSeconds;
|
||||
|
||||
if (_weapon._target == null || !_weapon._target.Available)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_weapon.IsInRange(_weapon._target, _weapon._sqrRange))
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Attack);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLeave() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfd76c0fb7284ceaa28f78c702f595da
|
||||
timeCreated: 1771579231
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponHandgun
|
||||
{
|
||||
private class CheckOutRangeState : WeaponStateBase
|
||||
{
|
||||
private WeaponHandgun _weapon;
|
||||
public override WeaponStateType State => WeaponStateType.Check_OutRange;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
|
||||
public override void OnEnter() { }
|
||||
|
||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
_weapon.Check();
|
||||
_weapon.RotateToTarget(elapseSeconds);
|
||||
_weapon._currAttackTimer += elapseSeconds;
|
||||
|
||||
if (_weapon._target == null || !_weapon._target.Available)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Idle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_weapon.IsInRange(_weapon._target, _weapon._sqrRange))
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Check_InRange);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLeave() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a9f936d3c8643f7bf632734548f800c
|
||||
timeCreated: 1771579241
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponHandgun
|
||||
{
|
||||
private class IdleState : WeaponStateBase
|
||||
{
|
||||
private WeaponHandgun _weapon;
|
||||
public override WeaponStateType State => WeaponStateType.Idle;
|
||||
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
|
||||
public override void OnEnter() { }
|
||||
|
||||
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
_weapon.Check();
|
||||
_weapon.RotateToOrigin(elapseSeconds);
|
||||
_weapon._currAttackTimer += elapseSeconds;
|
||||
|
||||
if (_weapon._target != null && _weapon._target.Available)
|
||||
{
|
||||
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnLeave() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c5341944d1a467b8553d31bc3d81d5b
|
||||
timeCreated: 1771579252
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
using SepCore.Definition;
|
||||
using Entity.EntityData;
|
||||
using CustomUtility;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace Entity.Weapon
|
||||
{
|
||||
public partial class WeaponHandgun : WeaponBase
|
||||
{
|
||||
#region Property
|
||||
|
||||
private WeaponHandgunData _weaponData;
|
||||
|
||||
private Quaternion _cachedRotation;
|
||||
|
||||
[SerializeField] private float _rotateSpeed = 6f;
|
||||
[SerializeField] private LayerMask _hitMask = ~0;
|
||||
[SerializeField] private Vector3 _fireOriginOffset = Vector3.zero;
|
||||
|
||||
[SerializeField] private float _hitMarkerSize = 0.2f;
|
||||
[SerializeField] private float _hitMarkerYOffset = 1.2f;
|
||||
[SerializeField] private float _hitMarkerDuration = 0.15f;
|
||||
[SerializeField] private Color _hitMarkerColor = new(1f, 0f, 0f, 0.95f);
|
||||
|
||||
private IWeaponAttackEffect _attackEffect;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public override ImpactData GetImpactData()
|
||||
{
|
||||
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
|
||||
}
|
||||
|
||||
protected override void BuildStates()
|
||||
{
|
||||
RegisterState(new IdleState());
|
||||
RegisterState(new CheckOutRangeState());
|
||||
RegisterState(new CheckInRangeState());
|
||||
RegisterState(new AttackState());
|
||||
}
|
||||
|
||||
protected override void Attack()
|
||||
{
|
||||
FaceTargetImmediately();
|
||||
|
||||
if (!TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_attackEffect?.Play(this, hitPosition, targetable, 0f);
|
||||
_isAttacking = true;
|
||||
AIUtility.PerformCollision(targetable, this);
|
||||
_isAttacking = false;
|
||||
}
|
||||
|
||||
protected override void Check()
|
||||
{
|
||||
_target = SelectTarget(_sqrRange);
|
||||
}
|
||||
|
||||
private void RotateToTarget(float elapseSeconds)
|
||||
{
|
||||
if (_target == null || !_target.Available) return;
|
||||
|
||||
Vector3 directionToTarget = (_target.CachedTransform.position - CachedTransform.position).normalized;
|
||||
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget, Vector3.up);
|
||||
CachedTransform.rotation = Quaternion.Slerp(CachedTransform.rotation, targetRotation,
|
||||
_rotateSpeed * elapseSeconds);
|
||||
}
|
||||
|
||||
private void RotateToOrigin(float elapseSeconds)
|
||||
{
|
||||
CachedTransform.rotation = Quaternion.Slerp(CachedTransform.rotation, _cachedRotation,
|
||||
_rotateSpeed * elapseSeconds);
|
||||
}
|
||||
|
||||
private void FaceTargetImmediately()
|
||||
{
|
||||
if (_target == null || !_target.Available) return;
|
||||
|
||||
Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position;
|
||||
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
|
||||
|
||||
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
|
||||
}
|
||||
|
||||
private bool TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition)
|
||||
{
|
||||
targetable = _target as TargetableObject;
|
||||
hitPosition = CachedTransform.position;
|
||||
if (targetable == null || !targetable.Available || targetable.IsDead)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform targetTransform = targetable.CachedTransform;
|
||||
if (targetTransform == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
hitPosition = targetTransform.position;
|
||||
|
||||
Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset);
|
||||
Vector3 directionToTarget = targetTransform.position - fireOrigin;
|
||||
float maxDistance = Mathf.Max(0.1f, _weaponData.AttackRange);
|
||||
if (directionToTarget.sqrMagnitude > Mathf.Epsilon &&
|
||||
Physics.Raycast(fireOrigin, directionToTarget.normalized, out RaycastHit hit, maxDistance, _hitMask,
|
||||
QueryTriggerInteraction.Collide))
|
||||
{
|
||||
TargetableObject raycastTarget = hit.collider.GetComponentInParent<TargetableObject>();
|
||||
if (raycastTarget == targetable)
|
||||
{
|
||||
hitPosition = hit.point;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
protected override bool OnWeaponShow(object userData)
|
||||
{
|
||||
_weaponData = RequireWeaponData<WeaponHandgunData>(userData);
|
||||
if (_weaponData == null) return false;
|
||||
|
||||
WeaponData = _weaponData;
|
||||
_currAttackTimer = 0f;
|
||||
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
|
||||
_cachedRotation = CachedTransform.rotation;
|
||||
_attackEffect = new HandgunHitMarkerAttackEffect(_hitMarkerSize, _hitMarkerYOffset, _hitMarkerDuration,
|
||||
_hitMarkerColor);
|
||||
|
||||
if (_weaponData.OwnerCamp == CampType.Player)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("PlayerWeapon");
|
||||
_hitMask = LayerMask.GetMask("Enemy");
|
||||
}
|
||||
else if (_weaponData.OwnerCamp == CampType.Enemy)
|
||||
{
|
||||
gameObject.layer = LayerMask.NameToLayer("EnemyWeapon");
|
||||
_hitMask = LayerMask.GetMask("Player");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnWeaponHide(object userData)
|
||||
{
|
||||
_attackEffect = null;
|
||||
}
|
||||
|
||||
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
|
||||
{
|
||||
BindAttackStatFromOwner(parentEntity);
|
||||
}
|
||||
|
||||
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
|
||||
{
|
||||
ReleaseAttackStatSubscription();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44ae1bdd1d43e274980743df21658730
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f89893f21b3046b7a93a368cd688451e
|
||||
timeCreated: 1771578597
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user