Hoe een JRPG te bouwen een inleiding voor game-ontwikkelaars

Dit artikel is een overzicht op hoog niveau voor het maken van een JRPG (Japanese Role-Playing Game), zoals de vroege Final Fantasy-games. We zullen kijken naar de architectuur en systemen die het skelet vormen van een JRPG, hoe je spelmodi beheert, hoe je tilemaps gebruikt om de wereld te laten zien, en hoe je een RPG-vechtsysteem codeert.

Notitie: Dit artikel is geschreven met behulp van een Java-achtige pseudo-codetaal, maar de concepten zijn van toepassing op elke ontwikkelomgeving voor games.


Inhoud

  • De onwaarschijnlijke geboorteplaats van JRPG's
  • The Genre Talk
  • Vijf redenen om een ​​JRPG te maken
  • architectuur
  • Spelstaat beheren
  • Kaarten
  • gevecht
  • Beoordeling


De onwaarschijnlijke geboorteplaats van JRPG's

Het slijm - een van de iconische vijanden van Dragon Warrior.

In 1983 vlogen Yuji Horii, Koichi Nakamura en Yukinobu Chida naar Amerika en woonden ze AppleFest '83 bij, een verzameling ontwikkelaars die hun nieuwste creaties voor de Apple II lieten zien. Ze werden weggeblazen door de nieuwste versie van een RPG genaamd Wizardry.

Toen ze terugkeerden naar Japan, besloten ze Dragon Warrior te maken, een RPG die vergelijkbaar was maar gestroomlijnd voor de NES. Het was een enorme hit, die het JRPG-genre definieerde. Dragon Warrior ging het niet zo goed in Amerika, maar een paar jaar later deed een ander spel dat wel.

In 1987 kwam de originele Final Fantasy uit, die een van de bestverkopende gamesegmenten op aarde voortbracht die, althans in het Westen, de iconische JRPG werd.



The Genre Talk

Spelgenres zijn nooit precies gedefinieerd - ze zijn meer een vage verzameling conventies. RPG's hebben meestal een levelingsysteem, een of meerdere spelerpersonages met vaardigheden en statistieken, wapens en uitrusting, gevechts- en verkenningsmodi en sterke verhalen; spelvoortgang wordt vaak bereikt door over een kaart te gaan.

Japanse RPG's zijn RPG's gemaakt in de vorm van Dragon Warrior; ze zijn meer lineair, combat is vaak turn-based en er zijn meestal twee soorten kaarten: een wereldkaart en een lokale kaart. Archetypische JRPG's zijn Dragon Warrior, Final Fantasy, Wild Arms, Phantasy Star en Chrono Trigger. Het type JRPG waar we het in dit artikel over zullen hebben is vergelijkbaar met een vroege Final Fantasy.




Vijf redenen om een ​​JRPG te maken

1. Ze hebben de test van de tijd doorstaan

Games zoals Final Fantasy VI en Chrono Trigger zijn nog steeds erg leuk om te spelen. Als je een JRPG maakt, leer je een tijdloos spelformaat waaraan moderne spelers nog steeds erg ontvankelijk zijn. Ze zorgen voor een geweldig kader om je eigen draai en experiment toe te voegen - of dat nu in het verhaal, de presentatie of de mechanica is. Het is geweldig als je een spel kunt maken dat nog tientallen jaren na de eerste release nog steeds wordt gespeeld en genoten!

2. De spelmechanica is op grote schaal toepasbaar

Call of Duty, een van 's werelds populairste FPS-games, maakt gebruik van RPG-elementen; de sociale gameboom rondom FarmVille was in feite een kloon van de SNES RPG Harvest Moon; en zelfs racegames zoals Gran Turismo hebben niveaus en ervaring.

3. Beperkingen Pleeg Creativiteit

Net zoals een schrijver misschien wordt geïntimideerd door een blanco vel papier, raakt een gameontwikkelaar mogelijk verlamd door het grote aantal mogelijke keuzes bij het ontwerpen van een nieuw spel. Met een JRPG zijn veel van de keuzes voor jou bepaald, dus je hebt die keuze verlamming niet, je bent vrij om de conventies te volgen voor de meeste beslissingen en af ​​te wijken van conventie op de punten die er toe doen voor jou.

