Skip to content

Rozdział 1.1: Zmienne i typy ​


Wprowadzenie ​

Enforce Script to język skryptowy silnika Enfusion, używany przez DayZ Standalone. Jest to język obiektowy o składni podobnej do C, pod wieloma względami podobny do C#, ale z własnym zestawem typów, reguł i ograniczeń. Jeśli masz doświadczenie z C#, Javą lub C++, szybko poczujesz się jak w domu --- ale zwróć szczególną uwagę na różnice, ponieważ właśnie w miejscach, gdzie Enforce Script odbiega od tych języków, kryją się błędy.

Ten rozdział obejmuje podstawowe elementy: typy prymitywne, jak deklarować i inicjalizować zmienne oraz jak działa konwersja typów. Każda linia kodu moda DayZ zaczyna się tutaj.


Typy prymitywne ​

Enforce Script ma mały, stały zestaw typów prymitywnych. Nie można definiować nowych typów wartościowych --- tylko klasy (omówione w Rozdziale 1.3).

TypRozmiarWartość domyślnaOpis
int32-bitowy ze znakiem0Liczby całkowite od -2 147 483 648 do 2 147 483 647
float32-bitowy IEEE 7540.0Liczby zmiennoprzecinkowe
bool1 bit logicznyfalsetrue lub false
stringZmienny"" (pusty)Tekst. Niezmienny typ wartościowy --- przekazywany przez wartość, nie przez referencję
vector3x float"0 0 0"Trzykomponentowy float (x, y, z). Przekazywany przez wartość
typenameReferencja silnikanullReferencja do samego typu, używana do refleksji
voidN/AN/AUżywany tylko jako typ zwracany do oznaczenia "nie zwraca niczego"

Diagram hierarchii typów ​

Stałe typów ​

Niektóre typy udostępniają przydatne stałe:

c
// granice int
int maxInt = int.MAX;    // 2147483647
int minInt = int.MIN;    // -2147483648

// granice float
float smallest = float.MIN;     // najmniejszy dodatni float (~1.175e-38)
float largest  = float.MAX;     // największy float (~3.403e+38)
float lowest   = float.LOWEST;  // najbardziej ujemny float (-3.403e+38)

Deklarowanie zmiennych ​

Zmienne deklaruje się, wpisując typ, a następnie nazwę. Można deklarować i przypisywać w jednej instrukcji lub oddzielnie.

c
void MyFunction()
{
    // Tylko deklaracja (inicjalizacja wartością domyślną)
    int health;          // health == 0
    float speed;         // speed == 0.0
    bool isAlive;        // isAlive == false
    string name;         // name == ""

    // Deklaracja z inicjalizacją
    int maxPlayers = 60;
    float gravity = 9.81;
    bool debugMode = true;
    string serverName = "My DayZ Server";
}

Słowo kluczowe auto ​

Gdy typ jest oczywisty z prawej strony wyrażenia, można użyć auto, aby kompilator go wydedukował:

c
void Example()
{
    auto count = 10;           // int
    auto ratio = 0.75;         // float
    auto label = "Hello";      // string
    auto player = GetGame().GetPlayer();  // DayZPlayer (lub cokolwiek zwraca GetPlayer)
}

To jest wyłącznie dla wygody --- kompilator rozwiązuje typ w czasie kompilacji. Nie ma żadnej różnicy w wydajności.

Stałe ​

Użyj słowa kluczowego const dla wartości, które nigdy nie powinny się zmieniać po inicjalizacji:

c
const int MAX_SQUAD_SIZE = 8;
const float SPAWN_RADIUS = 150.0;
const string MOD_PREFIX = "[MyMod]";

void Example()
{
    int a = MAX_SQUAD_SIZE;  // OK: odczyt stałej
    MAX_SQUAD_SIZE = 10;     // BŁĄD: nie można przypisać do stałej
}

Stałe są zwykle deklarowane na poziomie pliku (poza jakąkolwiek funkcją) lub jako składowe klasy. Konwencja nazewnictwa: UPPER_SNAKE_CASE.


Praca z int ​

Liczby całkowite to najczęściej używany typ. DayZ używa ich do liczenia przedmiotów, identyfikatorów graczy, wartości zdrowia (przy dyskretyzacji), wartości wyliczeniowych, flag bitowych i wielu innych.

