OpenTTD #Polska - Polskie forum gry OpenTTD [ARCHIWUM]
INNE Tworzenie NewGRF w NML #2 - parowóz Pt31 i inne pojazdy przegubowe. - Wersja do druku

+- OpenTTD #Polska - Polskie forum gry OpenTTD [ARCHIWUM] (https://forum.openttd.pl)
+-- Dział: OpenTTD (https://forum.openttd.pl/forumdisplay.php?fid=1)
+--- Dział: Dodatki (https://forum.openttd.pl/forumdisplay.php?fid=27)
+--- Wątek: INNE Tworzenie NewGRF w NML #2 - parowóz Pt31 i inne pojazdy przegubowe. (/showthread.php?tid=950)



Tworzenie NewGRF w NML #2 - parowóz Pt31 i inne pojazdy przegubowe. - TadeuszD - 09-11-2011

Tym razem zaprezentuję krótki tutorial opisujący budowę pojazdu przegubowego (ang.: articulated), dość często spotykanego w OTTD, zarówno wśród pociągów jak i samochodów. Inspirację do napisania tego dodatku NewGRF będzie stanowił polski parowóz Pt31.

Opracowanie grafiki
Zasady opracowywania grafik dla pojazdów przegubowych są w zasadzie identyczne, jak dla zwykłych pojazdów - dla każdego z członów pojazdu (w tym wypadku: lokomotywy i węglarki) należy opracować osobny zestaw spritesetów, ukazujących pojazd z każdej strony.
W przypadku projektowania grafiki dla pojazdów przegubowych można jednak zastosować pewne triki, które w przypadku normalnych pojazdów są niedostępne. Jeden z takich trików będzie miał miejsce właśnie w przypadku omawianego parowozu Pt31. Powodem jego zastosowania jest długość oryginału. Pt31 jest bowiem drugim (zaraz po Pu29) najdłuższym polskim parowozem - jego całkowita długość to prawie 24m! Tymczasem w OTTD najdłuższy pojazd może mieć rozmiar 8/8 kratek, czyli zaledwie 32 pixele. Zastosowany trik polega na tym, że pojazd dwuczłonowy (lokomotywa + węglarka) może mieć już łącznie 2*8/8 kratek, czyli w sumie 64 pixele. Dodatkowo, te 64 pixele mogą być rozdzielone między człony nieproporcjonalnie, niezgodnie z teoretyczną długością tych członów:

[Obrazek: 752pt31_metoda_10_8.png]

Obrazek powyżej przedstawia zasadę działania zastosowanego triku. Lokomotywa i jej węglarka będą miały nadal zaprogramowaną teoretyczną maksymalną długość 8/8, jednak graficznie będą wyglądały jakby miały długość 6/8 oraz 10/8. Część pixeli należących do węglarki została bowiem wykorzystana do odwzorowania znacznie dłuższej lokomotywy.
Takie realistyczne odwzorowanie lokomotywy ma oczywiście swoje wady i zalety. Długa lokomotywa będzie się prezentowała znacznie lepiej niż oryginalne "ogryzki" z TTD. Będzie to jednak okupione wyraźnym jej "łamaniem" się na zakrętach oraz koniecznością budowy dłuższych stacji.

Zaprojektowanie lokomotywy wymagało opracowania nowego szablonu, dostosowanego do rozmiaru 10/8. Do narysowania węglarki wystarczył natomiast standardowy szablon 8/8, wykorzystywany do projektowania innych pojazdów. Kompletny zestaw grafik obejmuje także widok parowozu, który będzie ukazywał się w menu zakupu pojazdów (purchase menu). Dzięki takiemu obrazkowi w menu zakupu będzie pojawiał się kompletny parowóz, a nie tylko jeden jego człon:

[Obrazek: 858pt31.png]

Kod NML, służący do wczytania sprite-ów z wyżej przedstawionego zestawu grafik, będzie miał następującą postać:
Kod:
/* graphics definition */
template template_sprite_train_10(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         10,        32,          -5,        -14]
    [x+ 18,      y,         30,        30,         -19,        -17]
    [x+ 54,      y,         44,        20,         -26,        -13]
    [x+104,      y,         30,        30,         -11,        -19]
    [x+140,      y,         10,        32,          -5,        -18]
    [x+158,      y,         30,        30,         -15,        -19]
    [x+194,      y,         44,        20,         -18,        -13]
    [x+244,      y,         30,        30,          -7,        -17]
}
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         10,        28,          -5,        -14]
    [x+ 16,      y,         26,        28,         -15,        -17]
    [x+ 46,      y,         36,        20,         -18,        -13]
    [x+ 86,      y,         26,        28,          -7,        -17]
    [x+120,      y,         10,        28,          -5,        -14]
    [x+136,      y,         26,        28,         -15,        -17]
    [x+166,      y,         36,        20,         -18,        -13]
    [x+206,      y,         26,        28,          -7,        -17]
}
template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -6]
}

/* empty sprites */
spriteset(spriteset_pt31_purchase, "gfx/pt31.png") {
        template_sprite_purchase(4, 100)
}
spriteset(spriteset_pt31, "gfx/pt31.png") {
        template_sprite_train_10(4, 20)
}
spriteset(spriteset_pt31_tender, "gfx/pt31.png") {
        template_sprite_train(4, 60)
}

c.d.n.


RE: Mój drugi NewGRF - parowóz Pt31. - TadeuszD - 11-11-2011

Parametry parowozu
Następnym krokiem w projektowaniu parowozu jest zaprogramowanie jego parametrów. Struktura tych parametrów wygląda podobnie jak w przypadku poprzedniej drezyny WM-15A:
Kod:
/* Define the actual train */
item(FEAT_TRAINS, item_pt31) {
    property {
        /* common properties */
        name:                           string(STR_PT31_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1931, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   30;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS);
        non_refittable_cargo_classes:   bitmask();
        refittable_cargo_types:         bitmask();
        loading_speed:                  5;
        cost_factor:                    18;
        running_cost_factor:            122;
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          111 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_PASSENGER;
        power:                          1471 kW;
        running_cost_base:              RUNNING_COST_STEAM;
        dual_headed:                    0;
        cargo_capacity:                 0;
        weight:                         173 ton;    // 106+67 ton
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_STEAM;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3*74/173;
        air_drag_coefficient:           0.07;
        shorten_vehicle:                SHORTEN_TO_8_8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DEFAULT, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }

    graphics {
        purchase:                   spriteset_pt31_purchase;
        default:                    spriteset_pt31;        // <- tymczasowo
    }
}

Znaczenie większości parametrów jest opisane w dokumentacji, zamieszczonej na OpenTTD Wiki. Niektóre z nich wymagają jednak dokładniejszego wytłumaczenia:
speed (prędkość maksymalna) - niby wszystko jasne, lecz OTTD wprowadza szereg zaokrągleń w trakcie dokonywanych przeliczeń, przez co prędkość widoczna w grze czasem odbiega dość mocno od tej zadanej w GRF-ie. Dlatego należy ją doświadczalnie zwiększyć o 1-3 km/h, aby uzyskać wynik jak najbardziej zbliżony do żądanego.
weight (ciężar) - w przypadku pojazdu przegubowego jest to masa całego pojazdu. Dla Pt31 podana jest więc suma ciężaru lokomotywy (106 ton) oraz jej węglarki (67 ton).
tractive_effort_coefficient (współczynnik maksymalnej siły pociągowej) - współczynnik określający jaka część ciężaru pojazdu przekłada się na jego siłę pociągową. W przypadku pojazdów szynowych przyjmuje się 0.3 (30%). Dla parowozu współczynnik ten musi być jeszcze skorygowany ze względu na fakt, że nie wszystkie osie parowozu są napędzane. W przypadku Pt31 nacisk na jedną oś napędową wynosi 18,3 tony. Po przemnożeniu przez 4 osie mamy "zaledwie" ok. 74 ton masy czynnej w porównaniu ze 173 tonami masy całkowitej.

Dodanie obsługi przegubowości
Najistotniejszym etapem tworzenia pojazdu przegubowego jest napisanie odpowiednich funkcji (switch-y), odpowiedzialnych za realizację efektów przegubowości:
Kod:
/* Pt31 switches */
switch(FEAT_TRAINS, SELF, switch_pt31_articulated, extra_callback_info1) {
    1: return item_pt31;    // Add tender
    return 0xFF;
}
switch(FEAT_TRAINS, SELF, switch_pt31_graphics, position_in_vehid_chain % 2) {
    0: return spriteset_pt31;
    return spriteset_pt31_tender;
}
switch(FEAT_TRAINS, SELF, switch_pt31_visual_effect, position_in_vehid_chain % 2) {
    0: return visual_effect_and_powered(VISUAL_EFFECT_STEAM, -4, DISABLE_WAGON_POWER);
    return visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
}