4. Het is uitvoerbaar als een solo-project

Final Fantasy werd bijna volledig gecodeerd door een enkele programmeur, Nasir Gebelli, en hij deed het in assemblage! Met moderne hulpmiddelen en talen is het veel gemakkelijker om dit type spel te maken. Het grootste deel van de meeste RPG's is niet de programmering, het is de inhoud, maar dit hoeft niet het geval te zijn voor je game. Als je het een beetje draait op de inhoud en je concentreert op kwaliteit boven kwantiteit, dan is een JRPG een geweldig solo-project.

Het hebben van een team kan helpen bij elke game en misschien wil je de kunst en muziek uitbesteden, of een paar van de uitstekende creatieve commons-items gebruiken van plaatsen zoals opengameart.org. (Noot van de redactie: onze zustersite GraphicRiver verkoopt ook sprite-sheets.)

5. Voor winst!

JRPG's hebben een dedicated volgsysteem en een aantal indie-JRPG's (zoals de onderstaande) hebben het goed gedaan in de commercie en zijn beschikbaar op platforms zoals Steam.



architectuur


JRPG's delen zoveel conventies en mechanica dat het mogelijk is om een ​​typische JRPG te onderbreken in een aantal systemen:

In softwareontwikkeling is steeds weer een patroon zichtbaar: gelaagdheid. Dit verwijst naar hoe de systemen van een programma op elkaar zijn gebouwd, met breed toepasbare lagen aan de onderkant en lagen die nauwer omgaan met het probleem dichtbij de top. JRPG's zijn niet anders en kunnen als een aantal lagen worden bekeken - lagere lagen hebben te maken met grafische basisfuncties en bovenste lagen hebben te maken met speurtochten en karakterstatistieken.

Tip: Bij het ontwikkelen van een nieuw systeem kunt u het beste eerst de onderste lagen maken en vervolgens laag voor laag naar boven verplaatsen. Middleware gebruiken helpt om een ​​aantal van de lagere lagen over te slaan die veel spellen gemeen hebben. Op het architectuurdiagram hierboven worden alle lagen onder de stippellijn verwerkt door een 2D-game-engine.

Zoals je kunt zien in het bovenstaande architectuurdiagram, zijn er veel systemen die een JRPG vormen, maar de meeste systemen kunnen als afzonderlijk worden gegroepeerd modes van het spel. JRPG's hebben zeer verschillende spelmodi; ze hebben een wereldkaart, een lokale kaart, een gevechtsmodus en verschillende menumodi. Deze modi zijn bijna volledig afzonderlijke, op zichzelf staande stukjes code, waardoor ze allemaal eenvoudig te ontwikkelen zijn.

Modi zijn belangrijk, maar ze zouden nutteloos zijn zonder game-inhoud. Een RPG bevat veel kaartbestanden, monsterdefinities, dialoogvensterregels, scripts om tussenfilmpjes en gameplaycode uit te voeren om te bepalen hoe de speler vordert. Omvat hoe je een JRPG in detail moet bouwen, zou een heel boek vullen, dus we gaan ons concentreren op enkele van de belangrijkste delen. Eenvoudig omgaan met de spelmodi is van cruciaal belang voor het produceren van een beheersbare JRPG, dus dat is het eerste systeem dat we zullen verkennen.



Spelstaat beheren


De onderstaande afbeelding laat zien hoe de gamelus wegspoelt en een updatefunctie per frame oproept. Dit is de hartslag van het spel en bijna alle spellen zijn op deze manier gestructureerd.

Ben je ooit een project begonnen maar vastgelopen omdat je het te moeilijk vond om nieuwe functies toe te voegen of geplaagd door mysterieuze bugs? Misschien probeerde je al je code in de updatefunctie te proppen met weinig structuur en ontdekte dat de code een cryptische puinhoop werd. Een uitstekende oplossing voor dit soort problemen is het scheiden van de code in verschillende spel staten, een veel duidelijker beeld geven van wat er gebeurt.

