This commit is contained in:
옥소명_playm 2026-01-30 15:32:26 +09:00
commit 6b2bddc1be
14 changed files with 60064 additions and 39 deletions

View File

@ -62447,7 +62447,7 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
healthSource: {fileID: 2112919314} healthSource: {fileID: 2112919314}
hpFillImage: {fileID: 0} hpFillImage: {fileID: 910004995}
hpText: {fileID: 0} hpText: {fileID: 0}
--- !u!1001 &603601734 --- !u!1001 &603601734
PrefabInstance: PrefabInstance:
@ -81852,6 +81852,10 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: df5024494d479b4498082208d661953d, type: 3} m_Script: {fileID: 11500000, guid: df5024494d479b4498082208d661953d, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_vCam: {fileID: 398512783}
defaultFOV: 60
zoomedFOV: 45
zoomSpeed: 5
--- !u!4 &797851020 --- !u!4 &797851020
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -81919,7 +81923,7 @@ MonoBehaviour:
m_DissipationMode: 2 m_DissipationMode: 2
m_DissipationDistance: 100 m_DissipationDistance: 100
m_PropagationSpeed: 343 m_PropagationSpeed: 343
m_DefaultVelocity: {x: 1, y: 0, z: 0} m_DefaultVelocity: {x: 1, y: 1, z: 0}
--- !u!1001 &798036354 --- !u!1001 &798036354
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -146825,6 +146829,7 @@ MonoBehaviour:
aim: {fileID: 1432447528} aim: {fileID: 1432447528}
interaction: {fileID: 1432447532} interaction: {fileID: 1432447532}
attack: {fileID: 1432447527} attack: {fileID: 1432447527}
statsUI: {fileID: 0}
--- !u!114 &1432447530 --- !u!114 &1432447530
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -392,8 +392,8 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 36 m_fontSize: 32.1
m_fontSizeBase: 36 m_fontSizeBase: 32.1
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18

View File

@ -392,8 +392,8 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 36 m_fontSize: 32.1
m_fontSizeBase: 36 m_fontSizeBase: 32.1
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
@ -408,7 +408,7 @@ MonoBehaviour:
m_lineSpacingMax: 0 m_lineSpacingMax: 0
m_paragraphSpacing: 0 m_paragraphSpacing: 0
m_charWidthMaxAdj: 0 m_charWidthMaxAdj: 0
m_enableWordWrapping: 1 m_enableWordWrapping: 0
m_wordWrappingRatios: 0.4 m_wordWrappingRatios: 0.4
m_overflowMode: 0 m_overflowMode: 0
m_linkedTextComponent: {fileID: 0} m_linkedTextComponent: {fileID: 0}

View File

@ -57,14 +57,21 @@ ModelImporter:
bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000
curves: [] curves: []
events: events:
- time: 0.5073891 - time: 0.60481036
functionName: StartWeaponCollision functionName: StartWeaponCollision
data: data:
objectReferenceParameter: {instanceID: 0} objectReferenceParameter: {instanceID: 0}
floatParameter: 0 floatParameter: 0
intParameter: 0 intParameter: 0
messageOptions: 0 messageOptions: 0
- time: 0.7897755 - time: 0.6131805
functionName: OnAttackShake
data:
objectReferenceParameter: {instanceID: 0}
floatParameter: 0
intParameter: 0
messageOptions: 0
- time: 0.8470821
functionName: StopWeaponCollision functionName: StopWeaponCollision
data: data:
objectReferenceParameter: {instanceID: 0} objectReferenceParameter: {instanceID: 0}
@ -107,13 +114,20 @@ ModelImporter:
floatParameter: 0 floatParameter: 0
intParameter: 0 intParameter: 0
messageOptions: 0 messageOptions: 0
- time: 0.5366044 - time: 0.44848183
functionName: StartWeaponCollision functionName: StartWeaponCollision
data: data:
objectReferenceParameter: {instanceID: 0} objectReferenceParameter: {instanceID: 0}
floatParameter: 0 floatParameter: 0
intParameter: 0 intParameter: 0
messageOptions: 0 messageOptions: 0
- time: 0.4597701
functionName: OnAttackShake
data:
objectReferenceParameter: {instanceID: 0}
floatParameter: 0
intParameter: 0
messageOptions: 0
- time: 0.8300493 - time: 0.8300493
functionName: StartWeaponCollision functionName: StartWeaponCollision
data: data:
@ -157,13 +171,20 @@ ModelImporter:
bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000
curves: [] curves: []
events: events:
- time: 0.15307902 - time: 0.13009052
functionName: StartWeaponCollision functionName: StartWeaponCollision
data: data:
objectReferenceParameter: {instanceID: 0} objectReferenceParameter: {instanceID: 0}
floatParameter: 0 floatParameter: 0
intParameter: 0 intParameter: 0
messageOptions: 0 messageOptions: 0
- time: 0.14239462
functionName: OnAttackShake
data:
objectReferenceParameter: {instanceID: 0}
floatParameter: 0
intParameter: 0
messageOptions: 0
- time: 0.5402832 - time: 0.5402832
functionName: StopWeaponCollision functionName: StopWeaponCollision
data: data:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -11,12 +11,11 @@ public class PlayerAttack : MonoBehaviour
[Header("--- 설정 ---")] [Header("--- 설정 ---")]
[SerializeField] private float attackCooldown = 0.5f; [SerializeField] private float attackCooldown = 0.5f;
[SerializeField] private float fullChargeTime = 2f; // 차징 기준 시간 [SerializeField] private float fullChargeTime = 2f;
private float _lastAttackTime, _chargeTimer; private float _lastAttackTime, _chargeTimer;
private bool _isCharging; private bool _isCharging;
// UI와 이동 스크립트에서 참조하는 통로
public float ChargeProgress => Mathf.Clamp01(_chargeTimer / fullChargeTime); public float ChargeProgress => Mathf.Clamp01(_chargeTimer / fullChargeTime);
public bool IsCharging => _isCharging; public bool IsCharging => _isCharging;
@ -25,12 +24,7 @@ public class PlayerAttack : MonoBehaviour
if (_isCharging) if (_isCharging)
{ {
_chargeTimer += Time.deltaTime; _chargeTimer += Time.deltaTime;
if (Input.GetMouseButtonDown(1)) { CancelCharging(); Debug.Log("차징 취소됨"); }
if (Input.GetMouseButtonDown(1))
{
CancelCharging();
Debug.Log("차징 취소됨");
}
} }
} }
@ -42,8 +36,20 @@ public class PlayerAttack : MonoBehaviour
pAnim.TriggerAttack(); pAnim.TriggerAttack();
_lastAttackTime = Time.time; _lastAttackTime = Time.time;
// ❌ 여기서 ShakeAttack()을 호출하지 않습니다. (애니메이션 이벤트로 이동)
} }
// ⭐ [추가] 애니메이션 이벤트에서 실제 타격 시점에 호출할 함수
public void OnAttackShake()
{
Debug.Log("<color=cyan>[Event]</color> 타격 타이밍! 카메라 흔들기 명령 전송");
if (CinemachineShake.Instance != null)
{
CinemachineShake.Instance.ShakeAttack(); // 애니메이션 타이밍에 맞춰 쾅!
}
}
// (기존 StartWeaponCollision, StopWeaponCollision 로직 유지...)
public void StartWeaponCollision() public void StartWeaponCollision()
{ {
if (interaction.CurrentWeapon == null || weaponHitBox == null) return; if (interaction.CurrentWeapon == null || weaponHitBox == null) return;
@ -63,7 +69,7 @@ public class PlayerAttack : MonoBehaviour
pAnim.TriggerThrow(); pAnim.TriggerThrow();
pAnim.SetCharging(false); pAnim.SetCharging(false);
// 1. 레이캐스트 정밀 조준 // 레이캐스트 조준 로직
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, transform.position); Plane groundPlane = new Plane(Vector3.up, transform.position);
float rayDistance; float rayDistance;
@ -78,25 +84,22 @@ public class PlayerAttack : MonoBehaviour
if (targetDirection != Vector3.zero) transform.rotation = Quaternion.LookRotation(targetDirection); if (targetDirection != Vector3.zero) transform.rotation = Quaternion.LookRotation(targetDirection);
// 2. ⭐ [핵심 수정] 무기 컨피그에서 데이터 직접 가져오기 // 차징 레벨 계산 및 무기 던지기
// 차징 시간에 따라 단계를 나눕니다. (1초 미만 Lv1, 1~2초 Lv2, 2초 이상 Lv3)
int lv = _chargeTimer >= 2f ? 3 : (_chargeTimer >= 1f ? 2 : 1); int lv = _chargeTimer >= 2f ? 3 : (_chargeTimer >= 1f ? 2 : 1);
// WeaponConfig에 있는 GetSpread와 GetForce 함수를 사용합니다.
float currentSpread = interaction.CurrentWeapon.Config.GetSpread(lv); float currentSpread = interaction.CurrentWeapon.Config.GetSpread(lv);
float throwForce = interaction.CurrentWeapon.Config.GetForce(lv); float throwForce = interaction.CurrentWeapon.Config.GetForce(lv);
// 3. 정확도(Spread)가 적용된 최종 방향 계산
Vector3 finalThrowDir = Quaternion.Euler(0, Random.Range(-currentSpread, currentSpread), 0) * targetDirection; Vector3 finalThrowDir = Quaternion.Euler(0, Random.Range(-currentSpread, currentSpread), 0) * targetDirection;
// 무기 던지기 실행
interaction.CurrentWeapon.OnThrown(finalThrowDir, throwForce, lv, stats); interaction.CurrentWeapon.OnThrown(finalThrowDir, throwForce, lv, stats);
// 상태 초기화 // 상태 초기화 및 카메라 효과
interaction.ClearCurrentWeapon(); interaction.ClearCurrentWeapon();
stats.ResetWeight(); stats.ResetWeight();
_isCharging = false; _isCharging = false;
_chargeTimer = 0f; _chargeTimer = 0f;
CinemachineShake.Instance.SetZoom(false);
CinemachineShake.Instance.ShakeAttack(); // 강공격도 묵직하게 흔들어줌
} }
public void StartCharging() public void StartCharging()
@ -106,6 +109,7 @@ public class PlayerAttack : MonoBehaviour
_isCharging = true; _isCharging = true;
_chargeTimer = 0f; _chargeTimer = 0f;
pAnim.SetCharging(true); pAnim.SetCharging(true);
CinemachineShake.Instance.SetZoom(true);
} }
public void CancelCharging() public void CancelCharging()
@ -113,5 +117,6 @@ public class PlayerAttack : MonoBehaviour
_isCharging = false; _isCharging = false;
_chargeTimer = 0f; _chargeTimer = 0f;
if (pAnim != null) pAnim.SetCharging(false); if (pAnim != null) pAnim.SetCharging(false);
CinemachineShake.Instance.SetZoom(false);
} }
} }