c
void IntExamples()
{
    int count = 5;
    int total = count + 10;     // 15
    int doubled = count * 2;    // 10
    int remainder = 17 % 5;     // 2 (modulo)

    // Inkrementacja i dekrementacja
    count++;    // count wynosi teraz 6
    count--;    // count znów wynosi 5

    // Przypisanie złożone
    count += 3;  // count wynosi teraz 8
    count -= 2;  // count wynosi teraz 6
    count *= 4;  // count wynosi teraz 24
    count /= 6;  // count wynosi teraz 4

    // Dzielenie całkowitoliczbowe obcina (nie zaokrągla)
    int result = 7 / 2;    // result == 3, nie 3.5

    // Operacje bitowe (używane dla flag)
    int flags = 0;
    flags = flags | 0x01;   // ustaw bit 0
    flags = flags | 0x04;   // ustaw bit 2
    bool hasBit0 = (flags & 0x01) != 0;  // true
}

Przykład z praktyki: Liczba graczy ​

c
void PrintPlayerCount()
{
    array<Man> players = new array<Man>;
    GetGame().GetPlayers(players);
    int count = players.Count();
    Print(string.Format("Players online: %1", count));
}

Praca z float ​

Liczby zmiennoprzecinkowe reprezentują liczby dziesiętne. DayZ szeroko je wykorzystuje do pozycji, odległości, procentów zdrowia, wartości obrażeń i timerów.

c
void FloatExamples()
{
    float health = 100.0;
    float damage = 25.5;
    float remaining = health - damage;   // 74.5

    // Specyficzne dla DayZ: mnożnik obrażeń
    float headMultiplier = 3.0;
    float actualDamage = damage * headMultiplier;  // 76.5

    // Dzielenie floatów daje wyniki dziesiętne
    float ratio = 7.0 / 2.0;   // 3.5

    // Przydatna matematyka
    float dist = 150.7;
    float rounded = Math.Round(dist);    // 151
    float floored = Math.Floor(dist);    // 150
    float ceiled  = Math.Ceil(dist);     // 151
    float clamped = Math.Clamp(dist, 0.0, 100.0);  // 100
}

Przykład z praktyki: Sprawdzanie odległości ​

c
bool IsPlayerNearby(PlayerBase player, vector targetPos, float radius)
{
    if (!player)
        return false;

    vector playerPos = player.GetPosition();
    float distance = vector.Distance(playerPos, targetPos);
    return distance <= radius;
}

Praca z bool ​

Zmienne logiczne przechowują true lub false. Są używane w warunkach, flagach i śledzeniu stanów.

c
void BoolExamples()
{
    bool isAdmin = true;
    bool isBanned = false;

    // Operatory logiczne
    bool canPlay = isAdmin || !isBanned;    // true (OR, NOT)
    bool isSpecial = isAdmin && !isBanned;  // true (AND)

    // Negacja
    bool notAdmin = !isAdmin;   // false

    // Wyniki porównań są typu bool
    int health = 50;
    bool isLow = health < 25;       // false
    bool isHurt = health < 100;     // true
    bool isDead = health == 0;      // false
    bool isAlive = health != 0;     // true
}

Prawdziwość w warunkach ​

W Enforce Script można używać wartości niebędących typem bool w warunkach. Następujące są uznawane za false:

  • 0 (int)
  • 0.0 (float)
  • "" (pusty łańcuch)
  • null (pusta referencja obiektu)

Wszystko inne jest true. Jest to powszechnie używane do sprawdzania wartości null:

c
void SafeCheck(PlayerBase player)
{
    // Te dwa zapisy są równoważne:
    if (player != null)
        Print("Player exists");

    if (player)
        Print("Player exists");

    // I te dwa również:
    if (player == null)
        Print("No player");

    if (!player)
        Print("No player");
}

Praca z string ​

Łańcuchy w Enforce Script są typami wartościowymi --- są kopiowane przy przypisaniu lub przekazaniu do funkcji, tak jak int czy float. To różni się od C# lub Javy, gdzie łańcuchy są typami referencyjnymi.

