update tùm lum tùm la

This commit is contained in:
2026-04-01 02:41:07 +07:00
parent 6ebf140ff6
commit a50209b05c
754 changed files with 136616 additions and 55 deletions

View File

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

View 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.");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 33ce7e871a1d2b445a1dbbb4fe07ba48

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 637de84addeb4db6878e00c627bb8bcc
timeCreated: 1774960546

View File

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

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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 77496ebe7c1d9c74ebbe6c390d147e04

View 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}");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d41bcdbf11a6d6c4bb61a32e85d1635f

View 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!");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c7587a9b1da1aef4da01893214275034

View 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")}");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8ad2ce50b06995b49b7380826f67114d

View File

@@ -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)

View File

@@ -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()

View File

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