Een veelvoorkomende gamedev-tool is de state machine; het wordt overal gebruikt, voor het verwerken van animaties, menu's, spelverloop, AI ... het is een essentieel hulpmiddel in onze kit. Voor de JRPG kunnen we een toestandsmachine gebruiken voor de verschillende spelmodi. We zullen een normale statusmachine bekijken en dan zullen we het een beetje vermengen om het meer geschikt te maken voor de JRPG. Maar laten we eerst een beetje tijd nemen om de algemene spelstroom hieronder te bekijken.

In een typische JRPG begin je waarschijnlijk in de lokale kaartgame-modus, vrij om door een stad te dwalen en interactie te hebben met zijn inwoners. Vanuit de stad kun je vertrekken - hier ga je naar een andere spelmodus en zie je de wereldkaart.

De wereldkaart lijkt veel op de lokale kaart, maar op een grotere schaal; je kunt bergen en steden zien in plaats van bomen en hekken. Als je op de wereldkaart bent als je terugloopt naar de stad, keert de modus terug naar de lokale kaart.

In zowel de wereldkaart als de lokale kaart kun je een menu openen om je personages te bekijken, en soms word je op de wereldkaart in een gevecht gegooid. Het bovenstaande schema beschrijft deze spelmodi en overgangen; dit is de basisstroom van JRPG-gameplay en dat is waar we onze spelstatussen van gaan maken.

Omgang met complexiteit met een staatsmachine

Een staatsmachine is voor ons doel een stukje code dat alle verschillende modi van onze spellen bevat, waarmee we van de ene modus naar de andere kunnen gaan en die updates en rendert, ongeacht de huidige modus..

Afhankelijk van de implementatietaal bestaat een toestandsmachine meestal uit een statemachine klasse en een interface, Ik zeg, die alle staten implementeren.

Tip: Een interface is slechts een klasse met lidfunctie-definities maar geen implementatie. Klassen die overerven van een interface zijn vereist om de ledfuncties te implementeren. Dit betekent dat een interface geen code heeft, het geeft alleen aan dat andere klassen bepaalde functionaliteit bieden. Hierdoor kunnen verschillende klassen op dezelfde manier worden gebruikt, omdat we weten dat ze een groep lidfuncties hebben die worden gedefinieerd door een gemeenschappelijke interface. gerelateerde berichten
  • Inleiding tot object-georiënteerde programmering voor spelontwikkeling

Een staatsmachine kan het best worden beschreven door een basissysteem in pseudocode te schetsen:

class StateMachine Map mStates = nieuwe kaart(); IState mCurrentState = EmptyState; public void Update (float elapsedTime) mCurrentState.Update (elapsedTime);  public void Render () mCurrentState.Render ();  public void Change (String stateName, optional var params) mCurrentState.OnExit (); mCurrentState = mStates [stateName]; mCurrentState.OnEnter (params);  public void Toevoegen (String naam, IState staat) mStates [naam] = staat; 

Deze code hierboven toont een eenvoudige toestandsmachine zonder foutcontrole.

Laten we eens kijken naar hoe de machinecode van hierboven wordt gebruikt in een game. Aan het begin van het spel statemachine wordt gemaakt, alle verschillende staten van het spel toegevoegd en de aanvankelijke staat ingesteld. Elke staat is uniek geïdentificeerd door een Draad naam die wordt gebruikt bij het aanroepen van de change state-functie. Er is slechts één huidige status, mCurrentState, en het wordt elke gamelus weergegeven en bijgewerkt.

De code kan er als volgt uitzien:

StateMachine gGameMode = new StateMachine (); // Een status voor elke spelmodus gGameMode.Add ("hoofdmenu", nieuwe MainMenuState (gGameMode)); gGameMode.Add ("localmap", nieuwe LocalMapState (gGameMode)); gGameMode.Add ("worldmap", nieuwe WorldMapState (gGameMode)); gGameMode.Add ("battle", nieuwe BattleState (gGameMode)); gGameMode.Add ("ingamemenu", nieuwe InGameMenuState (gGameMode)); gGameMode.Change ( "hoofdmenu"); // Main Game Update Loop public void Update () float elapsedTime = GetElapsedFrameTime (); gGameMode.Update (ElapsedTime); gGameMode.Render (); 

