/// <summary> /// Initialise the movement with the given movement data. /// </summary> /// <param name="enemy">The enemy.</param> /// <param name="movementData">Movement data.</param> override public EnemyMovement Init(Enemy enemy) { base.Init(enemy); int anyMoveCount = 0; // Initialise and check each movement for (int i = 0; i < statesToMovements.Length; i++) { if (statesToMovements[i].state == EnemyState.DEFAULT) { anyMoveCount++; } if (statesToMovements[i].movement == null) { Debug.LogError("The state " + statesToMovements[i].state + " does not have a movement assigned."); } else { statesToMovements[i].movement.Init(enemy); if (statesToMovements[i].state == EnemyState.DEFAULT) { defaultMovement = statesToMovements[i].movement; } } } // Report any move errors if (anyMoveCount == 0) { Debug.LogError("You must have a move assigned to the DEFAULT enemy state"); } else if (anyMoveCount > 1) { Debug.LogWarning("You have more than one move assigned to the ANY state, only the first will be used."); } currentMovement = defaultMovement; uniqueMovements = statesToMovements.Select(s => s.movement).Distinct().ToArray(); return(this); }
/// <summary> /// Init this enemy AI. /// </summary> override public void Init(Enemy enemy) { base.Init(enemy); // Listen to damage events and if we get them hide enemy.Damaged += EnemyDamaged; enemy.Collided += EnemyDamaged; // If we need it try and find a charge movement if (useChargeState) { EnemyMovement_Distributor distributor = enemy.GetComponentInChildren <EnemyMovement_Distributor>(); if (distributor != null) { foreach (EnemyStateToMovement estm in distributor.statesToMovements) { if (estm.state == EnemyState.CHARGING) { chargeMovement = estm.movement; break; } } } else { chargeMovement = enemy.GetComponentInChildren <EnemyMovement_Charge>(); } } // Try to find a cahracter hurt box so we can collect coins and damage other enemies characterHitBox = GetComponentInChildren <CharacterHitBox>(); if (characterHitBox != null) { // Don't hit ourselves EnemyHurtBox myHurtBox = enemy.GetComponentInChildren <EnemyHurtBox>(); if (myHurtBox != null) { Physics2D.IgnoreCollision(characterHitBox.GetComponent <Collider2D>(), myHurtBox.GetComponent <Collider2D>()); } // Init hit box characterHitBox.Init(new DamageInfo(1, DamageType.PHYSICAL, Vector2.zero)); } }
virtual protected void DrawStateInfo(SequenceDrivenEnemy enemy, EnemyPhase phase, EnemyStateInfo info, int phaseIndex, int index) { GUI.color = (info.gotoState >= 0) ? Color.yellow : (info.assignedMovement == null) ? Color.red : Color.green; GUILayout.BeginVertical(EditorStyles.textArea); GUI.color = Color.white; if (phase == null) { gotoFoldOutStates [index] = EditorGUILayout.Foldout(gotoFoldOutStates [index], new GUIContent(info.stateName)); } else { foldOutState [index] = EditorGUILayout.Foldout(foldOutState [index], new GUIContent(info.stateName)); } if ((phase == null && gotoFoldOutStates[index]) || (phase != null && foldOutState[index])) { string newStateName = EditorGUILayout.TextField(new GUIContent("Name", "State name."), info.stateName); if (newStateName != info.stateName) { info.stateName = newStateName; } if (info.gotoState >= 0) { int gotoPhase = EditorGUILayout.IntField(new GUIContent("Go To Phase", "State we go back to until exit is reached."), info.gotoPhase); if (gotoPhase >= 0 && gotoPhase < enemy.phaseInfo.Count) { if (gotoPhase != info.gotoPhase) { info.gotoPhase = gotoPhase; } } int gotoState = EditorGUILayout.IntField(new GUIContent("Go To State", "State we go back to until exit is reached."), info.gotoState); if (gotoState >= 0 && (info.gotoPhase != phaseIndex || gotoState != index) && gotoState < enemy.phaseInfo[info.gotoPhase].stateInfo.Count) { if (gotoState != info.gotoState) { info.gotoState = gotoState; } } EnemyStateExitType exitType = (EnemyStateExitType)EditorGUILayout.EnumPopup(new GUIContent("Go To when...", "What condition causes us to run this Go To."), info.exitType); if (exitType != info.exitType) { info.exitType = exitType; } if (info.exitType == EnemyStateExitType.NONE) { EditorGUILayout.HelpBox("The type NONE means this goto will never execute", MessageType.Warning); } } else { EnemyMovement newAssignedMovement = (EnemyMovement)EditorGUILayout.ObjectField(new GUIContent("Movement", "Assocaited enemy movement."), info.assignedMovement, typeof(EnemyMovement), true); if (newAssignedMovement != info.assignedMovement) { info.assignedMovement = newAssignedMovement; } EditorGUILayout.HelpBox(info.assignedMovement == null ? "No movement set" : info.assignedMovement.GetType().Name, MessageType.None); EnemyStateExitType exitType = (EnemyStateExitType)EditorGUILayout.EnumPopup(new GUIContent("Exit Type", "How we decide to exit this state."), info.exitType); if (exitType != info.exitType) { info.exitType = exitType; } if (info.exitType == EnemyStateExitType.ALWAYS) { EditorGUILayout.HelpBox("The type ALWAYS is mainly meant for GOTO states. In this case it means this state will always exit instantly.", MessageType.Warning); } } float exitSupportingData; float exitSupportingDataAlt; switch (info.exitType) { case EnemyStateExitType.TIMER: exitSupportingData = EditorGUILayout.FloatField(new GUIContent("Exit Time", "Time to spend in state before exiting."), info.exitSupportingData); if (exitSupportingData != info.exitSupportingData) { info.exitSupportingData = exitSupportingData; } break; case EnemyStateExitType.TIMER_PLUS_RANDOM: exitSupportingData = EditorGUILayout.FloatField(new GUIContent("Exit Time", "Time to spend in state before exiting."), info.exitSupportingData); if (exitSupportingData != info.exitSupportingData) { info.exitSupportingData = exitSupportingData; } int altAsInt = (int)info.exitSupportingDataAlt; altAsInt = EditorGUILayout.IntSlider(new GUIContent("Random Chance", "Chance that we will exit state after timer is expired."), altAsInt, 0, 100); exitSupportingDataAlt = (float)altAsInt; if (exitSupportingDataAlt != info.exitSupportingDataAlt) { info.exitSupportingDataAlt = exitSupportingDataAlt; } break; case EnemyStateExitType.HEALTH_PERCENTAGE: int healthAsInt = (int)(info.exitSupportingData * 100.0f); healthAsInt = EditorGUILayout.IntSlider(new GUIContent("Percentage", "Health percentage, state will exit when health is below this."), healthAsInt, 0, 100); exitSupportingData = ((float)healthAsInt) / 100.0f; if (exitSupportingData != info.exitSupportingData) { info.exitSupportingData = exitSupportingData; } break; case EnemyStateExitType.TARGET_WITHIN_RANGE: exitSupportingData = EditorGUILayout.FloatField(new GUIContent("Range", "Range target must be within."), info.exitSupportingData); if (exitSupportingData != info.exitSupportingData) { info.exitSupportingData = exitSupportingData; } exitSupportingDataAlt = (EditorGUILayout.Toggle(new GUIContent("Must Be Visible", "If true enemy must have a clear line of sight to the target."), info.exitSupportingData == 1.0f) ? 1.0f : 0.0f); if (exitSupportingDataAlt != info.exitSupportingDataAlt) { info.exitSupportingDataAlt = exitSupportingDataAlt; } break; case EnemyStateExitType.NUMBER_OF_HITS: exitSupportingData = (float)EditorGUILayout.IntField(new GUIContent("Number of Hits", "Number of hits before exiting to the next state."), (int)info.exitSupportingData); if (exitSupportingData != info.exitSupportingData) { info.exitSupportingData = exitSupportingData; } DamageType exitSupportingDamageType = (DamageType)EditorGUILayout.EnumPopup(new GUIContent("Damage Type", "If not set to NONE, only damage of this type will count towards number of hits."), info.exitSupportingDamageType); if (exitSupportingDamageType != info.exitSupportingDamageType) { info.exitSupportingDamageType = exitSupportingDamageType; } break; } GUILayout.Space(4); if (phase != null) { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (index == 0) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Move Up", "Move this state up."), EditorStyles.miniButton)) { phase.stateInfo[index] = phase.stateInfo[index - 1]; phase.stateInfo[index - 1] = info; } GUI.enabled = true; if (index >= phase.stateInfo.Count - 1) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Move Down", "Move this state down."), EditorStyles.miniButton)) { phase.stateInfo[index] = phase.stateInfo[index + 1]; phase.stateInfo[index + 1] = info; } GUI.enabled = true; if (phase.stateInfo.Count <= 1) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Delete", "Delete this state."), EditorStyles.miniButton)) { phase.stateInfo.Remove(info); } GUI.enabled = true; GUILayout.EndHorizontal(); } else { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); if (index == 0) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Move Up", "Move this state up."), EditorStyles.miniButton)) { enemy.globalGotos[index] = enemy.globalGotos[index - 1]; enemy.globalGotos[index - 1] = info; } GUI.enabled = true; if (index >= enemy.globalGotos.Count - 1) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Move Down", "Move this state down."), EditorStyles.miniButton)) { enemy.globalGotos[index] = enemy.globalGotos[index + 1]; enemy.globalGotos[index + 1] = info; } GUI.enabled = true; if (enemy.globalGotos.Count <= 1) { GUI.enabled = false; } if (GUILayout.Button(new GUIContent("Delete", "Delete this state."), EditorStyles.miniButton)) { enemy.globalGotos.Remove(info); } GUI.enabled = true; GUILayout.EndHorizontal(); } } GUILayout.EndVertical(); GUILayout.Space(4); }
/// <summary> /// Initialise the movement with the given movement data. /// </summary> /// <param name="enemy">The enemy.</param> /// <param name="movementData">Movement data.</param> public override EnemyMovement Init(Enemy enemy) { base.Init (enemy); int anyMoveCount = 0; // Initialise and check each movement for (int i = 0; i < statesToMovements.Length; i++) { if (statesToMovements[i].state == EnemyState.DEFAULT) anyMoveCount++; if (statesToMovements[i].movement == null) { Debug.LogError ("The state " + statesToMovements[i].state + " does not have a movement assigned."); } else { statesToMovements[i].movement.Init (enemy); if (statesToMovements[i].state == EnemyState.DEFAULT) defaultMovement = statesToMovements[i].movement; } } // Report any move errors if (anyMoveCount == 0) Debug.LogError ("You must have a move assigned to the DEFAULT enemy state"); else if (anyMoveCount > 1) Debug.LogWarning ("You have more than one move assigned to the ANY state, only the first will be used."); currentMovement = defaultMovement; uniqueMovements = statesToMovements.Select (s => s.movement).Distinct().ToArray(); return this; }
/// <summary> /// Called when the enemy hits the character. /// </summary> /// <param name="character">Character.</param> /// <param name="info">Damage info.</param> public override void HitCharacter(Character character, DamageInfo info) { for (int i = 0; i < statesToMovements.Length; i++) { if (statesToMovements[i].state == EnemyState.HITTING) currentMovement = statesToMovements[i].movement; } currentMovement.HitCharacter(character, info); }
/// <summary> /// Moves the character. /// </summary> public override bool DoMove() { EnemyMovement previousMovement = currentMovement; for (int i = 0; i < statesToMovements.Length; i++) { if (statesToMovements[i].state == enemy.State) { if (statesToMovements[i].movement != previousMovement) { if (previousMovement.LosingControl()) { // The previous movement wants to hold on to control, don't update it. return true; } else { statesToMovements[i].movement.GainingControl(); currentMovement = statesToMovements[i].movement; } } if (currentMovement.DoMove ()) return true; } } if (currentMovement == null) { currentMovement = defaultMovement; return currentMovement.DoMove(); } return false; }
/// <summary> /// Do the death movement /// </summary> public override void DoDeath(DamageInfo info) { for (int i = 0; i < statesToMovements.Length; i++) { if (statesToMovements[i].state == EnemyState.DEAD) currentMovement = statesToMovements[i].movement; } currentMovement.DoDeath(info); }