Vývoj her: Hrajeme si s AI

Podpořit high-voltage.cz na STARTOVAČI

21 patronů = 2120 Kč měsíčně

Je to malé pade pro čtenáře, ale velká vzpruha pro HV! ;)

Tento článek vznikl díku článku Vývoj her: Laborování se závodní AI. Děkuji ti za inspiraci a postrčení k napsání vlastního článku.

V tomto článku si řekneme o využití AI (nejen) pro vývoj her. Konkrétně se budeme zabývat hrami na PC (vedle nich existují i hry stolní, venkovní, atd). Používat budeme běžný PC s MS Windows i GNU/Linux, vývojové prostředí/knihovnu Processing a samozřejmě AI. V mém případě pak free verzi ChatGpt od openAi (v některých situacích i pak i jednodušší model po vypršení bezplatného plánu). Součástí článku jsou i zdrojové kódy, které si budete moci hned vyzkoušet, stačí je zkopírovat a vložit do IDE Processing.

Vývoj her: Hrajeme si s AI

Processing

Ještě je třeba zmínit pár slov o Processingu. Ti, kdo ho znají, mohou tento odstavec v klidu přeskočit. Processing je vývojové prostředí a zároveň knihovna původně postavená nad Javou (dnes má i JS verzi) určená původně pro neprogramátory a umělce. Tím jsou dané jeho výhody a nevýhody, začněme výhodami:

  • Běží všude, kde běží Java.
  • Hodně knihoven s příklady.
  • Vývoj je extrémně rychlý.
  • Uzpůsoben na vizuální obsah.
  • Stáhnu a spustím (javu má v sobě).

Na druhou stranu má samozřejmě i nevýhody:

  • IDE je velice jednoduché (na úrovni textového editoru).
  • Pokud se projekt rozroste, je těžké jej pak udržovat.
  • Všechny nevýhody spojené s Javou.

Instalační souboru Processingu lze zdarma stáhnout na processing.org nebo portable verzi přímo z githubu projektu. Ve většině případů není potřeba nic doinstalovávat a stačí jen spustit.

Obecně o vývoji her

Vraťme se ale zpět k vývoji her. Musím zde zdůraznit, že vývoj nejen hry v jednom člověku je obtížný. Dnes je doba mnohem složitější a hráče rozhodně neuspokojí hra kompletně vytvořená jedním člověkem (čest výjimkám). Jsou proto zapotřebí následující role:

  • Herní designér
  • Level designér
  • Programátor/Kodér
  • Grafik
  • Zvukař
  • Tester

Role se samozřejmě mohou prolínat, ovšem ideální stav je mít je odděleně. Vzhledem k zaměření tohoto článku můžeme vypustit následující role, coby samostatné osoby:

  • Tester: Prototypujeme, důsledné testování nepotřebujeme.
  • Zvukař: Bez toho se do začátku obejdeme.
  • Grafik: Využijeme volně dostupnou práci někoho jiného.
  • Level designér: Prototypujeme, spokojíme se s tím "že to nějak funguje".

Zbývá nám tedy herní designér a programátor, to jsme my. Funkce programátora je čtenářům High Voltage jasná, tu není třeba představovat. Co ale dělá herní designér? Odpověď je jednoduchá, navrhuje hru. Představte si to na stolní hře šachy, herní designér je ten, kdo vymyslí pravidla, je to přesně ta osoba, která má za úkol, aby všechny herní mechaniky a entity spolu fungovali harmonicky. Zodpovídá za to, že herní prvky budou například vyvážené (např. kůň v šachách může procházet skrz ostatní figury, ale jen v omezeném směru). Výsledek jeho práce je design document, je to taková bible s popsanými mechanikami ke hře. Pokud vezmu za příklad šachy, tak design document je jejich pravidla, tam je popsáno vše, co je potřeba ke hraní.

AI tajemství zbavená - první prompt

No a jakou roli hraje AI? Ta pro nás bude dělat "špinavou" práci. Bude to náš kodér, tj. bude bouchat kód podle našeho návrhu, který my jako programátoři pak upravíme. Než ovšem začneme zběsile psát prompt ve stylu "udělej úžasnou hru, která bude mít super grafiku a bude všechny bavit", pojďme se jen letmo zmínit, jak AI funguje.