View File

@ -1,24 +1,69 @@
using UnityEngine; using UnityEngine;
using Cinemachine; using Cinemachine;
using System.Collections;
public class CinemachineShake : MonoBehaviour public class CinemachineShake : MonoBehaviour
{ {
public static CinemachineShake Instance { get; private set; } public static CinemachineShake Instance { get; private set; }
[Header("--- 컴포넌트 연결 ---")]
private CinemachineImpulseSource _impulseSource; private CinemachineImpulseSource _impulseSource;
[SerializeField] private CinemachineVirtualCamera _vCam;
[Header("--- 줌 설정 ---")]
[SerializeField] private float defaultFOV = 60f;
[SerializeField] private float zoomedFOV = 45f;
[SerializeField] private float zoomSpeed = 5f;
private Coroutine _zoomCoroutine;
private void Awake() private void Awake()
{ {
Instance = this; Instance = this;
_impulseSource = GetComponent<CinemachineImpulseSource>(); _impulseSource = GetComponent<CinemachineImpulseSource>();
// ⭐ 가상 카메라가 연결 안 되어 있으면 자동으로 찾아보는 안전장치
if (_vCam == null) _vCam = GetComponent<CinemachineVirtualCamera>();
} }
// ⭐ 힘 부족 시 호출할 도리도리 (좌우 흔들림) // 1. ⭐ [수정] 일반 공격 흔들림 (수직으로 묵직하게!)
public void ShakeAttack()
{
if (_impulseSource == null) return;
// Vector3.down 방향으로 힘을 주어 아래로 쾅! 찍는 느낌을 줍니다.
_impulseSource.GenerateImpulse(Vector3.down * 0.5f);
}
// 2. ⭐ [수정] 힘 부족 시 도리도리 (수평으로 가볍게!)
public void ShakeNoNo() public void ShakeNoNo()
{ {
if (_impulseSource == null) return; if (_impulseSource == null) return;
// Vector3.right 방향으로 힘을 주어 고개를 좌우로 흔드는 느낌을 줍니다.
// Vector3.right 방향으로 신호를 주어 좌우로 흔들리게 합니다. _impulseSource.GenerateImpulse(Vector3.right * 0.4f);
_impulseSource.GenerateImpulse(Vector3.right * 0.5f);
Debug.Log("<color=red>[Shake]</color> 힘 수치 부족! 도리도리 실행"); Debug.Log("<color=red>[Shake]</color> 힘 수치 부족! 도리도리 실행");
} }
public void SetZoom(bool isZooming)
{
// ⭐ [에러 방지] 카메라가 없으면 코루틴을 실행하지 않음
if (_vCam == null) return;
if (_zoomCoroutine != null) StopCoroutine(_zoomCoroutine);
float targetFOV = isZooming ? zoomedFOV : defaultFOV;
_zoomCoroutine = StartCoroutine(AnimateZoom(targetFOV));
}
private IEnumerator AnimateZoom(float target)
{
// ⭐ [에러 방지] 실행 중 카메라가 사라질 경우 대비
if (_vCam == null) yield break;
while (Mathf.Abs(_vCam.m_Lens.FieldOfView - target) > 0.1f)
{
if (_vCam == null) yield break;
_vCam.m_Lens.FieldOfView = Mathf.Lerp(_vCam.m_Lens.FieldOfView, target, Time.deltaTime * zoomSpeed);
yield return null;
}
_vCam.m_Lens.FieldOfView = target;
}
} }

