สร้างเกม 2D ด้วย Unity ตั้งแต่ศูนย์: เอาตัวละครเข้าเกม เขียนโค้ด รัน และทดสอบ

Unity เป็นเครื่องมือสร้างเกมที่นิยมที่สุดเครื่องมือหนึ่งสำหรับทั้งมือใหม่และทีมมืออาชีพ เกม 2D มีข้อดีคือโครงสร้างไม่ซับซ้อนมาก เหมาะกับการเรียนรู้หลักการสำคัญ เช่น ฉาก ออบเจกต์ คอมโพเนนต์ ฟิสิกส์ และการอ่านปุ่มกด บทความนี้จะพาคุณตั้งโปรเจกต์ใหม่ เอาตัวละครเข้าไปในฉาก เขียนสคริปต์ให้เดิน กระโดด และให้กล้องตามตัวละคร แล้วทดสอบว่าโค้ดทำงานถูกต้อง

ภาพแผนผังหน้าต่าง Unity (ไม่ใช่ภาพหน้าจอจริง): 1 ปุ่ม Play อยู่ด้านบน 2 เครื่องหมาย + ใน Hierarchy คือจุดสร้างออบเจกต์ 3 Inspector ใช้กด Add Component 4 แผง Project ใช้สร้างสคริปต์ C#
ภาพแผนผังหน้าต่าง Unity (ไม่ใช่ภาพหน้าจอจริง): 1 ปุ่ม Play อยู่ด้านบน 2 เครื่องหมาย + ใน Hierarchy คือจุดสร้างออบเจกต์ 3 Inspector ใช้กด Add Component 4 แผง Project ใช้สร้างสคริปต์ C#

สิ่งที่ต้องเตรียม

ขั้นที่ 1: สร้างโปรเจกต์ 2D

1. เปิด Unity Hub กด **New project** แล้วเลือกเทมเพลต **Universal 2D** (URP) หรือ **2D** (Built-in) ก็ได้ ชื่อให้ตรงกับที่ Unity Hub แสดง

2. ตั้งชื่อโปรเจกต์ เช่น `MyPlatformer` แล้วกด **Create project**

3. รอให้ Unity เปิดขึ้น จะได้ฉากว่างชื่อ `SampleScene`

ฉากคือพื้นที่ของเกม ทุกสิ่งในเกมเช่นตัวละคร พื้น และกล้อง จะเป็น **GameObject** ซึ่งต่อกับ **Component** เพื่อให้ทำหน้าที่ต่าง ๆ

แผนผังเมนู Edit → Project Settings → Player → Active Input Handling (ไม่ใช่ภาพหน้าจอจริง): 1 เมนู Edit 2 Project Settings 3 แท็บ Player 4 ตัวเลือก Active Input Handling
แผนผังเมนู Edit → Project Settings → Player → Active Input Handling (ไม่ใช่ภาพหน้าจอจริง): 1 เมนู Edit 2 Project Settings 3 แท็บ Player 4 ตัวเลือก Active Input Handling

ขั้นที่ 2: ตั้งค่าระบบรับปุ่มกด

สคริปต์ในบทความนี้ใช้ระบบรับปุ่มแบบดั้งเดิม (Input Manager) ตรวจให้แน่ใจว่าตั้งค่าไว้ดังนี้

1. เมนู **Edit → Project Settings → Player**

2. หา **Active Input Handling** แล้วเลือก **Both** หรือ **Input Manager (Old)**

3. Unity จะถามให้รีสตาร์ทเอดิเตอร์ ให้กดยอมรับ

แกนนำทาง **Horizontal** (ซ้ายขวา) และ **Jump** (กระโดด) มีมาให้ในเทมเพลตอยู่แล้ว ปุ่มมาตรฐานคือ A/D หรือลูกศรสำหรับซ้ายขวา และ Space สำหรับกระโดด

ขั้นที่ 3: สร้างพื้น

1. คลิกขวาที่ Hierarchy เลือก **2D Object → Sprites → Square** ตั้งชื่อเป็น `Ground`

2. ใน Inspector ตั้งค่า Scale เป็น `X = 20`, `Y = 1` เพื่อให้เป็นพื้นยาว

3. ตั้ง Position เป็น `X = 0`, `Y = -2`

4. กด **Add Component** แล้วเพิ่ม **Box Collider 2D** เพื่อให้ตัวละครเดินชนและยืนได้