Ve skutečnosti je AI hodně dobrý prediktor textu, nic víc, nic míň. Podle zadání odhadne (interně spočítá), jaká odpověď nejlépe vyhovuje. To jak a jakým způsobem budeme dávat zadání, silně ovlivňuje výstup (garbage in -* garbage out). Také je třeba brát v potaz, že se učila na datech od lidí, tak že se naučila i chybné věci a lhát. Dobré je vědět, že i prompt má danou strukturu (tj. co vše má obsahovat) a je více než vhodné ji zachovávat, jedná se o:

  • Instrukce
  • Kontext
  • Role
  • Formátování
  • Tón
  • Příklady

AI rozumí dobře i česky a dokonce bez diakritiky (a odpustí drobné chyby, viz vabrhni), takže můžeme při psaní "ušetřit" trochu času nepřepínáním klávesnice. Ukažme si to na následujícím příkladu (pseudo kód je doplněný jen pro čtenáře, jinak se nepoužívá):

PROMT: "<role><ton>Jsi specialista</ton> na 2D herni enginy pro hry videne shora <kontext>v jazyce Java s nadstavbou processing.</kontext></role> <formatovani>Nabrhni jednoduchy kod</formatovani>.<instrukce> Ve kterem se hrac bude pohybovat po svete. Hrac je dole uprostred okna, jezdi dopredu, dozadu a otaci se. Poloha hrace je vzdy stejna, otaci se svet</instrukce>"

AI odpověď:
"Jasně! Popisuješ klasický top-down 2D engine, kde hráč stojí "stacionárně" uprostřed obrazovky a svět kolem něj se otáčí a posouvá podle pohybu hráče. V Java + Processing to můžeme řešit poměrně jednoduše. Níže je minimalistický příklad:"

Ukázka 1. kódu ▼ (klikni pro rozbalení)

// Rozměry okna
int windowWidth = 800;
int windowHeight = 600;

// Hráč - jen úhel otáčení
float playerAngle = 0;
float speed = 5;
float rotationSpeed = 0.05;

// Svět - mapa (jednoduché pozadí)
PImage world;

void setup() {
  size(windowWidth, windowHeight);
  // Jednoduchá textura pozadí
  world = createImage(2000, 2000, RGB);
  world.loadPixels();
  for (int i = 0; i < world.pixels.length; i++) {
    if (random(1) > 0.5) {
      world.pixels[i] = color(200, 200, 200); // světlé políčko
    } else {
      world.pixels[i] = color(100, 100, 100); // tmavé políčko
    }
  }
  world.updatePixels();
}

void draw() {
  background(50);

  // Ovládání hráče
  if (keyPressed) {
    if (keyCode == LEFT) {
      playerAngle -= rotationSpeed;
    } else if (keyCode == RIGHT) {
      playerAngle += rotationSpeed;
    }
  }

  float dx = 0;
  float dy = 0;
  if (keyPressed) {
    if (keyCode == UP) {
      dx = cos(playerAngle) * speed;
      dy = sin(playerAngle) * speed;
    } else if (keyCode == DOWN) {
      dx = -cos(playerAngle) * speed;
      dy = -sin(playerAngle) * speed;
    }
  }

  // Posun pozadí opačně, aby hráč zůstal uprostřed
  pushMatrix();
  translate(width/2, height/2);
  rotate(-playerAngle);
  imageMode(CENTER);
  image(world, -dx, -dy);  // posun mapy
  popMatrix();

  // Nakreslení hráče uprostřed
  fill(255, 0, 0);
  noStroke();
  triangle(-10, 15, 10, 15, 0, -15); // trojúhelník jako hráč
  pushMatrix();
  translate(width/2, height/2);
  rotate(playerAngle);
  fill(255, 0, 0);
  triangle(-10, 15, 10, 15, 0, -15);
  popMatrix();
}

AI odpověď:
"Pokud chceš, můžu ti připravit vylepšenou verzi, kde svět bude plynule scrollovat a hráč může kolidovat s překážkami, stejně jako malá fyzika pohybu s inercií.
Chceš, abych takovou verzi udělal?"

Iterujeme, ladíme a opravujeme

