first commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class BillboardNameplate : MonoBehaviour
|
||||
{
|
||||
public Camera targetCamera;
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
Camera cam = targetCamera != null ? targetCamera : Camera.main;
|
||||
if (cam == null)
|
||||
return;
|
||||
|
||||
transform.rotation = Quaternion.LookRotation(transform.position - cam.transform.position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a56680789bcc19bc2af5ed5e1245b7e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class DialogueHUD : MonoBehaviour
|
||||
{
|
||||
public static DialogueHUD Instance { get; private set; }
|
||||
|
||||
[Header("UI")]
|
||||
public Text hintText;
|
||||
public Text statusText;
|
||||
public Text inventoryText;
|
||||
|
||||
[Header("Data")]
|
||||
public Inventory inventory;
|
||||
public float messageDuration = 2.5f;
|
||||
|
||||
private Coroutine messageRoutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (inventory != null)
|
||||
inventory.OnInventoryChanged += UpdateInventoryText;
|
||||
|
||||
HideHint();
|
||||
UpdateInventoryText();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (inventory != null)
|
||||
inventory.OnInventoryChanged -= UpdateInventoryText;
|
||||
}
|
||||
|
||||
public void ShowHint(string message)
|
||||
{
|
||||
if (hintText == null)
|
||||
return;
|
||||
|
||||
hintText.text = message;
|
||||
SetTextRootActive(hintText, !string.IsNullOrEmpty(message));
|
||||
}
|
||||
|
||||
public void HideHint()
|
||||
{
|
||||
if (hintText == null)
|
||||
return;
|
||||
|
||||
hintText.text = string.Empty;
|
||||
SetTextRootActive(hintText, false);
|
||||
}
|
||||
|
||||
public void SetStatus(string message)
|
||||
{
|
||||
if (statusText == null)
|
||||
return;
|
||||
|
||||
statusText.text = message;
|
||||
SetTextRootActive(statusText, !string.IsNullOrEmpty(message));
|
||||
|
||||
if (messageRoutine != null)
|
||||
StopCoroutine(messageRoutine);
|
||||
|
||||
if (!string.IsNullOrEmpty(message) && gameObject.activeInHierarchy)
|
||||
messageRoutine = StartCoroutine(ClearStatusAfterDelay());
|
||||
}
|
||||
|
||||
public void UpdateInventoryText()
|
||||
{
|
||||
if (inventoryText == null)
|
||||
return;
|
||||
|
||||
inventoryText.text = inventory != null ? inventory.GetDebugText() : "Инвентарь: недоступен";
|
||||
}
|
||||
|
||||
private IEnumerator ClearStatusAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(messageDuration);
|
||||
|
||||
if (statusText != null)
|
||||
{
|
||||
statusText.text = string.Empty;
|
||||
SetTextRootActive(statusText, false);
|
||||
}
|
||||
|
||||
messageRoutine = null;
|
||||
}
|
||||
|
||||
private static void SetTextRootActive(Text text, bool active)
|
||||
{
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
Transform parent = text.transform.parent;
|
||||
if (parent != null && parent.GetComponent<Image>() != null)
|
||||
parent.gameObject.SetActive(active);
|
||||
else
|
||||
text.gameObject.SetActive(active);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 209c58a4fe0343f04ad50081998de8c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,404 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class DialogueManager : MonoBehaviour
|
||||
{
|
||||
public static DialogueManager Instance { get; private set; }
|
||||
|
||||
[Header("UI")]
|
||||
public GameObject dialoguePanel;
|
||||
public Text speakerText;
|
||||
public Text npcText;
|
||||
public Transform responseContainer;
|
||||
public ScrollRect responseScrollRect;
|
||||
public GameObject responseButtonPrefab;
|
||||
public Text hintText;
|
||||
public Text statusText;
|
||||
|
||||
[Header("Scene References")]
|
||||
public Inventory playerInventory;
|
||||
public SimplePlayerController playerController;
|
||||
|
||||
[Header("Typewriter")]
|
||||
public float characterDelay = 0.025f;
|
||||
|
||||
public bool IsDialogueActive { get; private set; }
|
||||
|
||||
private DialogueNode currentNode;
|
||||
private string currentNpcLine;
|
||||
private Coroutine typewriterRoutine;
|
||||
private bool isTyping;
|
||||
private string overrideSpeakerName;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
|
||||
if (dialoguePanel != null)
|
||||
dialoguePanel.SetActive(false);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsDialogueActive)
|
||||
return;
|
||||
|
||||
if (isTyping && (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0)))
|
||||
SkipTypewriter();
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
EndDialogue();
|
||||
}
|
||||
|
||||
public void StartDialogue(DialogueNode startNode, string npcDisplayName = null)
|
||||
{
|
||||
if (IsDialogueActive || startNode == null)
|
||||
return;
|
||||
|
||||
IsDialogueActive = true;
|
||||
overrideSpeakerName = npcDisplayName;
|
||||
|
||||
if (dialoguePanel != null)
|
||||
dialoguePanel.SetActive(true);
|
||||
|
||||
SetHint(string.Empty);
|
||||
SetStatus(string.Empty);
|
||||
|
||||
if (playerController != null)
|
||||
playerController.SetInputEnabled(false);
|
||||
else
|
||||
{
|
||||
Cursor.visible = true;
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
}
|
||||
|
||||
ShowNode(startNode);
|
||||
}
|
||||
|
||||
public void ShowNode(DialogueNode node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
currentNode = node;
|
||||
currentNpcLine = string.IsNullOrEmpty(node.npcLine) ? string.Empty : node.npcLine;
|
||||
|
||||
if (speakerText != null)
|
||||
{
|
||||
string speaker = !string.IsNullOrEmpty(node.speakerName) ? node.speakerName : overrideSpeakerName;
|
||||
speakerText.text = string.IsNullOrEmpty(speaker) ? "NPC" : speaker;
|
||||
}
|
||||
|
||||
ClearResponses();
|
||||
|
||||
if (typewriterRoutine != null)
|
||||
StopCoroutine(typewriterRoutine);
|
||||
|
||||
typewriterRoutine = StartCoroutine(TypeLine(currentNpcLine));
|
||||
}
|
||||
|
||||
public void EndDialogue()
|
||||
{
|
||||
if (!IsDialogueActive)
|
||||
return;
|
||||
|
||||
if (typewriterRoutine != null)
|
||||
{
|
||||
StopCoroutine(typewriterRoutine);
|
||||
typewriterRoutine = null;
|
||||
}
|
||||
|
||||
isTyping = false;
|
||||
currentNode = null;
|
||||
ClearResponses();
|
||||
|
||||
if (dialoguePanel != null)
|
||||
dialoguePanel.SetActive(false);
|
||||
|
||||
if (playerController != null)
|
||||
playerController.SetInputEnabled(true);
|
||||
else
|
||||
{
|
||||
Cursor.visible = false;
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
}
|
||||
|
||||
IsDialogueActive = false;
|
||||
SetStatus("Диалог завершён");
|
||||
}
|
||||
|
||||
public void OnResponseSelected(DialogueResponse response)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTyping)
|
||||
{
|
||||
SkipTypewriter();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckRequirements(response))
|
||||
{
|
||||
string requiredName = response.requiredItem != null ? response.requiredItem.itemName : "предмет";
|
||||
SetStatus(!string.IsNullOrEmpty(response.unavailableText) ? response.unavailableText : "Нужен предмет: " + requiredName);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyResponseEffects(response);
|
||||
|
||||
if (response.endsDialogue || response.nextNode == null)
|
||||
EndDialogue();
|
||||
else
|
||||
ShowNode(response.nextNode);
|
||||
}
|
||||
|
||||
public void CreateResponseButton(DialogueResponse response, bool interactable, string buttonText)
|
||||
{
|
||||
if (responseContainer == null)
|
||||
return;
|
||||
|
||||
GameObject buttonObject = responseButtonPrefab != null
|
||||
? Instantiate(responseButtonPrefab, responseContainer)
|
||||
: CreateFallbackButton(responseContainer);
|
||||
|
||||
buttonObject.name = interactable ? "ResponseButton" : "ResponseButton_Disabled";
|
||||
buttonObject.SetActive(true);
|
||||
|
||||
Text label = buttonObject.GetComponentInChildren<Text>(true);
|
||||
if (label != null)
|
||||
{
|
||||
label.text = buttonText;
|
||||
label.color = interactable ? Color.white : new Color(0.68f, 0.7f, 0.73f, 1f);
|
||||
}
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
if (button == null)
|
||||
button = buttonObject.AddComponent<Button>();
|
||||
|
||||
button.interactable = interactable;
|
||||
button.onClick.RemoveAllListeners();
|
||||
|
||||
if (interactable)
|
||||
button.onClick.AddListener(() => OnResponseSelected(response));
|
||||
|
||||
Image image = buttonObject.GetComponent<Image>();
|
||||
if (image != null && !interactable)
|
||||
image.color = new Color(0.12f, 0.13f, 0.15f, 0.88f);
|
||||
|
||||
if (responseScrollRect != null)
|
||||
responseScrollRect.verticalNormalizedPosition = 1f;
|
||||
}
|
||||
|
||||
public bool CheckRequirements(DialogueResponse response)
|
||||
{
|
||||
if (response == null || response.requiredItem == null)
|
||||
return true;
|
||||
|
||||
if (playerInventory == null)
|
||||
return false;
|
||||
|
||||
return playerInventory.HasItem(response.requiredItem, Mathf.Max(1, response.requiredItemAmount));
|
||||
}
|
||||
|
||||
public void ApplyResponseEffects(DialogueResponse response)
|
||||
{
|
||||
if (response == null || playerInventory == null)
|
||||
return;
|
||||
|
||||
if (response.consumeRequiredItem && response.requiredItem != null)
|
||||
{
|
||||
int amount = Mathf.Max(1, response.requiredItemAmount);
|
||||
if (playerInventory.RemoveItem(response.requiredItem, amount))
|
||||
SetStatus("Списан предмет: " + response.requiredItem.itemName);
|
||||
}
|
||||
|
||||
if (response.rewardItem != null && response.rewardAmount > 0)
|
||||
{
|
||||
if (playerInventory.AddItem(response.rewardItem, response.rewardAmount))
|
||||
SetStatus("Получен предмет: " + response.rewardItem.itemName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(response.eventId))
|
||||
Debug.Log("Dialogue event fired: " + response.eventId);
|
||||
}
|
||||
|
||||
public void SkipTypewriter()
|
||||
{
|
||||
if (!isTyping)
|
||||
return;
|
||||
|
||||
if (typewriterRoutine != null)
|
||||
{
|
||||
StopCoroutine(typewriterRoutine);
|
||||
typewriterRoutine = null;
|
||||
}
|
||||
|
||||
if (npcText != null)
|
||||
npcText.text = currentNpcLine;
|
||||
|
||||
isTyping = false;
|
||||
BuildResponseButtons();
|
||||
}
|
||||
|
||||
public void ClearResponses()
|
||||
{
|
||||
if (responseContainer == null)
|
||||
return;
|
||||
|
||||
for (int i = responseContainer.childCount - 1; i >= 0; i--)
|
||||
Destroy(responseContainer.GetChild(i).gameObject);
|
||||
}
|
||||
|
||||
public void SetStatus(string message)
|
||||
{
|
||||
if (statusText != null)
|
||||
{
|
||||
statusText.text = message;
|
||||
SetTextRootActive(statusText, !string.IsNullOrEmpty(message));
|
||||
}
|
||||
|
||||
if (DialogueHUD.Instance != null && !string.IsNullOrEmpty(message))
|
||||
DialogueHUD.Instance.SetStatus(message);
|
||||
}
|
||||
|
||||
private IEnumerator TypeLine(string line)
|
||||
{
|
||||
isTyping = true;
|
||||
|
||||
if (npcText != null)
|
||||
npcText.text = string.Empty;
|
||||
|
||||
for (int i = 0; i < line.Length; i++)
|
||||
{
|
||||
if (npcText != null)
|
||||
npcText.text = line.Substring(0, i + 1);
|
||||
|
||||
yield return new WaitForSeconds(characterDelay);
|
||||
}
|
||||
|
||||
isTyping = false;
|
||||
typewriterRoutine = null;
|
||||
BuildResponseButtons();
|
||||
}
|
||||
|
||||
private void BuildResponseButtons()
|
||||
{
|
||||
ClearResponses();
|
||||
|
||||
if (currentNode == null)
|
||||
return;
|
||||
|
||||
int visibleCount = 0;
|
||||
int interactableCount = 0;
|
||||
DialogueResponse[] responses = currentNode.responses;
|
||||
|
||||
if (responses != null)
|
||||
{
|
||||
foreach (DialogueResponse response in responses)
|
||||
{
|
||||
if (response == null)
|
||||
continue;
|
||||
|
||||
bool requirementsMet = CheckRequirements(response);
|
||||
if (!requirementsMet && response.hideIfRequirementsNotMet)
|
||||
continue;
|
||||
|
||||
string text = response.responseText;
|
||||
if (!requirementsMet)
|
||||
text = !string.IsNullOrEmpty(response.unavailableText) ? response.unavailableText : BuildUnavailableText(response);
|
||||
|
||||
CreateResponseButton(response, requirementsMet, text);
|
||||
visibleCount++;
|
||||
|
||||
if (requirementsMet)
|
||||
interactableCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleCount == 0 || interactableCount == 0 || currentNode.isEndNode)
|
||||
{
|
||||
DialogueResponse closeResponse = new DialogueResponse
|
||||
{
|
||||
responseText = "До свидания.",
|
||||
endsDialogue = true
|
||||
};
|
||||
CreateResponseButton(closeResponse, true, "До свидания");
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildUnavailableText(DialogueResponse response)
|
||||
{
|
||||
if (response.requiredItem == null)
|
||||
return "Недоступно";
|
||||
|
||||
int amount = Mathf.Max(1, response.requiredItemAmount);
|
||||
return amount > 1
|
||||
? "Недоступно: нужен предмет " + response.requiredItem.itemName + " x" + amount
|
||||
: "Недоступно: нужен предмет " + response.requiredItem.itemName;
|
||||
}
|
||||
|
||||
private void SetHint(string message)
|
||||
{
|
||||
if (hintText != null)
|
||||
{
|
||||
hintText.text = message;
|
||||
SetTextRootActive(hintText, !string.IsNullOrEmpty(message));
|
||||
}
|
||||
|
||||
if (DialogueHUD.Instance != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message))
|
||||
DialogueHUD.Instance.HideHint();
|
||||
else
|
||||
DialogueHUD.Instance.ShowHint(message);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject CreateFallbackButton(Transform parent)
|
||||
{
|
||||
GameObject buttonObject = new GameObject("ResponseButton", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
buttonObject.transform.SetParent(parent, false);
|
||||
|
||||
GameObject labelObject = new GameObject("Text", typeof(RectTransform), typeof(Text));
|
||||
labelObject.transform.SetParent(buttonObject.transform, false);
|
||||
|
||||
RectTransform labelRect = labelObject.GetComponent<RectTransform>();
|
||||
labelRect.anchorMin = Vector2.zero;
|
||||
labelRect.anchorMax = Vector2.one;
|
||||
labelRect.offsetMin = new Vector2(16f, 6f);
|
||||
labelRect.offsetMax = new Vector2(-16f, -6f);
|
||||
|
||||
Text label = labelObject.GetComponent<Text>();
|
||||
label.color = Color.white;
|
||||
label.alignment = TextAnchor.MiddleLeft;
|
||||
label.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
|
||||
return buttonObject;
|
||||
}
|
||||
|
||||
private static void SetTextRootActive(Text text, bool active)
|
||||
{
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
Transform parent = text.transform.parent;
|
||||
if (parent != null && parent.GetComponent<Image>() != null)
|
||||
parent.gameObject.SetActive(active);
|
||||
else
|
||||
text.gameObject.SetActive(active);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d5c728207b3a368ea9bfcc9e30ffb6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "DialogueNode", menuName = "Dialogue/DialogueNode")]
|
||||
public class DialogueNode : ScriptableObject
|
||||
{
|
||||
public string nodeId;
|
||||
public string speakerName;
|
||||
|
||||
[TextArea(3, 8)]
|
||||
public string npcLine;
|
||||
|
||||
public DialogueResponse[] responses;
|
||||
public bool isEndNode;
|
||||
public string editorNote;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DialogueResponse
|
||||
{
|
||||
public string responseText;
|
||||
public DialogueNode nextNode;
|
||||
public bool endsDialogue;
|
||||
public ItemData requiredItem;
|
||||
public int requiredItemAmount = 1;
|
||||
public string unavailableText;
|
||||
public bool hideIfRequirementsNotMet;
|
||||
public bool consumeRequiredItem;
|
||||
public ItemData rewardItem;
|
||||
public int rewardAmount;
|
||||
public string eventId;
|
||||
public string editorNote;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 760b0a0b8bcf617edbf21f60d51169f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
[Serializable]
|
||||
public class InventorySlot
|
||||
{
|
||||
public ItemData item;
|
||||
public int amount;
|
||||
|
||||
public InventorySlot(ItemData item, int amount)
|
||||
{
|
||||
this.item = item;
|
||||
this.amount = Mathf.Max(0, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public class Inventory : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private List<InventorySlot> slots = new List<InventorySlot>();
|
||||
|
||||
public event Action OnInventoryChanged;
|
||||
|
||||
public IReadOnlyList<InventorySlot> Slots => slots;
|
||||
|
||||
public bool HasItem(ItemData item, int amount)
|
||||
{
|
||||
if (item == null)
|
||||
return true;
|
||||
|
||||
return GetAmount(item) >= Mathf.Max(1, amount);
|
||||
}
|
||||
|
||||
public int GetAmount(ItemData item)
|
||||
{
|
||||
if (item == null)
|
||||
return 0;
|
||||
|
||||
InventorySlot slot = FindSlot(item);
|
||||
return slot != null ? Mathf.Max(0, slot.amount) : 0;
|
||||
}
|
||||
|
||||
public bool AddItem(ItemData item, int amount)
|
||||
{
|
||||
if (item == null || amount <= 0)
|
||||
return false;
|
||||
|
||||
InventorySlot slot = FindSlot(item);
|
||||
int maxStack = Mathf.Max(1, item.maxStack);
|
||||
|
||||
if (slot == null)
|
||||
{
|
||||
slots.Add(new InventorySlot(item, Mathf.Min(amount, maxStack)));
|
||||
RaiseChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
int newAmount = Mathf.Clamp(slot.amount + amount, 0, maxStack);
|
||||
if (newAmount == slot.amount)
|
||||
return false;
|
||||
|
||||
slot.amount = newAmount;
|
||||
RaiseChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveItem(ItemData item, int amount)
|
||||
{
|
||||
if (item == null || amount <= 0)
|
||||
return false;
|
||||
|
||||
InventorySlot slot = FindSlot(item);
|
||||
if (slot == null || slot.amount < amount)
|
||||
return false;
|
||||
|
||||
slot.amount -= amount;
|
||||
if (slot.amount <= 0)
|
||||
slots.Remove(slot);
|
||||
|
||||
RaiseChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetDebugText()
|
||||
{
|
||||
if (slots.Count == 0)
|
||||
return "Инвентарь: пусто";
|
||||
|
||||
StringBuilder builder = new StringBuilder("Инвентарь: ");
|
||||
bool first = true;
|
||||
|
||||
foreach (InventorySlot slot in slots)
|
||||
{
|
||||
if (slot == null || slot.item == null || slot.amount <= 0)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
builder.Append(", ");
|
||||
|
||||
builder.Append(slot.item.itemName);
|
||||
builder.Append(" x");
|
||||
builder.Append(slot.amount);
|
||||
first = false;
|
||||
}
|
||||
|
||||
return first ? "Инвентарь: пусто" : builder.ToString();
|
||||
}
|
||||
|
||||
private InventorySlot FindSlot(ItemData item)
|
||||
{
|
||||
return slots.Find(slot => slot != null && slot.item == item);
|
||||
}
|
||||
|
||||
private void RaiseChanged()
|
||||
{
|
||||
OnInventoryChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 041b00ae48363aabaaf30a53abcd35ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(fileName = "ItemData", menuName = "Dialogue/ItemData")]
|
||||
public class ItemData : ScriptableObject
|
||||
{
|
||||
public string itemId;
|
||||
public string itemName;
|
||||
|
||||
[TextArea(2, 5)]
|
||||
public string description;
|
||||
|
||||
public Color itemColor = Color.white;
|
||||
public int maxStack = 99;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10cf6353066082490a62140ae9883b4e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(SphereCollider))]
|
||||
public class NPCInteract : MonoBehaviour
|
||||
{
|
||||
public DialogueNode startNode;
|
||||
public string npcDisplayName = "Смотритель ворот";
|
||||
public float interactionRadius = 3f;
|
||||
public KeyCode interactKey = KeyCode.E;
|
||||
public DialogueManager dialogueManager;
|
||||
public Renderer highlightRenderer;
|
||||
public Color highlightColor = new Color(1f, 0.78f, 0.22f, 1f);
|
||||
|
||||
private Transform player;
|
||||
private bool playerInRange;
|
||||
private Color baseColor;
|
||||
private Material runtimeMaterial;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SphereCollider trigger = GetComponent<SphereCollider>();
|
||||
trigger.isTrigger = true;
|
||||
trigger.radius = interactionRadius;
|
||||
|
||||
if (highlightRenderer == null)
|
||||
highlightRenderer = GetComponentInChildren<Renderer>();
|
||||
|
||||
if (highlightRenderer != null)
|
||||
{
|
||||
runtimeMaterial = highlightRenderer.material;
|
||||
baseColor = runtimeMaterial.color;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (dialogueManager == null)
|
||||
dialogueManager = DialogueManager.Instance;
|
||||
|
||||
SimplePlayerController controller = FindObjectOfType<SimplePlayerController>();
|
||||
if (controller != null)
|
||||
player = controller.transform;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (player != null)
|
||||
SetPlayerInRange(Vector3.Distance(transform.position, player.position) <= interactionRadius);
|
||||
|
||||
bool dialogueActive = dialogueManager != null && dialogueManager.IsDialogueActive;
|
||||
|
||||
if (!playerInRange || dialogueActive)
|
||||
return;
|
||||
|
||||
ShowHint();
|
||||
|
||||
if (Input.GetKeyDown(interactKey) && startNode != null && dialogueManager != null)
|
||||
dialogueManager.StartDialogue(startNode, npcDisplayName);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
{
|
||||
player = other.transform;
|
||||
SetPlayerInRange(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
SetPlayerInRange(false);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(1f, 0.75f, 0.2f, 0.25f);
|
||||
Gizmos.DrawSphere(transform.position, interactionRadius);
|
||||
Gizmos.color = new Color(1f, 0.75f, 0.2f, 1f);
|
||||
Gizmos.DrawWireSphere(transform.position, interactionRadius);
|
||||
}
|
||||
|
||||
private void SetPlayerInRange(bool inRange)
|
||||
{
|
||||
if (playerInRange == inRange)
|
||||
return;
|
||||
|
||||
playerInRange = inRange;
|
||||
SetHighlight(inRange);
|
||||
|
||||
if (!inRange && DialogueHUD.Instance != null)
|
||||
DialogueHUD.Instance.HideHint();
|
||||
}
|
||||
|
||||
private void SetHighlight(bool enabled)
|
||||
{
|
||||
if (runtimeMaterial == null)
|
||||
return;
|
||||
|
||||
runtimeMaterial.color = enabled ? highlightColor : baseColor;
|
||||
}
|
||||
|
||||
private void ShowHint()
|
||||
{
|
||||
if (DialogueHUD.Instance != null)
|
||||
DialogueHUD.Instance.ShowHint("Нажмите E для разговора");
|
||||
else if (dialogueManager != null && dialogueManager.hintText != null)
|
||||
{
|
||||
dialogueManager.hintText.text = "Нажмите E для разговора";
|
||||
dialogueManager.hintText.gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df6b7e66747a822b487e1fb1c9b6e736
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(SphereCollider))]
|
||||
public class PickupItem : MonoBehaviour
|
||||
{
|
||||
public ItemData item;
|
||||
public int amount = 1;
|
||||
public KeyCode interactKey = KeyCode.E;
|
||||
public float interactionRadius = 2.25f;
|
||||
public Inventory targetInventory;
|
||||
|
||||
private Transform player;
|
||||
private bool playerInRange;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SphereCollider trigger = GetComponent<SphereCollider>();
|
||||
trigger.isTrigger = true;
|
||||
trigger.radius = interactionRadius;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (targetInventory == null)
|
||||
targetInventory = FindObjectOfType<Inventory>();
|
||||
|
||||
SimplePlayerController controller = FindObjectOfType<SimplePlayerController>();
|
||||
if (controller != null)
|
||||
player = controller.transform;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (player != null)
|
||||
SetPlayerInRange(Vector3.Distance(transform.position, player.position) <= interactionRadius);
|
||||
|
||||
if (!playerInRange || item == null || targetInventory == null)
|
||||
return;
|
||||
|
||||
if (DialogueManager.Instance != null && DialogueManager.Instance.IsDialogueActive)
|
||||
return;
|
||||
|
||||
ShowHint();
|
||||
|
||||
if (Input.GetKeyDown(interactKey) && targetInventory.AddItem(item, amount))
|
||||
{
|
||||
if (DialogueHUD.Instance != null)
|
||||
{
|
||||
DialogueHUD.Instance.SetStatus("Получен предмет: " + item.itemName);
|
||||
DialogueHUD.Instance.HideHint();
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
{
|
||||
player = other.transform;
|
||||
SetPlayerInRange(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (other.GetComponentInParent<SimplePlayerController>() != null)
|
||||
SetPlayerInRange(false);
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.25f);
|
||||
Gizmos.DrawSphere(transform.position, interactionRadius);
|
||||
Gizmos.color = new Color(0.2f, 0.8f, 1f, 1f);
|
||||
Gizmos.DrawWireSphere(transform.position, interactionRadius);
|
||||
}
|
||||
|
||||
private void SetPlayerInRange(bool inRange)
|
||||
{
|
||||
if (playerInRange == inRange)
|
||||
return;
|
||||
|
||||
playerInRange = inRange;
|
||||
|
||||
if (!inRange && DialogueHUD.Instance != null)
|
||||
DialogueHUD.Instance.HideHint();
|
||||
}
|
||||
|
||||
private void ShowHint()
|
||||
{
|
||||
if (DialogueHUD.Instance != null)
|
||||
DialogueHUD.Instance.ShowHint("Нажмите E, чтобы подобрать: " + item.itemName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05965783683e09fc7b4fdcb59d65f7bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class SimplePlayerController : MonoBehaviour
|
||||
{
|
||||
[Header("Movement")]
|
||||
public float moveSpeed = 4.5f;
|
||||
public float jumpForce = 1.2f;
|
||||
public float gravity = -18f;
|
||||
|
||||
[Header("Look")]
|
||||
public Camera playerCamera;
|
||||
public float mouseSensitivity = 2.2f;
|
||||
public float minPitch = -75f;
|
||||
public float maxPitch = 75f;
|
||||
|
||||
private CharacterController characterController;
|
||||
private Vector3 verticalVelocity;
|
||||
private float pitch;
|
||||
private bool inputEnabled = true;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
characterController = GetComponent<CharacterController>();
|
||||
|
||||
if (playerCamera == null)
|
||||
playerCamera = GetComponentInChildren<Camera>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetInputEnabled(true);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!inputEnabled)
|
||||
return;
|
||||
|
||||
Look();
|
||||
Move();
|
||||
}
|
||||
|
||||
public void SetInputEnabled(bool enabled)
|
||||
{
|
||||
inputEnabled = enabled;
|
||||
|
||||
if (!enabled)
|
||||
verticalVelocity = Vector3.zero;
|
||||
|
||||
Cursor.visible = !enabled;
|
||||
Cursor.lockState = enabled ? CursorLockMode.Locked : CursorLockMode.None;
|
||||
}
|
||||
|
||||
private void Look()
|
||||
{
|
||||
float yaw = Input.GetAxis("Mouse X") * mouseSensitivity;
|
||||
float pitchDelta = Input.GetAxis("Mouse Y") * mouseSensitivity;
|
||||
|
||||
transform.Rotate(Vector3.up * yaw);
|
||||
pitch = Mathf.Clamp(pitch - pitchDelta, minPitch, maxPitch);
|
||||
|
||||
if (playerCamera != null)
|
||||
playerCamera.transform.localEulerAngles = new Vector3(pitch, 0f, 0f);
|
||||
}
|
||||
|
||||
private void Move()
|
||||
{
|
||||
bool grounded = characterController.isGrounded;
|
||||
if (grounded && verticalVelocity.y < 0f)
|
||||
verticalVelocity.y = -2f;
|
||||
|
||||
float horizontal = Input.GetAxis("Horizontal");
|
||||
float vertical = Input.GetAxis("Vertical");
|
||||
Vector3 move = transform.right * horizontal + transform.forward * vertical;
|
||||
|
||||
if (move.sqrMagnitude > 1f)
|
||||
move.Normalize();
|
||||
|
||||
characterController.Move(move * (moveSpeed * Time.deltaTime));
|
||||
|
||||
if (grounded && Input.GetKeyDown(KeyCode.Space))
|
||||
verticalVelocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
|
||||
|
||||
verticalVelocity.y += gravity * Time.deltaTime;
|
||||
characterController.Move(verticalVelocity * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0993d8504dc71019bbeb442778b3b51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user