【バイオハザード風のゲームを作ってみよう。その20】

今回も下記動画をやっていきます。
youtu.be

今回はゾンビの攻撃のバリエーションを増やしていきます。

・攻撃モーションを準備します。mixamoから好きなアニメーションをゲットします。

・下記スクリプトを追加します。

public class AttackState : State //EP10追加 ゾンビ直下のstatesオブジェクトにアタッチ
{
    PurseTargetState purseTargetState; //EP12追加

    //EP20移動
    /*
    [Header("Zombie Attacks")]
    public ZombieAttackAction[] zombieAttackActions; //EP12追加

    [Header("Potential Attacks Performed Right Now")]
    public List<ZombieAttackAction> potentialAttacks; //EP12追加
    */

    [Header("Current Attack Being Performed")]
    public ZombieAttackAction currentAttack; //EP12追加

    [Header("Stage Flags")]
    public bool hasPerformAttack; //EP12追加

    private void Awake()
    {
        purseTargetState = GetComponent<PurseTargetState>(); //EP12追加
    }

    public override State Tick(ZombieManager zombieManager)
    {
        zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime);

        //EP20追加 If when in the attack state, our target gets too far away, we switch back to the purse target state
        if (zombieManager.distanceFromCurrentTarget > zombieManager.maximumAttackDistance)
        {
            return purseTargetState;
        }

        //If our target is dead, return here and nothing.
        //プレイヤーが死んでいる場合、以降の処理を行わない
        if (zombieManager.currentTarget.isDead) //EP18追加
        {
            return this;
        }

        //if the zombie is being hurt, or is in some action, pause the state
        if (zombieManager.isPerformingAction) //EP11追加
        {
            zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime);
            return this;
        }

        if(!hasPerformAttack && zombieManager.attackCoolDownTimer <= 0) //EP12追加
        {
            //EP20削除
            /*
            if(currentAttack == null)
            {
                //GET A NEW ATTACK BASED ON DISTANCE AND ANGLE FROM CURRENT TARGET
                GetNewAttack(zombieManager);
            }
            */
          //  else
          //  {
                //ATTACK THE CURRENT TARGET
                AttackTarget(zombieManager);
          //  }
        }

        if (hasPerformAttack) //EP12追加
        {
            ResetStateFlags();
            return purseTargetState;
        }
        else if (currentAttack == null) //EP20追加
        {
            return purseTargetState;
        }
        else
        {
            return this;
        }

    }

    /*
    //EP20移動 EP12追加
    private void GetNewAttack(ZombieManager zombieManager)
    {
        for(int i = 0; i < zombieAttackActions.Length; i++)
        {
            ZombieAttackAction zombieAttack = zombieAttackActions[i];

            //Check for attackdistanced needed to perform the potential attack
            if(zombieManager.distanceFromCurrentTarget <= zombieAttack.maxmumAttackDistance &&
                zombieManager.distanceFromCurrentTarget >= zombieAttack.minimumAttackDistance)
            {
                //Check for attack angles, needed to perform the potential attack
                if(zombieManager.viewAngleFromCurrentTarget<=zombieAttack.maxmumAttackAngle &&
                    zombieManager.viewAngleFromCurrentTarget >= zombieAttack.minimumAttackAngle)
                {
                    //if the attack passes the distance and angle, add it to the list of attacks we MAY preform right now
                    potentialAttacks.Add(zombieAttack);
                }
            }
        }

        int randomValue = Random.Range(0, potentialAttacks.Count); //EP12追加

        if (potentialAttacks.Count > 0) //EP12追加
        {
            currentAttack = potentialAttacks[randomValue];
            potentialAttacks.Clear(); //List全ての要素を削除
        }   
    }
    */

    //EP12追加
    private void AttackTarget(ZombieManager zombieManager)
    {
        if (currentAttack != null)
        {
            hasPerformAttack = true;
            zombieManager.zombieCombatManager.SetAttackType(currentAttack.attackType); //EP20追加
            zombieManager.attackCoolDownTimer = currentAttack.attackCooldown;
            zombieManager.ZombieAnimationManager.PlayTargetAttackAnimation(currentAttack.attackanimation);
        }
        else
        {
            //Debug.Log の派生で警告メッセージをコンソール出力
            Debug.LogWarning("WARNING: ZoMbie is attempting to perform an attack,but has no attack");
        }
    }

    //EP12追加
    private void ResetStateFlags()
    {
        hasPerformAttack = false;
        //We clear the attack when leaving the state, so the same attack does not always respect
        currentAttack = null; //EP20追加
    }
}
public class ZombieDamageCollider : MonoBehaviour //EP17追加 ゾンビ右手/左手直下のRight Hnad grapple Collider/Left Hnad grapple Colliderにアタッチ
{
    ZombieManager zombie;

    [Header("Collider")]
    public Collider grappleCollider;

    [Header("Damage Collider Hand")]
    public bool isRightHandDamageCollider;

    [Header("Current Attack Type")]
    public ZombieAttackType attackType; //EP20追加

    [Header("Damage")]
    public int damageAmount = 25; //EP20追加

    private void Awake()
    {
        zombie = GetComponentInParent<ZombieManager>();
        grappleCollider = GetComponent<Collider>();
    }

    //右手/左手にコライダーが接触した場合、実行
    private void OnTriggerEnter(Collider other)
    {
        //If we grab a collider on the "Player" layer,we proceed
        if (other.gameObject.layer == LayerMask.NameToLayer("Player"))
        {
            PlayerManager player = other.GetComponent<PlayerManager>();

            if (player != null)
            {
                DecideAttackAction(player);
            }
        }
    }

