/Modul-GIGa-Tutor-2021

GIGa Tutor 2021 - Shooting-Platformer 2D with Unity Modul

Primary LanguageASP.NET

Modul GIGa Tutor 2021

image

Shooting-Platformer 2D Unity Tutorial

Gameplay-Overview

A. Pendahuluan

Unity adalah cross-platform game engine dimana Unity adalah berfungsi sebagai aplikasi "editor" untuk pengembangan game (Semacam halnya Photoshop adalah aplikasi editor untuk manipulasi gambar).

Dalam Unity, dikenal namanya "Unity Project". Unity project secara umunya dibedakan menjadi 2 yaitu Project 3D dan 2D. Dalam modul ini kita akan lebih lanjut membahas membuat game dalam Unity Project 2D.

Contoh Tampilan Project Unity 3D dan 2D:

Unity 3D Preview

Sumber: (https://www.iamag.co/)

Unity 2D Preview

Sumber: (https://www.unity.com/)

B. Download Assets

Assets Sprite untuk modul ini dapat di download di Unlucid Adopted Assets

C. Setting Up Project

  1. Buka Unity Hub yang telah di download.

SettingUp-UnityHub

  1. Pilih New pada pojok kanan atas untuk membuat Project baru.

  2. Pilih Template 2D untuk membuat game 2D dan isi Project Name untuk Nama Project Game yang ingin dibuat dan Location untuk lokasi project yang akan ditempatkan.

SettingUp-UnityHubNewProject

  1. Tekan Create dan menunggu loading dari Unity untuk membuat Project Game 2D baru.

SettingUp-UnityInterface

D. Pengenalan Layout Unity

Scene View

LayoutUnity-SceneView

  • Sceneview adalah tempat dimana kita berinteraksi dengan world yang telah kita buat. Di dalam Sceneview kita dapat select, memanipulasi, menggerakkan (dan lain sebagainya) Camera, Lighting, dan GameObject lain yang berada di dalam Scene tersebut.

  • Didalam satu project game Unity bisa terdapat beberapa Scene. Biasanya jika dalam project game tersebut terdapat sistem Tahapan Level maka 1 level tersebut direpesentasikan dengan 1 Scene. Jika kita ingin membuat Level baru dengan environment dan object yang mungkin berbeda, maka kita akan membuat scene baru. Scene baru juga bisa dibuat untuk membuat main menu, Score Detail Scene dan lain sebagainya.

Game View

LayoutUnity-GameView

  • GameView adalah bentuk rendered dari scene yang telah dibangun dari perspektif camera. Secara singkatnya GameView berfungsi untuk preview bagaimana nantinya game kita jika dimainkan. Dengan GameView, kita bisa test memainkan game yang kita buat sebelum diBuild menjadi game utuh. Untuk menjalankan/test Game melalui GameView kita bisa menggunakan tombol play berikut: GameView-Play

Project Window

LayoutUnity-ProjectWindow

  • Project Window adalah window yang menampilkan semua files yang berhubungan dengan project kita. Project Window juga berfungsi untuk navigate dan mengatur/manage asset dan project files dalam suatu project. Semacam File Explorer dalam Windows, kita dapat membuat folder dalam Project Window agar para files bisa tersusun secara rapi (Semua Files yang berada di Project juga bisa diakses/dilihat dari File Explorer Windows).

Hierarchy

LayoutUnity-Hierarchy

  • Hierarchy atau Hierarchy Window mengandung dan menampilkan semua GameObject yang berhubungan dalam Scene yang sedang dikerjakan. GameObject seperti camera, lighting ,model 3D, dsb yang digunakan dalam Scene pasti akan tertampil dalam Hierarchy.

  • Dalam Hierarchy dikenal namanya Parenting.

Inspector

LayoutUnity-Inspector

  • Inspector befungsi untuk mengatur properties dan pengaturan untuk hampir semua yang ada di Unity Editor. Seperti GameObject, Unity Component, Assets, Materials, dan lain sebagainya.
  • GameObject adalah base class dari semua entitas yang berada dalam suatu Scene. Secara umumnya entitas yang berada dalam Scene seperti Camera, Lighting, Player, dan lain-lain base nya adalah GameObject. Dalam contoh Lighting/Light Object merupakan GameObject dengan menambahkan Light Component ke GameObject.
  • Component adalah behaviour dari GameObject. Component yang terdapat di GameObject bisa dilihat di Inspector Window. Transform yang mengatur posisi, scale, dan rotasi suatu object merupakan salah satu Component Object.

E. Import Assets

Extract file Assets yang telah didownload sebelumnya pada lokasi Project yang barusan dibuat.

ImportAssets-Extract

Sehingga struktur project yang baru kurang lebih seperti berikut ini.

ImportAssets-Structure

F. Pengenalan GameObject pada Unity

Membuat Player

  • Untuk membuat object player, seperti yang dijelaskan di atas pasti berasal dari GameObject dasar. Nantinya GameObject yang dibuat menjadi Player akan diberi beberapa component yang dibutuhkan sehingga object Player dapat dimainkan. Component yang umum untuk membangun suatu player biasanya adalah RigidBody,Collider,Script Movement, dan lain sebagainya (tergantung bagaimana player mau dibuat). Component yang nanti kita akan gunakan akan dijelaskan lebih lanjut.

  • Buat GameObject kosong (Empty) dengan cara GameObject > Create Empty.

IntroductionGameObject-PlayerEmpty

  • Ganti nama GameObject ini menjadi Player.

IntroductionGameObject-PlayerRename

  • Pada Hierarchy, klik kanan pada Player dan pilih Create Empty dan ubah namanya menjadi Graphic.

IntroductionGameObject-PlayerCreateChild

  • Pada child Player Graphic ini, tambahkan component Sprite Renderer.

IntroductionGameObject-PlayerAddSpriteRenderer

  • Sprite Renderer adalah suatu component di Unity yang berfungsi untuk render Sprite yang kita punya dan mengatur bagaimana Sprite tersebut tertampilkan di Scene.

  • Navigasi pada Assets/Sprites/Player dan pilih salah satu Sprite, dalam modul ini kami menggunakan player_idle_0.png sebagai contoh.

IntroductionGameObject-PlayerSpriteAssets

  • Klik pada sprite tersebut lalu pada Inspector ubah Pixels Per Unit menjadi 32 lalu Apply.

IntroductionGameObject-PlayerSpritePixels32

  • Drag Sprite tersebut menuju field Sprite pada component SpriteRenderer yang ada pada GameObject Graphic.

IntroductionGameObject-PlayerSpriteAssign

  • Jika berhasil, kita dapat melihat Player pada Scene View. Pilih Player lalu coba pindahkan posisinya dengan menggunakan axis transform.

IntroductionGameObject-PlayerPreview

  • Transform disini dapat diatur melalui Inspector Window(melalui Component Transport) atau bisa langsung dipindahkan memakai Transport Axis (bisa langsung menggunakan mouse untuk drag dan transform object yang diselect).

Mencoba Sprite Slicing untuk Forest Tile

  • Pada Project Assets pilih Sprite forest_tile.png.

IntroductionGameObject-ForestTileAssets

  • Pada Inspector ubah Sprite Mode menjadi Multiple, Pixels Per Unit menjadi 32, Mesh Type menjadi Full Rect lalu Apply.

IntroductionGameObject-ForestTileInspector

  • Pada Inspector lagi, klik button Sprite Editor

IntroductionGameObject-ForestTileInspectorSpriteEditor

  • Kemudian pada Sprite Editor pilih Slice lalu pilih Grid by Cell Size dan ganti Pixel Size menjadi 16x16. Kemudian klik Apply.

IntroductionGameObject-ForestTileSpriteEditor

  • Sprite Slicing adalah membagi kumpulan Sprite (biasanya Sprite yang objeknya sama tapi dengan detail yang berbeda akan dikumpulkan agar tersusun secara rapi/Sprite Sheet) menjadi sprite individual. Namun sprite slicing tidak hanya digunakan untuk Sprite sheet saja, tapi juga bisa pada sprite individual yang sudah ada, kita bagi menjadi beberapa sprite individual yang baru.

  • Hasil slicing sprite dapat dilihat pada Project Window, maka setiap potongan kotak dapat digunakan secara terpisah.

IntroductionGameObject-ForestTileSliceResult

  • Buat Game Object Empty dan ganti namanya menjadi Environment. Game Object ini untuk menampung lingkungan / pijakan / dekorasi pada level nanti

IntroductionGameObject-ForestTileEnvironment

  • Coba cari kotak dengan nama forest_tile_57 dan drag and drop ke Game Object Environment pada Hierarchy dan ubah namanya menjadi nama lain agar mudah dikenali.

IntroductionGameObject-ForestTileEnvironmentHierarchy

  • Pada SpriteRenderer ubah Draw Mode menjadi Tiled.

IntroductionGameObject-ForestTileEnvironmentSpriteRenderer

  • Sprite Tiling seperti kata dasarnya Tile. Seperti lantai yang tiap tile (ubin) nya itu sama, sprite yang kita punya akan diduplikasi sehingga tidak perlu memasukkan sprite satu-satu. Select object yang ingin kita tiling lalu kita tinggal mengatur width dan/atau Height yang kita preferensikan.

  • Modifikasi ukuran dan posisi Sprite dengan bantuan tools transform yang ada.

IntroductionGameObject-ForestTileEnvironmentTilingResult

  • Dengan cara yang sama coba cari forest_tile_44 dan coba hias level sedemikian rupa agar indah dan cantik.

IntroductionGameObject-ForestTileEnvironmentResult

Membuat Level

  • Pada seluruh sprite pada Project Window, ubah Pixels Per Unit nya menjadi 32. Bisa dengan cara select menggunakan shift click pada sprite yang diinginkan.

IntroductionGameObject-CreateLevel32

  • Pada inspector ubah Pixels Per Unit nya menjadi 32, lalu Apply. Lakukan pada semua sprite yang ada (Bullet, Environment, Mushroom, Player dan UI).

IntroductionGameObject-CreateLevel32Apply

  • Dengan cara drag and drop dari Project Window ke Graphic dan jadikan sprite tersebut sebagai child dari Environment, hias Level mu sesuai keinginan!

IntroductionGameObject-CreateLevel32Result

  • Terkadang urutan tampilan sprite tidak benar, bisa diatur pada Order in Layer pada setiap Sprite Renderer.

IntroductionGameObject-CreateLevel32LayerOrder

  • Sprite Layer Order adalah suatu fitur yang digunakan untuk mengatur urutan render dari suatu Sprite Renderer. Layer Order dengan nilai terbesar akan berada pada posisi paling depan, sedangkan nilai terkecil akan berada pada posisi paling bawah.

Menambahkan Physics

  • Physics 2D adalah bagaimana mekanis fisika di game kita bekerja (dalam kasus ini fisika 2 dimensi nya bagaimana). Physiscs di Unity bisa meliputi Gravity,Velocity,Speed Rotation dan banyak lagi lainnya yang bisa kita atur melalui Global Setting untuk Physics 2D. Physics 2D View Sumber: (www.unity.com)

  • Pada player menambahkan Rigidbody2D

image

  • Centang Freeze Rotation Z pada constraint Rigidbody2D

image

  • Tambahkan component Capsule Collider2D pada Player

image

  • Capsule Collider 2D adalah Component yang berfungsi untuk menetapkan batas bagaimana sebuah objek 2D dapat "bertabrakan"/Berinteraksi dengan objek lain.

  • Ubah ukuran Collider sesuai dengan sprite Player

image

  • Pada Environment tambahkan Box Collider2D pada tanah yang dapat dipijak Player dan sesuaikan ukuran collidernya

image

image

Membuat Prefab untuk Player

  • Dalam suatu Project di Unity kita bisa menggunakan suatu object dalam beberapa Scene (Karena biasanya setiap Scene memiliki object yang berbeda-beda antara 1 Scene dengan Scene yang lain). Jadi jika kita ingin menggunakan object tidak hanya di dalam satu Scene saja, kita dapat membuat object tersebut menjadi Prefab.

  • Pada Project Window, click kanan lalu Create > Folder untuk membuat folder baru pada Assets lalu beri nama Prefabs.

image

  • Buka Folder Prefabs tersebut, lalu pada Hierachy pilih Player dan drag pad Project Assets untuk membuat prefab Player.

image

Test Play pada Game

  • Edit mode adalah disaat kita dapat mengedit semua yang ada dalam Scene dan perubnahan tersebut bersifat permanent/tersimpan sehingga apabila beralih ke mode lain (Play Mode) perubahan yang kita lakukan tersebut tersimpan.

  • Game mode adalah saat kita menjalankan play dan kita dapat berinteraksi dengan game view. Saat berada di Game Mode kita tetap dapat melakukan perubahan di scene view. Namun, perubahan yang kita lalukan hanya bersifat temporary sehingga saat kuita kembalikan ke edit mode, perubahan yag kita lalukan akan hilang. Game Mode dapat diaktifkan dengan menekan tombol play lalu tekan tombol stop untuk kembali ke Edit mode.

  • Lakukan test Play dengan menekan tombol Play

image

  • Tampilan Game Mode, Player akan jatuh karena pengaruh Rigidbody2D

image

G. Membuat Entitas pada Game

Membuat Code Player

  • Buat Folder baru dengan nama Scripts dan buat Script baru dengan Create > C# Script dan beri nama PlayerController

image

  • Drag Script PlayerController dan drag pada gameobject Player pada Hierarchy. Atau bisa juga dengan klik Player dulu, lalu drag script PlayerController ke Inspector.

image

  • Jika kita menggunakan Engine Unity maka setiap script yang ingin kita buat dan terapkan dalam engine harus menggunakan bahasa pemrograman C#. Script ini dapat kita terapkan ke object yang kita inginkan sebagai salah satu component GameObject.

  • Klik dua kali script PlayerController dan masukkan Code Player berikut untuk Code Input dan Jalan

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

public class PlayerController : MonoBehaviour
{
    public static PlayerController Instance { get; private set; }

    [Header("Properties")]
    [SerializeField] private SpriteRenderer graphic;

    [Header("Status")]
    public float health = 100;
    public float attack = 5;

    public bool canBeMoved = true;

    public float healthMax { private set; get; }

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;

    private Rigidbody2D rb2;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();        

        healthMax = health;
    }

    void Update()
    {
        if (canBeMoved)
        {
            MovementController();
        }
    }

    void MovementController()
    {
        float x = Input.GetAxisRaw("Horizontal");

        Vector2 direction = rb2.velocity;
        direction.x = x * moveSpeed;

        rb2.velocity = direction;

        //sprite dibalik ketika arahnya ke kiri
        if (direction.x < 0)
        {
            graphic.flipX = true;
        } else if (direction.x > 0)
        {
            graphic.flipX = false;
        }
    }
}
  • Blok/Bagian Awake() bekerja saat scene mulai/start.

  • Blok/Bagian Start() bekerja sebelum first frame update

  • Blok/Bagian Update() akan bekerja sekali tiap 1 frame (akan bekerja berkali-kali secara terus menerus karena dalam game terdapat banyak frame per detik nya).

  • Vector3: Representasi gerakan (vector) 3 Dimensi dalam script.

  • Vector2: Representasi gerakan (vector) 2 Dimensi dalam script.

  • private: Private adalah salah satu acces modifiers dalam pemrograman berorientasi objek. Private sendiri berarti hanya dalam satu class yang sama yang dapat mengakses method atau variabel tersebut.

  • public: Public juga merupakan salah satu acces modifiers. kebalikannya dari private, public berarti method atau variabelnya bisa dilihat oleh class yang lain.

  • atribut [Serializable]: Atribut ini menandakan jika field yang diberi atribut ini dapat diserialized (field dapat diformat dan datanya disimpan oleh Unity).

  • atribut [SerializeField]: membuat field yang access modifiernya private dapat diserializedkan.

  • GetComponent: Berfungsi mendapatkan dan mengakses data dari Component GameObject. Sehingga kita bisa mendapatkan hingga merubah value dari Component GameObject melalui Script.

  • Assign Child Player (Graphic) pada Field Graphic Player Controller.

image

  • Coba Play dan test jalan dengan menggunakan Arrow Key kiri dan kanan. Gimana, Keren kan !?

image

Membuat Fitur Player Menembak

  • Buat GameObject baru dan beri nama Bullet lalu tambahkan child empty beri nama Graphic

image

  • Pada assets cari sprite Bullet dan ubah Pixel Per Unit menjadi 32 lalu Apply.

image

  • Pada Graphic tambahkan component Sprite Renderer dan pada field sprite pilih sprite Bullet dan ubah scale transform menjadi (0.4, 0.4, 0.4)

image

  • Pada GameObject Bullet tambahkan Rigidbody2D dan Circle Collider2D. Pada Rigidbody2D ubah Body Type menjadi Kinematik dan centang contraint Freeze Rotation Z. Pada Circle Collider2D sesuaikan ukuran dan letak circle collider dengan sprite bullet dan centang Is Trigger.

image

  • IsTrigger adalah collider yang dapat menjalankan script saat Collider tersebut menyentuh Trigger, Saat di Trigger, dan saat Exit dari object Trigger. IsTrigger di Script dapat dapat diTrigger dengan fungsi OnTriggerEnter,OnTriggerStay,dan OnTriggerExit.

  • Buat Script untuk bullet dengan nama Bullet dan simpan pada folder Scripts. Lalu tambahkan component script Bullet ini pada GameObject Bullet.

image

  • Membuat prefab GameObject Bullet dan simpan prefab Bullet pada folder Prefabs. Setelah itu hapus GameObject Bullet dari Hierarchy.

image

  • Berikut adalah script untuk Bullet
using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour
{
    [SerializeField] private float speed = 2;
    [SerializeField] private float damage = 2;
    [SerializeField] private float dieTime = 5;

    public string targetTag = "Enemy";

    private Vector2 direction;

    private Rigidbody2D rb2;

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        rb2.velocity = direction.normalized * speed;

        dieTime -= Time.deltaTime;

        if (dieTime < 0)
        {
            Destroy(gameObject);
        }
    }

    public void Launch(Vector2 direction, string targetTag, float speed, float damage)
    {
        this.direction = direction;
        this.speed = speed;
        this.damage = damage;
        this.targetTag = targetTag;
    }

    public float GetDamage()
    {
        return damage;
    }
}
  • Pada Script PlayerController modifikasi agar player dapat melakukan shooting bullet
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public static PlayerController Instance { get; private set; }

    [Header("Properties")]
    [SerializeField] private SpriteRenderer graphic;

    [Header("Status")]
    public float health = 100;
    public float attack = 5;

    public bool canBeMoved = true;

    public float healthMax { private set; get; }

    private bool isShooting = true;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;
    [SerializeField] private float shootingTimeMax = 1.0f;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float bulletSpeed = 10;

    [SerializeField] private Vector2 gunOffset;

    private float shootingTime = 0;

    private Rigidbody2D rb2;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();

        shootingTime = shootingTimeMax;
        healthMax = health;
    }

    void Update()
    {
        if (canBeMoved)
        {
            MovementController();
            ShootController();
        }
    }

    void MovementController()
    {
        float x = Input.GetAxisRaw("Horizontal");

        Vector2 direction = rb2.velocity;
        direction.x = x * moveSpeed;

        rb2.velocity = direction;

        //sprite dibalik ketika arahnya ke kiri
        if (direction.x < 0)
        {
            graphic.flipX = true;
        } else if (direction.x > 0)
        {
            graphic.flipX = false;
        }
    }

    void ShootController()
    {
        isShooting = Input.GetKey(KeyCode.Z);

        if (isShooting)
        {
            shootingTime -= Time.deltaTime;
        } else
        {
            shootingTime = shootingTimeMax;
        }

        if (isShooting && shootingTime < 0)
        {
            shootingTime = shootingTimeMax;
            Shoot();
        }
    }

    void Shoot()
    {
        int direction = (graphic.flipX == false ? 1 : -1);

        Vector2 gunPos = new Vector2(gunOffset.x * direction + transform.position.x, gunOffset.y + transform.position.y);

        GameObject bulletObj = Instantiate(bulletPrefab, gunPos, Quaternion.identity);
        Bullet bullet = bulletObj.GetComponent<Bullet>();

        if (bullet)
        {
            bullet.Launch(new Vector2(direction, 0),"Enemy", bulletSpeed, attack);
        }
    }
}
  • Assign prefab Bullet pada component PlayerController. Ubah property Gun Offset menjadi (0.75, -0.5)

