難波八郎
難波八郎

Reputation: 49

Unity C# inheritance

I am learning Unity by this Japanese Website. https://feynman.co.jp/unityforest/game-create-lesson/fps-game/enemy/

I want to use "inheritance" in Unity. I made a base class in the following.

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine;

public class EnemyBase : MonoBehaviour 
{
    // 最大HP. 
    [SerializeField] protected int maxHp = 3;
    // 現在のHP. 
    protected int hp = 0;
    // 攻撃を受けるフラグ. 
    protected bool canHit = true;

    protected virtual void Start()
    { Init();
    }

    protected virtual void Update()
    {

    }

    // ---------------------------------------------------------------
    /// <summary>
    /// 初期化処理.
    /// </summary>
    // --------------------------------------------------------------- 
    public virtual void Init()
    { hp = maxHp;
    }

    // ---------------------------------------------------------------
    /// <summary>
    /// コライダーエンター処理.
    /// </summary>
    /// <param name="col"></param>
    // --------------------------------------------------------------- 
    public virtual void OnEnemyColliderEnter(Collision col)
    { 
        Debug.Log("Even this one does not call" + col.gameObject.name);


        if (col.gameObject.tag == "Arrow" && canHit == true)
        { 
            // Arrowを取得して「Arrow」の敵にヒットした時の処理を実行. 
            var arrow = col.gameObject.GetComponent<Arrow>(); 
            arrow.OnEnemyHit(); 
            // HPを矢の攻撃力分マイナス. 
            hp -= arrow.Attack;

            if (hp <= 0)
            {
                // 死亡時処理. 
                OnDead();
            } 
            else
            { 
                Debug.Log(gameObject.name + " に攻撃がヒット。残りHP " + hp);
                // 次回ヒットまでの待機時間. 
                StartCoroutine(HitWait());
            }
        }
    }

    // ---------------------------------------------------------------
    /// <summary>
    /// 死亡時処理.
    /// </summary>
    // --------------------------------------------------------------- 
    protected virtual void OnDead()
    { 
        Debug.Log(gameObject.name + "を倒しました"); 
        Destroy(gameObject);
    }

    // ---------------------------------------------------------------
    /// <summary>
    /// 攻撃ヒット後次の攻撃が当たるまでの待機処理.
    /// </summary>
    // --------------------------------------------------------------- 
    IEnumerator HitWait()
    {
        // 指定時間待機してフラグを戻す. 
        canHit = false; 
        yield return new WaitForSeconds(0.5f);
        canHit = true;
    }
}

And I made a derived class here.

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine;

public class EnemyCrocodile : EnemyBase
{
    // Start is called before the first frame update
    protected override void Start()
    { 
        base.Start();   
    }

    protected override void Update()
    {
        base.Update();
    }
}

And I put a picture of the inspector of Enemy. My problem is that public virtual void OnEnemyColliderEnter(Collision col) does not start, when the arrow hit the enemy.

And I made an arrow.cs here.

using System.Collections; 
using System.Collections.Generic;
using UnityEngine;

public class Arrow : MonoBehaviour 
{
    // リジッドボディ. 
    Rigidbody rigid = null;

    // 攻撃処理フラグ. 
    bool isAttack = true;

    // 攻撃力. 
    public int Attack = 1;

    // 自動破壊コル-チン. 
    Coroutine autoDestroyCor = null;

    // Start is called before the first frame update 
    void Start()
    {

    }

    // Update is called once per frame 
    void Update()
    {

    }

    // --------------------------------------------------------------------------
    /// <summary>
    /// 生成時コールバック.
    /// </summary>
    // -------------------------------------------------------------------------- 
    public void OnCreated()
    { 
        Init();
    }

    // --------------------------------------------------------------------------
    /// <summary>
    /// 発射.
    /// </summary>
    /// <param name="direction"> 発射方向. </param>
    /// <param name="forceMode"> フォースモード. </param>
    // -------------------------------------------------------------------------- 
    public void Shoot(Vector3 direction, ForceMode forceMode)
    { 
        rigid.useGravity = false; 
        rigid.isKinematic = false;

        rigid.AddForce(direction, forceMode);

        // 元の記載↓は削除して、それをそのまま変数に代入する形に変更. 
        StartCoroutine(AutoDestroy(1f)); 
        autoDestroyCor = StartCoroutine(AutoDestroy(1f));
    }


    // --------------------------------------------------------------------------
    /// <summary>
    /// 初期化処理.
    /// </summary>
    // -------------------------------------------------------------------------- 
    void Init()
    {
        if (rigid == null) rigid = GetComponent<Rigidbody>();
    }