5. สร้างเลเยอร์ใหม่ชื่อ `Ground` โดยกดที่ช่อง **Layer** ด้านบนของ Inspector แล้วเลือก **Add Layer** จากนั้นกลับไปที่ `Ground` แล้วเลือกเลเยอร์ `Ground`

การสร้างเลเยอร์เพื่อให้สคริปต์ตรวจได้ว่าสิ่งที่เท้าแตะคือพื้น ไม่ใช่กำแพงหรือศัตรู

ขั้นที่ 4: เพิ่มตัวละคร

1. คลิกขวาที่ Hierarchy เลือก **2D Object → Sprites → Square** ตั้งชื่อเป็น `Player`

2. ตั้ง Position เป็น `X = 0`, `Y = 1` ให้ตัวละครอยู่เหนือพื้น

3. ถ้ามีรูปตัวละคร ลากไฟล์ PNG ใส่ช่อง **Sprite** ของ Sprite Renderer

4. เพิ่ม **Rigidbody 2D** ตั้ง **Body Type** เป็น Dynamic และติ๊ก **Freeze Rotation Z** เพื่อไม่ให้ตัวละครล้ม

5. เพิ่ม **Box Collider 2D** ให้ตัวละคร

Rigidbody 2D ทำให้วัตถุตกตามแรงโน้มถ่วงและชนกับวัตถุอื่นได้ ถ้าไม่มี Rigidbody ตัวละครจะลอยอยู่กับที่

ขั้นที่ 5: สร้างจุดตรวจพื้น

1. คลิกขวาที่ `Player` ใน Hierarchy เลือก **Create Empty** ตั้งชื่อเป็น `GroundCheck`

2. ลาก `GroundCheck` ไปไว้ที่เท้าของตัวละคร เช่น Position `X = 0`, `Y = -0.5`

จุดนี้เป็นตำแหน่งที่สคริปต์จะวงกลมตรวจว่ามีพื้นอยู่หรือไม่

ขั้นที่ 6: เขียนสคริปต์ควบคุมตัวละคร

คลิกขวาในหน้าต่าง Project เลือก **Create → C# Script** ตั้งชื่อ `PlayerController2D` แล้วดับเบิลคลิกเพื่อเปิดในโปรแกรมเขียนโค้ด จากนั้นวางโค้ดนี้ทั้งหมด

using UnityEngine;

// ควบคุมตัวละคร 2D: เดินซ้ายขวา กระโดด และหันหน้าตามทิศทาง
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(SpriteRenderer))]
public class PlayerController2D : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;      // ความเร็วเดิน
    [SerializeField] private float jumpForce = 12f;     // แรงกระโดด
    [SerializeField] private Transform groundCheck;     // จุดตรวจพื้น
    [SerializeField] private float groundRadius = 0.15f;
    [SerializeField] private LayerMask groundLayer;     // เลเยอร์ที่นับว่าเป็นพื้น

    private Rigidbody2D rb;
    private SpriteRenderer sprite;
    private float moveInput;
    private bool isGrounded;
    private bool jumpRequested;

    public bool IsGrounded => isGrounded;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        sprite = GetComponent<SpriteRenderer>();
    }

    // Update ทำงานทุกเฟรม ใช้อ่านปุ่มกด แต่ยังไม่กระโดดทันที
    // เพราะค่า isGrounded ถูกอัปเดตใน FixedUpdate ถ้ากระโดดใน Update อาจใช้สถานะพื้นเก่า
    private void Update()
    {
        moveInput = Input.GetAxisRaw("Horizontal");   // -1 ซ้าย, 0 ไม่กด, 1 ขวา

        if (Input.GetButtonDown("Jump"))
        {
            jumpRequested = true;                      // จำไว้ว่าผู้เล่นกดกระโดด
        }

        if (moveInput != 0)
        {
            sprite.flipX = moveInput < 0;             // หันหน้าไปทางที่เดิน
        }
    }

    // FixedUpdate ทำงานตามจังหวะฟิสิกส์ ตรวจพื้นก่อน แล้วค่อยตัดสินใจกระโดด
    private void FixedUpdate()
    {
        // ถ้ายังไม่ได้ผูก groundCheck ใน Inspector ให้ใช้ตำแหน่งของตัวละครแทน
        Vector2 checkPosition = groundCheck != null ? groundCheck.position : transform.position;
        isGrounded = Physics2D.OverlapCircle(checkPosition, groundRadius, groundLayer);

        float verticalSpeed = rb.velocity.y;
        if (jumpRequested && isGrounded)
        {
            verticalSpeed = jumpForce;
        }
        jumpRequested = false;                         // ใช้คำขอกระโดดแล้ว

        rb.velocity = new Vector2(moveInput * moveSpeed, verticalSpeed);
    }
}