image

  • Instantiate berfungsi untuk melakukan spawn game object. Biasanya Instantiate dilakukan untuk melakukan spawn prefab pada posisi tertentu.

  • Quaternion berfungsi untuk representasi rotasi dalam script. Jadi kita bisa mendapatkan informasi bagaimana posisi rotasi object dengan memanggil fungsi Quarterion.

  • Coba Test Play dan Tahan Z untuk melakukan shooting.

Membuat Enemy

  • Buat GameObject baru dan beri nama Mushroom lalu tambahkan child empty beri nama Graphic

image

  • Pada assets cari masuk ke Assets/Sprites/Mushroom dan ubah semua Pixel Per Unit dari sprites tersebut menjadi 32 lalu Apply.

image

image

  • Pada Graphic tambahkan component Sprite Renderer dan pada field sprite pilih sprite mushroom_0 sebagai contoh.

image

  • Pada GameObject Mushroom tambahkan Rigidbody2D dan Box Collider2D. Pada Rigidbody2D centang contraint Freeze Rotation Z. Pada Box Collider2D sesuaikan ukuran dan letak box collider dengan sprite mushroom.

image

  • Sebelum membuat code untuk Enemy. Buat tag untuk Player, Bullet dan Enemy dengan cara Klik Untagged lalu Add Tag.