Kód samozřejmě na první dobrou nefungnuje. Za prvé nejde vůbec spustit, ale to jde lehce opravit. v metodě "size(windowWidth, windowHeight);" je definovaná velikost okna proměnou, to fungovalo pouze ve starších verzích (my jsme ale nedefinovali, jako verzi Processingu použít, tohle jde za námi). Pro opravu stačí změnit na size(800, 600);. Druhá chyba je horší, hráč se otáčí, ale nejde pohyb. Takže zkusíme další prompt:

PROMPT: "Hrac se nema otacet. Engine by mel evokovat pohyb tanku nebo lode."

AI si pamatuje kontext (částečně), takže nemusíme v našem případě nic připomínat. Je ale důležité psát přesně, co od ní chcete. V tomto popisu jsme navíc přidali příklad, který nám předtím chyběl - "pohyb tanku nebo lodě". Pokud bychom měli připravený design dokument, této chybě se dalo předejít. (Stále dělá chybu s proměnou v metodě setup - to jsme také v promptu neřešili. Kód v příkladech již opravený je)

AI odpověď: "Ah, chápu přesně! Chceš tankový/top-down pohyb, kde hráč je stále uprostřed obrazovky, neotáčí se, ale svět se posouvá podle toho, kam hráč "jede" – tedy klasický „pohon vpřed/dozadu a rotace těla světového objektu“ se simuluje posunem světa a rotací mapy. Pojďme na jednoduchý kód v Processing:"

Ukázka 2. kódu ▼ (klikni pro rozbalení)


int windowWidth = 800;
int windowHeight = 600;

// Hráč je uprostřed, jen jeho pohyb (rychlost a směr)
float playerAngle = 0;  // směr, kam jede
float speed = 0;
float maxSpeed = 5;
float accel = 0.2;
float rotationSpeed = 0.05;

// Pozice světa (relativní vůči hráči)
float worldX = 0;
float worldY = 0;

// Jednoduchá mapa
PImage world;

void setup() {
  size(800, 600);
  world = createImage(2000, 2000, RGB);
  world.loadPixels();
  for (int i = 0; i < world.pixels.length; i++) {
    if (random(1) > 0.5) {
      world.pixels[i] = color(180, 180, 180);
    } else {
      world.pixels[i] = color(100, 100, 100);
    }
  }
  world.updatePixels();
}

void draw() {
  background(50);

  // Ovládání rotace a pohybu
  if (keyPressed) {
    if (keyCode == LEFT) {
      playerAngle -= rotationSpeed;
    } else if (keyCode == RIGHT) {
      playerAngle += rotationSpeed;
    }

    if (keyCode == UP) {
      speed += accel;
    } else if (keyCode == DOWN) {
      speed -= accel;
    }
  }

  // Omezení rychlosti
  speed = constrain(speed, -maxSpeed, maxSpeed);

  // Posun světa opačně než pohyb hráče
  worldX -= cos(playerAngle) * speed;
  worldY -= sin(playerAngle) * speed;

  // Nakreslení světa
  pushMatrix();
  translate(width/2, height/2);
  rotate(-playerAngle);  // natočíme svět tak, aby hráč jel "vpřed"
  imageMode(CENTER);
  image(world, -worldX, -worldY);
  popMatrix();

  // Hráč uprostřed (stále)
  pushMatrix();
  translate(width/2, height/2);
  fill(255, 0, 0);
  noStroke();
  triangle(-10, 15, 10, 15, 0, -15);  // trojúhelník jako hráč
  popMatrix();
}


AI odpověď: "Mohu ti udělat ještě verzi s inercií a plynulým bržděním, aby to skutečně připomínalo tank nebo loď s houpavým pohybem. Chceš, abych to připravil?"

Tento kód je mnohem lepší, hráč už se pohybuje, sice do boku, ale pohybuje. Necháme to tedy AI opravit následujícím promptem.

PROMT: "Hrac se nepohybuje ve smeru natoceni, oprav kod."

AI odpověď: "Aha! Teď chápu přesně: chceš, aby hráč stál uprostřed, nepřetáčel se a jezdil po světě jako tank nebo loď, tedy:

  • Hráč jede dopředu, dozadu, vlevo a vpravo podle kláves (bez otáčení hráče).
  • Směr pohybu není nutně orientovaný podle natočení.
  • Svět se posouvá opačně než pohyb hráče.

Zde je opravený kód:"