In het voorbeeld maken we alle vereiste staten, voeg ze toe aan de statemachine en stel de startstatus in op het hoofdmenu. Als we deze code hebben uitgevoerd, is de MainMenuState zou eerst worden weergegeven en bijgewerkt. Dit vertegenwoordigt het menu dat u in de meeste spellen ziet wanneer u voor het eerst opstart, met opties zoals Start het spel en Spel laden.

Wanneer een gebruiker selecteert Start het spel, de MainMenuState roept zoiets als gGameMode.Change ("localmap", "map_001") en de LocalMapState wordt de nieuwe huidige staat. Deze status wordt dan bijgewerkt en de kaart wordt weergegeven, zodat de speler het spel kan gaan verkennen.

Het onderstaande diagram toont een visualisatie van een statusmachine die beweegt tussen de WorldMapState en BattleState. In een spel zou dit hetzelfde zijn als een speler die de wereld ronddoolt, wordt aangevallen door monsters, de gevechtsmodus ingaat en vervolgens terugkeert naar de kaart.

Laten we even kijken naar de statusinterface en een EmptyState klasse die het implementeert:

openbare interface IState openbare virtuele ongeldige update (float verstreken tijd); openbare virtuele leegte Render (); openbare virtuele ongeldige OnEnter (); openbare virtuele ongeldig OnExit ();  public EmptyState: IState public void Update (float elapsedTime) // Niets om te updaten in de lege staat.  public void Render () // Niets om te renderen in de lege staat public void OnEnter () // Geen actie die moet worden ondernomen wanneer de staat wordt ingevoerd public void OnExit () // Geen actie te ondernemen wanneer de staat is afgesloten

De interface Ik zeg vereist dat elke staat vier methoden heeft voordat deze kan worden gebruikt als een staat in de statusmachine: Bijwerken(), Render (), OnEnter () en OnExit ().

Bijwerken() en Render () worden elk frame genoemd voor de huidige actieve status; OnEnter () en OnExit () worden aangeroepen als de status wordt gewijzigd. Afgezien daarvan is het allemaal vrij eenvoudig. Nu je dit weet, kun je allerlei soorten toestanden creëren voor alle verschillende delen van je spel.

Dat is de basismodelmachine. Het is handig voor veel situaties, maar als het gaat om spelmodi kunnen we het verbeteren! Met het huidige systeem kan een veranderende status veel overhead hebben - soms bij het overschakelen naar een BattleState we willen de WorldState, voer de strijd uit en keer dan terug naar de WorldState in de exacte opstelling was het voor de strijd. Dit soort bewerking kan onhandig zijn met behulp van de standaard state-machine die we hebben beschreven. Een betere oplossing zou zijn om a te gebruiken stack van staten.

Game Logic makkelijker maken met een state-stack

We kunnen de standaard toestandsmachine naar een stapel toestanden schakelen, zoals het onderstaande diagram laat zien. Bijvoorbeeld de MainMenuState wordt eerst op de stapel gedrukt, aan het begin van het spel. Wanneer we een nieuw spel beginnen, is de LocalMapState wordt daar bovenop geduwd. Op dit punt de MainMenuState wordt niet langer weergegeven of bijgewerkt, maar wacht rond, klaar voor ons om naar terug te keren.

Vervolgens, als we een gevecht beginnen, de BattleState wordt op de bovenkant geduwd; wanneer het gevecht eindigt, is het van de stapel gesprongen en kunnen we verder gaan op de kaart precies waar we gebleven waren. Als we dan in de game sterven LocalMapState is eraf en we keren terug naar MainMenuState.

Het onderstaande diagram geeft een visualisatie van een statusstack met de InGameMenuState op de stapel worden geduwd en vervolgens eraf gehaald.

Nu hebben we een idee hoe de stapel werkt, laten we eens kijken naar een code om het te implementeren:

openbare klasse StateStack Map mStates = nieuwe kaart(); Lijst mStack = Lijst(); public void Update (float elapsedTime) IState top = mStack.Top () top.Update (elapsedTime) public void Render () IState top = mStack.Top () top.Render () public void Push (String name) IState state = mStates [naam]; mStack.Push (state);  openbare IState Pop () return mStack.Pop (); 