c
void StringExamples()
{
    string greeting = "Hello";
    string name = "Survivor";

    // Łączenie za pomocą +
    string message = greeting + ", " + name + "!";  // "Hello, Survivor!"

    // Formatowanie łańcuchów (indeksowanie od 1)
    string formatted = string.Format("Player %1 has %2 health", name, 75);
    // Wynik: "Player Survivor has 75 health"

    // Długość
    int len = message.Length();    // 17

    // Porównanie
    bool same = (greeting == "Hello");  // true

    // Konwersja z innych typów
    string fromInt = "Score: " + 42;     // działa -- operator + konwertuje 42 na "42"
    string correct = "Score: " + 42.ToString();  // "Score: 42" (jawnie, ten sam wynik)

    // Użycie Format to preferowane podejście
    string best = string.Format("Score: %1", 42);  // "Score: 42"
}

Sekwencje ucieczki ​

Łańcuchy obsługują standardowe sekwencje ucieczki:

SekwencjaZnaczenie
\nNowa linia
\rPowrót karetki
\tTabulacja
\\Literalny ukośnik wsteczny
\"Literalny cudzysłów

Ostrzeżenie: Chociaż te sekwencje są udokumentowane, ukośnik wsteczny (\\) i cudzysłowy z escape (\") są znane z powodowania problemów z CParserem w niektórych kontekstach, szczególnie w operacjach związanych z JSON. Pracując ze ścieżkami plików lub łańcuchami JSON, unikaj ukośników wstecznych, gdy to możliwe. Używaj ukośników zwykłych do ścieżek --- DayZ akceptuje je na wszystkich platformach.

Przykład z praktyki: Wiadomość na czacie ​

c
void SendAdminMessage(string adminName, string text)
{
    string msg = string.Format("[ADMIN] %1: %2", adminName, text);
    Print(msg);
}

Praca z vector ​

Typ vector przechowuje trzy komponenty float (x, y, z). Jest to podstawowy typ DayZ dla pozycji, kierunków, rotacji i prędkości. Podobnie jak łańcuchy i typy prymitywne, wektory są typami wartościowymi --- są kopiowane przy przypisaniu.

Inicjalizacja ​

Wektory można zainicjalizować na dwa sposoby:

c
void VectorInit()
{
    // Sposób 1: Inicjalizacja łańcuchem (trzy liczby rozdzielone spacjami)
    vector pos1 = "100.5 0 200.3";

    // Sposób 2: Funkcja konstruktora Vector()
    vector pos2 = Vector(100.5, 0, 200.3);

    // Wartość domyślna to "0 0 0"
    vector empty;   // empty == <0, 0, 0>
}

Ważne: Format inicjalizacji łańcuchem używa spacji jako separatorów, nie przecinków. "1 2 3" jest prawidłowy; "1,2,3" nie.

Dostęp do komponentów ​

Dostęp do poszczególnych komponentów odbywa się za pomocą indeksowania w stylu tablicy:

c
void VectorComponents()
{
    vector pos = Vector(100.5, 25.0, 200.3);

    // Odczyt komponentów
    float x = pos[0];   // 100.5  (Wschód/Zachód)
    float y = pos[1];   // 25.0   (Góra/Dół, wysokość)
    float z = pos[2];   // 200.3  (Północ/Południe)

    // Zapis komponentów
    pos[1] = 50.0;      // Zmień wysokość na 50
}

System współrzędnych DayZ:

  • [0] = X = Wschód(+) / Zachód(-)
  • [1] = Y = Góra(+) / Dół(-) (wysokość nad poziomem morza)
  • [2] = Z = Północ(+) / Południe(-)

Stałe statyczne ​

c
vector zero    = vector.Zero;      // "0 0 0"
vector up      = vector.Up;        // "0 1 0"
vector right   = vector.Aside;     // "1 0 0"
vector forward = vector.Forward;   // "0 0 1"

Typowe operacje wektorowe ​

c
void VectorOps()
{
    vector pos1 = Vector(100, 0, 200);
    vector pos2 = Vector(150, 0, 250);

    // Odległość między dwoma punktami
    float dist = vector.Distance(pos1, pos2);

    // Kwadrat odległości (szybszy, dobry do porównań)
    float distSq = vector.DistanceSq(pos1, pos2);

    // Kierunek z pos1 do pos2
    vector dir = vector.Direction(pos1, pos2);

    // Normalizacja wektora (długość = 1)
    vector norm = dir.Normalized();

    // Długość wektora
    float len = dir.Length();

    // Interpolacja liniowa (50% między pos1 a pos2)
    vector midpoint = vector.Lerp(pos1, pos2, 0.5);

    // Iloczyn skalarny
    float dot = vector.Dot(dir, vector.Up);
}

Przykład z praktyki: Pozycja spawnu ​

c
// Uzyskaj pozycję na ziemi na danych współrzędnych X, Z
vector GetGroundPosition(float x, float z)
{
    vector pos = Vector(x, 0, z);
    pos[1] = GetGame().SurfaceY(x, z);  // Ustaw Y na wysokość terenu
    return pos;
}

// Uzyskaj losową pozycję w promieniu od punktu centralnego
vector GetRandomPositionAround(vector center, float radius)
{
    float angle = Math.RandomFloat(0, Math.PI2);
    float dist = Math.RandomFloat(0, radius);

    vector offset = Vector(Math.Cos(angle) * dist, 0, Math.Sin(angle) * dist);
    vector pos = center + offset;
    pos[1] = GetGame().SurfaceY(pos[0], pos[2]);
    return pos;
}

Praca z typename ​

Typ typename przechowuje referencję do samego typu. Jest używany do refleksji --- badania i pracy z typami w czasie wykonywania. Spotkasz go przy pisaniu systemów generycznych, ładowarek konfiguracji i wzorców fabrycznych.

c
void TypenameExamples()
{
    // Uzyskaj typename klasy
    typename t = PlayerBase;

    // Uzyskaj typename z łańcucha
    typename t2 = "PlayerBase".ToType();

    // Porównaj typy
    if (t == PlayerBase)
        Print("It's PlayerBase!");

    // Uzyskaj typename instancji obiektu
    PlayerBase player;
    // ... załóżmy, że player jest prawidłowy ...
    typename objType = player.Type();

    // Sprawdź dziedziczenie
    bool isMan = objType.IsInherited(Man);

    // Konwertuj typename na łańcuch
    string name = t.ToString();  // "PlayerBase"

    // Utwórz instancję z typename (wzorzec fabryczny)
    Class instance = t.Spawn();
}

Konwersja wyliczeń z typename ​

c
enum DamageType
{
    MELEE = 0,
    BULLET = 1,
    EXPLOSION = 2
};

void EnumConvert()
{
    // Wyliczenie na łańcuch
    string name = typename.EnumToString(DamageType, DamageType.BULLET);
    // name == "BULLET"

    // Łańcuch na wyliczenie
    int value;
    typename.StringToEnum(DamageType, "EXPLOSION", value);
    // value == 2
}

Klasa Managed ​

Managed to specjalna klasa bazowa, która wyłącza zliczanie referencji przez silnik. Klasy rozszerzające Managed nie są śledzone przez garbage collector silnika --- ich czas życia jest całkowicie zarządzany przez skryptowe referencje ref.

c
class MyScriptHandler : Managed
{
    // Ta klasa nie będzie zbierana przez garbage collector silnika
    // Zostanie usunięta dopiero gdy zostanie zwolniona ostatnia ref
}

Większość klas czysto skryptowych (które nie reprezentują encji gry) powinna rozszerzać Managed. Klasy encji jak PlayerBase, ItemBase rozszerzają EntityAI (które jest zarządzane przez silnik, NIE Managed).

Kiedy używać Managed ​

Użyj Managed dla...NIE używaj Managed dla...
Klas danych konfiguracjiPrzedmiotów (ItemBase)
Singletonowych menedżerówBroni (Weapon_Base)
Kontrolerów UIPojazdów (CarScript)
Obiektów obsługi zdarzeńGraczy (PlayerBase)
Klas pomocniczych/narzędziowychJakiejkolwiek klasy rozszerzającej EntityAI

Jeśli twoja klasa nie reprezentuje fizycznej encji w świecie gry, powinna prawie na pewno rozszerzać Managed.


Konwersja typów ​

Enforce Script obsługuje zarówno konwersje niejawne, jak i jawne między typami.

Konwersje niejawne ​

Niektóre konwersje zachodzą automatycznie:

c
void ImplicitConversions()
{
    // int na float (zawsze bezpieczne, brak utraty danych)
    int count = 42;
    float fCount = count;    // 42.0

    // float na int (OBCINA, nie zaokrągla!)
    float precise = 3.99;
    int truncated = precise;  // 3, NIE 4

    // int/float na bool
    bool fromInt = 5;      // true (niezerowe)
    bool fromZero = 0;     // false
    bool fromFloat = 0.1;  // true (niezerowe)

    // bool na int
    int fromBool = true;   // 1
    int fromFalse = false; // 0
}

Konwersje jawne (parsowanie) ​

Do konwersji między łańcuchami a typami numerycznymi użyj metod parsowania:

c
void ExplicitConversions()
{
    // Łańcuch na int
    int num = "42".ToInt();           // 42
    int bad = "hello".ToInt();        // 0 (kończy się po cichu)

    // Łańcuch na float
    float f = "3.14".ToFloat();       // 3.14

    // Łańcuch na vector
    vector v = "100 25 200".ToVector();  // <100, 25, 200>

    // Liczba na łańcuch (za pomocą Format)
    string s1 = string.Format("%1", 42);       // "42"
    string s2 = string.Format("%1", 3.14);     // "3.14"

    // int/float .ToString()
    string s3 = (42).ToString();     // "42"
}

Rzutowanie obiektów ​

Dla typów klas użyj Class.CastTo() lub ClassName.Cast(). Jest to szczegółowo omówione w Rozdziale 1.3, ale oto podstawowy wzorzec:

c
void CastExample()
{
    Object obj = GetSomeObject();

    // Bezpieczne rzutowanie (preferowane)
    PlayerBase player;
    if (Class.CastTo(player, obj))
    {
        // player jest prawidłowy i bezpieczny do użycia
        string name = player.GetIdentity().GetName();
    }

    // Alternatywna składnia rzutowania
    PlayerBase player2 = PlayerBase.Cast(obj);
    if (player2)
    {
        // player2 jest prawidłowy
    }
}

Zasięg zmiennych ​

Zmienne istnieją tylko w bloku kodu (nawiasach klamrowych), w którym zostały zadeklarowane. Enforce Script nie pozwala na ponowną deklarację zmiennej o tej samej nazwie w zagnieżdżonych lub siostrzanych blokach.

c
void ScopeExample()
{
    int x = 10;

    if (true)
    {
        // int x = 20;  // BŁĄD: ponowna deklaracja 'x' w zagnieżdżonym bloku
        x = 20;         // OK: modyfikacja zewnętrznego x
        int y = 30;     // OK: nowa zmienna w tym bloku
    }

    // y NIE jest tutaj dostępne (zadeklarowane w wewnętrznym bloku)
    // Print(y);  // BŁĄD: niezadeklarowany identyfikator 'y'

    // WAŻNE: dotyczy to również pętli for
    for (int i = 0; i < 5; i++)
    {
        // i istnieje tutaj
    }
    // for (int i = 0; i < 3; i++)  // BŁĄD w DayZ: 'i' już zadeklarowane
    // Użyj innej nazwy:
    for (int j = 0; j < 3; j++)
    {
        // j istnieje tutaj
    }
}

Pułapka siostrzanych bloków ​

Jest to jedna z najbardziej znanych osobliwości Enforce Script. Deklaracja zmiennej o tej samej nazwie w blokach if i else powoduje błąd kompilacji:

c
void SiblingTrap()
{
    if (someCondition)
    {
        int result = 10;    // Zadeklarowane tutaj
        Print(result);
    }
    else
    {
        // int result = 20; // BŁĄD: wielokrotna deklaracja 'result'
        // Mimo że jest to siostrzany blok, nie ten sam blok
    }

    // ROZWIĄZANIE: zadeklaruj powyżej if/else
    int result;
    if (someCondition)
    {
        result = 10;
    }
    else
    {
        result = 20;
    }
}

Priorytet operatorów ​

Od najwyższego do najniższego priorytetu:

PriorytetOperatorOpisŁączność
1() [] .Grupowanie, dostęp do tablicy, dostęp do składowejOd lewej do prawej
2! - (unarny) ~Logiczne NOT, negacja, bitowe NOTOd prawej do lewej
3* / %Mnożenie, dzielenie, moduloOd lewej do prawej
4+ -Dodawanie, odejmowanieOd lewej do prawej
5<< >>Przesunięcie bitoweOd lewej do prawej
6< <= > >=RelacyjneOd lewej do prawej
7== !=RównośćOd lewej do prawej
8&Bitowe ANDOd lewej do prawej
9^Bitowe XOROd lewej do prawej
10|Bitowe OROd lewej do prawej
11&&Logiczne ANDOd lewej do prawej
12||Logiczne OROd lewej do prawej
13= += -= *= /= %= &= |= ^= <<= >>=PrzypisanieOd prawej do lewej

Wskazówka: W razie wątpliwości używaj nawiasów. Enforce Script przestrzega reguł priorytetu podobnych do C, ale jawne grupowanie zapobiega błędom i poprawia czytelność.


Najlepsze praktyki ​

  • Zawsze jawnie inicjalizuj zmienne przy deklaracji, nawet gdy wartość domyślna odpowiada twojemu zamiarowi -- komunikuje to intencje przyszłym czytelnikom.
  • Używaj const dla każdej wartości, która nigdy nie powinna się zmieniać; umieszczaj stałe na poziomie pliku lub klasy z nazewnictwem UPPER_SNAKE_CASE.
  • Preferuj string.Format() zamiast łączenia + przy mieszaniu typów -- unikasz problemów z niejawną konwersją i jest to łatwiejsze do czytania.
  • Używaj vector.DistanceSq() zamiast vector.Distance() przy porównywaniu odległości -- unikasz kosztownej operacji pierwiastka kwadratowego.
  • Nigdy nie porównuj floatów za pomocą ==; zawsze używaj tolerancji epsilon przez Math.AbsFloat(a - b) < 0.001.

Zaobserwowane w prawdziwych modach ​

Wzorce potwierdzone przez analizę kodu źródłowego profesjonalnych modów DayZ.

WzorzecModSzczegół
const string LOG_PREFIX na poziomie klasyCOT / ExpansionKażdy moduł definiuje stałą łańcuchową dla prefiksów logów, aby uniknąć literówek
Nazewnictwo składowych m_PascalCaseVPP / Dabs FrameworkWszystkie zmienne składowe konsekwentnie używają prefiksu m_, nawet dla typów prymitywnych
string.Format dla całego wyjścia logówExpansion MarketNigdy nie używa łączenia + z liczbami -- zawsze symbole zastępcze %1..%9
vector.Zero zamiast literału "0 0 0"COT Admin ToolsUżywa nazwanych stałych dla czytelności i aby uniknąć narzutu parsowania łańcucha

Teoria a praktyka ​

KonceptTeoriaRzeczywistość
Słowo kluczowe autoPowinno dedukować dowolny typDziała dla prostych przypisań, ale może mylić czytelników -- większość modów deklaruje typy jawnie
Obcinanie float do intUdokumentowane jako "zaokrągla w kierunku zera"Łapie prawie każdego przynajmniej raz; 3.99 staje się 3, nie 4
string jest typem wartościowymPrzekazywany przez kopię jak intZaskakuje programistów C#/Java, którzy oczekują semantyki referencyjnej; modyfikacje kopii nigdy nie wpływają na oryginał

Najczęstsze błędy ​

1. Niezainicjalizowane zmienne używane w logice ​

Typy prymitywne otrzymują wartości domyślne (0, 0.0, false, ""), ale poleganie na tym czyni kod kruchym i trudnym do czytania. Zawsze inicjalizuj jawnie.

c
// ŹLE: poleganie na niejawnym zerze
int count;
if (count > 0)  // To działa, bo count == 0, ale intencja jest niejasna
    DoThing();

// DOBRZE: jawna inicjalizacja
int count = 0;
if (count > 0)
    DoThing();

2. Obcinanie float do int ​

Konwersja float na int obcina (zaokrągla w kierunku zera), nie zaokrągla do najbliższej:

c
float f = 3.99;
int i = f;         // i == 3, NIE 4

// Jeśli chcesz zaokrąglenia:
int rounded = Math.Round(f);  // 4

3. Precyzja float w porównaniach ​

Nigdy nie porównuj floatów pod kątem dokładnej równości:

c
float a = 0.1 + 0.2;
// ŹLE: może się nie powieść z powodu reprezentacji zmiennoprzecinkowej
if (a == 0.3)
    Print("Equal");

// DOBRZE: użyj tolerancji (epsilon)
if (Math.AbsFloat(a - 0.3) < 0.001)
    Print("Close enough");

4. Łączenie łańcuchów z liczbami ​

Nie można po prostu dodać liczby do łańcucha za pomocą +. Użyj string.Format():

c
int kills = 5;
// Potencjalnie problematyczne:
// string msg = "Kills: " + kills;

// PRAWIDŁOWO: użyj Format
string msg = string.Format("Kills: %1", kills);

5. Format łańcucha wektora ​

Inicjalizacja wektora łańcuchem wymaga spacji, nie przecinków:

c
vector good = "100 25 200";     // PRAWIDŁOWO
// vector bad = "100, 25, 200"; // BŁĘDNIE: przecinki nie są prawidłowo parsowane
// vector bad2 = "100,25,200";  // BŁĘDNIE

6. Zapominanie, że łańcuchy i wektory to typy wartościowe ​

W przeciwieństwie do obiektów klas, łańcuchy i wektory są kopiowane przy przypisaniu. Modyfikacja kopii nie wpływa na oryginał:

c
vector posA = "10 20 30";
vector posB = posA;       // posB to KOPIA
posB[1] = 99;             // Zmienia się tylko posB
// posA nadal wynosi "10 20 30"

Ćwiczenia praktyczne ​

Ćwiczenie 1: Podstawy zmiennych ​

Zadeklaruj zmienne do przechowywania:

  • Imienia gracza (string)
  • Jego procentu zdrowia (float, 0-100)
  • Jego liczby zabójstw (int)
  • Czy jest administratorem (bool)
  • Jego pozycji w świecie (vector)

Wydrukuj sformatowane podsumowanie za pomocą string.Format().

Ćwiczenie 2: Konwerter temperatury ​

Napisz funkcję float CelsiusToFahrenheit(float celsius) i jej odwrotność float FahrenheitToCelsius(float fahrenheit). Przetestuj z punktem wrzenia (100C = 212F) i punktem zamarzania (0C = 32F).

Ćwiczenie 3: Kalkulator odległości ​

Napisz funkcję, która przyjmuje dwa wektory i zwraca:

  • Odległość 3D między nimi
  • Odległość 2D (ignorując wysokość / oś Y)
  • Różnicę wysokości

Wskazówka: Dla odległości 2D utwórz nowe wektory z [1] ustawionym na 0 przed obliczeniem odległości.

Ćwiczenie 4: Żonglowanie typami ​

Mając łańcuch "42", skonwertuj go na:

  1. int
  2. float
  3. Z powrotem na string za pomocą string.Format()
  4. bool (powinno być true, ponieważ wartość int jest niezerowa)

Ćwiczenie 5: Pozycja na ziemi ​

Napisz funkcję vector SnapToGround(vector pos), która przyjmuje dowolną pozycję i zwraca ją z komponentem Y ustawionym na wysokość terenu w danej lokalizacji X, Z. Użyj GetGame().SurfaceY().


Podsumowanie ​

KonceptKluczowy punkt
Typyint, float, bool, string, vector, typename, void
Wartości domyślne0, 0.0, false, "", "0 0 0", null
StałeSłowo kluczowe const, konwencja UPPER_SNAKE_CASE
WektoryInicjalizacja łańcuchem "x y z" lub Vector(x,y,z), dostęp przez [0], [1], [2]
ZasięgZmienne ograniczone do bloków {}; brak ponownej deklaracji w zagnieżdżonych/siostrzanych blokach
Konwersjafloat na int obcina; do parsowania łańcuchów użyj .ToInt(), .ToFloat(), .ToVector()
FormatowanieZawsze używaj string.Format() do budowania łańcuchów z mieszanych typów

Released under CC BY-SA 4.0 | Code examples under MIT License