0
\$\begingroup\$

In my game, you jump on zombies'heads to squash them.

Currently I'm only able to have one zombie in my game. When I add another (duplicate or drag from prefab), then run the game, I can squash one enemy but when I try to jump on the second zombie's head it says "Coroutine couldn't be started because the game object "Zombie (1)" is inactive".

Here's the player's stompbox script code & below that the ZombieController script code:

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

public class StompEnemy : MonoBehaviour {

    private Rigidbody2D playerRigidbody;
    public ZombieController zombieController;

    public float bounceForce;
    // a bounce force of 12 is wonderful (set in unity's inspector)

    // Use this for initialization
    void Start () {
        playerRigidbody = transform.parent.GetComponent<Rigidbody2D>();
        zombieController = FindObjectOfType<ZombieController>();
    }

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

    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if(other.tag == "Zombie")
        {
            zombieController.Stomped();
            playerRigidbody.velocity = new Vector2(playerRigidbody.velocity.x, bounceForce);
        } 
    }

}

Now here's the ZombieController script code:

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

public class ZombieController : MonoBehaviour {

    Animator zombieAnimator;
    Rigidbody2D myRigidBody;
    [SerializeField] float moveSpeed = 1f;

    public bool isAlive = true;

    // Use this for initialization
    void Start () {
        zombieAnimator = GetComponent<Animator>();
        myRigidBody = GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void Update () {
        if (!isAlive) { return; }
        if (isFacingRight())
        {
            myRigidBody.velocity = new Vector2(moveSpeed, 0f);
        }
        else
        {
            myRigidBody.velocity = new Vector2(-moveSpeed, 0f);
        }
    }

    bool isFacingRight()
    {
        return transform.localScale.x > 0;
        // if it's positive he's facing right, and if negative, facing left.
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        transform.localScale = new Vector2(-(Mathf.Sign(myRigidBody.velocity.x)), 1f);
    }

    public void Stomped()
    {
        isAlive = false;
        transform.localScale = new Vector2(-(Mathf.Sign(myRigidBody.velocity.x)), 1f);
        zombieAnimator.SetBool("Squash", true);
        if (isFacingRight())
        {
            myRigidBody.velocity = new Vector2(Mathf.Epsilon, 0f);
        }
        else if (!isFacingRight())
        {
            myRigidBody.velocity = new Vector2(-Mathf.Epsilon, 0f);
        }
        gameObject.layer = 11;
        StartCoroutine(WaitXSecond());
    }

    public IEnumerator WaitXSecond()
    {
        yield return new WaitForSeconds(2f);
        gameObject.SetActive(false);
        zombieAnimator.SetBool("Squash", false);
    }

}

How do I implement multiple zombies using the same script attached to each one?

When I've tried having three zombies I'd jump on the first one's head and another zombie would die (show the squashed animation, then disappear after two seconds) and the remaining zombies would no longer be jump-on-head-able, displaying the coroutine error above.

\$\endgroup\$
1
  • \$\begingroup\$ Hi and welcome to gamedev stack exchange! Is there a particular reason why you need to disable and not destroy zombies when you kill them in your game? \$\endgroup\$
    – user115399
    Commented Nov 28, 2018 at 16:33

2 Answers 2

1
\$\begingroup\$

Your problem is these two lines:

zombieController = FindObjectOfType<ZombieController>();

This says "give me the first ZombieController you find in the scene"

zombieController.Stomped();

This says "squash that specific zombie you found for me earlier" - ie. it doesn't care whether that's the zombie you actually landed on, or whether this zombie is still alive. You've set up your script so that one and only one zombie is predestined to receive every stomp.

To avoid this, I'd recommend centralizing the work of handling being squashed on the object that can be squashed. That way, any squashing always applies to "me," making it trivial to ensure it's hitting the right target.

Presumably you'll want other things you can squash beyond zombies (eg. other monster types, powerups, maybe even the player themselves (from falling blocks/etc), so let's separate the concept of Squashable from the concept of Zombie while we're at it.

First, we'll use a very simple script to mark our collider that does the squashing. You can also do this with tags, but doing it with a script lets you attach parameters to it (eg. a damage value dealt by this squasher, or flags for selective squash immunity, so you can ensure players don't squash other players)

public class Squasher : MonoBehaviour { }

Next we'll define our script for being squashed. I've set this up generically so it can work on anything that has a squash animation, so you minimize code duplication if you add other squashable types later.

public class Squashable : MonoBehaviour {

    bool squashed = false;

    void OnTriggerEnter2D(Collider2D other) {
        // Don't double-squash if you get two trigger enter messages at once.
        if(squashed)
           return;

        // Only squash if we got hit by a squasher collider.
        // (Can check that it came from above too)
        var squasher = other.GetComponent<Squasher>();
        if(squasher == null)
            return;

        StartCoroutine(SquashMe());
    }

    IEnumerator SquashMe() {
        squashed = true;

        var animator = GetComponent<Animator>();
        if(animator != null)
            animator.SetBool("Squash", true);

        var body = GetComponent<RigidBody2D>();
        if(body != null)
            body = new Vector2(Mathf.Epsilon * Mathf.Sign(transform.localScale.x), 0f);

        // Make your Zombie controller or other monster AI implement this interface,
        // so we can switch off their behaviour / award score or pickups as needed,
        // without the Squash logic knowing anything about "zombies."
        var killable = GetComponent<IKillable>();
        if(killable != null)
            killable.Kill();

        int oldLayer = gameObject.layer;
        gameObject.layer = 11;

        yield return new WaitForSeconds(2f);

        // Hide this object and put all its components back the way we found them.
        gameObject.SetActive(false);
        gameObject.layer = oldLayer;
        if(animator != null)
            animator.SetBool("Squash", false);
        if(killable != null)
            killable.Resurrect();
        squashed = false;
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much. Very kind and much appreciated! \$\endgroup\$
    – LG Wolf
    Commented Nov 28, 2018 at 18:13
0
\$\begingroup\$
public IEnumerator WaitXSecond()
{
    yield return new WaitForSeconds(2f);
    gameObject.SetActive(false);
    zombieAnimator.SetBool("Squash", false);
}

You are calling gameObject.SetActive(false) from within the ZombieController. You are actually disabling the ZombieController gameobject rather than the StompEnemy.

Try passing in a reference to your StompEnemy gameobject and call SetActive(false) on that instead.

public IEnumerator WaitXSecond(GameObject enemyObject)
{
    yield return new WaitForSeconds(2f);
    enemyObject.SetActive(false);
    zombieAnimator.SetBool("Squash", false);
}
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .