ผู้เริ่มต้น45 นาทีบทที่ 3

พื้นฐาน C# ที่จำเป็นสำหรับการเขียนมอด Stardew Valley

สรุปพื้นฐานภาษา C# ที่ต้องใช้เขียนมอด SMAPI ทั้งตัวแปร เงื่อนไข เมธอด class, namespace, inheritance และ event พร้อมตัวอย่างที่เชื่อมกับโค้ดมอดจริง

เขียนโดย Pokpong Limpaphan · อัปเดต

สถานะการตรวจสอบ: ตรวจกับเอกสารทางการแล้ว

เนื้อหาตรวจสอบกับเอกสารทางการแล้ว แต่ยังไม่ได้ทดสอบในเกมจริงทุกขั้นตอน (ตรวจล่าสุด )

ตรวจกับ: เอกสารภาษา C# ของ Microsoft

ตัวอย่างโค้ดเป็น C# มาตรฐาน ยังไม่ได้คอมไพล์ในระบบที่ใช้เขียนบทความ แนะนำให้ลองพิมพ์และรันด้วยตัวเอง

สิ่งที่จะได้เรียนรู้

  • อ่านและเข้าใจโครงสร้างของไฟล์ ModEntry.cs
  • ใช้ตัวแปร ชนิดข้อมูล if และ method ได้
  • เข้าใจ class, inheritance (override) และการสมัคร event ด้วย +=
  • จัดการค่า null อย่างปลอดภัย

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

  • อ่านบท "Stardew Valley Modding คืออะไร" แล้ว
  • ติดตั้ง .NET SDK ตามบท "ติดตั้งเครื่องมือบน PC" (ถ้าต้องการลองรันโค้ด)
สารบัญ

บทนี้ไม่ได้สอน C# ทั้งภาษา แต่เลือกเฉพาะเรื่องที่เจอแน่นอนเมื่อเขียนมอด SMAPI แต่ละหัวข้อมีตัวอย่างที่โยงกลับไปยังโค้ดมอดจริง เพื่อให้เห็นว่าความรู้นั้นใช้ตรงไหน

1. ตัวแปรและชนิดข้อมูล

ตัวแปร (variable) คือกล่องเก็บค่า C# เป็นภาษาที่ต้องรู้ชนิดของค่าเสมอ

int gold = 500;              // จำนวนเต็ม
double speed = 1.5;          // ทศนิยม
bool isRaining = true;       // จริง/เท็จ
string farmerName = "Somchai"; // ข้อความ

var dayOfMonth = 12;         // ให้ C# เดาชนิดเอง (ในที่นี้คือ int)

ในมอด เราอ่านค่าจากเกมมาเก็บในตัวแปรบ่อยมาก เช่นเงินของผู้เล่นหรือวันที่ในเกม

2. ต่อข้อความด้วย string interpolation

ใส่ $ หน้าเครื่องหมายคำพูด แล้วใส่ตัวแปรใน { } ได้เลย

string message = $"{farmerName} มีเงิน {gold}g";

รูปแบบนี้เจอบ่อยมากเวลาเขียน log เช่น this.Monitor.Log($"Pressed {e.Button}.", LogLevel.Debug);

3. เงื่อนไข if และ return ก่อนกำหนด

if (gold >= 1000)
{
    // ทำงานเมื่อเงื่อนไขเป็นจริง
}
else
{
    // ทำงานเมื่อเงื่อนไขเป็นเท็จ
}

ในมอดนิยมเขียนแบบ “ตรวจแล้วออกก่อน” (early return) เพื่อลดการซ้อน if หลายชั้น

if (!Context.IsWorldReady)
    return; // ยังไม่ได้โหลดเซฟ ไม่ต้องทำอะไรต่อ

! แปลว่า “ไม่” ดังนั้นบรรทัดนี้อ่านว่า “ถ้าโลกของเกมยังไม่พร้อม ให้ออกจากเมธอด”

4. เมธอด (method)

เมธอดคือชุดคำสั่งที่ตั้งชื่อไว้ เรียกใช้ซ้ำได้

int AddBonus(int baseGold, int bonus)
{
    return baseGold + bonus;
}

int total = AddBonus(500, 50); // total = 550

ส่วนประกอบของเมธอดคือ ชนิดของค่าที่ส่งกลับ (int) ชื่อ (AddBonus) และพารามิเตอร์ในวงเล็บ ถ้าไม่ส่งค่ากลับให้ใช้ void

5. class, object และ namespace

class คือแบบพิมพ์เขียวของข้อมูลและเมธอด object คือสิ่งที่สร้างจาก class นั้น

namespace YourName.MyFirstMod
{
    internal class Crop
    {
        public string Name;
        public int DaysToGrow;

        public Crop(string name, int daysToGrow)
        {
            this.Name = name;
            this.DaysToGrow = daysToGrow;
        }
    }
}
  • namespace ใช้จัดกลุ่มโค้ดและกันชื่อซ้ำกับโค้ดของคนอื่น ในมอดนิยมตั้งตามรูปแบบ ชื่อผู้สร้าง.ชื่อมอด
  • this หมายถึง object ปัจจุบัน
  • public ให้โค้ดอื่นเข้าถึงได้ ส่วน private และ internal จำกัดการเข้าถึง

ด้านบนสุดของไฟล์มักมีบรรทัด using ซึ่งบอกว่าจะใช้ namespace ไหนบ้าง เช่น

using StardewModdingAPI;        // API หลักของ SMAPI
using StardewModdingAPI.Events; // event ต่างประเภท
using StardewValley;            // โค้ดของตัวเกม

6. Inheritance และ override