Deze bovenstaande statusstackcode heeft geen foutcontrole en is vrij eenvoudig. Staten kunnen op de stapel worden geduwd met behulp van de Duwen() bel en knalde met een Knal() bellen en de status helemaal bovenaan de stapel is de status die is bijgewerkt en weergegeven.

Het gebruik van een stack-gebaseerde aanpak is goed voor menu's en met een kleine aanpassing kan het ook worden gebruikt voor dialoogvensters en meldingen. Als je avontuurlijk bent, kun je beide combineren en een state-machine hebben die ook stacks ondersteunt.

Gebruik makend van statemachine, StateStack, of een combinatie van beide creëert een uitstekende structuur om je RPG op te bouwen.

Volgende acties:

  1. Implementeer de machinecode van de staat in uw favoriete programmeertaal.
  2. Maak een MenuMenuState en gamestate erven van Ik zeg.
  3. Stel de status van het hoofdmenu in als de beginstatus.
  4. Hebben beide staten verschillende afbeeldingen.
  5. Als u op een knop drukt, wijzigt de status van het hoofdmenu naar de spelsituatie.


Kaarten


Kaarten beschrijven de wereld; woestijnen, ruimteschepen en jungles kunnen allemaal worden weergegeven met behulp van een tilemap. Een tilemap is een manier om een ​​beperkt aantal kleine afbeeldingen te gebruiken om een ​​groter beeld op te bouwen. Het onderstaande diagram laat zien hoe het werkt:

Het bovenstaande diagram bestaat uit drie delen: het tegelspalet, een visualisatie van hoe de tilemap is geconstrueerd en de uiteindelijke kaart die op het scherm is weergegeven.

Het tegelpalet is een verzameling van alle tegels die zijn gebruikt om een ​​kaart te maken. Elke tegel in het palet wordt uniek geïdentificeerd door een geheel getal. Tegel nummer 1 is bijvoorbeeld gras; let op de plaatsen waar het op de tilemap-visualisatie wordt gebruikt.

Een tilemap is slechts een reeks getallen, elk nummer gerelateerd aan een tegel in het palet. Als we een kaart vol met gras wilden maken, konden we gewoon een grote reeks met nummer 1 hebben, en toen we die tegels weergaven, zagen we een kaart van gras opgebouwd uit vele kleine grastegels. Het tegelpalet wordt meestal geladen als één grote structuur met veel kleinere tegels, maar elke vermelding in het palet kan net zo goed zijn eigen grafische bestand zijn.

Tip: Waarom niet een array van arrays gebruiken om de tilemap te vertegenwoordigen? De eerste array kan een reeks rijen met tegels vertegenwoordigen.

De reden dat we dit niet doen, is alleen voor eenvoud en efficiëntie. Als u een array van gehele getallen hebt, is dat één doorlopend blok geheugen. Als u een array met arrays hebt, is dat één blok geheugen voor de eerste array met verwijzingen, waarbij elke aanwijzer wijst naar een rij tegels. Deze indirectie kan dingen vertragen - en aangezien we de kaart elk frame tekenen, des te sneller hoe beter!

Laten we eens kijken naar een code voor het beschrijven van een tegelkaart:

// // Neemt een structuurkaart van meerdere tegels en verdeelt deze in // afzonderlijke afbeeldingen van 32 x 32. // De laatste reeks ziet er als volgt uit: // gTilePalette [1] = afbeelding // onze eerste grastegel // gTilePalette [2] = Afbeelding // Tweede variant grassoort // ... // gTilePalette [15] = Afbeelding // Rots- en grastegel // Array gTilePalette = SliceTexture ("grass_tiles.png", 32, 32) gMap1Width = 10 gMap1Height = 10 Array gMap1Layer1 = new Array () [2, 2, 7, 3, 11, 11, 11, 12, 2, 2, 1, 1, 10, 11, 11, 4, 11, 12, 2, 2, 2, 1, 13, 5, 11, 11, 11, 4, 8, 2, 1, 2, 1, 10, 11, 11, 11, 11, 11, 9, 10, 11, 12, 13, 5, 11, 11, 11, 11, 4, 13, 14, 15, 1, 10, 11, 11, 11, 11, 6, 2, 2, 2, 2, 13, 14, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 11, 11, 11, 11, 2, 2, 2, 2, 2, 2, 5, 11, 11, 11, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,];

