Compare commits

...

19 Commits

Author SHA1 Message Date
Sehyeon
28c72be3b8 Merge branch 'main' into DEG-54-엔딩-로직 2025-04-29 16:19:45 +09:00
5dcde907a1 Merge pull request 'DEG-100 원거리-마법사-보스-몬스터-개발-단계' (!3) from DEG-100-원거리-마법사-보스-몬스터-개발-단계 into main
Reviewed-on: #3
Reviewed-by: Sehyeon <sehyeon1837@gmail.com>
Reviewed-by: Lim0_C <dladudcks22@gmail.com>
2025-04-29 07:07:06 +00:00
Fiore
2708df17df [feat] 랜덤 패턴 수행을 위한 메서드 추가
Work in JIRA ISSUE DEG-100
2025-04-29 16:06:07 +09:00
Fiore
882ef3b8dc 더미 몬스터 프리팹화
Work in JIRA ISSUE DEG-100
2025-04-29 15:25:01 +09:00
Fiore
d0d42cf551 제자리로 판단하는 거리 조정
Work in JIRA ISSUE DEG-100
2025-04-29 15:18:55 +09:00
Fiore
ea45936114 몬스터 테스트 씬 변경사항 저장
Work in JIRA ISSUE DEG-100
2025-04-29 15:02:25 +09:00
Fiore
18211fff05 feat : 몬스터 순간이동 구현
- 몬스터가 구석에 몰려 더이상 움직일 수 없는 경우 중앙으로 워프

Work in JIRA ISSUE DEG-100
2025-04-29 15:00:50 +09:00
Fiore
50e5b8db98 feat : 매직미사일 기본 동작 구현
- 플레이어가 움직이는 방향을 참고하여 예측샷 발사
2025-04-29 14:00:03 +09:00
Fiore
393b538920 feat : bullet base 작성 완료
- 이동속도, 데미지, 라이프타임을 받음
- bulletdata를 받아 초기화 진행
- 발사 위치를 기준으로 목표 방향 세팅
- 테스트를 위해 초기값 임의로 작성

work in DEG-100 JIRA
2025-04-29 11:43:58 +09:00
Fiore
bc88d47e6b [feat] 매직미사일 구현 시작
- 몬스터가 사용할 총알 베이스 생성

