Crate Escape Completed
This commit is contained in:
parent
fc8f230187
commit
885657b910
@ -2,13 +2,18 @@
|
|||||||
%TAG !u! tag:unity3d.com,2011:
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
--- !u!21 &2100000
|
--- !u!21 &2100000
|
||||||
Material:
|
Material:
|
||||||
serializedVersion: 6
|
serializedVersion: 8
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
m_PrefabParentObject: {fileID: 0}
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
m_PrefabInternal: {fileID: 0}
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
m_Name: em_clenchteeth2
|
m_Name: em_clenchteeth2
|
||||||
m_Shader: {fileID: 211, guid: 0000000000000000f000000000000000, type: 0}
|
m_Shader: {fileID: 211, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
m_ShaderKeywords: _ALPHABLEND_ON
|
m_Parent: {fileID: 0}
|
||||||
|
m_ModifiedSerializedProperties: 0
|
||||||
|
m_ValidKeywords:
|
||||||
|
- _ALPHABLEND_ON
|
||||||
|
m_InvalidKeywords: []
|
||||||
m_LightmapFlags: 0
|
m_LightmapFlags: 0
|
||||||
m_EnableInstancingVariants: 0
|
m_EnableInstancingVariants: 0
|
||||||
m_DoubleSidedGI: 0
|
m_DoubleSidedGI: 0
|
||||||
@ -16,7 +21,8 @@ Material:
|
|||||||
stringTagMap:
|
stringTagMap:
|
||||||
RenderType: Transparent
|
RenderType: Transparent
|
||||||
disabledShaderPasses:
|
disabledShaderPasses:
|
||||||
- ALWAYS
|
- GRABPASS
|
||||||
|
m_LockedProperties:
|
||||||
m_SavedProperties:
|
m_SavedProperties:
|
||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_TexEnvs:
|
m_TexEnvs:
|
||||||
@ -32,6 +38,7 @@ Material:
|
|||||||
m_Texture: {fileID: 2800000, guid: deb28cb372e1ee54fad618f6df43247f, type: 3}
|
m_Texture: {fileID: 2800000, guid: deb28cb372e1ee54fad618f6df43247f, type: 3}
|
||||||
m_Scale: {x: 1, y: 1}
|
m_Scale: {x: 1, y: 1}
|
||||||
m_Offset: {x: 0, y: 0}
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
m_Floats:
|
m_Floats:
|
||||||
- _BlendOp: 0
|
- _BlendOp: 0
|
||||||
- _BumpScale: 1
|
- _BumpScale: 1
|
||||||
@ -63,3 +70,4 @@ Material:
|
|||||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
- _SoftParticleFadeParams: {r: 0, g: 0, b: 0, a: 0}
|
- _SoftParticleFadeParams: {r: 0, g: 0, b: 0, a: 0}
|
||||||
- _TintColor: {r: 0.5, g: 0.5, b: 0.5, a: 0.5}
|
- _TintColor: {r: 0.5, g: 0.5, b: 0.5, a: 0.5}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
|
@ -580,6 +580,32 @@ AnimatorStateTransition:
|
|||||||
m_InterruptionSource: 0
|
m_InterruptionSource: 0
|
||||||
m_OrderedInterruption: 1
|
m_OrderedInterruption: 1
|
||||||
m_CanTransitionToSelf: 1
|
m_CanTransitionToSelf: 1
|
||||||
|
--- !u!1102 &-3331804382887163694
|
||||||
|
AnimatorState:
|
||||||
|
serializedVersion: 6
|
||||||
|
m_ObjectHideFlags: 1
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Celebration
|
||||||
|
m_Speed: 1
|
||||||
|
m_CycleOffset: 0
|
||||||
|
m_Transitions: []
|
||||||
|
m_StateMachineBehaviours: []
|
||||||
|
m_Position: {x: 50, y: 50, z: 0}
|
||||||
|
m_IKOnFeet: 0
|
||||||
|
m_WriteDefaultValues: 1
|
||||||
|
m_Mirror: 0
|
||||||
|
m_SpeedParameterActive: 0
|
||||||
|
m_MirrorParameterActive: 0
|
||||||
|
m_CycleOffsetParameterActive: 0
|
||||||
|
m_TimeParameterActive: 0
|
||||||
|
m_Motion: {fileID: 3060610079319413300, guid: f4cc7d9b87c59db4b83522e99e842364, type: 3}
|
||||||
|
m_Tag:
|
||||||
|
m_SpeedParameter:
|
||||||
|
m_MirrorParameter:
|
||||||
|
m_CycleOffsetParameter:
|
||||||
|
m_TimeParameter:
|
||||||
--- !u!1101 &-2923408276037091438
|
--- !u!1101 &-2923408276037091438
|
||||||
AnimatorStateTransition:
|
AnimatorStateTransition:
|
||||||
m_ObjectHideFlags: 1
|
m_ObjectHideFlags: 1
|
||||||
@ -956,6 +982,9 @@ AnimatorStateMachine:
|
|||||||
- serializedVersion: 1
|
- serializedVersion: 1
|
||||||
m_State: {fileID: 7977088065826294681}
|
m_State: {fileID: 7977088065826294681}
|
||||||
m_Position: {x: 1635, y: 2665, z: 0}
|
m_Position: {x: 1635, y: 2665, z: 0}
|
||||||
|
- serializedVersion: 1
|
||||||
|
m_State: {fileID: -3331804382887163694}
|
||||||
|
m_Position: {x: 420, y: 400, z: 0}
|
||||||
m_ChildStateMachines:
|
m_ChildStateMachines:
|
||||||
- serializedVersion: 1
|
- serializedVersion: 1
|
||||||
m_StateMachine: {fileID: 7036095546467220257}
|
m_StateMachine: {fileID: 7036095546467220257}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,106 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
// Joystick Pack (Asset Store)
|
|
||||||
// Drag any Joystick (Fixed/Floating/Dynamic/Variable) into moveJoystick
|
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Joystick")]
|
[Header("Joystick")]
|
||||||
public Joystick moveJoystick; // assign from Joystick Pack
|
public Joystick moveJoystick;
|
||||||
|
|
||||||
[Header("Movement")]
|
[Header("Movement")]
|
||||||
public float moveSpeed = 4f; // base speed
|
public float moveSpeed = 4f;
|
||||||
public float moveDamp = 12f; // velocity smoothing
|
public float moveDamp = 12f; // smoothing for XZ
|
||||||
public float maxSpeedMultiplier = 3f; // full-stick multiplier
|
public float maxSpeedMultiplier = 3f; // full-stick multiplier
|
||||||
[Range(0f, 0.5f)] public float inputDeadZone = 0.1f;
|
[Range(0f, 0.5f)] public float inputDeadZone = 0.1f;
|
||||||
|
|
||||||
[Tooltip("If true, stick is interpreted relative to the camera view.")]
|
[Tooltip("If true, stick is interpreted relative to the camera view.")]
|
||||||
public bool cameraRelative = true;
|
public bool cameraRelative = true;
|
||||||
public Transform cameraTransform; // auto-fills with Camera.main if null
|
public Transform cameraTransform;
|
||||||
|
|
||||||
|
[Header("Movement Plane")]
|
||||||
|
[Tooltip("If OFF, uses Static Up as the movement plane normal. If ON, raycasts to read ground normal under the character.")]
|
||||||
|
public bool useDynamicGroundNormal = false;
|
||||||
|
[Tooltip("Used when useDynamicGroundNormal = false. Set this to your level's 'up' direction (the plane normal).")]
|
||||||
|
public Vector3 staticUp = Vector3.up;
|
||||||
|
[Tooltip("Layer mask for reading ground normal when dynamic is ON.")]
|
||||||
|
public LayerMask groundForNormalMask = ~0;
|
||||||
|
[Tooltip("Ray length for sampling ground normal beneath groundCheck.")]
|
||||||
|
public float groundNormalRayLength = 3f;
|
||||||
|
|
||||||
[Header("Facing")]
|
[Header("Facing")]
|
||||||
public bool faceMoveDirection = true; // rotate toward move direction
|
public bool faceMoveDirection = true;
|
||||||
public float turnSpeed = 540f; // deg/sec rotation speed
|
public float turnSpeed = 540f; // deg/sec
|
||||||
|
|
||||||
[Header("Grounding")]
|
[Header("Grounding")]
|
||||||
public LayerMask groundMask = ~0;
|
public LayerMask groundMask = ~0;
|
||||||
public float groundCheckRadius = 0.2f;
|
public float groundCheckRadius = 0.2f;
|
||||||
public Transform groundCheck;
|
public Transform groundCheck;
|
||||||
|
|
||||||
|
[Header("Jump")]
|
||||||
|
public float jumpForce = 7f; // upward impulse
|
||||||
|
public bool allowAirJump = false;
|
||||||
|
public Button jumpButton;
|
||||||
|
|
||||||
[Header("Optional Animator Driver")]
|
[Header("Optional Animator Driver")]
|
||||||
public ZibuAnimDriver anim;
|
public ZibuAnimDriver anim;
|
||||||
|
|
||||||
[Header("Input Locks")]
|
[Header("Input Locks")]
|
||||||
public float initialLockSeconds = 4f; // joystick disabled for first N seconds
|
public float initialLockSeconds = 0f; // keep 0 while testing
|
||||||
public bool useUnscaledForInitialLock = false; // set true if you pause time at start
|
public bool useUnscaledForInitialLock = false;
|
||||||
public bool autoLockOnGameOver = true; // auto-freeze if GameManager reports isGameOver
|
public bool autoLockOnGameOver = true;
|
||||||
|
|
||||||
|
[Header("Debug / Safety")]
|
||||||
|
public bool debugLogs = true; // show values in console
|
||||||
|
public bool drawMoveRay = true; // shows a cyan ray for move direction
|
||||||
|
public bool keyboardFallbackInEditor = true; // WASD/Arrow fallback when joystick is null or zero
|
||||||
|
public bool ignoreGameOverLock = false; // bypass GameManager lock (testing)
|
||||||
|
|
||||||
// --- runtime ---
|
// --- runtime ---
|
||||||
Rigidbody _rb;
|
Rigidbody _rb;
|
||||||
Vector3 _vel; // smoothed velocity
|
Vector3 _velXZ; // horizontal velocity (XZ only)
|
||||||
Vector3 _moveDirWorld; // desired movement direction in world space
|
Vector3 _moveDirWorld; // desired move dir on movement plane
|
||||||
float _stickMag; // 0..1
|
float _stickMag; // 0..1
|
||||||
bool _inputEnabled = false; // starts locked; enabled after initialLockSeconds
|
bool _inputEnabled = false; // true when controls active
|
||||||
|
bool _jumpQueued;
|
||||||
|
bool _usedAirJump;
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
_rb = GetComponent<Rigidbody>();
|
_rb = GetComponent<Rigidbody>();
|
||||||
// keep physics stable
|
_rb.useGravity = true;
|
||||||
|
_rb.isKinematic = false;
|
||||||
|
|
||||||
|
// Lock only tilt, never lock Position X/Y/Z here
|
||||||
_rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
|
_rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
|
||||||
|
|
||||||
|
// Good physics defaults
|
||||||
|
if (_rb.mass <= 0f) _rb.mass = 1f;
|
||||||
|
_rb.drag = 0f; // don’t damp velocity away
|
||||||
|
_rb.angularDrag = 0.05f;
|
||||||
|
_rb.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||||
|
|
||||||
if (!groundCheck) groundCheck = transform;
|
if (!groundCheck) groundCheck = transform;
|
||||||
if (!anim) anim = GetComponent<ZibuAnimDriver>();
|
if (!anim) anim = GetComponent<ZibuAnimDriver>();
|
||||||
if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform;
|
if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform;
|
||||||
|
if (!groundCheck) groundCheck = transform;
|
||||||
|
}
|
||||||
|
|
||||||
void OnEnable()
|
void OnEnable()
|
||||||
{
|
{
|
||||||
// start locked, then enable after delay
|
|
||||||
_inputEnabled = false;
|
_inputEnabled = false;
|
||||||
StopAllCoroutines();
|
StopAllCoroutines();
|
||||||
StartCoroutine(EnableAfterDelay(initialLockSeconds, useUnscaledForInitialLock));
|
StartCoroutine(EnableAfterDelay(initialLockSeconds, useUnscaledForInitialLock));
|
||||||
|
if (jumpButton) jumpButton.onClick.AddListener(OnJumpUIButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable()
|
||||||
|
{
|
||||||
|
if (jumpButton) jumpButton.onClick.RemoveListener(OnJumpUIButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Collections.IEnumerator EnableAfterDelay(float seconds, bool unscaled)
|
System.Collections.IEnumerator EnableAfterDelay(float seconds, bool unscaled)
|
||||||
@ -69,6 +111,7 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
else yield return new WaitForSeconds(seconds);
|
else yield return new WaitForSeconds(seconds);
|
||||||
}
|
}
|
||||||
_inputEnabled = true;
|
_inputEnabled = true;
|
||||||
|
if (debugLogs) Debug.Log($"[CrateEscape] Controls ENABLED (delay={seconds}) on {name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
@ -76,35 +119,52 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
bool blocked = IsBlocked();
|
bool blocked = IsBlocked();
|
||||||
|
|
||||||
if (!blocked)
|
if (!blocked)
|
||||||
ReadJoystick();
|
{
|
||||||
|
ReadMoveInput();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// when blocked, zero input so the player stops + anim goes idle
|
|
||||||
_stickMag = 0f;
|
_stickMag = 0f;
|
||||||
_moveDirWorld = Vector3.zero;
|
_moveDirWorld = Vector3.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// animator hooks
|
||||||
if (anim)
|
if (anim)
|
||||||
{
|
{
|
||||||
float speedUnits = Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag);
|
float speedUnits = Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag);
|
||||||
anim.SetGrounded(IsGrounded());
|
anim.SetGrounded(IsGrounded());
|
||||||
anim.SetSpeed(speedUnits);
|
anim.SetSpeed(speedUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset air-jump on ground contact
|
||||||
|
if (IsGrounded()) _usedAirJump = false;
|
||||||
|
|
||||||
|
// Debug ray
|
||||||
|
if (drawMoveRay && _moveDirWorld.sqrMagnitude > 0.0001f)
|
||||||
|
Debug.DrawRay(transform.position + Vector3.up * 0.1f, _moveDirWorld.normalized, Color.cyan, 0f, false);
|
||||||
|
|
||||||
|
if (debugLogs)
|
||||||
|
{
|
||||||
|
if (Time.frameCount % 30 == 0)
|
||||||
|
{
|
||||||
|
Debug.Log($"[CrateEscape] {name} inputEnabled={_inputEnabled} blocked={blocked} stickMag={_stickMag:F2} dir={_moveDirWorld}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
{
|
{
|
||||||
bool blocked = IsBlocked();
|
bool blocked = IsBlocked();
|
||||||
|
|
||||||
Vector3 targetVel = Vector3.zero;
|
Vector3 targetVelXZ = Vector3.zero;
|
||||||
|
|
||||||
if (!blocked && _stickMag > inputDeadZone)
|
if (!blocked && _stickMag > inputDeadZone)
|
||||||
{
|
{
|
||||||
Vector3 dir = _moveDirWorld.normalized;
|
Vector3 dir = _moveDirWorld.normalized;
|
||||||
float targetSpeed = moveSpeed * Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag);
|
float targetSpeed = moveSpeed * Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag);
|
||||||
targetVel = dir * targetSpeed;
|
targetVelXZ = dir * targetSpeed;
|
||||||
|
|
||||||
if (faceMoveDirection)
|
if (faceMoveDirection && dir.sqrMagnitude > 0.0001f)
|
||||||
{
|
{
|
||||||
Quaternion targetRot = Quaternion.LookRotation(dir, Vector3.up);
|
Quaternion targetRot = Quaternion.LookRotation(dir, Vector3.up);
|
||||||
_rb.MoveRotation(Quaternion.RotateTowards(_rb.rotation, targetRot, turnSpeed * Time.fixedDeltaTime));
|
_rb.MoveRotation(Quaternion.RotateTowards(_rb.rotation, targetRot, turnSpeed * Time.fixedDeltaTime));
|
||||||
@ -113,47 +173,131 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
|
|
||||||
if (blocked)
|
if (blocked)
|
||||||
{
|
{
|
||||||
// hard-stop while locked
|
// Stop horizontal movement but preserve Y (gravity / jump settle)
|
||||||
_vel = Vector3.zero;
|
_velXZ = Vector3.zero;
|
||||||
_rb.velocity = Vector3.zero;
|
_rb.velocity = new Vector3(0f, _rb.velocity.y, 0f);
|
||||||
return; // don't MovePosition this frame
|
_jumpQueued = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smooth velocity and move
|
// Smooth horizontal velocity (keep physics-driven Y)
|
||||||
_vel = Vector3.Lerp(_vel, targetVel, 1f - Mathf.Exp(-moveDamp * Time.fixedDeltaTime));
|
_velXZ = Vector3.Lerp(_velXZ, targetVelXZ, 1f - Mathf.Exp(-moveDamp * Time.fixedDeltaTime));
|
||||||
_rb.MovePosition(_rb.position + _vel * Time.fixedDeltaTime);
|
|
||||||
|
// Apply horizontal velocity while preserving current Y
|
||||||
|
_rb.velocity = new Vector3(_velXZ.x, _rb.velocity.y, _velXZ.z);
|
||||||
|
|
||||||
|
// Handle jump in physics step
|
||||||
|
if (_jumpQueued)
|
||||||
|
{
|
||||||
|
_jumpQueued = false;
|
||||||
|
DoJump();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadJoystick()
|
// ---------- Input ----------
|
||||||
|
|
||||||
|
void ReadMoveInput()
|
||||||
{
|
{
|
||||||
if (!moveJoystick) { _stickMag = 0f; _moveDirWorld = Vector3.zero; return; }
|
Vector2 stick = Vector2.zero;
|
||||||
|
|
||||||
float h = moveJoystick.Horizontal; // -1..1
|
// Prefer joystick if present and non-zero
|
||||||
float v = moveJoystick.Vertical; // -1..1
|
if (moveJoystick)
|
||||||
|
{
|
||||||
|
stick = new Vector2(moveJoystick.Horizontal, moveJoystick.Vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
// Optional keyboard fallback (Editor/testing)
|
||||||
|
if (keyboardFallbackInEditor)
|
||||||
|
{
|
||||||
|
if (stick.sqrMagnitude <= 0.0001f)
|
||||||
|
{
|
||||||
|
float h = Input.GetAxisRaw("Horizontal"); // A/D or Left/Right
|
||||||
|
float v = Input.GetAxisRaw("Vertical"); // W/S or Up/Down
|
||||||
|
if (Mathf.Abs(h) > 0.01f || Mathf.Abs(v) > 0.01f)
|
||||||
|
stick = new Vector2(h, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Vector2 stick = new Vector2(h, v);
|
|
||||||
_stickMag = Mathf.Clamp01(stick.magnitude);
|
_stickMag = Mathf.Clamp01(stick.magnitude);
|
||||||
|
|
||||||
if (_stickMag <= inputDeadZone) { _moveDirWorld = Vector3.zero; return; }
|
if (_stickMag <= inputDeadZone)
|
||||||
|
{
|
||||||
|
_moveDirWorld = Vector3.zero;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector2 n = stick.normalized;
|
Vector2 n = stick.normalized;
|
||||||
|
|
||||||
if (cameraRelative && cameraTransform)
|
if (cameraRelative && cameraTransform)
|
||||||
{
|
{
|
||||||
// Project camera forward to XZ and build a right vector
|
// Plane-aware camera relative vectors
|
||||||
Vector3 camF = cameraTransform.forward; camF.y = 0f;
|
Vector3 planeUp = GetMovementUp();
|
||||||
camF = camF.sqrMagnitude > 0.0001f ? camF.normalized : Vector3.forward;
|
|
||||||
Vector3 camR = new Vector3(camF.z, 0f, -camF.x);
|
// Project camera forward onto the plane
|
||||||
|
Vector3 camF = Vector3.ProjectOnPlane(cameraTransform.forward, planeUp);
|
||||||
|
if (camF.sqrMagnitude < 0.0001f)
|
||||||
|
camF = Vector3.ProjectOnPlane(cameraTransform.up, planeUp);
|
||||||
|
camF.Normalize();
|
||||||
|
|
||||||
|
// Right is perpendicular on the plane
|
||||||
|
Vector3 camR = Vector3.Cross(planeUp, camF).normalized;
|
||||||
|
|
||||||
|
// Re-orthonormalize camF to be safe
|
||||||
|
camF = Vector3.Cross(camR, planeUp).normalized;
|
||||||
|
|
||||||
_moveDirWorld = camR * n.x + camF * n.y;
|
_moveDirWorld = camR * n.x + camF * n.y;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// World-relative (X/Z)
|
// World-relative, but still constrained to the plane
|
||||||
_moveDirWorld = new Vector3(n.x, 0f, n.y);
|
Vector3 planeUp = GetMovementUp();
|
||||||
|
Vector3 worldX = Vector3.ProjectOnPlane(Vector3.right, planeUp).normalized;
|
||||||
|
Vector3 worldZ = Vector3.ProjectOnPlane(Vector3.forward, planeUp).normalized;
|
||||||
|
_moveDirWorld = (worldX * n.x + worldZ * n.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3 GetMovementUp()
|
||||||
|
{
|
||||||
|
if (useDynamicGroundNormal && groundCheck)
|
||||||
|
{
|
||||||
|
// Cast along character's local down to find the surface normal
|
||||||
|
Ray ray = new Ray(groundCheck.position + Vector3.up * 0.05f, -transform.up);
|
||||||
|
if (Physics.Raycast(ray, out var hit, groundNormalRayLength, groundForNormalMask, QueryTriggerInteraction.Ignore))
|
||||||
|
{
|
||||||
|
return hit.normal.normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to static up
|
||||||
|
return staticUp.sqrMagnitude > 0.0001f ? staticUp.normalized : Vector3.up;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoJump()
|
||||||
|
{
|
||||||
|
bool grounded = IsGrounded();
|
||||||
|
|
||||||
|
if (!grounded)
|
||||||
|
{
|
||||||
|
if (!(allowAirJump && !_usedAirJump)) return;
|
||||||
|
_usedAirJump = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset vertical velocity then apply impulse for consistent jumps
|
||||||
|
Vector3 v = _rb.velocity;
|
||||||
|
v.y = 0f;
|
||||||
|
_rb.velocity = v;
|
||||||
|
|
||||||
|
_rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
|
||||||
|
|
||||||
|
if (anim) anim.PlayAnimation(AnimationState.JumpStart);
|
||||||
|
|
||||||
|
if (debugLogs) Debug.Log($"[CrateEscape] {name} JUMP (grounded={grounded}, airUsed={_usedAirJump})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Ground / Gizmos ----------
|
||||||
|
|
||||||
bool IsGrounded()
|
bool IsGrounded()
|
||||||
{
|
{
|
||||||
return Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore);
|
return Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore);
|
||||||
@ -168,7 +312,6 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
|
|
||||||
// ---------- Public API ----------
|
// ---------- Public API ----------
|
||||||
|
|
||||||
/// <summary>Enable/disable all player input & movement immediately.</summary>
|
|
||||||
public void LockControls(bool locked)
|
public void LockControls(bool locked)
|
||||||
{
|
{
|
||||||
_inputEnabled = !locked;
|
_inputEnabled = !locked;
|
||||||
@ -176,25 +319,26 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
{
|
{
|
||||||
_stickMag = 0f;
|
_stickMag = 0f;
|
||||||
_moveDirWorld = Vector3.zero;
|
_moveDirWorld = Vector3.zero;
|
||||||
_vel = Vector3.zero;
|
_velXZ = Vector3.zero;
|
||||||
if (_rb) _rb.velocity = Vector3.zero;
|
if (_rb) _rb.velocity = new Vector3(0f, _rb.velocity.y, 0f);
|
||||||
if (anim) { anim.SetSpeed(0f); }
|
if (anim) { anim.SetSpeed(0f); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Enable controls now (e.g., if you want to skip the initial delay).</summary>
|
|
||||||
public void EnableControlsNow()
|
public void EnableControlsNow()
|
||||||
{
|
{
|
||||||
StopAllCoroutines();
|
StopAllCoroutines();
|
||||||
_inputEnabled = true;
|
_inputEnabled = true;
|
||||||
|
if (debugLogs) Debug.Log($"[CrateEscape] Controls ENABLED by call on {name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Helpers ----------
|
// ---------- Helpers ----------
|
||||||
|
|
||||||
bool IsBlocked()
|
bool IsBlocked()
|
||||||
{
|
{
|
||||||
if (!_inputEnabled) return true;
|
if (!_inputEnabled) return true;
|
||||||
|
|
||||||
if (autoLockOnGameOver)
|
if (!ignoreGameOverLock && autoLockOnGameOver)
|
||||||
{
|
{
|
||||||
var gm = CrateEscapeGameManager.Instance;
|
var gm = CrateEscapeGameManager.Instance;
|
||||||
if (gm != null && gm.isGameOver) return true; // freeze after game over
|
if (gm != null && gm.isGameOver) return true; // freeze after game over
|
||||||
@ -203,6 +347,24 @@ public class CrateEscapePlayerControllerJoystick : MonoBehaviour
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For runtime hookup if you spawn the joystick UI
|
|
||||||
public void SetJoystick(Joystick j) => moveJoystick = j;
|
public void SetJoystick(Joystick j) => moveJoystick = j;
|
||||||
|
|
||||||
|
// ---------- UI Button Hook ----------
|
||||||
|
/// <summary>Call this from a UI Button OnClick() to make the player jump.</summary>
|
||||||
|
public void OnJumpUIButton()
|
||||||
|
{
|
||||||
|
if (IsBlocked()) return;
|
||||||
|
|
||||||
|
if (IsGrounded() || (allowAirJump && !_usedAirJump))
|
||||||
|
_jumpQueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Inspector Utilities ----------
|
||||||
|
[ContextMenu("Test/Nudge Forward")]
|
||||||
|
void NudgeForward()
|
||||||
|
{
|
||||||
|
// Small forward push to verify movement pipeline
|
||||||
|
_rb.velocity = new Vector3(2f, _rb.velocity.y, 0f);
|
||||||
|
if (debugLogs) Debug.Log($"[CrateEscape] {name} NudgeForward called.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,15 @@ using System.Collections;
|
|||||||
[RequireComponent(typeof(LineRenderer))]
|
[RequireComponent(typeof(LineRenderer))]
|
||||||
public class LaserBeam : MonoBehaviour
|
public class LaserBeam : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
[Header("Control")]
|
||||||
|
[Tooltip("If true, this beam will NOT run its own timers or intro; it only responds to the controller.")]
|
||||||
|
public bool externalControl = true;
|
||||||
|
|
||||||
[Header("Laser Settings")]
|
[Header("Laser Settings")]
|
||||||
public float maxDistance = 20f;
|
public float maxDistance = 20f;
|
||||||
public LayerMask collisionMask = ~0;
|
public LayerMask collisionMask = ~0;
|
||||||
public float chargeDuration = 3f;
|
public float chargeDuration = 3f; // used only if externalControl == false
|
||||||
public float fireDuration = 1f;
|
public float fireDuration = 1f; // used only if externalControl == false
|
||||||
|
|
||||||
[Header("Laser Appearance")]
|
[Header("Laser Appearance")]
|
||||||
public Color laserColor = Color.red;
|
public Color laserColor = Color.red;
|
||||||
@ -22,7 +26,7 @@ public class LaserBeam : MonoBehaviour
|
|||||||
public float hitRadius = 0.1f;
|
public float hitRadius = 0.1f;
|
||||||
public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide;
|
public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide;
|
||||||
|
|
||||||
[Header("Intro Show")]
|
[Header("Intro Show (ignored if externalControl)")]
|
||||||
public bool showIntro = true;
|
public bool showIntro = true;
|
||||||
public float initialShowDuration = 3f;
|
public float initialShowDuration = 3f;
|
||||||
public bool introIsDeadly = true;
|
public bool introIsDeadly = true;
|
||||||
@ -59,16 +63,30 @@ public class LaserBeam : MonoBehaviour
|
|||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
SetupLaserRenderer();
|
SetupLaserRenderer();
|
||||||
|
// Always start safe
|
||||||
|
currentPhase = LaserPhase.Idle;
|
||||||
|
hasTriggeredDeathThisBurst = false;
|
||||||
|
DisableLaser();
|
||||||
|
StopAllCoroutines();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
if (showIntro)
|
// Only self-run if not externally controlled
|
||||||
|
if (!externalControl && showIntro)
|
||||||
StartCoroutine(IntroShow());
|
StartCoroutine(IntroShow());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
|
if (externalControl)
|
||||||
|
{
|
||||||
|
// Passive: only draw debug line if visible
|
||||||
|
if (debugDraw) Debug.DrawLine(laserStart, laserEnd, Color.cyan);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy self-FSM (optional use)
|
||||||
if (introRunning) return;
|
if (introRunning) return;
|
||||||
|
|
||||||
timer += Time.deltaTime;
|
timer += Time.deltaTime;
|
||||||
@ -113,8 +131,19 @@ public class LaserBeam : MonoBehaviour
|
|||||||
Debug.DrawLine(laserStart, laserEnd, Color.cyan);
|
Debug.DrawLine(laserStart, laserEnd, Color.cyan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LaserBeam.cs — replace SetLaserPhase with this version
|
||||||
public void SetLaserPhase(LaserPhase phase)
|
public void SetLaserPhase(LaserPhase phase)
|
||||||
{
|
{
|
||||||
|
// If we’re externally controlled, cancel any internal coroutines immediately
|
||||||
|
if (externalControl)
|
||||||
|
{
|
||||||
|
StopAllCoroutines();
|
||||||
|
introRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line) SetupLaserRenderer(); // make sure 'line' exists even if GO was inactive
|
||||||
|
|
||||||
|
if (phase != currentPhase) hasTriggeredDeathThisBurst = false;
|
||||||
currentPhase = phase;
|
currentPhase = phase;
|
||||||
|
|
||||||
switch (phase)
|
switch (phase)
|
||||||
@ -131,6 +160,7 @@ public class LaserBeam : MonoBehaviour
|
|||||||
|
|
||||||
case LaserPhase.Firing:
|
case LaserPhase.Firing:
|
||||||
UpdateLaserPath();
|
UpdateLaserPath();
|
||||||
|
line.enabled = true;
|
||||||
SetLineColor(laserColor);
|
SetLineColor(laserColor);
|
||||||
hasTriggeredDeathThisBurst = false;
|
hasTriggeredDeathThisBurst = false;
|
||||||
CheckHit();
|
CheckHit();
|
||||||
@ -140,20 +170,16 @@ public class LaserBeam : MonoBehaviour
|
|||||||
|
|
||||||
public void TickLaserDuringFiring()
|
public void TickLaserDuringFiring()
|
||||||
{
|
{
|
||||||
if (currentPhase == LaserPhase.Firing)
|
if (currentPhase != LaserPhase.Firing) return;
|
||||||
{
|
UpdateLaserPath();
|
||||||
UpdateLaserPath();
|
CheckHit();
|
||||||
CheckHit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TickLaserDuringCharging()
|
public void TickLaserDuringCharging()
|
||||||
{
|
{
|
||||||
if (currentPhase == LaserPhase.Charging)
|
if (currentPhase != LaserPhase.Charging) return;
|
||||||
{
|
BlinkWarning();
|
||||||
BlinkWarning();
|
UpdateLaserPath();
|
||||||
UpdateLaserPath();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IntroShow()
|
IEnumerator IntroShow()
|
||||||
@ -183,6 +209,9 @@ public class LaserBeam : MonoBehaviour
|
|||||||
void DisableLaser()
|
void DisableLaser()
|
||||||
{
|
{
|
||||||
line.enabled = false;
|
line.enabled = false;
|
||||||
|
// Also clear endpoint for safety/debug
|
||||||
|
laserStart = transform.position;
|
||||||
|
laserEnd = laserStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlinkWarning()
|
void BlinkWarning()
|
||||||
@ -194,6 +223,13 @@ public class LaserBeam : MonoBehaviour
|
|||||||
|
|
||||||
void CheckHit()
|
void CheckHit()
|
||||||
{
|
{
|
||||||
|
// HARD GATES: never kill unless we’re allowed to
|
||||||
|
if (!isActiveAndEnabled) return;
|
||||||
|
if (!gameObject.activeInHierarchy) return;
|
||||||
|
|
||||||
|
bool lethalNow = (currentPhase == LaserPhase.Firing) || (introRunning && introIsDeadly);
|
||||||
|
if (!lethalNow) return;
|
||||||
|
|
||||||
if (hasTriggeredDeathThisBurst) return;
|
if (hasTriggeredDeathThisBurst) return;
|
||||||
|
|
||||||
bool CheckSegment(Vector3 a, Vector3 b)
|
bool CheckSegment(Vector3 a, Vector3 b)
|
||||||
@ -224,7 +260,7 @@ public class LaserBeam : MonoBehaviour
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestCol.CompareTag(boxTag))
|
if (bestCol && bestCol.CompareTag(boxTag))
|
||||||
{
|
{
|
||||||
var boxHealth = bestCol.GetComponent<LaserBoxHealth>();
|
var boxHealth = bestCol.GetComponent<LaserBoxHealth>();
|
||||||
if (boxHealth != null)
|
if (boxHealth != null)
|
||||||
|
@ -5,112 +5,133 @@ using static LaserBeam;
|
|||||||
|
|
||||||
public class LaserBeamController : MonoBehaviour
|
public class LaserBeamController : MonoBehaviour
|
||||||
{
|
{
|
||||||
public float interval = 20f;
|
[Header("Cycle Durations")]
|
||||||
public float chargeDuration = 3f;
|
public float interval = 20f; // how long each batch runs before switching
|
||||||
public float fireDuration = 1f;
|
public float idleBeforeCharge = 3f; // Idle time before Charging
|
||||||
|
public float chargingWindup = 1f; // Charging duration (blink)
|
||||||
|
public float fireDuration = 1f; // Firing duration (lethal)
|
||||||
|
|
||||||
private List<LaserBeam> allLasers = new List<LaserBeam>();
|
[Header("Beams (auto-populated)")]
|
||||||
private List<List<LaserBeam>> batches = new List<List<LaserBeam>>();
|
public List<LaserBeam> allLasers = new List<LaserBeam>();
|
||||||
private List<LaserBeam> activeLasers = new List<LaserBeam>();
|
|
||||||
|
|
||||||
private float timer = 0f;
|
private readonly List<List<LaserBeam>> batches = new();
|
||||||
private LaserPhase currentPhase = LaserPhase.Idle;
|
private readonly List<LaserBeam> activeLasers = new();
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
|
// Use active-only; if you need inactive too, use FindObjectsOfType<LaserBeam>(true)
|
||||||
allLasers.AddRange(FindObjectsOfType<LaserBeam>());
|
allLasers.AddRange(FindObjectsOfType<LaserBeam>());
|
||||||
|
|
||||||
|
foreach (var lb in allLasers)
|
||||||
|
{
|
||||||
|
if (!lb) continue;
|
||||||
|
lb.externalControl = true; // controller owns timing
|
||||||
|
lb.ResetCycle(); // Idle + line off
|
||||||
|
lb.enabled = false; // completely inert until selected
|
||||||
|
}
|
||||||
|
|
||||||
// Shuffle
|
// Shuffle
|
||||||
for (int i = 0; i < allLasers.Count; i++)
|
for (int i = 0; i < allLasers.Count; i++)
|
||||||
{
|
{
|
||||||
var temp = allLasers[i];
|
int r = Random.Range(i, allLasers.Count);
|
||||||
int rand = Random.Range(i, allLasers.Count);
|
(allLasers[i], allLasers[r]) = (allLasers[r], allLasers[i]);
|
||||||
allLasers[i] = allLasers[rand];
|
|
||||||
allLasers[rand] = temp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split into 3 batches
|
// Split into 3 batches
|
||||||
int batchSize = Mathf.CeilToInt(allLasers.Count / 3f);
|
int batchSize = Mathf.CeilToInt(Mathf.Max(1, allLasers.Count) / 3f);
|
||||||
for (int i = 0; i < allLasers.Count; i += batchSize)
|
for (int i = 0; i < allLasers.Count; i += batchSize)
|
||||||
{
|
|
||||||
batches.Add(allLasers.GetRange(i, Mathf.Min(batchSize, allLasers.Count - i)));
|
batches.Add(allLasers.GetRange(i, Mathf.Min(batchSize, allLasers.Count - i)));
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var lb in allLasers)
|
StartCoroutine(RunBatches());
|
||||||
lb.enabled = false;
|
|
||||||
|
|
||||||
StartCoroutine(ActivateBatches());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator ActivateBatches()
|
IEnumerator RunBatches()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < batches.Count; i++)
|
for (int bi = 0; bi < batches.Count; bi++)
|
||||||
{
|
{
|
||||||
activeLasers.AddRange(batches[i]);
|
// Disable everything first
|
||||||
|
foreach (var lb in allLasers)
|
||||||
|
if (lb) { lb.enabled = false; lb.ResetCycle(); }
|
||||||
|
|
||||||
foreach (var laser in activeLasers)
|
// Activate this batch
|
||||||
laser.enabled = true;
|
activeLasers.Clear();
|
||||||
|
activeLasers.AddRange(batches[bi]);
|
||||||
|
foreach (var l in activeLasers)
|
||||||
|
{
|
||||||
|
if (!l) continue;
|
||||||
|
l.enabled = true;
|
||||||
|
l.ResetCycle();
|
||||||
|
l.SetLaserPhase(LaserPhase.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
ResetAllLasers();
|
float batchEnd = Time.time + interval;
|
||||||
|
|
||||||
yield return new WaitForSeconds(interval);
|
// Loop phases for the duration of this batch
|
||||||
|
while (Time.time < batchEnd)
|
||||||
|
{
|
||||||
|
// Compute runway required to complete a Charge->Fire sequence
|
||||||
|
float runway = Mathf.Max(0f, chargingWindup) + Mathf.Max(0f, fireDuration);
|
||||||
|
float timeLeft = batchEnd - Time.time;
|
||||||
|
|
||||||
|
// If not enough time to do Charging+Firing, just stay Idle until the batch ends.
|
||||||
|
if (timeLeft < runway)
|
||||||
|
{
|
||||||
|
yield return PhaseBlock(LaserPhase.Idle, timeLeft, batchEnd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDLE
|
||||||
|
float idleTime = Mathf.Max(0f, idleBeforeCharge);
|
||||||
|
// If idleTime itself would consume too much and leave < runway, trim idle to fit
|
||||||
|
if (idleTime > 0f && idleTime > timeLeft - runway)
|
||||||
|
idleTime = Mathf.Max(0f, timeLeft - runway);
|
||||||
|
|
||||||
|
if (idleTime > 0f)
|
||||||
|
{
|
||||||
|
yield return PhaseBlock(LaserPhase.Idle, idleTime, batchEnd);
|
||||||
|
if (Time.time >= batchEnd) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHARGING (blink)
|
||||||
|
if (chargingWindup > 0f)
|
||||||
|
{
|
||||||
|
yield return PhaseBlock(LaserPhase.Charging, chargingWindup, batchEnd);
|
||||||
|
// we guaranteed runway, so we should still have time left for Firing
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIRING (lethal)
|
||||||
|
if (fireDuration > 0f)
|
||||||
|
{
|
||||||
|
yield return PhaseBlock(LaserPhase.Firing, fireDuration, batchEnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
// Set phase for all active lasers and tick per-frame for 'duration' (clamped by batch end)
|
||||||
|
IEnumerator PhaseBlock(LaserPhase phase, float duration, float batchHardEnd)
|
||||||
{
|
{
|
||||||
if (activeLasers.Count == 0) return;
|
SetPhaseForAll(phase);
|
||||||
|
|
||||||
timer += Time.deltaTime;
|
float end = Mathf.Min(Time.time + duration, batchHardEnd);
|
||||||
|
while (Time.time < end)
|
||||||
switch (currentPhase)
|
|
||||||
{
|
{
|
||||||
case LaserPhase.Idle:
|
if (phase == LaserPhase.Charging)
|
||||||
if (timer >= chargeDuration)
|
{
|
||||||
{
|
foreach (var l in activeLasers) if (l && l.isActiveAndEnabled) l.TickLaserDuringCharging();
|
||||||
timer = 0f;
|
}
|
||||||
currentPhase = LaserPhase.Charging;
|
else if (phase == LaserPhase.Firing)
|
||||||
foreach (var laser in activeLasers)
|
{
|
||||||
laser.SetLaserPhase(LaserPhase.Charging);
|
foreach (var l in activeLasers) if (l && l.isActiveAndEnabled) l.TickLaserDuringFiring();
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case LaserPhase.Charging:
|
yield return null;
|
||||||
foreach (var laser in activeLasers)
|
|
||||||
laser.TickLaserDuringCharging();
|
|
||||||
|
|
||||||
if (timer >= 1f)
|
|
||||||
{
|
|
||||||
timer = 0f;
|
|
||||||
currentPhase = LaserPhase.Firing;
|
|
||||||
foreach (var laser in activeLasers)
|
|
||||||
laser.SetLaserPhase(LaserPhase.Firing);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LaserPhase.Firing:
|
|
||||||
foreach (var laser in activeLasers)
|
|
||||||
laser.TickLaserDuringFiring();
|
|
||||||
|
|
||||||
if (timer >= fireDuration)
|
|
||||||
{
|
|
||||||
timer = 0f;
|
|
||||||
currentPhase = LaserPhase.Idle;
|
|
||||||
foreach (var laser in activeLasers)
|
|
||||||
laser.SetLaserPhase(LaserPhase.Idle);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetAllLasers()
|
void SetPhaseForAll(LaserPhase p)
|
||||||
{
|
{
|
||||||
timer = 0f;
|
foreach (var l in activeLasers)
|
||||||
currentPhase = LaserPhase.Idle;
|
if (l && l.isActiveAndEnabled) l.SetLaserPhase(p);
|
||||||
|
|
||||||
foreach (var laser in activeLasers)
|
|
||||||
{
|
|
||||||
laser.SetLaserPhase(LaserPhase.Idle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,10 @@ using UnityEngine;
|
|||||||
public class LevelCompleteDoorTrigger : MonoBehaviour
|
public class LevelCompleteDoorTrigger : MonoBehaviour
|
||||||
{
|
{
|
||||||
public string playerTag = "Player";
|
public string playerTag = "Player";
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
void Reset()
|
void Reset()
|
||||||
{
|
{
|
||||||
// Make sure the collider is a trigger
|
// Make sure the collider is a trigger
|
||||||
@ -16,7 +19,7 @@ public class LevelCompleteDoorTrigger : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (other.CompareTag(playerTag))
|
if (other.CompareTag(playerTag))
|
||||||
{
|
{
|
||||||
CrateEscapeGameManager.Instance?.OnLevelCompleteTriggered();
|
//CrateEscapeGameManager.Instance?.OnLevelCompleteTriggered();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +1,134 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using DG.Tweening;
|
using DG.Tweening;
|
||||||
|
|
||||||
|
[RequireComponent(typeof(Collider))]
|
||||||
public class SmoothChildRotation : MonoBehaviour
|
public class SmoothChildRotation : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("References")]
|
[Header("Door Parts")]
|
||||||
public Transform targetChild; // Child object to rotate
|
public Transform targetChild; // door mesh child to rotate
|
||||||
public string playerTag = "Player"; // Tag for player detection
|
public string playerTag = "Player";
|
||||||
|
|
||||||
[Header("Rotation Settings")]
|
[Header("Door Rotation")]
|
||||||
public float rotationDuration = 0.5f; // Time in seconds for rotation
|
public float rotationDuration = 0.5f;
|
||||||
public Ease rotationEase = Ease.OutQuad; // Easing type
|
public Ease rotationEase = Ease.OutQuad;
|
||||||
public Vector3 targetRotationOnEnter = new Vector3(0, 0, -90);
|
public Vector3 targetRotationOnEnter = new Vector3(0, 0, -90);
|
||||||
public Vector3 targetRotationOnExit = new Vector3(0, 0, 0);
|
public Vector3 targetRotationOnExit = new Vector3(0, 0, 0);
|
||||||
|
|
||||||
private Tween currentTween;
|
[Header("Cutscene: Exit Path")]
|
||||||
|
[Tooltip("If not set, we use (this.transform.position + this.transform.forward * exitForwardDistance).")]
|
||||||
|
public Transform exitPoint;
|
||||||
|
public float exitForwardDistance = 3f; // used when exitPoint is null
|
||||||
|
public float faceDuration = 0.2f; // how quickly the player faces exit
|
||||||
|
|
||||||
private void Start()
|
[Header("Cutscene: Anim States")]
|
||||||
|
public string celebrationStateName = "Celebration";
|
||||||
|
public string walkStateName = "Run"; // or your locomotion state/BT name
|
||||||
|
|
||||||
|
[Header("Cutscene: Timing")]
|
||||||
|
public float celebrationDuration = 1.2f; // seconds to hold Celebration
|
||||||
|
public float walkMoveDuration = 1.5f; // seconds to walk to exit
|
||||||
|
public Ease walkMoveEase = Ease.Linear;
|
||||||
|
|
||||||
|
[Header("Behavior")]
|
||||||
|
public bool disableTriggerAfterUse = true; // prevents re-entrant triggers
|
||||||
|
|
||||||
|
private Tween _doorTween;
|
||||||
|
private bool _cutsceneRunning;
|
||||||
|
|
||||||
|
void Reset()
|
||||||
{
|
{
|
||||||
if (targetChild == null)
|
var col = GetComponent<Collider>();
|
||||||
|
if (col) col.isTrigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (!targetChild)
|
||||||
{
|
{
|
||||||
Debug.LogError("Target child is not assigned!");
|
Debug.LogError("[SmoothChildRotation] Target child not assigned!", this);
|
||||||
enabled = false;
|
enabled = false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTriggerEnter(Collider other)
|
void OnTriggerEnter(Collider other)
|
||||||
{
|
{
|
||||||
if (other.CompareTag(playerTag))
|
if (_cutsceneRunning) return;
|
||||||
|
if (!other.CompareTag(playerTag)) return;
|
||||||
|
|
||||||
|
// Open the door
|
||||||
|
RotateTo(targetRotationOnEnter);
|
||||||
|
|
||||||
|
// Start the cutscene
|
||||||
|
StartCoroutine(RunDoorCutscene(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTriggerExit(Collider other)
|
||||||
|
{
|
||||||
|
if (!other.CompareTag(playerTag)) return;
|
||||||
|
if (_cutsceneRunning) return; // don’t close while cutscene is ongoing
|
||||||
|
RotateTo(targetRotationOnExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RotateTo(Vector3 targetRot)
|
||||||
|
{
|
||||||
|
_doorTween?.Kill();
|
||||||
|
_doorTween = targetChild
|
||||||
|
.DOLocalRotate(targetRot, rotationDuration)
|
||||||
|
.SetEase(rotationEase);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Collections.IEnumerator RunDoorCutscene(Collider hit)
|
||||||
|
{
|
||||||
|
_cutsceneRunning = true;
|
||||||
|
if (disableTriggerAfterUse) { var c = GetComponent<Collider>(); if (c) c.enabled = false; }
|
||||||
|
|
||||||
|
// Find player components
|
||||||
|
Transform root = hit.attachedRigidbody ? hit.attachedRigidbody.transform : hit.transform.root;
|
||||||
|
var controller = root.GetComponentInChildren<CrateEscapePlayerControllerJoystick>();
|
||||||
|
var anim = root.GetComponentInChildren<ZibuAnimDriver>();
|
||||||
|
var rb = root.GetComponentInChildren<Rigidbody>();
|
||||||
|
|
||||||
|
// 1) Freeze player control & velocity
|
||||||
|
if (controller) controller.LockControls(true);
|
||||||
|
if (rb) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; }
|
||||||
|
|
||||||
|
// 2) Work out exit position / facing
|
||||||
|
Vector3 dst = exitPoint ? exitPoint.position : (transform.position + transform.forward * exitForwardDistance);
|
||||||
|
if (rb) dst.y = rb.position.y; else dst.y = root.position.y;
|
||||||
|
|
||||||
|
Vector3 faceDir = dst - root.position; faceDir.y = 0f;
|
||||||
|
if (faceDir.sqrMagnitude > 0.0001f)
|
||||||
{
|
{
|
||||||
RotateTo(targetRotationOnEnter);
|
Quaternion look = Quaternion.LookRotation(faceDir.normalized, Vector3.up);
|
||||||
|
// quick face toward the door exit
|
||||||
|
root.DORotateQuaternion(look, faceDuration).SetEase(Ease.OutQuad);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTriggerExit(Collider other)
|
// 3) Celebration anim
|
||||||
{
|
if (anim && !string.IsNullOrEmpty(celebrationStateName))
|
||||||
if (other.CompareTag(playerTag))
|
anim.PlayState(celebrationStateName, 0.1f);
|
||||||
{
|
|
||||||
RotateTo(targetRotationOnExit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RotateTo(Vector3 targetRot)
|
yield return new WaitForSeconds(celebrationDuration);
|
||||||
{
|
|
||||||
// Kill any existing tween to avoid stacking
|
|
||||||
currentTween?.Kill();
|
|
||||||
|
|
||||||
currentTween = targetChild.DOLocalRotate(
|
// 4) Walk/Run anim
|
||||||
targetRot,
|
if (anim && !string.IsNullOrEmpty(walkStateName))
|
||||||
rotationDuration
|
anim.PlayState(walkStateName, 0.1f);
|
||||||
).SetEase(rotationEase);
|
|
||||||
|
// 5) Move player outside the door
|
||||||
|
Tween moveT;
|
||||||
|
if (rb != null)
|
||||||
|
moveT = rb.DOMove(dst, walkMoveDuration).SetEase(walkMoveEase).SetUpdate(UpdateType.Fixed);
|
||||||
|
else
|
||||||
|
moveT = root.DOMove(dst, walkMoveDuration).SetEase(walkMoveEase);
|
||||||
|
|
||||||
|
yield return moveT.WaitForCompletion();
|
||||||
|
|
||||||
|
// 6) Level complete signal
|
||||||
|
CrateEscapeGameManager.Instance?.OnLevelCompleteTriggered();
|
||||||
|
|
||||||
|
// (Optional) keep controls locked while level completes. Remove next line if you want to give control back:
|
||||||
|
// if (controller) controller.LockControls(false);
|
||||||
|
|
||||||
|
_cutsceneRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,11 @@ public class ZibuAnimDriver : MonoBehaviour
|
|||||||
default: CrossFadeSafe(state.ToString(), xfade); break;
|
default: CrossFadeSafe(state.ToString(), xfade); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public void PlayState(string stateName, float xfade = 0.1f)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(stateName)) return;
|
||||||
|
CrossFadeSafe(stateName, xfade);
|
||||||
|
}
|
||||||
|
|
||||||
void CrossFadeSafe(string stateName, float transition)
|
void CrossFadeSafe(string stateName, float transition)
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@ EditorBuildSettings:
|
|||||||
- enabled: 0
|
- enabled: 0
|
||||||
path: Assets/Scenes/ChaseRun.unity
|
path: Assets/Scenes/ChaseRun.unity
|
||||||
guid: be6c423b3d68dcb48bc49a7d2ed4957d
|
guid: be6c423b3d68dcb48bc49a7d2ed4957d
|
||||||
- enabled: 0
|
- enabled: 1
|
||||||
path: Assets/Scenes/CrateEscape.unity
|
path: Assets/Scenes/CrateEscape.unity
|
||||||
guid: af5c5d2a2d201e24f8c4913ae531addf
|
guid: af5c5d2a2d201e24f8c4913ae531addf
|
||||||
- enabled: 0
|
- enabled: 0
|
||||||
@ -44,7 +44,7 @@ EditorBuildSettings:
|
|||||||
- enabled: 0
|
- enabled: 0
|
||||||
path: Assets/Scenes/BlockDrop.unity
|
path: Assets/Scenes/BlockDrop.unity
|
||||||
guid: a99fde5053c68e0478ef4665b9c7b510
|
guid: a99fde5053c68e0478ef4665b9c7b510
|
||||||
- enabled: 1
|
- enabled: 0
|
||||||
path: Assets/Scenes/SkyWalker.unity
|
path: Assets/Scenes/SkyWalker.unity
|
||||||
guid: 71a02188e9a650b47a6c28ddde2142d6
|
guid: 71a02188e9a650b47a6c28ddde2142d6
|
||||||
- enabled: 0
|
- enabled: 0
|
||||||
|
@ -44,10 +44,10 @@ PlayerSettings:
|
|||||||
m_HolographicTrackingLossScreen: {fileID: 0}
|
m_HolographicTrackingLossScreen: {fileID: 0}
|
||||||
defaultScreenWidth: 1920
|
defaultScreenWidth: 1920
|
||||||
defaultScreenHeight: 1080
|
defaultScreenHeight: 1080
|
||||||
defaultScreenWidthWeb: 960
|
defaultScreenWidthWeb: 600
|
||||||
defaultScreenHeightWeb: 600
|
defaultScreenHeightWeb: 960
|
||||||
m_StereoRenderingPath: 0
|
m_StereoRenderingPath: 0
|
||||||
m_ActiveColorSpace: 0
|
m_ActiveColorSpace: 1
|
||||||
unsupportedMSAAFallback: 0
|
unsupportedMSAAFallback: 0
|
||||||
m_SpriteBatchVertexThreshold: 300
|
m_SpriteBatchVertexThreshold: 300
|
||||||
m_MTRendering: 1
|
m_MTRendering: 1
|
||||||
@ -536,7 +536,7 @@ PlayerSettings:
|
|||||||
m_Automatic: 1
|
m_Automatic: 1
|
||||||
- m_BuildTarget: WebGLSupport
|
- m_BuildTarget: WebGLSupport
|
||||||
m_APIs: 0b000000
|
m_APIs: 0b000000
|
||||||
m_Automatic: 0
|
m_Automatic: 1
|
||||||
m_BuildTargetVRSettings:
|
m_BuildTargetVRSettings:
|
||||||
- m_BuildTarget: Standalone
|
- m_BuildTarget: Standalone
|
||||||
m_Enabled: 0
|
m_Enabled: 0
|
||||||
@ -828,7 +828,7 @@ PlayerSettings:
|
|||||||
webGLWasmArithmeticExceptions: 0
|
webGLWasmArithmeticExceptions: 0
|
||||||
webGLLinkerTarget: 1
|
webGLLinkerTarget: 1
|
||||||
webGLThreadsSupport: 0
|
webGLThreadsSupport: 0
|
||||||
webGLDecompressionFallback: 1
|
webGLDecompressionFallback: 0
|
||||||
webGLInitialMemorySize: 32
|
webGLInitialMemorySize: 32
|
||||||
webGLMaximumMemorySize: 2048
|
webGLMaximumMemorySize: 2048
|
||||||
webGLMemoryGrowthMode: 2
|
webGLMemoryGrowthMode: 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user