View File

@ -0,0 +1,51 @@
using UnityEngine;
using TMPro; // TextMeshPro를 위해 필수
public class PlayerStatsUI : MonoBehaviour
{
[Header("--- 데이터 연결 ---")]
[SerializeField] private Stats playerStats;
[Header("--- UI 오브젝트 ---")]
[SerializeField] private GameObject statWindowPanel; // C키로 껐다 켤 부모 판넬
[Header("--- 텍스트 UI ---")]
[SerializeField] private TextMeshProUGUI maxHealthText;
[SerializeField] private TextMeshProUGUI strengthText;
[SerializeField] private TextMeshProUGUI damageText;
[SerializeField] private TextMeshProUGUI speedText;
private void Start()
{
// 시작할 때는 창을 닫아둡니다.
if (statWindowPanel != null)
statWindowPanel.SetActive(false);
}
// 창을 열거나 닫는 함수
public void ToggleWindow()
{
if (statWindowPanel == null) return;
bool isActive = !statWindowPanel.activeSelf;
statWindowPanel.SetActive(isActive);
// 창이 켜질 때만 최신 정보로 글자를 갱신합니다.
if (isActive)
{
UpdateStatTexts();
}
}
private void UpdateStatTexts()
{
if (playerStats == null) return;
// Stats.cs의 실제 로직용 프로퍼티에서 데이터를 가져옵니다.
maxHealthText.text = $"MaxHP: {playerStats.MaxHealth}";
strengthText.text = $"Strength: {playerStats.Strength}";
damageText.text = $"Damage: {playerStats.BaseAttackDamage}";
// 스피드는 소수점 한 자리까지 깔끔하게 표시
speedText.text = $"Speed: {playerStats.CurrentMoveSpeed:F1}";
}
}

