/// <summary> /// Make this character perform the event specified in the given object /// </summary> public void PerformEvent(Brawler.Event e) { if (e == null) { Debug.LogError("Event passed to CharacterControl.PerformEvent() is null for character " + character.name); return; } // Stores true if the event specifies a starting time bool requiresStartTime = (e.type != Brawler.EventType.None); // Holds true if the event requires a duration to be specified bool requiresDuration = (e.type == Brawler.EventType.SlowMotion || e.type == Brawler.EventType.Force || e.type == Brawler.EventType.ColorFlash || e.type == Brawler.EventType.FreezeAnimation || e.type == Brawler.EventType.ScreenShake || e.type == Brawler.EventType.Tween || e.type == Brawler.EventType.Ghosting); // The starting time and duration of the event, in seconds float startTime = 0; float duration = 0; // If the event requires a starting time or duration to be specified, compute them and store them in their respective variables. if (requiresStartTime) { startTime = GetStartTime(e.startTime); } if (requiresDuration) { duration = GetDuration(e.duration, startTime); } // Call a coroutine. Start the given event in 'startTime' seconds. The event will last 'duration' seconds StartCoroutine(PerformEventCoroutine(e, startTime, duration)); }
private IEnumerator PlayTween(Brawler.Event e) { float startTime = 0; if (e.startTime.type == DurationType.Frame) { startTime = e.startTime.nFrames / CharacterAnimator.FRAME_RATE; } else { startTime = e.startTime.seconds; } Debug.Log("Play tween at " + startTime + " seconds"); yield return(new WaitForSeconds(startTime)); float duration; if (e.duration.type == DurationType.Frame) { duration = e.duration.nFrames / CharacterAnimator.FRAME_RATE; } else { duration = e.duration.seconds; } Debug.Log("Play tween for " + duration + " seconds"); tweener.PerformEvent(e.tweenEvent, duration); }
/// <summary> /// Update the given event. Updates the event's member variables to the correct info. For instance, /// if the event's type is set to 'CameraMovement', the target position of the camera must be updated. /// For instance, if the camera must move to the position of the targetted enemy, the event must be /// updated to store the Transform or position of this enemy /// </summary> private void UpdateEvent(Brawler.Event e) { // If the event is a CameraMovement event if (e.type == Brawler.EventType.CameraMovement) { // If the camera movement requires the camera to move to the location of the event which triggered the event if (e.cameraMovement.target == TargetPosition.Self) { // Set the camera to follow this character's Transform e.cameraMovement.targetTransform = character.Transform; } // Else, if the camera needs to move to the position of the object that was touched else if (e.cameraMovement.target == TargetPosition.TouchedObject) { // Set the camera to follow the targetObject which is targetted by the action that activated this event e.cameraMovement.targetTransform = targetObject.transform; } // Else, if the camera needs to move to the position where the user touched to activate this event else if (e.cameraMovement.target == TargetPosition.TouchedPosition) { // Tell the camera to move to the action's 'targetPosition', the position where the user last touched e.cameraMovement.targetPosition = targetPosition; } } }
/// <summary> /// Creates a new force instance. /// </summary> /// <param name="createOnCompleteEvent">If set to <c>true</c> create an onComplete event. This is set to true /// by default. This parameter is set to false when the Force is created inside a ForceEvent instance. In this /// case, creating an 'onComplete' Event would cause infinite recursion. This is because each ForceEvent holds /// an instance of a Force, which creates a new 'Brawler.Event'. Then, the Brawler.Event creates a new ForceEvent, /// which then creates a new Force, and the recursion never stops. Therefore, if this Force is a member variable /// inside a ForceEvent instance, the onComplete event should not be created. /// </param> public Force(bool createOnCompleteEvent) { if (createOnCompleteEvent) { // Create a new event which will be triggered when the force is done being applied onCompleteEvent = new Brawler.Event(); } }
/// <summary> /// Returns a deep copy of the given array of events. A deep copy creates a duplicate of each /// element in the given array, so that the two arrays don't share any references. /// </summary> public static Brawler.Event[] DeepCopy(Brawler.Event[] array) { Brawler.Event[] copy = new Brawler.Event[array.Length]; for (int i = 0; i < array.Length; i++) { copy[i] = new Brawler.Event(array[i]); } return(copy); }
/// <summary> /// Update the action's events so that their member variables correctly correspond to the action's /// current state. For instance, some CameraMovement events require the camera to look at the position /// which was touched when the event was triggered. In this case, the touched position of each CameraMovement /// has to be updated to correspond to the position of the touch that triggered the CameraMovement event. /// </summary> public void UpdateEvents() { // Cycle through each 'onStartEvent' in the action for (int i = 0; i < onStartEvents.Length; i++) { Brawler.Event e = onStartEvents[i]; // Update the event so that its member variables are set to the correct information UpdateEvent(e); } // Cycle through each 'Force' applied in the action for (int i = 0; i < forces.Length; i++) { // Retrieve the force's 'OnComplete' event Brawler.Event e = forces[i].onCompleteEvent; // Update the event so that its member variables are set to the correct information UpdateEvent(e); } // Cycle through each 'onStartEvent' in the action for (int i = 0; i < hitBoxes.Length; i++) { // Stores the events performed on the same character that performed this action. Brawler.Event[] selfEvents = hitBoxes[i].hitInfo.selfEvents; // Cycle through each 'selfEvent' for the hit for (int j = 0; j < selfEvents.Length; j++) { Brawler.Event e = selfEvents[j]; // Update the event so that its member variables are set to the correct information UpdateEvent(e); } // Stores the events performed by the adversary hit by the hit box. Brawler.Event[] adversaryEvents = hitBoxes[i].hitInfo.adversaryEvents; // Cycle through each event for the adversary hit by this hit box has to perform for (int j = 0; j < adversaryEvents.Length; j++) { Brawler.Event e = adversaryEvents[j]; // Update the event so that its member variables are set to the correct information UpdateEvent(e); } } }
/// <summary> /// Creates a new force instance given a template. /// </summary> /// <param name="createOnCompleteEvent">If set to <c>true</c> create an onComplete event. This is set to true /// by default. This parameter is set to false when the Force is created inside a ForceEvent instance. In this /// case, creating an 'onComplete' Event would cause infinite recursion. This is because each ForceEvent holds /// an instance of a Force, which creates a new 'Brawler.Event'. Then, the Brawler.Event creates a new ForceEvent, /// which then creates a new Force, and the recursion never stops. Therefore, if this Force is a member variable /// inside a ForceEvent instance, the onComplete event should not be created. /// </param> public Force(Force template, bool createOnCompleteEvent) { if (createOnCompleteEvent) { // Create a deep copy of the event onCompleteEvent = new Brawler.Event(template.onCompleteEvent); } // Copy the templates values into this new instance forceType = template.forceType; velocity = template.velocity; relativeToFacingDirection = template.relativeToFacingDirection; target = template.target; customTargetPosition = template.customTargetPosition; faceTarget = template.faceTarget; startTime = template.startTime; duration = template.duration; }
/// <summary> /// Called when the force is done being applied on the character. If the force has any events that should /// occur once it has ended, they will be performed here. /// </summary> public void OnForceEnd(Force force) { // Stores the action that should be completed once the given force is done being applied Brawler.Event eventOnComplete = force.onCompleteEvent; // Perform the event if it exists if (eventOnComplete != null) { // Perform the event that should be performed once the force is done being applied. character.CharacterControl.PerformEvent(eventOnComplete); } // If the given force is the same as the force being currently applied on the character if (currentForce == force) { // Nullify the current force acting on the character, since the force has just finished being applied if this statement is reached. currentForce = null; } }
/// <summary> /// Called when this hit box hits another character. This method is called from the CharacterCollider.OnCollision() /// method when a hit box enters a character's collider. This method performs necessary actions, such as inflicting /// damage on the character and playing an impact sound. /// </summary> public void OnHit(Character adversary) { // Stores the 'HitInfo' instance which dictates how much damage to deal when this HitBox hits an adversary HitInfo hitInfo = hitBoxInfo.hitInfo; // Inform the CharacterStats component that he was hit by a hit box. Inflicts damage to the character hit by this hit box adversary.CharacterStats.OnHit(hitInfo, hitBoxInfo.Character); // Generate a knockback force on the adversary. Wait until 'freezeFrames' frames have passed before applying the knockback. Knockback(adversary, hitInfo.freezeFrames); // Freeze the characters' animations for a small amount of time to add impact to the hit FreezeAnimations(hitBoxInfo.Character, adversary); // Cycle through the events in the 'hitInfo.selfEvents' array. These events are supposed to be executed by the character which // generated this hit for (int i = 0; i < hitInfo.selfEvents.Length; i++) { // Cache the event being cycled through Brawler.Event e = hitInfo.selfEvents[i]; // Make the character that hit the adversary perform the events specified in the 'hitInfo.events' array. These events // are meant to be triggered when the hit is registered. hitBoxInfo.Character.CharacterControl.PerformEvent(hitInfo.selfEvents[i]); } // Cycle through the events in the 'hitInfo.adversaryEvents' array. These events are supposed to be executed by the character which // received the hit for (int i = 0; i < hitInfo.adversaryEvents.Length; i++) { // Make the character that got hit (the adversary) perform the events specified in the 'hitInfo.adversaryEvents' array. These events // are meant to be triggered when the hit is registered. adversary.CharacterControl.PerformEvent(hitInfo.adversaryEvents[i]); } // Increment the combo for the character belonging to this hit box. This character just hit an opponent and must thus increase his combo hitBoxInfo.Character.CharacterStats.IncrementCombo(); // Play one of the impact sounds associated to this hit box. This plays a sound right when the hit box hits a target hitBoxInfo.Character.Sound.PlayRandomSound(hitBoxInfo.Action.impactSounds); }
/** Displays a foldout of a list of events */ public Brawler.Event[] Display() { showFoldout = EditorGUILayout.Foldout(showFoldout, title + " (" + events.Length + ")"); if (showFoldout) { EditorGUILayout.BeginVertical(); { EditorGUI.indentLevel++; // Cycle through each event for (int i = 0; i < events.Length; i++) { Brawler.Event e = events[i]; EditorGUILayout.BeginHorizontal(); { e.type = (Brawler.EventType)EditorGUILayout.EnumPopup("Type:", e.type); if (GUILayout.Button("X", GUILayout.Width(40))) { this.events = ArrayUtils.RemoveAt <Brawler.Event> (this.events, i); showStartTimeFoldouts = ArrayUtils.RemoveAt <bool>(showStartTimeFoldouts, i); showDurationFoldouts = ArrayUtils.RemoveAt <bool>(showDurationFoldouts, i); continue; } } EditorGUILayout.EndHorizontal(); if (e.type == Brawler.EventType.PerformAction) { // Select an action e.actionToPerform = (ActionScriptableObject)EditorGUILayout.ObjectField("Action:", e.actionToPerform, typeof(ActionScriptableObject), false); } else if (e.type == Brawler.EventType.PerformBasicAction) { // Select a basic action e.basicActionToPerform = (BasicActionType)EditorGUILayout.EnumPopup("Basic action:", e.basicActionToPerform); } else if (e.type == Brawler.EventType.CameraMovement) { // Set the camera settings e.cameraMovement.target = (TargetPosition)EditorGUILayout.EnumPopup("Target position:", e.cameraMovement.target); if (e.cameraMovement.target == TargetPosition.CustomPosition) { e.cameraMovement.targetPosition = EditorGUILayout.Vector2Field("Position to move to:", e.cameraMovement.targetPosition); } e.cameraMovement.zoom = EditorGUILayout.FloatField("Zoom:", e.cameraMovement.zoom); e.cameraMovement.cameraSpeed = EditorGUILayout.FloatField("Camera speed:", e.cameraMovement.cameraSpeed); } else if (e.type == Brawler.EventType.SoundEffect) { // Select the sound effect to play when the event is triggered e.soundEffect = (AudioClip)EditorGUILayout.ObjectField("Sound effect:", e.soundEffect, typeof(AudioClip), false); } else if (e.type == Brawler.EventType.SlowMotion) { e.slowMotion.timeScale = EditorGUILayout.Slider("Time scale:", e.slowMotion.timeScale, 0.001f, 1.0f); } else if (e.type == Brawler.EventType.ParticleEffect) { // Select a particle effect e.particleEvent.effect = (ParticleEffect)EditorGUILayout.EnumPopup("Particle Effect:", e.particleEvent.effect); e.particleEvent.spawnPoint = (ParticleSpawnPoint)EditorGUILayout.EnumPopup("Spawn Point:", e.particleEvent.spawnPoint); e.particleEvent.offset = EditorGUILayout.Vector3Field("Offset:", e.particleEvent.offset); } else if (e.type == Brawler.EventType.Force) { Force force = e.forceEvent; // Select a force type force.forceType = (ForceType)EditorGUILayout.EnumPopup("Force Type:", force.forceType); switch (force.forceType) { case ForceType.Velocity: force.velocity = EditorGUILayout.Vector2Field("Velocity:", force.velocity); force.relativeToFacingDirection = EditorGUILayout.Toggle("Relative to facing direction?", force.relativeToFacingDirection); break; case ForceType.Position: force.target = (TargetPosition)EditorGUILayout.EnumPopup("Target Position:", force.target); if (force.target == TargetPosition.CustomPosition) { force.customTargetPosition = EditorGUILayout.Vector2Field("Custom Position:", force.customTargetPosition); } force.faceTarget = EditorGUILayout.Toggle("Face target?", force.faceTarget); break; } } else if (e.type == Brawler.EventType.ColorFlash) { ColorFlash colorFlash = e.colorFlash; // Edit the color-flashing event colorFlash.color = EditorGUILayout.ColorField("Color:", colorFlash.color); colorFlash.renderInFront = EditorGUILayout.Toggle("Render in front", colorFlash.renderInFront); } else if (e.type == Brawler.EventType.Ghosting) { GhostEffect ghostEffect = e.ghostEffect; // Edit the ghosting effect ghostEffect.color = EditorGUILayout.ColorField("Color:", ghostEffect.color); ghostEffect.renderInFront = EditorGUILayout.Toggle("Render in front", ghostEffect.renderInFront); } else if (e.type == Brawler.EventType.ScreenShake) { ScreenShake screenShake = e.screenShake; // Modify the screen shake settings screenShake.speed = EditorGUILayout.FloatField("Speed:", screenShake.speed); screenShake.magnitude = EditorGUILayout.FloatField("Magnitude:", screenShake.magnitude); } else if (e.type == Brawler.EventType.Tween) { TweenEvent tweenEvent = e.tweenEvent; TweenEventEditor(tweenEvent); } // Stores true if the event being edited requires a starting time to be specified bool editStartTime = (e.type != Brawler.EventType.None); // If we require to edit the duration if (editStartTime) { // "Starting Time" foldout showStartTimeFoldouts[i] = EditorGUILayout.Foldout(showStartTimeFoldouts[i], "Starting Time"); if (showStartTimeFoldouts[i]) { ActionEditor.StartTimeFoldout(e.startTime); } } // Stores true if the event being edited requires a 'duration' to be specified bool editDuration = (e.type == Brawler.EventType.SlowMotion || e.type == Brawler.EventType.Force || e.type == Brawler.EventType.ColorFlash || e.type == Brawler.EventType.FreezeAnimation || e.type == Brawler.EventType.ScreenShake || e.type == Brawler.EventType.Tween || e.type == Brawler.EventType.Ghosting); // If we require to edit the duration if (editDuration) { // "Duration" foldout showDurationFoldouts[i] = EditorGUILayout.Foldout(showDurationFoldouts[i], "Duration"); if (showDurationFoldouts[i]) { ActionEditor.DurationFoldout(e.duration); } } EditorGUILayout.Space(); } // Add event ("+") button EditorGUILayout.BeginHorizontal(); { EditorGUILayout.LabelField(""); // Add new event if (GUILayout.Button("+", GUILayout.Width(40))) { this.events = ArrayUtils.Add <Brawler.Event>(this.events, new Brawler.Event()); showStartTimeFoldouts = ArrayUtils.Add <bool>(showStartTimeFoldouts, false); showDurationFoldouts = ArrayUtils.Add <bool>(showDurationFoldouts, false); } } EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel--; } EditorGUILayout.EndVertical(); } // Return the modified array of events return(events); }
/// <summary> /// Initialializes the basic moves' default properties. Accepts the Character which is performing these basic actions. /// This method is called in the 'ActionSetEditor.OnEnabled()' method every time the basic actions need to be edited. /// Ensures that, if ever a new basic action is created or edited, it is initialized with the correct properties /// </summary> public void Init() { // Sets the default properties for the idle action idle.name = "Idle"; idle.cancelable = true; idle.animationSequences[0].loopLastAnimation = true; basicActionsDictionary.Insert(BasicActionType.Idle, idle); // Sets the default properties for the walking action walk.name = "Walk"; // Create the force which will make the player walk Force walkForce = new Force(); // Start walking immediately walkForce.startTime.type = DurationType.Frame; walkForce.startTime.nFrames = 0; walkForce.duration.type = DurationType.UsePhysicsData; // Move to the user's cursor walkForce.forceType = ForceType.Position; walkForce.target = TargetPosition.TouchedPosition; // Return to idle once character has reached his move target walkForce.onCompleteEvent.type = Brawler.EventType.PerformBasicAction; walkForce.onCompleteEvent.basicActionToPerform = BasicActionType.Idle; walkForce.faceTarget = true; walk.forces = new Force[] { walkForce }; // Extra properties walk.cancelable = true; walk.listensToInput = true; walk.inputType = InputType.Click; walk.inputRegion = InputRegion.Any; walk.animationSequences[0].loopLastAnimation = true; basicActionsDictionary.Insert(BasicActionType.Walk, walk); // Sets the default properties for the idle action hit.name = "Hit"; hit.cancelable = true; hit.overrideCancelable = true; basicActionsDictionary.Insert(BasicActionType.Hit, hit); // Set the default properties for the 'knockback' action knockback.name = "Knockback"; // Create the force which will make the character be knocked back /*Force knockbackForce = new Force(); * // Start the knockback force immediately * knockbackForce.startTime.type = DurationType.Frame; * knockbackForce.startTime.nFrames = 0; * knockbackForce.duration.type = DurationType.UsePhysicsData; * knockbackForce.forceType = ForceType.Velocity; * knockbackForce.target = TargetPosition.TouchedPosition; * // Return to idle once character has reached his move target * walkForce.onCompleteEvent.type = Brawler.EventType.PerformBasicAction; * walkForce.onCompleteEvent.basicActionToPerform = BasicAction.Idle; * walkForce.faceTarget = true; * walk.forces = new Force[]{walkForce};*/ knockback.cancelable = false; knockback.overrideCancelable = true; basicActionsDictionary.Insert(BasicActionType.Knockback, knockback); // Set the default properties for the 'rising after knockback' action knockbackRise.name = "Knockback_Rise"; knockbackRise.cancelable = false; basicActionsDictionary.Insert(BasicActionType.KnockbackRise, knockbackRise); // Set the default properties for the 'die' action death.name = "Death"; // The event which makes this character die after the death animation finishes playing. Brawler.Event deathEvent = new Brawler.Event(); deathEvent.type = Brawler.EventType.Die; // Kill the character once the death animation is complete deathEvent.startTime.type = DurationType.WaitForAnimationComplete; deathEvent.startTime.animationToWaitFor = 0; death.onStartEvents = new Brawler.Event[1] { deathEvent }; death.cancelable = false; basicActionsDictionary.Insert(BasicActionType.Death, death); // Set the default properties for the 'DeathKnockback' action deathKnockback.name = "DeathKnockback"; deathKnockback.cancelable = false; deathKnockback.overrideCancelable = true; basicActionsDictionary.Insert(BasicActionType.DeathKnockback, deathKnockback); // Set the default properties for the 'Null' action nullAction.name = "NullAction"; // Loop the 'Null' animation, freezing the character's animation until he performs a new action nullAction.animationSequences[0].loopLastAnimation = true; basicActionsDictionary.Insert(BasicActionType.NullAction, nullAction); }
/// <summary> /// Performs the given event. The event will start at 'startTime' seconds, and last a total of 'duration' seconds. /// </summary> /// <returns>The event coroutine.</returns> /// <param name="startTime">The time in seconds relative to the time this function is called at which the event /// starts. Some events don't require this field.</param> /// <param name="duration">The time in seconds that this event lasts. Some events don't require this field, and thus /// this value is ignored.</param> private IEnumerator PerformEventCoroutine(Brawler.Event e, float startTime, float duration) { // Wait 'startTime' seconds before performing the given event yield return(StartCoroutine(Wait(startTime))); Debug.Log("Perform event: " + e.type.ToString() + " on " + character.name + " in " + startTime + " seconds"); // Perform the given event. if (e.type == Brawler.EventType.PerformAction) { PerformAction(e.actionToPerform); } else if (e.type == Brawler.EventType.PerformBasicAction) { PerformAction(e.basicActionToPerform); } else if (e.type == Brawler.EventType.CameraMovement) { Debug.Log("Game Camera: " + GameManager.Instance.GameCamera); // Apply the camera movement to the main game camera. GameManager.Instance.GameCamera.ApplyCameraMovement(e.cameraMovement); } else if (e.type == Brawler.EventType.SoundEffect) { // Play the sound effect specified by the event character.Sound.Play(e.soundEffect); } else if (e.type == Brawler.EventType.SlowMotion) { // Activate slow motion using the TimeManager, and by changing the game's time scale TimeManager.Instance.SetTimeScale(e.slowMotion.timeScale, duration); } else if (e.type == Brawler.EventType.ParticleEffect) { // If the particle effect should spawn at this character's position if (e.particleEvent.spawnPoint == ParticleSpawnPoint.Self) { // Calculate the position where the particles should spawn. They should spawn at this character's // position, plus the offset specified by the particle event Vector3 position = character.Transform.position; // Stores the offset of the particle effect, relative to the character's position Vector3 offset = e.particleEvent.offset; // If the character is facing left if (character.CharacterMovement.FacingDirection == Direction.Left) { // Flip the x-direction of the particle effect's offset. This way, the offset is relative to the character offset.x *= -1; } // Add the pre-computed offset to the particle effect's spawn position. position += offset; // Play the particle effect at the position computed above. ParticleManager.Instance.Play(e.particleEvent.effect, position); } } else if (e.type == Brawler.EventType.Force) { // Apply the force event on this character character.CharacterForces.ApplyForceEvent(e.forceEvent, duration); } else if (e.type == Brawler.EventType.ColorFlash) { // Tell the character's animator to flash the given color for the desired duration character.CharacterAnimator.ColorFlash(e.colorFlash.color, duration, e.colorFlash.renderInFront); } else if (e.type == Brawler.EventType.Ghosting) { // Enable the ghosting effect for 'duration' seconds. character.CharacterAnimator.EnableGhosting(e.ghostEffect.color, duration, e.ghostEffect.renderInFront); } else if (e.type == Brawler.EventType.FreezeAnimation) { // Freeze the character's animation for 'duration' seconds character.CharacterAnimator.FreezeAnimation(duration); } else if (e.type == Brawler.EventType.ScreenShake) { // Shake the screen with the settings given by the event GameManager.Instance.GameCamera.Shake(duration, e.screenShake.speed, e.screenShake.magnitude); } else if (e.type == Brawler.EventType.Tween) { // Tween the character, as indicated by the tween event character.CharacterAnimator.Tween(e.tweenEvent, duration); } else if (e.type == Brawler.EventType.Die) { // Inform the character instance that he has died so that subscribers to the 'Character.OnDeath' event can be // notified of this character's death. character.Die(); } }