Vergelijk de bovenstaande code met het diagram en het is vrij duidelijk hoe een tilemap is opgebouwd uit een kleine reeks tegels. Zodra een kaart als volgt is beschreven, kunnen we een eenvoudige renderfunctie schrijven om deze op het scherm te tekenen. De exacte details van de functie veranderen afhankelijk van de instellingen van de viewport en de tekenfuncties. Onze renderfunctie wordt hieronder getoond.

static int TilePixelSize = 32; // Tekent een tilemap van linksboven, op pixelpositie x, y // x, y - de pixelpositie van de kaart wordt weergegeven op // map - de kaart om te renderen // breedte - de breedte van de kaart in tegels public void RenderMap (int x, int y, Array-kaart, int mapWidth) // Begin met het indexeren van de meest linkse tegel int int tileColumn = 1; int tileRow = 1; for (int i = 1; map.Count (); i ++) // Minus 1 zodat de eerste tegel tekent op 0, 0 int pixelPosX = x + (tileColumn - 1) * TilePixelSize; int pixelPosY = y + (tileRow - 1) * TilePixelSize; RenderImage (x, y, gTilePalette [gMap1Layer1 [i]]); // Doorgaan naar de volgende tegel tegelKolom + = 1; if (tileColumn> mapWidth) tileColumn = 1; tileRow + = 1;  - Hoe het wordt gebruikt in de hoofdupdate lus public void Update () // Trek feitelijk een kaart op het scherm RenderMap (0, 0, gMap1Layer1, gMap1Width)

De kaart die we tot nu toe hebben gebruikt, is vrij eenvoudig; de meeste JRPG's gebruiken meerdere lagen tilemaps om interessantere scènes te maken. Het onderstaande diagram toont onze eerste kaart, met nog drie lagen toegevoegd, resulterend in een veel aangenamere kaart.

Zoals we eerder zagen, is elke tilemap slechts een array van getallen en daarom kan een volledige gelaagde kaart worden gemaakt op basis van een array van die arrays. Natuurlijk is het renderen van de tilemap eigenlijk slechts de eerste stap in het toevoegen van verkenning aan je spel; kaarten moeten ook informatie hebben over botsingen, ondersteuning voor bewegende entiteiten en basisinteractiviteit triggers.

Een trigger is een stuk code dat alleen wordt geactiveerd wanneer de speler het 'activeert' door een actie uit te voeren. Er zijn veel acties die een trigger kan herkennen. Het verplaatsen van het personage van de speler naar een tegel kan bijvoorbeeld een actie activeren - dit gebeurt vaak bij het verplaatsen naar een deuropening, teleporter of de randtegel van de kaart. Triggers kunnen op deze tegels worden geplaatst om het personage te teleporteren naar een binnenkaart, een wereldkaart of een verwante lokale kaart.

Een andere trigger kan afhankelijk zijn van de "gebruik" -knop die wordt ingedrukt. Als de speler bijvoorbeeld naar een bord gaat en op 'gebruik' drukt, wordt een trigger geactiveerd en wordt een dialoogvenster weergegeven met de tekst van het bord. Triggers worden overal gebruikt om kaarten samen te stellen en interactiviteit te bieden.

JRPG's hebben vaak heel wat vrij gedetailleerde en gecompliceerde kaarten, dus ik raad aan dat je ze niet met de hand probeert te maken, het is een veel beter idee om een ​​tilemap-editor te gebruiken. U kunt een van de uitstekende gratis bestaande oplossingen gebruiken of zelf een oplossing maken. Als je een bestaand hulpmiddel wilt proberen, raad ik je aan om Tiled te bekijken, het hulpprogramma dat ik heb gebruikt om deze voorbeeldkaarten te maken.