View File

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

View File

@ -6,15 +6,25 @@ public class PlayerInput : MonoBehaviour
[SerializeField] private PlayerAim aim; [SerializeField] private PlayerAim aim;
[SerializeField] private PlayerInteraction interaction; [SerializeField] private PlayerInteraction interaction;
[SerializeField] private PlayerAttack attack; [SerializeField] private PlayerAttack attack;
[SerializeField] private PlayerStatsUI statsUI; // ⭐ 추가: UI 매니저 연결
private void Update() private void Update()
{ {
if (health != null && health.IsDead) return; if (health != null && health.IsDead) return;
// ⭐ [추가] C키를 누르면 상태창 토글
if (Input.GetKeyDown(KeyCode.C) && statsUI != null)
{
statsUI.ToggleWindow();
}
float h = Input.GetAxisRaw("Horizontal"); float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical"); float v = Input.GetAxisRaw("Vertical");
bool sprint = Input.GetKey(KeyCode.LeftShift); bool sprint = Input.GetKey(KeyCode.LeftShift);
if (movement != null) movement.SetMoveInput(new Vector3(h, 0, v).normalized, sprint); if (movement != null) movement.SetMoveInput(new Vector3(h, 0, v).normalized, sprint);
if (aim != null) aim.RotateTowardsMouse(); if (aim != null) aim.RotateTowardsMouse();
if (Input.GetKeyDown(KeyCode.F) && interaction != null) interaction.TryInteract(); if (Input.GetKeyDown(KeyCode.F) && interaction != null) interaction.TryInteract();
if (attack != null) if (attack != null)
{ {
if (Input.GetMouseButtonDown(1)) attack.StartCharging(); if (Input.GetMouseButtonDown(1)) attack.StartCharging();