image

image

  • Jangan lupa untuk mengganti tag Mushroom ke tag Enemy

image

  • Tambahkan juga tag Bullet pada Bullet dan tag Player pada Player

image

image

  • Tag adalah representasi Object dalam bentuk "kata" yang dapat dikustomisasi. Misal dalam satu scene kita mempunyai 2 Objek Balok yang sama. Tag dapat membedakan GameObject yang sama tersebut jika memiliki purpose yang berbeda-beda. Tag sangat penting untuk mengindentifikasi object game didalam script.

  • Tag dari tiap object pasti layernya diset default. Oleh karena itu player yang sudah kita buat layernya sudah diset ke default.

  • Buat Script baru dan beri nama EnemyController.

image

  • Berikut adalah code untuk EnemyController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    [Header("Status")]
    public float health = 20;
    public float attack = 5;

    public Transform attackTarget;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;

    void Update()
    {
        Movement();

        FallDie();
    }

    protected virtual void Movement()
    {

    }

    public void DamagedBy(float damage)
    {
        health -= damage;
        if (health <= 0)
        {
            health = 0;
            Die();
        }
    }

    void FallDie()
    {
        if (transform.position.y < -20)
        {
            Die();
        }
    }

    void Die()
    {
        Destroy(gameObject);
    }

    public float GetAttackDamage()
    {
        return attack;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Bullet")
        {
            Bullet bullet = collision.GetComponent<Bullet>();
            if (bullet.targetTag == "Enemy")
            {
                float damage = bullet.GetDamage();
                DamagedBy(damage);
                Destroy(collision.gameObject);
            }
        }
    }
}
  • OnTriggerEnter bekerja saat collider "menyentuh" trigger.

  • OnCollisionEnter prinsipnya sama seperti OnTriggerEnter namun bekerja saat collider "bertabrak" atau bertemu dengan collider lain.

  • Code EnemyController akan digunakan sebagai parent class dari enemy yang akan kita buat. Jadi untuk menambah enemy baru kita dapat melakukan inherit pada Class EnemyController ini.

