using UnityEngine;
using UniRx;
using UniRx.Triggers;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 攻撃処理 Presenter
/// </summary>
public class AttackModePresenter : ISetup {
private LifeBase attackTarget;
private ReactiveProperty<bool> IsAttackReady = new(false);
private AttackModeModel myAttackModeModel;
private ActionGuageBar attackModeView;
private HpBar hpBarView;
private AttackRangeCollider attackRangeCollider;
/// <summary>
/// コンストラクタ
/// </summary>
public AttackModePresenter() {}
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="spd"></param>
/// <param name="attackPower"></param>
/// <param name="attackRange"></param>
public AttackModePresenter(float spd, int atk, int inte, int def, int mdef, float attackRange, DamageType damageType, eAttribute attributeType, int minDmg = 0) {
myAttackModeModel = new(spd, atk, inte, def, mdef, attackRange, damageType, attributeType, minDmg);
}
/// <summary>
/// 初期設定
/// </summary>
/// <param name="entityObject"></param>
public void SetUp(GameObject entityObject) {
// entityObject から攻撃用のデータを参照してもらう
if (entityObject.TryGetComponent(out attackModeView)) {
attackModeView.SetUpView(ConstData.MAX_ACTION_GAUGE_POINT);
}
// Hp 取得
LifeBase myLife;
if (!entityObject.TryGetComponent(out myLife)) {
return;
}
myAttackModeModel = myLife.AttackModeModel;
// HpBar 取得
if (entityObject.TryGetComponent(out hpBarView)) {
hpBarView.SetUpView(myLife.Hp.Value);
}
// Hp の購読 Hp ゲージ更新
myLife.Hp.Subscribe(hp => hpBarView.UpdateBar(myLife.Hp.Value)).AddTo(entityObject);
// 攻撃範囲用のコライダーの取得ができない場合、処理しない
if (!entityObject.TryGetComponent(out attackRangeCollider)) {
return;
}
// 攻撃範囲のコライダーのサイズを設定
attackRangeCollider.SetUpColliderRange(myAttackModeModel.AttackRange);
// モック用
if (myLife.EntityType == EntityType.Enemy) {
return;
}
// 攻撃対象となるタグを設定
string tag = myLife.GetTargetEntityType().ToString();
// 攻撃対象の探索
entityObject.UpdateAsObservable()
.Subscribe(_ => SearchAttackTarget(entityObject, tag));
// 侵入判定の購読 自動攻撃準備
//attackRangeCollider.AttackRange.OnTriggerStay2DAsObservable()
// .Where(col => attackTarget == null)
// .Where(col => col.transform.TryGetComponent(out attackTarget)) // コライダーへの侵入判定
// .Where(_ => myLife.CheckTarget(attackTarget.EntityType)) // 攻撃対象か判定
// .Subscribe(col => {
// // 攻撃準備開始とゲージ表示
// IsAttackReady.Value = true;
// attackModeView.ShowBarView();
// Debug.Log($"攻撃準備 開始 {attackTarget}");
// });
// いなくなった判定の購読 自動攻撃停止
//attackRangeCollider.AttackRange.OnTriggerExit2DAsObservable()
// .Where(col => col.transform.TryGetComponent(out attackTarget)) // コライダーへの侵入判定
// .Where(_ => myLife.CheckTarget(attackTarget.EntityType)) // 攻撃対象か判定
// .Subscribe(col => StopAttack());
// Update の購読
entityObject.UpdateAsObservable()
.Where(_ => IsAttackReady.Value)
.Where(_ => myAttackModeModel.CurrentActionGaugePoint.Value < myAttackModeModel.MaxActionGaugePoint)
.Subscribe(_ => {
// 攻撃ゲージの加算
myAttackModeModel.CurrentActionGaugePoint.Value += myAttackModeModel.AttackSpeed * Time.deltaTime;
// 攻撃ゲージの View 更新
attackModeView.UpdateBar(myAttackModeModel.CurrentActionGaugePoint.Value);
});
// 攻撃ゲージの購読
myAttackModeModel.CurrentActionGaugePoint
.Where(_ => attackTarget)
.Where(currentActionGaugePoint => currentActionGaugePoint >= myAttackModeModel.MaxActionGaugePoint)
.Subscribe(_ => {
if (attackTarget == null) {
// アクションゲージリセット
myAttackModeModel.ResetActionGaugePoint();
} else {
// 自動攻撃
AutoAttack();
}
})
.AddTo(entityObject);
// 破壊時の購読
entityObject.OnDestroyAsObservable().Subscribe(_ => myAttackModeModel.Dispose());
// TODO 名前表示
//Debug.Log($"{this} Setup 完了");
}
/// <summary>
/// 自動攻撃
/// </summary>
private void AutoAttack() {
//Debug.Log("自動攻撃");
// 属性係数算出
float attributeRate = BattleCalculater.CalcAttributeRate(myAttackModeModel.AttributeType, attackTarget.AttackModeModel.AttributeType);
//Debug.Log($"属性補正 : {attributeRate}");
//Debug.Log($"攻撃力 : {myAttackModeModel.Atk}");
//Debug.Log($"知力 : {myAttackModeModel.Inte}");
//Debug.Log($"防御力 : {attackTarget.AttackModeModel.Def}");
// 現在の攻撃タイプを元にダメージ計算
int damage = myAttackModeModel.DamageType == DamageType.atk
? BattleCalculater.CalcAtkDamage(myAttackModeModel.Atk, myAttackModeModel.Inte, attributeRate, attackTarget.AttackModeModel.Def) // 物理ダメージ
: BattleCalculater.CalcInteDamage(myAttackModeModel.Inte, myAttackModeModel.Atk, attributeRate, attackTarget.AttackModeModel.Mdef); // 魔法ダメージ
Debug.Log($"最終ダメージ : {damage}");
// アクションゲージリセット
myAttackModeModel.ResetActionGaugePoint();
// 攻撃対象が残っているか確認
if (attackTarget == null) {
Debug.Log("攻撃対象消失");
return;
}
// ダメージ値をフロート表示。オブジェクトプール利用
attackTarget.ShowFloatingView(damage);
//FloatingView floatingView = (FloatingView)FloatingViewGenerator.instance.GetObjectFromPool(attackTarget.transform.position, attackTarget.transform.rotation);
//floatingView.SetUpView(damage.ToString());
// Hp 計算。対象の 残 hp が 0 以下なら
if (!attackTarget.CalcLife(-damage)) {
// 自動攻撃停止
StopAttack();
}
//Debug.Log("攻撃完了");
}
/// <summary>
/// 自動攻撃停止
/// </summary>
private void StopAttack() {
attackTarget = null;
// 攻撃停止。アクションゲージリセットとゲージ非表示
IsAttackReady.Value = false;
myAttackModeModel.ResetActionGaugePoint();
attackModeView.HideBarView();
//Debug.Log("攻撃停止");
}
/// <summary>
/// 攻撃力などのデータ更新
/// </summary>
/// <param name="attackModeModel"></param>
public void UpdateAttackModel(AttackModeModel attackModeModel, GameObject entityObject) {
myAttackModeModel = attackModeModel;
myAttackModeModel.Dispose();
// 攻撃ゲージの購読やり直し
myAttackModeModel.CurrentActionGaugePoint
.Where(_ => attackTarget)
.Where(currentActionGaugePoint => currentActionGaugePoint >= myAttackModeModel.MaxActionGaugePoint)
.Subscribe(_ => AutoAttack())
.AddTo(entityObject);
Debug.Log("更新");
}
/// <summary>
/// 攻撃対象の探索
/// </summary>
/// <param name="entityObject"></param>
/// <param name="tag"></param>
private void SearchAttackTarget(GameObject entityObject, string tag) {
// OverlapCircleAll メソッドを利用して、範囲内のコライダー付きのオブジェクトを取得
//Collider2D[] hitColliders = Physics2D.OverlapCircleAll(entityObject.transform.position, myAttackModeModel.AttackRange / 2);
//List<GameObject> objList = new();
//// 配列内の要素を1つずつ取り出す
//foreach (Collider2D collider in hitColliders) {
// // 指定されたタグを持つオブジェクトであるか判定。それを攻撃対象候補として認識
// if (collider.gameObject.CompareTag(tag)) {
// objList.Add(collider.gameObject);
// }
//}
// 上記を LINQ で記述
List<GameObject> objList = Physics2D.OverlapCircleAll(entityObject.transform.position, myAttackModeModel.AttackRange / 2)
.Where(collider => collider.CompareTag(tag))
.Select(collider => collider.gameObject)
.ToList();
// 取得したゲームオブジェクトが 0 なら攻撃対象の候補なし
if (objList.Count == 0) {
Debug.Log("攻撃対象なし");
StopAttack();
return;
}
// 最も近いオブジェクトの距離を代入するための変数
//float nearDistance = 0;
// 検索された最も近いゲームオブジェクトを代入するための変数
//GameObject searchTargetObj = null;
// objsから1つずつobj変数に取り出す
//foreach (GameObject obj in objList) {
// // objに取り出したゲームオブジェクトと、このゲームオブジェクトとの距離を計算して取得
// float distance = Vector3.Distance(obj.transform.position, entityObject.transform.transform.position);
// // nearDistanceが0(最初はこちら)、あるいはnearDistanceがdistanceよりも大きい値なら
// if (nearDistance == 0 || nearDistance > distance) {
// // nearDistanceを更新
// nearDistance = distance;
// // searchTargetObjを更新
// searchTargetObj = obj;
// }
//}
// 上記を LINQ で記述。最も近い対象を抽出
GameObject searchTargetObj = objList
.OrderBy(obj => Vector3.Distance(obj.transform.position, entityObject.transform.position))
.FirstOrDefault();
//最も近かったオブジェクトを攻撃対象とする
attackTarget = searchTargetObj.GetComponent<LifeBase>();
// 攻撃準備開始とゲージ表示
IsAttackReady.Value = true;
attackModeView.ShowBarView();
Debug.Log($"攻撃準備 開始 {attackTarget}");
}
}