Ukázka 3. kódu ▼ (klikni pro rozbalení)


int windowWidth = 800;
int windowHeight = 600;

// Hráč je uprostřed, jen pohyb
float playerSpeed = 5;

// Pozice světa relativní vůči hráči
float worldX = 0;
float worldY = 0;

// Jednoduchá mapa
PImage world;

void setup() {
  size(800, 600);
  world = createImage(2000, 2000, RGB);
  world.loadPixels();
  for (int i = 0; i < world.pixels.length; i++) {
    if (random(1) > 0.5) {
      world.pixels[i] = color(180, 180, 180);
    } else {
      world.pixels[i] = color(100, 100, 100);
    }
  }
  world.updatePixels();
}

void draw() {
  background(50);

  float dx = 0;
  float dy = 0;

  // Ovládání hráče
  if (keyPressed) {
    if (keyCode == UP) {
      dy -= playerSpeed;
    }
    if (keyCode == DOWN) {
      dy += playerSpeed;
    }
    if (keyCode == LEFT) {
      dx -= playerSpeed;
    }
    if (keyCode == RIGHT) {
      dx += playerSpeed;
    }
  }

  // Posun světa opačně než pohyb hráče
  worldX -= dx;
  worldY -= dy;

  // Nakreslení světa
  imageMode(CORNER);
  image(world, -worldX, -worldY);

  // Hráč uprostřed
  pushMatrix();
  translate(width/2, height/2);
  fill(255, 0, 0);
  noStroke();
  rectMode(CENTER);
  rect(0, 0, 30, 30); // jednoduchý čtverec jako hráč
  popMatrix();
}


AI odpověď: "Pokud chceš, můžu připravit i tankový pohyb s inercií, kde hráč nemění orientaci, ale pohybuje se jako skutečná loď/tank s setrvačností a plynulým bržděním. Chceš, abych to udělal?"

Vzpomínáte jak jsem o pár odstavců psal, že si AI pamatuje kontext jen částečně? Tak zde se to naplno projevilo. Hráč se už neotáčí, ale pohybuje se ortogonálně po světě (navíc vždy v protisměru, ale to je jen detail). Můžeme ovšem využít kontext, který právě napsal "pohybuje se jako skutečná loď/tank s setrvačností a plynulým bržděním". Takže náš následující prompt vypadá takto (ano, i s tím chybným otazníkem)

PROMT: "Ano udelej s inercii. Svet se musi otacet aby evokoval otaceni hrace?"

AI odpověď: "Perfektně, teď už to bude vypadat jako tank nebo loď: hráč stojí stále uprostřed, má inercií, takže po rozjetí pomalu zpomaluje, a svět se může otáčet, aby evokoval směr pohybu hráče – protože hráč se fyzicky neotáčí, ale vizuálně je směr otáčení "napřed" reprezentován rotací světa."

Ukázka 4. kódu ▼ (klikni pro rozbalení)


int windowWidth = 800;
int windowHeight = 600;

// Hráč je stacionární
float playerX = 0;
float playerY = 0;

// Rychlost pohybu
PVector velocity = new PVector(0, 0);
float accel = 0.2;       // zrychlení
float maxSpeed = 5;      // max rychlost
float friction = 0.05;   // brzdění/inercie

// Směr otáčení světa
float worldAngle = 0;
float rotationSpeed = 0.03;

// Pozice světa
float worldX = 0;
float worldY = 0;

// Jednoduchá mapa
PImage world;

void setup() {
  size(800, 600);
  world = createImage(2000, 2000, RGB);
  world.loadPixels();
  for (int i = 0; i < world.pixels.length; i++) {
    if (random(1) > 0.5) {
      world.pixels[i] = color(180, 180, 180);
    } else {
      world.pixels[i] = color(100, 100, 100);
    }
  }
  world.updatePixels();
}

