20 KiB
Csataadatok és csatafüggvények technikai leírása
Ez a dokumentum a csatamód (battle állapot) forráskódja alapján írja le, hogyan kell felépíteni a battle_data.battles bejegyzéseit, és milyen argumentumokkal hívódnak az MI-függvények, a képességek/tárgyak függvényei és az akcióanimációk.
Jelölések: Ami a csatamód forrásából közvetlenül kiolvasható, azt tényként írom le. Ami következtetés, vagy a nem látott fájlokban (
battle.py,battle_data.py,menu.py,text.py) dől el, azt [Feltételezés] vagy TODO jelzi.
1. Terminológia
A kód belső nevei eltérnek a játékbeli fogalmaktól, ezért érdemes tisztán tartani:
| Kód | Játékbeli jelentés | Képernyőpozíció |
|---|---|---|
party_members |
A játékos által irányított karakter(ek), vagyis a megszállt főellenfél (és kísérői) | Bal oldal |
enemies |
A hősök, akik ellen a játékos harcol | Jobb oldal |
enemy_turn_func |
A hősök MI-függvénye | |
battle_id |
A csata azonosítója (a négy csataszoborhoz tartozik) |
TODO: Melyik
battle_idmelyik szoborhoz tartozik, és ki állítja be az értékét a csata előtt (feltehetően az overworld).
2. A csata betöltése
A switch(game) a battle_data.battles[battle_id] bejegyzésből tölti be a csatát. A következő mezők mély másolatot (copy.deepcopy) kapnak, tehát a csata közbeni módosításaik nem maradnak meg a következő csatára, és az újraindítás mindig tiszta állapotot ad:
enemies, party_members, enemy_turn_func, init_ai_data, menus, spells, inventory.
A sprite-ok, a háttér és a zene nem másolódnak, csak hivatkozás történik rájuk.
Fontos: Mivel a másolat készül, az MI-függvények és képességfüggvények mindig a nekik átadott objektumokkal dolgozzanak, ne a
battle_datamodulban lévő eredetikre hivatkozzanak (pl. egy zárványon (closure) keresztül). Különben az eredeti, nem a csatában résztvevő példány módosulna. Adamage_callbackis csak olyan célponttal ad helyes pozíciót, amely a jelenlegienemiesvagyparty_memberslistában van, különben a szám a(0, 0)pozícióban jelenik meg.
3. A battle_data.battles[battle_id] szerkezete
Egy bejegyzés egy szótár (dict) a következő kulcsokkal:
| Kulcs | Típus | Leírás |
|---|---|---|
"enemies" |
list[Combatant] |
A hősök listája |
"party_members" |
list[Combatant] |
A játékos által irányított karakterek listája |
"enemy_turn_func" |
függvény | A hősök MI-függvénye (lásd 8. fejezet) |
"init_ai_data" |
másolható objektum (általában dict) |
Az MI kezdeti állapota, a csata elején másolódik |
"menus" |
list[list[tuple[str, str]]] |
Karakterenkénti parancsmenük (lásd 5. fejezet) |
"spells" |
list[list[tuple]] |
Karakterenkénti képességlisták (lásd 6. fejezet) |
"inventory" |
list[list] |
A csatában használható tárgyak, közösek (lásd 7. fejezet) |
"enemy_sprites" |
list[list[azonosító]] |
Hősönként 3 sprite-azonosító (lásd 11. fejezet) |
"party_sprites" |
list[list[azonosító]] |
Karakterenként 3 sprite-azonosító |
"background" |
azonosító | Kulcs az assets.sprites szótárba |
"mus_id" |
azonosító | Kulcs az assets.sounds szótárba, a zene ismétlődve (loops=True) szól |
A menus, spells, party_sprites listák indexe a party_members indexével egyezik. Az enemy_sprites indexe az enemies indexével egyezik.
A csatát a battle_data.battles[battle_id] alapján választja ki a rendszer. A battles tehát olyan tároló, amely a battle_id értékkel indexelhető.
Váz példa
battles = [
{
"enemies": [Combatant(...), Combatant(...)],
"party_members": [Combatant(...)],
"enemy_turn_func": hero_ai_1,
"init_ai_data": {"turn": 0},
"menus": [
[("Támadás", "phys_att"), ("Varázslat", "magic"), ("Tárgyak", "items")],
],
"spells": [
[("Tűzgolyó", 5, ("Tűzgolyó", fireball, "ene"))],
],
"inventory": [
["Gyógyital", 3, ("Gyógyital", use_potion, "ally")],
],
"enemy_sprites": [
["hero1_normal", "hero1_hurt", "hero1_ko"],
["hero2_normal", "hero2_hurt", "hero2_ko"],
],
"party_sprites": [
["boss1_normal", "boss1_hurt", "boss1_ko"],
],
"background": "bg_arena_1",
"mus_id": "mus_battle_1",
},
# ... további 3 csata
]
4. Combatant
A Combatant osztály (battle.py) konstruktora nem látható a csatamód forrásában, ezért itt csak az onnan elérhető attribútumokat sorolom fel:
| Attribútum | Használat a csatamódban |
|---|---|
name |
Megjelenített név (menü, célpontválasztó) |
hp |
Aktuális életerő (célpontválasztóban kijelzett; sprite-váltás) |
maxhp |
Maximális életerő. A 10%-os sprite-váltás küszöbe |
ko |
Legyőzött-e (a szereplő nem cselekszik, a célpontválasztóból kimarad) |
stone |
Kővé változott-e (nem cselekszik, szürke sprite) |
def_up |
Védekezik-e (a célpontválasztóban „Védés” felirat) |
atb |
Az akcióig hátralévő időzítő (lásd 9. és 12. fejezet) |
attack |
Támadóerő, a „Támadás” parancs ezt adja a Damage-nek |
TODO: A
Combatantkonstruktorának paraméterei és sorrendje, a kezdőatbértékek, további mezők (pl. MP, sebesség).
5. Menük (menus)
Karakterenként egy lista, amelynek elemei (felirat, művelet) párok:
("Támadás", "phys_att")
A művelet értéke a menu_action függvényben kerül feldolgozásra:
| Művelet | Hatás |
|---|---|
"phys_att" |
Célpontválasztó ("ene"), majd egy Action("Támadás", user, target, Damage(user.attack, 0, 0, 0), StatusChange(0, 0, 0), battle_data.anim_default) kerül a sorba |
"magic" |
Megnyitja a képességmenüt (spells[karakter_index]) |
"items" |
Megnyitja a tárgymenüt (inventory) |
"unselectable" |
Nem csinál semmit, a kijelölés a menüben marad |
| bármi más | Kiírja az Unknown action hibát a konzolra, és bezárja a menüt (lásd 12. fejezet) |
A menüpontok x = 19, y = 64 - i * 13 pozícióban jelennek meg, ez a menüpanelen belül legfeljebb 5 sort jelent (lásd 12. fejezet).
6. Képességek (spells)
Karakterenként egy lista, amelynek elemei:
(név, mp_költség, (akció_név, függvény, célpont_típus))
| Elem | Leírás |
|---|---|
név |
A képességmenüben megjelenő felirat |
mp_költség |
A kódban kicsomagolódik, de a csatamód nem használja (nem ellenőrzi, nem vonja le) |
akció_név |
A kódban kicsomagolódik, de a csatamód nem használja |
függvény |
függvény(user, target) -> Action. Lásd lent |
célpont_típus |
Lásd 8. fejezet |
A kiválasztott célpont után a csatamód ezt hívja:
action_queue.append(függvény(user, targets[0]))
Vagyis a függvény két argumentumot kap: a cselekvő karaktert és a kiválasztott egyetlen célpontot, és egy Action objektumot kell visszaadnia.
TODO: Az MP-költség levonása és ellenőrzése. A csatamódban nincs ilyen, tehát vagy az
Action/animáció végzi, vagy még nincs megvalósítva.
7. Tárgyak (inventory)
Egyetlen, a csata összes karaktere által közösen használt lista. Elemei módosítható listák (nem tuple-ök, mert a mennyiség csökken):
[név, mennyiség, (akció_név, függvény, célpont_típus)]
| Elem | Leírás |
|---|---|
név |
A menüben név xMENNYISÉG formában jelenik meg (pl. Gyógyital x3) |
mennyiség |
int. A 0 vagy kisebb értékű tárgyak nem jelennek meg |
függvény |
Ugyanaz, mint a képességeknél: függvény(user, target) -> Action |
célpont_típus |
Lásd 8. fejezet |
A mennyiség a kiválasztáskor (az akció sorba állításakor) csökken eggyel, nem a végrehajtáskor.
8. Célpont-típusok
Az open_target_menu(user, aparty, eparty, target_type, on_pick, back) a target_type alapján állítja össze a választható célpontokat.
Dokumentáció és kód eltérése: A függvény docstringje az
"enemy","ally","self","all_enemies","all_allies"értékeket említi, de a tényleges kód más szavakat vizsgál. A kód a mérvadó.
target_type |
Célpont-lista | Legyőzöttek? | Megjegyzés |
|---|---|---|---|
"self" |
A cselekvő maga | Nem releváns | Nincs célpontválasztó, a menü azonnal bezárul |
"ene" |
eparty (a hősök) |
Kimaradnak | A „Támadás” is ezt használja |
"ene_rev" |
eparty |
Benne vannak | |
"all_rev" |
aparty (a saját oldal) |
Benne vannak | Újraélesztő képességekhez |
| bármilyen más szöveg | aparty (a saját oldal) |
Kimaradnak | Pl. "ally" ide esik |
További tudnivalók:
- Az
apartya játékos oldala (party_members), azepartya hősöké (enemies). - Ha nincs választható célpont, a függvény visszatér, és az előző menü marad nyitva.
- A célpont-visszahívás (
on_pick) mindig egyelemű listát kap, így a csatamód többcélpontos kijelölést nem támogat. A célpontválasztó listája a célpont nevét és az aktuális életerejét mutatja, kiegészítve aKőésVédésállapotokkal. - Ha területi hatást akarsz, azt a függvénynek/animációnak kell kezelnie. TODO: A függvény csak a
useréstargetargumentumot kapja, így a többi szereplő eléréséhez a modul szintű listákra (battle.enemies,battle.party_members) lenne szükség. Ennek a tervezett módja még tisztázandó.
9. Az MI-függvény (enemy_turn_func)
Aláírás
def hero_ai(e, enemies, party_members, ai_data):
...
return Action(...) # vagy None
| Argumentum | Tartalom |
|---|---|
e |
A cselekvésre kész hős (Combatant) |
enemies |
A hősök (saját oldal) listája |
party_members |
A játékos karaktereinek listája (az ellenfél, az MI szemszögéből) |
ai_data |
A init_ai_data csatánkénti másolata. Az MI ebben tárolhat állapotot (pl. számlálót) |
Visszatérési érték:
Action(vagy bármi igaz értékű): az akció a sor végére kerül, és a hősatbértékemath.inflesz.None/ hamis érték: a hős nem csinál semmit ebben a képkockában, és az MI-függvény a következő képkockában újra meghívódik, amíg nem ad vissza akciót. Ez lehetővé teszi, hogy az MI egy feltételre várjon.
Mikor hívódik?
A függvény minden képkockában (update) meghívódik azokra a hősökre, akik:
- nincsenek legyőzve és nincsenek kővé változva (
not ko and not stone), és - az
atbértékük<= 0.
Minden képkockában minden hős atb-ja eggyel csökken (képkockafüggő, nem dt-alapú. Lásd 12. fejezet).
Példa
def hero_ai_1(e, enemies, party_members, ai_data):
targets = [p for p in party_members if not p.ko and not p.stone]
if not targets:
return None
ai_data["turn"] += 1
target = random.choice(targets)
return Action(
"Támadás", e, target,
Damage(e.attack, 0, 0, 0), StatusChange(0, 0, 0),
battle_data.anim_default,
)
10. Az Action és az akcióanimáció
Az Action felépítése
A kódban látható hívás alapján:
Action(név, user, target, damage, status_change, animation)
| Argumentum | Leírás |
|---|---|
név |
Az akció neve (a képernyő alján megjelenő panelhez) |
user |
A cselekvő Combatant |
target |
A célpont Combatant |
damage |
Damage(a, b, c, d). Az első érték a támadásnál a user.attack |
status_change |
StatusChange(a, b, c). A „Támadás” csupa nullát ad |
animation |
Az animációs függvény (lásd lent) |
A csatamód közvetlenül csak az animation mezőt olvassa. A többi attribútum nevét (pl. action.user, action.target) az animációknak kell ismerniük.
TODO: Az
Action,Damage,StatusChangepontos attribútumnevei és mezőinek jelentése (aDamageésStatusChangetovábbi paraméterei). Ezek abattle.py-ban vannak. TODO: Abattle_data.anim_defaultműködése.
Az animációs függvény aláírása
def animation(action, action_text, timer, dt, damage_callback) -> bool:
...
| Argumentum | Tartalom |
|---|---|
action |
Az éppen futó Action objektum |
action_text |
A TextRenderer(122, 204, None, True) példány, az akciópanel szövege |
timer |
Az animáció kezdete óta eltelt idő másodpercben. Az első híváskor pontosan 0 |
dt |
Az előző képkocka óta eltelt idő |
damage_callback |
damage_callback(amount, target). Lebegő szám létrehozása (lásd lent) |
Visszatérési érték:
True: az animáció véget ért. A csatamód törli az aktuális akciót, és elrejti az akciópanelt.False: az animáció még fut. Atimeradt-vel nő, és a következő képkockában újra hívódik.
Az akciópanel (action_panel) a teljes animáció ideje alatt látható, de az action_text szövegét az animációnak kell beállítania (pl. action_text.reset(...), type_all()), ahogy a menü szövegét is a csatamód teszi a menüben. Az action_text.render() minden képkockában lefut, még ha a panel rejtett is, ezért az animáció végén érdemes kiüríteni a szöveget (action_text.reset("")) [Feltételezés].
damage_callback(amount, target)
| Paraméter | Jelentés |
|---|---|
amount |
Negatív szám = sebzés (fehér számként, előjel nélkül jelenik meg). Nem negatív szám = gyógyulás (zöld szám) |
target |
A célpont Combatant. A szám a sprite pozíciója fölött jelenik meg (a sprite_pos(target) alapján) |
A callback csak a lebegő számot hozza létre. A HP tényleges módosítása és a ko beállítása nem itt történik, ezeket az animációnak/akciónak kell elvégeznie [Feltételezés].
A lebegő szám 0,5 másodpercig él, és képkockánként (0, 0.4) értékkel mozog felfelé.
Példa (szemléltető jellegű)
def anim_example(action, action_text, timer, dt, damage_callback):
# Első képkocka: szöveg kiírása
if timer == 0:
action_text.reset(action.name) # [Feltételezés] az attribútum neve
action_text.type_all()
# Egyszeri találat 0,6 mp-nél
if timer >= 0.6 and not getattr(action, "_hit", False):
action._hit = True
action.target.hp -= 5 # [Feltételezés] a HP-módosítás az animáció dolga
damage_callback(-5, action.target)
# Vége 1,2 mp-nél
if timer >= 1.2:
action_text.reset("")
return True
return False
11. Egy akció életciklusa
A update(game, dt) minden képkockában a következő sorrendben fut:
- Hősök időzítője: minden él(ő), nem kővé vált hősnél
atb -= 1. Haatb <= 0, meghívódik azenemy_turn_func. Ha akciót ad vissza, az a sorba kerül, ésatb = math.inf. - Játékos karaktereinek időzítője: ugyanígy
atb -= 1. Haatb <= 0, a karakter amenu_queue-ba kerül, ésatb = math.inf. - Következő akció indítása: ha az
action_queuenem üres és nincs futó akció, az első elem lesz az aktuális (cur_action), az akciópanel megjelenik,action_timer = 0. - Futó akció léptetése:
cur_action.animation(...)meghívása.Trueesetén vége az akciónak, különbenaction_timer += dt. - Menü megnyitása: ha van várakozó karakter és nincs nyitott menü, megnyílik a következő karakter menüje. A szöveg: „név menüje”.
- Menüpontok és lebegő számok frissítése.
- Győzelem ellenőrzése: ha az összes hős
kovagystone, a zene megáll, és a játék átvált az"overworld"állapotra. - Vereség ellenőrzése: ha a játékos összes karaktere
kovagystone, a zene megáll, és a játék átvált a"gameover"állapotra.
A győzelem-ellenőrzés a vereségé előtt fut, ezért ha mindkét feltétel egyszerre teljesül, a játékos nyer.
12. Elrendezés és sprite-ok
Pozíciók
| Szereplő | Pozíció (képernyőpont) |
|---|---|
Hős (i. index) |
(185, 175 - i * 20) |
Játékos karaktere (i. index) |
(40 + (i % 2) * 30, 165 - i * 25) |
A sprite-ok középpontja ezen a ponton van. A pozíciók a lista sorrendjét követik. Az alsó menüpanel 230 × 100 méretű, ezért a lista végén lévő szereplők a panel mögé/fölé kerülhetnek (hősöknél kb. a 4. index után, a játékos karaktereinél kb. a 3. index után).
Három sprite az állapotokhoz
Az enemy_sprites[i] és party_sprites[i] pontosan 3 azonosítót tartalmaz, ebben a sorrendben:
| Index | Mikor látszik |
|---|---|
[0] |
Normál állapot |
[1] |
Ha hp <= maxhp * 0.1 (sebzett) |
[2] |
Ha ko igaz (a KO sprite elsőbbséget élvez) |
Ha a szereplő stone állapotban van (és nem ko), akkor az éppen kiválasztott sprite szürkeárnyalatos változata látszik. A szürke változatot a csatamód létrehozza és gyorsítótárazza.
Az azonosítók az assets.sprites szótár kulcsai. Az asset lehet közvetlenül textúra, vagy lehet .texture attribútuma.
13. Ismert korlátok és buktatók
Ezeket a csatamód forrásából olvastam ki. Érdemes őket a csatatervezés közben szem előtt tartani.
- Üres képesség- vagy tárgymenü beragadást okoz. Az
open_spell_menués azopen_item_menuelőször kiüríti amenulistát, és csak utána ellenőrzi, hogy van-e elem. Ha nincs (pl. egy karakternek nincs képessége, vagy elfogytak a tárgyak), a menü üres marad, amenu_openpedig nem nullázódik, így a csata ettől a karaktertől kezdve megakadhat. Ezért:- ne szerepeljen
"magic"a menüben, ha a karakternek nincs képessége, - ne szerepeljen
"items", ha nincs tárgy a csatában, vagy gondoskodni kell róla, hogy a tárgyak ne fogyjanak el.
- ne szerepeljen
- Ismeretlen menüművelet elveszíti a kört. A
menu_actionaz ismeretlen értéknél bezárja a menüt (close_menu()), ezért a karakteratbértékemath.infmarad, és nem fog újra cselekedni, hacsak más nem állítja vissza. - Az
atbvisszaállítása nem a csatamódban történik. A csatamód az akciók sorba állításakormath.inf-re állítja azatb-t, de újra nem tölti fel. Ezért az akcióknak/animációknak kell auser.atbértékét pozitív számra visszaállítani a végrehajtás után [Feltételezés, ellenőrizendő abattle.py-ban]. Ha ez elmarad, a karakter többé nem cselekszik. - Az
atbképkockafüggő. Azatb -= 1mindenupdatehíváskor lefut, nem adt-től függ. A csata sebessége így a frissítési gyakoriságtól függ. - Legfeljebb 5 menüsor fér el. A menüpontok
y = 64 - i * 13pozícióban vannak. A 6. elem már a képernyőn kívülre esik. Ez a képesség-, tárgy- és célpontlistára egyaránt vonatkozik. - Nincs ellenőrzés az akció végrehajtásakor. A sorba állított akció akkor is lefut, ha közben a cselekvő vagy a célpont legyőzötté vált. Az animációnak/akciónak kell kezelnie ezt az esetet [Feltételezés].
- Többcélpontos akció nincs. A célpontválasztás mindig egyetlen célpontot ad át.
- Az MP nem érvényesül a csatamódban (lásd 6. fejezet).
14. Nyitott kérdések (TODO)
- A
Combatant,Action,Damage,StatusChangeosztályok teljes definíciója (battle.py). - A
battle_data.anim_defaultés az egyéb beépített animációk működése. - Az
atbújratöltésének helye és értékei. - A HP-módosítás, a
ko,stone,def_upállapotok beállításának helye. - Az MP-rendszer megléte és működése.
- A
TextRendererésMenuItempontos API-ja (a csatamódban használt hívások:reset,type_all,move,render,select,update). - A
battle_idés a csataszobrok összerendelése.