gerelateerde berichten
  • Inleiding tot betegeld
  • Parsing Tiled TMX Format-kaarten in uw eigen game-engine
  • Kennismaken met Ogmo Editor

Volgende acties:

  1. Word betegeld.
  2. Krijg wat tegels van opengameart.org.
  3. Maak een kaart en laad hem in je spel.
  4. Voeg een personage toe.
  5. Verplaats het personage van tegel naar tegel.
  6. Laat het personage vloeiend van tegel naar tegel bewegen.
  7. Voeg botsingsdetectie toe (u kunt een nieuwe laag gebruiken om collisiegegevens op te slaan).
  8. Voeg een eenvoudige trigger toe om kaarten te verwisselen.
  9. Voeg een trigger toe om tekens te lezen - overweeg het gebruik van de statusstapel waarover we eerder hebben gesproken om het dialoogvenster weer te geven.
  10. Maak een hoofdmenu met een "Start Game" -optie en een lokale kaartstatus en koppel ze samen.
  11. Ontwerp enkele kaarten, voeg wat NPC's toe, probeer een eenvoudige ophaaltocht - laat je fantasie de vrije loop!


gevecht

Eindelijk, op de gevechten! Wat heb je aan een JRPG zonder gevecht? Vechten is waar veel games kiezen om te innoveren, nieuwe vaardigheidssystemen, nieuwe gevechtsstructuur of verschillende spell-systemen introduceren - er is veel variatie.

De meeste vechtsystemen maken gebruik van een turn-based structuur waarbij slechts één strijder een actie tegelijk mag uitvoeren. De allereerste turn-based gevechtssystemen waren eenvoudig, waarbij elke entiteit een ommekeer kreeg: de beurt van de speler, de beurt van de vijand, de beurt van de speler, de beurt van de vijand, enzovoort. Dit maakte snel plaats voor meer ingewikkelde systemen die meer speelruimte bieden voor tactiek en strategie.

We gaan het bekijken Active-Time op basis van gevechtsstelsels, waarbij strijders niet noodzakelijk evenveel beurten krijgen. Snellere entiteiten kunnen meer beurten krijgen en het type actie dat wordt ondernomen, heeft ook invloed op hoe lang een afslag duurt. Bijvoorbeeld, een krijger die een dolk insnijdt kan 20 seconden duren, maar een wizard die een monster oproept, kan twee minuten duren.


De bovenstaande schermafbeelding toont de gevechtsmodus in een typische JRPG. Door spelers bestuurde karakters staan ​​rechts, vijandige karakters links en een tekstvak onderaan geeft informatie over de strijders.

Aan het begin van het gevecht worden de sprites van het monster en de speler aan de scène toegevoegd en dan is er een beslissing over de volgorde waarin de entiteiten hun beurt nemen. Deze beslissing kan gedeeltelijk afhangen van hoe het gevecht werd gelanceerd: als de speler in een hinderlaag werd gelokt, zullen de monsters als eerste aanvallen, anders is het meestal gebaseerd op een van de entiteitsstatistieken zoals snelheid.

Alles wat de speler of monsters doen is een actie: aanvallen is een actie, magie gebruiken is een actie, en zelfs beslissen welke actie de volgende actie is, is een actie! De volgorde van acties kan het best worden gevolgd via een wachtrij. De actie bovenaan is de actie die vervolgens zal plaatsvinden, tenzij er geen snellere actie wordt ondernomen. Elke actie zal een aftelling hebben die afneemt naarmate elk frame passeert.

De gevechtsstroom wordt bestuurd met behulp van een toestandsmachine met twee toestanden; één staat om de acties aan te vinken en een andere staat om de topactie uit te voeren wanneer de tijd daar is. Zoals altijd is de beste manier om iets te begrijpen, te kijken naar de code. Het volgende voorbeeld implementeert een basisgevechtstatus met een actiewachtrij:

klasse BattleState: IState Lijst mActions = Lijst(); Lijst mEntities = Lijst(); StateMachine mBattleStates = new StateMachine (); public static bool SortByTime (Actie a, Actie b) return a.TimeRemaining ()> b.TimeRemaining () public BattleState () mBattleStates.Add ("tick", nieuwe BattleTick (mBattleStates, mActions)); mBattleStates.Add ("execute", nieuwe BattleExecute (mBattleStates, mActions));  public void OnEnter (var params) mBattleStates.Change ("tick"); // // Krijg een beslissingsactie voor elke entiteit in de actiewachtrij // Sorteer het zo dat de snelste acties bovenaan staan ​​// mEntities = params.entities; foreach (Entity e in mNntities) if (e.playerControlled) PlayerDecide action = nieuwe PlayerDecide (e, e.Speed ​​()); mActions.Add (actie);  else AIDecide action = new AIDecide (e, e.Speed ​​()); mActions.Add (actie);  Sorteren (mActions, BattleState :: SortByTime);  public void Update (float elapsedTime) mBattleStates.Update (elapsedTime);  public void Render () // Teken de scène, gui, personages, animaties enz. mBattleState.Render ();  openbare ongeldig OnExit () 

De bovenstaande code demonstreert de besturing van de gevechtsmodusstroom met behulp van een eenvoudige toestandsmachine en een reeks acties. Om te beginnen hebben alle bij de strijd betrokken entiteiten een besluit-actie toegevoegd aan de wachtrij.

Een beslissingsactie voor de speler zal een menu openen met de RPG-stabiele opties Aanval, Magie, en Item; Zodra de speler een actie besluit, wordt de beslissingsactie uit de wachtrij verwijderd en wordt de nieuw gekozen actie toegevoegd.

Een beslissingsactie voor de AI inspecteert de scène en beslist wat te doen (met behulp van bijvoorbeeld een gedragsboom, beslissingsboom of vergelijkbare techniek) en vervolgens verwijdert het ook de beslissingsactie en voegt de nieuwe actie toe aan de wachtrij..

De BattleTick klasse bepaalt de update van de acties, zoals hieronder weergegeven:

klasse BattleTick: IState StateMachine mStateMachine; Lijst mActions; openbare BattleTick (StateMachine state Machine, Lijst acties): mStateMachine (stateMachine), mActions (actie)  // Er kunnen dingen gebeuren in deze functies, maar niets waarin we geïnteresseerd zijn. public void OnEnter ()  public void OnExit ()  public void Render ()   public void Update (float elapsedTime) foreach (Action a in mActions) a.Update (elapsedTime);  if (mActions.Top (). IsReady ()) Action top = mActions.Pop (); mStateMachine: Wijzigen ("uitvoeren", bovenaan); 

BattleTick is een substatus van de BattleMode-status en deze tikt gewoon door totdat het aftellen van de bovenste actie nul is. Vervolgens worden de bovenste actie uit de wachtrij geplaatst en worden de wijzigingen in de wachtrij geplaatst uitvoeren staat.


Het diagram hierboven toont een actiewachtrij aan het begin van een gevecht. Niemand heeft nog actie ondernomen en iedereen is geordend op zijn tijd om een ​​beslissing te nemen.

De Giant Plant heeft een aftelling van 0, dus bij de volgende tik wordt het uitgevoerd AIDecide actie. In dit geval de AIDecide actie resulteert in het monster dat besluit aan te vallen. De aanvalsactie is bijna onmiddellijk en wordt als de tweede actie weer in de wachtrij geplaatst.

Op de volgende iteratie van BattleTick, de speler zal kiezen welke actie zijn dwerg "Mark" moet nemen, wat de rij opnieuw zal veranderen. De volgende iteratie van BattleTick daarna zal de plant een van de dwergen aanvallen. De aanvalsactie wordt uit de wachtrij verwijderd en doorgegeven aan de BattleExecute staat, en het zal de plant doen aanvallen evenals alle nodige gevechtsberekeningen uitvoeren.

Zodra de aanval van het monster is voltooid, is er nog een AIDecide actie wordt toegevoegd aan de wachtrij voor het monster. De BattleState zal zo doorgaan tot het einde van de strijd.

Als een entiteit sterft tijdens het gevecht, moeten al zijn acties uit de rij worden verwijderd - we willen niet dat dode monsters plotseling reanimeren en aanvallen tijdens het spel (tenzij we opzettelijk zombies maken of een soort ondoden!).

De actiewa