อธิบายโค้ดสำคัญ

แผนผัง Inspector ของ Player (ไม่ใช่ภาพหน้าจอจริง): 1 สคริปต์ PlayerController2D 2 Rigidbody 2D ที่ติ๊ก Freeze Rotation Z 3 ช่อง Ground Check 4 ช่อง Ground Layer
แผนผัง Inspector ของ Player (ไม่ใช่ภาพหน้าจอจริง): 1 สคริปต์ PlayerController2D 2 Rigidbody 2D ที่ติ๊ก Freeze Rotation Z 3 ช่อง Ground Check 4 ช่อง Ground Layer

ขั้นที่ 7: ผูกสคริปต์และค่าต่าง ๆ

1. ลากสคริปต์ `PlayerController2D` ไปวางบน `Player`

2. ใน Inspector ของ Player ช่อง **Ground Check** ให้ลาก `GroundCheck` เข้าไป

3. ช่อง **Ground Layer** ให้เลือกเลเยอร์ `Ground`

4. ปรับ **Move Speed** เป็น 5 และ **Jump Force** เป็น 12 หรือเลือกค่าที่ชอบ

ขั้นที่ 8: ทำให้กล้องตามตัวละคร

สร้างสคริปต์ชื่อ `CameraFollow2D` แล้วใส่โค้ดนี้

using UnityEngine;

// กล้องติดตามตัวละครแบบนุ่มนวล และไม่ให้ตกเกินขอบล่างของฉาก
public class CameraFollow2D : MonoBehaviour
{
    [SerializeField] private Transform target;          // ตัวละครที่จะติดตาม
    [SerializeField] private float smoothTime = 0.2f;   // ยิ่งน้อยยิ่งตามทันเร็ว
    [SerializeField] private float minY = -2f;          // ขอบล่างของกล้อง

    private Vector3 velocity;

    private void LateUpdate()
    {
        if (target == null) return;

        Vector3 desired = new Vector3(target.position.x, Mathf.Max(target.position.y, minY), transform.position.z);
        transform.position = Vector3.SmoothDamp(transform.position, desired, ref velocity, smoothTime);
    }
}

`LateUpdate` ทำงานหลัง Update ทั้งหมด จึงเหมาะกับกล้องที่ต้องตามตำแหน่งตัวละครที่เพิ่งขยับในเฟรมนั้น `Vector3.SmoothDamp` ทำให้กล้องเคลื่อนที่อย่างนุ่มนวล ไม่กระตุก

เลือก **Main Camera** ใส่สคริปต์ `CameraFollow2D` แล้วลาก `Player` เข้าไปในช่อง **Target** ตั้งค่า Z ของกล้องเป็น `-10` เพื่อให้เห็นวัตถุทั้งหมดอยู่หน้ากล้อง

ขั้นที่ 9: รันเกม

กดปุ่ม **Play** ที่ด้านบนของเอดิเตอร์ แล้วทดสอบ

กด Play อีกครั้งเพื่อหยุด ค่าที่ปรับใน Inspector ขณะรันจะกลับเป็นค่าเดิมเมื่อหยุด ให้ปรับค่าตอนหยุดแล้วบันทึกโปรเจกต์ไว้

ขั้นที่ 10: เขียนการทดสอบอัตโนมัติ

การทดสอบด้วยมือให้ผลลัพธ์ที่ดี แต่เมื่อโค้ดซับซ้อนขึ้น การมีการทดสอบอัตโนมัติจะช่วยจับข้อผิดพลาดได้เร็วกว่ามาก Unity มีระบบทดสอบ (Unity Test Framework) ที่ใช้ได้จริง ส่วนตัวอย่างนี้ใช้วิธีที่เข้าใจง่ายกว่า คือจำลองเฟรมของ Unity ด้วยโค้ดธรรมดา

การทดสอบต่อไปนี้จำลองพฤติกรรมการทำงานของ Unity ได้แก่ การเรียก Update และ FixedUpdate การอ่านปุ่ม การตรวจพื้น และการเลื่อนตำแหน่งจากความเร็ว เพื่อตรวจว่า logic ของสคริปต์ถูกต้อง