    //EP20追加
    private void DecideAttackAction(PlayerManager player)
    {
        if (attackType == ZombieAttackType.grapple)
        {
            //if the player is not already performing an action, we proceed
            if (!player.isPreformingAction)
            {
                //Play the zombie's grapple animation, and set their walking movement at 0
                zombie.ZombieAnimationManager.PlayGrappleAnimation("Zombie Grapple", true);
                zombie.animator.SetFloat("Vertical", 0f);

                //Play the player's grapple animation
                player.animatorManager.ClearHandIKWeights();
                player.animatorManager.PlayAnimation("Player Grapple", true);
                player.playerStatManager.pendingDamage = zombie.zombieCombatManager.grappleBiteDamage; //EP18追加

                //Mask the Zombie face it's target ゾンビを掴むターゲット(プレイヤー)方向に向ける
                Quaternion targetZombieRotation = Quaternion.LookRotation(player.transform.position - zombie.transform.position);
                zombie.transform.rotation = targetZombieRotation;

                //Mask the target face Zombie プレイヤーを掴まれたターゲット(ゾンビ)方向に向ける
                Quaternion targetPlayerRotation = Quaternion.LookRotation(zombie.transform.position - player.transform.position);
                player.transform.rotation = targetPlayerRotation;

                //FUTURE TO DO
                //PLAY DIFFRENT GRABS DEPENDING ON ANGLE OF TARGET
                //EX:If Zombie is behind the player, allow the zombie to grab player from behind
                //ROTATE SMOOTHILY OVER TIME IF PLAYER HAS TO ROATE
            }
        }
        else if (attackType == ZombieAttackType.swipe)
        {
            //We make the player a damage animation
            player.animatorManager.PlayAnimation("Hit Reaction", true);

            //We damage the player
            player.playerStatManager.TakeDamage(damageAmount);
        }
        
    }
}
public class ZombieCombatManager : MonoBehaviour //EP17追加 ゾンビにアタッチ
{
  (省略)
    //EP20追加 
    public void SetAttackType(ZombieAttackType attackType)
    {
        rightHandDamageCollider.attackType = attackType;
        leftHandDamageCollider.attackType = attackType;
    }
}
public class Enums : MonoBehaviour //EP15追加
{
   
}

public enum AmmoType
{
    pistal,
    shotgun
}

//EP20追加
public enum ZombieAttackType
{
    grapple,
    swipe
}
public class PlayerStatManager : MonoBehaviour //EP18追加 プレイヤーにアタッチ
{
    (省略)
    //EP20追加 Called via code when the player takes damage from a damage collider
    public void TakeDamage(int damageTaken)
    {
        playerHealth = playerHealth - damageTaken;

        if (playerHealth == 0)
        {
            KillPlayer();
        }
        else
        {
            player.playerUIManager.DisplayHealthPopUp();
        }
    }
}
public class PurseTargetState : State //EP8追加 ゾンビ直下のstatesオブジェクトにアタッチ
{
    AttackState attackState; //EP10追加

    [Header("Zombie Attacks")]
    public ZombieAttackAction[] zombieAttackActions; //EP20追加

    [Header("Potential Attacks Performed Right Now")]
    public List<ZombieAttackAction> potentialAttacks; //EP20追加

    private void Awake()
    {
        attackState = GetComponent<AttackState>(); //EP10追加
    }

    public override State Tick(ZombieManager zombieManager)
    {
        //if the zombie is being hurt, or is in some action, pause the state
        if (zombieManager.isPerformingAction) //EP11追加
        {
            RotateTwardsTargetWhilsAttacking(zombieManager); //EP17追加
            zombieManager.animator.SetFloat("Vertical", 0f, 0.2f, Time.deltaTime);
            return this;
        }
           

       // Debug.Log("Running pursue target state");
        MoveTowardsCurrentTarget(zombieManager); //EP10追加
        RotateTowardsTarget(zombieManager); //EP10追加

        if (zombieManager.distanceFromCurrentTarget <= zombieManager.maximumAttackDistance) //EP12追加
        {
            if (attackState.currentAttack == null) //EP20追加
            {
                //GET A NEW ATTACK BASED ON DISTANCE AND ANGLE FROM CURRENT TARGET
                GetNewAttack(zombieManager);
                return this;
            }
            else //EP20追加
            {
                //We disable the nav mesh agent WHEN attacking, so the attack rotation can be handled via code
                zombieManager.zombieNavmeshAgent.enabled = false;
                return attackState;
            }
            
        }
        else
        {
            return this;
        }   
    }

//EP20追加
    private void GetNewAttack(ZombieManager zombieManager)
    {
        for (int i = 0; i < zombieAttackActions.Length; i++)
        {
            ZombieAttackAction zombieAttack = zombieAttackActions[i];

            //Check for attackdistanced needed to perform the potential attack
            if (zombieManager.distanceFromCurrentTarget <= zombieAttack.maxmumAttackDistance &&
                zombieManager.distanceFromCurrentTarget >= zombieAttack.minimumAttackDistance)
            {
                //Check for attack angles, needed to perform the potential attack
                if (zombieManager.viewAngleFromCurrentTarget <= zombieAttack.maxmumAttackAngle &&
                    zombieManager.viewAngleFromCurrentTarget >= zombieAttack.minimumAttackAngle)
                {
                    //if the attack passes the distance and angle, add it to the list of attacks we MAY preform right now
                    potentialAttacks.Add(zombieAttack);
                }
            }
        }

        int randomValue = Random.Range(0, potentialAttacks.Count); //EP12追加

        if (potentialAttacks.Count > 0) //EP12追加
        {
            Debug.Log("Distance:" + zombieManager.distanceFromCurrentTarget);
            attackState.currentAttack = potentialAttacks[randomValue];
            potentialAttacks.Clear(); //List全ての要素を削除
        }
    }
}

3パターンの攻撃を実装しました。
youtu.be

今回は以上です。