first commit

This commit is contained in:
2026-06-04 14:27:16 +03:00
commit 96cb7e9f0e
79 changed files with 8862 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
using UnityEngine;
public class Door : MonoBehaviour
{
public Vector3 openOffset = Vector3.up * 3f;
public float openSpeed = 2f;
public bool isOpen;
private Vector3 closedPosition;
private Vector3 targetPosition;
private Collider doorCollider;
private void Awake()
{
closedPosition = transform.position;
targetPosition = closedPosition;
doorCollider = GetComponent<Collider>();
}
private void Update()
{
// MoveTowards даёт предсказуемое плавное открытие без физики и аниматора.
transform.position = Vector3.MoveTowards(transform.position, targetPosition, openSpeed * Time.deltaTime);
if (isOpen && doorCollider != null && Vector3.Distance(transform.position, targetPosition) < 0.02f)
{
doorCollider.enabled = false;
}
}
public void Open()
{
isOpen = true;
targetPosition = closedPosition + openOffset;
}
public void Close()
{
isOpen = false;
targetPosition = closedPosition;
if (doorCollider != null)
{
doorCollider.enabled = true;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 849ebb2701cd89a3880164b587c2401f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+267
View File
@@ -0,0 +1,267 @@
using UnityEngine;
[RequireComponent(typeof(Collider))]
public class DraggableObject : MonoBehaviour
{
[Header("Item")]
public ItemType itemType;
public Material normalMaterial;
public Material highlightMaterial;
public Material dragMaterial;
public Material inactiveMaterial;
public Vector3 startPosition;
public bool isLocked;
[Header("Drag")]
public float dragLift = 0.35f;
public float snapDistance = 1.25f;
public bool isInteractable = true;
private Camera mainCamera;
private Renderer itemRenderer;
private Plane dragPlane;
private Vector3 dragOffset;
private Vector3 returnPosition;
private Slot highlightedSlot;
private bool isDragging;
private bool isMouseOver;
private void Awake()
{
mainCamera = Camera.main;
itemRenderer = GetComponent<Renderer>();
startPosition = transform.position;
returnPosition = startPosition;
if (itemRenderer != null && normalMaterial == null)
{
normalMaterial = itemRenderer.sharedMaterial;
}
}
private void Start()
{
ApplyNormalMaterial();
}
private void OnMouseEnter()
{
if (!CanInteract())
{
return;
}
isMouseOver = true;
ApplyMaterial(highlightMaterial);
}
private void OnMouseExit()
{
isMouseOver = false;
if (!isDragging && !isLocked)
{
ApplyNormalMaterial();
}
}
private void OnMouseDown()
{
if (!CanInteract())
{
return;
}
if (mainCamera == null)
{
mainCamera = Camera.main;
}
// Запоминаем позицию перед перетаскиванием, чтобы вернуть предмет при ошибке.
returnPosition = transform.position;
dragPlane = new Plane(Vector3.up, new Vector3(0f, startPosition.y, 0f));
if (TryGetMousePointOnDragPlane(out Vector3 mouseWorldPoint))
{
dragOffset = transform.position - mouseWorldPoint;
dragOffset.y = 0f;
}
else
{
dragOffset = Vector3.zero;
}
isDragging = true;
ApplyMaterial(dragMaterial != null ? dragMaterial : highlightMaterial);
}
private void OnMouseDrag()
{
if (!isDragging || !CanInteract())
{
return;
}
if (!TryGetMousePointOnDragPlane(out Vector3 mouseWorldPoint))
{
return;
}
// Offset сохраняет относительное положение курсора и предмета без резких скачков.
Vector3 targetPosition = mouseWorldPoint + dragOffset;
targetPosition.y = startPosition.y + dragLift;
transform.position = targetPosition;
UpdateSlotPreview();
}
private void OnMouseUp()
{
if (!isDragging)
{
return;
}
isDragging = false;
// Предмет фиксируется только в ближайшем подходящем слоте.
Slot nearestSlot = FindNearestSlot();
ClearHighlightedSlot();
if (nearestSlot != null && nearestSlot.CanAccept(this))
{
nearestSlot.PlaceItem(this);
return;
}
transform.position = returnPosition;
ApplyNormalMaterial();
}
public void LockToSlot(Vector3 slotPosition)
{
isLocked = true;
isInteractable = false;
transform.position = slotPosition;
ApplyNormalMaterial();
}
public void SetInteractable(bool value)
{
if (isLocked)
{
isInteractable = false;
return;
}
isInteractable = value;
ApplyNormalMaterial();
}
public void SetNormalMaterial(Material material)
{
normalMaterial = material;
ApplyNormalMaterial();
}
private bool CanInteract()
{
return isInteractable && !isLocked;
}
private bool TryGetMousePointOnDragPlane(out Vector3 worldPoint)
{
worldPoint = Vector3.zero;
if (mainCamera == null)
{
return false;
}
// Raycast в математическую плоскость делает drag стабильным для ортографической камеры.
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (!dragPlane.Raycast(ray, out float distance))
{
return false;
}
worldPoint = ray.GetPoint(distance);
return true;
}
private void UpdateSlotPreview()
{
Slot nearestSlot = FindNearestSlot();
if (nearestSlot == highlightedSlot)
{
return;
}
ClearHighlightedSlot();
highlightedSlot = nearestSlot;
if (highlightedSlot != null)
{
highlightedSlot.ShowPreview(this);
}
}
private Slot FindNearestSlot()
{
Slot[] slots = FindObjectsOfType<Slot>();
Slot nearest = null;
float bestDistance = snapDistance;
foreach (Slot slot in slots)
{
float distance = Vector3.Distance(transform.position, slot.transform.position);
if (distance <= bestDistance)
{
bestDistance = distance;
nearest = slot;
}
}
return nearest;
}
private void ClearHighlightedSlot()
{
if (highlightedSlot != null)
{
highlightedSlot.ClearPreview();
highlightedSlot = null;
}
}
private void ApplyNormalMaterial()
{
if (isLocked)
{
ApplyMaterial(normalMaterial);
return;
}
if (isMouseOver && CanInteract())
{
ApplyMaterial(highlightMaterial);
return;
}
if (!isInteractable && inactiveMaterial != null)
{
ApplyMaterial(inactiveMaterial);
return;
}
ApplyMaterial(normalMaterial);
}
private void ApplyMaterial(Material material)
{
if (itemRenderer != null && material != null)
{
itemRenderer.sharedMaterial = material;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9ba685c5e754cd0adb5c37bc78754cb1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+7
View File
@@ -0,0 +1,7 @@
public enum ItemType
{
Key,
Gem,
Cog,
Battery
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ebf65485a9a3e6c9a4cee39cb1cfaaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+79
View File
@@ -0,0 +1,79 @@
using UnityEngine;
public class PuzzleManager : MonoBehaviour
{
public bool keyPlaced;
public bool gemPlaced;
public bool puzzleSolved;
public PuzzleUI puzzleUI;
public DraggableObject gemItem;
private const string KeyObjective = "Перетащите ключ в слот";
private const string GemAvailableObjective = "Дверь открыта. Возьмите самоцвет за дверью";
private const string GemObjective = "Перетащите самоцвет во второй слот";
private const string SolvedObjective = "Пазл решён. Выход открыт";
private void Start()
{
// Самоцвет становится доступен только после решения первого шага.
if (gemItem != null)
{
gemItem.SetInteractable(false);
}
if (puzzleUI != null)
{
puzzleUI.SetObjective(KeyObjective);
puzzleUI.SetSolvedVisible(false);
puzzleUI.UpdateDebug(keyPlaced, gemPlaced, puzzleSolved);
}
}
public void OnKeyPlaced()
{
keyPlaced = true;
if (gemItem != null)
{
gemItem.SetInteractable(true);
}
if (puzzleUI != null)
{
puzzleUI.SetObjective(GemAvailableObjective);
puzzleUI.ShowMessage(GemObjective);
}
CheckPuzzleSolved();
}
public void OnGemPlaced()
{
gemPlaced = true;
if (puzzleUI != null)
{
puzzleUI.SetObjective(SolvedObjective);
}
CheckPuzzleSolved();
}
public void CheckPuzzleSolved()
{
// Финальное состояние наступает только после обоих правильных размещений.
puzzleSolved = keyPlaced && gemPlaced;
if (puzzleUI != null)
{
puzzleUI.UpdateDebug(keyPlaced, gemPlaced, puzzleSolved);
if (puzzleSolved)
{
puzzleUI.SetSolvedVisible(true);
puzzleUI.ShowMessage("Пазл решён!");
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d6851990dab06249b1fa47ad466c337
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+62
View File
@@ -0,0 +1,62 @@
using UnityEngine;
using UnityEngine.UI;
public class PuzzleUI : MonoBehaviour
{
public Text objectiveText;
public Text solvedText;
public Text debugText;
public Text messageText;
private float messageTimer;
private void Update()
{
if (messageTimer <= 0f || messageText == null || solvedText == null)
{
return;
}
messageTimer -= Time.deltaTime;
if (messageTimer <= 0f && !solvedText.gameObject.activeSelf)
{
messageText.text = string.Empty;
}
}
public void SetObjective(string text)
{
if (objectiveText != null)
{
objectiveText.text = text;
}
}
public void SetSolvedVisible(bool visible)
{
if (solvedText != null)
{
solvedText.gameObject.SetActive(visible);
}
}
public void UpdateDebug(bool keyPlaced, bool gemPlaced, bool solved)
{
if (debugText != null)
{
debugText.text = "Key placed: " + keyPlaced + "\n"
+ "Gem placed: " + gemPlaced + "\n"
+ "Puzzle solved: " + solved;
}
}
public void ShowMessage(string text)
{
if (messageText != null)
{
messageText.text = text;
messageTimer = 3f;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acd7a2645e9fbd10e9308e1f0ad16716
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+92
View File
@@ -0,0 +1,92 @@
using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(Collider))]
public class Slot : MonoBehaviour
{
public ItemType requiredItemType;
public bool isOccupied;
[Header("Visuals")]
public Material normalMaterial;
public Material validPreviewMaterial;
public Material invalidPreviewMaterial;
public Material successMaterial;
[Header("Placement")]
public Transform snapPoint;
public UnityEvent onItemPlaced = new UnityEvent();
private Renderer slotRenderer;
private DraggableObject placedItem;
private void Awake()
{
slotRenderer = GetComponent<Renderer>();
if (slotRenderer != null && normalMaterial == null)
{
normalMaterial = slotRenderer.sharedMaterial;
}
}
private void Start()
{
ApplyMaterial(normalMaterial);
}
public bool CanAccept(DraggableObject item)
{
return item != null && !isOccupied && item.itemType == requiredItemType;
}
public void PlaceItem(DraggableObject item)
{
if (!CanAccept(item))
{
ShowPreview(item);
return;
}
isOccupied = true;
placedItem = item;
// SnapPoint задаёт точку фиксации предмета над плоскостью слота.
Vector3 targetPosition = snapPoint != null ? snapPoint.position : transform.position + Vector3.up * 0.35f;
item.LockToSlot(targetPosition);
ApplyMaterial(successMaterial);
onItemPlaced.Invoke();
}
public void ResetSlot()
{
isOccupied = false;
placedItem = null;
ApplyMaterial(normalMaterial);
}
public void ShowPreview(DraggableObject item)
{
if (isOccupied)
{
ApplyMaterial(successMaterial);
return;
}
ApplyMaterial(CanAccept(item) ? validPreviewMaterial : invalidPreviewMaterial);
}
public void ClearPreview()
{
ApplyMaterial(isOccupied ? successMaterial : normalMaterial);
}
private void ApplyMaterial(Material material)
{
if (slotRenderer != null && material != null)
{
slotRenderer.sharedMaterial = material;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 968f7a479c33d6aa9a32a2b1176a06ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: