Demon fixes
This commit is contained in:
@@ -4998,7 +4998,7 @@ CapsuleCollider:
|
||||
m_Radius: 2.5
|
||||
m_Height: 2.5
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 1, z: 0}
|
||||
m_Center: {x: 0, y: 1.5, z: 0}
|
||||
--- !u!1 &5185508652979790054
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
9759
Assets/AI/Demon/Meteor.prefab
Normal file
9759
Assets/AI/Demon/Meteor.prefab
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/AI/Demon/Meteor.prefab.meta
Normal file
8
Assets/AI/Demon/Meteor.prefab.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f99aa3faf46a5f94985344f44aaf21aa
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 100100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
472
Assets/AI/Demon/MeteorProjectile.cs
Normal file
472
Assets/AI/Demon/MeteorProjectile.cs
Normal file
@@ -0,0 +1,472 @@
|
||||
using Invector;
|
||||
using Invector.vCharacterController;
|
||||
using Lean.Pool;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace DemonBoss.Magic
|
||||
{
|
||||
public class MeteorProjectile : MonoBehaviour
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Tag of the target to pick impact point from on spawn")]
|
||||
public string targetTag = "Player";
|
||||
|
||||
[Tooltip("If true, raycasts down from impact point to find ground (better visuals)")]
|
||||
public bool snapImpactToGround = true;
|
||||
|
||||
[Tooltip("LayerMask used when snapping impact point to ground")]
|
||||
public LayerMask groundMask = ~0;
|
||||
|
||||
[Header("Entry Geometry")]
|
||||
[Tooltip("If > 0, teleports start Y above impact point by this height (keeps current XZ). 0 = keep original height.")]
|
||||
public float spawnHeightAboveTarget = 12f;
|
||||
|
||||
[Tooltip("Entry angle measured DOWN from horizontal (e.g. 15° = shallow, 45° = steeper)")]
|
||||
[Range(0f, 89f)]
|
||||
public float entryAngleDownFromHorizontal = 20f;
|
||||
|
||||
[Tooltip("How closely to aim towards the target in XZ before tilting down (0..1). 1 = purely towards target, 0 = purely downward.")]
|
||||
[Range(0f, 1f)]
|
||||
public float azimuthAimWeight = 0.85f;
|
||||
|
||||
[Header("Movement")]
|
||||
[Tooltip("Initial speed magnitude in units per second")]
|
||||
public float speed = 30f;
|
||||
|
||||
[Tooltip("Extra downward acceleration to add a slight curve (0 = disabled)")]
|
||||
public float gravityLikeAcceleration = 0f;
|
||||
|
||||
[Tooltip("Rotate the meteor mesh to face velocity")]
|
||||
public bool rotateToVelocity = true;
|
||||
|
||||
[Tooltip("Seconds before the meteor auto-despawns")]
|
||||
public float maxLifeTime = 12f;
|
||||
|
||||
[Header("Impact / AOE")]
|
||||
[Tooltip("Explosion radius at impact (0 = no AOE, only single target)")]
|
||||
public float explosionRadius = 0f;
|
||||
|
||||
[Tooltip("Layers that should receive AOE damage")]
|
||||
public LayerMask explosionMask = ~0;
|
||||
|
||||
[Tooltip("If true, AOE will only damage colliders with the same tag as targetTag")]
|
||||
public bool aoeOnlyTargetTag = false;
|
||||
|
||||
[Tooltip("Sphere radius for continuous collision checks")]
|
||||
public float castRadius = 0.25f;
|
||||
|
||||
[Tooltip("Layers that block the meteor during flight (ground/walls/etc.)")]
|
||||
public LayerMask collisionMask = ~0;
|
||||
|
||||
[Tooltip("Damage that will be dealt to Player")]
|
||||
public float damage = 40.0f;
|
||||
|
||||
[Tooltip("Knockback that will be applied to Player")]
|
||||
public float knockbackForce = 5.0f;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable verbose logs and debug rays")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
[Header("Events")]
|
||||
[Tooltip("Invoked once on any impact (use for VFX/SFX/CameraShake)")]
|
||||
public UnityEvent onImpact;
|
||||
|
||||
#endregion Configuration
|
||||
|
||||
#region Runtime
|
||||
|
||||
private Vector3 impactPoint;
|
||||
private Vector3 velocityDir;
|
||||
private Vector3 velocity;
|
||||
private float timer = 0f;
|
||||
private bool hasDealtDamage = false;
|
||||
private bool hasImpacted = false;
|
||||
private Vector3 prevPos;
|
||||
|
||||
#endregion Runtime
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Resets runtime state, locks the impact point, computes entry direction and initial velocity.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
timer = 0f;
|
||||
hasDealtDamage = false;
|
||||
hasImpacted = false;
|
||||
|
||||
// Acquire player and lock impact point
|
||||
Vector3 targetPos = GetPlayerPosition();
|
||||
impactPoint = snapImpactToGround ? SnapPointToGround(targetPos) : targetPos;
|
||||
|
||||
// Optionally start from above to guarantee top-down feel
|
||||
if (spawnHeightAboveTarget > 0f)
|
||||
{
|
||||
Vector3 p = transform.position;
|
||||
p.y = impactPoint.y + spawnHeightAboveTarget;
|
||||
transform.position = p;
|
||||
}
|
||||
|
||||
Vector3 toImpactXZ = new Vector3(impactPoint.x, transform.position.y, impactPoint.z) - transform.position;
|
||||
Vector3 azimuthDir = toImpactXZ.sqrMagnitude > 0.0001f ? toImpactXZ.normalized : transform.forward;
|
||||
|
||||
Vector3 tiltAxis = Vector3.Cross(Vector3.up, azimuthDir);
|
||||
if (tiltAxis.sqrMagnitude < 0.0001f) tiltAxis = Vector3.right;
|
||||
Quaternion downTilt = Quaternion.AngleAxis(-entryAngleDownFromHorizontal, tiltAxis);
|
||||
Vector3 tiltedDir = (downTilt * azimuthDir).normalized;
|
||||
|
||||
velocityDir = Vector3.Slerp(Vector3.down, tiltedDir, Mathf.Clamp01(azimuthAimWeight)).normalized;
|
||||
|
||||
velocity = velocityDir * Mathf.Max(0f, speed);
|
||||
|
||||
if (rotateToVelocity && velocity.sqrMagnitude > 0.0001f)
|
||||
transform.rotation = Quaternion.LookRotation(velocity.normalized);
|
||||
|
||||
if (enableDebug)
|
||||
{
|
||||
Debug.Log($"[Meteor] Impact={impactPoint}, start={transform.position}, dir={velocityDir}");
|
||||
Debug.DrawLine(transform.position, transform.position + velocityDir * 6f, Color.red, 3f);
|
||||
Debug.DrawLine(transform.position, impactPoint, Color.yellow, 2f);
|
||||
}
|
||||
|
||||
prevPos = transform.position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integrates motion with optional downward acceleration, performs a SphereCast for continuous collision,
|
||||
/// rotates to face velocity, and handles lifetime expiry.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
|
||||
// Downward "gravity-like" acceleration
|
||||
if (gravityLikeAcceleration > 0f)
|
||||
velocity += Vector3.down * gravityLikeAcceleration * Time.deltaTime;
|
||||
|
||||
Vector3 nextPos = transform.position + velocity * Time.deltaTime;
|
||||
|
||||
// Continuous collision: SphereCast from current position toward next
|
||||
Vector3 castDir = nextPos - transform.position;
|
||||
float castDist = castDir.magnitude;
|
||||
if (castDist > 0.0001f)
|
||||
{
|
||||
if (Physics.SphereCast(transform.position, castRadius, castDir.normalized,
|
||||
out RaycastHit hit, castDist, collisionMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
transform.position = hit.point;
|
||||
Impact(hit.collider, hit.point, hit.normal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No hit — apply movement
|
||||
transform.position = nextPos;
|
||||
|
||||
// Face velocity if requested
|
||||
if (rotateToVelocity && velocity.sqrMagnitude > 0.0001f)
|
||||
transform.rotation = Quaternion.LookRotation(velocity.normalized);
|
||||
|
||||
prevPos = transform.position;
|
||||
|
||||
// Auto-despawn on lifetime cap
|
||||
if (timer >= maxLifeTime)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[Meteor] Max lifetime reached → Despawn");
|
||||
Despawn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger-based contact: funnels into unified Impact().
|
||||
/// </summary>
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
// Even if not the intended target, a meteor typically impacts anything it touches
|
||||
Impact(other, transform.position, -SafeNormal(velocity));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collision-based contact: funnels into unified Impact().
|
||||
/// </summary>
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
var cp = collision.contacts.Length > 0 ? collision.contacts[0] : default;
|
||||
Impact(collision.collider, cp.point != default ? cp.point : transform.position, cp.normal != default ? cp.normal : Vector3.up);
|
||||
}
|
||||
|
||||
#endregion Unity
|
||||
|
||||
#region Helpers: Target / Ground
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target's current position (by tag). If not found, projects forward from current position.
|
||||
/// </summary>
|
||||
private Vector3 GetPlayerPosition()
|
||||
{
|
||||
GameObject playerObj = GameObject.FindGameObjectWithTag(targetTag);
|
||||
if (playerObj != null)
|
||||
return playerObj.transform.position;
|
||||
|
||||
if (enableDebug) Debug.LogWarning($"[Meteor] No target with tag '{targetTag}' found, using forward guess.");
|
||||
return transform.position + transform.forward * 10f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts down from above the source point to find ground; returns original point if no hit.
|
||||
/// </summary>
|
||||
private Vector3 SnapPointToGround(Vector3 source)
|
||||
{
|
||||
Vector3 start = source + Vector3.up * 50f;
|
||||
if (Physics.Raycast(start, Vector3.down, out RaycastHit hit, 200f, groundMask, QueryTriggerInteraction.Ignore))
|
||||
return hit.point;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a safe normalized version of a vector (falls back to Vector3.down if tiny).
|
||||
/// </summary>
|
||||
private static Vector3 SafeNormal(Vector3 v)
|
||||
{
|
||||
return v.sqrMagnitude > 0.0001f ? v.normalized : Vector3.down;
|
||||
}
|
||||
|
||||
#endregion Helpers: Target / Ground
|
||||
|
||||
#region Impact & Damage
|
||||
|
||||
/// <summary>
|
||||
/// Unified impact handler (idempotent). Deals single-target damage if applicable,
|
||||
/// optional AOE damage, invokes events, and despawns.
|
||||
/// </summary>
|
||||
private void Impact(Collider hitCol, Vector3 hitPoint, Vector3 hitNormal)
|
||||
{
|
||||
if (hasImpacted) return;
|
||||
hasImpacted = true;
|
||||
|
||||
if (enableDebug)
|
||||
Debug.Log($"[Meteor] Impact with {(hitCol ? hitCol.name : "null")} at {hitPoint}");
|
||||
|
||||
// Single target (original logic)
|
||||
if (!hasDealtDamage && hitCol != null && hitCol.CompareTag(targetTag))
|
||||
DealDamageToTarget(hitCol);
|
||||
|
||||
// AOE (if enabled)
|
||||
if (explosionRadius > 0f)
|
||||
DealAreaDamage(hitPoint);
|
||||
|
||||
// Hook for VFX/SFX/CameraShake
|
||||
onImpact?.Invoke();
|
||||
|
||||
Despawn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deals damage to the intended single target using Invector interfaces/components.
|
||||
/// </summary>
|
||||
private void DealDamageToTarget(Collider targetCollider)
|
||||
{
|
||||
if (enableDebug) Debug.Log($"[MeteorDamage] Dealing {damage} damage to: {targetCollider.name}");
|
||||
|
||||
Vector3 hitPoint = GetClosestPointOnCollider(targetCollider);
|
||||
Vector3 hitDirection = GetHitDirection(targetCollider);
|
||||
|
||||
vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage))
|
||||
{
|
||||
sender = transform,
|
||||
hitPosition = hitPoint
|
||||
};
|
||||
|
||||
if (knockbackForce > 0f)
|
||||
damageInfo.force = hitDirection * knockbackForce;
|
||||
|
||||
bool damageDealt = false;
|
||||
|
||||
// vIDamageReceiver
|
||||
var receiver = targetCollider.GetComponent<vIDamageReceiver>() ??
|
||||
targetCollider.GetComponentInParent<vIDamageReceiver>();
|
||||
if (receiver != null)
|
||||
{
|
||||
receiver.TakeDamage(damageInfo);
|
||||
damageDealt = true;
|
||||
hasDealtDamage = true;
|
||||
if (enableDebug) Debug.Log("[MeteorDamage] Damage via vIDamageReceiver");
|
||||
}
|
||||
|
||||
// vHealthController
|
||||
if (!damageDealt)
|
||||
{
|
||||
var health = targetCollider.GetComponent<vHealthController>() ??
|
||||
targetCollider.GetComponentInParent<vHealthController>();
|
||||
if (health != null)
|
||||
{
|
||||
health.TakeDamage(damageInfo);
|
||||
damageDealt = true;
|
||||
hasDealtDamage = true;
|
||||
if (enableDebug) Debug.Log("[MeteorDamage] Damage via vHealthController");
|
||||
}
|
||||
}
|
||||
|
||||
// vThirdPersonController (including Beyond variant)
|
||||
if (!damageDealt)
|
||||
{
|
||||
var tpc = targetCollider.GetComponent<vThirdPersonController>() ??
|
||||
targetCollider.GetComponentInParent<vThirdPersonController>();
|
||||
if (tpc != null)
|
||||
{
|
||||
if (tpc is Beyond.bThirdPersonController beyond)
|
||||
{
|
||||
if (!beyond.GodMode && !beyond.isImmortal)
|
||||
{
|
||||
tpc.TakeDamage(damageInfo);
|
||||
damageDealt = true;
|
||||
hasDealtDamage = true;
|
||||
if (enableDebug) Debug.Log("[MeteorDamage] Damage via bThirdPersonController");
|
||||
}
|
||||
else if (enableDebug)
|
||||
{
|
||||
Debug.Log("[MeteorDamage] Target is immortal/GodMode – no damage");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tpc.TakeDamage(damageInfo);
|
||||
damageDealt = true;
|
||||
hasDealtDamage = true;
|
||||
if (enableDebug) Debug.Log("[MeteorDamage] Damage via vThirdPersonController");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!damageDealt && enableDebug)
|
||||
Debug.LogWarning("[MeteorDamage] No valid damage receiver found!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deals AOE damage to all colliders within explosionRadius using Invector-compatible logic.
|
||||
/// </summary>
|
||||
private void DealAreaDamage(Vector3 center)
|
||||
{
|
||||
Collider[] hits = Physics.OverlapSphere(center, explosionRadius, explosionMask, QueryTriggerInteraction.Ignore);
|
||||
foreach (var col in hits)
|
||||
{
|
||||
if (col == null) continue;
|
||||
if (aoeOnlyTargetTag && !col.CompareTag(targetTag)) continue;
|
||||
|
||||
// Avoid double-hitting the same single target if already processed
|
||||
if (col.CompareTag(targetTag) && hasDealtDamage) continue;
|
||||
|
||||
Vector3 hp = col.ClosestPoint(center);
|
||||
Vector3 dir = (col.bounds.center - center).normalized;
|
||||
|
||||
vDamage damageInfo = new vDamage(Mathf.RoundToInt(damage))
|
||||
{
|
||||
sender = transform,
|
||||
hitPosition = hp,
|
||||
force = knockbackForce > 0f ? dir * knockbackForce : Vector3.zero
|
||||
};
|
||||
|
||||
bool dealt = false;
|
||||
|
||||
var receiver = col.GetComponent<vIDamageReceiver>() ?? col.GetComponentInParent<vIDamageReceiver>();
|
||||
if (receiver != null) { receiver.TakeDamage(damageInfo); dealt = true; }
|
||||
|
||||
if (!dealt)
|
||||
{
|
||||
var health = col.GetComponent<vHealthController>() ?? col.GetComponentInParent<vHealthController>();
|
||||
if (health != null) { health.TakeDamage(damageInfo); dealt = true; }
|
||||
}
|
||||
|
||||
if (!dealt)
|
||||
{
|
||||
var tpc = col.GetComponent<vThirdPersonController>() ?? col.GetComponentInParent<vThirdPersonController>();
|
||||
if (tpc != null)
|
||||
{
|
||||
if (tpc is Beyond.bThirdPersonController beyond)
|
||||
{
|
||||
if (!beyond.GodMode && !beyond.isImmortal) { tpc.TakeDamage(damageInfo); dealt = true; }
|
||||
}
|
||||
else { tpc.TakeDamage(damageInfo); dealt = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consider AOE as the final damage application for this projectile
|
||||
hasDealtDamage = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on a collider to this projectile's position.
|
||||
/// </summary>
|
||||
private Vector3 GetClosestPointOnCollider(Collider col)
|
||||
{
|
||||
return col.ClosestPoint(transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a hit direction from projectile toward a collider's center; falls back to current velocity.
|
||||
/// </summary>
|
||||
private Vector3 GetHitDirection(Collider col)
|
||||
{
|
||||
Vector3 dir = (col.bounds.center - transform.position).normalized;
|
||||
return dir.sqrMagnitude > 0.0001f ? dir : SafeNormal(velocity);
|
||||
}
|
||||
|
||||
#endregion Impact & Damage
|
||||
|
||||
#region Pooling
|
||||
|
||||
/// <summary>
|
||||
/// Returns this projectile to the LeanPool.
|
||||
/// </summary>
|
||||
private void Despawn()
|
||||
{
|
||||
if (enableDebug) Debug.Log("[Meteor] Despawn via LeanPool");
|
||||
LeanPool.Despawn(gameObject);
|
||||
}
|
||||
|
||||
#endregion Pooling
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Editor-only gizmos to visualize entry direction and impact point when selected.
|
||||
/// </summary>
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.25f);
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(transform.position, transform.position + SafeNormal(velocity.sqrMagnitude > 0 ? velocity : velocityDir) * 3f);
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(impactPoint, 0.35f);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#region Validation
|
||||
|
||||
/// <summary>
|
||||
/// Clamps and validates configuration values in the inspector.
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
speed = Mathf.Max(0f, speed);
|
||||
maxLifeTime = Mathf.Max(0.01f, maxLifeTime);
|
||||
explosionRadius = Mathf.Max(0f, explosionRadius);
|
||||
castRadius = Mathf.Clamp(castRadius, 0.01f, 2f);
|
||||
entryAngleDownFromHorizontal = Mathf.Clamp(entryAngleDownFromHorizontal, 0f, 89f);
|
||||
azimuthAimWeight = Mathf.Clamp01(azimuthAimWeight);
|
||||
}
|
||||
|
||||
#endregion Validation
|
||||
}
|
||||
}
|
||||
2
Assets/AI/Demon/MeteorProjectile.cs.meta
Normal file
2
Assets/AI/Demon/MeteorProjectile.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d6ec41ae03923f45a963ca66dbb6c56
|
||||
@@ -6,8 +6,10 @@ using UnityEngine;
|
||||
namespace DemonBoss.Magic
|
||||
{
|
||||
/// <summary>
|
||||
/// StateAction for Meteor Strike spell - boss summons meteor falling on player
|
||||
/// First places decal at player position, after 1.5s checks if path is clear and drops rock
|
||||
/// FSM Action: Boss calls down a meteor.
|
||||
/// Shows a decal at the player's position, locks an impact point on ground,
|
||||
/// then spawns the MeteorProjectile prefab above that point after a delay.
|
||||
/// Cancels cleanly on state exit.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Invector/FSM/Actions/DemonBoss/Call Meteor")]
|
||||
public class SA_CallMeteor : vStateAction
|
||||
@@ -15,338 +17,128 @@ namespace DemonBoss.Magic
|
||||
public override string categoryName => "DemonBoss/Magic";
|
||||
public override string defaultName => "Call Meteor";
|
||||
|
||||
[Header("Meteor Configuration")]
|
||||
[Tooltip("Meteor rock prefab")]
|
||||
public GameObject rockPrefab;
|
||||
[Header("Meteor Setup")]
|
||||
[Tooltip("Prefab with MeteorProjectile component")]
|
||||
public GameObject meteorPrefab;
|
||||
|
||||
[Tooltip("Decal prefab showing impact location")]
|
||||
[Tooltip("Visual decal prefab marking impact point")]
|
||||
public GameObject decalPrefab;
|
||||
|
||||
[Tooltip("Height from which meteor falls")]
|
||||
public float meteorHeight = 30f;
|
||||
[Tooltip("Height above ground at which meteor spawns")]
|
||||
public float spawnHeight = 40f;
|
||||
|
||||
[Tooltip("Delay between placing decal and spawning meteor")]
|
||||
[Tooltip("Delay before meteor spawns after decal")]
|
||||
public float castDelay = 1.5f;
|
||||
|
||||
[Tooltip("Obstacle check radius above target")]
|
||||
public float obstacleCheckRadius = 2f;
|
||||
|
||||
[Tooltip("Layer mask for obstacles blocking meteor")]
|
||||
public LayerMask obstacleLayerMask = -1;
|
||||
|
||||
[Tooltip("Layer mask for ground")]
|
||||
public LayerMask groundLayerMask = -1;
|
||||
|
||||
[Tooltip("Animator trigger name for meteor summoning animation")]
|
||||
public string animatorTrigger = "CastMeteor";
|
||||
|
||||
[Header("Targeting")]
|
||||
[Tooltip("Maximum raycast distance to find ground")]
|
||||
public float maxGroundDistance = 100f;
|
||||
|
||||
[Tooltip("Height above ground for obstacle checking")]
|
||||
public float airCheckHeight = 5f;
|
||||
[Header("Ground")]
|
||||
[Tooltip("Layer mask for ground raycast")]
|
||||
public LayerMask groundMask = -1;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("Enable debug logging")]
|
||||
public bool enableDebug = false;
|
||||
|
||||
[Tooltip("Show gizmos in Scene View")]
|
||||
public bool showGizmos = true;
|
||||
|
||||
private Transform player;
|
||||
private GameObject spawnedDecal;
|
||||
private Vector3 impactPoint;
|
||||
|
||||
private Transform playerTransform;
|
||||
private Vector3 targetPosition;
|
||||
private bool meteorCasting = false;
|
||||
private MonoBehaviour coroutineRunner;
|
||||
private Coroutine _spawnRoutine;
|
||||
private CoroutineRunner _runner;
|
||||
|
||||
/// <summary>
|
||||
/// Main action execution method called by FSM
|
||||
/// Entry point for the FSM action, delegates to OnEnter/OnExit depending on execution type.
|
||||
/// </summary>
|
||||
public override void DoAction(vIFSMBehaviourController fsmBehaviour, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||
public override void DoAction(vIFSMBehaviourController fsm, vFSMComponentExecutionType executionType = vFSMComponentExecutionType.OnStateUpdate)
|
||||
{
|
||||
if (executionType == vFSMComponentExecutionType.OnStateEnter)
|
||||
{
|
||||
OnStateEnter(fsmBehaviour);
|
||||
}
|
||||
else if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||
{
|
||||
OnStateExit(fsmBehaviour);
|
||||
}
|
||||
OnEnter(fsm);
|
||||
|
||||
if (executionType == vFSMComponentExecutionType.OnStateExit)
|
||||
OnExit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when entering state - starts meteor casting
|
||||
/// Acquires the player, locks the impact point on the ground, shows the decal,
|
||||
/// and starts the delayed meteor spawn coroutine.
|
||||
/// </summary>
|
||||
private void OnStateEnter(vIFSMBehaviourController fsmBehaviour)
|
||||
private void OnEnter(vIFSMBehaviourController fsm)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Starting meteor casting");
|
||||
|
||||
FindPlayer(fsmBehaviour);
|
||||
|
||||
var animator = fsmBehaviour.transform.GetComponent<Animator>();
|
||||
if (animator != null && !string.IsNullOrEmpty(animatorTrigger))
|
||||
player = GameObject.FindGameObjectWithTag("Player")?.transform;
|
||||
if (player == null)
|
||||
{
|
||||
animator.SetTrigger(animatorTrigger);
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Set trigger: {animatorTrigger}");
|
||||
}
|
||||
|
||||
StartMeteorCast(fsmBehaviour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when exiting state - cleanup
|
||||
/// </summary>
|
||||
private void OnStateExit(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Exiting meteor state");
|
||||
meteorCasting = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds player transform
|
||||
/// </summary>
|
||||
private void FindPlayer(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
GameObject player = GameObject.FindGameObjectWithTag("Player");
|
||||
if (player != null)
|
||||
{
|
||||
playerTransform = player.transform;
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Player found by tag");
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No player found!");
|
||||
return;
|
||||
}
|
||||
|
||||
var aiController = fsmBehaviour as Invector.vCharacterController.AI.vIControlAI;
|
||||
if (aiController != null && aiController.currentTarget != null)
|
||||
{
|
||||
playerTransform = aiController.currentTarget.transform;
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Player found through AI target");
|
||||
return;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Player not found!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts meteor casting process
|
||||
/// </summary>
|
||||
private void StartMeteorCast(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] No target - finishing state");
|
||||
return;
|
||||
}
|
||||
|
||||
if (FindGroundUnderPlayer(out Vector3 groundPos))
|
||||
{
|
||||
targetPosition = groundPos;
|
||||
|
||||
SpawnDecal(targetPosition);
|
||||
|
||||
coroutineRunner = fsmBehaviour.transform.GetComponent<MonoBehaviour>();
|
||||
if (coroutineRunner != null)
|
||||
{
|
||||
coroutineRunner.StartCoroutine(MeteorCastCoroutine(fsmBehaviour));
|
||||
}
|
||||
else
|
||||
{
|
||||
CastMeteorImmediate();
|
||||
}
|
||||
|
||||
meteorCasting = true;
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor target: {targetPosition}");
|
||||
}
|
||||
// Raycast down from the player to lock the impact point
|
||||
Vector3 rayStart = player.position + Vector3.up * 5f;
|
||||
if (Physics.Raycast(rayStart, Vector3.down, out RaycastHit hit, 100f, groundMask, QueryTriggerInteraction.Ignore))
|
||||
impactPoint = hit.point;
|
||||
else
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Cannot find ground under player");
|
||||
}
|
||||
impactPoint = player.position;
|
||||
|
||||
// Spawn decal
|
||||
if (decalPrefab != null)
|
||||
spawnedDecal = LeanPool.Spawn(decalPrefab, impactPoint, Quaternion.identity);
|
||||
|
||||
// Get or add a dedicated runner for coroutines
|
||||
var hostGO = fsm.transform.gameObject;
|
||||
_runner = hostGO.GetComponent<CoroutineRunner>();
|
||||
if (_runner == null) _runner = hostGO.AddComponent<CoroutineRunner>();
|
||||
|
||||
// Start delayed spawn
|
||||
_spawnRoutine = _runner.StartCoroutine(SpawnMeteorAfterDelay());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds ground under player using raycast
|
||||
/// Cancels the pending spawn and cleans up the decal when exiting the state.
|
||||
/// </summary>
|
||||
private bool FindGroundUnderPlayer(out Vector3 groundPosition)
|
||||
private void OnExit()
|
||||
{
|
||||
groundPosition = Vector3.zero;
|
||||
|
||||
Vector3 playerPos = playerTransform.position;
|
||||
Vector3 rayStart = playerPos + Vector3.up * 5f;
|
||||
|
||||
Ray groundRay = new Ray(rayStart, Vector3.down);
|
||||
if (Physics.Raycast(groundRay, out RaycastHit hit, maxGroundDistance, groundLayerMask))
|
||||
if (_runner != null && _spawnRoutine != null)
|
||||
{
|
||||
groundPosition = hit.point;
|
||||
return true;
|
||||
_runner.StopCoroutine(_spawnRoutine);
|
||||
_spawnRoutine = null;
|
||||
}
|
||||
|
||||
groundPosition = playerPos;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns decal showing meteor impact location
|
||||
/// </summary>
|
||||
private void SpawnDecal(Vector3 position)
|
||||
{
|
||||
if (decalPrefab == null)
|
||||
{
|
||||
if (enableDebug) Debug.LogWarning("[SA_CallMeteor] Missing decal prefab");
|
||||
return;
|
||||
}
|
||||
|
||||
Quaternion decalRotation = Quaternion.identity;
|
||||
|
||||
Ray surfaceRay = new Ray(position + Vector3.up * 2f, Vector3.down);
|
||||
if (Physics.Raycast(surfaceRay, out RaycastHit hit, 5f, groundLayerMask))
|
||||
{
|
||||
decalRotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
|
||||
}
|
||||
|
||||
spawnedDecal = LeanPool.Spawn(decalPrefab, position, decalRotation);
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Decal spawned at: {position}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine handling meteor casting process with delay
|
||||
/// </summary>
|
||||
private IEnumerator MeteorCastCoroutine(vIFSMBehaviourController fsmBehaviour)
|
||||
{
|
||||
yield return new WaitForSeconds(castDelay);
|
||||
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Checking if path is clear for meteor");
|
||||
|
||||
if (IsPathClearForMeteor(targetPosition))
|
||||
{
|
||||
SpawnMeteor(targetPosition);
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Meteor spawned");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Path blocked - meteor not spawned");
|
||||
}
|
||||
|
||||
CleanupDecal();
|
||||
|
||||
DEC_CheckCooldown.SetCooldownStatic(fsmBehaviour, "Meteor", 20f);
|
||||
|
||||
meteorCasting = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediate meteor cast without coroutine (fallback)
|
||||
/// </summary>
|
||||
private void CastMeteorImmediate()
|
||||
{
|
||||
if (IsPathClearForMeteor(targetPosition))
|
||||
{
|
||||
SpawnMeteor(targetPosition);
|
||||
}
|
||||
CleanupDecal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if path above target is clear for meteor
|
||||
/// </summary>
|
||||
private bool IsPathClearForMeteor(Vector3 targetPos)
|
||||
{
|
||||
Vector3 checkStart = targetPos + Vector3.up * airCheckHeight;
|
||||
Vector3 checkEnd = targetPos + Vector3.up * meteorHeight;
|
||||
|
||||
if (Physics.CheckSphere(checkStart, obstacleCheckRadius, obstacleLayerMask))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Ray pathRay = new Ray(checkEnd, Vector3.down);
|
||||
if (Physics.SphereCast(pathRay, obstacleCheckRadius, meteorHeight - airCheckHeight, obstacleLayerMask))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns meteor in air above target
|
||||
/// </summary>
|
||||
private void SpawnMeteor(Vector3 targetPos)
|
||||
{
|
||||
if (rockPrefab == null)
|
||||
{
|
||||
Debug.LogError("[SA_CallMeteor] Missing meteor prefab!");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 meteorSpawnPos = targetPos + Vector3.up * meteorHeight;
|
||||
|
||||
GameObject meteor = LeanPool.Spawn(rockPrefab, meteorSpawnPos, Quaternion.identity);
|
||||
|
||||
Rigidbody meteorRb = meteor.GetComponent<Rigidbody>();
|
||||
if (meteorRb != null)
|
||||
{
|
||||
meteorRb.linearVelocity = Vector3.down * 5f;
|
||||
meteorRb.angularVelocity = Random.insideUnitSphere * 2f;
|
||||
}
|
||||
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor spawned at height: {meteorHeight}m");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes decal from map
|
||||
/// </summary>
|
||||
private void CleanupDecal()
|
||||
{
|
||||
if (spawnedDecal != null)
|
||||
{
|
||||
LeanPool.Despawn(spawnedDecal);
|
||||
spawnedDecal = null;
|
||||
if (enableDebug) Debug.Log("[SA_CallMeteor] Decal removed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if meteor is currently being cast
|
||||
/// Waits for the configured cast delay and then spawns the meteor.
|
||||
/// </summary>
|
||||
public bool IsCasting()
|
||||
private IEnumerator SpawnMeteorAfterDelay()
|
||||
{
|
||||
return meteorCasting;
|
||||
yield return new WaitForSeconds(castDelay);
|
||||
SpawnMeteor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns meteor target position
|
||||
/// Spawns the meteor prefab above the locked impact point and cleans up the decal.
|
||||
/// </summary>
|
||||
public Vector3 GetTargetPosition()
|
||||
private void SpawnMeteor()
|
||||
{
|
||||
return targetPosition;
|
||||
}
|
||||
if (meteorPrefab == null) return;
|
||||
|
||||
/// <summary>
|
||||
/// Draws gizmos in Scene View for debugging
|
||||
/// </summary>
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showGizmos) return;
|
||||
Vector3 spawnPos = impactPoint + Vector3.up * spawnHeight;
|
||||
LeanPool.Spawn(meteorPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
if (meteorCasting && targetPosition != Vector3.zero)
|
||||
if (enableDebug) Debug.Log($"[SA_CallMeteor] Meteor spawned at {spawnPos}, impact={impactPoint}");
|
||||
|
||||
if (spawnedDecal != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(targetPosition, 1f);
|
||||
|
||||
Vector3 spawnPos = targetPosition + Vector3.up * meteorHeight;
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(spawnPos, 0.5f);
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(spawnPos, targetPosition);
|
||||
|
||||
Vector3 checkPos = targetPosition + Vector3.up * airCheckHeight;
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireSphere(checkPos, obstacleCheckRadius);
|
||||
LeanPool.Despawn(spawnedDecal);
|
||||
spawnedDecal = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight helper component dedicated to running coroutines for ScriptableObject actions.
|
||||
/// </summary>
|
||||
public sealed class CoroutineRunner : MonoBehaviour
|
||||
{ }
|
||||
}
|
||||
@@ -112,8 +112,8 @@ MonoBehaviour:
|
||||
selectedTrue: 0
|
||||
selectedFalse: 0
|
||||
cooldownKey: Turret
|
||||
cooldownTime: 120
|
||||
availableAtStart: 0
|
||||
cooldownTime: 75
|
||||
availableAtStart: 1
|
||||
enableDebug: 1
|
||||
--- !u!114 &-7880340678925941764
|
||||
MonoBehaviour:
|
||||
@@ -164,7 +164,7 @@ MonoBehaviour:
|
||||
selectedTrue: 0
|
||||
selectedFalse: 0
|
||||
cooldownKey: Meteor
|
||||
cooldownTime: 25
|
||||
cooldownTime: 75
|
||||
availableAtStart: 1
|
||||
enableDebug: 0
|
||||
--- !u!114 &-6568372008305276654
|
||||
@@ -259,23 +259,15 @@ MonoBehaviour:
|
||||
parentFSM: {fileID: 11400000}
|
||||
executionType: -1
|
||||
editingName: 0
|
||||
rockPrefab: {fileID: 1947871717301538, guid: 9591667a35466484096c6e63785e136c, type: 3}
|
||||
decalPrefab: {fileID: 1743301284569550, guid: ad2ab49d0a2a17148bd51c93d977bdbc,
|
||||
meteorPrefab: {fileID: 1947871717301538, guid: f99aa3faf46a5f94985344f44aaf21aa,
|
||||
type: 3}
|
||||
meteorHeight: 30
|
||||
decalPrefab: {fileID: 0}
|
||||
spawnHeight: 40
|
||||
castDelay: 1.5
|
||||
obstacleCheckRadius: 2
|
||||
obstacleLayerMask:
|
||||
groundMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
groundLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
animatorBlockingBool: IsBlocking
|
||||
maxGroundDistance: 100
|
||||
airCheckHeight: 5
|
||||
enableDebug: 1
|
||||
showGizmos: 1
|
||||
--- !u!114 &-6379838510941931433
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 1
|
||||
@@ -345,11 +337,11 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 1
|
||||
decision: {fileID: 7927421991537792917}
|
||||
isValid: 1
|
||||
isValid: 0
|
||||
validated: 0
|
||||
- trueValue: 1
|
||||
decision: {fileID: -1886887719286938116}
|
||||
isValid: 1
|
||||
isValid: 0
|
||||
validated: 0
|
||||
trueState: {fileID: -2904979146780567904}
|
||||
falseState: {fileID: 0}
|
||||
@@ -409,11 +401,11 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 1
|
||||
decision: {fileID: 4031404829621142413}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
- trueValue: 1
|
||||
decision: {fileID: -2866484833343459521}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
trueState: {fileID: 2986668563461644515}
|
||||
falseState: {fileID: 0}
|
||||
@@ -667,7 +659,7 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 0
|
||||
decision: {fileID: 7927421991537792917}
|
||||
isValid: 1
|
||||
isValid: 0
|
||||
validated: 0
|
||||
trueState: {fileID: -312774025800194259}
|
||||
falseState: {fileID: 0}
|
||||
@@ -775,8 +767,8 @@ MonoBehaviour:
|
||||
executionType: -1
|
||||
editingName: 0
|
||||
crystalPrefab: {fileID: 115880, guid: b5eabfbed738a224284015c33b62e54b, type: 3}
|
||||
minSpawnDistance: 6
|
||||
maxSpawnDistance: 15
|
||||
minSpawnDistance: 10
|
||||
maxSpawnDistance: 20
|
||||
obstacleCheckRadius: 1
|
||||
groundCheckHeight: 2
|
||||
obstacleLayerMask:
|
||||
@@ -1286,10 +1278,10 @@ MonoBehaviour:
|
||||
nodeRect:
|
||||
serializedVersion: 2
|
||||
x: 840
|
||||
y: 415
|
||||
y: 410
|
||||
width: 150
|
||||
height: 62
|
||||
positionRect: {x: 840, y: 415}
|
||||
positionRect: {x: 840, y: 410}
|
||||
rectWidth: 150
|
||||
editingName: 1
|
||||
nodeColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
@@ -1309,13 +1301,13 @@ MonoBehaviour:
|
||||
trueRect:
|
||||
serializedVersion: 2
|
||||
x: 830
|
||||
y: 445
|
||||
y: 440
|
||||
width: 10
|
||||
height: 10
|
||||
falseRect:
|
||||
serializedVersion: 2
|
||||
x: 990
|
||||
y: 455
|
||||
y: 450
|
||||
width: 10
|
||||
height: 10
|
||||
selectedTrue: 0
|
||||
@@ -1490,7 +1482,7 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 1
|
||||
decision: {fileID: 7927421991537792917}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
trueState: {fileID: -2904979146780567904}
|
||||
falseState: {fileID: 0}
|
||||
@@ -1554,7 +1546,7 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 0
|
||||
decision: {fileID: -6379838510941931433}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
- trueValue: 1
|
||||
decision: {fileID: -7938248970223304488}
|
||||
@@ -1590,7 +1582,7 @@ MonoBehaviour:
|
||||
- decisions:
|
||||
- trueValue: 0
|
||||
decision: {fileID: -6379838510941931433}
|
||||
isValid: 0
|
||||
isValid: 1
|
||||
validated: 0
|
||||
- trueValue: 1
|
||||
decision: {fileID: 8113515040269600600}
|
||||
@@ -1712,14 +1704,14 @@ MonoBehaviour:
|
||||
canSetAsDefault: 1
|
||||
canEditName: 1
|
||||
canEditColor: 1
|
||||
isOpen: 1
|
||||
isOpen: 0
|
||||
isSelected: 1
|
||||
nodeRect:
|
||||
serializedVersion: 2
|
||||
x: 840
|
||||
y: 625
|
||||
width: 150
|
||||
height: 62
|
||||
height: 30
|
||||
positionRect: {x: 840, y: 625}
|
||||
rectWidth: 150
|
||||
editingName: 1
|
||||
@@ -1739,16 +1731,16 @@ MonoBehaviour:
|
||||
parentState: {fileID: 4162026404432437805}
|
||||
trueRect:
|
||||
serializedVersion: 2
|
||||
x: 830
|
||||
y: 655
|
||||
width: 10
|
||||
height: 10
|
||||
x: 915
|
||||
y: 640
|
||||
width: 0
|
||||
height: 0
|
||||
falseRect:
|
||||
serializedVersion: 2
|
||||
x: 990
|
||||
y: 665
|
||||
width: 10
|
||||
height: 10
|
||||
x: 915
|
||||
y: 640
|
||||
width: 0
|
||||
height: 0
|
||||
selectedTrue: 0
|
||||
selectedFalse: 0
|
||||
trueSideRight: 0
|
||||
@@ -1868,7 +1860,7 @@ MonoBehaviour:
|
||||
selectedTrue: 0
|
||||
selectedFalse: 0
|
||||
cooldownKey: Shield
|
||||
cooldownTime: 25
|
||||
cooldownTime: 75
|
||||
availableAtStart: 1
|
||||
enableDebug: 0
|
||||
--- !u!114 &8860036500635384459
|
||||
@@ -1908,15 +1900,15 @@ MonoBehaviour:
|
||||
canSetAsDefault: 1
|
||||
canEditName: 1
|
||||
canEditColor: 1
|
||||
isOpen: 0
|
||||
isOpen: 1
|
||||
isSelected: 0
|
||||
nodeRect:
|
||||
serializedVersion: 2
|
||||
x: 845
|
||||
y: 225
|
||||
y: 230
|
||||
width: 150
|
||||
height: 30
|
||||
positionRect: {x: 845, y: 225}
|
||||
height: 62
|
||||
positionRect: {x: 845, y: 230}
|
||||
rectWidth: 150
|
||||
editingName: 1
|
||||
nodeColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
@@ -1935,20 +1927,20 @@ MonoBehaviour:
|
||||
parentState: {fileID: 9112689765763526057}
|
||||
trueRect:
|
||||
serializedVersion: 2
|
||||
x: 920
|
||||
y: 240
|
||||
width: 0
|
||||
height: 0
|
||||
x: 835
|
||||
y: 260
|
||||
width: 10
|
||||
height: 10
|
||||
falseRect:
|
||||
serializedVersion: 2
|
||||
x: 920
|
||||
y: 240
|
||||
width: 0
|
||||
height: 0
|
||||
x: 995
|
||||
y: 270
|
||||
width: 10
|
||||
height: 10
|
||||
selectedTrue: 0
|
||||
selectedFalse: 0
|
||||
trueSideRight: 0
|
||||
falseSideRight: 0
|
||||
falseSideRight: 1
|
||||
decisionEditor: {fileID: 0}
|
||||
isOpen: 0
|
||||
scroolView: {x: 0, y: 0, z: 0}
|
||||
|
||||
Reference in New Issue
Block a user