... oraz podpięcie ich pod odpowiednie callback-i w sekcji graphics pojazdu:
Kod:
graphics {
    purchase:                   spriteset_pt31_purchase;
    default:                    switch_pt31_graphics;
    articulated_part:           switch_pt31_articulated;
    visual_effect_and_powered:  switch_pt31_visual_effect;
}

Callback-iem decydującym o istnieniu przegubowości jest callback articuladed_part. Podpiety pod niego swith switch_pt31_articulated jest wywoływany w trakcie tworzenia pojazdu wielokrotnie, w celu określenia identyfikatorów dodatkowych członów pojazdu. Za każdym wywołaniem swoitch-a w jego parametrze extra_callback_info1 znajduje się numer kolejnego członu pojazdu. Zwracaną przez switch wartością powinien być identyfikator tego kolejnego członu lub wartość 0xFF, oznaczająca koniec pojazdu.
W naszym przypadku zarówno lokomotywa, jak i jej węglarka, powinny mieć dokładnie ten sam identyfikator (parowóz jest pojazdem nierozdzielnym), dlatego na pytanie o drugi człon pojazdu (extra_callback_info1 = 1) zwracany jest również identyfikator item_pt31.

Drugi ze zdefiniowanych switch-y, switch_pt31_graphics odpowiada za właściwą wizualizację parowozu jako pojazdu przegubowego. Konieczność stworzenia tego switcha wynika z faktu, że wszystkie człony parowozu mają ten sam identyfikator. Musimy więc poinformowac grę, kiedy w ramach tego samego identyfikatora item_pt31 ma wyświetlić lokomotywę, a kiedy węglarkę.
Decyzja ta jest podejmowana na podstawie parametru position_in_vehid_chain, który określa ni mniej, ni więcej, tylko kolejną pozycję danego członu w łańcuchu pojazdów o tym samym identyfikatorze. W pokazanym przypadku wartość ta jest jeszcze poddana operacji modulo 2, aby właściwe sprite-y były wyświetlane także dla kolejnych lokomotyw w składzie, jadących w tzw. "trakcji wielokrotnej".

Trzeci switch, switch_pt31_visual_effect pełni bardzo podobną rolę, co poprzedni switch, lecz dotyczy "efektów specjalnych" wyświetlanych przez grę. Zadaniem tego switcha jest spowodowanie, że dym z komina będzie generowany jedynie przez lokomotywę. Węglarka będzie miała nieaktywne wszelkie efekty.

Kompletny kod NewGRF
Poniżej zamieszczam pełną treść plików, niezbędnych do skompilowania dodatku:
custom_tags.txt
Kod:
VERSION        :0.1.0
TITLE        :Pt31

lang/english.lng
Kod:
##grflangid 0x01
# This is the English language file

# GRF name and description
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{ORANGE}{TITLE} - polish steam locomotive.{BLACK}{}{COPYRIGHT}2011 {WHITE}Tadeusz Domagalski{BLACK}{}License: GPL v2

STR_PT31_NAME           :Pt31 (Steam)

lang/polish.lng
Kod:
##grflangid 0x30
# Polska wersja językowa

# Nazwa GRF oraz opis
STR_GRF_NAME        :{TITLE} {VERSION}
STR_GRF_DESCRIPTION :{ORANGE}Polski parowóz {TITLE}.{BLACK}{}{COPYRIGHT}2011 {WHITE}Tadeusz Domagalski{BLACK}{}License: GPL v2

STR_PT31_NAME           :Pt31 (Parowóz)

pt31.nml
Kod:
grf {
    grfid: "TD\01\02";
    name: string(STR_GRF_NAME);
    desc: string(STR_GRF_DESCRIPTION);
    version: 0;
    min_compatible_version: 0;
}