Membuat Enemy Mushroom

  • Buat Script baru dan beri nama EnemyMushroom

image

  • Berikut adalah code script EnemyMushroom yang merupakan child class dari EnemyController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyMushroom : EnemyController
{
    [Header("Mushroom")]
    [SerializeField] SpriteRenderer graphic;

    [SerializeField] GameObject bulletPrefab;
    [SerializeField] private float bulletSpeed;

    [SerializeField] private float attackMinDistance;
    [SerializeField] private float attackTimeMax;

    [SerializeField] private Vector3 shootOffset;
    private float attackTime = 0;

    protected override void Movement()
    {
        if (attackTarget)
        {
            float distance = Vector2.Distance(transform.position, attackTarget.position);
            if (distance < attackMinDistance)
            {
                if (attackTarget.position.x < transform.position.x)
                {
                    graphic.flipX = true;
                } else
                {
                    graphic.flipX = false;
                }

                if (attackTime < 0)
                {
                    Shoot();
                    attackTime = attackTimeMax;
                }

                attackTime -= Time.deltaTime;
            }
        }
    }

    void Shoot()
    {
        int direction = (graphic.flipX == false ? 1 : -1);

        Vector3 shootOffsetDirection = shootOffset;

        if (direction < 0)
        {
            shootOffsetDirection.x *= -1;
        }

        Vector3 shootPos = transform.position + shootOffsetDirection;

        GameObject bulletObj = Instantiate(bulletPrefab, shootPos, Quaternion.identity);
        Bullet bullet = bulletObj.GetComponent<Bullet>();
        bullet.Launch(new Vector2(direction, 0), "Player", bulletSpeed, attack);
    }
}
  • Tambahkan Component EnemyMushroom pada GameObject Mushroom. Isi field graphic dengan child Graphic pada Mushroom dan Attack Target isi dengan Game Object Player.

image

  • Karena mushroom menyerang dengan cara shooting bullet juga maka kita lakukan duplicate pada prefab Bullet dan ganti namanya menjadi EnemyBullet lalu ubah warnanya menjadi merah.

image

image

  • Pada BulletEnemy ubah TargetTag menjadi "Player". Ini akan membuat bullet hanya dapat mengenai collider gameobject yang memiliki tag "Player". Seperti halnya bullet milik player hanya dapat mengenai collider gameobject yang memiliki tag "Enemy". Property TargetTag ini akan digunakan pada script - script yang akan datang.

image

  • Assign bullet pada component EnemyMushroom sebelumnya dengan EnemyBullet. Atur konfigurasinya pada inspector seperti berikut.

image

  • Membuat Prefab untuk Enemy Mushroom.

image

Membuat Fitur Player Loncat

  • Sebelum membuat fitur loncat, pada setiap GameObject tanah yang memiliki collider pada Environment, ubah layer nya menjadi Obstacle. Jika belum ada tambahkan dengan Add Layer.

image

image

  • Setelah itu modifikasi Script PlayerController sebagai berikut.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public static PlayerController Instance { get; private set; }

    [Header("Properties")]
    [SerializeField] private SpriteRenderer graphic;

    [Header("Status")]
    public float health = 100;
    public float attack = 5;

    public bool canBeMoved = true;

    public float healthMax { private set; get; }

    private bool isGrounded = true;
    private bool isShooting = true;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float shootingTimeMax = 1.0f;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float bulletSpeed = 10;

    [SerializeField] private Vector2 gunOffset;

    private float shootingTime = 0;

    [SerializeField] private float minGroundDistance = 1.5f;

    private Rigidbody2D rb2;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();

        shootingTime = shootingTimeMax;
        healthMax = health;
    }

    void Update()
    {
        if (canBeMoved)
        {
            MovementController();
            JumpController();
            ShootController();
        }
    }

    void MovementController()
    {
        float x = Input.GetAxisRaw("Horizontal");

        Vector2 direction = rb2.velocity;
        direction.x = x * moveSpeed;

        rb2.velocity = direction;

        //sprite dibalik ketika arahnya ke kiri
        if (direction.x < 0)
        {
            graphic.flipX = true;
        }
        else if (direction.x > 0)
        {
            graphic.flipX = false;
        }
    }

    void JumpController()
    {
        RaycastHit2D ray = Physics2D.Raycast(transform.position, Vector2.down, 10, LayerMask.GetMask("Obstacle"));

        if (ray && ray.distance < minGroundDistance)
        {
            isGrounded = true;
        }
        else
        {
            isGrounded = false;
        }

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Jump();
        }
    }

    void Jump()
    {
        if (isGrounded)
        {
            rb2.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
        }
    }

    void ShootController()
    {
        isShooting = Input.GetKey(KeyCode.Z);

        if (isShooting)
        {
            shootingTime -= Time.deltaTime;
        }
        else
        {
            shootingTime = shootingTimeMax;
        }

        if (isShooting && shootingTime < 0)
        {
            shootingTime = shootingTimeMax;
            Shoot();
        }
    }

    void Shoot()
    {
        int direction = (graphic.flipX == false ? 1 : -1);

        Vector2 gunPos = new Vector2(gunOffset.x * direction + transform.position.x, gunOffset.y + transform.position.y);

        GameObject bulletObj = Instantiate(bulletPrefab, gunPos, Quaternion.identity);
        Bullet bullet = bulletObj.GetComponent<Bullet>();

        if (bullet)
        {
            bullet.Launch(new Vector2(direction, 0), "Enemy", bulletSpeed, attack);
        }
    }
}
  • Ray dapat dimisalkan sebagai cahaya yang ditembakkan ke arah tertentu untuk mendapatkan Raycast Hit. Dalam Unity Ray dapat digunakan sebagai pengukur jarak dari posisi Ray ditembakkan ke arah yang dituju. Ray akan berhenti ketika melewati jarak yang diinginkan atau menabrak suatu Collider tertentu.

  • Raycast Hit merupakan hasil dari Ray yang ditembakkan. Terdapat banyak property pada RaycastHit. Contohnya adalah posisi berhenti, jarak hit dan banyak lainnya.

  • Layer merupakan suatu golongan yang dapat digunakan untuk mengelompokkan suatu game object pada scene. Namun fungsi layer tidak hanya terbatas untuk pengelompokan saja. Terdapat fungsi penting, salah satunya ada pada Camera, kita dapat mengatur layer mana saja yang dapat dirender oleh camra tersebut.

  • Layer Mask merupakan suatu layer yang digunakan untuk masking collider. Salah satu penggunaannya adalah saat melakukan Raycast dari titik tertentu, kita dapat mencegah ray itu hanya dapat menabrak collider yang berada pada layer "Obstacle" misalnya sehingga Raycast tidak terhenti pada collider Player.

  • Coba Play Test lalu tekan Up Arrow untuk loncat dengan Player.