    // --------------------------------------------------------------------------
    /// <summary>
    /// 自動破壊コルーチン.
    /// </summary>
    /// <param name="time"> 破棄までの時間. </param>
    // -------------------------------------------------------------------------- 
    IEnumerator AutoDestroy(float time)
    { 
        yield return new WaitForSeconds(time); 
        Destroy(gameObject);
    }

    // --------------------------------------------------------------------------
    /// <summary>
    /// コライダーエンター.
    /// </summary>
    /// <param name="col"></param>
    // -------------------------------------------------------------------------- 
    public void OnArrowCollisionEnter(Collision col)
    {
        //if (col.gameObject.tag == "Ground" || col.gameObject.tag == "Enemy") 
        if (col.gameObject.tag == "Ground")
        { 
            if (isAttack == true)
            { 
                Debug.Log("Ground!!!!!" + col.gameObject.name);

                rigid.isKinematic = true; 
                rigid.velocity = Vector3.zero; 
                rigid.angularVelocity = Vector3.zero;

                isAttack = false; 
                Destroy(gameObject);
            }
        }
    }

    // --------------------------------------------------------------------------
    /// <summary>
    /// 敵にヒットした時の処理.
    /// </summary>
    // -------------------------------------------------------------------------- 
    public void OnEnemyHit()
    { 
        if (autoDestroyCor != null)
        { 
            StopCoroutine(autoDestroyCor);
        } 

        rigid.isKinematic = true; 
        rigid.velocity = Vector3.zero;
        rigid.angularVelocity = Vector3.zero;

        isAttack = false; 
        Destroy(gameObject);
    } 
}

When the arrow hit the ground, the console window shows

Ground!!!!!

So the

Debug.Log("Ground!!!!!" + col.gameObject.name); 

works well.

enter image description here

Could you give me some tips for this problem? Thank you for reading this.

I checked "arrow" and "enemy" have a RigidBody component. I checked "arrow" and "enemy" respectively are tagged "Arrow" and "Enemy".

Upvotes: 1

Views: 87

Answers (1)

derHugo
derHugo

Reputation: 90813

I think the main issue would be: I don't see where you would ever call OnEnemyColliderEnter or OnArrowCollisionEnter.

In Unity there is only OnTriggerEnter and OnCollisionEnter

These two are event messages that are automatically triggered by the physics engine - but only if they have the exact correct name and signature, and if their respective conditions are met (see Layer Collision Matrix and Collider Interactions)

Otherwise you would have to call that method yourself from somewhere which I don't see no code for.


In general there are a lot of small API, syntax and logic issues in your code.

I will do my best to point them out and how I would improve/correct them

First of all as mentioned

  • This

    public virtual void OnEnemyColliderEnter(Collision col)
    

    and this

    public void OnArrowCollisionEnter(Collision col)
    

    should both be

    protected virtual void OnCollisionEnter(Collision col)
    

In order to make them get recognized by the engine and called at all.

  • Further this (a minor API detail)

    col.gameObject.tag == "Arrow"
    

    should rather be

    col.gameObject.CompareTag("Arrow")
    

    which is not only slightly faster but also prevents silent fails if the tag is misspelled or doesn't exist at all (see CompareTag).

  • (Another optional API detail) Instead of checking the tag at all and this

    var arrow = col.gameObject.GetComponent<Arrow>();
    

    you could even simply use

    if(col.gameObject.TryGetComponent<Arrow>(out var arrow))
    {
        ...
    }
    

    The tag check is redundant if you check for a specific component anyway (see TryGetComponent).

  • Instead of the entire Arrow.OnEnemyHit you could simply do

    Destroy(arrow.gameObject);
    

    There is actually no use at all to make adjustments to your Rigidbody component if you are anyway destroying this object.

    The same also goes for the Arrow.OnCollisionEnter. If you anyway Destroy this arrow object you can get rid of this entire code which only modifies this arrow instance you are about to destroy

    rigid.isKinematic = true; 
    rigid.velocity = Vector3.zero; 
    rigid.angularVelocity = Vector3.zero;
    
    isAttack = false;
    

    And then actually the isAttack seems to become redundant as well.

  • Instead of your Coroutine AutoDestroy not that Destroy already has a built-in optional parameter float t

    The optional amount of time to delay before destroying the object.

    so you can simply do

     private void Start()
     {
         Destroy(gameObbject, 1f);
     }
    
  • And in general, if you don't need Unity message methods like Start, Update etc, then don't implement them.

    Empty message methods only cost resources.

  • And if you don't extend/override the behavior of a virtual method then there is also no need to explicitly have a

    protected override void Start()
    { 
        base.Start();   
    }
    

    you can completely delete the methods from EnemyCrocodile.

Upvotes: 1

Related Questions