/* graphics definition */
template template_sprite_train10(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         10,        32,          -5,        -14]
    [x+ 18,      y,         30,        30,         -20,        -18]
    [x+ 54,      y,         44,        20,         -26,        -14]
    [x+104,      y,         30,        30,         -12,        -20]
    [x+140,      y,         10,        32,          -5,        -18]
    [x+158,      y,         30,        30,         -16,        -20]
    [x+194,      y,         44,        20,         -18,        -14]
    [x+244,      y,         30,        30,          -8,        -18]
}
template template_sprite_train(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         10,        28,          -5,        -14]
    [x+ 16,      y,         26,        28,         -16,        -18]
    [x+ 46,      y,         36,        20,         -18,        -14]
    [x+ 86,      y,         26,        28,          -8,        -18]
    [x+120,      y,         10,        28,          -5,        -14]
    [x+136,      y,         26,        28,         -16,        -18]
    [x+166,      y,         36,        20,         -18,        -14]
    [x+206,      y,         26,        28,          -8,        -18]
}
template template_sprite_purchase(x, y) {
// [left_x,   upper_y,    width,    height,    offset_x,    offset_y]
    [x,          y,         50,        12,         -25,         -6]
}

/* Pt31 sprites */
spriteset(spriteset_pt31_purchase, "gfx/pt31.png") {
    template_sprite_purchase(4, 100)
}
spriteset(spriteset_pt31, "gfx/pt31.png") {
    template_sprite_train10(4, 20)
}
spriteset(spriteset_pt31_tender, "gfx/pt31.png") {
    template_sprite_train(4, 60)
}

/* Pt31 switches */
switch(FEAT_TRAINS, SELF, switch_pt31_articulated, extra_callback_info1) {
    1: return item_pt31;    // Add tender
    return 0xFF;
}
switch(FEAT_TRAINS, SELF, switch_pt31_graphics, position_in_vehid_chain % 2) {
    0: return spriteset_pt31;
    return spriteset_pt31_tender;
}
switch(FEAT_TRAINS, SELF, switch_pt31_visual_effect, position_in_vehid_chain % 2) {
    0: return visual_effect_and_powered(VISUAL_EFFECT_STEAM, -4, DISABLE_WAGON_POWER);
    return visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER);
}

/* Define the actual train */
item(FEAT_TRAINS, item_pt31) {
    property {
        /* common properties */
        name:                           string(STR_PT31_NAME);
        climates_available:             bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC);
        introduction_date:              date(1931, 1, 1);
        model_life:                     VEHICLE_NEVER_EXPIRES;
        vehicle_life:                   30;
        reliability_decay:              20;
        refittable_cargo_classes:       bitmask(CC_PASSENGERS);
        non_refittable_cargo_classes:   bitmask();
        refittable_cargo_types:         bitmask();
        loading_speed:                  5;
        cost_factor:                    18;     // 7% (18=7%, 20=8%)
        running_cost_factor:            122;    // 48% (122=48%)
        /* train properties */
        sprite_id:                      SPRITE_ID_NEW_TRAIN;
        speed:                          111 km/h;
        misc_flags:                     bitmask();
        refit_cost:                     0;
        track_type:                     RAIL;
        ai_special_flag:                AI_FLAG_PASSENGER;
        power:                          1471 kW;
        running_cost_base:              RUNNING_COST_STEAM;
        dual_headed:                    0;
        cargo_capacity:                 0;
        weight:                         173 ton;    // 106+67 ton
        ai_engine_rank:                 0; // not intended to be used by the ai
        engine_class:                   ENGINE_CLASS_STEAM;
        extra_power_per_wagon:          0 kW;
        tractive_effort_coefficient:    0.3*74/173;
        air_drag_coefficient:           0.07;
        shorten_vehicle:                SHORTEN_TO_8_8;
        extra_weight_per_wagon:         0;
        visual_effect_and_powered:      visual_effect_and_powered(VISUAL_EFFECT_DEFAULT, 0, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:         0 ton;
        bitmask_vehicle_info:           0;
    }

    graphics {
        purchase:                   spriteset_pt31_purchase;
        default:                    switch_pt31_graphics;
        articulated_part:           switch_pt31_articulated;
        visual_effect_and_powered:  switch_pt31_visual_effect;
    }
}

Na koniec przedstawiam zrzut ekranu, przedstawiający różne przykłady szynowych pojazdów przegubowych pod OTTD Wink :

[Obrazek: 550Pt31_Bipa.png]