using System;

public static class PlayerTests
{
    private const float Dt = 1f / 50f;   // 1 FixedUpdate เท่ากับ 1/50 วินาที
    private static int passed = 0, failed = 0;

    private static void Check(string name, bool condition)
    {
        if (condition) { passed++; Console.WriteLine("PASS  " + name); }
        else { failed++; Console.WriteLine("FAIL  " + name); }
    }

    private static PlayerController2D CreatePlayer(out Rigidbody2D rb, out SpriteRenderer sprite)
    {
        var player = new PlayerController2D();
        rb = new Rigidbody2D();
        sprite = new SpriteRenderer();
        player.Components[typeof(Rigidbody2D)] = rb;
        player.Components[typeof(SpriteRenderer)] = sprite;
        player.Call("Awake");
        return player;
    }

    private static void Step(PlayerController2D player, Rigidbody2D rb)
    {
        player.Call("Update");
        player.Call("FixedUpdate");
        player.transform.position = new Vector3(
            player.transform.position.x + rb.velocity.x * Dt,
            player.transform.position.y + rb.velocity.y * Dt, 0);
    }

    public static int Main()
    {
        // 1. กดขวาแล้วเดินไปทางขวา
        {
            Rigidbody2D rb; SpriteRenderer sprite;
            var player = CreatePlayer(out rb, out sprite);
            Physics2D.GroundBelowPlayer = true;
            Input.HorizontalAxis = 1f;
            for (int i = 0; i < 50; i++) Step(player, rb);
            Check("เดินขวา: ความเร็ว x เท่ากับ moveSpeed (5)", Math.Abs(rb.velocity.x - 5f) < 0.001f);
            Check("เดินขวา: ตำแหน่ง x เพิ่มขึ้นประมาณ 5 หน่วยต่อวินาที", Math.Abs(player.transform.position.x - 5f) < 0.2f);
            Check("เดินขวา: หันหน้าไปทางขวา (flipX = false)", sprite.flipX == false);
        }

        // 2. กดซ้ายแล้วหันหน้าไปทางซ้าย
        {
            Rigidbody2D rb; SpriteRenderer sprite;
            var player = CreatePlayer(out rb, out sprite);
            Physics2D.GroundBelowPlayer = true;
            Input.HorizontalAxis = -1f;
            Step(player, rb);
            Check("เดินซ้าย: ความเร็ว x เป็นลบ", rb.velocity.x < 0);
            Check("เดินซ้าย: หันหน้าไปทางซ้าย (flipX = true)", sprite.flipX == true);
        }

        // 3. ไม่กดอะไรเลย ตัวละครต้องอยู่นิ่ง
        {
            Rigidbody2D rb; SpriteRenderer sprite;
            var player = CreatePlayer(out rb, out sprite);
            Physics2D.GroundBelowPlayer = true;
            Input.HorizontalAxis = 0f;
            Step(player, rb);
            Check("ไม่กดปุ่ม: ความเร็ว x เป็น 0", rb.velocity.x == 0f);
        }

        // 4. กดกระโดดตอนยืนบนพื้น ต้องได้ความเร็วขึ้น
        {
            Rigidbody2D rb; SpriteRenderer sprite;
            var player = CreatePlayer(out rb, out sprite);
            Physics2D.GroundBelowPlayer = true;
            Input.HorizontalAxis = 0f;
            Step(player, rb);                    // ให้ตรวจพื้นก่อน
            Input.JumpPressedThisFrame = true;
            Step(player, rb);
            Input.JumpPressedThisFrame = false;
            Check("กระโดดบนพื้น: ความเร็ว y เท่ากับ jumpForce (12)", Math.Abs(rb.velocity.y - 12f) < 0.001f);
        }

        // 5. กดกระโดดตอนลอยอยู่กลางอากาศ ต้องไม่กระโดดซ้ำ
        {
            Rigidbody2D rb; SpriteRenderer sprite;
            var player = CreatePlayer(out rb, out sprite);
            Physics2D.GroundBelowPlayer = true;
            Input.HorizontalAxis = 0f;
            Step(player, rb);
            Physics2D.GroundBelowPlayer = false; // ลอยขึ้นจากพื้น
            rb.velocity = new Vector2(0, -3f);   // กำลังตกลง
            Input.JumpPressedThisFrame = true;
            Step(player, rb);
            Input.JumpPressedThisFrame = false;
            Check("กระโดดกลางอากาศ: ไม่เปลี่ยนความเร็ว y (ยังคงตกลง)", Math.Abs(rb.velocity.y + 3f) < 0.001f);
        }

        Console.WriteLine();
        Console.WriteLine("ผ่าน " + passed + " / ไม่ผ่าน " + failed);
        return failed == 0 ? 0 : 1;
    }
}

