Compare commits

...

10 Commits

Author SHA1 Message Date
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
Fiore
99082eeb7c [feat] 마법사 몬스터의 도망 로직 정리
- 경로 탐색 주기를 설정해 성능 향상
- 상태 진입과 이탈시 초기화처리
- 버그 수정 : 플레이어와 거리를 체크하고 전투 시작
- 경로 탐색 성능 향상
- 배틀 시퀸스 프레임 작성

work in #DEG-100
2025-04-29 09:26:09 +09:00
13 changed files with 295 additions and 29 deletions

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

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

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.

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;
}