image

  • ICE BREAKING : Coba edit Jump Force menjadi 20 untuk lompatan super atau ganti Min Ground Distance menjadi 5 agar bisa jump on air.

Membuat Camera Follow

  • Camera berfungsi sebagai object yang digunakan user/pemain untuk melihat world dari game yang sudah dibuat. Tampilan pada GameView akan mengikuti arah view camera yang telah diatur.

image

  • Pada Camera terdapat property Clear Flags. Clear Flags merupakan suatu fitur agar camera melakukan render pada background dengan cara yang diberikan. Misalnya pada Skybox, camera akan melakukan render Skybox pada bagian camera yang tidak merender game object, atau pada Solid Color sehingga camera memberkian warna solid pada bagian camera yang tidak merender game object.

image

  • Mengubah warna Background Camera, coba ubah warna background camera menjadi #03071C

image

  • Selanjutnya adalah membuat suatu script agar Main Camera dapat mengikuti pergerakan Player.

  • Buat Script baru di folder Scripts dengan nama CameraFollow

image

  • Berikut adalah kode script untuk CameraFollow
using UnityEngine;
using System.Collections;

public class CameraFollow : MonoBehaviour
{
    public bool isFollowing = true;
    public Transform target;

    private Vector3 targetPos;
    public Vector3 targetOffset;

    [Range(0, 1.0f)] public float followRatio = 1.0f;

    void Update()
    {
        if (target)
        {
            if (isFollowing)
            {
                targetPos = target.position + targetOffset;
            }

            Vector3 newPos = Vector3.Lerp(transform.position, targetPos, followRatio);
            newPos.z = transform.position.z;

            transform.position = newPos;
        }
    }
}
  • Attribute Range merupakan salah satu attribute yang dapat mengubah field menjadi Range dari suatu variable. Contohnya adalah menggunakan Range dari 0 sampai 1 sehingga nilai variable tersebut dapat diatur dari inspector dari 0 hingga 1.

  • Lerp atau Linear Interpolation merupakan satu metode yang digunakan untuk melakukan specify nilai tertentu. Misal terdapat suatu nilai dari 0 hingga 100, dengan menggunakan Lerp dan ratio 0.5 maka nilai tersebut akan bernilai 50.

  • Tambahkan component script CameraFollow pada Game Object Main Camera

image

  • Assign Player pada field Target dan isi field yang lain sesuai kebutuhan

image

H. Membuat GameMaster yang mengontrol sebagian besar Game

Membuat GameManager

  • SceneManagement adalah salah satu library/module dalam script Unity yang berisi berbagai fungsi untuk manage para scenes yang sudah kita buat di unity. Bagaimana kita mengatur perpindahan dari scene A ke scene B, misal dari scene Main menu ke Scene Level 1 atau sebaliknya dan berbagai fungsi lain.

  • ActiveScene adalah Scene yang sedang running/active saat game berjalan.

  • LoadScene berfungsi untuk memuat scene yang akan diakses selanjutnya/berpindah ke scene yang dipilih.

  • buildIndex: Setiap Scene pasti mempunyai index nya sendiri (diatur saat akan build game). Sehingga jika ingin memanggil suatu Scene tinggal memanggil index dari scene tersebut. Contoh :SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); berarti kita mengakses Scene yang index nya lebih 1 dari Active Scene yang sedang berjalan.

  • Kita akan mengamalkan Singleton Pattern pada GameManager. Singleton Pattern merupakan suatu metode menyimpan Instance dari class tersebut pada suatu static property pada class itu sendiri. Hal ini digunakan untuk menghindar dari referensi object yang berlebihan atau tidak cocok untuk direferensikan.

  • Berikut adalah code untuk Script GameManager.

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

public class GameManager : MonoBehaviour
{
    public static bool isGameOver { get; private set; }
    public static bool isLevelComplete { get; private set; }

    private void Awake()
    {
        isGameOver = false;
        isLevelComplete = false;
    }

    private void Update()
    {
        if (isLevelComplete)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                NextLevel();
            }
        }
        else if (isGameOver)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            { 
                RetryGame();
            }
        }
    }

    public static void GameOver()
    {
        isGameOver = true;
    }

    public static void CompleteLevel()
    {
        isLevelComplete = true;
    }

    public static void NextLevel()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
    }

    public static void RetryGame()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

Membuat ScoreManager

  • Sama seperti GameManager, ScoreManager juga akan mengamalkan Singleton Pattern.

  • Berikut merupakan code untuk Script ScoreManager.

using UnityEngine;
using System.Collections;

public class ScoreManager : MonoBehaviour
{
    public static ScoreManager Instance { get; private set; }

    public static int currentEnemyProgress { get; private set; }
    public static int targetEnemyProgress { get; private set; }

    void Awake()
    {
        Instance = this;
    }

    private void Start()
    {
        EnemyController[] enemyControllers = FindObjectsOfType<EnemyController>();

        targetEnemyProgress = enemyControllers.Length;
        currentEnemyProgress = 0;
    }

    public static void DefeatEnemy()
    {
        currentEnemyProgress += 1;

        if (currentEnemyProgress >= targetEnemyProgress)
        {
            GameManager.CompleteLevel();
        }
    }
}

Membuat Game Master

  • Buat GameObject Empty baru, lalu tambahkan component GameManager dan ScoreManager

image