void draw() {
  background(50);

  PVector acceleration = new PVector(0, 0);

  // Ovládání hráče (inertní pohyb)
  if (keyPressed) {
    if (keyCode == UP) {
      acceleration.y = accel;
    }
    if (keyCode == DOWN) {
      acceleration.y = -accel;
    }
    if (keyCode == LEFT) {
      worldAngle -= rotationSpeed;
    }
    if (keyCode == RIGHT) {
      worldAngle += rotationSpeed;
    }
  }

  // Rotace akcelerace podle světového úhlu
  acceleration.rotate(worldAngle);

  // Přidání akcelerace k rychlosti
  velocity.add(acceleration);

  // Omezení rychlosti
  if (velocity.mag() > maxSpeed) {
    velocity.setMag(maxSpeed);
  }

  // Friction/inercie
  velocity.mult(1 - friction);

  // Posun světa opačně než hráč
  worldX -= velocity.x;
  worldY -= velocity.y;

  // Nakreslení světa s rotací
  pushMatrix();
  translate(width/2, height/2);
  rotate(-worldAngle);
  imageMode(CENTER);
  image(world, -worldX, -worldY);
  popMatrix();

  // Hráč uprostřed
  pushMatrix();
  translate(width/2, height/2);
  fill(255, 0, 0);
  noStroke();
  rectMode(CENTER);
  rect(0, 0, 30, 30); // hráč jako čtverec
  popMatrix();
}


AI odpověď: "Pokud chceš, můžu připravit ještě verzi s hladkým kličkováním a diagonální pohybovou fyzikou, která se bude chovat přesně jako skutečný tank nebo loď, včetně setrvačnosti při otáčení. Chceš, abych to udělal?"

Tenhle kód už je, až na malou chybku (šipka v před a vzad je prohozená), funkční. Nicméně se nedá zatáčet při pohybu. To bychom dokázali sami předělat, ale pojďme to zkusit s AI:

PROMPT: "Ano to je ono, uprav kod tak aby se dalo otacet za jizdy"

AI odpověď: "Perfektně, teď uděláme tankový/loďový pohyb s inercií, kde hráč stojí uprostřed, může se otáčet za jízdy, a svět se otáčí tak, aby evokoval směr hráče. Přidáme možnost otočení v průběhu pohybu vpřed/dozadu, takže hráč „kličkuje“ a stále má setrvačnost.!"

Ukázka 5. kódu ▼ (klikni pro rozbalení)


int windowWidth = 800;
int windowHeight = 600;

// Hráč je stacionární
float playerX = 0;
float playerY = 0;

// Rychlost pohybu
PVector velocity = new PVector(0, 0);
float accel = 0.2;       // zrychlení
float maxSpeed = 5;      // max rychlost
float friction = 0.05;   // brzdění/inercie

// Směr otáčení světa
float worldAngle = 0;
float rotationSpeed = 0.03;

// Pozice světa
float worldX = 0;
float worldY = 0;

// Jednoduchá mapa
PImage world;

void setup() {
  size(800, 600);
  world = createImage(2000, 2000, RGB);
  world.loadPixels();
  for (int i = 0; i < world.pixels.length; i++) {
    if (random(1) > 0.5) {
      world.pixels[i] = color(180, 180, 180);
    } else {
      world.pixels[i] = color(100, 100, 100);
    }
  }
  world.updatePixels();
}

void draw() {
  background(50);

  PVector acceleration = new PVector(0, 0);

  // Ovládání hráče (inertní pohyb)
  if (keyPressed) {
    if (keyCode == UP) {
      acceleration.y = accel;
    }
    if (keyCode == DOWN) {
      acceleration.y = -accel;
    }
    if (keyCode == LEFT) {
      worldAngle -= rotationSpeed;
    }
    if (keyCode == RIGHT) {
      worldAngle += rotationSpeed;
    }
  }

  // Rotace akcelerace podle světového úhlu
  acceleration.rotate(worldAngle);

  // Přidání akcelerace k rychlosti
  velocity.add(acceleration);

  // Omezení rychlosti
  if (velocity.mag() > maxSpeed) {
    velocity.setMag(maxSpeed);
  }

  // Friction/inercie
  velocity.mult(1 - friction);

  // Posun světa opačně než hráč
  worldX -= velocity.x;
  worldY -= velocity.y;

  // Nakreslení světa s rotací
  pushMatrix();
  translate(width/2, height/2);
  rotate(-worldAngle);
  imageMode(CENTER);
  image(world, -worldX, -worldY);
  popMatrix();

  // Hráč uprostřed
  pushMatrix();
  translate(width/2, height/2);
  fill(255, 0, 0);
  noStroke();
  rectMode(CENTER);
  rect(0, 0, 30, 30); // hráč jako čtverec
  popMatrix();
}