ทุกมอด SMAPI มี class ที่ สืบทอด (inherit) จาก class Mod ของ SMAPI

internal sealed class ModEntry : Mod
{
    public override void Entry(IModHelper helper)
    {
        // SMAPI เรียกเมธอดนี้ตอนโหลดมอด
    }
}
  • : Mod แปลว่า ModEntry ได้ความสามารถทั้งหมดของ Mod มา เช่น this.Monitor สำหรับเขียน log และ this.Helper
  • override แปลว่าเรากำหนดการทำงานของเมธอด Entry เอง แทนของเดิมใน class แม่
  • sealed แปลว่าไม่มี class อื่นสืบทอดจาก ModEntry ต่อได้
classDiagram
  class Mod {
    +IModHelper Helper
    +IMonitor Monitor
    +IManifest ModManifest
    +Entry(IModHelper helper)*
  }
  class ModEntry {
    +Entry(IModHelper helper)
    -OnButtonPressed(sender, e)
  }
  Mod <|-- ModEntry : สืบทอด

7. Event และ +=

เกมเกิดเหตุการณ์ (event) ตลอดเวลา เช่น เริ่มวันใหม่ ผู้เล่นกดปุ่ม หรือโหลดเซฟ SMAPI ให้เรา “สมัครรับ” event ได้ด้วย +=

public override void Entry(IModHelper helper)
{
    helper.Events.GameLoop.DayStarted += this.OnDayStarted;
}

private void OnDayStarted(object? sender, DayStartedEventArgs e)
{
    this.Monitor.Log("A new day started!", LogLevel.Info);
}

อ่านบรรทัดแรกได้ว่า “เมื่อเกิด event DayStarted ให้เรียกเมธอด OnDayStarted” ส่วนเมธอดที่ถูกเรียก (event handler) ต้องรับพารามิเตอร์สองตัวตามที่ SMAPI กำหนด

  • sender คือผู้ส่ง event (ปกติไม่ได้ใช้)
  • e คือข้อมูลของ event นั้น เช่น ButtonPressedEventArgs มี e.Button บอกว่ากดปุ่มอะไร

ดูรายชื่อ event ทั้งหมดได้ที่ SMAPI Events

8. null และเครื่องหมาย ?

null แปลว่า “ไม่มีค่า” ถ้าเรียกใช้ค่าที่เป็น null จะเกิด NullReferenceException ซึ่งเป็น error ที่พบบ่อยที่สุดในมอด

string? itemName = null;     // ? หลังชนิดข้อมูล แปลว่าค่านี้อาจเป็น null ได้

int length = itemName?.Length ?? 0;
// ?.  ถ้า itemName เป็น null จะไม่เรียก .Length และให้ผลเป็น null แทน
// ??  ถ้าค่าทางซ้ายเป็น null ให้ใช้ค่าทางขวา (0)

ในเกม ค่าหลายอย่างเป็น null ได้ในบางสถานการณ์ เช่น ไอเท็มที่ผู้เล่นถืออยู่ (Game1.player.CurrentItem) จะเป็น null เมื่อมือว่าง จึงควรตรวจก่อนใช้เสมอ

if (Game1.player.CurrentItem is null)
    return;

9. List และ foreach

var crops = new List<string> { "Parsnip", "Potato", "Cauliflower" };

foreach (string crop in crops)
{
    Console.WriteLine(crop);
}

List<string> คือรายการของข้อความ ใช้ using System.Collections.Generic; (ในโปรเจกต์ console ของ .NET รุ่นใหม่ บรรทัดนี้ถูกเพิ่มให้อัตโนมัติผ่าน implicit usings)

ลองอ่านโค้ดมอดจริง

ลองอ่านโค้ดนี้แล้วอธิบายแต่ละบรรทัดด้วยความรู้จากบทนี้

using StardewModdingAPI;
using StardewModdingAPI.Events;
using StardewValley;

namespace YourName.MyFirstMod
{
    internal sealed class ModEntry : Mod
    {
        public override void Entry(IModHelper helper)
        {
            helper.Events.Input.ButtonPressed += this.OnButtonPressed;
        }

        private void OnButtonPressed(object? sender, ButtonPressedEventArgs e)
        {
            if (!Context.IsWorldReady)
                return;

            this.Monitor.Log($"{Game1.player.Name} pressed {e.Button}.", LogLevel.Debug);
        }
    }
}

คำตอบโดยย่อ: มอดนี้สมัครรับ event การกดปุ่ม เมื่อผู้เล่นกดปุ่มหลังจากโหลดเซฟแล้ว จะเขียนชื่อผู้เล่นและปุ่มที่กดลงใน console ของ SMAPI

แก้ปัญหาที่พบบ่อย

ข้อความ error สาเหตุที่พบบ่อย
CS0246: The type or namespace name 'X' could not be found ลืม using หรือยังไม่ได้ติดตั้งแพ็กเกจ ModBuildConfig
CS1002: ; expected ลืมเครื่องหมาย ; ท้ายคำสั่ง
CS0103: The name 'X' does not exist in the current context สะกดชื่อตัวแปรผิด หรือประกาศไว้นอกขอบเขต { }
NullReferenceException (ตอนเล่นเกม) ใช้ค่าที่เป็น null โดยไม่ได้ตรวจ

ขั้นต่อไป

พร้อมแล้วไปสร้างมอดจริงตัวแรกใน SMAPI Hello World หรือสร้างโปรเจกต์ด้วย Mod Generator แล้วเปิด ModEntry.cs อ่านประกอบ

เอกสารอ้างอิง

ค้นหาใน SVMT

กรองตามประเภท

พิมพ์คำที่ต้องการค้นหา (ภาษาไทยหรืออังกฤษ)