Membuat Fitur Player mati

  • Setiap kali player terkena bullet dari Enemy atau player menyentuh Enemy atau ketika player jatuh, kita dapat menambahkan fitur agar Player bisa mati sehingga level akan di restart. Untuk itu modifikasi PlayerController sebagai berikut.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public static PlayerController Instance { get; private set; }

    [Header("Properties")]
    [SerializeField] private SpriteRenderer graphic;

    [Header("Status")]
    public float health = 100;
    public float attack = 5;

    public bool canBeMoved = true;

    public float healthMax { private set; get; }

    private bool isGrounded = true;
    private bool isShooting = true;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float shootingTimeMax = 1.0f;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float bulletSpeed = 10;

    [SerializeField] private Vector2 gunOffset;

    private float shootingTime = 0;

    [SerializeField] private float minGroundDistance = 1.5f;

    private Rigidbody2D rb2;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();

        shootingTime = shootingTimeMax;
        healthMax = health;
    }

    void Update()
    {
        if (canBeMoved)
        {
            MovementController();
            JumpController();
            ShootController();            
        }

        FallDie();
    }

    void MovementController()
    {
        float x = Input.GetAxisRaw("Horizontal");

        Vector2 direction = rb2.velocity;
        direction.x = x * moveSpeed;

        rb2.velocity = direction;

        //sprite dibalik ketika arahnya ke kiri
        if (direction.x < 0)
        {
            graphic.flipX = true;
        } else if (direction.x > 0)
        {
            graphic.flipX = false;
        }
    }

    void JumpController()
    {
        RaycastHit2D ray = Physics2D.Raycast(transform.position, Vector2.down, 10, LayerMask.GetMask("Obstacle"));
        
        if (ray && ray.distance < minGroundDistance)
        {
            isGrounded = true;
        } else
        {
            isGrounded = false;
        }

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Jump();
        }
    }

    void Jump()
    {
        if (isGrounded)
        {
            rb2.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
        }
    }

    void ShootController()
    {
        isShooting = Input.GetKey(KeyCode.Z);

        if (isShooting)
        {
            shootingTime -= Time.deltaTime;
        } else
        {
            shootingTime = shootingTimeMax;
        }

        if (isShooting && shootingTime < 0)
        {
            shootingTime = shootingTimeMax;
            Shoot();
        }
    }

    void Shoot()
    {
        int direction = (graphic.flipX == false ? 1 : -1);

        Vector2 gunPos = new Vector2(gunOffset.x * direction + transform.position.x, gunOffset.y + transform.position.y);

        GameObject bulletObj = Instantiate(bulletPrefab, gunPos, Quaternion.identity);
        Bullet bullet = bulletObj.GetComponent<Bullet>();

        if (bullet)
        {
            bullet.Launch(new Vector2(direction, 0),"Enemy", bulletSpeed, attack);
        }
    }

    public void DamagedBy(float damage)
    {
        health -= damage;
        if (health <= 0)
        {
            health = 0;
            Die();
        }
    }

    void FallDie()
    {
        if (transform.position.y < -20)
        {
            Die();
        }
    }

    void Die()
    {
        graphic.enabled = false;
        canBeMoved = false;
        GameManager.GameOver();
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == "Enemy")
        {
            float damage = collision.collider.GetComponent<EnemyController>().GetAttackDamage();
            DamagedBy(damage);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Bullet")
        {
            Bullet bullet = collision.GetComponent<Bullet>();
            if (bullet.targetTag == "Player")
            {
                float damage = bullet.GetDamage();
                DamagedBy(damage);
                Destroy(collision.gameObject);
            }
        }
    }
}

I. Membuat UI untuk memperindah Game

Membuat UI

  • UI atau kepanjangannya adalah User Interface adalah salah satu komponen dalam game yang sangat penting. UI berfungsi sebagai sistem komponen visual yang berfungsi menjembatani user/player dengan game yang kita buat. Bagaimana player dari game kita berinteraksi dengan game, informasi penting dari game yang ditampilkan ke player, status dari game kita (Game Over dan sejenisnya) termasuk fungsi dari User Interface. UI dapat meliputi tampilan HUD dalam game, tombol-tombol, hingga tampilan pengaturan game.

  • Buat GameObject Canvas untuk UI

image

  • Canvas adalah tempat dimana semua yang berhubungan dengan komponen UI dalam game ditaruh dan diatur placementnya. Tampilan canvas bisa diatur melalui Scene View.

  • Select GameObject Canvas dan ubah konfigurasi component Canvas dan Canvas Scaler sebagai berikut.

image

  • Canvas Scaler adalah Component dari UI yang berfungsi mengatur overall scale dan pixel density dari semua element UI yang berada di dalam Canvas. Oleh karena itu, saat mengubah/mengatur di Canvas Scaler, semua elemen UI yang berada di Canvas yang telah dipilih akan terpengaruh (dari segi ukuran (scale dan pixel)).

Membuat Healthbar Player

  • Pada Project Assets masuk ke folder UI, ubah semua Pixels Per Unit sprite menjadi 32.

image

image

  • Pada Hierarchy, klik kanan pada Canvas lalu Create Empty. Beri nama child tersebut menjadi Healthbar

image

  • Klik kanan lagi pada Healthbar, lalu pilih UI > Image. Lakukan sebanyak 3 kali karena masing - masing untuk Healthbar Bg, Healthbar Border, Healthbar sebelumnya.

image

  • Sorting Order: Sorting order sistemnya seperti layer. object yang berada di bawah object lain (di panel Hierarchy) akan ditampilkan lebih depan. Contoh: SortingOrder1 Dapat dilihat dalam hierarchy object MainMenu dibawah Panel sehingga Object MainMenu berada di depan panel hitam. Coba kita balik urutannya: SortingOrder2 Bisa kita lihat saat order nya dibalik object panel akan membelakangi object main menu.

  • Untuk masing - masing Image UI tersebut, assign sprite yang sesuai. Sesuaikan konfigurasinya seperti berikut agar healthbar terlihat rapi dan bagus.

image

image

image

  • Berikut adalah hasil sementara dari Healthbar UI.

image

Membuat Score UI

  • Pada Hierarchy, klik kanan pada Canvas lalu Create Empty. Beri nama child tersebut menjadi EnemyProgress

image

  • Klik kanan lagi pada EnemyProgress, lalu pilih UI > Text. Beri nama Text tersebut menjadi ProgressText. UI Text ini untuk melakukan track pada score Player.

image

  • UI Text adalah komponen UI yang menampilkan text pada tampilan layar player.

  • Pada Component Text, ubah Text menjadi "4 of 10" sebagai contoh. Lalu ubah juga Font Size, Horizontal Overflow dan Vertical Overflow nya.

image

  • Klik kanan lagi pada EnemyProgress, lalu pilih UI > Image. Beri nama Image tersebut menjadi ProgressCover. Image UI ini digunakan untuk hiasan score saja.

image

  • Berikut adalah tampilan sementara dari GameObject EnemyProgress.

image

Membuat Tampilan Game Over

  • Pada Canvas, buat UI > Image dan beri nama GameOver untuk membuat vignette. Ubah warnanya menjadi hitam dan alpha yang tipis.

image

  • Lalu pada GameObject GameOver tambahkan child text untuk tulisan "Game Over" dan "Press Space to Retry Level".

image

image

image

  • Berikut adalah tampilan sementara dari GameOver.

image

  • Lalu Disable GameOver, karena kita ingin GameOver ditampilkan saat Game Over saja.

image

Membuat Tampilan Level Complete

  • Duplicate UI GameOver dan ganti namanya menjadi LevelComplete

image

  • Edit tulisan seperti yang diinginkan sehingga tampilan LevelComplete adalah sebagai berikut.

image

  • Disable juga LevelComplete, karena kita ingin LevelComplete ditampilkan saat Level berhasil dimenangkan saja.

image

Membuat UIManager

  • Agar UI dapat berubah seiring dengan Game dimainkan maka perlu sebuah script untuk mengatur UI tersebut.

  • Buat script baru pada folder Scripts dan beri nama menjadi UIManager.