ตัวจำลอง Unity API (`Vector2`, `Rigidbody2D`, `Input`, `Physics2D` และอื่น ๆ) ต้องมีไฟล์แยกต่างหากเพื่อให้โค้ดข้างบนคอมไพล์และรันนอก Unity ได้ ตัวจำลองนี้ไม่ได้จำลองฟิสิกส์จริงหรือการแสดงผล แต่ตรวจการคำนวณและลำดับการทำงานของสคริปต์ได้

ไฟล์จำลอง Unity API (UnityStubs.cs)

ไฟล์นี้วางไว้ในโฟลเดอร์ `test` ร่วมกับ `PlayerTests.cs` โดยเป็นตัวจำลองขั้นต่ำให้โค้ดคอมไพล์ได้นอก Unity

using System;
using System.Collections.Generic;

public struct Vector2
{
    public float x, y;
    public Vector2(float x, float y) { this.x = x; this.y = y; }
    public static implicit operator Vector2(Vector3 v) { return new Vector2(v.x, v.y); }
}

public struct Vector3
{
    public float x, y, z;
    public Vector3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }

    public static Vector3 SmoothDamp(Vector3 current, Vector3 target, ref Vector3 velocity, float smoothTime)
    {
        // จำลองแบบง่าย: เข้าใกล้เป้าหมายครึ่งทางต่อการเรียก 1 ครั้ง
        return new Vector3((current.x + target.x) / 2, (current.y + target.y) / 2, target.z);
    }
}

public static class Mathf
{
    public static float Max(float a, float b) { return a > b ? a : b; }
}

public struct LayerMask { }

public class Transform
{
    public Vector3 position;
}

public class Rigidbody2D
{
    public Vector2 velocity;
}

public class SpriteRenderer
{
    public bool flipX;
}

public static class Input
{
    public static float HorizontalAxis;     // ค่าที่ทดสอบจะตั้ง: -1, 0 หรือ 1
    public static bool JumpPressedThisFrame;

    public static float GetAxisRaw(string name)
    {
        if (name != "Horizontal") throw new ArgumentException("unknown axis " + name);
        return HorizontalAxis;
    }

    public static bool GetButtonDown(string name)
    {
        if (name != "Jump") throw new ArgumentException("unknown button " + name);
        return JumpPressedThisFrame;
    }
}

public static class Physics2D
{
    public static bool GroundBelowPlayer;   // ค่าที่ทดสอบจะตั้งว่ามีพื้นอยู่ใต้เท้าหรือไม่

    public static bool OverlapCircle(Vector2 point, float radius, LayerMask mask)
    {
        return GroundBelowPlayer;
    }
}

public class SerializeFieldAttribute : Attribute { }

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class RequireComponentAttribute : Attribute
{
    public RequireComponentAttribute(Type type) { }
}

public class MonoBehaviour
{
    // เก็บคอมโพเนนต์ที่ผู้ทดสอบเพิ่มให้ GameObject ตัวนี้
    public readonly Dictionary<Type, object> Components = new Dictionary<Type, object>();
    public Transform transform = new Transform();

    public T GetComponent<T>() where T : class
    {
        object value;
        return Components.TryGetValue(typeof(T), out value) ? (T)value : null;
    }

