OctoCore 0.0.1
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63017153b92a8844fb10983e8151eb0d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Components
|
||||
{
|
||||
public class OctoCharacterAnimation : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Animator animator;
|
||||
[SerializeField] private float walkSpeed = 8;
|
||||
[SerializeField] private float runSpeed = 15;
|
||||
[SerializeField, Range(1, 10)] private int sampling = 5;
|
||||
|
||||
private bool backward;
|
||||
private float backwardSmooth;
|
||||
private float sideSmooth;
|
||||
private float smoothSpeed;
|
||||
private Vector4[] positions;
|
||||
private Vector3 lastStep;
|
||||
private Vector3 direction;
|
||||
|
||||
private float step = .1f;
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawRay(transform.position + Vector3.up, transform.forward);
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawRay(transform.position + Vector3.up, direction);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
positions = new Vector4[sampling + 1];
|
||||
for (int i = 0; i < sampling; i++)
|
||||
{
|
||||
positions[i] = transform.position;
|
||||
positions[i].z = .002f;
|
||||
}
|
||||
|
||||
direction = transform.forward;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (lastStep != transform.position && Vector3.Distance(lastStep, transform.position) > step)
|
||||
{
|
||||
Vector3 offset = (lastStep - transform.position).normalized * step;
|
||||
lastStep = transform.position + offset;
|
||||
}
|
||||
|
||||
direction = Vector3.Lerp(direction, (transform.position - lastStep).normalized, Time.deltaTime * 10);
|
||||
|
||||
float speed = GetSpeed();
|
||||
smoothSpeed = Mathf.Lerp(smoothSpeed, speed, Time.deltaTime * 10);
|
||||
|
||||
float move = Mathf.Clamp01(smoothSpeed / walkSpeed);
|
||||
float run = Mathf.Clamp01((smoothSpeed - walkSpeed) / (runSpeed - walkSpeed));
|
||||
|
||||
float dot = Vector3.Dot(transform.forward, direction.normalized);
|
||||
Vector3 crs = Vector3.Cross(transform.forward, direction.normalized);
|
||||
|
||||
backward = dot < 0;
|
||||
backwardSmooth = Mathf.Lerp(backwardSmooth, backward ? 1 : 0, Time.deltaTime * 10);
|
||||
|
||||
sideSmooth = Mathf.Lerp(sideSmooth, (1 - Mathf.Abs(dot)) * Mathf.Sign(transform.InverseTransformDirection(crs).y) * direction.magnitude, Time.deltaTime * 20);
|
||||
|
||||
run *= 1 - backwardSmooth;
|
||||
run *= 1 - Mathf.Abs(sideSmooth);
|
||||
|
||||
//Debug.Log(dot);
|
||||
|
||||
animator.SetFloat("move", Mathf.Sqrt(move));
|
||||
animator.SetFloat("run", run);
|
||||
animator.SetFloat("backward", backwardSmooth);
|
||||
animator.SetFloat("side", sideSmooth);
|
||||
}
|
||||
|
||||
float GetSpeed()
|
||||
{
|
||||
float offset = 0;
|
||||
float delta = 0;
|
||||
|
||||
for (int i = 0; i < positions.Length - 1; i++)
|
||||
{
|
||||
offset += Vector3.Distance(positions[i], positions[i + 1]);
|
||||
delta += positions[i].w;
|
||||
}
|
||||
|
||||
for (int i = positions.Length - 1; i > 0; i--)
|
||||
positions[i] = positions[i - 1];
|
||||
|
||||
positions[0] = transform.position;
|
||||
positions[0].w = Time.deltaTime;
|
||||
|
||||
if (offset > 0 && delta > 0)
|
||||
return offset / (delta / 3.6f);
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4b0dbb024f42dc4c8c80d40a271d6d3
|
||||
@@ -0,0 +1,205 @@
|
||||
using UnityEngine;
|
||||
using static UnityEngine.GraphicsBuffer;
|
||||
|
||||
public class OctoIkArms : MonoBehaviour
|
||||
{
|
||||
private class RaycastInfos
|
||||
{
|
||||
public bool HasHit;
|
||||
public float Distance;
|
||||
public float MaxDistance;
|
||||
public Vector3 Position;
|
||||
public Vector3 Normal;
|
||||
public Transform Transform;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class RaycastRig
|
||||
{
|
||||
public Transform Root;
|
||||
public float Distance = 1;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class ArmRig
|
||||
{
|
||||
public bool Enabled;
|
||||
public OctoIkLimb Arm;
|
||||
public Vector3 RotationOffset;
|
||||
public RaycastRig[] Raycasts;
|
||||
|
||||
[HideInInspector] public Vector3 TargetOriginPos;
|
||||
[HideInInspector] public Quaternion TargetOriginRot;
|
||||
[HideInInspector] public Vector3 PoleOriginPos;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
if (Arm)
|
||||
{
|
||||
if (Arm.Target)
|
||||
{
|
||||
TargetOriginPos = Arm.Target.localPosition;
|
||||
TargetOriginRot = Arm.Target.localRotation;
|
||||
}
|
||||
|
||||
if (Arm.Pole)
|
||||
{
|
||||
PoleOriginPos = Arm.Pole.localPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Settings
|
||||
{
|
||||
public LayerMask LayerMask;
|
||||
}
|
||||
|
||||
[SerializeField] private ArmRig _leftArm;
|
||||
[SerializeField] private ArmRig _rightArm;
|
||||
[SerializeField] private Settings _settings;
|
||||
|
||||
private ArmRig[] _arms;
|
||||
private RaycastInfos[][] _raycasts = new RaycastInfos[2][];
|
||||
private RaycastInfos[] _nearHitRaycasts = new RaycastInfos[2];
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_arms = new ArmRig[2] { _leftArm, _rightArm };
|
||||
foreach (ArmRig armRig in _arms)
|
||||
armRig.Init();
|
||||
|
||||
_raycasts = new RaycastInfos[_arms.Length][];
|
||||
for (int i = 0; i < _arms.Length; i++)
|
||||
{
|
||||
_raycasts[i] = new RaycastInfos[_arms[i].Raycasts.Length];
|
||||
for (int u = 0; u < _raycasts[i].Length; u++)
|
||||
_raycasts[i][u] = new RaycastInfos() { MaxDistance = 1 };
|
||||
}
|
||||
|
||||
_nearHitRaycasts = new RaycastInfos[_arms.Length];
|
||||
for (int i = 0; i < _nearHitRaycasts.Length; i++)
|
||||
_nearHitRaycasts[i] = new RaycastInfos() { MaxDistance = 1 };
|
||||
}
|
||||
|
||||
void OnDrawGizmos()//Selected()
|
||||
{
|
||||
if (Application.isPlaying || !enabled) return;
|
||||
|
||||
foreach (ArmRig armRig in new ArmRig[2] { _leftArm, _rightArm })
|
||||
{
|
||||
for (int i = 0; i < armRig.Raycasts.Length; i++)
|
||||
{
|
||||
RaycastRig raycastRig = armRig.Raycasts[i];
|
||||
Debug.DrawRay(raycastRig.Root.position, raycastRig.Root.TransformDirection(Vector3.forward) * raycastRig.Distance, Color.red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Get raycats infos
|
||||
|
||||
for (int i = 0; i < _arms.Length; i++)
|
||||
{
|
||||
ArmRig armRig = _arms[i];
|
||||
|
||||
if (!armRig.Enabled)
|
||||
continue;
|
||||
|
||||
for (int u = 0; u < armRig.Raycasts.Length; u++)
|
||||
{
|
||||
RaycastRig raycastRig = armRig.Raycasts[u];
|
||||
RaycastInfos raycastInfos = _raycasts[i][u];
|
||||
|
||||
RaycastHit hit;
|
||||
|
||||
if (Physics.Raycast(raycastRig.Root.position, raycastRig.Root.TransformDirection(Vector3.forward), out hit, raycastRig.Distance, _settings.LayerMask))
|
||||
{
|
||||
Debug.DrawRay(raycastRig.Root.position, raycastRig.Root.TransformDirection(Vector3.forward) * hit.distance, Color.green);
|
||||
|
||||
raycastInfos.HasHit = true;
|
||||
raycastInfos.Distance = hit.distance;
|
||||
raycastInfos.MaxDistance = raycastRig.Distance;
|
||||
raycastInfos.Position = hit.point;
|
||||
raycastInfos.Normal = hit.normal;
|
||||
raycastInfos.Transform = hit.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.DrawRay(raycastRig.Root.position, raycastRig.Root.TransformDirection(Vector3.forward) * raycastRig.Distance, Color.red);
|
||||
|
||||
raycastInfos.HasHit = false;
|
||||
raycastInfos.Distance = 1;
|
||||
raycastInfos.MaxDistance = 1;
|
||||
raycastInfos.Position = Vector3.zero;
|
||||
raycastInfos.Normal = Vector3.zero;
|
||||
raycastInfos.Transform = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate near hit
|
||||
|
||||
for (int i = 0; i < _arms.Length; i++)
|
||||
{
|
||||
ArmRig armRig = _arms[i];
|
||||
_nearHitRaycasts[i].HasHit = false;
|
||||
|
||||
for (int u = 0; u < armRig.Raycasts.Length; u++)
|
||||
{
|
||||
RaycastInfos raycastInfos = _raycasts[i][u];
|
||||
|
||||
if (raycastInfos.HasHit)
|
||||
{
|
||||
_nearHitRaycasts[i].HasHit = raycastInfos.HasHit;
|
||||
_nearHitRaycasts[i].Distance = _raycasts[i][u].Distance;
|
||||
_nearHitRaycasts[i].MaxDistance = _raycasts[i][u].MaxDistance;
|
||||
_nearHitRaycasts[i].Position = _raycasts[i][u].Position;
|
||||
_nearHitRaycasts[i].Normal = _raycasts[i][u].Normal;
|
||||
_nearHitRaycasts[i].Transform = _raycasts[i][u].Transform;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move hands
|
||||
|
||||
for (int i = 0; i < _arms.Length; i++)
|
||||
{
|
||||
ArmRig armRig = _arms[i];
|
||||
RaycastInfos raycastInfos = _nearHitRaycasts[i];
|
||||
|
||||
Vector3 targetPos = armRig.TargetOriginPos;
|
||||
Quaternion targetRot = armRig.TargetOriginRot;
|
||||
|
||||
float sticknessPos = 0;
|
||||
float sticknessRot = 0;
|
||||
|
||||
if (raycastInfos.HasHit)
|
||||
{
|
||||
float doorForward = transform.InverseTransformDirection(raycastInfos.Transform.forward).z;
|
||||
bool isGoodArm = i == 0 ? (doorForward < 0) : (doorForward > 0);
|
||||
|
||||
if (isGoodArm)
|
||||
{
|
||||
targetPos = armRig.Arm.Target.parent.InverseTransformPoint(raycastInfos.Position);
|
||||
targetRot = Quaternion.LookRotation(-armRig.Arm.Target.parent.InverseTransformDirection(raycastInfos.Normal), transform.up) * Quaternion.Euler(armRig.RotationOffset);
|
||||
|
||||
float t = 1 - Mathf.Clamp01(raycastInfos.Distance / raycastInfos.MaxDistance);
|
||||
|
||||
sticknessPos = Mathf.SmoothStep(0, .7f, t);
|
||||
sticknessRot = Mathf.SmoothStep(.5f, .9f, t);
|
||||
}
|
||||
}
|
||||
|
||||
float tPos = Mathf.SmoothStep(Time.deltaTime * 3, 1, sticknessPos);
|
||||
float tRot = Mathf.SmoothStep(Time.deltaTime * 5, 1, sticknessRot);
|
||||
|
||||
armRig.Arm.Target.localPosition = Vector3.Lerp(armRig.Arm.Target.localPosition, targetPos, tPos);
|
||||
armRig.Arm.Target.localRotation = Quaternion.Lerp(armRig.Arm.Target.localRotation, targetRot, tRot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 015ed493fd8775f479bf25c92aa04e57
|
||||
@@ -0,0 +1,303 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Two-bone IK solver for a single limb (arm or leg).
|
||||
/// Works with or without an Animator.
|
||||
/// Assumes a 3-bone chain: start -> mid -> end.
|
||||
/// Bones are assumed to point along a configurable local primary axis,
|
||||
/// with a configurable local secondary axis used as "bend up".
|
||||
/// </summary>
|
||||
public class OctoIkLimb : MonoBehaviour
|
||||
{
|
||||
public enum Axis
|
||||
{
|
||||
XPlus,
|
||||
XMinus,
|
||||
YPlus,
|
||||
YMinus,
|
||||
ZPlus,
|
||||
ZMinus
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Joints
|
||||
{
|
||||
[Tooltip("start joint of the limb (e.g. shoulder or hip).")]
|
||||
public Transform Start;
|
||||
|
||||
[Tooltip("Middle joint of the limb (e.g. elbow or knee).")]
|
||||
public Transform Mid;
|
||||
|
||||
[Tooltip("End joint of the limb (e.g. wrist or foot).")]
|
||||
public Transform End;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Controllers
|
||||
{
|
||||
[Tooltip("Target the end joint should try to reach.")]
|
||||
public Transform Target;
|
||||
|
||||
[Tooltip("Optional pole to control the bending direction of the limb (e.g. elbow or knee direction).")]
|
||||
public Transform Pole;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Settings
|
||||
{
|
||||
[Tooltip("Local primary axis pointing toward the next bone.")]
|
||||
public Axis PrimaryAxis = Axis.YPlus;
|
||||
|
||||
[Tooltip("Local secondary axis defining the bend 'up' direction.")]
|
||||
public Axis SecondaryAxis = Axis.XMinus;
|
||||
|
||||
[Header("Settings")]
|
||||
|
||||
[Tooltip("Global IK blend weight (0 = no IK, 1 = full IK).")]
|
||||
[Range(0f, 1f)]
|
||||
public float Weight = 1;
|
||||
|
||||
[Tooltip("Apply the target rotation to the end joint.")]
|
||||
public bool OrientEnd = true;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class VisualDebug
|
||||
{
|
||||
[Tooltip("Enable or disable IK debug gizmos.")]
|
||||
public bool Gizmos = true;
|
||||
|
||||
[Tooltip("Scale of the axis gizmos drawn at each joint.")]
|
||||
public float Size = 0.1f;
|
||||
}
|
||||
|
||||
[SerializeField] private Joints _joints;
|
||||
[SerializeField] private Controllers _controllers;
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private VisualDebug _debug;
|
||||
|
||||
public Transform Target => _controllers.Target;
|
||||
public Transform Pole => _controllers.Pole;
|
||||
|
||||
private float _upperLength;
|
||||
private float _lowerLength;
|
||||
private float _maxReach;
|
||||
|
||||
private Quaternion _axesCorrection = Quaternion.identity;
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
RecalculateLengths();
|
||||
RecalculateAxesCorrection();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
RecalculateLengths();
|
||||
RecalculateAxesCorrection();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
Solve();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recomputes bone lengths and max reach.
|
||||
/// </summary>
|
||||
private void RecalculateLengths()
|
||||
{
|
||||
if (_joints.Start && _joints.Mid && _joints.End)
|
||||
{
|
||||
_upperLength = Vector3.Distance(_joints.Start.position, _joints.Mid.position);
|
||||
_lowerLength = Vector3.Distance(_joints.Mid.position, _joints.End.position);
|
||||
_maxReach = Mathf.Max(0f, _upperLength + _lowerLength - 0.0001f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Precomputes a local correction rotation so that:
|
||||
/// - local primary axis is treated as +Z (forward)
|
||||
/// - local secondary axis is treated as +Y (up)
|
||||
/// </summary>
|
||||
private void RecalculateAxesCorrection()
|
||||
{
|
||||
Vector3 pLocal = AxisToVector(_settings.PrimaryAxis).normalized;
|
||||
Vector3 sLocal = AxisToVector(_settings.SecondaryAxis).normalized;
|
||||
|
||||
// Step 1: rotate primary axis to +Z
|
||||
Quaternion qPrimary = Quaternion.FromToRotation(pLocal, Vector3.forward);
|
||||
|
||||
// Step 2: after this, rotate around Z so that secondary goes to +Y
|
||||
Vector3 s1 = qPrimary * sLocal;
|
||||
Vector3 s1Proj = Vector3.ProjectOnPlane(s1, Vector3.forward);
|
||||
|
||||
if (s1Proj.sqrMagnitude < 0.000001f)
|
||||
{
|
||||
_axesCorrection = qPrimary;
|
||||
return;
|
||||
}
|
||||
|
||||
s1Proj.Normalize();
|
||||
Quaternion qTwist = Quaternion.FromToRotation(s1Proj, Vector3.up);
|
||||
|
||||
// This maps (primary, secondary) -> (Z+, Y+)
|
||||
_axesCorrection = qTwist * qPrimary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solves IK for the current limb configuration.
|
||||
/// Can be called manually if autoSolve is disabled.
|
||||
/// </summary>
|
||||
public void Solve()
|
||||
{
|
||||
if (_settings.Weight <= 0f)
|
||||
return;
|
||||
|
||||
if (!_joints.Start || !_joints.Mid || !_joints.End || !_controllers.Target)
|
||||
return;
|
||||
|
||||
if (_upperLength <= 0f || _lowerLength <= 0f)
|
||||
RecalculateLengths();
|
||||
|
||||
Vector3 startPos = _joints.Start.position;
|
||||
Vector3 midPos = _joints.Mid.position;
|
||||
Vector3 endPos = _joints.End.position;
|
||||
|
||||
Vector3 startToTarget = _controllers.Target.position - startPos;
|
||||
float dist = startToTarget.magnitude;
|
||||
if (dist < 0.0001f)
|
||||
return;
|
||||
|
||||
dist = Mathf.Min(dist, _maxReach);
|
||||
Vector3 dirToTarget = startToTarget.normalized;
|
||||
|
||||
Vector3 bendNormal;
|
||||
|
||||
if (_controllers.Pole)
|
||||
{
|
||||
Vector3 startToPole = (_controllers.Pole.position - startPos);
|
||||
Vector3 side = Vector3.ProjectOnPlane(startToPole, dirToTarget);
|
||||
if (side.sqrMagnitude < 0.000001f)
|
||||
{
|
||||
// Fallback if pole is collinear with forward
|
||||
side = Vector3.right;
|
||||
}
|
||||
side.Normalize();
|
||||
|
||||
// Bend normal is perpendicular to forward and to that side
|
||||
bendNormal = Vector3.Cross(dirToTarget, side);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: use start.up to define a side, then build a normal
|
||||
Vector3 side = Vector3.ProjectOnPlane(_joints.Start.up, dirToTarget);
|
||||
if (side.sqrMagnitude < 0.000001f)
|
||||
side = Vector3.right;
|
||||
side.Normalize();
|
||||
|
||||
bendNormal = Vector3.Cross(dirToTarget, side);
|
||||
}
|
||||
|
||||
if (bendNormal.sqrMagnitude < 0.000001f)
|
||||
return;
|
||||
|
||||
bendNormal.Normalize();
|
||||
|
||||
// Bend direction lies in the plane defined by dirToTarget and bendNormal
|
||||
Vector3 bendDir = Vector3.Cross(bendNormal, dirToTarget).normalized;
|
||||
|
||||
float a = _upperLength;
|
||||
float b = _lowerLength;
|
||||
float c = dist;
|
||||
|
||||
// Law of cosines to compute start angle
|
||||
float cos0 = Mathf.Clamp((a * a + c * c - b * b) / (2f * a * c), -1f, 1f);
|
||||
float angle0 = Mathf.Acos(cos0);
|
||||
|
||||
float along = Mathf.Cos(angle0) * a;
|
||||
float height = Mathf.Sin(angle0) * a;
|
||||
|
||||
Vector3 desiredMid = startPos + dirToTarget * along + bendDir * height;
|
||||
Vector3 desiredEnd = _controllers.Target.position;
|
||||
|
||||
// Canonical solver rotations assuming +Z forward, +Y up
|
||||
Quaternion startVirtual = Quaternion.LookRotation(desiredMid - startPos, bendNormal);
|
||||
Quaternion midVirtual = Quaternion.LookRotation(desiredEnd - desiredMid, bendNormal);
|
||||
|
||||
// Map canonical frame to bone-local frame using axesCorrection
|
||||
// (we expect bones to point along primaryAxis and bend using secondaryAxis)
|
||||
Quaternion startDesired = startVirtual * _axesCorrection;
|
||||
Quaternion midDesired = midVirtual * _axesCorrection;
|
||||
|
||||
_joints.Start.rotation = Quaternion.Slerp(_joints.Start.rotation, startDesired, _settings.Weight);
|
||||
_joints.Mid.rotation = Quaternion.Slerp(_joints.Mid.rotation, midDesired, _settings.Weight);
|
||||
|
||||
if (_settings.OrientEnd)
|
||||
{
|
||||
_joints.End.rotation = Quaternion.Slerp(_joints.End.rotation, _controllers.Target.rotation, _settings.Weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a LocalAxis6 enum to a unit Vector3.
|
||||
/// </summary>
|
||||
private Vector3 AxisToVector(Axis axis)
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
default: return Vector3.forward;
|
||||
case Axis.XPlus: return Vector3.right;
|
||||
case Axis.XMinus: return Vector3.left;
|
||||
case Axis.YPlus: return Vector3.up;
|
||||
case Axis.YMinus: return Vector3.down;
|
||||
case Axis.ZMinus: return Vector3.back;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (!_debug.Gizmos)
|
||||
return;
|
||||
|
||||
if (!_joints.Start || !_joints.Mid || !_joints.End)
|
||||
return;
|
||||
|
||||
// Bones to Controllers
|
||||
Gizmos.color = Color.yellow;
|
||||
if (_controllers.Target)
|
||||
Gizmos.DrawLine(_joints.End.position, _controllers.Target.position);
|
||||
if (_controllers.Pole)
|
||||
Gizmos.DrawLine(_joints.Mid.position, _controllers.Pole.position);
|
||||
|
||||
// Bones
|
||||
Gizmos.color = Color.white;
|
||||
Gizmos.DrawLine(_joints.Start.position, _joints.Mid.position);
|
||||
Gizmos.DrawLine(_joints.Mid.position, _joints.End.position);
|
||||
Gizmos.matrix = _joints.Start.localToWorldMatrix;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, _debug.Size * .15f);
|
||||
Gizmos.matrix = _joints.Mid.localToWorldMatrix;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, _debug.Size * .15f);
|
||||
Gizmos.matrix = _joints.End.localToWorldMatrix;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, _debug.Size * .15f);
|
||||
|
||||
// Target
|
||||
if (_controllers.Target)
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.matrix = _controllers.Target.localToWorldMatrix;
|
||||
Gizmos.DrawWireCube(Vector3.zero, Vector3.one * _debug.Size);
|
||||
}
|
||||
|
||||
// Pole
|
||||
if (_controllers.Pole)
|
||||
{
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.matrix = _controllers.Pole.localToWorldMatrix;
|
||||
Gizmos.DrawWireCube(Vector3.zero, Vector3.one * _debug.Size * .5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5a9786856021274fbe10be8b0cef721
|
||||
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class OctoIkRaycast : MonoBehaviour
|
||||
{
|
||||
public float _distance = 1;
|
||||
public Vector3 rotationOffset;
|
||||
public Transform _target;
|
||||
public LayerMask _layerMask;
|
||||
|
||||
Vector3 originPos;
|
||||
Vector3 targetPos;
|
||||
Quaternion originRot;
|
||||
Quaternion targetRot;
|
||||
|
||||
void Start()
|
||||
{
|
||||
originPos = targetPos = _target.localPosition;
|
||||
originRot = targetRot = _target.localRotation;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
float deltaPos = Time.deltaTime * 3f;
|
||||
float deltaRot = Time.deltaTime * 3f;
|
||||
|
||||
RaycastHit hit;
|
||||
|
||||
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, _distance, _layerMask))
|
||||
{
|
||||
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
|
||||
|
||||
targetPos = transform.InverseTransformPoint(hit.point);
|
||||
targetRot = Quaternion.LookRotation(-transform.InverseTransformDirection(hit.normal), transform.up) * Quaternion.Euler(rotationOffset);
|
||||
|
||||
deltaPos = Mathf.Lerp(1, deltaPos, Mathf.Clamp01(hit.distance / _distance));
|
||||
deltaRot = Mathf.Lerp(.1f, deltaRot, Mathf.Clamp01(hit.distance / _distance));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * _distance, Color.white);
|
||||
|
||||
targetPos = originPos;
|
||||
targetRot = originRot;
|
||||
}
|
||||
|
||||
_target.localPosition = Vector3.Lerp(_target.localPosition, targetPos, deltaPos);
|
||||
_target.localRotation = Quaternion.Lerp(_target.localRotation, targetRot, deltaRot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99a9ae539a68f554cbab92ca6eb4e7e5
|
||||
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore
|
||||
{
|
||||
[DefaultExecutionOrder(-102)]
|
||||
public class OctoLink : MonoBehaviour
|
||||
{
|
||||
[SerializeField] Object _octoCore;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!OctoCore.Instance && _octoCore)
|
||||
Instantiate(_octoCore);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aca1ddad5b073a9488d37617a5fd8d9a
|
||||
@@ -0,0 +1,297 @@
|
||||
using OctoCore.Inputs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Components
|
||||
{
|
||||
public class OctoPlayer : MonoBehaviour
|
||||
{
|
||||
public static OctoPlayer Instance { get; private set; }
|
||||
|
||||
public enum PlayerModes { FirstPerson, ThirdPerson }
|
||||
public enum Orientations { World, Camera }
|
||||
|
||||
[System.Serializable]
|
||||
private class References
|
||||
{
|
||||
public CharacterController Controller;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Settings
|
||||
{
|
||||
public PlayerModes Mode = PlayerModes.FirstPerson;
|
||||
public Orientations Orientation = Orientations.Camera;
|
||||
|
||||
[Tooltip("Walk speed (km/h).")]
|
||||
public float WalkSpeed = 10;
|
||||
|
||||
[Tooltip("Run speed (km/h).")]
|
||||
public float RunSpeed = 20;
|
||||
|
||||
[Tooltip("Jump duration (seconds).")]
|
||||
public float JumpDuration = .4f;
|
||||
|
||||
[Tooltip("Maximum jump height reached at the apex (meters).")]
|
||||
public float JumpHeight = .4f;
|
||||
|
||||
[Tooltip("Downward acceleration applied to the player (m/s²).")]
|
||||
public float Gravity = 9.8f;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Mapping
|
||||
{
|
||||
public OctoActions MoveAction;
|
||||
public OctoActions LookAction;
|
||||
public OctoActions RunAction;
|
||||
public OctoActions JumpAction;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class FirstPerson
|
||||
{
|
||||
[Header("References")]
|
||||
|
||||
public Camera Camera;
|
||||
|
||||
[Header("Settings")]
|
||||
|
||||
[Tooltip("Horizontal sensitivity (Yaw).")]
|
||||
public float SensitivityX = 0.1f;
|
||||
|
||||
[Tooltip("Vertical sensitivity (Pitch).")]
|
||||
public float SensitivityY = 0.1f;
|
||||
|
||||
[Tooltip("Minimum pitch (looking down).")]
|
||||
public float MinPitch = -80f;
|
||||
|
||||
[Tooltip("Maximum pitch (looking up).")]
|
||||
public float MaxPitch = 80f;
|
||||
}
|
||||
|
||||
[SerializeField] private References _references;
|
||||
[SerializeField] private Settings _settings;
|
||||
[SerializeField] private Mapping _mapping;
|
||||
[SerializeField] private FirstPerson _firstPerson;
|
||||
|
||||
private bool _isPaused;
|
||||
private bool _jumpInput;
|
||||
private bool _jumpping;
|
||||
private bool _runInput;
|
||||
private float _runInertia;
|
||||
private float _yaw;
|
||||
private float _pitch;
|
||||
private float _jumpLerp;
|
||||
private float _jumpTime;
|
||||
private float _jumpAltitude;
|
||||
private Vector2 _moveInput;
|
||||
private Vector2 _lookInput;
|
||||
private Vector3 _moveInertia;
|
||||
private Vector3 _moveDirection;
|
||||
|
||||
public bool IsPaused => _isPaused;
|
||||
public float Radius => _references.Controller ? _references.Controller.radius : 0;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (!Instance) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
|
||||
OctoCore.Instance.Inputs.SubscribeAction(_mapping.MoveAction, Move);
|
||||
OctoCore.Instance.Inputs.SubscribeAction(_mapping.LookAction, Look);
|
||||
OctoCore.Instance.Inputs.SubscribeAction(_mapping.RunAction, Run);
|
||||
OctoCore.Instance.Inputs.SubscribeAction(_mapping.JumpAction, Jump);
|
||||
|
||||
_yaw = transform.eulerAngles.y;
|
||||
|
||||
Pause(_isPaused);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.MoveAction, Move);
|
||||
OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.RunAction, Run);
|
||||
OctoCore.Instance.Inputs.UnsubscribeAction(_mapping.JumpAction, Jump);
|
||||
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
_moveInput = _isPaused ? Vector2.zero : Vector2.ClampMagnitude(_moveInput, 1);
|
||||
Camera cam = (_firstPerson.Camera && _firstPerson.Camera.enabled && _firstPerson.Camera.gameObject.activeInHierarchy) ? _firstPerson.Camera : Camera.main;
|
||||
|
||||
bool accelerating = _moveInput.magnitude > .1f;
|
||||
bool isGrounded = _references.Controller ? _references.Controller.isGrounded : true;
|
||||
float t = Time.deltaTime / 3.6f;
|
||||
|
||||
// Move
|
||||
|
||||
Vector3 move = new Vector3(_moveInput.x, 0, _moveInput.y);
|
||||
|
||||
if (_settings.Orientation == Orientations.Camera && cam)
|
||||
{
|
||||
Vector3 camForward = cam.transform.forward;
|
||||
camForward.y = 0;
|
||||
camForward.Normalize();
|
||||
|
||||
Vector3 camRight = cam.transform.right;
|
||||
camRight.y = 0;
|
||||
camRight.Normalize();
|
||||
|
||||
move = camRight * move.x + camForward * move.z;
|
||||
}
|
||||
|
||||
float deltaSmooth = t * (accelerating ? 30 : 15);
|
||||
_moveDirection = Vector3.Lerp(_moveDirection, move, deltaSmooth * 1.5f);
|
||||
|
||||
deltaSmooth *= 1 - Vector3.Cross(_moveInertia, _moveDirection).magnitude;
|
||||
_moveInertia = Vector3.Lerp(_moveInertia, move, deltaSmooth);
|
||||
|
||||
if (!accelerating && _moveInertia.magnitude < .01f) _moveInertia = Vector3.zero;
|
||||
|
||||
_runInertia = Mathf.Lerp(_runInertia, _runInput ? 1 : 0, t * (accelerating ? 5 : 3));
|
||||
|
||||
Vector3 moveDelta = _moveInertia * t * Mathf.Lerp(_settings.WalkSpeed, _settings.RunSpeed, _runInertia);
|
||||
|
||||
if (isGrounded && _jumpInput && !_jumpping)
|
||||
{
|
||||
_jumpInput = false;
|
||||
_jumpping = true;
|
||||
_jumpLerp = 0;
|
||||
_jumpTime = Time.timeSinceLevelLoad;
|
||||
_jumpAltitude = transform.position.y;
|
||||
}
|
||||
|
||||
float jumpOffset = 0;
|
||||
|
||||
if (_jumpping)
|
||||
{
|
||||
_jumpLerp = (Time.timeSinceLevelLoad - _jumpTime) / _settings.JumpDuration;
|
||||
|
||||
if (_jumpLerp >= 1 || (_jumpLerp > .3f && isGrounded))
|
||||
{
|
||||
_jumpping = false;
|
||||
_jumpLerp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float x = _jumpLerp;
|
||||
x = x * 2 - 1f;
|
||||
|
||||
float y;
|
||||
if (x < 0) y = Mathf.Sqrt(1f - x * x);
|
||||
else y = Mathf.Cos(x * .5f * Mathf.PI);
|
||||
y = _jumpAltitude + y * _settings.JumpHeight;
|
||||
|
||||
jumpOffset = y - transform.position.y;
|
||||
}
|
||||
}
|
||||
|
||||
if (_jumpping)
|
||||
moveDelta.y = jumpOffset;
|
||||
else
|
||||
moveDelta.y -= _settings.Gravity * Time.deltaTime;
|
||||
|
||||
if (_references.Controller)
|
||||
_references.Controller.Move(moveDelta);
|
||||
else
|
||||
transform.position += moveDelta;
|
||||
|
||||
// Rotate
|
||||
|
||||
switch (_settings.Mode)
|
||||
{
|
||||
case PlayerModes.FirstPerson:
|
||||
if (cam && !_isPaused)
|
||||
{
|
||||
Vector2 delta = _lookInput;
|
||||
|
||||
float mouseX = delta.x * _firstPerson.SensitivityX;
|
||||
float mouseY = delta.y * _firstPerson.SensitivityY * -1;
|
||||
|
||||
_yaw += mouseX;
|
||||
_pitch = Mathf.Clamp(_pitch + mouseY, _firstPerson.MinPitch, _firstPerson.MaxPitch);
|
||||
|
||||
transform.rotation = Quaternion.Euler(0, _yaw, 0);
|
||||
cam.transform.localRotation = Quaternion.Euler(_pitch, 0, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case PlayerModes.ThirdPerson:
|
||||
{
|
||||
Quaternion targetRot = transform.rotation;
|
||||
if (_moveInertia.magnitude > .002f)
|
||||
{
|
||||
Vector3 offset = _moveInertia - _moveDirection;
|
||||
offset *= _moveInertia.magnitude * _runInertia;
|
||||
offset = Vector3.ClampMagnitude(offset * 1f, 1f);
|
||||
Vector3 up = (Vector3.up - offset).normalized;
|
||||
Quaternion lookRot = Quaternion.LookRotation(_moveInertia, up);
|
||||
targetRot = Quaternion.Lerp(targetRot, lookRot, t * 30);
|
||||
}
|
||||
transform.rotation = targetRot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset
|
||||
|
||||
_moveInput = Vector2.zero;
|
||||
_lookInput = Vector2.zero;
|
||||
_runInput = false;
|
||||
}
|
||||
|
||||
public void Teleport(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
_isPaused = _jumpInput = _jumpping = _runInput = false;
|
||||
_runInertia = _jumpLerp = _jumpTime = _jumpAltitude = 0;
|
||||
_moveInput = _lookInput = Vector2.zero;
|
||||
_moveInertia = _moveDirection = Vector3.zero;
|
||||
_yaw = rotation.eulerAngles.y;
|
||||
_pitch = 0;
|
||||
|
||||
if (_references.Controller)
|
||||
_references.Controller.enabled = false;
|
||||
|
||||
transform.position = position;
|
||||
transform.rotation = rotation;
|
||||
|
||||
if (_references.Controller)
|
||||
_references.Controller.enabled = true;
|
||||
}
|
||||
|
||||
public void Pause(bool pause)
|
||||
{
|
||||
_isPaused = pause;
|
||||
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.UI.SetCursor();
|
||||
}
|
||||
|
||||
private void Move(Vector2 value) { _moveInput = value; }
|
||||
private void Look(Vector2 value) { _lookInput = value; }
|
||||
private void Run() { _runInput = true; }
|
||||
private void Jump() { _jumpInput = true; }
|
||||
|
||||
private void SetMode()
|
||||
{
|
||||
switch (_settings.Mode)
|
||||
{
|
||||
case PlayerModes.FirstPerson:
|
||||
// Activate First Person Camera
|
||||
break;
|
||||
|
||||
case PlayerModes.ThirdPerson:
|
||||
// Activate Third Person Camera
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*public static float Arc(float x)
|
||||
{
|
||||
x = Mathf.Clamp01(x) * 2 - 1f;
|
||||
return Mathf.Sqrt(1f - x * x);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c85c0434d95406f4da8daa149ba90838
|
||||
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using OctoCore.Save.Data;
|
||||
|
||||
namespace OctoCore.Components
|
||||
{
|
||||
public class OctoSaveEvent : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private OctoData data;
|
||||
|
||||
[SerializeField] private bool invertValue;
|
||||
|
||||
[SerializeField] private UnityEvent<bool> onInitBool;
|
||||
[SerializeField] private UnityEvent<int> onInitInt;
|
||||
[SerializeField] private UnityEvent<float> onInitFloat;
|
||||
[SerializeField] private UnityEvent<string> onInitString;
|
||||
[SerializeField] private UnityEvent<Vector4> onInitVector;
|
||||
|
||||
[SerializeField] private UnityEvent<bool> onChangeBool;
|
||||
[SerializeField] private UnityEvent<int> onChangeInt;
|
||||
[SerializeField] private UnityEvent<float> onChangeFloat;
|
||||
[SerializeField] private UnityEvent<string> onChangeString;
|
||||
[SerializeField] private UnityEvent<Vector4> onChangeVector;
|
||||
|
||||
private Action<bool> onValueChangedBool;
|
||||
private Action<int> onValueChangedInt;
|
||||
private Action<float> onValueChangedFloat;
|
||||
private Action<string> onValueChangedString;
|
||||
private Action<Vector4> onValueChangedVector;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (!OctoCore.Instance) return;
|
||||
|
||||
if (data)
|
||||
{
|
||||
switch (data)
|
||||
{
|
||||
case global::OctoCore.Save.Data.OctoDataBool:
|
||||
OnValueInit(OctoCore.Instance.Save.GetBool(data.Id));
|
||||
OctoCore.Instance.Save.Subscribe(data, onValueChangedBool = OnValueChanged);
|
||||
break;
|
||||
|
||||
case global::OctoCore.Save.Data.OctoDataInt:
|
||||
OnValueInit(OctoCore.Instance.Save.GetInt(data.Id));
|
||||
OctoCore.Instance.Save.Subscribe(data, onValueChangedInt = OnValueChanged);
|
||||
break;
|
||||
|
||||
case global::OctoCore.Save.Data.OctoDataFloat:
|
||||
OnValueInit(OctoCore.Instance.Save.GetFloat(data.Id));
|
||||
OctoCore.Instance.Save.Subscribe(data, onValueChangedFloat = OnValueChanged);
|
||||
break;
|
||||
|
||||
case global::OctoCore.Save.Data.OctoDataString:
|
||||
OnValueInit(OctoCore.Instance.Save.GetString(data.Id));
|
||||
OctoCore.Instance.Save.Subscribe(data, onValueChangedString = OnValueChanged);
|
||||
break;
|
||||
|
||||
case global::OctoCore.Save.Data.OctoDataVector:
|
||||
OnValueInit(OctoCore.Instance.Save.GetVector(data.Id));
|
||||
OctoCore.Instance.Save.Subscribe(data, onValueChangedVector = OnValueChanged);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"Unsupported data: {data.Id}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"{name} subscribed on: {data.Id}");
|
||||
}
|
||||
else if (OctoCore.Instance.Development.Log && data)
|
||||
{
|
||||
Debug.Log($"Data is null");
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (!OctoCore.Instance) return;
|
||||
|
||||
if (data)
|
||||
{
|
||||
if (onValueChangedBool != null)
|
||||
{
|
||||
OctoCore.Instance.Save.Unsubscribe(data, onValueChangedBool);
|
||||
onValueChangedBool = null;
|
||||
}
|
||||
|
||||
if (onValueChangedInt != null)
|
||||
{
|
||||
OctoCore.Instance.Save.Unsubscribe(data, onValueChangedInt);
|
||||
onValueChangedInt = null;
|
||||
}
|
||||
|
||||
if (onValueChangedFloat != null)
|
||||
{
|
||||
OctoCore.Instance.Save.Unsubscribe(data, onValueChangedFloat);
|
||||
onValueChangedFloat = null;
|
||||
}
|
||||
|
||||
if (onValueChangedString != null)
|
||||
{
|
||||
OctoCore.Instance.Save.Unsubscribe(data, onValueChangedString);
|
||||
onValueChangedString = null;
|
||||
}
|
||||
|
||||
if (onValueChangedVector != null)
|
||||
{
|
||||
OctoCore.Instance.Save.Unsubscribe(data, onValueChangedVector);
|
||||
onValueChangedVector = null;
|
||||
}
|
||||
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"{name} unsubscribed on: {data.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
void OnValueInit()
|
||||
{
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"Value initialized on: {data.Id}");
|
||||
}
|
||||
|
||||
void OnValueInit(bool value)
|
||||
{
|
||||
onInitBool?.Invoke(invertValue ? !value : value);
|
||||
OnValueInit();
|
||||
}
|
||||
|
||||
void OnValueInit(int value)
|
||||
{
|
||||
onInitInt?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueInit(float value)
|
||||
{
|
||||
onInitFloat?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueInit(string value)
|
||||
{
|
||||
onInitString?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueInit(Vector4 value)
|
||||
{
|
||||
onInitVector?.Invoke(value);
|
||||
OnValueInit();
|
||||
}
|
||||
|
||||
void OnValueChanged()
|
||||
{
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"Value changed on: {data.Id}");
|
||||
}
|
||||
|
||||
void OnValueChanged(bool value)
|
||||
{
|
||||
onChangeBool?.Invoke(invertValue ? !value : value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueChanged(int value)
|
||||
{
|
||||
onChangeInt?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueChanged(float value)
|
||||
{
|
||||
onChangeFloat?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueChanged(string value)
|
||||
{
|
||||
onChangeString?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
void OnValueChanged(Vector4 value)
|
||||
{
|
||||
onChangeVector?.Invoke(value);
|
||||
OnValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2978f4ffc9fdd0f4387d6b5e41746646
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Save.Data;
|
||||
|
||||
namespace OctoCore.Components
|
||||
{
|
||||
public class OctoSaveProxy : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private OctoData data;
|
||||
|
||||
public void SetData(OctoData data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void SetBool(bool value)
|
||||
{
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Save.Set(data as global::OctoCore.Save.Data.OctoDataBool, value);
|
||||
}
|
||||
public void SetInt(int value)
|
||||
{
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Save.Set(data as global::OctoCore.Save.Data.OctoDataInt, value);
|
||||
}
|
||||
public void SetFloat(float value)
|
||||
{
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Save.Set(data as global::OctoCore.Save.Data.OctoDataFloat, value);
|
||||
}
|
||||
public void SetString(string value)
|
||||
{
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Save.Set(data as global::OctoCore.Save.Data.OctoDataString, value);
|
||||
}
|
||||
public void SetVector(Vector4 value)
|
||||
{
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Save.Set(data as global::OctoCore.Save.Data.OctoDataVector, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1b0281cf6e469a43ad8ef7171913dad
|
||||
@@ -0,0 +1,201 @@
|
||||
using OctoCore.Inputs;
|
||||
using OctoCore.Utilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace OctoCore.Components
|
||||
{
|
||||
public class OctoWorldEvent : MonoBehaviour
|
||||
{
|
||||
public enum Conditions { None, Player, Tags, Targets }
|
||||
|
||||
//[SerializeField] private InputActionAsset _actionAsset;
|
||||
//[SerializeField] private InputActionReference _actionReference;
|
||||
//[Space(10)]
|
||||
|
||||
[SerializeField] private Utilities.OctoEvents _trigger;
|
||||
[SerializeField] private OctoActions _action;
|
||||
[SerializeField] private Conditions _condition;
|
||||
[SerializeField] private string[] _tags;
|
||||
[SerializeField] private GameObject[] _targets;
|
||||
[SerializeField] private UnityEvent _onTrigger;
|
||||
|
||||
private bool _playerInside;
|
||||
private bool[] _tagsInside;
|
||||
private bool[] _targetsInside;
|
||||
|
||||
private const string PlayerTag = "Player";
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_tagsInside = new bool[_tags.Length];
|
||||
_targetsInside = new bool[_targets.Length];
|
||||
|
||||
switch (_trigger)
|
||||
{
|
||||
case Utilities.OctoEvents.Awake:
|
||||
SetTrigger();
|
||||
break;
|
||||
|
||||
case Utilities.OctoEvents.Action:
|
||||
OctoCore.Instance.Inputs.SubscribeAction(_action, SetTrigger);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.OnEnable)
|
||||
SetTrigger();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.Start)
|
||||
SetTrigger();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.Update)
|
||||
SetTrigger();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.LateUpdate)
|
||||
SetTrigger();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.FixedUpdate)
|
||||
SetTrigger();
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.OnDisable)
|
||||
SetTrigger();
|
||||
|
||||
_playerInside = false;
|
||||
_tagsInside = new bool[_tags.Length];
|
||||
_targetsInside = new bool[_targets.Length];
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
switch (_trigger)
|
||||
{
|
||||
case Utilities.OctoEvents.OnDestroy:
|
||||
SetTrigger();
|
||||
break;
|
||||
|
||||
case Utilities.OctoEvents.Action:
|
||||
OctoCore.Instance?.Inputs.UnsubscribeAction(_action, SetTrigger);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerEnter(Collider col)
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.OnTriggerEnter)
|
||||
{
|
||||
EvaluateTrigger(col);
|
||||
SetTrigger();
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerStay(Collider col)
|
||||
{
|
||||
EvaluateTrigger(col);
|
||||
if (_trigger == Utilities.OctoEvents.OnTriggerStay)
|
||||
SetTrigger();
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider col)
|
||||
{
|
||||
if (_trigger == Utilities.OctoEvents.OnTriggerExit)
|
||||
SetTrigger();
|
||||
EvaluateTrigger(col, false);
|
||||
}
|
||||
|
||||
public void SetTrigger()
|
||||
{
|
||||
switch (_condition)
|
||||
{
|
||||
case Conditions.None:
|
||||
_onTrigger?.Invoke();
|
||||
break;
|
||||
|
||||
case Conditions.Player:
|
||||
if (_playerInside)
|
||||
_onTrigger?.Invoke();
|
||||
break;
|
||||
|
||||
case Conditions.Tags:
|
||||
for (int i = 0; i < _tagsInside.Length; i++)
|
||||
{
|
||||
if (_tagsInside[i])
|
||||
{
|
||||
_onTrigger?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Conditions.Targets:
|
||||
for (int i = 0; i < _targetsInside.Length; i++)
|
||||
{
|
||||
if (_targetsInside[i])
|
||||
{
|
||||
_onTrigger?.Invoke();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EvaluateTrigger(Collider col, bool inside = true)
|
||||
{
|
||||
switch (_condition)
|
||||
{
|
||||
case Conditions.Player:
|
||||
if (col.tag == PlayerTag)
|
||||
_playerInside = inside;
|
||||
break;
|
||||
|
||||
case Conditions.Tags:
|
||||
if (_tags.Contains(col.tag))
|
||||
{
|
||||
for (int i = 0; i < _tags.Length; i++)
|
||||
{
|
||||
bool tagMatch = col.tag == _tags[i];
|
||||
if (tagMatch)
|
||||
{
|
||||
_tagsInside[i] = inside ? tagMatch : !tagMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Conditions.Targets:
|
||||
if (_targets.Contains(col.gameObject))
|
||||
{
|
||||
for (int i = 0; i < _targets.Length; i++)
|
||||
{
|
||||
bool targetMatch = col.gameObject == _targets[i];
|
||||
if (targetMatch)
|
||||
{
|
||||
_targetsInside[i] = inside ? targetMatch : !targetMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5170627695bd877419928fa9ca37865d
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 086b0be20b6bc5e42b2fe80f2ce8e863
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace OctoCore.Development
|
||||
{
|
||||
/// <summary>
|
||||
/// Development and debugging module.
|
||||
/// Provides settings for in-game logging.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoDevelopment : IOctoModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to enable debug logging throughout the framework.
|
||||
/// </summary>
|
||||
public bool Log;
|
||||
|
||||
public void Init(OctoSettings settings)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnSceneChanged(Scene current, Scene next)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnQuit()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21618ec4511f27a43975352bde789bb6
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e051aab1fc4e4e243b307fd7f0b0756f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1be27ce87e441a545bd6f3fdd3c18b61
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace OctoCore.Dialogue.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component to trigger dialogue events.
|
||||
/// Listens to dialogue start/end and invokes UnityEvents.
|
||||
/// </summary>
|
||||
public class OctoDialogueEvent : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Dialogue data to listen for")]
|
||||
private Data.OctoDialogueData _dialogue;
|
||||
|
||||
[SerializeField, Tooltip("Called when the specified dialogue starts")]
|
||||
private UnityEvent _onDialogueStarted;
|
||||
|
||||
[SerializeField, Tooltip("Called when the specified dialogue ends")]
|
||||
private UnityEvent _onDialogueEnded;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (OctoDialogueManager.Instance != null)
|
||||
{
|
||||
// Subscribe to manager events
|
||||
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
|
||||
._events.OnDialogueStarted.AddListener(OnDialogueStarted);
|
||||
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
|
||||
._events.OnDialogueEnded.AddListener(OnDialogueEnded);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (OctoDialogueManager.Instance != null)
|
||||
{
|
||||
// Unsubscribe from manager events
|
||||
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
|
||||
._events.OnDialogueStarted.RemoveListener(OnDialogueStarted);
|
||||
OctoDialogueManager.Instance.GetComponent<OctoDialogueManager>()
|
||||
._events.OnDialogueEnded.RemoveListener(OnDialogueEnded);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDialogueStarted()
|
||||
{
|
||||
if (OctoDialogueManager.Instance == null)
|
||||
return;
|
||||
|
||||
var currentDialogue = OctoDialogueManager.Instance.GetCurrentDialogue();
|
||||
if (currentDialogue == _dialogue)
|
||||
{
|
||||
_onDialogueStarted?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDialogueEnded()
|
||||
{
|
||||
// Dialogue already ended, so we check if it was our dialogue
|
||||
// Note: This might not work perfectly if dialogue changes rapidly
|
||||
// A better implementation would track the last active dialogue
|
||||
_onDialogueEnded?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7c573c7064e10f4ba4855e8f448ee81
|
||||
@@ -0,0 +1,291 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using OctoCore.Localization;
|
||||
using OctoCore.Components;
|
||||
|
||||
namespace OctoCore.Dialogue.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialogue manager component.
|
||||
/// Singleton that handles dialogue display and flow.
|
||||
/// Uses UI Text components if assigned, otherwise falls back to GUI.
|
||||
/// </summary>
|
||||
public class OctoDialogueManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton instance.
|
||||
/// </summary>
|
||||
public static OctoDialogueManager Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// References to UI components.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class References
|
||||
{
|
||||
[Tooltip("Legacy UI Text component (optional)")]
|
||||
public Text Text;
|
||||
|
||||
[Tooltip("TextMeshPro component (optional)")]
|
||||
public TextMeshPro TextMeshPro;
|
||||
|
||||
[Tooltip("TextMeshPro UGUI component (optional)")]
|
||||
public TextMeshProUGUI TextMeshProUGUI;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dialogue events.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Events
|
||||
{
|
||||
[Tooltip("Called when a dialogue starts")]
|
||||
public UnityEvent OnDialogueStarted;
|
||||
|
||||
[Tooltip("Called when a new entry starts displaying")]
|
||||
public UnityEvent OnEntryStarted;
|
||||
|
||||
[Tooltip("Called when a choice is selected")]
|
||||
public UnityEvent OnChoiceSelected;
|
||||
|
||||
[Tooltip("Called when a dialogue ends")]
|
||||
public UnityEvent OnDialogueEnded;
|
||||
}
|
||||
|
||||
[SerializeField] private References _references;
|
||||
[SerializeField] public Events _events;
|
||||
|
||||
private Data.OctoDialogueData _currentDialogue;
|
||||
private int _currentEntryIndex;
|
||||
private List<OctoDialogueChoice> _availableChoices = new List<OctoDialogueChoice>();
|
||||
private bool _isActive;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (Instance == this)
|
||||
{
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a new dialogue.
|
||||
/// Automatically displays the first entry.
|
||||
/// </summary>
|
||||
public void StartDialogue(Data.OctoDialogueData dialogue)
|
||||
{
|
||||
if (dialogue == null || dialogue.Entries == null || dialogue.Entries.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[OctoDialogueManager] Cannot start dialogue: null or empty");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentDialogue = dialogue;
|
||||
_currentEntryIndex = 0;
|
||||
_isActive = true;
|
||||
|
||||
_events.OnDialogueStarted?.Invoke();
|
||||
if (OctoPlayer.Instance) OctoPlayer.Instance.Pause(true); // TMP
|
||||
DisplayCurrentEntry();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display the current dialogue entry.
|
||||
/// </summary>
|
||||
private void DisplayCurrentEntry()
|
||||
{
|
||||
if (_currentDialogue == null || _currentEntryIndex >= _currentDialogue.Entries.Length)
|
||||
{
|
||||
EndDialogue();
|
||||
return;
|
||||
}
|
||||
|
||||
OctoDialogueEntry entry = _currentDialogue.Entries[_currentEntryIndex];
|
||||
|
||||
_events.OnEntryStarted?.Invoke();
|
||||
|
||||
// Display text
|
||||
string text = OctoLocalization.GetLocalizedText(entry.Text);
|
||||
SetText(text);
|
||||
|
||||
// Build available choices
|
||||
_availableChoices.Clear();
|
||||
if (entry.Choices != null)
|
||||
{
|
||||
foreach (var choice in entry.Choices)
|
||||
{
|
||||
if (choice.IsAvailable())
|
||||
{
|
||||
_availableChoices.Add(choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select a choice by index.
|
||||
/// </summary>
|
||||
public void SelectChoice(int choiceIndex)
|
||||
{
|
||||
if (!_isActive || choiceIndex < 0 || choiceIndex >= _availableChoices.Count)
|
||||
return;
|
||||
|
||||
OctoDialogueChoice choice = _availableChoices[choiceIndex];
|
||||
|
||||
_events.OnChoiceSelected?.Invoke();
|
||||
|
||||
if (choice.NextDialogue != null)
|
||||
{
|
||||
StartDialogue(choice.NextDialogue);
|
||||
}
|
||||
else
|
||||
{
|
||||
EndDialogue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continue to next entry (when no choices available).
|
||||
/// </summary>
|
||||
public void Continue()
|
||||
{
|
||||
if (!_isActive)
|
||||
return;
|
||||
|
||||
if (_availableChoices.Count == 0)
|
||||
{
|
||||
_currentEntryIndex++;
|
||||
DisplayCurrentEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the current dialogue.
|
||||
/// </summary>
|
||||
private void EndDialogue()
|
||||
{
|
||||
_isActive = false;
|
||||
_currentDialogue = null;
|
||||
_currentEntryIndex = 0;
|
||||
_availableChoices.Clear();
|
||||
|
||||
if (OctoPlayer.Instance) OctoPlayer.Instance.Pause(false); // TMP
|
||||
_events.OnDialogueEnded?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set text using available UI component or GUI fallback.
|
||||
/// </summary>
|
||||
private void SetText(string text)
|
||||
{
|
||||
if (_references.Text != null)
|
||||
{
|
||||
_references.Text.text = text;
|
||||
}
|
||||
else if (_references.TextMeshPro != null)
|
||||
{
|
||||
_references.TextMeshPro.text = text;
|
||||
}
|
||||
else if (_references.TextMeshProUGUI != null)
|
||||
{
|
||||
_references.TextMeshProUGUI.text = text;
|
||||
}
|
||||
// GUI fallback handled in OnGUI
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
// Only use GUI if no text component is assigned
|
||||
if (_references.Text != null || _references.TextMeshPro != null || _references.TextMeshProUGUI != null)
|
||||
return;
|
||||
|
||||
if (!_isActive || _currentDialogue == null)
|
||||
return;
|
||||
|
||||
if (_currentEntryIndex >= _currentDialogue.Entries.Length)
|
||||
return;
|
||||
|
||||
OctoDialogueEntry entry = _currentDialogue.Entries[_currentEntryIndex];
|
||||
|
||||
GUILayout.BeginArea(new Rect(50, Screen.height - 200, Screen.width - 100, 150));
|
||||
GUILayout.Box("", GUILayout.Width(Screen.width - 100), GUILayout.Height(150));
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 10, Screen.width - 120, 130));
|
||||
|
||||
// Speaker name
|
||||
if (entry.Speaker != null && entry.Speaker.Name != null)
|
||||
{
|
||||
string speakerName = OctoLocalization.GetLocalizedText(entry.Speaker.Name);
|
||||
GUILayout.Label(speakerName, GUI.skin.box);
|
||||
}
|
||||
|
||||
// Dialogue text
|
||||
string text = OctoLocalization.GetLocalizedText(entry.Text);
|
||||
GUILayout.Label(text, GUILayout.ExpandHeight(true));
|
||||
|
||||
// Choices
|
||||
if (_availableChoices.Count > 0)
|
||||
{
|
||||
GUILayout.Space(10);
|
||||
for (int i = 0; i < _availableChoices.Count; i++)
|
||||
{
|
||||
string choiceText = OctoLocalization.GetLocalizedText(_availableChoices[i].Text);
|
||||
if (GUILayout.Button(choiceText))
|
||||
{
|
||||
SelectChoice(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Space(10);
|
||||
if (GUILayout.Button("→"))
|
||||
{
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current dialogue data.
|
||||
/// </summary>
|
||||
public Data.OctoDialogueData GetCurrentDialogue()
|
||||
{
|
||||
return _currentDialogue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get available choices for current entry.
|
||||
/// </summary>
|
||||
public List<OctoDialogueChoice> GetAvailableChoices()
|
||||
{
|
||||
return new List<OctoDialogueChoice>(_availableChoices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if dialogue is currently active.
|
||||
/// </summary>
|
||||
public bool IsActive()
|
||||
{
|
||||
return _isActive;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a41b5f79f61ad94c9479c28b4397de4
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be1a654d39699564cb1dc1342aa93085
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Dialogue.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialogue data containing a sequence of entries.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "dialogue_", menuName = "OctoCore/Dialogue/Dialogue Data", order = 1)]
|
||||
public class OctoDialogueData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Sequence of dialogue entries.
|
||||
/// First entry starts automatically.
|
||||
/// </summary>
|
||||
[Tooltip("Sequence of dialogue entries")]
|
||||
public OctoDialogueEntry[] Entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c6fdb9b9b4581f4b912b64147fa3969
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Localization;
|
||||
|
||||
namespace OctoCore.Dialogue.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Speaker data for dialogue system.
|
||||
/// Contains localized name and sprite.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "speaker_", menuName = "OctoCore/Dialogue/Speaker Data", order = 0)]
|
||||
public class OctoSpeakerData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Localized name of the speaker.
|
||||
/// </summary>
|
||||
[Tooltip("Localized name of the speaker")]
|
||||
public OctoLocalizedTextData Name;
|
||||
|
||||
/// <summary>
|
||||
/// Speaker portrait sprite.
|
||||
/// </summary>
|
||||
[Tooltip("Speaker portrait sprite")]
|
||||
public Sprite Sprite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a14c37f184ae07b4b8f641e7e2896ed7
|
||||
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Localization;
|
||||
|
||||
namespace OctoCore.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialogue choice with condition and next dialogue.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoDialogueChoice
|
||||
{
|
||||
/// <summary>
|
||||
/// Localized text for this choice.
|
||||
/// </summary>
|
||||
[Tooltip("Localized text for this choice")]
|
||||
public OctoLocalizedTextData Text;
|
||||
|
||||
/// <summary>
|
||||
/// Condition to display this choice.
|
||||
/// If null or condition is true, choice is available.
|
||||
/// </summary>
|
||||
[Tooltip("Condition to display this choice (null = always show)")]
|
||||
public OctoDialogueCondition Condition;
|
||||
|
||||
/// <summary>
|
||||
/// Next dialogue to load when this choice is selected.
|
||||
/// If null, dialogue ends.
|
||||
/// </summary>
|
||||
[Tooltip("Next dialogue when selected (null = end dialogue)")]
|
||||
public Data.OctoDialogueData NextDialogue;
|
||||
|
||||
/// <summary>
|
||||
/// Check if this choice should be available.
|
||||
/// </summary>
|
||||
public bool IsAvailable()
|
||||
{
|
||||
if (Condition == null)
|
||||
return true;
|
||||
|
||||
return Condition.Evaluate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b828f206b626c94a964cbb5e4aaddc4
|
||||
@@ -0,0 +1,90 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Save.Data;
|
||||
|
||||
namespace OctoCore.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// Condition for dialogue choices.
|
||||
/// Supports OctoDataBool, OctoDataInt, and OctoDataFloat.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoDialogueCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// Source data to evaluate (OctoDataBool, OctoDataInt, or OctoDataFloat).
|
||||
/// </summary>
|
||||
[Tooltip("Source data to evaluate")]
|
||||
public OctoData Source;
|
||||
|
||||
/// <summary>
|
||||
/// Operation to perform.
|
||||
/// </summary>
|
||||
[Tooltip("Operation to perform")]
|
||||
public OctoDialogueOperation Operation = OctoDialogueOperation.Equal;
|
||||
|
||||
/// <summary>
|
||||
/// Value to compare against.
|
||||
/// For bool: 0 = false, 1 = true
|
||||
/// For int/float: direct comparison
|
||||
/// </summary>
|
||||
[Tooltip("Value to compare against (bool: 0=false, 1=true)")]
|
||||
public float Value;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate if the condition is met.
|
||||
/// </summary>
|
||||
public bool Evaluate()
|
||||
{
|
||||
if (Source == null)
|
||||
return true; // No condition = always true
|
||||
|
||||
float sourceValue = GetSourceValue();
|
||||
|
||||
switch (Operation)
|
||||
{
|
||||
case OctoDialogueOperation.Equal:
|
||||
return Mathf.Approximately(sourceValue, Value);
|
||||
|
||||
case OctoDialogueOperation.NotEqual:
|
||||
return !Mathf.Approximately(sourceValue, Value);
|
||||
|
||||
case OctoDialogueOperation.GreaterThan:
|
||||
return sourceValue > Value;
|
||||
|
||||
case OctoDialogueOperation.GreaterOrEqual:
|
||||
return sourceValue >= Value;
|
||||
|
||||
case OctoDialogueOperation.LessThan:
|
||||
return sourceValue < Value;
|
||||
|
||||
case OctoDialogueOperation.LessOrEqual:
|
||||
return sourceValue <= Value;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetSourceValue()
|
||||
{
|
||||
if (!OctoCore.Instance)
|
||||
return 0f;
|
||||
|
||||
if (Source is OctoDataBool dataBool)
|
||||
{
|
||||
return OctoCore.Instance.Save.Get(dataBool) ? 1f : 0f;
|
||||
}
|
||||
else if (Source is OctoDataInt dataInt)
|
||||
{
|
||||
return OctoCore.Instance.Save.Get(dataInt);
|
||||
}
|
||||
else if (Source is OctoDataFloat dataFloat)
|
||||
{
|
||||
return OctoCore.Instance.Save.Get(dataFloat);
|
||||
}
|
||||
|
||||
Debug.LogWarning("[OctoDialogueCondition] Unsupported data type: " + Source.GetType().Name);
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 553d5d510ff034f44b70a8a032565a4e
|
||||
@@ -0,0 +1,31 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Localization;
|
||||
|
||||
namespace OctoCore.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// Single dialogue entry with speaker, text, and choices.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoDialogueEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Speaker for this entry.
|
||||
/// </summary>
|
||||
[Tooltip("Speaker for this entry")]
|
||||
public Data.OctoSpeakerData Speaker;
|
||||
|
||||
/// <summary>
|
||||
/// Localized dialogue text.
|
||||
/// </summary>
|
||||
[Tooltip("Localized dialogue text")]
|
||||
public OctoLocalizedTextData Text;
|
||||
|
||||
/// <summary>
|
||||
/// Available choices for this entry.
|
||||
/// If empty, dialogue continues automatically or ends.
|
||||
/// </summary>
|
||||
[Tooltip("Available choices (empty = auto-continue or end)")]
|
||||
public OctoDialogueChoice[] Choices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec95cc134f0fa214eb3cd8c0e9016347
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace OctoCore.Dialogue
|
||||
{
|
||||
/// <summary>
|
||||
/// Operations for dialogue conditions.
|
||||
/// </summary>
|
||||
public enum OctoDialogueOperation
|
||||
{
|
||||
/// <summary>Equal (bool, int, float)</summary>
|
||||
Equal,
|
||||
|
||||
/// <summary>Not equal (bool, int, float)</summary>
|
||||
NotEqual,
|
||||
|
||||
/// <summary>Greater than (int, float only)</summary>
|
||||
GreaterThan,
|
||||
|
||||
/// <summary>Greater or equal (int, float only)</summary>
|
||||
GreaterOrEqual,
|
||||
|
||||
/// <summary>Less than (int, float only)</summary>
|
||||
LessThan,
|
||||
|
||||
/// <summary>Less or equal (int, float only)</summary>
|
||||
LessOrEqual
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4c04f460edc6d949933bd4237fb2e0a
|
||||
@@ -0,0 +1,36 @@
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace OctoCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for all OctoCore modules.
|
||||
/// Defines the lifecycle methods that all modules must implement.
|
||||
/// </summary>
|
||||
public interface IOctoModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the module with global settings.
|
||||
/// Called once during OctoCore startup.
|
||||
/// </summary>
|
||||
/// <param name="settings">Global settings configuration.</param>
|
||||
void Init(OctoSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame to update the module.
|
||||
/// </summary>
|
||||
void Tick();
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene change occurs.
|
||||
/// </summary>
|
||||
/// <param name="current">The scene being left.</param>
|
||||
/// <param name="next">The scene being loaded.</param>
|
||||
void OnSceneChanged(Scene current, Scene next);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the application is quitting.
|
||||
/// Use this for cleanup and saving final state.
|
||||
/// </summary>
|
||||
void OnQuit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15167e6c98ed86e448464574144f40f4
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb7efa3243ed91043a79bc110b57e653
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Generated by OctoSettings
|
||||
// Backup saved in: Assets/OctoCore/Generated/OctoActions.json
|
||||
// To regenerate: OctoSettings > Mapping > Click 'Generate Inputs'
|
||||
// Generated from: PlayerController
|
||||
// Generated on: 2026-02-09 21:55:05
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
namespace OctoCore.Inputs
|
||||
{
|
||||
/// <summary>
|
||||
/// Actions from Unity Input Actions.
|
||||
/// Generated from: PlayerController
|
||||
/// Format: ActionMap_ActionName
|
||||
/// Values are stable hashes to prevent serialization issues when regenerating.
|
||||
/// </summary>
|
||||
public enum OctoActions
|
||||
{
|
||||
/// <summary>Player > Move</summary>
|
||||
Player_Move = -560403583,
|
||||
/// <summary>Player > Look</summary>
|
||||
Player_Look = -916633937,
|
||||
/// <summary>Player > Attack</summary>
|
||||
Player_Attack = -350647692,
|
||||
/// <summary>Player > Interact</summary>
|
||||
Player_Interact = -2066596810,
|
||||
/// <summary>Player > Crouch</summary>
|
||||
Player_Crouch = 510396034,
|
||||
/// <summary>Player > Jump</summary>
|
||||
Player_Jump = -1877919292,
|
||||
/// <summary>Player > Previous</summary>
|
||||
Player_Previous = -1566477299,
|
||||
/// <summary>Player > Next</summary>
|
||||
Player_Next = -1266215613,
|
||||
/// <summary>Player > Sprint</summary>
|
||||
Player_Sprint = 1115268836,
|
||||
/// <summary>Player > Diapason</summary>
|
||||
Player_Diapason = -1931519977,
|
||||
/// <summary>Player > Fire</summary>
|
||||
Player_Fire = 380421744,
|
||||
/// <summary>UI > Navigate</summary>
|
||||
UI_Navigate = -1768796602,
|
||||
/// <summary>UI > Submit</summary>
|
||||
UI_Submit = -1966140153,
|
||||
/// <summary>UI > Cancel</summary>
|
||||
UI_Cancel = 728645621,
|
||||
/// <summary>UI > Point</summary>
|
||||
UI_Point = 1869126131,
|
||||
/// <summary>UI > Click</summary>
|
||||
UI_Click = -468029055,
|
||||
/// <summary>UI > RightClick</summary>
|
||||
UI_RightClick = -1344942599,
|
||||
/// <summary>UI > MiddleClick</summary>
|
||||
UI_MiddleClick = 1604886768,
|
||||
/// <summary>UI > ScrollWheel</summary>
|
||||
UI_ScrollWheel = 207319517,
|
||||
/// <summary>UI > TrackedDevicePosition</summary>
|
||||
UI_TrackedDevicePosition = 1110904968,
|
||||
/// <summary>UI > TrackedDeviceOrientation</summary>
|
||||
UI_TrackedDeviceOrientation = 1632432327
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84cdf2dfd7a86434b9cd00c7a43c58e8
|
||||
@@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using static OctoCore.OctoSettings;
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
namespace OctoCore.Inputs
|
||||
{
|
||||
/// <summary>
|
||||
/// Input management module that wraps Unity's Input System.
|
||||
/// Provides enum-based API with automatic enable/disable based on subscriptions.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoInputs : IOctoModule
|
||||
{
|
||||
[System.Serializable]
|
||||
public class ActionInfo
|
||||
{
|
||||
[HideInInspector]
|
||||
public string Name;
|
||||
|
||||
[HideInInspector]
|
||||
public string MapName;
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
[Tooltip("List of registered callbacks")]
|
||||
public string[] Callbacks;
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
[System.NonSerialized]
|
||||
public InputActionReference Reference;
|
||||
|
||||
[System.NonSerialized]
|
||||
public List<Action> CallbacksList = new List<Action>();
|
||||
|
||||
[System.NonSerialized]
|
||||
public List<Action<Vector2>> CallbacksVector2List = new List<Action<Vector2>>();
|
||||
|
||||
[System.NonSerialized]
|
||||
public Vector2 LastValue = Vector2.zero;
|
||||
#endif
|
||||
|
||||
public ActionInfo(string name, string mapName)
|
||||
{
|
||||
Name = name;
|
||||
MapName = mapName;
|
||||
Enabled = false;
|
||||
Callbacks = new string[0];
|
||||
}
|
||||
}
|
||||
|
||||
[Header("Actions")]
|
||||
public List<ActionInfo> Actions = new List<ActionInfo>();
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
private MappingSettings _mappingData;
|
||||
private Dictionary<OctoActions, ActionInfo> _actionsDict = new Dictionary<OctoActions, ActionInfo>();
|
||||
private Dictionary<string, InputActionMap> _actionMaps = new Dictionary<string, InputActionMap>();
|
||||
private bool _initialized;
|
||||
#endif
|
||||
|
||||
public void Init(OctoSettings settings)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
_mappingData = settings.Mapping;
|
||||
|
||||
if (_mappingData == null)
|
||||
{
|
||||
Debug.LogError("[OctoInputs] No mapping settings found in OctoSettings.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mappingData.InputActionAsset == null)
|
||||
{
|
||||
Debug.LogError("[OctoInputs] No InputActionAsset assigned in OctoSettings.");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildActions();
|
||||
_initialized = true;
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoInputs] Initialized with {_actionsDict.Count} actions.");
|
||||
#else
|
||||
Debug.LogWarning("[OctoInputs] Input System Package is not enabled.");
|
||||
#endif
|
||||
|
||||
// Callback OctoEvents
|
||||
if (OctoEvents.Instance)
|
||||
OctoEvents.Instance.SetInputsInitialized();
|
||||
}
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
private void BuildActions()
|
||||
{
|
||||
Actions.Clear();
|
||||
_actionsDict.Clear();
|
||||
_actionMaps.Clear();
|
||||
|
||||
// Cache all action maps
|
||||
foreach (var map in _mappingData.InputActionAsset.actionMaps)
|
||||
{
|
||||
_actionMaps[map.name] = map;
|
||||
}
|
||||
|
||||
// Build actions list
|
||||
foreach (var actionData in _mappingData.Actions)
|
||||
{
|
||||
var actionInfo = new ActionInfo(actionData.Name, actionData.MapName);
|
||||
actionInfo.Reference = actionData.Reference;
|
||||
|
||||
Actions.Add(actionInfo);
|
||||
_actionsDict[actionData.Action] = actionInfo;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnableAction(OctoActions action)
|
||||
{
|
||||
if (!_actionsDict.TryGetValue(action, out var actionInfo))
|
||||
return;
|
||||
|
||||
if (actionInfo.Enabled || actionInfo.Reference == null)
|
||||
return;
|
||||
|
||||
actionInfo.Reference.action.Enable();
|
||||
actionInfo.Enabled = true;
|
||||
|
||||
// Enable parent action map if needed
|
||||
if (_actionMaps.TryGetValue(actionInfo.MapName, out var actionMap))
|
||||
{
|
||||
if (!actionMap.enabled)
|
||||
{
|
||||
actionMap.Enable();
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoInputs] Enabled action map: {actionInfo.MapName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoInputs] Enabled action: {action}");
|
||||
}
|
||||
|
||||
private void DisableAction(OctoActions action)
|
||||
{
|
||||
if (!_actionsDict.TryGetValue(action, out var actionInfo))
|
||||
return;
|
||||
|
||||
if (!actionInfo.Enabled || actionInfo.Reference == null)
|
||||
return;
|
||||
|
||||
// Check if there are still callbacks
|
||||
int totalCallbacks = actionInfo.CallbacksList.Count + actionInfo.CallbacksVector2List.Count;
|
||||
if (totalCallbacks > 0)
|
||||
return;
|
||||
|
||||
actionInfo.Reference.action.Disable();
|
||||
actionInfo.Enabled = false;
|
||||
|
||||
// Check if we should disable the action map
|
||||
CheckActionMapState(actionInfo.MapName);
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoInputs] Disabled action: {action}");
|
||||
}
|
||||
|
||||
private void CheckActionMapState(string mapName)
|
||||
{
|
||||
if (!_actionMaps.TryGetValue(mapName, out var actionMap))
|
||||
return;
|
||||
|
||||
// Check if any actions in this map are still enabled
|
||||
bool hasActiveActions = false;
|
||||
foreach (var actionInfo in Actions)
|
||||
{
|
||||
if (actionInfo.MapName == mapName && actionInfo.Enabled)
|
||||
{
|
||||
hasActiveActions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable map if no active actions
|
||||
if (!hasActiveActions && actionMap.enabled)
|
||||
{
|
||||
actionMap.Disable();
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoInputs] Disabled action map: {mapName}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCallbacksList(OctoActions action)
|
||||
{
|
||||
if (!_actionsDict.TryGetValue(action, out var actionInfo))
|
||||
return;
|
||||
|
||||
var callbackNames = new List<string>();
|
||||
|
||||
foreach (var callback in actionInfo.CallbacksList)
|
||||
{
|
||||
if (callback != null)
|
||||
callbackNames.Add($"{callback.Method.DeclaringType?.Name}.{callback.Method.Name}");
|
||||
}
|
||||
|
||||
foreach (var callback in actionInfo.CallbacksVector2List)
|
||||
{
|
||||
if (callback != null)
|
||||
callbackNames.Add($"{callback.Method.DeclaringType?.Name}.{callback.Method.Name} (Vector2)");
|
||||
}
|
||||
|
||||
actionInfo.Callbacks = callbackNames.ToArray();
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (!_initialized)
|
||||
return;
|
||||
|
||||
// Poll continuous actions (Vector2 callbacks)
|
||||
foreach (var kvp in _actionsDict)
|
||||
{
|
||||
var actionInfo = kvp.Value;
|
||||
|
||||
// Only poll enabled actions with callbacks
|
||||
if (actionInfo.Enabled &&
|
||||
actionInfo.Reference != null &&
|
||||
(actionInfo.CallbacksList.Count > 0 || actionInfo.CallbacksVector2List.Count > 0) &&
|
||||
actionInfo.Reference.action.IsPressed())
|
||||
{
|
||||
// Call Action callbacks
|
||||
foreach (var callback in actionInfo.CallbacksList.ToArray())
|
||||
{
|
||||
callback?.Invoke();
|
||||
}
|
||||
|
||||
// Call Vector2 callbacks
|
||||
foreach (var callback in actionInfo.CallbacksVector2List.ToArray())
|
||||
{
|
||||
Vector2 value = actionInfo.Reference.action.ReadValue<Vector2>();
|
||||
callback?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnSceneChanged(Scene current, Scene next)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnQuit()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (_mappingData != null && _mappingData.InputActionAsset != null)
|
||||
{
|
||||
_mappingData.InputActionAsset.Disable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to an action with no parameters (button press).
|
||||
/// </summary>
|
||||
public void SubscribeAction(OctoActions action, Action callback, bool subscribe = true)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (!_initialized || callback == null)
|
||||
return;
|
||||
|
||||
if (!_actionsDict.TryGetValue(action, out var actionInfo))
|
||||
{
|
||||
Debug.LogWarning($"[OctoInputs] Action {action} not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionInfo.Reference == null)
|
||||
{
|
||||
Debug.LogWarning($"[OctoInputs] InputActionReference is null for {action}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
if (!actionInfo.CallbacksList.Contains(callback))
|
||||
{
|
||||
actionInfo.CallbacksList.Add(callback);
|
||||
|
||||
EnableAction(action);
|
||||
UpdateCallbacksList(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionInfo.CallbacksList.Remove(callback);
|
||||
|
||||
DisableAction(action);
|
||||
UpdateCallbacksList(action);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to an action with Vector2 parameter (movement, look).
|
||||
/// Uses continuous polling in Tick() to call the callback while pressed.
|
||||
/// </summary>
|
||||
public void SubscribeAction(OctoActions action, Action<Vector2> callback, bool subscribe = true)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
if (!_initialized || callback == null)
|
||||
return;
|
||||
|
||||
if (!_actionsDict.TryGetValue(action, out var actionInfo))
|
||||
{
|
||||
Debug.LogWarning($"[OctoInputs] Action {action} not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionInfo.Reference == null)
|
||||
{
|
||||
Debug.LogWarning($"[OctoInputs] InputActionReference is null for {action}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
if (!actionInfo.CallbacksVector2List.Contains(callback))
|
||||
{
|
||||
actionInfo.CallbacksVector2List.Add(callback);
|
||||
|
||||
EnableAction(action);
|
||||
UpdateCallbacksList(action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionInfo.CallbacksVector2List.Remove(callback);
|
||||
|
||||
DisableAction(action);
|
||||
UpdateCallbacksList(action);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from an action.
|
||||
/// </summary>
|
||||
public void UnsubscribeAction(OctoActions action, Action callback)
|
||||
{
|
||||
SubscribeAction(action, callback, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from an action with Vector2.
|
||||
/// </summary>
|
||||
public void UnsubscribeAction(OctoActions action, Action<Vector2> callback)
|
||||
{
|
||||
SubscribeAction(action, callback, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5ddaafd81189174185c64028547631d
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50247461158afe041a87e1db4ab8a806
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e212b1c0974558f40ac2ccaa96db495d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for ceiling tiles.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Ceiling_", menuName = "Octo/Level Builder/Ceiling")]
|
||||
public class OctoLevelBuilderCeilingData : OctoLevelBuilderData
|
||||
{
|
||||
public GameObject Prefab;
|
||||
|
||||
public override GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight)
|
||||
{
|
||||
return Prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 663b230d0aa40fb448ac219c0c7d57f6
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for tile data.
|
||||
/// </summary>
|
||||
public abstract class OctoLevelBuilderData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The referenece grid for the tile.
|
||||
/// </summary>
|
||||
public OctoLevelBuilderGridData Grid;
|
||||
|
||||
/// <summary>
|
||||
/// Get the prefab to instantiate for this tile configuration.
|
||||
/// </summary>
|
||||
public abstract GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8ed7c7ac825b7e49aa3695b5393da29
|
||||
@@ -0,0 +1,30 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for fence tiles.
|
||||
/// Similar to walls but typically lower and more decorative.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Fence_", menuName = "Octo/Level Builder/Fence")]
|
||||
public class OctoLevelBuilderFenceData : OctoLevelBuilderData
|
||||
{
|
||||
[Header("Fence Prefabs")]
|
||||
public GameObject FenceStraight;
|
||||
public GameObject FenceCorner;
|
||||
public GameObject FenceT;
|
||||
public GameObject FenceDeadEnd;
|
||||
|
||||
public override GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight)
|
||||
{
|
||||
return shape switch
|
||||
{
|
||||
WallShape.Straight => FenceStraight,
|
||||
WallShape.Corner => FenceCorner,
|
||||
WallShape.T => FenceT,
|
||||
WallShape.DeadEnd => FenceDeadEnd,
|
||||
_ => FenceStraight
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d87d7d32e3833944be2a3972446de25
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for floor tiles.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Floor_", menuName = "Octo/Level Builder/Floor")]
|
||||
public class OctoLevelBuilderFloorData : OctoLevelBuilderData
|
||||
{
|
||||
public GameObject Prefab;
|
||||
|
||||
public override GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight)
|
||||
{
|
||||
return Prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8e08d3fd3a3b694da03a093f5b375cf
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for grid data.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Grid_", menuName = "Octo/Level Builder/Grid")]
|
||||
public class OctoLevelBuilderGridData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Width en depth of a tile.
|
||||
/// </summary>
|
||||
public float TileSize = 2f;
|
||||
|
||||
/// <summary>
|
||||
/// Floor height.
|
||||
/// </summary>
|
||||
public float FloorHeight = 3.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b13c2498f7c6bd499d3dc1815e1758d
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for stairs tiles.
|
||||
/// Connects two floor levels.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Stairs_", menuName = "Octo/Level Builder/Stairs")]
|
||||
public class OctoLevelBuilderStairsData : OctoLevelBuilderData
|
||||
{
|
||||
[Header("Stairs Prefab")]
|
||||
public GameObject StairsPrefab;
|
||||
|
||||
public override GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight)
|
||||
{
|
||||
return StairsPrefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77641830072b3944882267f440086535
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for grid data.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Style_", menuName = "Octo/Level Builder/Style")]
|
||||
public class OctoLevelBuilderStyleData : ScriptableObject
|
||||
{
|
||||
public OctoLevelBuilderGridData Grid;
|
||||
public OctoLevelBuilderCeilingData Ceiling;
|
||||
public OctoLevelBuilderWallData Wall;
|
||||
public OctoLevelBuilderFloorData Floor;
|
||||
public OctoLevelBuilderStairsData Stairs;
|
||||
public OctoLevelBuilderFenceData Fence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0d5c998a010b2243b41c3eb69285ff5
|
||||
@@ -0,0 +1,36 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for wall tiles.
|
||||
/// Supports different shapes: straight, corner, T-junction, cross, dead-end, door, window.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Wall_", menuName = "Octo/Level Builder/Wall")]
|
||||
public class OctoLevelBuilderWallData : OctoLevelBuilderData
|
||||
{
|
||||
[Header("Wall Prefabs")]
|
||||
public GameObject WallStraight;
|
||||
public GameObject WallCorner;
|
||||
public GameObject WallT;
|
||||
public GameObject WallCross;
|
||||
public GameObject WallDeadEnd;
|
||||
public GameObject WallDoor;
|
||||
public GameObject WallWindow;
|
||||
|
||||
public override GameObject GetPrefab(int angle, WallShape shape = WallShape.Straight)
|
||||
{
|
||||
return shape switch
|
||||
{
|
||||
WallShape.Straight => WallStraight,
|
||||
WallShape.Corner => WallCorner,
|
||||
WallShape.T => WallT,
|
||||
WallShape.Cross => WallCross,
|
||||
WallShape.DeadEnd => WallDeadEnd,
|
||||
WallShape.Door => WallDoor,
|
||||
WallShape.Window => WallWindow,
|
||||
_ => WallStraight
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5eb6603754edde4f85b7713df5381a4
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
public enum TileType
|
||||
{
|
||||
Wall,
|
||||
Floor,
|
||||
Stairs,
|
||||
Fence
|
||||
}
|
||||
|
||||
public enum WallShape
|
||||
{
|
||||
Straight,
|
||||
Corner,
|
||||
T,
|
||||
Cross,
|
||||
DeadEnd,
|
||||
Door,
|
||||
Window
|
||||
}
|
||||
|
||||
public enum WallType
|
||||
{
|
||||
Wall,
|
||||
Door,
|
||||
Window,
|
||||
Fence
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d4bac6e1a95c924db7e2c026cc28e70
|
||||
@@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class OctoLevelBuilderMap : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
private class MapRow
|
||||
{
|
||||
public OctoLevelBuilderTile[] Rows;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class MapColumn
|
||||
{
|
||||
public MapRow[] Columns;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Map
|
||||
{
|
||||
private bool _isMapUpToDate;
|
||||
[SerializeField] private Vector3Int _minBounds;
|
||||
[SerializeField] private Vector3Int _maxBounds;
|
||||
[SerializeField] private Transform _tilesRoot;
|
||||
[SerializeField] private List<OctoLevelBuilderTile> _tiles = new List<OctoLevelBuilderTile>();
|
||||
private List<OctoLevelBuilderTile> _tilesToRefresh = new List<OctoLevelBuilderTile>();
|
||||
[SerializeField] private MapColumn[] _floors = new MapColumn[0];
|
||||
|
||||
public Transform TilesRoot => _tilesRoot;
|
||||
public OctoLevelBuilderTile[] Tiles => _tiles.ToArray();
|
||||
|
||||
public void SetTilesRoot(Transform tilesRoot)
|
||||
{
|
||||
_tilesRoot = tilesRoot;
|
||||
}
|
||||
|
||||
public void RequireMapRefresh()
|
||||
{
|
||||
_isMapUpToDate = false;
|
||||
}
|
||||
|
||||
public void RequireTileRefresh(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (tile && !_tilesToRefresh.Contains(tile))
|
||||
_tilesToRefresh.Add(tile);
|
||||
}
|
||||
|
||||
public void AddTile(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (!_tiles.Contains(tile))
|
||||
{
|
||||
_tiles.Add(tile);
|
||||
RequireMapRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTile(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (_tiles.Contains(tile))
|
||||
{
|
||||
_tiles.Remove(tile);
|
||||
RequireMapRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
public OctoLevelBuilderTile GetTile(Vector3Int position)
|
||||
{
|
||||
Vector3Int p = position - _minBounds;
|
||||
|
||||
if (p.y < 0 || p.y >= _floors.Length)
|
||||
return null;
|
||||
|
||||
if (p.z < 0 || p.z >= _floors[p.y].Columns.Length)
|
||||
return null;
|
||||
|
||||
if (p.x < 0 || p.x >= _floors[p.y].Columns[p.z].Rows.Length)
|
||||
return null;
|
||||
|
||||
return _floors[p.y].Columns[p.z].Rows[p.x];
|
||||
}
|
||||
|
||||
public void RefreshMap(string name)
|
||||
{
|
||||
if (_isMapUpToDate)
|
||||
return;
|
||||
|
||||
_minBounds = new Vector3Int(int.MaxValue, int.MaxValue, int.MaxValue);
|
||||
_maxBounds = new Vector3Int(int.MinValue, int.MinValue, int.MinValue);
|
||||
|
||||
// Get map bounds
|
||||
|
||||
foreach (OctoLevelBuilderTile tile in _tiles)
|
||||
{
|
||||
Vector3Int p = tile.GetCoordinates(true);
|
||||
|
||||
if (p.x < _minBounds.x)
|
||||
_minBounds.x = p.x;
|
||||
if (p.x > _maxBounds.x)
|
||||
_maxBounds.x = p.x;
|
||||
if (p.y < _minBounds.y)
|
||||
_minBounds.y = p.y;
|
||||
if (p.y > _maxBounds.y)
|
||||
_maxBounds.y = p.y;
|
||||
if (p.z < _minBounds.z)
|
||||
_minBounds.z = p.z;
|
||||
if (p.z > _maxBounds.z)
|
||||
_maxBounds.z = p.z;
|
||||
}
|
||||
|
||||
// Copy previous map
|
||||
|
||||
MapColumn[] previousFloors = new MapColumn[_floors.Length];
|
||||
|
||||
for (int y = 0; y < previousFloors.Length; y++)
|
||||
{
|
||||
previousFloors[y] = new MapColumn();
|
||||
previousFloors[y].Columns = new MapRow[_floors[y].Columns.Length];
|
||||
|
||||
for (int z = 0; z < previousFloors[y].Columns.Length; z++)
|
||||
{
|
||||
previousFloors[y].Columns[z] = new MapRow();
|
||||
previousFloors[y].Columns[z].Rows = new OctoLevelBuilderTile[_floors[y].Columns[z].Rows.Length];
|
||||
|
||||
for (int x = 0; x < previousFloors[y].Columns[z].Rows.Length; x++)
|
||||
previousFloors[y].Columns[z].Rows[x] = _floors[y].Columns[z].Rows[x];
|
||||
}
|
||||
}
|
||||
|
||||
// Init map
|
||||
|
||||
Vector3Int size = _maxBounds - _minBounds + Vector3Int.one;
|
||||
_floors = new MapColumn[size.y];
|
||||
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
_floors[y] = new MapColumn();
|
||||
_floors[y].Columns = new MapRow[size.z];
|
||||
|
||||
for (int z = 0; z < size.z; z++)
|
||||
{
|
||||
_floors[y].Columns[z] = new MapRow();
|
||||
_floors[y].Columns[z].Rows = new OctoLevelBuilderTile[size.x];
|
||||
}
|
||||
}
|
||||
|
||||
// Assing tiles to the map
|
||||
|
||||
foreach (OctoLevelBuilderTile tile in _tiles)
|
||||
{
|
||||
Vector3Int p = tile.GetCoordinates(true) - _minBounds;
|
||||
|
||||
_floors[p.y].Columns[p.z].Rows[p.x] = tile;
|
||||
}
|
||||
|
||||
// Refresh tiles
|
||||
|
||||
for (int y = 0; y < size.y; y++)
|
||||
{
|
||||
for (int z = 0; z < size.z; z++)
|
||||
{
|
||||
for (int x = 0; x < size.x; x++)
|
||||
{
|
||||
OctoLevelBuilderTile tile = _floors[y].Columns[z].Rows[x];
|
||||
|
||||
if (tile)
|
||||
{
|
||||
bool refreshTile = !tile.IsUpToDate;
|
||||
|
||||
if (!refreshTile)
|
||||
{
|
||||
OctoLevelBuilderTile previousTile = null;
|
||||
if (previousFloors.Length > y && previousFloors[y].Columns.Length > z && previousFloors[y].Columns[z].Rows.Length > x)
|
||||
previousTile = previousFloors[y].Columns[z].Rows[x];
|
||||
|
||||
refreshTile = tile != previousTile;
|
||||
}
|
||||
|
||||
if (refreshTile)
|
||||
{
|
||||
RequireTileRefresh(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_isMapUpToDate = true;
|
||||
Debug.Log($"[OctoLevelGrid] {name}: Updated");
|
||||
}
|
||||
|
||||
public void RefreshTiles()
|
||||
{
|
||||
if (_tilesToRefresh.Count == 0)
|
||||
return;
|
||||
|
||||
OctoLevelBuilderTile[] _tmpTilesToRefresh = _tilesToRefresh.ToArray();
|
||||
_tilesToRefresh.Clear();
|
||||
|
||||
foreach (OctoLevelBuilderTile tile in _tmpTilesToRefresh)
|
||||
if (tile)
|
||||
tile.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public static float DEFAULT_TILE_SIZE = 2f;
|
||||
public static float DEFAULT_FLOOR_HEIGHT = 3.5f;
|
||||
|
||||
[SerializeField] private OctoLevelBuilderStyleData _defaultStyle;
|
||||
[SerializeField, OctoReadOnly] private Map _map;
|
||||
|
||||
public OctoLevelBuilderStyleData DefaultStyle => _defaultStyle;
|
||||
public OctoLevelBuilderGridData Grid => DefaultStyle ? DefaultStyle.Grid : null;
|
||||
public float TileSize => Grid ? Grid.TileSize : DEFAULT_TILE_SIZE;
|
||||
public float FloorHeight => Grid ? Grid.FloorHeight : DEFAULT_FLOOR_HEIGHT;
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
_map.RefreshTiles();
|
||||
_map.RefreshMap(name);
|
||||
}
|
||||
|
||||
public void SetLayer(string layer)
|
||||
{
|
||||
LayerMask layerMask = LayerMask.NameToLayer(layer);
|
||||
SetLayerRecursively(gameObject, layerMask);
|
||||
}
|
||||
|
||||
public void RegisterTile(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (tile == null)
|
||||
return;
|
||||
|
||||
_map.AddTile(tile);
|
||||
}
|
||||
|
||||
public void UnregisterTile(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (tile == null)
|
||||
return;
|
||||
|
||||
_map.RemoveTile(tile);
|
||||
}
|
||||
|
||||
public void RequireMapRefresh()
|
||||
{
|
||||
_map.RequireMapRefresh();
|
||||
}
|
||||
|
||||
public void RequireTileRefresh(OctoLevelBuilderTile tile)
|
||||
{
|
||||
if (tile)
|
||||
_map.RequireTileRefresh(tile);
|
||||
}
|
||||
|
||||
public bool GetTile(Vector3Int gridPosition, out OctoLevelBuilderTile tile)
|
||||
{
|
||||
tile = _map.GetTile(gridPosition);
|
||||
return tile;
|
||||
}
|
||||
|
||||
public Vector3 GridToWorld(Vector3Int gridPosition)
|
||||
{
|
||||
return new Vector3(
|
||||
gridPosition.x * TileSize,
|
||||
gridPosition.y * FloorHeight,
|
||||
gridPosition.z * TileSize
|
||||
);
|
||||
}
|
||||
|
||||
public Vector3Int WorldToGrid(Vector3 worldPosition, int floorLevel)
|
||||
{
|
||||
return new Vector3Int(
|
||||
Mathf.RoundToInt(worldPosition.x / TileSize),
|
||||
floorLevel,
|
||||
Mathf.RoundToInt(worldPosition.z / TileSize)
|
||||
);
|
||||
}
|
||||
|
||||
[ContextMenu("GenerateMap (5x5)")] public void GenerateMap5x5() { GenerateMap(new Vector3Int(5, 1, 5)); }
|
||||
[ContextMenu("GenerateMap (7x7)")] public void GenerateMap7x7() { GenerateMap(new Vector3Int(7, 1, 7)); }
|
||||
[ContextMenu("GenerateMap (9x9)")] public void GenerateMap9x9() { GenerateMap(new Vector3Int(9, 1, 9)); }
|
||||
public void GenerateMap(Vector3Int size)
|
||||
{
|
||||
if (!_map.TilesRoot)
|
||||
{
|
||||
GameObject tilesRoot = new GameObject("Tiles");
|
||||
tilesRoot.transform.parent = transform;
|
||||
|
||||
_map.SetTilesRoot(tilesRoot.transform);
|
||||
}
|
||||
|
||||
int xMin = size.x / -2;
|
||||
int yMin = size.y / -2;
|
||||
int zMin = size.z / -2;
|
||||
|
||||
int xMax = xMin + size.x;
|
||||
int yMax = yMin + size.y;
|
||||
int zMax = zMin + size.z;
|
||||
|
||||
for (int y = yMin; y < yMax; y++)
|
||||
{
|
||||
for (int z = zMin; z < zMax; z++)
|
||||
{
|
||||
for (int x = xMin; x < xMax; x++)
|
||||
{
|
||||
GameObject tileObj = new GameObject();
|
||||
tileObj.transform.parent = _map.TilesRoot;
|
||||
tileObj.transform.localPosition = new Vector3(x * TileSize, y * FloorHeight, z * TileSize);
|
||||
|
||||
OctoLevelBuilderTile tile = tileObj.AddComponent<OctoLevelBuilderTile>();
|
||||
tile.Init(x == xMin || x == xMax - 1 || z == zMin || z == zMax - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("[OctoLevelBluiderMap] Map generated");
|
||||
}
|
||||
|
||||
private void SetLayerRecursively(GameObject obj, int newLayer)
|
||||
{
|
||||
obj.layer = newLayer;
|
||||
|
||||
foreach (Transform child in obj.transform)
|
||||
{
|
||||
SetLayerRecursively(child.gameObject, newLayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 078c608cc8fc63b40beb970edf317e56
|
||||
@@ -0,0 +1,614 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.LevelBuilder
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class OctoLevelBuilderTile : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
private class Configuration
|
||||
{
|
||||
public OctoLevelBuilderStyleData _overrideStyle;
|
||||
public bool Ceiling;
|
||||
public bool Wall;
|
||||
public bool Floor;
|
||||
public bool Stairs;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class References
|
||||
{
|
||||
[OctoReadOnly] public OctoLevelBuilderMap Map;
|
||||
[OctoReadOnly] public GameObject CeilingInstance;
|
||||
[OctoReadOnly] public GameObject WallInstance;
|
||||
[OctoReadOnly] public GameObject FloorInstance;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class WallConnections
|
||||
{
|
||||
public OctoLevelBuilderTile North;
|
||||
public OctoLevelBuilderTile Est;
|
||||
public OctoLevelBuilderTile South;
|
||||
public OctoLevelBuilderTile West;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class WallConfiguration
|
||||
{
|
||||
public bool AutoConnect = true;
|
||||
[Range(0, 3)] public int Orientation = 0;
|
||||
public WallType Type = WallType.Wall;
|
||||
public WallShape Shape = WallShape.Straight;
|
||||
[OctoReadOnly] public WallConnections Connections = new WallConnections();
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class Infos
|
||||
{
|
||||
public Vector3Int Coordinates;
|
||||
public Vector3 Position;
|
||||
}
|
||||
|
||||
[SerializeField] private Configuration _configuration = new Configuration();
|
||||
[SerializeField] private WallConfiguration _wall = new WallConfiguration();
|
||||
[SerializeField] private References _references = new References();
|
||||
[SerializeField, OctoReadOnly] private Infos _infos = new Infos();
|
||||
|
||||
private bool _isMapUpToDate;
|
||||
|
||||
public bool IsUpToDate => _isMapUpToDate;
|
||||
public bool HasWall => _configuration.Wall;
|
||||
public bool HasFloor => _configuration.Floor;
|
||||
public bool HasCeiling => _configuration.Ceiling;
|
||||
public bool HasStairs => _configuration.Stairs;
|
||||
public float TileSize => Map ? Map.TileSize : OctoLevelBuilderMap.DEFAULT_TILE_SIZE;
|
||||
public float FloorHeight => Map ? Map.FloorHeight : OctoLevelBuilderMap.DEFAULT_FLOOR_HEIGHT;
|
||||
public OctoLevelBuilderMap Map => _references.Map;
|
||||
public OctoLevelBuilderStyleData Style => _configuration._overrideStyle ? _configuration._overrideStyle : _references.Map ? _references.Map.DefaultStyle : null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
UnityEditor.EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (this != null)
|
||||
RequireMapRefresh();
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
RegisterToMap();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
UnregisterFromMap();
|
||||
//DestroyInstance(_wallInstance);
|
||||
//DestroyInstance(_floorInstance);
|
||||
//DestroyInstance(_ceilingInstance);
|
||||
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.North);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.Est);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.South);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.West);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
DrawTileGizmo(new Color(0, 1, 0, 0.2f));
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
DrawTileGizmo(new Color(0, 1, 0, 0.5f));
|
||||
}
|
||||
|
||||
private void DrawTileGizmo(Color color)
|
||||
{
|
||||
float tileSize = Map ? Map.TileSize : 2;
|
||||
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawCube(Vector3.up * tileSize * .025f, new Vector3(tileSize * .2f, tileSize * .05f, tileSize * .2f));
|
||||
|
||||
/*if (!HasWall())
|
||||
{
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawCube(transform.position + Vector3.up * 0.5f, new Vector3(1.8f, 1f, 1.8f));
|
||||
return;
|
||||
}
|
||||
|
||||
float rotation = Placement.Angle * -90f;
|
||||
if (Wall.Shape == WallShape.Straight)
|
||||
rotation += 90f;
|
||||
|
||||
Matrix4x4 oldMatrix = Gizmos.matrix;
|
||||
Gizmos.matrix = Matrix4x4.TRS(transform.position + Vector3.up * 1.5f, Quaternion.Euler(0, rotation, 0), Vector3.one);
|
||||
Gizmos.color = color;
|
||||
|
||||
float size = 2f;
|
||||
float thickness = 0.3f;
|
||||
float height = 3f;
|
||||
|
||||
switch (Wall.Shape)
|
||||
{
|
||||
case WallShape.Straight:
|
||||
Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness));
|
||||
break;
|
||||
|
||||
case WallShape.Corner:
|
||||
Vector3 arm1Pos = new Vector3(size / 4f, 0, 0);
|
||||
Vector3 arm2Pos = new Vector3(0, 0, size / 4f);
|
||||
Gizmos.DrawCube(arm1Pos, new Vector3(size / 2f, height, thickness));
|
||||
Gizmos.DrawCube(arm2Pos, new Vector3(thickness, height, size / 2f));
|
||||
break;
|
||||
|
||||
case WallShape.T:
|
||||
Vector3 basePos = new Vector3(0, 0, 0);
|
||||
Vector3 crossPos = new Vector3(0, 0, size / 4f);
|
||||
Gizmos.DrawCube(basePos, new Vector3(size, height, thickness));
|
||||
Gizmos.DrawCube(crossPos, new Vector3(thickness, height, size / 2f));
|
||||
break;
|
||||
|
||||
case WallShape.Cross:
|
||||
Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness));
|
||||
Gizmos.DrawCube(Vector3.zero, new Vector3(thickness, height, size));
|
||||
break;
|
||||
|
||||
case WallShape.DeadEnd:
|
||||
Vector3 deadEndPos = new Vector3(0, 0, size / 4f);
|
||||
Gizmos.DrawCube(deadEndPos, new Vector3(thickness, height, size / 2f));
|
||||
break;
|
||||
|
||||
case WallShape.Door:
|
||||
case WallShape.Window:
|
||||
Gizmos.DrawCube(Vector3.zero, new Vector3(size, height, thickness));
|
||||
break;
|
||||
|
||||
default:
|
||||
Gizmos.DrawCube(Vector3.zero, new Vector3(size * 0.5f, height, size * 0.5f));
|
||||
break;
|
||||
}
|
||||
|
||||
Gizmos.matrix = oldMatrix;*/
|
||||
}
|
||||
#endif
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_infos.Position != transform.localPosition)
|
||||
{
|
||||
_infos.Position = transform.localPosition;
|
||||
|
||||
SetName();
|
||||
RequireMapRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
public void Init(bool wall)
|
||||
{
|
||||
Debug.Log(wall);
|
||||
SetName();
|
||||
|
||||
_configuration.Ceiling = true;
|
||||
_configuration.Wall = wall;
|
||||
_configuration.Floor = true;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
SetConnections();
|
||||
ClearChildren();
|
||||
RefreshWall();
|
||||
RefreshFloor();
|
||||
RefreshCeiling();
|
||||
|
||||
_isMapUpToDate = true;
|
||||
//Debug.Log($"[OctoTile] {name}: Updated");
|
||||
}
|
||||
|
||||
public Vector3Int GetCoordinates(bool update = false)
|
||||
{
|
||||
if (update)
|
||||
SetCoordinates();
|
||||
|
||||
return _infos.Coordinates;
|
||||
}
|
||||
|
||||
private void SetCoordinates()
|
||||
{
|
||||
Vector3 pos = transform.position;
|
||||
|
||||
_infos.Coordinates = new Vector3Int(
|
||||
Mathf.RoundToInt(pos.x / TileSize),
|
||||
Mathf.RoundToInt(pos.y / FloorHeight),
|
||||
Mathf.RoundToInt(pos.z / TileSize)
|
||||
);
|
||||
}
|
||||
|
||||
public void SetConnections()
|
||||
{
|
||||
GetMap();
|
||||
if (!_references.Map)
|
||||
return;
|
||||
|
||||
// Require update for previous connections
|
||||
|
||||
OctoLevelBuilderTile previousNorth = _wall.Connections.North;
|
||||
OctoLevelBuilderTile previousEst = _wall.Connections.Est;
|
||||
OctoLevelBuilderTile previousSouth = _wall.Connections.South;
|
||||
OctoLevelBuilderTile previousWest = _wall.Connections.West;
|
||||
|
||||
// Get new connections
|
||||
|
||||
OctoLevelBuilderTile newNorth = GetNextTile(Vector3Int.forward);
|
||||
OctoLevelBuilderTile newEst = GetNextTile(Vector3Int.right );
|
||||
OctoLevelBuilderTile newSouth = GetNextTile(Vector3Int.back );
|
||||
OctoLevelBuilderTile newWest = GetNextTile(Vector3Int.left );
|
||||
|
||||
_wall.Connections.North = newNorth && newNorth.HasWall ? newNorth : null;
|
||||
_wall.Connections.Est = newEst && newEst .HasWall ? newEst : null;
|
||||
_wall.Connections.South = newSouth && newSouth.HasWall ? newSouth : null;
|
||||
_wall.Connections.West = newWest && newWest .HasWall ? newWest : null;
|
||||
|
||||
bool wallChanged = HasWall != (_references.WallInstance ? true : false);
|
||||
|
||||
// Require update for new connections
|
||||
|
||||
if (_wall.Connections.North != previousNorth || wallChanged)
|
||||
{
|
||||
Debug.Log(_wall.Connections.North);
|
||||
Debug.Log(previousNorth);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.North);
|
||||
_references.Map.RequireTileRefresh(previousNorth);
|
||||
}
|
||||
if (_wall.Connections.Est != previousEst || wallChanged)
|
||||
{
|
||||
Debug.Log(_wall.Connections.Est);
|
||||
Debug.Log(previousEst);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.Est);
|
||||
_references.Map.RequireTileRefresh(previousEst);
|
||||
}
|
||||
if (_wall.Connections.South != previousSouth || wallChanged)
|
||||
{
|
||||
Debug.Log(_wall.Connections.South);
|
||||
Debug.Log(previousSouth);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.South);
|
||||
_references.Map.RequireTileRefresh(previousSouth);
|
||||
}
|
||||
if (_wall.Connections.West != previousWest || wallChanged)
|
||||
{
|
||||
Debug.Log(_wall.Connections.West);
|
||||
Debug.Log(previousWest);
|
||||
_references.Map.RequireTileRefresh(_wall.Connections.West);
|
||||
_references.Map.RequireTileRefresh(previousWest);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshShape()
|
||||
{
|
||||
if (!HasWall || !_wall.AutoConnect)
|
||||
return;
|
||||
|
||||
int connectionHash = (_wall.Connections.North ? 1000 : 0) + (_wall.Connections.Est ? 100 : 0) + (_wall.Connections.South ? 10 : 0) + (_wall.Connections.West ? 1 : 0);
|
||||
int connectionCount = (_wall.Connections.North ? 1 : 0) + (_wall.Connections.Est ? 1 : 0) + (_wall.Connections.South ? 1 : 0) + (_wall.Connections.West ? 1 : 0);
|
||||
|
||||
switch (connectionCount)
|
||||
{
|
||||
default:
|
||||
_wall.Shape = WallShape.DeadEnd;
|
||||
switch(connectionHash)
|
||||
{
|
||||
case 0010:
|
||||
_wall.Orientation = 0;
|
||||
break;
|
||||
case 0001:
|
||||
_wall.Orientation = 1;
|
||||
break;
|
||||
case 1000:
|
||||
_wall.Orientation = 2;
|
||||
break;
|
||||
case 0100:
|
||||
_wall.Orientation = 3;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if ((_wall.Connections.North && _wall.Connections.South) || (_wall.Connections.Est && _wall.Connections.West))
|
||||
{
|
||||
_wall.Shape = WallShape.Straight;
|
||||
switch (connectionHash)
|
||||
{
|
||||
case 0101:
|
||||
_wall.Orientation = 0;
|
||||
break;
|
||||
case 1010:
|
||||
_wall.Orientation = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_wall.Shape = WallShape.Corner;
|
||||
switch (connectionHash)
|
||||
{
|
||||
case 1001:
|
||||
_wall.Orientation = 0;
|
||||
break;
|
||||
case 1100:
|
||||
_wall.Orientation = 1;
|
||||
break;
|
||||
case 0110:
|
||||
_wall.Orientation = 2;
|
||||
break;
|
||||
case 0011:
|
||||
_wall.Orientation = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
_wall.Shape = WallShape.T;
|
||||
switch (connectionHash)
|
||||
{
|
||||
case 1101:
|
||||
_wall.Orientation = 0;
|
||||
break;
|
||||
case 1110:
|
||||
_wall.Orientation = 1;
|
||||
break;
|
||||
case 0111:
|
||||
_wall.Orientation = 2;
|
||||
break;
|
||||
case 1011:
|
||||
_wall.Orientation = 3;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
_wall.Shape = WallShape.Cross;
|
||||
_wall.Orientation = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private OctoLevelBuilderTile GetNextTile(Vector3Int direction)
|
||||
{
|
||||
Vector3Int nextCoordinates = _infos.Coordinates + direction;
|
||||
|
||||
if (!_references.Map)
|
||||
{
|
||||
Debug.LogWarning("[OctoTile] No Map found");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_references.Map.GetTile(nextCoordinates, out OctoLevelBuilderTile nexTile))
|
||||
return nexTile;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ClearChildren()
|
||||
{
|
||||
for (int i = transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
GameObject instance = transform.GetChild(i).gameObject;
|
||||
|
||||
if (instance != _references.CeilingInstance &&
|
||||
instance != _references.WallInstance &&
|
||||
instance != _references.FloorInstance)
|
||||
{
|
||||
DestroyInstance(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshWall()
|
||||
{
|
||||
if (!HasWall)
|
||||
{
|
||||
DestroyInstance(_references.WallInstance);
|
||||
_references.WallInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshShape();
|
||||
|
||||
if (!Style)
|
||||
return;
|
||||
|
||||
GameObject prefab = GetWallPrefab(Style.Wall, _wall.Shape, _wall.Type);
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DestroyInstance(_references.WallInstance);
|
||||
_references.WallInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInstanceCorrespondingToPrefab(_references.WallInstance, prefab))
|
||||
{
|
||||
DestroyInstance(_references.WallInstance);
|
||||
_references.WallInstance = InstantiatePrefab(prefab, "Wall");
|
||||
}
|
||||
|
||||
if (_references.WallInstance != null)
|
||||
{
|
||||
float angle = _wall.Orientation * 90f;
|
||||
_references.WallInstance.transform.localRotation = Quaternion.Euler(0, angle, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject GetWallPrefab(OctoLevelBuilderWallData wallData, WallShape shape, WallType type)
|
||||
{
|
||||
if (type == WallType.Door)
|
||||
return wallData.WallDoor;
|
||||
if (type == WallType.Window)
|
||||
return wallData.WallWindow;
|
||||
|
||||
return wallData.GetPrefab(_wall.Orientation, shape);
|
||||
}
|
||||
|
||||
private void RefreshFloor()
|
||||
{
|
||||
if (!HasFloor)
|
||||
{
|
||||
DestroyInstance(_references.FloorInstance);
|
||||
_references.FloorInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Style)
|
||||
return;
|
||||
|
||||
GameObject prefab = Style.Floor.Prefab;
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DestroyInstance(_references.FloorInstance);
|
||||
_references.FloorInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInstanceCorrespondingToPrefab(_references.FloorInstance, prefab))
|
||||
{
|
||||
DestroyInstance(_references.FloorInstance);
|
||||
_references.FloorInstance = InstantiatePrefab(prefab, "Floor");
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshCeiling()
|
||||
{
|
||||
if (!HasCeiling)
|
||||
{
|
||||
DestroyInstance(_references.CeilingInstance);
|
||||
_references.CeilingInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Style)
|
||||
return;
|
||||
|
||||
GameObject prefab = Style.Ceiling.Prefab;
|
||||
|
||||
if (prefab == null)
|
||||
{
|
||||
DestroyInstance(_references.CeilingInstance);
|
||||
_references.CeilingInstance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInstanceCorrespondingToPrefab(_references.CeilingInstance, prefab))
|
||||
{
|
||||
DestroyInstance(_references.CeilingInstance);
|
||||
_references.CeilingInstance = InstantiatePrefab(prefab, "Ceiling");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInstanceCorrespondingToPrefab(GameObject instance, GameObject prefab)
|
||||
{
|
||||
if (instance == null)
|
||||
return true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
GameObject source = (GameObject)UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(instance);
|
||||
return source != prefab;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void DestroyInstance(GameObject instance)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
DestroyImmediate(instance);
|
||||
else
|
||||
#endif
|
||||
Destroy(instance);
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject InstantiatePrefab(GameObject prefab, string childName)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
GameObject instance = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefab, transform);
|
||||
SceneVisibilityManager.instance.DisablePicking(instance, true);
|
||||
#else
|
||||
GameObject instance = Instantiate(prefab, transform);
|
||||
#endif
|
||||
instance.name = childName;
|
||||
instance.transform.localPosition = Vector3.zero;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void GetMap()
|
||||
{
|
||||
if (!_references.Map)
|
||||
_references.Map = GetComponentInParent<OctoLevelBuilderMap>();
|
||||
}
|
||||
|
||||
private bool GetMap(out OctoLevelBuilderMap map)
|
||||
{
|
||||
GetMap();
|
||||
map = _references.Map;
|
||||
return map;
|
||||
}
|
||||
|
||||
private void RequireMapRefresh()
|
||||
{
|
||||
_isMapUpToDate = false;
|
||||
if (GetMap(out OctoLevelBuilderMap map))
|
||||
map.RequireMapRefresh();
|
||||
}
|
||||
|
||||
private void RegisterToMap()
|
||||
{
|
||||
if (GetMap(out OctoLevelBuilderMap map))
|
||||
map.RegisterTile(this);
|
||||
}
|
||||
|
||||
private void UnregisterFromMap()
|
||||
{
|
||||
if (GetMap(out OctoLevelBuilderMap map))
|
||||
map.UnregisterTile(this);
|
||||
}
|
||||
|
||||
private void SetName()
|
||||
{
|
||||
int x = _infos.Coordinates.x;
|
||||
int y = _infos.Coordinates.z;
|
||||
|
||||
//string tmpName = "Tile " + (x < 0 ? '-' : '+') + Mathf.Abs(x).ToString().PadLeft(3, '0') + " " + (y < 0 ? '-' : '+') + Mathf.Abs(y).ToString().PadLeft(3, '0');
|
||||
string tmpName = "Tile_" + transform.GetSiblingIndex().ToString().PadLeft(3, '0');
|
||||
|
||||
if (name != tmpName)
|
||||
name = tmpName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52265b52f92cb4846aa9f736eb80cfe1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10432775657503847bbbaae7825d4195
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace OctoCore.Localization
|
||||
{
|
||||
public enum OctoLanguages {
|
||||
English,
|
||||
French,
|
||||
//Spanish,
|
||||
//German,
|
||||
//Chinese,
|
||||
//Japanese,
|
||||
//Korean,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f177e44a5bc17824ab1fa6e1d4b1158c
|
||||
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace OctoCore.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// Localization module for managing text and audio languages.
|
||||
/// Handles language switching and provides localized text retrieval.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoLocalization : IOctoModule
|
||||
{
|
||||
public OctoLanguages WrittenLanguage;
|
||||
public OctoLanguages AudioLanguage;
|
||||
|
||||
private Action OnLanguageWrittenChange;
|
||||
private Action OnLanguageAudioChange;
|
||||
|
||||
private static Dictionary<OctoLanguages, string> _languagesKey = new Dictionary<OctoLanguages, string>();
|
||||
|
||||
public void Init(OctoSettings settings)
|
||||
{
|
||||
InitLanguagesKey();
|
||||
|
||||
// Load written language
|
||||
|
||||
if (settings.Languages.SaveWritten)
|
||||
{
|
||||
object language;
|
||||
Enum.TryParse(typeof(OctoLanguages), OctoCore.Instance.Save.Get(settings.Languages.SaveWritten), true, out language);
|
||||
if (language == null)
|
||||
{
|
||||
language = settings.Languages.Default;
|
||||
Debug.Log($"[OctoLocalization] Default language settled: {language.ToString()}");
|
||||
}
|
||||
|
||||
OctoCore.Instance.Localization.SetLanguageWritten((OctoLanguages)language);
|
||||
}
|
||||
|
||||
// Load audio language
|
||||
|
||||
if (settings.Languages.SaveAudio)
|
||||
{
|
||||
object language;
|
||||
Enum.TryParse(typeof(OctoLanguages), OctoCore.Instance.Save.Get(settings.Languages.SaveAudio), true, out language);
|
||||
if (language == null)
|
||||
language = settings.Languages.Default;
|
||||
|
||||
OctoCore.Instance.Localization.SetLanguageAudio((OctoLanguages)language);
|
||||
}
|
||||
|
||||
// Callback OctoEvents
|
||||
|
||||
if (OctoEvents.Instance)
|
||||
OctoEvents.Instance.SetLocalizationInitialized();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnSceneChanged(Scene current, Scene next)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnQuit()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetLanguageWritten(OctoLanguages language) { SetLanguage(language, false); }
|
||||
|
||||
public void SetLanguageAudio(OctoLanguages language) { SetLanguage(language, true); }
|
||||
|
||||
private void SetLanguage(OctoLanguages language, bool audio = false)
|
||||
{
|
||||
if (audio)
|
||||
{
|
||||
OctoCore.Instance.Localization.AudioLanguage = language;
|
||||
OctoCore.Instance.Save.Set(OctoCore.Instance.Settings.Languages.SaveAudio, language.ToString());
|
||||
OnLanguageAudioChange?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
OctoCore.Instance.Localization.WrittenLanguage = language;
|
||||
OctoCore.Instance.Save.Set(OctoCore.Instance.Settings.Languages.SaveWritten, language.ToString());
|
||||
OnLanguageWrittenChange?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnsubscribeOnLanguageWrittenChange(Action handler)
|
||||
{
|
||||
SubscribeOnLanguageWrittenChange(handler, false);
|
||||
}
|
||||
|
||||
public void SubscribeOnLanguageWrittenChange(Action handler, bool subscribe = true)
|
||||
{
|
||||
if (subscribe) OnLanguageWrittenChange += handler;
|
||||
else OnLanguageWrittenChange -= handler;
|
||||
}
|
||||
|
||||
private static void InitLanguagesKey()
|
||||
{
|
||||
foreach (OctoLanguages lang in Enum.GetValues(typeof(OctoLanguages)))
|
||||
_languagesKey[lang] = lang.ToString();
|
||||
}
|
||||
|
||||
public static string GetLocalizedText(OctoLocalizedTextData data, bool audio = false)
|
||||
{
|
||||
OctoLanguages language = 0;
|
||||
if (OctoCore.Instance)
|
||||
language = audio ? OctoCore.Instance.Localization.AudioLanguage : OctoCore.Instance.Localization.WrittenLanguage;
|
||||
|
||||
return GetLocalizedText(data, language);
|
||||
}
|
||||
|
||||
public static string GetLocalizedText(OctoLocalizedTextData data, OctoLanguages language)
|
||||
{
|
||||
if (data == null || data.Languages == null)
|
||||
{
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoLocalization] Data is null");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var dict = data.Languages.Dictionary;
|
||||
|
||||
if (_languagesKey.Count == 0 || !_languagesKey.ContainsKey(language))
|
||||
InitLanguagesKey();
|
||||
|
||||
if (dict != null)
|
||||
{
|
||||
if (dict.TryGetValue(_languagesKey[language], out string localizedText) && !string.IsNullOrEmpty(localizedText))
|
||||
{
|
||||
return localizedText;
|
||||
}
|
||||
|
||||
// Use default if language not found or empty
|
||||
|
||||
if (dict.TryGetValue(_languagesKey[OctoCore.Instance.Settings.Languages.Default], out string fallbackText) && !string.IsNullOrEmpty(fallbackText))
|
||||
{
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoLocalization] Use default language for: {data.name}");
|
||||
|
||||
return fallbackText;
|
||||
}
|
||||
}
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoLocalization] Empty text for: {data.name}");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01370154729dc1344af3e501de37cdcb
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Localization
|
||||
{
|
||||
public class OctoLocalizationProxy : MonoBehaviour
|
||||
{
|
||||
public void SetLanguageWritten(string language)
|
||||
{
|
||||
if (!OctoCore.Instance) return;
|
||||
|
||||
object value;
|
||||
Enum.TryParse(typeof(OctoLanguages), language, true, out value);
|
||||
if (value == null)
|
||||
value = OctoCore.Instance.Settings.Languages.Default;
|
||||
|
||||
OctoCore.Instance.Localization.SetLanguageWritten((OctoLanguages)value);
|
||||
}
|
||||
|
||||
public void SetLanguageAudio(string language)
|
||||
{
|
||||
if (!OctoCore.Instance) return;
|
||||
|
||||
object value;
|
||||
Enum.TryParse(typeof(OctoLanguages), language, true, out value);
|
||||
if (value == null)
|
||||
value = OctoCore.Instance.Settings.Languages.Default;
|
||||
|
||||
OctoCore.Instance.Localization.SetLanguageAudio((OctoLanguages)value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25b90d3d1039b994aa7a8950d3a9b046
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace OctoCore.Localization
|
||||
{
|
||||
public class OctoLocalizedText : MonoBehaviour
|
||||
{
|
||||
[SerializeField] OctoLocalizedTextData data;
|
||||
[SerializeField] Text text;
|
||||
[SerializeField] TextMeshPro textMeshPro;
|
||||
[SerializeField] TextMeshProUGUI textMeshProUGUI;
|
||||
|
||||
Action localize;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
bool setDirty = false;
|
||||
if (!text && !textMeshPro && !textMeshProUGUI)
|
||||
{
|
||||
text = GetComponent<Text>();
|
||||
textMeshPro = GetComponent<TextMeshPro>();
|
||||
textMeshProUGUI = GetComponent<TextMeshProUGUI>();
|
||||
|
||||
if (text || textMeshPro || textMeshProUGUI)
|
||||
setDirty = true;
|
||||
}
|
||||
|
||||
if (text && text.transform != transform)
|
||||
{
|
||||
text = null;
|
||||
setDirty = true;
|
||||
}
|
||||
|
||||
if (textMeshPro && textMeshPro.transform != transform)
|
||||
{
|
||||
textMeshPro = null;
|
||||
setDirty = true;
|
||||
}
|
||||
|
||||
if (textMeshProUGUI && textMeshProUGUI.transform != transform)
|
||||
{
|
||||
textMeshProUGUI = null;
|
||||
setDirty = true;
|
||||
}
|
||||
|
||||
if (setDirty)
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
|
||||
Localize();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!OctoCore.Instance)
|
||||
{
|
||||
Debug.LogWarning($"[OctoLocalizedText] OctoCore.Instance not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Localize();
|
||||
OctoCore.Instance.Localization.SubscribeOnLanguageWrittenChange(localize = Localize);
|
||||
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"{name} subscribed on language written change");
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (!OctoCore.Instance) return;
|
||||
|
||||
if (localize != null)
|
||||
{
|
||||
OctoCore.Instance.Localization.UnsubscribeOnLanguageWrittenChange(localize);
|
||||
localize = null;
|
||||
|
||||
if (OctoCore.Instance.Development.Log && data)
|
||||
Debug.Log($"{name} unsubscribed on language written change");
|
||||
}
|
||||
}
|
||||
|
||||
public void Localize()
|
||||
{
|
||||
if (!data) return;
|
||||
|
||||
string localizedText = OctoLocalization.GetLocalizedText(data);
|
||||
|
||||
if (text)
|
||||
{
|
||||
text.text = localizedText;
|
||||
}
|
||||
else if (textMeshPro)
|
||||
{
|
||||
textMeshPro.text = localizedText;
|
||||
}
|
||||
else if (textMeshProUGUI)
|
||||
{
|
||||
textMeshProUGUI.text = localizedText;
|
||||
//LayoutRebuilder.ForceRebuildLayoutImmediate(textMeshProUGUI.rectTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1023fdbb836c50f48bd4b52575ed3689
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using OctoCore.Save;
|
||||
|
||||
namespace OctoCore.Localization
|
||||
{
|
||||
[CreateAssetMenu(fileName = "text_", menuName = "Octo/Localization/Text", order = 0)]
|
||||
public class OctoLocalizedTextData : ScriptableObject
|
||||
{
|
||||
public OctoSerializableDictionary<string, string> Languages = new OctoSerializableDictionary<string, string>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
bool modified = false;
|
||||
var oldValues = new Dictionary<string, string>(Languages.Dictionary);
|
||||
|
||||
var newDict = new Dictionary<string, string>();
|
||||
|
||||
foreach (string lang in Enum.GetNames(typeof(OctoLanguages)))
|
||||
{
|
||||
if (oldValues.TryGetValue(lang, out string oldText))
|
||||
{
|
||||
newDict[lang] = oldText;
|
||||
}
|
||||
else
|
||||
{
|
||||
newDict[lang] = string.Empty;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
Languages.Dictionary.Clear();
|
||||
foreach (var kvp in newDict)
|
||||
Languages.Dictionary[kvp.Key] = kvp.Value;
|
||||
|
||||
if (modified)
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbde7b14ca8fbf24c917c47aa3c51676
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aeff222c1618a874d9395521097cdf22
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d2ab646438037043aad58a513ed2e03
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Navigation.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene configuration data.
|
||||
/// Contains all settings for a specific scene.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "scene_", menuName = "Octo/Scenes/Scene Data", order = 0)]
|
||||
public class OctoSceneData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this scene.
|
||||
/// Automatically set to the asset name.
|
||||
/// </summary>
|
||||
[OctoReadOnly] public string Id;
|
||||
|
||||
/// <summary>
|
||||
/// Scene name in Build Settings or Addressables.
|
||||
/// </summary>
|
||||
[Tooltip("Scene name in Build Settings or Addressables")]
|
||||
public string SceneName;
|
||||
|
||||
/// <summary>
|
||||
/// Enable fade transition when loading this scene.
|
||||
/// </summary>
|
||||
[Tooltip("Enable fade transition")]
|
||||
public bool UseFadeTransition = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
Id = name;
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1f04313388f6a846a9e2a6bfe6e985a
|
||||
@@ -0,0 +1,44 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace OctoCore.Navigation.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Teleporter configuration data.
|
||||
/// Defines teleportation behavior between locations or scenes.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "teleporter_", menuName = "Octo/Scenes/Teleporter Data", order = 1)]
|
||||
public class OctoTeleporterData : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this teleporter.
|
||||
/// Automatically set to the asset name.
|
||||
/// </summary>
|
||||
[OctoReadOnly] public string Id;
|
||||
|
||||
/// <summary>
|
||||
/// Target scene to load (null = stay in current scene).
|
||||
/// </summary>
|
||||
[Tooltip("Target scene (null = same scene)")]
|
||||
public OctoSceneData TargetScene;
|
||||
|
||||
/// <summary>
|
||||
/// Set player rotation to match destination teleporter orientation.
|
||||
/// </summary>
|
||||
[Tooltip("Player orientation after teleport")]
|
||||
public bool SetPlayerRotation = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable fade transition during teleport.
|
||||
/// </summary>
|
||||
[Tooltip("Enable fade transition")]
|
||||
public bool UseFadeTransition = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
Id = name;
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01c67a8f36397f441b79c801b74560f1
|
||||
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using OctoCore.Navigation.Data;
|
||||
|
||||
#if OCTO_ADDRESSABLES
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||
#endif
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene management module.
|
||||
/// Handles scene loading, transitions, and teleportation.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class OctoNavigation : IOctoModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently loaded scene data.
|
||||
/// </summary>
|
||||
[OctoReadOnly] public OctoSceneData CurrentScene;
|
||||
|
||||
/// <summary>
|
||||
/// Pending teleporter between scene changes (non-null only during scene transitions).
|
||||
/// </summary>
|
||||
[OctoReadOnly] public OctoTeleporterData PendingTeleporter;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when scene loading starts.
|
||||
/// </summary>
|
||||
public Action<OctoSceneData> OnSceneLoadStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when scene loading completes.
|
||||
/// </summary>
|
||||
public Action<OctoSceneData> OnSceneLoadCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when teleport starts.
|
||||
/// </summary>
|
||||
public Action<OctoTeleporterData, OctoTeleporterData> OnTeleportStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when teleport completes.
|
||||
/// </summary>
|
||||
public Action<OctoTeleporterData, OctoTeleporterData> OnTeleportCompleted;
|
||||
|
||||
private Coroutine _loadingCoroutine;
|
||||
private OctoSceneFade _fadeCanvas;
|
||||
private bool _setPlayerRotation;
|
||||
|
||||
public void Init(OctoSettings settings)
|
||||
{
|
||||
// Find current scene in settings
|
||||
string currentSceneName = SceneManager.GetActiveScene().name;
|
||||
foreach (var sceneData in settings.Scenes.SceneDatas)
|
||||
{
|
||||
if (sceneData.SceneName == currentSceneName)
|
||||
{
|
||||
CurrentScene = sceneData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create fade canvas
|
||||
CreateFadeCanvas();
|
||||
|
||||
if (OctoEvents.Instance)
|
||||
OctoEvents.Instance.SetScenesInitialized();
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoNavigation] Initialized (Addressables: {settings.Scenes.UseAddressables})");
|
||||
}
|
||||
|
||||
public void Tick() { }
|
||||
|
||||
public void OnSceneChanged(Scene current, Scene next)
|
||||
{
|
||||
// Update CurrentScene reference
|
||||
if (OctoCore.Instance && OctoCore.Instance.Settings)
|
||||
{
|
||||
foreach (var sceneData in OctoCore.Instance.Settings.Scenes.SceneDatas)
|
||||
{
|
||||
if (sceneData.SceneName == next.name)
|
||||
{
|
||||
CurrentScene = sceneData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnQuit() { }
|
||||
|
||||
/// <summary>
|
||||
/// Load a scene using OctoSceneData.
|
||||
/// </summary>
|
||||
public void LoadScene(OctoSceneData sceneData)
|
||||
{
|
||||
if (sceneData == null)
|
||||
{
|
||||
Debug.LogError("[OctoNavigation] SceneData is null");
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingCoroutine = OctoCore.Instance.StartCoroutine(LoadSceneCoroutine(sceneData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Teleport using OctoTeleporterData.
|
||||
/// </summary>
|
||||
public void Teleport(OctoTeleporterData teleporter)
|
||||
{
|
||||
if (teleporter == null)
|
||||
{
|
||||
Debug.LogError("[OctoNavigation] Teleporter is null");
|
||||
return;
|
||||
}
|
||||
|
||||
OnTeleportStarted?.Invoke(null, teleporter);
|
||||
|
||||
// Same scene teleport
|
||||
if (teleporter.TargetScene == null ||
|
||||
teleporter.TargetScene == CurrentScene)
|
||||
{
|
||||
OctoCore.Instance.StartCoroutine(TeleportSameSceneCoroutine(teleporter));
|
||||
}
|
||||
// Different scene teleport
|
||||
else
|
||||
{
|
||||
OctoCore.Instance.StartCoroutine(TeleportDifferentSceneCoroutine(teleporter));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator LoadSceneCoroutine(OctoSceneData sceneData)
|
||||
{
|
||||
OnSceneLoadStarted?.Invoke(sceneData);
|
||||
|
||||
// Get global fade settings
|
||||
OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings;
|
||||
|
||||
// Fade out
|
||||
if (sceneData.UseFadeTransition && _fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeOut(fadeSettings.FadeOutDuration, fadeSettings.FadeColor);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeOutDuration);
|
||||
}
|
||||
|
||||
// Check if using Addressables
|
||||
bool useAddressables = OctoCore.Instance.Settings.Scenes.UseAddressables;
|
||||
|
||||
#if OCTO_ADDRESSABLES
|
||||
if (useAddressables)
|
||||
{
|
||||
yield return LoadSceneAddressables(sceneData);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
yield return LoadSceneStandard(sceneData);
|
||||
}
|
||||
|
||||
// Fade in
|
||||
if (sceneData.UseFadeTransition && _fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeIn(fadeSettings.FadeInDuration);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeInDuration);
|
||||
}
|
||||
|
||||
OnSceneLoadCompleted?.Invoke(sceneData);
|
||||
|
||||
if (OctoCore.Instance.Development.Log)
|
||||
Debug.Log($"[OctoNavigation] Scene loaded: {sceneData.SceneName}");
|
||||
}
|
||||
|
||||
private IEnumerator LoadSceneStandard(OctoSceneData sceneData)
|
||||
{
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneData.SceneName);
|
||||
yield return asyncLoad;
|
||||
}
|
||||
|
||||
#if OCTO_ADDRESSABLES
|
||||
private IEnumerator LoadSceneAddressables(OctoSceneData sceneData)
|
||||
{
|
||||
AsyncOperationHandle<SceneInstance> handle = Addressables.LoadSceneAsync(sceneData.SceneName, LoadSceneMode.Single);
|
||||
yield return handle;
|
||||
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Debug.LogError($"[OctoNavigation] Failed to load scene: {sceneData.SceneName}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private IEnumerator TeleportSameSceneCoroutine(OctoTeleporterData teleporter)
|
||||
{
|
||||
// Get global fade settings
|
||||
OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings;
|
||||
|
||||
// Fade out
|
||||
if (teleporter.UseFadeTransition && _fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeOut(fadeSettings.FadeOutDuration, fadeSettings.FadeColor);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeOutDuration);
|
||||
}
|
||||
|
||||
// Find target teleporter
|
||||
OctoTeleporter destination = OctoTeleporter.Find(teleporter);
|
||||
|
||||
if (destination)
|
||||
{
|
||||
MovePlayerToTeleporter(destination, teleporter.SetPlayerRotation);
|
||||
destination.TriggerTeleport();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[OctoNavigation] Target teleporter not found: " + (teleporter ? teleporter.name : "null"));
|
||||
}
|
||||
|
||||
// Fade in
|
||||
if (teleporter.UseFadeTransition && _fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeIn(fadeSettings.FadeInDuration);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeInDuration);
|
||||
}
|
||||
|
||||
OnTeleportCompleted?.Invoke(null, teleporter);
|
||||
}
|
||||
|
||||
private IEnumerator TeleportDifferentSceneCoroutine(OctoTeleporterData teleporter)
|
||||
{
|
||||
// Get global fade settings
|
||||
OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings;
|
||||
|
||||
// Fade out
|
||||
if (teleporter.UseFadeTransition && _fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeOut(fadeSettings.FadeOutDuration, fadeSettings.FadeColor);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeOutDuration);
|
||||
}
|
||||
|
||||
// Store pending teleporter
|
||||
PendingTeleporter = teleporter;
|
||||
_setPlayerRotation = teleporter.SetPlayerRotation;
|
||||
|
||||
if (OctoCore.Instance.Settings.Scenes.UseAddressables)
|
||||
{
|
||||
#if OCTO_ADDRESSABLES
|
||||
AsyncOperationHandle<SceneInstance> handle = Addressables.LoadSceneAsync(teleporter.TargetScene.SceneName, LoadSceneMode.Single);
|
||||
yield return handle;
|
||||
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Debug.LogError("[OctoNavigation] Failed to load scene: " + teleporter.TargetScene.SceneName);
|
||||
PendingTeleporter = null;
|
||||
yield break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(teleporter.TargetScene.SceneName);
|
||||
yield return asyncLoad;
|
||||
}
|
||||
|
||||
// CompletePendingTeleport will be handled by OctoTeleporters
|
||||
}
|
||||
|
||||
public void EvaluatePendingTeleport(OctoTeleporter teleporter)
|
||||
{
|
||||
if (PendingTeleporter != null &&
|
||||
teleporter != null &&
|
||||
teleporter.TeleporterData != null &&
|
||||
teleporter.TeleporterData == PendingTeleporter)
|
||||
{
|
||||
PendingTeleporter = null;
|
||||
OctoCore.Instance.StartCoroutine(CompletePendingTeleport(teleporter));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator CompletePendingTeleport(OctoTeleporter teleporter)
|
||||
{
|
||||
if (teleporter)
|
||||
{
|
||||
MovePlayerToTeleporter(teleporter, _setPlayerRotation);
|
||||
teleporter.TriggerTeleport();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[OctoNavigation] Target teleporter not found in new scene: " + teleporter.name);
|
||||
}
|
||||
|
||||
// Get global fade settings
|
||||
OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings;
|
||||
|
||||
// Fade in
|
||||
if (_fadeCanvas)
|
||||
{
|
||||
_fadeCanvas.FadeIn(fadeSettings.FadeInDuration);
|
||||
yield return new WaitForSeconds(fadeSettings.FadeInDuration);
|
||||
}
|
||||
|
||||
OnTeleportCompleted?.Invoke(null, teleporter.TeleporterData);
|
||||
}
|
||||
|
||||
private void MovePlayerToTeleporter(OctoTeleporter teleporter, bool setRotation)
|
||||
{
|
||||
if (Components.OctoPlayer.Instance)
|
||||
{
|
||||
Components.OctoPlayer.Instance.Teleport(teleporter.transform.position, setRotation ? teleporter.transform.rotation : Components.OctoPlayer.Instance.transform.rotation);
|
||||
}
|
||||
else if (OctoTeleporterProxy.Instance)
|
||||
{
|
||||
if (setRotation)
|
||||
OctoTeleporterProxy.Instance.Teleport(teleporter.transform.position, teleporter.transform.rotation);
|
||||
else
|
||||
OctoTeleporterProxy.Instance.Teleport(teleporter.transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateFadeCanvas()
|
||||
{
|
||||
GameObject fadeObj = new GameObject("OctoSceneFade");
|
||||
GameObject.DontDestroyOnLoad(fadeObj);
|
||||
_fadeCanvas = fadeObj.AddComponent<OctoSceneFade>();
|
||||
_fadeCanvas.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52668fd3a36f7d54a9180e2dd4946e37
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles fade in/out transitions for scene loading and teleportation.
|
||||
/// </summary>
|
||||
public class OctoSceneFade : MonoBehaviour
|
||||
{
|
||||
private Canvas _canvas;
|
||||
private Image _fadeImage;
|
||||
|
||||
private IEnumerator fadeOutCoroutine;
|
||||
private IEnumerator fadeInCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the fade canvas and image.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// Create canvas
|
||||
_canvas = gameObject.AddComponent<Canvas>();
|
||||
_canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
_canvas.sortingOrder = 9999;
|
||||
|
||||
gameObject.AddComponent<CanvasScaler>();
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// Create fade image
|
||||
GameObject imageObj = new GameObject("FadeImage");
|
||||
imageObj.transform.SetParent(transform);
|
||||
|
||||
_fadeImage = imageObj.AddComponent<Image>();
|
||||
_fadeImage.color = new Color(0, 0, 0, 0);
|
||||
|
||||
RectTransform rt = _fadeImage.GetComponent<RectTransform>();
|
||||
rt.anchorMin = Vector2.zero;
|
||||
rt.anchorMax = Vector2.one;
|
||||
rt.sizeDelta = rt.anchoredPosition = Vector2.zero;
|
||||
|
||||
_canvas.enabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fade out to specified color over duration.
|
||||
/// </summary>
|
||||
public void FadeOut(float duration, Color color)
|
||||
{
|
||||
if (fadeOutCoroutine == null)
|
||||
{
|
||||
if (fadeInCoroutine != null)
|
||||
StopCoroutine(fadeInCoroutine);
|
||||
|
||||
StartCoroutine(fadeOutCoroutine = FadeOutCoroutine(duration, color));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fade in from current color to transparent over duration.
|
||||
/// </summary>
|
||||
public void FadeIn(float duration)
|
||||
{
|
||||
if (fadeInCoroutine == null)
|
||||
{
|
||||
if (fadeOutCoroutine != null)
|
||||
StopCoroutine(fadeOutCoroutine);
|
||||
|
||||
StartCoroutine(fadeInCoroutine = FadeInCoroutine(duration));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator FadeOutCoroutine(float duration, Color color)
|
||||
{
|
||||
_canvas.enabled = true;
|
||||
|
||||
Color startColor = new Color(color.r, color.g, color.b, 0);
|
||||
Color endColor = new Color(color.r, color.g, color.b, 1);
|
||||
|
||||
for (float t = 0; t < duration; t += Time.unscaledDeltaTime)
|
||||
{
|
||||
float normalizedTime = Mathf.Clamp01(t / duration);
|
||||
_fadeImage.color = Color.Lerp(startColor, endColor, normalizedTime);
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
_fadeImage.color = endColor;
|
||||
|
||||
fadeOutCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fade in from current color to transparent over duration.
|
||||
/// </summary>
|
||||
private IEnumerator FadeInCoroutine(float duration)
|
||||
{
|
||||
Color startColor = _fadeImage.color;
|
||||
|
||||
if (startColor != Color.clear)
|
||||
{
|
||||
Color endColor = new Color(startColor.r, startColor.g, startColor.b, 0);
|
||||
|
||||
for (float t = 0; t < duration; t += Time.unscaledDeltaTime)
|
||||
{
|
||||
float normalizedTime = Mathf.Clamp01(t / duration);
|
||||
_fadeImage.color = Color.Lerp(startColor, endColor, normalizedTime);
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
_fadeImage.color = endColor;
|
||||
_canvas.enabled = false;
|
||||
}
|
||||
|
||||
fadeInCoroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53bf408fb989ba342a38b073dd1dafd1
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Navigation.Data;
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene trigger component.
|
||||
/// Links OctoWorldEvent to the scene loading system.
|
||||
/// Call LoadScene() from OctoWorldEvent to load a scene.
|
||||
/// </summary>
|
||||
public class OctoSceneTrigger : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Scene data to load")]
|
||||
private OctoSceneData _sceneData;
|
||||
|
||||
/// <summary>
|
||||
/// Load the configured scene.
|
||||
/// This method should be called from OctoWorldEvent.
|
||||
/// </summary>
|
||||
public void LoadScene()
|
||||
{
|
||||
if (!OctoCore.Instance)
|
||||
{
|
||||
Debug.LogError("[OctoSceneTrigger] OctoCore instance not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sceneData == null)
|
||||
{
|
||||
Debug.LogError("[OctoSceneTrigger] SceneData is null");
|
||||
return;
|
||||
}
|
||||
|
||||
OctoCore.Instance.Navigation.LoadScene(_sceneData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67296e1c23d8b3645add1d503a4b3a3f
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using OctoCore.Navigation.Data;
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Teleporter destination component.
|
||||
/// Marks a position as a valid teleport arrival point.
|
||||
/// </summary>
|
||||
public class OctoTeleporter : MonoBehaviour
|
||||
{
|
||||
private static List<OctoTeleporter> Teleporters = new List<OctoTeleporter>();
|
||||
|
||||
[SerializeField, Tooltip("Teleporter data identifying this destination")]
|
||||
private OctoTeleporterData _teleporterData;
|
||||
|
||||
[SerializeField, Tooltip("Event triggered when player teleports to this location")]
|
||||
private UnityEvent _onTeleport;
|
||||
|
||||
/// <summary>
|
||||
/// Teleporter data for this destination.
|
||||
/// </summary>
|
||||
public OctoTeleporterData TeleporterData => _teleporterData;
|
||||
|
||||
/// <summary>
|
||||
/// Find a teleporter by its data reference.
|
||||
/// </summary>
|
||||
public static OctoTeleporter Find(OctoTeleporterData data)
|
||||
{
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
foreach (var teleporter in Teleporters)
|
||||
{
|
||||
if (teleporter._teleporterData == data)
|
||||
return teleporter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (!Teleporters.Contains(this))
|
||||
Teleporters.Add(this);
|
||||
|
||||
if (OctoCore.Instance)
|
||||
OctoCore.Instance.Navigation.EvaluatePendingTeleport(this);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Teleporters.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger the teleport event.
|
||||
/// Called by OctoNavigation when player arrives.
|
||||
/// </summary>
|
||||
public void TriggerTeleport()
|
||||
{
|
||||
_onTeleport?.Invoke();
|
||||
|
||||
if (OctoCore.Instance && OctoCore.Instance.Development.Log)
|
||||
Debug.Log("[OctoTeleporter] Teleported to: " + (_teleporterData ? _teleporterData.name : "Unknown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b476baeb59f80b42aaa56a0901de859
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
public class OctoTeleporterProxy : MonoBehaviour
|
||||
{
|
||||
public static OctoTeleporterProxy Instance { get; private set; }
|
||||
|
||||
[SerializeField] private UnityEvent<bool> _onPreTeleport;
|
||||
[SerializeField] private UnityEvent<Vector3> _setPositon;
|
||||
[SerializeField] private UnityEvent<Quaternion> _setRotation;
|
||||
[SerializeField] private UnityEvent<bool> _onPostTeleport;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
Debug.LogWarning("Only one OctoTeleporterProxy per scene is supported");
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (Instance == this)
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
public void Teleport(Vector3 position) { Teleport(position, Quaternion.identity, false); }
|
||||
|
||||
public void Teleport(Vector3 position, Quaternion rotation, bool setRotation = true)
|
||||
{
|
||||
_onPreTeleport?.Invoke(false);
|
||||
_setPositon?.Invoke(position);
|
||||
if (setRotation)
|
||||
_setRotation?.Invoke(rotation);
|
||||
_onPostTeleport?.Invoke(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f788fcf1ffcc3342b6e7a87661a4262
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Navigation.Data;
|
||||
|
||||
namespace OctoCore.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Teleporter trigger component.
|
||||
/// Links OctoWorldEvent to the teleportation system.
|
||||
/// Call Teleport() from OctoWorldEvent to initiate teleportation.
|
||||
/// </summary>
|
||||
public class OctoTeleporterTrigger : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Tooltip("Teleporter to teleport to")]
|
||||
private OctoTeleporterData _teleporter;
|
||||
|
||||
/// <summary>
|
||||
/// Initiate teleportation to the configured teleporter.
|
||||
/// This method should be called from OctoWorldEvent.
|
||||
/// </summary>
|
||||
public void Teleport()
|
||||
{
|
||||
if (!OctoCore.Instance)
|
||||
{
|
||||
Debug.LogError("[OctoTeleporterTrigger] OctoCore instance not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_teleporter == null)
|
||||
{
|
||||
Debug.LogError("[OctoTeleporterTrigger] Teleporter is null");
|
||||
return;
|
||||
}
|
||||
|
||||
OctoCore.Instance.Navigation.Teleport(_teleporter);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user