image

  • Berikut adalah code untuk UIManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    public Text enemyProgressText;
    public Image playerHealthBar;
    public float playerHealthBarFullX = 78;

    public GameObject gameOverUI;
    public GameObject levelCompleteUI;

    public PlayerController player;

    void Update()
    {
        if (enemyProgressText)
        {
            enemyProgressText.text = ScoreManager.currentEnemyProgress + " of " + ScoreManager.targetEnemyProgress;
        }

        if (playerHealthBar)
        {
            Vector2 size = playerHealthBar.rectTransform.sizeDelta;
            size.x = player.health / player.healthMax * playerHealthBarFullX;

            playerHealthBar.rectTransform.sizeDelta = size;
        }

        if (levelCompleteUI && GameManager.isLevelComplete)
        {
            levelCompleteUI.SetActive(GameManager.isLevelComplete);
        } else if (gameOverUI && GameManager.isGameOver)
        {
            gameOverUI.SetActive(GameManager.isGameOver);
        }
    }
}
  • Elemen UI yang berupa text dapat diubah melalui script/code. contoh nya adalah script berikut: enemyProgressText.text = ScoreManager.currentEnemyProgress + " of " + ScoreManager.targetEnemyProgress;. Nantinya UI Text yang ingin diubah akan diattach ke Component script yang sudah terpasang di GameMaster.

  • Pada EnemyController panggil method DefeatEnemy() pada ScoreManager agar progress dapat bertambah ketika Enemy menjalankan method Die().

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

public class EnemyController : MonoBehaviour
{
    [Header("Status")]
    public float health = 20;
    public float attack = 5;

    public Transform attackTarget;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;

    void Update()
    {
        Movement();

        FallDie();
    }

    protected virtual void Movement()
    {

    }

    public void DamagedBy(float damage)
    {
        health -= damage;
        if (health <= 0)
        {
            health = 0;
            Die();
        }
    }

    void FallDie()
    {
        if (transform.position.y < -20)
        {
            Die();
        }
    }

    void Die()
    {
        Destroy(gameObject);
        ScoreManager.DefeatEnemy();
    }

    public float GetAttackDamage()
    {
        return attack;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Bullet")
        {
            Bullet bullet = collision.GetComponent<Bullet>();
            if (bullet.targetTag == "Enemy")
            {
                float damage = bullet.GetDamage();
                DamagedBy(damage);
                Destroy(collision.gameObject);
            }
        }
    }
}
  • Kemudian tambahkan component UIManager pada GameObject GameMaster. Isi konfigurasi UI seperti berikut ini.

image

  • Coba mainkan dan kalahkan semua enemy!

image

J. Membuat Scene Baru Agar Game Lebih Menyenangkan

Pengenalan pada Build Settings

  • Build Settings adalah window yang isi pengaturan utamanya adalah memilih target platform dari build dari game yang sudah kita buat. Namun dari Build Settings kita juga dapat menambahkan scene dan mengatur urutan bagaimana setiap scene akan dibuild.
  • Pilih File > Build Settings pada Menu Bar untuk membuka Build Settings

image

image

  • Saat ini hanya ada 1 scene, jika belum ada maka tambahkan dengan menekan tombol Add Open Scenes

image

  • Build Index: adalah index dari tiap scene yang akan dibuild. Index scene dimulai dari '0' hingga banyak scene-1. Urutan Build Index berdasarkan penempatan scene pada Build Settings (Urutan dari atas ke bawah).

Membuat Scene Baru

  • Pada Project Window folder Scenes rename Scene sekarang menjadi "Level 1"

image

  • Kemudian, lelakukan Duplicate Scene dari Scene Level 1 dengan Ctrl + D lalu ubah namanya menjadi Level 2. Hal ini dapat diulangi untuk membuat level - level selanjutnya.

image

  • Buka Scene Level 2 dan memodifikasi Scene tersebut. Sebagai contoh tambahkan enemy yang lebih banyak dari Level 1.

image

  • Menambah scene pada build

image

  • Buat Scene Baru lagi dan coba buat tampilan seperti berikut. Beri nama scene tersebut End atau EndLevel.

image

  • Kemudian tambahkan scene ini pada build

image

  • Coba Play Game dari scene Level 1 dan mainkan game mu.

K. Build Game agar game dapat Dimainkan!

Pengenalan Platform

  • Game yang ingin dibuild dapat dibuild ke versi berbagai platform. Misal kita rancang game kita untuk dimainkan di android, maka kita akan set platform build nya ke android. Begitu pula untuk yang lainnya.

image

Pengenalan Player Settings

  • Melakukan Build untuk Windows. Klik Build bukan yang Build and Run

image

  • Pilih lokasi build, disarankan folder kosong. Beri nama game dengan kata yang kamu sukai.

image

  • Tunggu hingga proses build selesai. Lalu coba Play Executable. Dalam modul ini nama game adalah Unlucid. Run Unlucid.exe untuk membuka game.

image

L. Selesai !!

Game dapat dimainkan

Gameplay-Overview

M. Extra!! Menambahkan Animasi

Menambah Animasi pada Player

  • Pada Project Window buat folder baru dan beri nama "Animations". Lalu pada folder Animations buat folder lagi dengan nama Player. Folder Player akan digunakan untuk menyimpan animasi dari Player.

image

image

  • Masuk ke folder Animation lalu buat Animator dengan cara klik kanan lalu Create > Animator Controller dan beri nama "PlayerAnimator".

image

  • Animator atau Animator Controller merupakan salah satu component di Unity yang digunakan untuk mengatur jalannya Animation pada suatu game object.

  • Masih di folder Animations/Player klik kanan lalu Create > Animation untuk membuat Animation Clip dan beri nama "PlayerIdle". Lakukan beberapa kali untuk membuat Animation Clip bernama "PlayerJumpUp", "PlayerJumpDown", "PlayerRun".

image

  • Animation merupakan potongan clip animasi yang dapat digunakan pada Animator. Contoh Animation adalah clip Run, Jump, Walk, Idle dan lain sebagainya.

  • Pada Hierarchy pilih Player, lalu tambahkan component Animator. Kemudian assign PlayerAnimator sebelumnya pada component tersebut.

image

  • Double klik pada PlayerAnimator untuk membuka Animator Window.

image

  • Untuk setiap Animation Clip Player yang dibuat, drag and drop pada Animator Window ini.

image

  • Pada Animator terdapat Transition yang digunakan untuk merubah jalannya animasi. Misal dari animasi Idle ke Run membutuhkan transition. Transition juga dicontrol berdasarkan arahan yang diberikan User. Misal animasi Jump hanya dapat dilakukan saat Idle atau Run, tetapi tidak bisa saat Sleep.

  • Pada Animator Window, Klik kanan pada Any State lalu Make Transition ke semua klip yang telah dimasukkan.

image

  • Any State maksudnya adalah transition dapat dilakukan pada state manapun. Sebagai contoh meskipun tidak ada transition dari PlayerIdle menuju PlayerRun, maka Any State tetap dapat menjadi penghubung transisi antara PlayerIdle menuju PlayerRun.

  • Animation Clip dapat berupa looping yang dapat di atur. Seperti namanya Animation Looping adalah animasi berulang, seperti Idle atau Running karena Animasi akan bergerak terus menerus.

  • Untuk setiap Animation Clip Player enable Loop Time.