AI odpověď: "Pokud chceš, můžu přidat vizuální orientační šipku nebo kanón, který ukáže aktuální směr, kam hráč „střílí“/míří, aby to bylo jako skutečný tank. Chceš, abych to udělal?"

Výsledkem našeho snažení je funkční kód, už se jde otáčet za jízdy, ale nemění se tím směr. Což je v pořádku, nebylo to v požadavku (ano, přesně na tohle je Design document), to bychom museli prompt napsat trochu jinak. Dokonce nám AI začíná trochu "kecat" do designu, navrhuje orientační kompas, inu nechme ho udělat práci za nás. Směrový kompas zní jako dobrý nápad. Další vygenerovaný kód "rozbil" pohyb. Neměli jsme definované, co znamená pohyb vpřed, proto v uvozovkách. Kompas byl nicméně funkční, tak jej "stačilo" jen implementovat do předchozí iterace. Kód pro kompas je v ukázce níže:

Ukázka kódu kompasu ▼ (klikni pro rozbalení)


void drawCompass()
{
  pushMatrix();
  translate(width - 80, 80); // umístění kompasu vpravo nahoře
  rotate(worldAngle);
  stroke(0, 255, 0);
  strokeWeight(3);
  fill(0, 255, 0, 100);
  triangle(0, -20, -10, 10, 10, 10); // šipka ukazující směr hráče
  noFill();
  stroke(255);
  strokeWeight(1);
  ellipse(0, 0, 50, 50); // kruh kompasu
  popMatrix();
}

Za domácí úkol si jej naimplementujte sami (není to těžké). Výsledek pak bude podobný jako na obrázku níže:

Vývoj her: Hrajeme si s AI

Závěr

Po cca 30 minutách povídání a ladění s AI máme Proof of Concept funkčního řešení, které můžeme dále rozvíjet. Přímo do produkce to samozřejmě není, potřebuje to ještě spoustu péče a ladění. Kód není nijak optimalizovaný, což pocítíte na slabších strojích. Výsledek jsem testoval na PC s procesorem i3 7. generace a už je to dost pomalé. Je to samozřejmě důsledek otáčení celého světa o rozměru 6000 na 6000 bodů, kdy se přepočítává (posouvá a rotuje) každý bod zvlášť. Řešením by bylo přepočítávat jen viditelnou oblast hráče, ale tyto optimalizace už jsou mimo rámec tohoto článku. Dále pak AI nabídlo i minimapu a po pár iteracích (upřesnění) fungovala dle představ. Ale to si povíme a ukážeme příště i s "finálním" kódem, řekneme si něco dalšího o Processingu a vývoji her, a možná přijde i kouzelník.

Autor: Míra L.

Komentáře k:
"Vývoj her: Hrajeme si s AI"

  • Milsa napsal:

    Ešte som to nečítal, len som to rýchlo prebehol, ale už sa na to teším, keď si nájdem čas to prečítať.

  • Kafral napsal:

    Pár měsíců zpátky mě napadlo něco podobného, v podstatě jako laik co naposled napsal něco v basicu na 8 bitu jsem jen textovým pripominkovanim donutil GPT vytvořit sidescroll střílečku s paralax krajinou, domky,stromy, mraky a horami. Byly tam zvuky,počítání skóre,přidávání životu za skóre, emc puls na nouzové zničení nepřátel. Hráč ovládal letadlo – jediný png vloženy objekt a střílel létající talíře – tři různé rychle a jinak barevné. Celé to zabralo cca 3h čistého času. Věřím že programátor takovou blbost zmakne za 20 min ale to já nejsem. V dalším pokusu jsem mu textem zadal že chci 3d variaci na 8bit bluemax a byl schopný vygenerovat krajinu a jednoduché letadlo nad ní. Pak jsem toho nechal, ale jsem přesvědčený že za pár let bude AI schopna na základě textového zadání vytvořit variací/remaster nějaké starší hry bez nějaké větší námahy.

Přidej komentář (přihlášení / registrace)

(Komentáře s více jak 2 odkazy musí počkat na schválení.)

Podpořit finančním příspěvkem » The Ultimate Retro Gaming PC in a Compact Package Shadow bazar, retro herní džungle