    // เรียกเมธอดวงจรชีวิต (Awake, Update, FixedUpdate) ตามชื่อ เหมือนที่ Unity เรียกให้ในแต่ละเฟรม
    public void Call(string methodName)
    {
        var method = GetType().GetMethod(methodName,
            System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
        method.Invoke(this, null);
    }
}

การคอมไพล์และรันทดสอบ: คัดลอกไฟล์ทั้งสี่ (`UnityStubs.cs`, `PlayerTests.cs`, `PlayerController2D.cs`, `CameraFollow2D.cs`) ไว้ในโฟลเดอร์เดียวกัน แล้วรันคำสั่งคอมไพล์ด้วยคอมไพเลอร์ Roslyn ของ Visual Studio หรือ `csc` รุ่นใหม่ จากนั้นรันไฟล์ `.exe` ที่ได้ ในไฟล์สคริปต์จริงที่ใช้กับ Unity ต้องมี `using UnityEngine;` แต่ตอนคอมไพล์ด้วยตัวจำลองให้ลบบรรทัดนั้นออกจากสำเนาที่ใช้ทดสอบ

ผลการคอมไพล์และรันการทดสอบ

ทดสอบด้วยคอมไพเลอร์ C# รุ่นใหม่ (Roslyn) ผลที่ได้คือ

PASS  เดินขวา: ความเร็ว x เท่ากับ moveSpeed (5)
PASS  เดินขวา: ตำแหน่ง x เพิ่มขึ้นประมาณ 5 หน่วยต่อวินาที
PASS  เดินขวา: หันหน้าไปทางขวา (flipX = false)
PASS  เดินซ้าย: ความเร็ว x เป็นลบ
PASS  เดินซ้าย: หันหน้าไปทางซ้าย (flipX = true)
PASS  ไม่กดปุ่ม: ความเร็ว x เป็น 0
PASS  กระโดดบนพื้น: ความเร็ว y เท่ากับ jumpForce (12)
PASS  กระโดดกลางอากาศ: ไม่เปลี่ยนความเร็ว y (ยังคงตกลง)

ผ่าน 8 / ไม่ผ่าน 0

การทดสอบพบข้อบกพร่องและการแก้ไข

ก่อนปรับปรุง เวอร์ชันแรกของ `PlayerController2D` กระโดดจาก Update โดยตรง การทดสอบข้อ "กระโดดกลางอากาศ" ล้มเหลว เพราะ Update ใช้ค่า `isGrounded` จากเฟรมก่อนหน้า ซึ่งยังเป็น true อยู่ ตัวละครจึงกระโดดซ้ำทั้งที่ลอยแล้ว ในเกมจริงปัญหานี้เกิดได้เมื่อเดินพ้นขอบพื้น จึงแก้ด้วยการเก็บคำขอกระโดดไว้ในตัวแปร `jumpRequested` แล้วให้ FixedUpdate ตรวจพื้นและตัดสินใจกระโดดในจังหวะเดียวกัน ซึ่งคือโค้ดในขั้นที่ 6 นั่นเอง

การทดสอบจึงช่วยให้เห็นข้อผิดพลาดที่ตรวจด้วยตาเปล่าได้ยาก ควรเขียนการทดสอบให้ครอบคลุมกรณีขอบเขตเช่นนี้เสมอ

ขั้นที่ 11: สร้างเกมให้เป็นไฟล์ที่เล่นได้

1. เมนู **File → Build Profiles** (หรือ **Build Settings** ในเวอร์ชันเก่า)

2. เลือกแพลตฟอร์มที่ต้องการ เช่น Windows หรือ WebGL แล้วกด **Build**

3. เลือกโฟลเดอร์ที่จะบันทึก Unity จะสร้างไฟล์ `.exe` หรือไฟล์เว็บให้เล่นได้

ปัญหาที่พบบ่อยและวิธีแก้

แนวทางต่อยอดหลังจากนี้

เมื่อตัวละครเดินและกระโดดได้แล้ว คุณสามารถเพิ่มการเก็บเหรียญโดยใช้ Trigger Collider 2D และ `OnTriggerEnter2D` เพิ่มศัตรูที่เดินไปมา เพิ่มระบบชีวิตและการตาย และเพิ่มฉากหลังด้วย Tilemap ทั้งหมดนี้ใช้หลักการเดียวกับที่ทำในบทความนี้

สรุป

การสร้างเกม 2D ด้วย Unity เริ่มจากการเข้าใจว่าทุกสิ่งเป็น GameObject ที่ประกอบด้วย Component สคริปต์ควบคุมตัวละครใช้ Update อ่านปุ่ม และใช้ FixedUpdate จัดการฟิสิกส์ การทดสอบอัตโนมัติช่วยจับข้อผิดพลาดที่ตรวจด้วยมือได้ยาก อย่างการกระโดดขณะลอยอยู่กลางอากาศ ลองทำตามขั้นตอนทีละขั้น แล้วปรับแต่งค่าใน Inspector เพื่อให้เกมเป็นของคุณเอง