update tùm lum tùm la
This commit is contained in:
8
Assets/Scripts/Attributes.meta
Normal file
8
Assets/Scripts/Attributes.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 756a700ebf1264c44822e56b7c9bac48
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
46
Assets/Scripts/Attributes/Health.cs
Normal file
46
Assets/Scripts/Attributes/Health.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class Health : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float maxHealth = 100f;
|
||||
[SerializeField] private float currentHealth;
|
||||
|
||||
public event Action<float> OnHealthChanged;
|
||||
public event Action OnDeath;
|
||||
|
||||
public float CurrentHealth => currentHealth;
|
||||
public float MaxHealth => maxHealth;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
currentHealth = maxHealth;
|
||||
}
|
||||
|
||||
public void Heal(float amount)
|
||||
{
|
||||
if (currentHealth <= 0) return;
|
||||
|
||||
currentHealth = Mathf.Min(currentHealth + amount, maxHealth);
|
||||
OnHealthChanged?.Invoke(currentHealth);
|
||||
Debug.Log($"[Health] Healed for {amount}. Current health: {currentHealth}");
|
||||
}
|
||||
|
||||
public void TakeDamage(float amount)
|
||||
{
|
||||
if (currentHealth <= 0) return;
|
||||
|
||||
currentHealth = Mathf.Max(currentHealth - amount, 0);
|
||||
OnHealthChanged?.Invoke(currentHealth);
|
||||
Debug.Log($"[Health] Took {amount} damage. Current health: {currentHealth}");
|
||||
|
||||
if (currentHealth <= 0)
|
||||
{
|
||||
OnDeath?.Invoke();
|
||||
Debug.Log("[Health] Died.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Attributes/Health.cs.meta
Normal file
2
Assets/Scripts/Attributes/Health.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33ce7e871a1d2b445a1dbbb4fe07ba48
|
||||
@@ -24,6 +24,9 @@ namespace OnlyScove.Scripts
|
||||
public bool IsSprinting => stateMachine != null ? stateMachine.Input.IsSprintHeld : false;
|
||||
public string TargetInteractable => stateMachine != null ? (stateMachine.GetInteractable()?.InteractionPrompt ?? "None") : "N/A";
|
||||
|
||||
public IInteractable GetActiveInteractable() => stateMachine?.GetInteractable();
|
||||
public Vector3 GetInteractionPoint() => stateMachine != null ? stateMachine.Scanner.GetLastInteractionPoint(stateMachine.InteractionRange, stateMachine.InteractionMask) : Vector3.zero;
|
||||
|
||||
private Vector3 currentVelocity;
|
||||
private Transform cameraTransform;
|
||||
private bool isVisible = true;
|
||||
|
||||
@@ -4,10 +4,15 @@ namespace OnlyScove.Scripts
|
||||
{
|
||||
public class EnvironmentScanner : MonoBehaviour
|
||||
{
|
||||
[Header("Obstacle Detection")]
|
||||
[SerializeField] private Vector3 forwardRayOffset = new Vector3(0, 2.5f, 0);
|
||||
[SerializeField] float forwardRayLength = 10f;
|
||||
[SerializeField] LayerMask obstacleLayer;
|
||||
[SerializeField] float heightRayLength;
|
||||
|
||||
[Header("Interaction Detection")]
|
||||
[SerializeField] private Vector3 interactionOffset = new Vector3(0, 1.5f, 0);
|
||||
[SerializeField] private float interactionRadius = 0.5f;
|
||||
|
||||
public ObstacleHitInfo ObstacleCheck()
|
||||
{
|
||||
@@ -35,6 +40,45 @@ namespace OnlyScove.Scripts
|
||||
}
|
||||
return hitData;
|
||||
}
|
||||
|
||||
public IInteractable ScanForInteractable(float range, LayerMask mask)
|
||||
{
|
||||
if (Camera.main == null) return null;
|
||||
|
||||
Transform camTransform = Camera.main.transform;
|
||||
Vector3 origin = camTransform.position;
|
||||
Vector3 direction = camTransform.forward;
|
||||
|
||||
// Vẽ tia debug trong cửa sổ Scene để bạn kiểm tra (Nhấn nút Gizmos trong Scene để thấy)
|
||||
Debug.DrawRay(origin, direction * range, Color.blue);
|
||||
|
||||
if (Physics.Raycast(origin, direction, out RaycastHit hit, range, mask))
|
||||
{
|
||||
if (hit.collider.TryGetComponent(out IInteractable interactable))
|
||||
{
|
||||
Debug.DrawLine(origin, hit.point, Color.red); // Tia chuyển đỏ khi chạm vật tương tác
|
||||
return interactable;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Vector3 GetLastInteractionPoint(float range, LayerMask mask)
|
||||
{
|
||||
if (Camera.main == null) return Vector3.zero;
|
||||
Transform camTransform = Camera.main.transform;
|
||||
if (Physics.Raycast(camTransform.position, camTransform.forward, out RaycastHit hit, range, mask))
|
||||
return hit.point;
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Vector3 origin = transform.position + interactionOffset;
|
||||
Gizmos.DrawLine(origin, origin + transform.forward * 2f);
|
||||
Gizmos.DrawWireSphere(origin + transform.forward * 2f, interactionRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,13 @@ using UnityEngine;
|
||||
[CreateAssetMenu(fileName = "ObjectInteraction", menuName = "Scriptable Objects/ObjectInteraction")]
|
||||
public class ObjectInteraction : ScriptableObject
|
||||
{
|
||||
[Header("UI Settings")]
|
||||
public string promptText = "Interact";
|
||||
|
||||
[Header("Audio & Visuals")]
|
||||
public AudioClip interactionSound;
|
||||
public GameObject interactionVFX;
|
||||
|
||||
[Header("Settings")]
|
||||
public float interactionCooldown = 0.5f;
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 637de84addeb4db6878e00c627bb8bcc
|
||||
timeCreated: 1774960546
|
||||
8
Assets/Scripts/Interactables.meta
Normal file
8
Assets/Scripts/Interactables.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f465518e0a6dc0e40982d7ffdfda36ac
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
37
Assets/Scripts/Interactables/BaseInteractable.cs
Normal file
37
Assets/Scripts/Interactables/BaseInteractable.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public abstract class BaseInteractable : MonoBehaviour, IInteractable
|
||||
{
|
||||
[SerializeField] protected ObjectInteraction interactionData;
|
||||
|
||||
private float lastInteractTime;
|
||||
|
||||
public virtual string InteractionPrompt => interactionData != null ? interactionData.promptText : "Interact";
|
||||
|
||||
public virtual void OnInteract(PlayerStateMachine player)
|
||||
{
|
||||
if (Time.time < lastInteractTime + (interactionData != null ? interactionData.interactionCooldown : 0f))
|
||||
return;
|
||||
|
||||
lastInteractTime = Time.time;
|
||||
|
||||
// Play sound if assigned
|
||||
if (interactionData != null && interactionData.interactionSound != null)
|
||||
{
|
||||
AudioSource.PlayClipAtPoint(interactionData.interactionSound, transform.position);
|
||||
}
|
||||
|
||||
// Spawn VFX if assigned
|
||||
if (interactionData != null && interactionData.interactionVFX != null)
|
||||
{
|
||||
Instantiate(interactionData.interactionVFX, transform.position, Quaternion.identity);
|
||||
}
|
||||
|
||||
PerformInteraction(player);
|
||||
}
|
||||
|
||||
protected abstract void PerformInteraction(PlayerStateMachine player);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Interactables/BaseInteractable.cs.meta
Normal file
2
Assets/Scripts/Interactables/BaseInteractable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77496ebe7c1d9c74ebbe6c390d147e04
|
||||
90
Assets/Scripts/Interactables/DoorInteractable.cs
Normal file
90
Assets/Scripts/Interactables/DoorInteractable.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using UnityEngine;
|
||||
using DoorScript; // Namespace của gói Wood Door Pack
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class DoorInteractable : BaseInteractable
|
||||
{
|
||||
[Header("Door Component (Optional)")]
|
||||
[SerializeField] private Door woodDoorScript;
|
||||
|
||||
[Header("Animator (Optional)")]
|
||||
[SerializeField] private Animator animator;
|
||||
[SerializeField] private string boolParameterName = "IsOpen";
|
||||
|
||||
private bool isOpen = false;
|
||||
|
||||
// Ghi đè Prompt để hiện trạng thái Mở/Đóng tùy vào cửa đang như thế nào
|
||||
public override string InteractionPrompt
|
||||
{
|
||||
get
|
||||
{
|
||||
bool currentOpen = woodDoorScript != null ? woodDoorScript.open : isOpen;
|
||||
return (currentOpen ? "Đóng " : "Mở ") + (interactionData != null ? interactionData.promptText : "Cửa");
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Debug.Log($"[DoorInteractable] Initializing on {gameObject.name}");
|
||||
|
||||
// 1. Tìm Door script (Tìm mọi nơi: bản thân, con, cha)
|
||||
if (woodDoorScript == null) woodDoorScript = GetComponent<Door>();
|
||||
if (woodDoorScript == null) woodDoorScript = GetComponentInChildren<Door>(true);
|
||||
if (woodDoorScript == null) woodDoorScript = GetComponentInParent<Door>();
|
||||
|
||||
if (woodDoorScript != null)
|
||||
{
|
||||
Debug.Log($"[DoorInteractable] SUCCESS: Found Door script on {woodDoorScript.gameObject.name}");
|
||||
|
||||
// Đảm bảo có AudioSource
|
||||
var source = woodDoorScript.GetComponent<AudioSource>();
|
||||
if (source == null) source = woodDoorScript.gameObject.AddComponent<AudioSource>();
|
||||
woodDoorScript.asource = source;
|
||||
|
||||
isOpen = woodDoorScript.open;
|
||||
}
|
||||
|
||||
// 2. Tìm Animator (Tìm mọi nơi)
|
||||
if (animator == null) animator = GetComponent<Animator>();
|
||||
if (animator == null) animator = GetComponentInChildren<Animator>(true);
|
||||
if (animator == null) animator = GetComponentInParent<Animator>();
|
||||
|
||||
// 3. TỰ ĐỘNG TẮT SCRIPT XUNG ĐỘT (CameraOpenDoor) nếu nó đang tồn tại trên Camera
|
||||
// Điều này giúp hệ thống của bạn chiếm quyền điều khiển hoàn toàn
|
||||
var conflictingScript = Object.FindFirstObjectByType<CameraDoorScript.CameraOpenDoor>();
|
||||
if (conflictingScript != null)
|
||||
{
|
||||
Debug.Log("[DoorInteractable] Disabling conflicting CameraOpenDoor script to take full control.");
|
||||
conflictingScript.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PerformInteraction(PlayerStateMachine player)
|
||||
{
|
||||
Debug.Log($"[Interaction] PerformInteraction CALLED on {gameObject.name}!");
|
||||
|
||||
// 1. Ưu tiên script của Door Pack (Wood Door Script)
|
||||
if (woodDoorScript != null)
|
||||
{
|
||||
Debug.Log($"[Interaction] Calling woodDoorScript.OpenDoor() on {gameObject.name}. Previous state: {woodDoorScript.open}");
|
||||
woodDoorScript.OpenDoor();
|
||||
isOpen = woodDoorScript.open;
|
||||
Debug.Log($"[Interaction] New state: {woodDoorScript.open}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Nếu không có script Pack mới dùng Animator
|
||||
if (animator != null)
|
||||
{
|
||||
isOpen = !isOpen;
|
||||
animator.SetBool(boolParameterName, isOpen);
|
||||
Debug.Log($"[Interaction] Triggered Animator: {boolParameterName} = {isOpen}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[Interaction] FAILED: No woodDoorScript or animator found on {gameObject.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Interactables/DoorInteractable.cs.meta
Normal file
2
Assets/Scripts/Interactables/DoorInteractable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d41bcdbf11a6d6c4bb61a32e85d1635f
|
||||
28
Assets/Scripts/Interactables/HealthInteractable.cs
Normal file
28
Assets/Scripts/Interactables/HealthInteractable.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class HealthInteractable : BaseInteractable
|
||||
{
|
||||
[SerializeField] private float healAmount = 25f;
|
||||
[SerializeField] private bool destroyOnInteract = true;
|
||||
|
||||
protected override void PerformInteraction(PlayerStateMachine player)
|
||||
{
|
||||
if (player.TryGetComponent(out Health health))
|
||||
{
|
||||
health.Heal(healAmount);
|
||||
Debug.Log($"[Healing] Restored {healAmount} health.");
|
||||
|
||||
if (destroyOnInteract)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Healing] Player has no Health component!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Interactables/HealthInteractable.cs.meta
Normal file
2
Assets/Scripts/Interactables/HealthInteractable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7587a9b1da1aef4da01893214275034
|
||||
25
Assets/Scripts/Interactables/LampInteractable.cs
Normal file
25
Assets/Scripts/Interactables/LampInteractable.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OnlyScove.Scripts
|
||||
{
|
||||
public class LampInteractable : BaseInteractable
|
||||
{
|
||||
[SerializeField] private Light targetLight;
|
||||
[SerializeField] private bool isOn = true;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (targetLight != null)
|
||||
targetLight.enabled = isOn;
|
||||
}
|
||||
|
||||
protected override void PerformInteraction(PlayerStateMachine player)
|
||||
{
|
||||
isOn = !isOn;
|
||||
if (targetLight != null)
|
||||
targetLight.enabled = isOn;
|
||||
|
||||
Debug.Log($"[Lamp] Toggled {(isOn ? "ON" : "OFF")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Interactables/LampInteractable.cs.meta
Normal file
2
Assets/Scripts/Interactables/LampInteractable.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ad2ce50b06995b49b7380826f67114d
|
||||
@@ -88,7 +88,13 @@ namespace OnlyScove.Scripts
|
||||
|
||||
public void OnInteract(InputAction.CallbackContext context)
|
||||
{
|
||||
if (context.performed) OnInteractEvent?.Invoke();
|
||||
Debug.Log($"[InputReader] Interaction State: {context.phase} | Started: {context.started} | Performed: {context.performed} | Canceled: {context.canceled}");
|
||||
|
||||
if (context.performed)
|
||||
{
|
||||
Debug.Log("[InputReader] Event OnInteractEvent triggered!");
|
||||
OnInteractEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnNext(InputAction.CallbackContext context)
|
||||
|
||||
@@ -101,24 +101,17 @@ namespace OnlyScove.Scripts
|
||||
private void UpdateInteractablesList()
|
||||
{
|
||||
interactablesNearby.Clear();
|
||||
Collider[] colliders = Physics.OverlapSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange, InteractionMask);
|
||||
foreach (var col in colliders)
|
||||
|
||||
// Sử dụng Scanner để tìm vật thể người chơi đang nhìn vào
|
||||
IInteractable target = Scanner.ScanForInteractable(InteractionRange, InteractionMask);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
if (col.TryGetComponent(out IInteractable interactable))
|
||||
{
|
||||
if (!interactablesNearby.Contains(interactable))
|
||||
interactablesNearby.Add(interactable);
|
||||
}
|
||||
interactablesNearby.Add(target);
|
||||
}
|
||||
|
||||
if (interactablesNearby.Count == 0)
|
||||
{
|
||||
currentInteractableIndex = 0;
|
||||
}
|
||||
else if (currentInteractableIndex >= interactablesNearby.Count)
|
||||
{
|
||||
currentInteractableIndex = interactablesNearby.Count - 1;
|
||||
}
|
||||
// Reset index vì hiện tại Scanner trả về 1 kết quả chính xác nhất
|
||||
currentInteractableIndex = 0;
|
||||
}
|
||||
|
||||
private void OnNextInteract()
|
||||
|
||||
@@ -8,35 +8,40 @@ namespace UI
|
||||
{
|
||||
public PlayerDebugProvider playerDebugProvider;
|
||||
|
||||
[Header("Text Fields")]
|
||||
public TextMeshProUGUI stateText;
|
||||
public TextMeshProUGUI groundedStatusText;
|
||||
public TextMeshProUGUI horizontalSpeedText;
|
||||
public TextMeshProUGUI verticaSpeedText;
|
||||
public TextMeshProUGUI moveInputText;
|
||||
public TextMeshProUGUI isSprintingText;
|
||||
[Header("UI Prompt")]
|
||||
public GameObject interactionPromptContainer;
|
||||
public TextMeshProUGUI interactionPromptText;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (playerDebugProvider == null)
|
||||
playerDebugProvider = FindFirstObjectByType<PlayerDebugProvider>();
|
||||
|
||||
// Luôn ẩn lúc bắt đầu
|
||||
if (interactionPromptContainer != null)
|
||||
interactionPromptContainer.SetActive(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (playerDebugProvider == null) return;
|
||||
if (playerDebugProvider == null || interactionPromptContainer == null) return;
|
||||
|
||||
IInteractable interactable = playerDebugProvider.GetActiveInteractable();
|
||||
|
||||
if (stateText != null)
|
||||
stateText.text = "State: " + playerDebugProvider.CurrentState;
|
||||
if (interactable != null)
|
||||
{
|
||||
// Hiện UI tại vị trí cố định bạn đã đặt trong Canvas
|
||||
interactionPromptContainer.SetActive(true);
|
||||
|
||||
if (groundedStatusText != null)
|
||||
groundedStatusText.text = "Grounded: " + playerDebugProvider.GroundedStatus;
|
||||
|
||||
if (horizontalSpeedText != null)
|
||||
horizontalSpeedText.text = "Speed (H): " + playerDebugProvider.HorizontalSpeed.ToString("F2") + " m/s";
|
||||
|
||||
if (verticaSpeedText != null)
|
||||
verticaSpeedText.text = "Speed (V): " + playerDebugProvider.VerticalSpeed.ToString("F2") + " m/s";
|
||||
|
||||
if (moveInputText != null)
|
||||
moveInputText.text = "Input: " + playerDebugProvider.MoveInput.ToString();
|
||||
|
||||
if (isSprintingText != null)
|
||||
isSprintingText.text = "Sprinting: " + (playerDebugProvider.IsSprinting ? "YES" : "NO");
|
||||
if (interactionPromptText != null)
|
||||
{
|
||||
interactionPromptText.text = interactable.InteractionPrompt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
interactionPromptContainer.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user