Compare commits
3 Commits
07626aa550
...
59f11a4b6f
Author | SHA1 | Date | |
---|---|---|---|
59f11a4b6f | |||
7962b96976 | |||
7bec58ece8 |
@ -100092,7 +100092,7 @@ GameObject:
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 630530476}
|
||||
m_Layer: 0
|
||||
m_Layer: 8
|
||||
m_Name: AbilityCastPoint
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
@ -191096,7 +191096,7 @@ Transform:
|
||||
m_GameObject: {fileID: 1236565186}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 141.0367, y: 26.4137, z: 389.6348}
|
||||
m_LocalPosition: {x: 140.95, y: 26.4137, z: 389.6348}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
@ -198170,7 +198170,7 @@ Transform:
|
||||
m_GameObject: {fileID: 1282845310}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalPosition: {x: 2.3, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@ -355507,11 +355507,21 @@ PrefabInstance:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 0}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2191382144852172495, guid: 576abe9759f86e542b8cee9ae2f0ffd7,
|
||||
type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 8
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3960509566317876621, guid: 576abe9759f86e542b8cee9ae2f0ffd7,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Character
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3960509566317876621, guid: 576abe9759f86e542b8cee9ae2f0ffd7,
|
||||
type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 8
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4580555029999605212, guid: 576abe9759f86e542b8cee9ae2f0ffd7,
|
||||
type: 3}
|
||||
propertyPath: manaFillImage
|
||||
|
@ -71,6 +71,7 @@ public class FirstPersonController: MonoBehaviour
|
||||
[SerializeField] private AudioClip[] stoneClips = default;
|
||||
[SerializeField] private AudioClip[] waterClips = default;
|
||||
[SerializeField] private AudioClip[] grassClips = default;
|
||||
public Camera PlayerCamera => playerCamera;
|
||||
|
||||
private float footstepTimer = 0;
|
||||
private float GetCurrentOffset => isCrouching ? baseStepSpeed * crouchStepMultiplier : isSprinting ? baseStepSpeed * sprintStepMultiplier : baseStepSpeed;
|
||||
|
@ -13,7 +13,7 @@ GameObject:
|
||||
- component: {fileID: 4256647660557216355}
|
||||
- component: {fileID: 6161152517530157023}
|
||||
- component: {fileID: 6947929681936346855}
|
||||
m_Layer: 0
|
||||
m_Layer: 7
|
||||
m_Name: PF_Fireball
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
@ -142,10 +142,26 @@ PrefabInstance:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 7202875268415372754}
|
||||
m_Modifications:
|
||||
- target: {fileID: 112572, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 126572, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 147436, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: FireballMissileFire
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 147436, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 166048, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 7
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 460430, guid: 3b671081e44be1c4aa4355e8ba6e8a5e, type: 3}
|
||||
propertyPath: m_LocalScale.x
|
||||
value: 2
|
||||
|
@ -12,14 +12,50 @@ public class FireballAbility : Ability
|
||||
{
|
||||
if (!user || !projectilePrefab) return;
|
||||
|
||||
var go = Instantiate(projectilePrefab, user.CastPos(), Quaternion.LookRotation(user.Forward()));
|
||||
var rb = go.GetComponent<Rigidbody>();
|
||||
if (rb) rb.velocity = user.Forward() * speed;
|
||||
// 1) Figure out an aim direction that includes camera pitch
|
||||
// Try to use an "aim camera" on the user if you have one; otherwise Camera.main.
|
||||
Camera cam = null;
|
||||
if (Polyart.FirstPersonController.instance)
|
||||
cam = Polyart.FirstPersonController.instance.PlayerCamera;
|
||||
if (!cam) cam = Camera.main;
|
||||
|
||||
|
||||
Vector3 castPos = user.CastPos();
|
||||
Vector3 dir = user.Forward(); // safe fallback
|
||||
|
||||
if (cam)
|
||||
{
|
||||
// Ray from camera forward
|
||||
Ray ray = new Ray(cam.transform.position, cam.transform.forward);
|
||||
|
||||
// If we hit something, aim at that point; otherwise aim far away
|
||||
Vector3 targetPoint;
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f, ~0, QueryTriggerInteraction.Ignore))
|
||||
targetPoint = hit.point;
|
||||
else
|
||||
targetPoint = ray.origin + ray.direction * 1000f;
|
||||
|
||||
dir = (targetPoint - castPos).normalized;
|
||||
}
|
||||
|
||||
// 2) Spawn & orient the projectile along that 3D direction
|
||||
var go = Instantiate(projectilePrefab, castPos, Quaternion.LookRotation(dir));
|
||||
|
||||
// 3) Give it velocity along the aimed direction
|
||||
var rb = go.GetComponent<Rigidbody>();
|
||||
if (rb)
|
||||
{
|
||||
rb.velocity = dir * speed; // or: rb.AddForce(dir * speed, ForceMode.VelocityChange);
|
||||
rb.useGravity = false; // optional: keep straight flight
|
||||
rb.collisionDetectionMode = CollisionDetectionMode.Continuous; // nicer for fast shots
|
||||
}
|
||||
|
||||
// 4) Damage component (unchanged)
|
||||
var dmg = go.GetComponent<ProjectileDamage>();
|
||||
if (!dmg) dmg = go.AddComponent<ProjectileDamage>();
|
||||
dmg.damage = damage;
|
||||
|
||||
Destroy(go, lifeTime);
|
||||
}
|
||||
|
||||
}
|
@ -14,19 +14,47 @@ public class FreezeShardAbility : Ability
|
||||
{
|
||||
if (!user || !projectilePrefab) return;
|
||||
|
||||
var pos = user.CastPos() + user.Forward() * spawnOffset;
|
||||
var rot = Quaternion.LookRotation(user.Forward());
|
||||
var go = Instantiate(projectilePrefab, pos, rot);
|
||||
// Get player camera (from FPS controller or fallback to Camera.main)
|
||||
Camera cam = null;
|
||||
if (Polyart.FirstPersonController.instance)
|
||||
cam = Polyart.FirstPersonController.instance.GetComponentInChildren<Camera>();
|
||||
if (!cam) cam = Camera.main;
|
||||
|
||||
// Start slightly in front of the cast position
|
||||
Vector3 castPos = user.CastPos() + user.Forward() * spawnOffset;
|
||||
|
||||
// Default direction if no camera found
|
||||
Vector3 dir = user.Forward();
|
||||
|
||||
if (cam)
|
||||
{
|
||||
// Ray from camera forward
|
||||
Ray ray = new Ray(cam.transform.position, cam.transform.forward);
|
||||
|
||||
Vector3 targetPoint;
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f, ~0, QueryTriggerInteraction.Ignore))
|
||||
targetPoint = hit.point;
|
||||
else
|
||||
targetPoint = ray.origin + ray.direction * 1000f;
|
||||
|
||||
dir = (targetPoint - castPos).normalized;
|
||||
}
|
||||
|
||||
// Spawn projectile oriented to camera direction
|
||||
var go = Instantiate(projectilePrefab, castPos, Quaternion.LookRotation(dir));
|
||||
|
||||
// Push it forward
|
||||
var rb = go.GetComponent<Rigidbody>();
|
||||
if (rb) rb.velocity = user.Forward() * speed;
|
||||
if (rb) rb.velocity = dir * speed;
|
||||
|
||||
// Apply freeze shard properties
|
||||
var shard = go.GetComponent<FreezeShardProjectile>();
|
||||
if (!shard) shard = go.AddComponent<FreezeShardProjectile>();
|
||||
shard.damage = damage;
|
||||
shard.freezeDuration = freezeDuration;
|
||||
shard.ownerRoot = user.transform;
|
||||
|
||||
// Ignore collisions with the caster
|
||||
var projCol = go.GetComponent<Collider>();
|
||||
if (projCol)
|
||||
{
|
||||
@ -36,4 +64,5 @@ public class FreezeShardAbility : Ability
|
||||
|
||||
Destroy(go, lifeTime);
|
||||
}
|
||||
|
||||
}
|
@ -25,5 +25,6 @@ public class AbilityUser : MonoBehaviour
|
||||
}
|
||||
|
||||
public Vector3 Forward() => castPoint ? castPoint.forward : transform.forward;
|
||||
public Vector3 Upward() => castPoint ? castPoint.up : transform.up;
|
||||
public Vector3 CastPos() => castPoint ? castPoint.position : transform.position;
|
||||
}
|
8
Assets/Scripts/Dialogues.meta
Normal file
8
Assets/Scripts/Dialogues.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f4c15fc4e9fb7a40b4e9a3abced59cc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
57
Assets/Scripts/Dialogues/DialogueAsset.cs
Normal file
57
Assets/Scripts/Dialogues/DialogueAsset.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[CreateAssetMenu(menuName = "Dialogue/Dialogue Asset", fileName = "NewDialogue")]
|
||||
public class DialogueAsset : ScriptableObject
|
||||
{
|
||||
[Tooltip("Start from this node id when dialogue begins")]
|
||||
public int startNodeId = 0;
|
||||
|
||||
public List<Node> nodes = new();
|
||||
|
||||
[Serializable]
|
||||
public class Node
|
||||
{
|
||||
public int id = 0;
|
||||
public string speakerName;
|
||||
public Sprite speakerPortrait;
|
||||
|
||||
[Tooltip("Shown in order. If more than one, player taps/presses to advance within the same node.")]
|
||||
[TextArea(2, 4)] public List<string> lines = new();
|
||||
|
||||
[Tooltip("Invoked when this node is entered (before showing text).")]
|
||||
public UnityEvent onEnter;
|
||||
|
||||
[Tooltip("If no choices and nextNodeId < 0, dialogue ends after this node.")]
|
||||
public int nextNodeId = -1;
|
||||
|
||||
public List<Choice> choices = new();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Choice
|
||||
{
|
||||
[TextArea(1, 2)] public string text;
|
||||
|
||||
[Tooltip("Go to this node when chosen. If < 0, ends dialogue.")]
|
||||
public int nextNodeId = -1;
|
||||
|
||||
[Header("Conditions / Effects")]
|
||||
[Tooltip("If non-empty, this choice is only visible when the flag is present in DialogueManager.")]
|
||||
public string requireFlag;
|
||||
|
||||
[Tooltip("If non-empty, set this flag on choose (stored in DialogueManager).")]
|
||||
public string setFlag;
|
||||
|
||||
[Tooltip("Optional SFX or other hook.")]
|
||||
public UnityEvent onChoose;
|
||||
}
|
||||
|
||||
public bool TryGetNode(int id, out Node node)
|
||||
{
|
||||
node = nodes.Find(n => n.id == id);
|
||||
return node != null;
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueAsset.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueAsset.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8603d6a0b297c554fbd9be8adf7daecf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
131
Assets/Scripts/Dialogues/DialogueManager.cs
Normal file
131
Assets/Scripts/Dialogues/DialogueManager.cs
Normal file
@ -0,0 +1,131 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class DialogueManager : MonoBehaviour
|
||||
{
|
||||
public static DialogueManager Instance { get; private set; }
|
||||
|
||||
[Header("Hook up the UI component here")]
|
||||
public DialogueUI ui;
|
||||
|
||||
[Header("Flags saved during play (simple global state)")]
|
||||
public HashSet<string> flags = new();
|
||||
|
||||
DialogueAsset _active;
|
||||
DialogueAsset.Node _node;
|
||||
int _lineIndex;
|
||||
|
||||
bool _isTyping;
|
||||
Coroutine _typeRoutine;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
// Public API -------------
|
||||
public bool IsRunning => _active != null;
|
||||
|
||||
public void StartDialogue(DialogueAsset asset)
|
||||
{
|
||||
if (asset == null) return;
|
||||
_active = asset;
|
||||
GoToNode(asset.startNodeId);
|
||||
ui.Show(true);
|
||||
}
|
||||
|
||||
public void StopDialogue()
|
||||
{
|
||||
_active = null;
|
||||
_node = null;
|
||||
ui.Show(false);
|
||||
}
|
||||
|
||||
public void Next() // Called by UI "Continue" button / input
|
||||
{
|
||||
if (!IsRunning || _node == null) return;
|
||||
|
||||
if (_isTyping)
|
||||
{
|
||||
ui.CompleteTypeThisFrame();
|
||||
_isTyping = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance line within node
|
||||
_lineIndex++;
|
||||
if (_lineIndex < _node.lines.Count)
|
||||
{
|
||||
StartTyping(_node.lines[_lineIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Lines finished -> show choices or auto-next / end
|
||||
var visibleChoices = ui.BuildVisibleChoices(_node.choices, flags);
|
||||
if (visibleChoices.Count > 0)
|
||||
{
|
||||
ui.ShowChoices(visibleChoices, OnChoicePicked);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_node.nextNodeId >= 0) GoToNode(_node.nextNodeId);
|
||||
else StopDialogue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal -------------
|
||||
void GoToNode(int id)
|
||||
{
|
||||
if (!_active.TryGetNode(id, out _node))
|
||||
{
|
||||
Debug.LogWarning($"Dialogue node {id} not found.");
|
||||
StopDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
_lineIndex = 0;
|
||||
_node.onEnter?.Invoke();
|
||||
|
||||
ui.BindSpeaker(_node.speakerName, _node.speakerPortrait);
|
||||
|
||||
if (_node.lines.Count == 0)
|
||||
{
|
||||
// Empty node: immediately resolve choices/next
|
||||
var visibleChoices = ui.BuildVisibleChoices(_node.choices, flags);
|
||||
if (visibleChoices.Count > 0) ui.ShowChoices(visibleChoices, OnChoicePicked);
|
||||
else if (_node.nextNodeId >= 0) GoToNode(_node.nextNodeId);
|
||||
else StopDialogue();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartTyping(_node.lines[_lineIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void StartTyping(string text)
|
||||
{
|
||||
ui.HideChoices();
|
||||
if (_typeRoutine != null) StopCoroutine(_typeRoutine);
|
||||
_typeRoutine = StartCoroutine(TypeRoutine(text));
|
||||
}
|
||||
|
||||
IEnumerator TypeRoutine(string text)
|
||||
{
|
||||
_isTyping = true;
|
||||
yield return ui.TypeText(text);
|
||||
_isTyping = false;
|
||||
}
|
||||
|
||||
void OnChoicePicked(DialogueAsset.Choice choice)
|
||||
{
|
||||
choice?.onChoose?.Invoke();
|
||||
if (!string.IsNullOrEmpty(choice.setFlag)) flags.Add(choice.setFlag);
|
||||
|
||||
if (choice == null || choice.nextNodeId < 0) { StopDialogue(); return; }
|
||||
GoToNode(choice.nextNodeId);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueManager.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c11bb4d5be167a240beb9566d35772ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
114
Assets/Scripts/Dialogues/DialogueUI.cs
Normal file
114
Assets/Scripts/Dialogues/DialogueUI.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class DialogueUI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
public CanvasGroup root; // Whole dialogue panel
|
||||
public TMP_Text speakerNameText;
|
||||
public Image speakerPortraitImage;
|
||||
public TMP_Text bodyText;
|
||||
|
||||
[Header("Choices")]
|
||||
public Transform choicesParent;
|
||||
public Button choiceButtonPrefab;
|
||||
|
||||
[Header("Typing")]
|
||||
[Range(0.005f, 0.1f)] public float charDelay = 0.02f;
|
||||
|
||||
string _targetFull;
|
||||
bool _completeThisFrame;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
root = GetComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
public void Show(bool on)
|
||||
{
|
||||
if (!root) return;
|
||||
root.alpha = on ? 1f : 0f;
|
||||
root.interactable = on;
|
||||
root.blocksRaycasts = on;
|
||||
|
||||
if (!on)
|
||||
{
|
||||
bodyText?.SetText(string.Empty);
|
||||
HideChoices();
|
||||
}
|
||||
}
|
||||
|
||||
public void BindSpeaker(string speakerName, Sprite portrait)
|
||||
{
|
||||
if (speakerNameText) speakerNameText.text = speakerName ?? "";
|
||||
if (speakerPortraitImage)
|
||||
{
|
||||
speakerPortraitImage.sprite = portrait;
|
||||
speakerPortraitImage.enabled = portrait != null;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator TypeText(string text)
|
||||
{
|
||||
_targetFull = text ?? string.Empty;
|
||||
bodyText.text = string.Empty;
|
||||
_completeThisFrame = false;
|
||||
|
||||
foreach (char c in _targetFull)
|
||||
{
|
||||
if (_completeThisFrame) break;
|
||||
bodyText.text += c;
|
||||
yield return new WaitForSeconds(charDelay);
|
||||
}
|
||||
|
||||
bodyText.text = _targetFull;
|
||||
_completeThisFrame = false;
|
||||
}
|
||||
|
||||
public void CompleteTypeThisFrame()
|
||||
{
|
||||
_completeThisFrame = true;
|
||||
}
|
||||
|
||||
public List<DialogueAsset.Choice> BuildVisibleChoices(
|
||||
List<DialogueAsset.Choice> all,
|
||||
HashSet<string> flags)
|
||||
{
|
||||
var list = new List<DialogueAsset.Choice>();
|
||||
if (all == null) return list;
|
||||
|
||||
foreach (var c in all)
|
||||
{
|
||||
if (string.IsNullOrEmpty(c.requireFlag) || flags.Contains(c.requireFlag))
|
||||
list.Add(c);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void ShowChoices(
|
||||
List<DialogueAsset.Choice> choices,
|
||||
Action<DialogueAsset.Choice> onPick)
|
||||
{
|
||||
HideChoices();
|
||||
|
||||
foreach (var c in choices)
|
||||
{
|
||||
var btn = Instantiate(choiceButtonPrefab, choicesParent);
|
||||
var label = btn.GetComponentInChildren<TMP_Text>();
|
||||
if (label) label.text = c.text;
|
||||
|
||||
btn.onClick.AddListener(() => onPick?.Invoke(c));
|
||||
}
|
||||
}
|
||||
|
||||
public void HideChoices()
|
||||
{
|
||||
if (!choicesParent) return;
|
||||
for (int i = choicesParent.childCount - 1; i >= 0; i--)
|
||||
Destroy(choicesParent.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/DialogueUI.cs.meta
Normal file
11
Assets/Scripts/Dialogues/DialogueUI.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88612b9886a695c4faf4af1fa2800a2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
36
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs
Normal file
36
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class NPCDialogueTrigger : MonoBehaviour
|
||||
{
|
||||
public DialogueAsset dialogue;
|
||||
public string interactKey = "e";
|
||||
public float interactRange = 2.2f;
|
||||
|
||||
Transform _player;
|
||||
|
||||
void Start()
|
||||
{
|
||||
var p = GameObject.FindGameObjectWithTag("Player");
|
||||
_player = p ? p.transform : null;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (DialogueManager.Instance && DialogueManager.Instance.IsRunning) return;
|
||||
if (_player == null) return;
|
||||
|
||||
if (Vector3.Distance(transform.position, _player.position) <= interactRange)
|
||||
{
|
||||
if (Input.GetKeyDown(interactKey.ToLower()))
|
||||
{
|
||||
DialogueManager.Instance.StartDialogue(dialogue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position, interactRange);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs.meta
Normal file
11
Assets/Scripts/Dialogues/NPCDialogueTrigger.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cafcb3d2d5194794089b630c916b6084
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/Scripts/Teleportation.meta
Normal file
8
Assets/Scripts/Teleportation.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1e704e47ac357c43a1c55af30b5e432
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
104
Assets/Scripts/Teleportation/TeleportManager.cs
Normal file
104
Assets/Scripts/Teleportation/TeleportManager.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
public class TeleportManager : MonoBehaviour
|
||||
{
|
||||
public static TeleportManager Instance { get; private set; }
|
||||
|
||||
[Header("Optional fade (CanvasGroup with RaycastBlock)")]
|
||||
public TeleportScreenFader screenFader; // drag a CanvasGroup-based fader (below)
|
||||
[Range(0f, 2f)] public float fadeDuration = 0.25f;
|
||||
|
||||
// Prevent immediate re-triggering after arrival
|
||||
private readonly Dictionary<GameObject, float> _entityCooldownUntil = new();
|
||||
private readonly Dictionary<GameObject, string> _entityLastPortalId = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public bool CanUsePortal(GameObject entity, string portalId, float cooldownSeconds)
|
||||
{
|
||||
if (cooldownSeconds <= 0f) return true;
|
||||
|
||||
var now = Time.time;
|
||||
if (_entityCooldownUntil.TryGetValue(entity, out float until) && now < until)
|
||||
return false;
|
||||
|
||||
// If just came out of this portal pair, block one frame of re-trigger
|
||||
if (_entityLastPortalId.TryGetValue(entity, out string lastId) && lastId == portalId)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void MarkUsed(GameObject entity, string portalId, float cooldownSeconds)
|
||||
{
|
||||
if (cooldownSeconds > 0f)
|
||||
_entityCooldownUntil[entity] = Time.time + cooldownSeconds;
|
||||
|
||||
_entityLastPortalId[entity] = portalId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleport entity to destination. Handles CharacterController, Rigidbody, and NavMeshAgent safely.
|
||||
/// </summary>
|
||||
public void Teleport(GameObject entity, Transform destination, bool matchRotation = true)
|
||||
{
|
||||
if (entity == null || destination == null) return;
|
||||
StartCoroutine(TeleportRoutine(entity, destination, matchRotation));
|
||||
}
|
||||
|
||||
private IEnumerator TeleportRoutine(GameObject entity, Transform destination, bool matchRotation)
|
||||
{
|
||||
// Fade out (optional)
|
||||
if (screenFader != null && fadeDuration > 0f)
|
||||
yield return screenFader.FadeTo(1f, fadeDuration);
|
||||
|
||||
// Disable common movers safely
|
||||
var cc = entity.GetComponent<CharacterController>();
|
||||
var rb = entity.GetComponent<Rigidbody>();
|
||||
var agent = entity.GetComponent<NavMeshAgent>();
|
||||
|
||||
bool ccWasEnabled = false;
|
||||
bool agentWasEnabled = false;
|
||||
|
||||
if (cc != null) { ccWasEnabled = cc.enabled; cc.enabled = false; }
|
||||
if (agent != null) { agentWasEnabled = agent.enabled; agent.enabled = false; }
|
||||
if (rb != null)
|
||||
{
|
||||
rb.isKinematic = true; // pause physics for an instant
|
||||
rb.velocity = Vector3.zero;
|
||||
rb.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
// Move + rotate
|
||||
if (agent != null)
|
||||
{
|
||||
agent.Warp(destination.position);
|
||||
if (matchRotation) entity.transform.rotation = destination.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.transform.position = destination.position;
|
||||
if (matchRotation) entity.transform.rotation = destination.rotation;
|
||||
}
|
||||
|
||||
// Small frame delay to settle
|
||||
yield return null;
|
||||
|
||||
// Re-enable components
|
||||
if (rb != null) rb.isKinematic = false;
|
||||
if (cc != null) cc.enabled = ccWasEnabled;
|
||||
if (agent != null) agent.enabled = agentWasEnabled;
|
||||
|
||||
// Fade in (optional)
|
||||
if (screenFader != null && fadeDuration > 0f)
|
||||
yield return screenFader.FadeTo(0f, fadeDuration);
|
||||
}
|
||||
}
|
11
Assets/Scripts/Teleportation/TeleportManager.cs.meta
Normal file
11
Assets/Scripts/Teleportation/TeleportManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dce2a4efa47e2f4aaa40c0820a478ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
62
Assets/Scripts/Teleportation/TeleportPoint.cs
Normal file
62
Assets/Scripts/Teleportation/TeleportPoint.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class TeleportPoint : MonoBehaviour
|
||||
{
|
||||
[Header("Destination")]
|
||||
public Transform destination; // set this to SkyCity_Spawn or any target
|
||||
|
||||
[Header("Filtering")]
|
||||
public string requiredTag = "Player"; // only teleport objects with this tag
|
||||
|
||||
[Header("Behavior")]
|
||||
public bool matchRotation = true;
|
||||
[Tooltip("Shared ID for A<->B portals to avoid instant ping-pong.")]
|
||||
public string portalId = "SkyCityGate";
|
||||
[Tooltip("Seconds the entity cannot re-trigger a portal after using one.")]
|
||||
public float reentryCooldown = 0.5f;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var c = GetComponent<Collider>();
|
||||
c.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!IsAllowed(other.gameObject)) return;
|
||||
|
||||
var tm = TeleportManager.Instance;
|
||||
if (tm == null) { Debug.LogWarning("[TeleportPoint] No TeleportManager in scene."); return; }
|
||||
|
||||
if (!tm.CanUsePortal(other.gameObject, portalId, reentryCooldown)) return;
|
||||
|
||||
tm.MarkUsed(other.gameObject, portalId, reentryCooldown);
|
||||
tm.Teleport(other.gameObject, destination, matchRotation);
|
||||
}
|
||||
|
||||
private bool IsAllowed(GameObject go)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(requiredTag) && !go.CompareTag(requiredTag))
|
||||
return false;
|
||||
|
||||
if (destination == null)
|
||||
{
|
||||
Debug.LogWarning($"[TeleportPoint] '{name}' has no destination assigned.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (destination == null) return;
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.3f);
|
||||
Gizmos.DrawLine(transform.position, destination.position);
|
||||
Gizmos.DrawWireSphere(destination.position, 0.3f);
|
||||
}
|
||||
#endif
|
||||
}
|
11
Assets/Scripts/Teleportation/TeleportPoint.cs.meta
Normal file
11
Assets/Scripts/Teleportation/TeleportPoint.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cd2ca85a22ffb642bb124e4b6d6b716
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
29
Assets/Scripts/Teleportation/TeleportScreenFader.cs
Normal file
29
Assets/Scripts/Teleportation/TeleportScreenFader.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(CanvasGroup))]
|
||||
public class TeleportScreenFader : MonoBehaviour
|
||||
{
|
||||
private CanvasGroup _cg;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_cg = GetComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
public IEnumerator FadeTo(float targetAlpha, float duration)
|
||||
{
|
||||
float start = _cg.alpha;
|
||||
float t = 0f;
|
||||
_cg.blocksRaycasts = targetAlpha > 0.01f;
|
||||
|
||||
while (t < duration)
|
||||
{
|
||||
t += Time.unscaledDeltaTime;
|
||||
_cg.alpha = Mathf.Lerp(start, targetAlpha, t / duration);
|
||||
yield return null;
|
||||
}
|
||||
_cg.alpha = targetAlpha;
|
||||
_cg.blocksRaycasts = targetAlpha > 0.01f;
|
||||
}
|
||||
}
|
11
Assets/Scripts/Teleportation/TeleportScreenFader.cs.meta
Normal file
11
Assets/Scripts/Teleportation/TeleportScreenFader.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf0f714f4603da745855c8b18be4de59
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -3,10 +3,11 @@
|
||||
--- !u!55 &1
|
||||
PhysicsManager:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
serializedVersion: 14
|
||||
m_Gravity: {x: 0, y: -9.81, z: 0}
|
||||
m_DefaultMaterial: {fileID: 0}
|
||||
m_BounceThreshold: 2
|
||||
m_DefaultMaxDepenetrationVelocity: 10
|
||||
m_SleepThreshold: 0.005
|
||||
m_DefaultContactOffset: 0.01
|
||||
m_DefaultSolverIterations: 6
|
||||
@ -17,10 +18,11 @@ PhysicsManager:
|
||||
m_ClothInterCollisionDistance: 0.1
|
||||
m_ClothInterCollisionStiffness: 0.2
|
||||
m_ContactsGeneration: 1
|
||||
m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_AutoSimulation: 1
|
||||
m_LayerCollisionMatrix: fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
m_SimulationMode: 0
|
||||
m_AutoSyncTransforms: 0
|
||||
m_ReuseCollisionCallbacks: 1
|
||||
m_InvokeCollisionCallbacks: 1
|
||||
m_ClothInterCollisionSettingsToggle: 0
|
||||
m_ClothGravity: {x: 0, y: -9.81, z: 0}
|
||||
m_ContactPairsMode: 0
|
||||
@ -32,5 +34,7 @@ PhysicsManager:
|
||||
m_FrictionType: 0
|
||||
m_EnableEnhancedDeterminism: 0
|
||||
m_EnableUnifiedHeightmaps: 1
|
||||
m_ImprovedPatchFriction: 0
|
||||
m_SolverType: 0
|
||||
m_DefaultMaxAngularSpeed: 50
|
||||
m_FastMotionThreshold: 3.4028235e+38
|
||||
|
@ -12,8 +12,8 @@ TagManager:
|
||||
- Water
|
||||
- UI
|
||||
- WalkableStreet
|
||||
-
|
||||
-
|
||||
- Fireball
|
||||
- Player
|
||||
-
|
||||
-
|
||||
-
|
||||
|
Loading…
x
Reference in New Issue
Block a user