first commit
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class DialogueUI : MonoBehaviour
|
||||
{
|
||||
private const string PanelKey = "DialogueUI";
|
||||
|
||||
public static DialogueUI Instance { get; private set; }
|
||||
|
||||
[SerializeField] private GameObject dialoguePanel;
|
||||
[SerializeField] private Text npcNameText;
|
||||
[SerializeField] private Text dialogueText;
|
||||
[SerializeField] private Transform optionsRoot;
|
||||
[SerializeField] private Button optionButtonTemplate;
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
private NPC currentNpc;
|
||||
|
||||
public bool IsOpen => dialoguePanel != null && dialoguePanel.activeSelf;
|
||||
|
||||
public void Configure(
|
||||
GameObject panel,
|
||||
Text nameLabel,
|
||||
Text messageLabel,
|
||||
Transform responseRoot,
|
||||
Button responseTemplate,
|
||||
Button closePanelButton)
|
||||
{
|
||||
dialoguePanel = panel;
|
||||
npcNameText = nameLabel;
|
||||
dialogueText = messageLabel;
|
||||
optionsRoot = responseRoot;
|
||||
optionButtonTemplate = responseTemplate;
|
||||
closeButton = closePanelButton;
|
||||
|
||||
if (dialoguePanel != null)
|
||||
{
|
||||
dialoguePanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (optionButtonTemplate != null)
|
||||
{
|
||||
optionButtonTemplate.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveListener(CloseDialogue);
|
||||
closeButton.onClick.AddListener(CloseDialogue);
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowDialogue(NPC npc)
|
||||
{
|
||||
if (npc == null || dialoguePanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Inventory.Instance != null && Inventory.Instance.IsOpen)
|
||||
{
|
||||
Inventory.Instance.CloseInventory();
|
||||
}
|
||||
|
||||
if (NoteUI.Instance != null && NoteUI.Instance.IsOpen)
|
||||
{
|
||||
NoteUI.Instance.CloseNote();
|
||||
}
|
||||
|
||||
currentNpc = npc;
|
||||
npcNameText.text = npc.NPCName;
|
||||
dialogueText.text = npc.OpeningLine;
|
||||
dialoguePanel.SetActive(true);
|
||||
RebuildOptions();
|
||||
GameUIState.SetPanelOpen(PanelKey, true);
|
||||
}
|
||||
|
||||
public void ShowFollowUp(string speakerName, string message)
|
||||
{
|
||||
if (dialoguePanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
npcNameText.text = speakerName;
|
||||
dialogueText.text = message;
|
||||
ClearOptionButtons();
|
||||
dialoguePanel.SetActive(true);
|
||||
GameUIState.SetPanelOpen(PanelKey, true);
|
||||
}
|
||||
|
||||
public void CloseDialogue()
|
||||
{
|
||||
ClearOptionButtons();
|
||||
|
||||
if (dialoguePanel != null)
|
||||
{
|
||||
dialoguePanel.SetActive(false);
|
||||
}
|
||||
|
||||
currentNpc = null;
|
||||
GameUIState.SetPanelOpen(PanelKey, false);
|
||||
}
|
||||
|
||||
private void RebuildOptions()
|
||||
{
|
||||
ClearOptionButtons();
|
||||
|
||||
if (currentNpc == null || optionsRoot == null || optionButtonTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var option in currentNpc.DialogueOptions)
|
||||
{
|
||||
var capturedOption = option;
|
||||
var optionButton = Instantiate(optionButtonTemplate, optionsRoot);
|
||||
optionButton.gameObject.SetActive(true);
|
||||
optionButton.onClick.RemoveAllListeners();
|
||||
optionButton.onClick.AddListener(() => currentNpc.SelectOption(capturedOption));
|
||||
optionButton.interactable = currentNpc.IsOptionAvailable(capturedOption);
|
||||
|
||||
var label = optionButton.GetComponentInChildren<Text>();
|
||||
|
||||
if (label != null)
|
||||
{
|
||||
label.text = currentNpc.GetOptionLabel(capturedOption);
|
||||
label.color = optionButton.interactable ? Color.white : new Color(0.82f, 0.82f, 0.82f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearOptionButtons()
|
||||
{
|
||||
if (optionsRoot == null || optionButtonTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = optionsRoot.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = optionsRoot.GetChild(i);
|
||||
|
||||
if (child == optionButtonTemplate.transform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eeb112326984994c3aa8c3d9244f74d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public static class GameUIState
|
||||
{
|
||||
private static readonly HashSet<string> OpenPanels = new HashSet<string>();
|
||||
|
||||
public static bool IsAnyBlockingUIOpen => OpenPanels.Count > 0;
|
||||
|
||||
public static bool IsPanelOpen(string panelKey)
|
||||
{
|
||||
return OpenPanels.Contains(panelKey);
|
||||
}
|
||||
|
||||
public static bool HasBlockingUIExcept(string panelKey)
|
||||
{
|
||||
var ignoredCount = OpenPanels.Contains(panelKey) ? 1 : 0;
|
||||
return OpenPanels.Count > ignoredCount;
|
||||
}
|
||||
|
||||
public static void SetPanelOpen(string panelKey, bool isOpen)
|
||||
{
|
||||
if (string.IsNullOrEmpty(panelKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOpen)
|
||||
{
|
||||
OpenPanels.Add(panelKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenPanels.Remove(panelKey);
|
||||
}
|
||||
|
||||
SyncCursorAndTime();
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
OpenPanels.Clear();
|
||||
SyncCursorAndTime();
|
||||
}
|
||||
|
||||
public static void SyncCursorAndTime()
|
||||
{
|
||||
var shouldPause = IsAnyBlockingUIOpen;
|
||||
Time.timeScale = shouldPause ? 0f : 1f;
|
||||
Cursor.visible = shouldPause;
|
||||
Cursor.lockState = shouldPause ? CursorLockMode.None : CursorLockMode.Locked;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f60629623d141482aafb1515664b6836
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,110 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class InteractionPromptUI : MonoBehaviour
|
||||
{
|
||||
public static InteractionPromptUI Instance { get; private set; }
|
||||
|
||||
[SerializeField] private GameObject promptPanel;
|
||||
[SerializeField] private Text promptText;
|
||||
|
||||
private Object currentOwner;
|
||||
private string currentMessage = "Нажмите E";
|
||||
|
||||
public void Configure(GameObject panel, Text textComponent)
|
||||
{
|
||||
promptPanel = panel;
|
||||
promptText = textComponent;
|
||||
|
||||
if (promptPanel != null)
|
||||
{
|
||||
promptPanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
RefreshVisibility();
|
||||
}
|
||||
|
||||
public void ShowPrompt(Object owner, string message)
|
||||
{
|
||||
if (owner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentOwner = owner;
|
||||
currentMessage = string.IsNullOrWhiteSpace(message) ? "Нажмите E" : message;
|
||||
RefreshVisibility();
|
||||
}
|
||||
|
||||
public void HidePrompt(Object owner)
|
||||
{
|
||||
if (owner == null || currentOwner != owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentOwner = null;
|
||||
RefreshVisibility();
|
||||
}
|
||||
|
||||
public void HideAll()
|
||||
{
|
||||
currentOwner = null;
|
||||
RefreshVisibility();
|
||||
}
|
||||
|
||||
private void RefreshVisibility()
|
||||
{
|
||||
if (promptPanel == null || promptText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsOwnerValid(currentOwner))
|
||||
{
|
||||
currentOwner = null;
|
||||
}
|
||||
|
||||
var shouldShow = currentOwner != null && !GameUIState.IsAnyBlockingUIOpen;
|
||||
promptPanel.SetActive(shouldShow);
|
||||
|
||||
if (shouldShow)
|
||||
{
|
||||
promptText.text = currentMessage;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsOwnerValid(Object owner)
|
||||
{
|
||||
if (owner == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (owner is Component component)
|
||||
{
|
||||
return component.gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
if (owner is GameObject gameObject)
|
||||
{
|
||||
return gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16132a6dfbdbfc4f3a2edb826d64ece7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class Inventory : MonoBehaviour
|
||||
{
|
||||
private const string PanelKey = "Inventory";
|
||||
|
||||
public static Inventory Instance { get; private set; }
|
||||
|
||||
[SerializeField] private GameObject inventoryPanel;
|
||||
[SerializeField] private Transform listRoot;
|
||||
[SerializeField] private Button noteButtonTemplate;
|
||||
[SerializeField] private Text emptyStateText;
|
||||
[SerializeField] private Button closeButton;
|
||||
[SerializeField] private List<NoteData> collectedNotes = new List<NoteData>();
|
||||
|
||||
public bool IsOpen => inventoryPanel != null && inventoryPanel.activeSelf;
|
||||
|
||||
public void Configure(GameObject panel, Transform notesRoot, Button templateButton, Text emptyText, Button closePanelButton)
|
||||
{
|
||||
inventoryPanel = panel;
|
||||
listRoot = notesRoot;
|
||||
noteButtonTemplate = templateButton;
|
||||
emptyStateText = emptyText;
|
||||
closeButton = closePanelButton;
|
||||
|
||||
if (inventoryPanel != null)
|
||||
{
|
||||
inventoryPanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (noteButtonTemplate != null)
|
||||
{
|
||||
noteButtonTemplate.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveListener(CloseInventory);
|
||||
closeButton.onClick.AddListener(CloseInventory);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!Input.GetKeyDown(KeyCode.I))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsOpen)
|
||||
{
|
||||
CloseInventory();
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenInventory();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddNote(NoteData note)
|
||||
{
|
||||
if (note == null || string.IsNullOrWhiteSpace(note.title))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var storedNote in collectedNotes)
|
||||
{
|
||||
if (storedNote.title == note.title && storedNote.content == note.content)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
collectedNotes.Add(new NoteData(note.title, note.content));
|
||||
RebuildNoteList();
|
||||
}
|
||||
|
||||
public void OpenInventory()
|
||||
{
|
||||
if (inventoryPanel == null || listRoot == null || noteButtonTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NoteUI.Instance != null && NoteUI.Instance.IsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DialogueUI.Instance != null && DialogueUI.Instance.IsOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RebuildNoteList();
|
||||
inventoryPanel.SetActive(true);
|
||||
GameUIState.SetPanelOpen(PanelKey, true);
|
||||
}
|
||||
|
||||
public void CloseInventory()
|
||||
{
|
||||
if (inventoryPanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryPanel.SetActive(false);
|
||||
GameUIState.SetPanelOpen(PanelKey, false);
|
||||
}
|
||||
|
||||
private void RebuildNoteList()
|
||||
{
|
||||
if (listRoot == null || noteButtonTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = listRoot.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = listRoot.GetChild(i);
|
||||
|
||||
if (child == noteButtonTemplate.transform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
var hasNotes = collectedNotes.Count > 0;
|
||||
|
||||
if (emptyStateText != null)
|
||||
{
|
||||
emptyStateText.gameObject.SetActive(!hasNotes);
|
||||
emptyStateText.text = "Пока что здесь пусто. Найдите записки на уровне.";
|
||||
}
|
||||
|
||||
foreach (var note in collectedNotes)
|
||||
{
|
||||
var noteCopy = note;
|
||||
var noteButton = Instantiate(noteButtonTemplate, listRoot);
|
||||
noteButton.gameObject.SetActive(true);
|
||||
noteButton.onClick.RemoveAllListeners();
|
||||
noteButton.onClick.AddListener(() => OpenStoredNote(noteCopy));
|
||||
|
||||
var buttonText = noteButton.GetComponentInChildren<Text>();
|
||||
|
||||
if (buttonText != null)
|
||||
{
|
||||
buttonText.text = note.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenStoredNote(NoteData note)
|
||||
{
|
||||
CloseInventory();
|
||||
|
||||
if (NoteUI.Instance != null)
|
||||
{
|
||||
NoteUI.Instance.ShowNote(note.title, note.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7595029b9892b80318970e7fd99641bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class LevelExitTrigger : MonoBehaviour
|
||||
{
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!other.CompareTag("Player"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QuestManager.Instance?.FinishLevel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f96fbb9f5d3b36f8a8265f5a21662140
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class NPC : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class DialogueOption
|
||||
{
|
||||
public string playerReply;
|
||||
public string npcReply;
|
||||
public bool closeAfterSelection;
|
||||
public bool requiresAllNotes;
|
||||
public string unavailableReply;
|
||||
}
|
||||
|
||||
[SerializeField] private string npcName = "Смотритель";
|
||||
[SerializeField] private string openingLine = "Вы нашли что-нибудь интересное?";
|
||||
[SerializeField] private List<DialogueOption> dialogueOptions = new List<DialogueOption>();
|
||||
[SerializeField] private Renderer npcRenderer;
|
||||
[SerializeField] private Material defaultMaterial;
|
||||
[SerializeField] private Material highlightMaterial;
|
||||
|
||||
private bool playerInRange;
|
||||
private bool hasRegisteredConversation;
|
||||
|
||||
public string NPCName => npcName;
|
||||
public string OpeningLine => openingLine;
|
||||
public IReadOnlyList<DialogueOption> DialogueOptions => dialogueOptions;
|
||||
|
||||
public void Configure(
|
||||
string characterName,
|
||||
string introLine,
|
||||
Renderer targetRenderer,
|
||||
Material baseMaterial,
|
||||
Material selectedMaterial,
|
||||
List<DialogueOption> options)
|
||||
{
|
||||
npcName = characterName;
|
||||
openingLine = introLine;
|
||||
npcRenderer = targetRenderer;
|
||||
defaultMaterial = baseMaterial;
|
||||
highlightMaterial = selectedMaterial;
|
||||
dialogueOptions = options ?? new List<DialogueOption>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (npcRenderer == null)
|
||||
{
|
||||
npcRenderer = GetComponentInChildren<Renderer>();
|
||||
}
|
||||
|
||||
if (defaultMaterial == null && npcRenderer != null)
|
||||
{
|
||||
defaultMaterial = npcRenderer.sharedMaterial;
|
||||
}
|
||||
|
||||
SetHighlighted(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (playerInRange && !GameUIState.IsAnyBlockingUIOpen && Input.GetKeyDown(KeyCode.E))
|
||||
{
|
||||
Interact();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlayerInRange(bool isInRange)
|
||||
{
|
||||
playerInRange = isInRange;
|
||||
SetHighlighted(isInRange);
|
||||
|
||||
if (isInRange)
|
||||
{
|
||||
InteractionPromptUI.Instance?.ShowPrompt(this, "Нажмите E");
|
||||
}
|
||||
else
|
||||
{
|
||||
InteractionPromptUI.Instance?.HidePrompt(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Interact()
|
||||
{
|
||||
DialogueUI.Instance?.ShowDialogue(this);
|
||||
}
|
||||
|
||||
public bool IsOptionAvailable(DialogueOption option)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !option.requiresAllNotes || (QuestManager.Instance != null && QuestManager.Instance.HasCollectedAllNotes);
|
||||
}
|
||||
|
||||
public string GetOptionLabel(DialogueOption option)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (IsOptionAvailable(option))
|
||||
{
|
||||
return option.playerReply;
|
||||
}
|
||||
|
||||
var requiredNotes = QuestManager.Instance != null ? QuestManager.Instance.TotalNotes : 3;
|
||||
return $"{option.playerReply} (нужно {requiredNotes} записки)";
|
||||
}
|
||||
|
||||
public void SelectOption(DialogueOption option)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsOptionAvailable(option))
|
||||
{
|
||||
var fallbackReply = string.IsNullOrWhiteSpace(option.unavailableReply)
|
||||
? "Сначала найди все записки, потом мы продолжим этот разговор."
|
||||
: option.unavailableReply;
|
||||
|
||||
DialogueUI.Instance?.ShowFollowUp(npcName, fallbackReply);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasRegisteredConversation)
|
||||
{
|
||||
hasRegisteredConversation = true;
|
||||
QuestManager.Instance?.NPCTalked();
|
||||
}
|
||||
|
||||
if (option.closeAfterSelection || string.IsNullOrWhiteSpace(option.npcReply))
|
||||
{
|
||||
DialogueUI.Instance?.CloseDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
DialogueUI.Instance?.ShowFollowUp(npcName, option.npcReply);
|
||||
}
|
||||
|
||||
private void SetHighlighted(bool highlighted)
|
||||
{
|
||||
if (npcRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetMaterial = highlighted && highlightMaterial != null ? highlightMaterial : defaultMaterial;
|
||||
|
||||
if (targetMaterial != null)
|
||||
{
|
||||
npcRenderer.material = targetMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e936210040647f69385668d1e0a276a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class NPCTriggerRelay : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NPC owner;
|
||||
|
||||
public void Initialize(NPC npc)
|
||||
{
|
||||
owner = npc;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (owner != null && other.CompareTag("Player"))
|
||||
{
|
||||
owner.SetPlayerInRange(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (owner != null && other.CompareTag("Player"))
|
||||
{
|
||||
owner.SetPlayerInRange(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31b9b66a41fabff4fbd8b31d6b3c2297
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(BoxCollider))]
|
||||
public class Note : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private string noteTitle = "Записка";
|
||||
[SerializeField] private string noteContent = "Текст записки.";
|
||||
[SerializeField] private Renderer noteRenderer;
|
||||
[SerializeField] private Material defaultMaterial;
|
||||
[SerializeField] private Material highlightMaterial;
|
||||
|
||||
private bool playerInRange;
|
||||
private bool collected;
|
||||
|
||||
public void Configure(
|
||||
string title,
|
||||
string content,
|
||||
Renderer targetRenderer,
|
||||
Material baseMaterial,
|
||||
Material selectedMaterial)
|
||||
{
|
||||
noteTitle = title;
|
||||
noteContent = content;
|
||||
noteRenderer = targetRenderer;
|
||||
defaultMaterial = baseMaterial;
|
||||
highlightMaterial = selectedMaterial;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var boxCollider = GetComponent<BoxCollider>();
|
||||
boxCollider.isTrigger = true;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var boxCollider = GetComponent<BoxCollider>();
|
||||
boxCollider.isTrigger = true;
|
||||
|
||||
if (noteRenderer == null)
|
||||
{
|
||||
noteRenderer = GetComponentInChildren<Renderer>();
|
||||
}
|
||||
|
||||
if (defaultMaterial == null && noteRenderer != null)
|
||||
{
|
||||
defaultMaterial = noteRenderer.sharedMaterial;
|
||||
}
|
||||
|
||||
SetHighlighted(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (collected || !playerInRange || GameUIState.IsAnyBlockingUIOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.E))
|
||||
{
|
||||
CollectNote();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (collected || !other.CompareTag("Player"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playerInRange = true;
|
||||
SetHighlighted(true);
|
||||
InteractionPromptUI.Instance?.ShowPrompt(this, "Нажмите E");
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (!other.CompareTag("Player"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
playerInRange = false;
|
||||
SetHighlighted(false);
|
||||
InteractionPromptUI.Instance?.HidePrompt(this);
|
||||
}
|
||||
|
||||
private void CollectNote()
|
||||
{
|
||||
collected = true;
|
||||
playerInRange = false;
|
||||
|
||||
Inventory.Instance?.AddNote(new NoteData(noteTitle, noteContent));
|
||||
NoteUI.Instance?.ShowNote(noteTitle, noteContent);
|
||||
QuestManager.Instance?.NoteCollected();
|
||||
InteractionPromptUI.Instance?.HidePrompt(this);
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void SetHighlighted(bool highlighted)
|
||||
{
|
||||
if (noteRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var targetMaterial = highlighted && highlightMaterial != null ? highlightMaterial : defaultMaterial;
|
||||
|
||||
if (targetMaterial != null)
|
||||
{
|
||||
noteRenderer.material = targetMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94d754049a3dbe924ad853dde1f45358
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
[Serializable]
|
||||
public class NoteData
|
||||
{
|
||||
public string title;
|
||||
public string content;
|
||||
|
||||
public NoteData()
|
||||
{
|
||||
}
|
||||
|
||||
public NoteData(string title, string content)
|
||||
{
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c5c1fee82530f99f93d9d8e5ac3bb7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class NoteUI : MonoBehaviour
|
||||
{
|
||||
private const string PanelKey = "NoteUI";
|
||||
|
||||
public static NoteUI Instance { get; private set; }
|
||||
|
||||
[SerializeField] private GameObject notePanel;
|
||||
[SerializeField] private Text titleText;
|
||||
[SerializeField] private Text contentText;
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
public bool IsOpen => notePanel != null && notePanel.activeSelf;
|
||||
|
||||
public void Configure(GameObject panel, Text titleLabel, Text contentLabel, Button closePanelButton)
|
||||
{
|
||||
notePanel = panel;
|
||||
titleText = titleLabel;
|
||||
contentText = contentLabel;
|
||||
closeButton = closePanelButton;
|
||||
|
||||
if (notePanel != null)
|
||||
{
|
||||
notePanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveListener(CloseNote);
|
||||
closeButton.onClick.AddListener(CloseNote);
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowNote(string noteTitle, string noteContent)
|
||||
{
|
||||
if (notePanel == null || titleText == null || contentText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Inventory.Instance != null && Inventory.Instance.IsOpen)
|
||||
{
|
||||
Inventory.Instance.CloseInventory();
|
||||
}
|
||||
|
||||
if (DialogueUI.Instance != null && DialogueUI.Instance.IsOpen)
|
||||
{
|
||||
DialogueUI.Instance.CloseDialogue();
|
||||
}
|
||||
|
||||
titleText.text = noteTitle;
|
||||
contentText.text = noteContent;
|
||||
notePanel.SetActive(true);
|
||||
GameUIState.SetPanelOpen(PanelKey, true);
|
||||
}
|
||||
|
||||
public void CloseNote()
|
||||
{
|
||||
if (notePanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
notePanel.SetActive(false);
|
||||
GameUIState.SetPanelOpen(PanelKey, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37e1407aec6254fb29b5229978202678
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,186 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class QuestManager : MonoBehaviour
|
||||
{
|
||||
private const string LevelCompletePanelKey = "LevelComplete";
|
||||
|
||||
public static QuestManager Instance { get; private set; }
|
||||
|
||||
[SerializeField] private int totalNotes = 3;
|
||||
[SerializeField] private Text statusText;
|
||||
[SerializeField] private GameObject questCompletePanel;
|
||||
[SerializeField] private Text questCompleteText;
|
||||
[SerializeField] private GameObject exitDoor;
|
||||
[SerializeField] private GameObject exitTrigger;
|
||||
[SerializeField] private GameObject levelCompletePanel;
|
||||
[SerializeField] private Text levelCompleteText;
|
||||
|
||||
private int notesCollected;
|
||||
private bool talkedToNPC;
|
||||
private bool questCompleted;
|
||||
private bool levelFinished;
|
||||
|
||||
public int TotalNotes => totalNotes;
|
||||
public int NotesCollected => notesCollected;
|
||||
public bool TalkedToNPC => talkedToNPC;
|
||||
public bool IsQuestCompleted => questCompleted;
|
||||
public bool HasCollectedAllNotes => notesCollected >= totalNotes;
|
||||
public bool IsLevelFinished => levelFinished;
|
||||
|
||||
public void Configure(
|
||||
Text statusLabel,
|
||||
GameObject completionPanel,
|
||||
Text completionLabel,
|
||||
GameObject doorObject,
|
||||
GameObject exitTriggerObject,
|
||||
GameObject levelFinishedPanel,
|
||||
Text levelFinishedLabel,
|
||||
int requiredNotes)
|
||||
{
|
||||
statusText = statusLabel;
|
||||
questCompletePanel = completionPanel;
|
||||
questCompleteText = completionLabel;
|
||||
exitDoor = doorObject;
|
||||
exitTrigger = exitTriggerObject;
|
||||
levelCompletePanel = levelFinishedPanel;
|
||||
levelCompleteText = levelFinishedLabel;
|
||||
totalNotes = Mathf.Max(1, requiredNotes);
|
||||
|
||||
if (questCompletePanel != null)
|
||||
{
|
||||
questCompletePanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (exitTrigger != null)
|
||||
{
|
||||
exitTrigger.SetActive(false);
|
||||
}
|
||||
|
||||
if (levelCompletePanel != null)
|
||||
{
|
||||
levelCompletePanel.SetActive(false);
|
||||
}
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (questCompletePanel != null)
|
||||
{
|
||||
questCompletePanel.SetActive(false);
|
||||
}
|
||||
|
||||
if (exitTrigger != null)
|
||||
{
|
||||
exitTrigger.SetActive(false);
|
||||
}
|
||||
|
||||
if (levelCompletePanel != null)
|
||||
{
|
||||
levelCompletePanel.SetActive(false);
|
||||
}
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
public void NoteCollected()
|
||||
{
|
||||
notesCollected = Mathf.Min(notesCollected + 1, totalNotes);
|
||||
UpdateStatus();
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
public void NPCTalked()
|
||||
{
|
||||
if (talkedToNPC)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
talkedToNPC = true;
|
||||
UpdateStatus();
|
||||
CheckCompletion();
|
||||
}
|
||||
|
||||
public void FinishLevel()
|
||||
{
|
||||
if (!questCompleted || levelFinished)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
levelFinished = true;
|
||||
|
||||
if (levelCompletePanel != null)
|
||||
{
|
||||
levelCompletePanel.SetActive(true);
|
||||
}
|
||||
|
||||
if (levelCompleteText != null)
|
||||
{
|
||||
levelCompleteText.text = "Уровень завершен!\nЛабораторная работа пройдена.";
|
||||
}
|
||||
|
||||
GameUIState.SetPanelOpen(LevelCompletePanelKey, true);
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void CheckCompletion()
|
||||
{
|
||||
if (questCompleted || notesCollected < totalNotes || !talkedToNPC)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
questCompleted = true;
|
||||
|
||||
if (exitDoor != null)
|
||||
{
|
||||
exitDoor.SetActive(false);
|
||||
}
|
||||
|
||||
if (exitTrigger != null)
|
||||
{
|
||||
exitTrigger.SetActive(true);
|
||||
}
|
||||
|
||||
if (questCompletePanel != null)
|
||||
{
|
||||
questCompletePanel.SetActive(true);
|
||||
}
|
||||
|
||||
if (questCompleteText != null)
|
||||
{
|
||||
questCompleteText.text = "Квест выполнен!\nВыход открыт.";
|
||||
}
|
||||
|
||||
UpdateStatus();
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
if (statusText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
statusText.text =
|
||||
$"Записки: {notesCollected}/{totalNotes}\n" +
|
||||
$"Диалог с NPC: {(talkedToNPC ? "✓" : "✗")}\n" +
|
||||
(levelFinished
|
||||
? "Статус: уровень завершен"
|
||||
: questCompleted
|
||||
? "Статус: пройдите к выходу"
|
||||
: "Статус: исследуйте уровень");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f46aaf9121401153e81d19b18efb518a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,97 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class SimpleFPSController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Camera playerCamera;
|
||||
[SerializeField] private float moveSpeed = 5f;
|
||||
[SerializeField] private float mouseSensitivity = 2f;
|
||||
[SerializeField] private float gravity = -20f;
|
||||
[SerializeField] private bool allowJump = true;
|
||||
[SerializeField] private float jumpHeight = 1.2f;
|
||||
|
||||
private CharacterController characterController;
|
||||
private float verticalVelocity;
|
||||
private float cameraPitch;
|
||||
|
||||
public void Configure(Camera targetCamera)
|
||||
{
|
||||
playerCamera = targetCamera;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
if (playerCamera == null)
|
||||
{
|
||||
playerCamera = GetComponentInChildren<Camera>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
GameUIState.Reset();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandleLook();
|
||||
HandleMovement();
|
||||
}
|
||||
|
||||
private void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
if (hasFocus)
|
||||
{
|
||||
GameUIState.SyncCursorAndTime();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLook()
|
||||
{
|
||||
if (GameUIState.IsAnyBlockingUIOpen || playerCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
|
||||
var mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
|
||||
|
||||
cameraPitch -= mouseY;
|
||||
cameraPitch = Mathf.Clamp(cameraPitch, -80f, 80f);
|
||||
|
||||
playerCamera.transform.localRotation = Quaternion.Euler(cameraPitch, 0f, 0f);
|
||||
transform.Rotate(Vector3.up * mouseX);
|
||||
}
|
||||
|
||||
private void HandleMovement()
|
||||
{
|
||||
if (characterController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (characterController.isGrounded && verticalVelocity < 0f)
|
||||
{
|
||||
verticalVelocity = -2f;
|
||||
}
|
||||
|
||||
if (!GameUIState.IsAnyBlockingUIOpen)
|
||||
{
|
||||
var horizontal = Input.GetAxisRaw("Horizontal");
|
||||
var vertical = Input.GetAxisRaw("Vertical");
|
||||
var moveDirection = (transform.right * horizontal + transform.forward * vertical).normalized;
|
||||
|
||||
characterController.Move(moveDirection * moveSpeed * Time.deltaTime);
|
||||
|
||||
if (allowJump && Input.GetButtonDown("Jump") && characterController.isGrounded)
|
||||
{
|
||||
verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||||
}
|
||||
}
|
||||
|
||||
verticalVelocity += gravity * Time.deltaTime;
|
||||
characterController.Move(Vector3.up * verticalVelocity * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7cb1146f751d99d3a0b71c46bca8f94
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user