Hook Pro 5v5 Game Development (inspired from Dota2’s Arcade)

Hook Pro 5v5 is a 5 versus 5 game. Player controls a Pudge (Butcher) Character with various spell to use like: Meat Hook. The two teams faces off on a rectangular map divided by an uncross-able division. This game is inspired from Valve Corp.’s DOTA2 Arcade Game ‘Pudge Wars’ and ‘1 Hook 1 Kill’.

[Android App Link]

  • Pre-Unity Engine Phase

– Idea: Replicating Dota2’s Arcade “1 Hook 1 Kill” or “Pudge Wars” for Android devices.
– Get Pudge 3D Model & Texture form Dota2’s official website. All assets by Valve Corp. are provided free for personal use and project use (limitation on commercial use limited to Youtube videos)
– Make 3D lowpoly Map and hand paint it; basic Main Menu UI leading one click directly to game.
– Make our Character have Humanoid Animation in Unity, so that we can use default walk, run animations.

  • Scripting Player (+ UI)

– A fixed joystick for movement. Basically gives vector direction and magnitude to TPC (Third Person Character Controller).

void Update()
{
Vector3 moveVector = (Vector3.right * joystick.Horizontal + Vector3.forward * joystick.Vertical); //movement vector

if (moveVector != Vector3.zero && !hook && !earthbound)
tpc.Move(moveVector.normalized * moveSpeed, false, false); //movement given to TPC

else if (earthbound)
tpc.Move(Vector3.zero, false, false); //not to move when affected by earthbind

else
tpc.Move(Vector3.zero, false, false); //not to move when no input from joystick
}

– Powers (here: Hook, Earthbind, Blur)

#region HOOK!
    /// <summary>
    /// When hook button is began.
    /// </summary>
    public void Do_Hook()
    {
        hook_coroutine = StartCoroutine(Hook());
        hook_w_t_coroutine = StartCoroutine(Hook_Wait_Timer());
    }

    public LineRenderer lr;
    public GameObject Hook_move, Hook_attack;
    public float h_time = 0f;
    public float actual_time = 0f;
    public float hit_time; //if gets hit earlier the hook should return
    /// <summary>
    /// Hooking in Progress.
    /// </summary>
    public IEnumerator Hook()
    {
        hook = true; //hooking in progress. //only for movement allowance purpose.
        enemy_agent.isStopped = true;
        lr.enabled = true;
        Vector3 start_hook = gameObject.transform.position;
        Vector3 dir = gameObject.transform.forward;
        Hook_move.SetActive(false); Hook_attack.SetActive(true); //hook mesh
        Hook_attack.transform.forward = dir;
        hit_time = 0.9f;
        gameObject.GetComponent<AudioSource>().clip = hook_mp3[0];
        gameObject.GetComponent<AudioSource>().Play();
        while (actual_time < 2 * hit_time)
        {
            actual_time += Time.deltaTime;
            if (actual_time < hit_time)
                h_time += Time.deltaTime;
            else
                h_time -= Time.deltaTime;
            lr.SetPosition(0, start_hook + gameObject.transform.TransformVector(1, 1, 0));
            Vector3 pos2 = start_hook + (dir * h_time * 25) + gameObject.transform.TransformVector(1, 1, 0);
            Hook_attack.transform.position = pos2;
            lr.SetPosition(1, pos2);
            if (actual_time >= hit_time) { hook = false; enemy_agent.isStopped = false; }
            if (actual_time >= hit_time * 2)
            {
                h_time = 0;
                actual_time = 0;
                lr.enabled = false;
                Hook_move.SetActive(true); //hook mesh
                Hook_attack.SetActive(false); //hook mesh
                hooked_other = false;
                gameObject.GetComponent<AudioSource>().Stop();
                StopCoroutine(hook_coroutine);
            }
            if (actual_time < hit_time && hooked_other) { hit_time = h_time; gameObject.GetComponent<AudioSource>().clip = hook_mp3[2]; gameObject.GetComponent<AudioSource>().Play(); }
            if (actual_time > 0.2f && hook) { Hook_attack.GetComponent<BoxCollider>().enabled = true; }
            else { Hook_attack.GetComponent<BoxCollider>().enabled = false; }
            yield return null;
        }
    }


    /// <summary>
    /// When to reprovide hook ability.
    /// </summary>
    public IEnumerator Hook_Wait_Timer()
    {
        hook_ability_available = false;
        yield return new WaitForSeconds(6);
        hook_ability_available = true;
        StopCoroutine(hook_w_t_coroutine);
    }
    #endregion