image

image

  • Untuk masing - masing Animation Clip Player kita tambahkan animasi di dalamnya. Sebagai contoh berikut adalah PlayerIdle. Double click PlayerIdle untuk memunculkan Animation Window.

image

  • Tambahkan Property dengan Add Property lalu pilih Graphic/SpriteRenderer/Sprite.

image

image

  • Pada Project Window masuk ke folder Sprites/Player dan pilih semua sprite player_idle lalu drag and drop ke Animation Window pada Graphic/SpriteRenderer/Sprite tadi.

image

  • Ubah Samples Rate menjadi 10 dan sesuaikan time-nya dengan gambar diatas.

  • Lakukan hal yang sama pada PlayerJumpUp, PlayerJumpDown dan PlayerRun.

image

image

image

  • Untuk berpindah Transition diperlukan parameter yang dapat mengatur jalannya animasi. Misal dari Idle ke Run hanya dapat dilakukan ketika parameter IsRunning bernilai true.

  • Pada PlayerAnimator, tambahkan parameter bertipe boolean dan beri nama isGrounded. Lalu parameter velocityX dan velocityY yang bertipe integer.

image

image

  • Untuk melakukan transisi diperlukan Condition untuk mengatur alur Transition. Untuk setiap Transition ubah konfigurasinya seperti berikut.

image

image

image

image

  • Transition dapat berubah karena perubahan Parameter yang mempengaruhi Condition. Oleh karena itu kita perlu melakukan modifikasi pada code PlayerController dan menambahkan perubahan paramter. Berikut adalah code PlayerController yang telah dimodifikasi.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public static PlayerController Instance { get; private set; }

    [Header("Properties")]
    [SerializeField] private SpriteRenderer graphic;

    [Header("Status")]
    public float health = 100;
    public float attack = 5;

    public bool canBeMoved = true;

    public float healthMax { private set; get; }

    private bool isGrounded = true;
    private bool isShooting = true;
    private int velocityX = 0;
    private int velocityY = 0;

    [Header("Configuration")]
    [SerializeField] private float moveSpeed = 2.5f;
    [SerializeField] private float jumpForce = 5.0f;
    [SerializeField] private float shootingTimeMax = 1.0f;
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private float bulletSpeed = 10;

    [SerializeField] private Vector2 gunOffset;

    private float shootingTime = 0;

    [SerializeField] private float minGroundDistance = 1.5f;

    private Rigidbody2D rb2;
    private Animator animator;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        rb2 = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();

        shootingTime = shootingTimeMax;
        healthMax = health;
    }

    void Update()
    {
        if (canBeMoved)
        {
            MovementController();
            JumpController();
            ShootController();

            AnimationController();
        }

        FallDie();
    }

    void MovementController()
    {
        float x = Input.GetAxisRaw("Horizontal");

        Vector2 direction = rb2.velocity;
        direction.x = x * moveSpeed;

        rb2.velocity = direction;

        //sprite dibalik ketika arahnya ke kiri
        if (direction.x < 0)
        {
            graphic.flipX = true;
        } else if (direction.x > 0)
        {
            graphic.flipX = false;
        }
    }

    void JumpController()
    {
        RaycastHit2D ray = Physics2D.Raycast(transform.position, Vector2.down, 10, LayerMask.GetMask("Obstacle"));
        
        if (ray && ray.distance < minGroundDistance)
        {
            isGrounded = true;
        } else
        {
            isGrounded = false;
        }

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Jump();
        }
    }

    void Jump()
    {
        if (isGrounded)
        {
            rb2.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
        }
    }

    void ShootController()
    {
        isShooting = Input.GetKey(KeyCode.Z);

        if (isShooting)
        {
            shootingTime -= Time.deltaTime;
        } else
        {
            shootingTime = shootingTimeMax;
        }

        if (isShooting && shootingTime < 0)
        {
            shootingTime = shootingTimeMax;
            Shoot();
        }
    }

    void Shoot()
    {
        int direction = (graphic.flipX == false ? 1 : -1);

        Vector2 gunPos = new Vector2(gunOffset.x * direction + transform.position.x, gunOffset.y + transform.position.y);

        GameObject bulletObj = Instantiate(bulletPrefab, gunPos, Quaternion.identity);
        Bullet bullet = bulletObj.GetComponent<Bullet>();

        if (bullet)
        {
            bullet.Launch(new Vector2(direction, 0),"Enemy", bulletSpeed, attack);
        }
    }

    public void DamagedBy(float damage)
    {
        health -= damage;
        if (health <= 0)
        {
            health = 0;
            Die();
        }
    }

    void FallDie()
    {
        if (transform.position.y < -20)
        {
            Die();
        }
    }

    void Die()
    {
        graphic.enabled = false;
        canBeMoved = false;
        GameManager.GameOver();
    }

    void AnimationController()
    {
        velocityX = (int)Mathf.Clamp(rb2.velocity.x, -1, 1);
        velocityY = (int)Mathf.Clamp(rb2.velocity.y, -1, 1);

        animator.SetBool("isGrounded", isGrounded);
        animator.SetInteger("velocityX", velocityX);
        animator.SetInteger("velocityY", velocityY);
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == "Enemy")
        {
            float damage = collision.collider.GetComponent<EnemyController>().GetAttackDamage();
            DamagedBy(damage);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Bullet")
        {
            Bullet bullet = collision.GetComponent<Bullet>();
            if (bullet.targetTag == "Player")
            {
                float damage = bullet.GetDamage();
                DamagedBy(damage);
                Destroy(collision.gameObject);
            }
        }
    }
}
  • Coba Play Game dan amati animasi baru pada Player.

Menambah Animasi pada Enemy

  • Pada folder Animations buat folder baru dengan nama "Mushroom"

image

  • Lalu buat Animator Controller baru dengan nama MushroomAnimator dan Animation Clip dengan nama MushroomIdle.

image

  • Pada MushroomIdle aktifkan Loop Time.

image

  • Pada prefab EnemyMushroom tambahkan component Animator. Assign MushroomAnimator pada component Animator tersebut.

image

  • Double click pada MushroomAnimator dan drag and drop MushroomIdle pada Animator Window.

image

  • Double click pada Animation Clip MushroomIdle untuk membuka Animation Window.

  • Pada clip MushroomIdle click Add Property lalu tambahkan Graphic/SpriteRenderer/Sprite.

image

  • Pada Project Window masuk ke folder Sprites/Mushroom lalu pilih semua sprites mushroom_idle. Drag and drop sprites tersebut pada Animation Window bagian Graphic/SpriteRenderer/Sprite tadi.

image

  • Ubah Samples Rate menjadi 10 dan sesuaikan seperti pada gambar diatas.

  • Pada Prefab Mushroom simpan perubahan agar seluruh Mushroom memiliki Animator yang sama.

  • Coba Play Game dan amati animasi baru pada Enemy Mushroom.

Gameplay-Overview

Penutup

Terima Kasih telah mengikuti GIGa Tutor 2021. Jangan lupa untuk datang pada Event GIGa Talk 2021 pada tanggal 23 Mei 2021! Banyak tema dan tips menarik yang akan dibahas!

image

image

Terima Kasih !!

Kami nantikan kedatanganmu di GIGa Talk 👊😎

Referensi Belajar GameDev Lebih Lanjut

http://notepad.pw/linkYoutubeGIGaTalk