/// <summary> /// Internal method which activates the state and then deactivates the state after the specified amount of time. /// </summary> /// <param name="stateGameObject">The GameObject to set the state on.</param> /// <param name="stateName">The name of the state to activate and then deactivate.</param> /// <param name="time">The amount of time that should elapse before the state is disabled.</param> private void DeactivateStateTimerInternal(GameObject stateGameObject, string stateName, float time) { if (m_DisableStateTimerMap == null) { m_DisableStateTimerMap = new Dictionary <GameObject, Dictionary <string, ScheduledEventBase> >(); } if (m_DisableStateTimerMap.TryGetValue(stateGameObject, out var stateNameEventMap)) { if (stateNameEventMap.TryGetValue(stateName, out var disableEvent)) { // The state name exists. This means that the timer is currently active and should first been cancelled. SchedulerBase.Cancel(disableEvent); disableEvent = SchedulerBase.Schedule(time, DeactivateState, stateGameObject, stateName); } else { // The state name hasn't been added yet. Add it to the map. disableEvent = SchedulerBase.Schedule(time, DeactivateState, stateGameObject, stateName); stateNameEventMap.Add(stateName, disableEvent); } } else { // Neither the GameObject nor the state has been activated. Create the maps. stateNameEventMap = new Dictionary <string, ScheduledEventBase>(); var disableEvent = SchedulerBase.Schedule(time, DeactivateState, stateGameObject, stateName); stateNameEventMap.Add(stateName, disableEvent); m_DisableStateTimerMap.Add(stateGameObject, stateNameEventMap); } }
/// <summary> /// Determines the location to respawn the object to and then does the respawn. /// </summary> public void Respawn() { m_ScheduledRespawnEvent = null; if (m_PositioningMode != SpawnPositioningMode.None) { Vector3 position; Quaternion rotation; if (m_PositioningMode == SpawnPositioningMode.SpawnPoint) { position = m_Transform.position; rotation = m_Transform.rotation; // If the object can't be spawned then try again in the future. if (!SpawnPointManager.GetPlacement(m_GameObject, m_Grouping, ref position, ref rotation)) { m_ScheduledRespawnEvent = SchedulerBase.Schedule(Random.Range(m_MinRespawnTime, m_MaxRespawnTime), Respawn); return; } } else // Spawn Location. { position = m_StartPosition; rotation = m_StartRotation; } Respawn(position, rotation, true); } else { Respawn(m_Transform.position, m_Transform.rotation, false); } }
/// <summary> /// Stops the cast. /// </summary> public override void Stop() { if (!m_Active) { return; } m_ParticleSystem.Stop(true, ParticleSystemStopBehavior.StopEmitting); // Optionally fade the particle out of the world. if (m_FadeOutDuration > 0) { if (m_FadeEvent != null) { SchedulerBase.Cancel(m_FadeEvent); m_FadeEvent = null; } if (m_Renderers == null) { m_Renderers = m_ParticleSystem.GetComponentsInChildren <ParticleSystemRenderer>(); } var interval = m_FadeOutDuration / (1 / m_FadeStep); // Reset the alpha if the renderers have no fade in duration. if (m_FadeInDuration == 0) { SetRendererAlpha(1); } m_FadeEvent = SchedulerBase.Schedule(interval, FadeMaterials, interval, 0f); } m_Active = false; base.Stop(); }
/// <summary> /// Updates the action. /// </summary> public override void Update() { if (!m_Active) { return; } var active = false; for (int i = 0; i < m_Materials.Count; ++i) { var color = m_Materials[i].GetColor(m_ColorID); color.a = Mathf.MoveTowards(color.a, m_TargetAlpha, m_FadeSpeed); m_Materials[i].SetColor(m_ColorID, color); if (color.a != m_TargetAlpha) { active = true; } } #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER // Update isn't called automatically for the remote players. if (active && m_MagicItem.NetworkInfo != null && !m_MagicItem.NetworkInfo.IsLocalPlayer()) { m_UpdateEvent = SchedulerBase.Schedule(0.001f, Update); } #endif m_Active = active; }
/// <summary> /// Sets the hit point that the tracer should move to. /// </summary> /// <param name="hitPoint">The hit point position.</param> public virtual void Initialize(Vector3 hitPoint) { m_LineRenderer.SetPosition(0, m_Transform.position); m_LineRenderer.SetPosition(1, hitPoint); SchedulerBase.Schedule(m_VisibleTime, DestroyObject); }
/// <summary> /// Updates the timescale according to the specified change speed. /// </summary> /// <param name="activate">Has the timescale been activated?</param> private void UpdateTimeScale(bool activate) { m_Time += m_TimeScaleChangeSpeed; var startTime = activate ? 1 : m_TimeScale; var endTime = activate ? m_TimeScale : 1; m_CharacterLocomotion.TimeScale = Mathf.SmoothStep(startTime, endTime, m_Time); if (m_Time > 1) { m_Time = 0; if (m_Active) { // Reset the time after the duration. m_Active = false; m_Time = 0; SchedulerBase.Schedule(m_Duration, UpdateTimeScale, !activate); } else { for (int i = 0; i < m_Children.Length; ++i) { m_Children[i].SetActive(true); } } } else { // Keep updating the timescale until the time is 1. SchedulerBase.Schedule(0.05f, UpdateTimeScale, activate); } }
/// <summary> /// The grenade should start to cook. /// </summary> /// <param name="originator">The object that instantiated the trajectory object.</param> public void StartCooking(GameObject originator) { SetOriginator(originator, Vector3.up); // The grenade should destruct after a specified amount of time. m_ScheduledDeactivation = SchedulerBase.Schedule(m_Lifespan, Deactivate); }
/// <summary> /// Reduces the health by the damage amount. /// </summary> private void ReduceHealth() { m_Health.Damage(m_DamageAmount.RandomValue); if (m_Health.IsAlive()) { // Keep reducing the object's health until is is no longer alive. SchedulerBase.Schedule(m_HealthReductionInterval.RandomValue, ReduceHealth); } else { // After the object is no longer alive spawn some wood shreds. These shreds should be cleaned up after a random // amount of time. var crateTransform = transform; m_SpawnedCrate = ObjectPoolBase.Instantiate(m_DestroyedCrate, crateTransform.position, crateTransform.rotation); var maxDestroyTime = 0f; for (int i = 0; i < m_SpawnedCrate.transform.childCount; ++i) { var destroyTime = m_WoodShreadRemovalTime.RandomValue; if (destroyTime > maxDestroyTime) { maxDestroyTime = destroyTime; } Destroy(m_SpawnedCrate.transform.GetChild(i).gameObject, destroyTime); } m_StopEvent = SchedulerBase.Schedule(maxDestroyTime, StopParticles); } }
/// <summary> /// An object has entered the trigger. /// </summary> /// <param name="other">The object that entered the trigger.</param> private void OnTriggerEnter(Collider other) { if (m_Health != null) { return; } // A main character collider is required. if (!MathUtility.InLayerMask(other.gameObject.layer, 1 << LayerManager.Character)) { return; } // The object must be a character. var characterLocomotion = other.GetComponentInParent <UltimateCharacterLocomotion>(); if (characterLocomotion == null) { return; } // With a health component. var health = characterLocomotion.GetComponent <Health>(); if (health == null) { return; } m_Health = health; m_HealthTransform = health.transform; m_ScheduledDamageEvent = SchedulerBase.Schedule(m_InitialDamageDelay, Damage); }
/// <summary> /// Fades all of the materials which belong to the renderers. /// </summary> /// <param name="interval">The time interval which updates the fade.</param> /// <param name="targetAlpha">The target alpha value.</param> private void FadeMaterials(float interval, float targetAlpha) { var arrived = true; for (int i = 0; i < m_Renderers.Length; ++i) { if (!m_Renderers[i].material.HasProperty(m_MaterialColorID)) { continue; } var color = m_Renderers[i].material.GetColor(m_MaterialColorID); color.a = Mathf.MoveTowards(color.a, targetAlpha, m_FadeStep); m_Renderers[i].material.SetColor(m_MaterialColorID, color); // Schedule the method again if the material isn't at the desired fade value. if (color.a != targetAlpha) { arrived = false; } } if (arrived) { m_FadeEvent = null; } else { m_FadeEvent = SchedulerBase.Schedule(interval, FadeMaterials, interval, targetAlpha); } }
/// <summary> /// Change the spring types to the specified type. /// </summary> /// <param name="type">The type to change the value to.</param> private void ChangeSpringType(SpringType type) { // Don't switch types if the type is equal to the current type or if the enable state event is active. This will occur if a new button is pressed // within the time that it takes to invoke the scheduled event. if (m_SpringType == type || m_EnableStateEvent != null) { return; } // Reset the button color and deactivate the previous character. The same character may be activated again depending on the spring type. if (m_SpringType != SpringType.None) { SetButtonColor((int)m_SpringType, m_NormalColor); if (m_ActiveCharacter != null) { m_ActiveCharacter.SetActive(false); StateManager.SetState(m_ActiveCharacter, System.Enum.GetName(typeof(SpringType), m_SpringType), false); } } // Remember the old spring type and activate the new. The button should reflect the selected spring type. var prevSpringType = m_SpringType; m_SpringType = type; SetButtonColor((int)m_SpringType, m_PressedColor); // If the previous spring type isn't None then a button was pressed. Activate the new character. if (prevSpringType != SpringType.None) { var prevCharacter = m_ActiveCharacter; // The active character depends on the spring type. if (m_SpringType == SpringType.Astronaut || m_SpringType == SpringType.DrunkPerson) { m_ActiveCharacter = m_DrunkAstronautCharacter; } else if (m_SpringType == SpringType.Giant) { m_ActiveCharacter = m_GiantCharacter; } else { m_ActiveCharacter = m_Character; } // Activate the correct character and set the camera to the character if that character changed. This shouldn't be done if the character didn't // change so the camera doesn't snap into position. var characterChange = m_ActiveCharacter != prevCharacter; m_ActiveCharacter.SetActive(true); if (characterChange) { m_CameraController.Character = m_ActiveCharacter; } // Wait a small amount of time if the springs are off so the item can get back into the correct position while the springs are enabled. m_EnableStateEvent = SchedulerBase.Schedule(m_SpringType == SpringType.SpringsOff ? 0.4f : 0, EnableSpringState, characterChange); } EnableInput(); }
/// <summary> /// Adjusts the collider's center position to the specified value. /// </summary> /// <param name="targetOffset">The desired offset value.</param> private void AdjustCenterOffset(Vector3 targetOffset) { var delta = targetOffset - m_CenterOffset; m_CapsuleCollider.center += delta; m_CapsuleCollider.height += delta.y / 2; m_ColliderOffsetEvent = null; if (!m_CharacterLocomotion.UsingHorizontalCollisionDetection) { m_CenterOffset = targetOffset; return; } // Apply the offset if there are no collisions. var collisionEnabled = m_CharacterLocomotion.CollisionLayerEnabled; m_CharacterLocomotion.EnableColliderCollisionLayer(false); Vector3 firstEndCap, secondEndCap; MathUtility.CapsuleColliderEndCaps(m_CapsuleCollider, m_Transform.position, m_Transform.rotation, out firstEndCap, out secondEndCap); if (Physics.OverlapCapsuleNonAlloc(firstEndCap, secondEndCap, m_CapsuleCollider.radius * MathUtility.ColliderRadiusMultiplier(m_CapsuleCollider), m_OverlapColliders, m_CharacterLayerManager.SolidObjectLayers, QueryTriggerInteraction.Ignore) > 0) { m_CapsuleCollider.center -= delta; m_CapsuleCollider.height -= delta.y / 2; m_ColliderOffsetEvent = SchedulerBase.Schedule(Time.fixedDeltaTime, AdjustCenterOffset, targetOffset); } else { m_CenterOffset = targetOffset; } m_CharacterLocomotion.EnableColliderCollisionLayer(collisionEnabled); }
/// <summary> /// Determines if a controller is connected. /// </summary> private void CheckForController() { if (!CanCheckForController) { return; } var controllerConencted = Input.GetJoystickNames().Length > 0; if (m_ControllerConnected != controllerConencted) { m_ControllerConnected = controllerConencted; #if FIRST_PERSON_CONTROLLER || THIRD_PERSON_CONTROLLER if (!string.IsNullOrEmpty(m_ConnectedControllerState)) { StateManager.SetState(gameObject, m_ConnectedControllerState, m_ControllerConnected); } #endif EventHandler.ExecuteEvent <bool>(gameObject, "OnInputControllerConnected", m_ControllerConnected); } // Schedule the controller check event if the rate is positive. // UnityEngine.Input.GetJoystickNames generates garbage so limit the amount of time the controller is checked. if (m_ControllerConnectedCheckRate > 0 && (m_ControllerCheckEvent == null || !m_ControllerCheckEvent.Active)) { m_ControllerCheckEvent = SchedulerBase.Schedule(m_ControllerConnectedCheckRate, CheckForController); } }
/// <summary> /// Fades the message according to the fade speed. /// </summary> private void FadeMessage() { m_ObjectAlphaColor = Mathf.Max(m_ObjectAlphaColor - m_ObjectFadeSpeed, 0); if (m_ObjectAlphaColor == 0) { m_GameObject.SetActive(false); m_ShouldFade = false; m_ScheduledFade = null; m_ObjectPickup = null; return; } // Fade the text and icon. if (m_Text != null) { var color = m_Text.color; color.a = m_ObjectAlphaColor; m_Text.color = color; } if (m_Icon) { var color = m_Icon.color; color.a = m_ObjectAlphaColor; m_Icon.color = color; } // Keep fading until there is nothing left to fade. m_ScheduledFade = SchedulerBase.Schedule(0.01f, FadeMessage); }
/// <summary> /// Fades the flame audio source. /// </summary> private void FadeAudioSource() { m_FlameParticleAudioSource.volume -= m_AudioSourceFadeAmount; if (m_FlameParticleAudioSource.volume > 0) { SchedulerBase.Schedule(0.2f, FadeAudioSource); } }
/// <summary> /// Clears the delta drag position. /// </summary> private void DampenDeltaPosition() { m_DeltaPosition /= (1 + m_ActiveDragDamping); if (m_DeltaPosition.sqrMagnitude > 0.1f) { m_ActiveDragScheduler = SchedulerBase.Schedule(Time.fixedDeltaTime, DampenDeltaPosition); } }
/// <summary> /// The ability has started. /// </summary> protected override void AbilityStarted() { base.AbilityStarted(); if (!m_DamageVisualizationCompleteEvent.WaitForAnimationEvent) { m_CompleteEvent = SchedulerBase.Schedule(m_DamageVisualizationCompleteEvent.Duration, OnDamageVisualizationComplete); } }
/// <summary> /// Schedules an auto update if the auto update value type is not set to none. /// </summary> /// <param name="delay">The amount to delay the attribute update event by.</param> public void ScheduleAutoUpdate(float delay) { SchedulerBase.Cancel(m_AutoUpdateEvent); if ((m_AutoUpdateValueType == AutoUpdateValue.Increase && m_Value != m_MaxValue) || (m_AutoUpdateValueType == AutoUpdateValue.Decrease && m_Value != m_MinValue)) { m_AutoUpdateEvent = SchedulerBase.Schedule(delay, UpdateValue); } }
/// <summary> /// Adds the specified object to the set. /// </summary> /// <param name="obj">The object that has been updated.</param> /// <param name="autoClear">Should the object updated map be automatically cleared on the next tick?</param> public static void AddUpdatedObject(object obj, bool autoClear) { s_ObjectUpdated.Add(obj); if (autoClear && s_ObjectClearEvent == null) { s_ObjectClearEvent = SchedulerBase.Schedule(0.0001f, ClearUpdatedObjectsEvent); } }
/// <summary> /// Resets the gravity direction and align to gravity to their stopping values. /// </summary> protected void ResetAlignToGravity() { if (m_StopGravityDirection.sqrMagnitude > 0) { m_CharacterLocomotion.GravityDirection = m_StopGravityDirection.normalized; } // Wait a frame to allow the camera to reset its rotation. This is useful if the ability is stopped in a single frame. m_AlignToGravityReset = SchedulerBase.Schedule(Time.deltaTime * 2, DoAlignToGravityReset); }
/// <summary> /// Disable the component when the Rigidbody has settled. /// </summary> private void CheckVelocity() { if (m_Rigidbody.velocity.sqrMagnitude < 0.01f) { m_TriggerEnableEvent = SchedulerBase.Schedule(m_TriggerEnableDelay, EnableTrigger); return; } // The Rigidbody hasn't settled yet - check the velocity again in the future. SchedulerBase.Schedule(0.2f, CheckVelocity); }
/// <summary> /// Apply damage to the health component. /// </summary> private void Damage() { m_Health.Damage(m_DamageAmount, m_HealthTransform.position + Random.insideUnitSphere, Vector3.zero, 0); // Apply the damage again if the object still has health remaining. if (m_Health.Value > 0) { m_ScheduledDamageEvent = SchedulerBase.Schedule(m_DamageInterval, Damage); } }
/// <summary> /// Initializes the object. This will be called from an object creating the projectile (such as a weapon). /// </summary> /// <param name="velocity">The velocity to apply.</param> /// <param name="torque">The torque to apply.</param> /// <param name="damageProcessor">Processes the damage dealt to a Damage Target.</param> /// <param name="damageAmount">The amount of damage to apply to the hit object.</param> /// <param name="impactForce">The amount of force to apply to the hit object.</param> /// <param name="impactForceFrames">The number of frames to add the force to.</param> /// <param name="impactLayers">The layers that the projectile can impact with.</param> /// <param name="impactStateName">The name of the state to activate upon impact.</param> /// <param name="impactStateDisableTimer">The number of seconds until the impact state is disabled.</param> /// <param name="surfaceImpact">A reference to the Surface Impact triggered when the object hits an object.</param> /// <param name="originator">The object that instantiated the trajectory object.</param> public override void Initialize(Vector3 velocity, Vector3 torque, DamageProcessor damageProcessor, float damageAmount, float impactForce, int impactForceFrames, LayerMask impactLayers, string impactStateName, float impactStateDisableTimer, SurfaceImpact surfaceImpact, GameObject originator) { // The projectile can deactivate after it comes in contact with another object or after a specified amount of time. Do the scheduling here to allow // it to activate after a set amount of time. if (m_Lifespan > 0) { m_ScheduledDeactivation = SchedulerBase.Schedule(m_Lifespan, Deactivate); } base.Initialize(velocity, torque, damageProcessor, damageAmount, impactForce, impactForceFrames, impactLayers, impactStateName, impactStateDisableTimer, surfaceImpact, originator); }
/// <summary> /// The action has started. /// </summary> /// <param name="origin">The location that the cast originates from.</param> public override void Start(Transform origin) { // Initialize any starting values after all of the actions have been deserialized. if (m_ColorID == 0) { m_ColorID = Shader.PropertyToID(m_ColorPropertyName); if (!m_BeginAction && m_MagicItem.BeginActions != null) { for (int i = 0; i < m_MagicItem.BeginActions.Length; ++i) { if (m_MagicItem.BeginActions[i] is FadeMaterials) { m_BeginFadeMaterials = m_MagicItem.BeginActions[i] as FadeMaterials; break; } } } } // The Object Fader should reset. EventHandler.ExecuteEvent(m_Character, "OnCharacterIndependentFade", true, true); if (m_BeginFadeMaterials == null) { // Return the previous objects. if (m_OriginalMaterialValuesMap.Count > 0) { for (int i = 0; i < m_Materials.Count; ++i) { GenericObjectPool.Return(m_OriginalMaterialValuesMap[m_Materials[i]]); m_OriginalMaterialValuesMap.Remove(m_Materials[i]); } } m_Materials.Clear(); m_ActiveMaterials.Clear(); EnableRendererFade(); } else { m_Materials = m_BeginFadeMaterials.Materials; m_ActiveMaterials = m_BeginFadeMaterials.ActiveMaterials; m_OriginalMaterialValuesMap = m_BeginFadeMaterials.OriginalMaterialValuesMap; } m_Active = true; #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER // Update isn't called automatically for the remote players. if (m_MagicItem.NetworkInfo != null && !m_MagicItem.NetworkInfo.IsLocalPlayer()) { m_UpdateEvent = SchedulerBase.Schedule(0.001f, Update); } #endif }
/// <summary> /// Lerp the text color back to the original material emissive color. /// </summary> private void UpdateTextColor() { var t = (Time.time - m_StartFadeTime) / m_FadeTextDuration; m_HeadshotTextMaterial.SetColor(m_Color, Color.Lerp(m_HeadshotColor, m_OriginalColor, t)); m_HeadshotTextMaterial.SetColor(m_EmissiveColorID, Color.Lerp(m_HeadshotEmissiveColor, m_OriginalEmissiveColor, t)); // Keep updating until the text is the original color. if (t < 1) { m_TextColorChangeEvent = SchedulerBase.Schedule(0.02f, UpdateTextColor); } }
/// <summary> /// The character has been attached to the camera. Initialze the camera-related values. /// </summary> /// <param name="cameraController">The camera controller attached to the character. Can be null.</param> private void OnAttachCamera(CameraController cameraController) { m_CameraController = cameraController; m_CameraTransform = (m_CameraController != null ? m_CameraController.Transform : null); // Delay the parent being set to prevent the transform from changing when the application is shutting down. SchedulerBase.Schedule(0.001f, () => { m_Transform.parent = (m_CameraController != null ? m_CameraTransform : (m_CharacterTransform != null ? m_CharacterTransform.parent : null)); m_Transform.localPosition = Vector3.zero; m_Transform.localRotation = Quaternion.identity; }); m_Pitch = m_Yaw = 0; enabled = IsActive(); }
/// <summary> /// Enables or disables the modifier. /// </summary> /// <param name="enable">Should the modifier be enabled?</param> public void EnableModifier(bool enable) { if (m_Attribute == null) { return; } m_DisableAutoUpdateEvent = null; // The attribute can be changed by a single value... if (enable && (!m_AutoUpdate || m_AutoUpdateStartDelay > 0)) { m_Attribute.Value += m_Amount; } if (!m_AutoUpdate || m_AutoUpdating == enable) { return; } // ...Or a change with a longer duration. m_AutoUpdating = enable; if (enable) { m_Attribute.StoreRestoreAutoUpdateValues(true); m_Attribute.AutoUpdateAmount = Mathf.Abs(m_Amount); m_Attribute.AutoUpdateStartDelay = -1; // Set the start delay to -1 to prevent the attribute from updating when changing the attribute properties. m_Attribute.AutoUpdateInterval = m_AutoUpdateInterval; m_Attribute.AutoUpdateValueType = m_Amount < 0 ? Attribute.AutoUpdateValue.Decrease : Attribute.AutoUpdateValue.Increase; m_Attribute.AutoUpdateStartDelay = m_AutoUpdateStartDelay; // Setting the actual start delay will update the value. if (m_AutoUpdateDuration > 0) { m_DisableAutoUpdateEvent = SchedulerBase.Schedule(m_AutoUpdateDuration, EnableModifier, false); } } else { m_Attribute.StoreRestoreAutoUpdateValues(false); if (m_DisableAutoUpdateEvent != null) { SchedulerBase.Cancel(m_DisableAutoUpdateEvent); m_DisableAutoUpdateEvent = null; } m_Attribute.ScheduleAutoUpdate(m_Attribute.AutoUpdateStartDelay); } EventHandler.ExecuteEvent(this, "OnAttributeModifierAutoUpdateEnabled", this, enable); }
/// <summary> /// The object has been disabled. /// </summary> protected virtual void OnDisable() { #if ULTIMATE_CHARACTER_CONTROLLER_MULTIPLAYER if (m_NetworkInfo != null && !m_NetworkInfo.IsLocalPlayer()) { return; } #endif if (ScheduleRespawnOnDisable && m_ScheduledRespawnEvent == null) { m_ScheduledRespawnEvent = SchedulerBase.Schedule(Random.Range(m_MinRespawnTime, m_MaxRespawnTime), Respawn); } }
/// <summary> /// Starts to fade the particle materials. /// </summary> /// <param name="particle">The GameObject that the particle belongs to.</param> private void StartMaterialFade(GameObject particle) { // Optionally fade the particle into the world. if (m_FadeInDuration > 0) { if (m_Renderers == null) { m_Renderers = particle.GetComponentsInChildren <ParticleSystemRenderer>(); } SetRendererAlpha(0); var interval = m_FadeInDuration / (1 / m_FadeStep); m_FadeEvent = SchedulerBase.Schedule(interval, FadeMaterials, interval, 1f); } }
/// <summary> /// The crate has been destroyed. Stop the particles. /// </summary> private void StopParticles() { if (m_StopEvent == null) { return; } SchedulerBase.Cancel(m_StopEvent); m_StopEvent = null; m_DamageTrigger.enabled = false; m_FlameParticle.Stop(true); m_FlameParticle = null; SchedulerBase.Schedule(0.2f, FadeAudioSource); }