Above is ‘hook’ coroutine which does hook animation as well as calculates all values in and out in this process. Usually I properly comment and write neat readable code, but for personal projects…

  • Scripting AI

Here comes the difficult part: MAKE AN AI to act as close as human does. Before moving towards scripting, here are few pre-built things that are required.
– Nav Mesh Agent and Navigation Area Baking
– TPC Movement
– Colliders
Here is the partial script code because it is too big.
– The function “RandomNavmeshLocation” is for AI’s random movement and also it avoids the character collision with allies.
– The function “GetDistance” calculates distance between this character and allies/enemies. The ally distance is calculated to avoid collision and enemy distance is calculated for hook purpose. If a particular enemy is withing range of hook and hook ability is available, initiate the “Hook” Coroutine.

public void RandomNavmeshLocation(float radius, bool rand)
    {
        Vector3 randomDirection = Random.insideUnitSphere * radius;
        if (rand == false)
        {
            randomDirection = -gameObject.transform.forward * radius;
        }
        randomDirection += transform.position;
        NavMeshHit hit;
        if (NavMesh.SamplePosition(randomDirection, out hit, radius, 1))
        {
            randPos = hit.position;
            enemy_agent.destination = randPos;
        }
    }

    void GetDistance()
    {
        if (gameObject.tag == "ally")
        {
            float[] dist = new float[5];
            float min_dist = 0;
            GameObject[] go = new GameObject[5];
            int min_obj = 0;
            for (int i = 0; i < 5; i++)
            {
                go[i] = opposite_team.GetComponent<Enemy_Manager>().enemy_instance[i].transform.GetChild(1).GetChild(0).gameObject;
                dist[i] = Vector3.Distance(transform.position, go[i].transform.position);
                if (i == 0) { min_dist = dist[0]; min_obj = 0; }
                else if (dist[i] < min_dist) { min_dist = dist[i]; min_obj = i; }
            }
            if (min_dist < 20f)
            {
                transform.LookAt(go[min_obj].transform.position);
                if (hook_ability_available)
                    Do_Hook();
                else if (eb_ability_available)
                    Do_Earthbind();

                if (puff_ability_available)
                    Do_Puff();
            }
        }
        else if (gameObject.tag == "enemy")
        {
            float[] dist = new float[5];
            float min_dist = 0;
            GameObject[] go = new GameObject[5];
            int min_obj = 0;
            for (int i = 0; i < 5; i++)
            {
                if (i != 4)
                {
                    go[i] = opposite_team.GetComponent<Enemy_Manager>().enemy_instance[i].transform.GetChild(1).GetChild(0).gameObject;
                    dist[i] = Vector3.Distance(transform.position, go[i].transform.position);
                    if (i == 0) { min_dist = dist[0]; min_obj = 0; }
                    else if (dist[i] < min_dist) { min_dist = dist[i]; min_obj = i; }
                }
                else
                {
                    go[4] = player.transform.GetChild(0).gameObject;
                    dist[4] = Vector3.Distance(transform.position, go[4].transform.position);
                    if (dist[i] < min_dist) { min_dist = dist[4]; min_obj = 4; }
                }
            }
            if (min_dist < 20f)
            {
                transform.LookAt(go[min_obj].transform.position);
                if (hook_ability_available)
                    Do_Hook();
                else if (eb_ability_available)
                    Do_Earthbind();

                if (puff_ability_available)
                    Do_Puff();
            }
        }
    }
  • Future: Multiplayer

– Currently the project include integration of Google Play Games Services (i.e. shows Leaderboards and Achievements)
– The future update of the project will include Online Deathmatch rooms for online play. (Integrated with Photon, currently in progress)

Leave a Reply

Your email address will not be published. Required fields are marked *