work in DEG-100
2025-04-29 11:13:18 +09:00
b5a39714f0 Merge pull request 'DEG-116-액션과-컷씬-연동-재작업' (#2) from DEG-116-액션과-컷씬-연동-재작업 into main
Reviewed-on: #2
Reviewed-by: Sehyeon <sehyeon1837@gmail.com>
Reviewed-by: fiore <cjsdlf44@gmail.com>
2025-04-29 01:45:02 +00:00
HaeinLEE
e1c1f6a329 [Fix] 폰트 재설치 2025-04-29 10:43:41 +09:00
Fiore
99082eeb7c [feat] 마법사 몬스터의 도망 로직 정리
- 경로 탐색 주기를 설정해 성능 향상
- 상태 진입과 이탈시 초기화처리
- 버그 수정 : 플레이어와 거리를 체크하고 전투 시작
- 경로 탐색 성능 향상
- 배틀 시퀸스 프레임 작성

work in #DEG-100
2025-04-29 09:26:09 +09:00
HaeinLEE
406dc8f84f [Style] 개인 프리팹 정리 2025-04-28 21:36:38 +09:00
HaeinLEE
c262b9e2f7 [Feat] 컷씬 자동 꺼짐 기능 2025-04-28 21:24:43 +09:00
HaeinLEE
cdded898ec [Feat] 기존 커밋 복구 2025-04-28 17:44:00 +09:00
Lim0_C
2ebeccfa69 [HOTFIX] 메인UI 복구 수정
씬 저장이슈
2025-04-28 15:35:07 +09:00
4e7ca0526c Merge pull request 'DEG-85 메인화면UI 복구' (#1) from DEG-85-메인화면-UI-복구 into main
Reviewed-on: #1
Reviewed-by: heain0122 <heain0122@gmail.com>
2025-04-28 06:27:02 +00:00
Lim0_C
976cfc793a DEG-85 메인화면UI 복구 2025-04-28 15:12:29 +09:00
49 changed files with 279989 additions and 1318 deletions

13
.idea/.idea.Degulleo3D116Rework/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# 디폴트 무시된 파일
/shelf/
/workspace.xml
# Rider에서 무시된 파일
/contentModel.xml
/projectSettingsUpdater.xml
/modules.xml
/.idea.Degulleo3D116Rework.iml
# 에디터 기반 HTTP 클라이언트 요청
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 37e30a87d9c904c898232118c82b5fbc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/JYY/Prefabs/Bullets/Dummy Magic Missaile.prefab (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5b14d6aa88de82f449e47d835ebf73c7
guid: 9c16b44c8736e4007ad5f0733ce433e1
PrefabImporter:
externalObjects: {}
userData:

BIN
Assets/JYY/Prefabs/[Enemy] Dummy Monster.prefab (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 06eebdb9d2c03437fb632d9e15fd1078
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Assets/JYY/Scenes/MonsterTest.unity (Stored with Git LFS)

Binary file not shown.

BIN
Assets/LIN/Housing Copy.unity (Stored with Git LFS)

Binary file not shown.

BIN
Assets/LIN/Prefabs/Canvas.prefab (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

8
Assets/LIN/Scripts.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 94531ff12d388974a9a50a9e33997d6e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -12,6 +12,7 @@ public class InteractionController : MonoBehaviour
[FormerlySerializedAs("housingCanvasManager")]
[Header("UI 연동")]
[SerializeField] HousingCanvasController housingCanvasController;
[SerializeField] private InteractionAnimationPanelController interactionAnimationPanelController;
private SuddenEventController _suddenEventController = new SuddenEventController();
@ -38,6 +39,7 @@ public class InteractionController : MonoBehaviour
if (interactionLayerMask == (interactionLayerMask | (1 << other.gameObject.layer)))
{
housingCanvasController.HideInteractionButton();
housingCanvasController.interactionTextsController.InitInteractionTexts();
}
}
@ -51,11 +53,12 @@ public class InteractionController : MonoBehaviour
if (playerStats.CanPerformByHealth(interactionType))
{
playerStats.PerformAction(interactionType);
interactionAnimationPanelController.ShowAnimationPanel(interactionType,interactionTexts.AnimationText);
}
else
{
housingCanvasController.SetActionText(interactionTexts.LackOfHealth);
housingCanvasController.SetDescriptionText();
housingCanvasController.interactionTextsController.ActiveTexts(interactionTexts.LackOfHealth);
}
});
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b5a9f238e478db241bcae4cd9192a195
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -3,13 +3,13 @@ using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class HousingCanvasController : MonoBehaviour
{
[Header("일상행동 상호작용")]
[SerializeField] GameObject interactionButton;
[SerializeField] TMP_Text actionText;
[SerializeField] TMP_Text descriptionText;
[SerializeField] private GameObject interactionButton;
public InteractionTextsContoller interactionTextsController;
[Header("돌발 이벤트")]
[SerializeField] private GameObject suddenPanel;
@ -20,34 +20,16 @@ public class HousingCanvasController : MonoBehaviour
void Awake()
{
InitInteractionTexts();
interactionTextsController.InitInteractionTexts();
interactionButton.SetActive(false);
suddenPanel.SetActive(false);
}
#region
//사물 이름 세팅
public void SetActionText(string text = "")
{
actionText.text = text;
}
//사물 상호작용 내용 설명
public void SetDescriptionText(string text = "")
{
descriptionText.text = text;
}
private void InitInteractionTexts()
{
SetActionText();
SetDescriptionText();
}
// 상호작용 가능한 사물에 가까이 갔을 때 화면에 텍스트, 버튼 표시
public void ShowInteractionButton(string actText, string descText,Action onInteractionButtonPressed)
{
SetActionText(actText);
SetDescriptionText(descText);
interactionTextsController.ActiveTexts(actText, descText);
interactionButton.SetActive(true);
//각 행동 별로 실행되어야 할 이벤트 구독
@ -56,8 +38,6 @@ public class HousingCanvasController : MonoBehaviour
//범위에서 벗어나면 상호작용 버튼 off
public void HideInteractionButton()
{
SetActionText();
SetDescriptionText();
interactionButton.SetActive(false);
//구독해놓은 이벤트 해제
@ -67,8 +47,13 @@ public class HousingCanvasController : MonoBehaviour
//상호작용 버튼 눌렀을 때
public void OnClickInteractionButton()
{
//상호작용 별 행동 수행
OnInteractionButtonPressed?.Invoke();
OnInteractionButtonPressed = null;
//상호작용 버튼과 텍스트 숨김
HideInteractionButton();
interactionTextsController.InitInteractionTexts();
}
#endregion

View File

@ -20,10 +20,15 @@ public static class HousingConstants
public static readonly Dictionary<ActionType, InteractionTexts> interactions =
new Dictionary<ActionType, InteractionTexts>
{
{ ActionType.Sleep, new InteractionTexts("침대에서 잘까?","숙면으로 시간 당 체력 1을 회복한다.", ".")},
{ ActionType.Housework, new InteractionTexts("밀린 집안일을 처리할까?","체력 1을 사용하고 좋은일이 일어날지도 모른다","힘들어서 못해..")},
{ ActionType.Dungeon, new InteractionTexts("던전에 입장할까?","체력 3을 사용하고 3시간이 흐른다.","던전에 갈 체력이 되지 않아..")},
{ ActionType.Work, new InteractionTexts("출근한다.","체력 3을 사용하고 저녁 6시에나 돌아오겠지..", "도저히 출근할 체력이 안되는걸..?")}
{ ActionType.Sleep, new InteractionTexts("침대에서 잘까?","숙면으로 시간 당 체력 1을 회복한다.",
".","쿨쿨 자는 중")},
{ ActionType.Housework, new InteractionTexts("밀린 집안일을 처리할까?","체력 1을 사용하고 좋은일이 일어날지도 모른다",
"힘들어서 못해..","세탁하는 중")},
{ ActionType.Dungeon, new InteractionTexts("던전에 입장할까?","체력 3을 사용하고 3시간이 흐른다.",
"던전에 갈 체력이 되지 않아..","던전 진입하는 중")},
{ ActionType.Work, new InteractionTexts("출근한다.","체력 3을 사용하고 저녁 6시에나 돌아오겠지..",
"도저히 출근할 체력이 안되는걸..?","출근하는 중")},
{ActionType.Eat, new InteractionTexts("식사를 하자","1시간 동안 체력 1을 회복한다.","밥 먹는 중")}
};
#endregion
@ -32,12 +37,14 @@ public static class HousingConstants
public string ActionText { get; private set; }
public string DescriptionText { get; private set; }
public string LackOfHealth { get; private set; }
public string AnimationText { get; private set; }
public InteractionTexts(string actionText, string descriptionText, string lackOfHealth)
public InteractionTexts(string actionText, string descriptionText, string lackOfHealth, string animationText = "")
{
ActionText = actionText;
DescriptionText = descriptionText;
LackOfHealth = lackOfHealth;
AnimationText = animationText;
}
}
}

View File

@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class InteractionAnimationPanelController : MonoBehaviour
{
[SerializeField] private GameObject panel;
[SerializeField] private Image doingImage;
[SerializeField] private TMP_Text doingText;
[SerializeField] private Animator animator;
[SerializeField] private float animationDuration = 2.0f;
private Coroutine _textAnimCoroutine;
private Coroutine _autoHideCoroutine;
public void SetDoingText(string text)
{
doingText.text = text;
}
public void ShowAnimationPanel(ActionType actionType, string animationText)
{
// 1) 패널 활성화
panel.SetActive(true);
// 2) 기존 코루틴 정리
if (_textAnimCoroutine != null) StopCoroutine(_textAnimCoroutine);
if (_autoHideCoroutine != null) StopCoroutine(_autoHideCoroutine);
// 3) 텍스트 및 애니메이션 세팅
doingText.text = animationText;
switch (actionType)
{
case ActionType.Sleep:
break;
case ActionType.Work:
break;
case ActionType.Eat:
break;
case ActionType.Dungeon:
break;
case ActionType.Housework:
animator.Play("Laundry");
break;
}
_textAnimCoroutine = StartCoroutine(TextDotsAnimation());
_autoHideCoroutine = StartCoroutine(AutoHidePanel());
}
private IEnumerator TextDotsAnimation()
{
var tempText = doingText.text;
float startTime = Time.time;
while (Time.time - startTime < 3)
{
for (int i = 0; i < 3; i++)
{
yield return new WaitForSeconds(0.3f);
doingText.text = tempText + new string('.', i + 1);
}
yield return new WaitForSeconds(0.3f);
}
}
/// <summary>
/// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다.
/// </summary>
/// <returns></returns>
private IEnumerator AutoHidePanel()
{
float startTime = Time.time;
while (Time.time - startTime < animationDuration)
{
if (Input.touchCount > 0 || Input.GetMouseButtonDown(0))
{
break;
}
yield return null;
}
//패널 닫고 애니메이션 null처리
HidePanel();
_autoHideCoroutine = null;
}
private void HidePanel()
{
panel.SetActive(false);
if (_textAnimCoroutine != null)
{
StopCoroutine(_textAnimCoroutine);
_textAnimCoroutine = null;
}
if (_autoHideCoroutine != null)
{
StopCoroutine(_autoHideCoroutine);
_autoHideCoroutine = null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a67d1bf02c4a3d34cb1b9709d9930fa1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class InteractionTextsContoller : MonoBehaviour
{
[SerializeField] private GameObject textsPanel;
[SerializeField] TMP_Text actionText;
[SerializeField] TMP_Text _descriptionText;
public void SetActionText(string text)
{
actionText.text = text;
}
/// <summary>
/// Panel 활성화를 끄고 텍스트들을 초기화 합니다.
/// </summary>
public void InitInteractionTexts()
{
textsPanel.SetActive(false);
SetActionText("");
_descriptionText.text = "";
}
/// <summary>
/// 특정 범위 안에 상호작용 물체가 들어왔을 때, 상호작용 텍스트 패널을 활성화시킴
/// </summary>
/// <param name="actionText">상호작용할 내용</param>
/// <param name="_descriptionText">스테이터스 변경에 대한 설명</param>
public void ActiveTexts(string actionText, string descriptionText = "")
{
SetActionText(actionText);
_descriptionText.text = descriptionText;
textsPanel.SetActive(true);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1a9631c7b5512cc4ab41bbca8c17b945
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 80e8f592861964445a697bb8c8fee37d
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

BIN
Assets/LYM/Scenes/MainUI.unity (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: fe5619f467b6e0f42b57ee216ffdd7c7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a636af603b3af4d1baccb8296add7485
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,75 @@
using UnityEngine;
public struct BulletData
{
public Vector3 TargetPos;
public float Damage;
public float LifeTime;
public float Speed;
public BulletData(Vector3 targetPos, float damage, float lifeTime, float speed)
{
TargetPos = targetPos;
Damage = damage;
LifeTime = lifeTime;
Speed = speed;
}
}
[RequireComponent(typeof(Collider))]
public class BulletBase : MonoBehaviour
{
// 데이터
private float _speed = 5f;
private float _damage = 1f;
private float _lifeTime = 10f;
// 내부용
private Vector3 _direction = Vector3.forward;
private float _timer;
public virtual void Initialize(BulletData bulletData)
{
_speed = bulletData.Speed;
_damage = bulletData.Damage;
_lifeTime = bulletData.LifeTime;
// 발사 위치 기준 목표 방향만 계산
_direction = (bulletData.TargetPos - transform.position).normalized;
// 탄환이 바라보는 방향 세팅
transform.rotation = Quaternion.LookRotation(_direction);
_timer = 0f;
}
private void Update()
{
// 1) 이동
transform.position += _direction * (_speed * Time.deltaTime);
// 2) 수명 카운트
_timer += Time.deltaTime;
if (_timer >= _lifeTime)
{
DestroyBullet();
}
}
private void OnTriggerEnter(Collider other)
{
// TODO: 주인공 캐릭터를 찾는 로직 추가 필요
// 주인공 스크립트를 찾아 처리할 것.
var character = other.GetComponent<CharacterBase>();
if (character != null)
{
character.TakeDamage(_damage);
DestroyBullet();
}
}
protected virtual void DestroyBullet()
{
Debug.Log("## Bullet destroyed");
Destroy(gameObject);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1c349f971ec844b19d94a06e8f93aca0
timeCreated: 1745890918

View File

@ -0,0 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MagicMissile : BulletBase
{
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc3f5b56a395f448c881888076d83cba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,16 +4,121 @@ using UnityEngine;
public class CasterDemonController : EnemyController
{
private bool _doneBattleSequence = true;
private bool _isFirstNoPath = true;
[SerializeField] private Transform teleportTransform;
[SerializeField] private Transform bulletShotPosition;
[SerializeField] private GameObject magicMissilePrefab;
[SerializeField] private GameObject teleportEffectPrefab;
public override void BattleSequence()
{
// TODO : 배틀 중일 때 루프
Debug.Log("## 몬스터의 교전 행동 루프");
// 전투 행동이 이미 진행 중일 경우 실행 막기
if (_doneBattleSequence)
{
// 전투 행동 시작
_doneBattleSequence = false;
// TODO : 배틀 중일 때 루프
Debug.Log("## 몬스터의 교전 행동 루프");
Thinking();
}
}
private void Thinking()
{
int selectedPattern = Random.Range(0, 10);
switch (selectedPattern)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
StartCoroutine(ShotMagicMissile());
break;
}
}
public override void OnCannotFleeBehaviour()
{
Debug.Log("## 몬스터가 막다른 길에 몰려 뭔가 함");
// 구석에 끼인 경우 탈출
Debug.Log("## 텔레포트 시전");
Teleport();
}
private IEnumerator ShotMagicMissile()
{
for (int i = 0; i < 3; i++)
{
// 1. 기본 위치
Vector3 basePos = TraceTargetTransform.position;
Vector3 aimPosition = basePos;
// 2. 플레이어 Rigidbody로 속도 얻기
if (TraceTargetTransform.TryGetComponent<Rigidbody>(out var rb))
{
// 아주 짧은 시간만 예측
float predictionTime = 0.3f;
aimPosition += rb.velocity * predictionTime;
}
// 높이는 변경할 필요 없음
float fixedY = bulletShotPosition.position.y;
aimPosition.y = fixedY;
// 3. 그 위치를 바라보고
transform.LookAt(aimPosition);
// 4. 미사일 생성 및 초기화
var missile = Instantiate(
magicMissilePrefab,
bulletShotPosition.position,
transform.rotation
);
missile.GetComponent<MagicMissile>()
.Initialize(new BulletData(aimPosition, 5f, 10f, 5f));
yield return new WaitForSeconds(0.4f);
}
// 짧은 텀 후 끝내기
yield return new WaitForSeconds(1f);
_doneBattleSequence = true;
}
private void Teleport()
{
if (teleportEffectPrefab != null)
Instantiate(teleportEffectPrefab, transform.position, Quaternion.identity);
if (Agent != null && teleportTransform != null)
Agent.Warp(teleportTransform.position);
else if (teleportTransform != null)
transform.position = teleportTransform.position;
if (teleportEffectPrefab != null && teleportTransform != null)
Instantiate(teleportEffectPrefab, teleportTransform.position, Quaternion.identity);
}
}

View File

@ -6,7 +6,11 @@ public class EnemyStateFlee :IEnemyState
private EnemyController _enemyController;
private Transform _playerTransform;
private float _fleeDistance = 5f; // 도망치는 거리
private float _attackRange = 7f; // 공격 범위
private float _safeRange = 7f; // 공격 범위
// 경로 탐색 주기 조절용
private float _fleeSearchTimer = 0;
private float _fleeThresholdTime = 0.2f;
// 막다른길 검사용
private Vector3 _lastPosition;
@ -21,9 +25,11 @@ public class EnemyStateFlee :IEnemyState
_playerTransform = _enemyController.TraceTargetTransform;
_lastPosition = _enemyController.transform.position;
_enemyController.Agent.ResetPath();
_enemyController.Agent.isStopped = false;
_stuckTimer = 0f;
_fleeSearchTimer = 0;
}
public void Update()
@ -34,8 +40,28 @@ public class EnemyStateFlee :IEnemyState
return;
}
float currentDist = Vector3.Distance(
_enemyController.transform.position,
_playerTransform.position
);
if (currentDist >= _safeRange)
{
// 목적지 리셋 후 전투 시작
_enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_enemyController.BattleSequence();
return;
}
FindPositionFlee();
if (!_enemyController.Agent.pathPending &&
_enemyController.Agent.pathStatus == NavMeshPathStatus.PathInvalid)
{
Debug.Log("## 길을 못찾음");
HandleDeadEnd();
}
// 막힘 감지 (실제 이동 체크)
CheckPath();
@ -44,45 +70,39 @@ public class EnemyStateFlee :IEnemyState
private void CheckPath()
{
float distance = Vector3.Distance(_enemyController.transform.position, _playerTransform.position);
if (distance < StuckMoveThreshold)
float moved = Vector3.Distance(_enemyController.transform.position, _lastPosition);
if (moved < StuckMoveThreshold)
{
_stuckTimer += Time.deltaTime;
if (_stuckTimer >= StuckThresholdTime)
{
// 막다른 길임 : 대체 행동 실행
Debug.Log("## 끼임");
HandleDeadEnd();
_stuckTimer = 0f;
}
}
else
{
// 정상적인 길: 배틀 루프 실행
_enemyController.BattleSequence();
_stuckTimer = 0f;
}
}
private void FindPositionFlee()
{
_fleeSearchTimer += Time.deltaTime;
if (_fleeSearchTimer <= _fleeThresholdTime) return;
// 1) 목표 도망 위치 계산
Vector3 fleeDirection = (_enemyController.transform.position - _playerTransform.position).normalized;
Vector3 fleeTarget = _enemyController.transform.position + fleeDirection * _fleeDistance;
// 2) 경로 계산해 보기
NavMeshPath path = new NavMeshPath();
_enemyController.Agent.CalculatePath(fleeTarget, path);
_enemyController.Agent.SetDestination(fleeTarget);
if (path.status == NavMeshPathStatus.PathComplete)
{
// 제대로 도망갈 수 있으면 목적지 설정
_enemyController.Agent.SetDestination(fleeTarget);
}
else
{
// 막다른 길임 : 대체 행동 실행
HandleDeadEnd();
}
// 3) 이동
_enemyController.Agent.isStopped = false;
_fleeSearchTimer = 0;
}
private void HandleDeadEnd()
@ -93,16 +113,24 @@ public class EnemyStateFlee :IEnemyState
if (NavMesh.SamplePosition(randomDirection, out var hit, (_fleeDistance * 2), NavMesh.AllAreas))
{
// 샘플링에 성공했으면 일단 그 위치로 가 보도록 세팅
Debug.Log("## 일단 가봄");
_enemyController.Agent.SetDestination(hit.position);
return;
_enemyController.OnCannotFleeBehaviour();
}
else
{
// 대체 경로도 찾을 수 없는 경우
Debug.Log("## 대체 경로도 못찾음");
_enemyController.OnCannotFleeBehaviour();
}
// 대체 경로도 찾을 수 없는 경우
_enemyController.OnCannotFleeBehaviour();
}
public void Exit()
{
_enemyController.Agent.isStopped = true;
_enemyController.Agent.ResetPath();
_playerTransform = null;
_enemyController = null;
}