De prijs voor meest sexy titel gaan we niet winnen. Maar naar de aloude traditie van de klassieke tekst-gebaseerde adventure-games (zie hier ) zullen we een eenvoudig object georiënteerd framework maken dat ons toelaat snel onze eigen games te maken. De nadruk van dit artikel ligt daarbij niet tot het creëren van een perfecte imitatie, maar wel om aan te tonen dat met een beetje object georiënteerde inzichten we tot zeer propere, herbruikbare én onderhoudbare code kunnen komen.
We maken uiteraard het spel in een Console-applicatie.
Main
We willen de Main() methode van Program.cs zo leeg mogelijk laten. Daarom zullen we de meeste functionaliteit verpakken in een klasse GameManager. Het enige dat we dan nog hoeven te doen in onze main is een loop starten die steeds 3 zaken zal doen:
Huidige locatie beschrijven
Aan gebruiker tonen welke acties hij kan uitvoeren
Gewenste actie van gebruiker verwerken en uitvoeren
In code behelst dit:
staticvoidMain(string[] args){Console.WriteLine("Welkom bij AP Adventure. Een avontuur voor moedige en minder moedige studenten. Ben je er klaar voor?");GameManager theGame=newGameManager(); //Start gameloopwhile(!theGame.Exit) { //Beschrijf kamertheGame.DescribeLocation(); //Toon actiestheGame.ToonActies(); //Lees actie uittheGame.VerwerkActie(); }}
Een bool property Exit binnen het GameManager object zal ons toelaten om de loop te stoppen wanneer het spel wordt beëindigd.
GameObject
Doorheen de verschillende locaties zullen elementen te vinden zijn. We beschrijven deze als GameObjects:
Waarbij Directions een eigen gemaakt enum-type is:
enumDirections{ North, South, West, East}
Van locatie veranderen
Binnen de locatie klasse voegen we een methode toe die de GameManager kan gebruiken om te weten te komen naar welke locatie de gebruiker gaat. De methode aanvaardt een Direction (i.e. de richting waarin de gebruiker wenst te gaan) en zal een referentie naar het location-object teruggeven waarnaar de gebruiker zal bewegen. Indien de richting waarin hij wenst te bewegen niet geldig is dan tonen we dit op het scherm:
publicLocationGetLocationOnMove(Directions direction){foreach (var exit in Exits) {if (exit.ExitDirection== direction) {returnexit.GoesToLocation; } }Console.WriteLine("Dat is geen geldige richting");returnthis;}
Wanneer dus een exit wordt gevonden in de Exits lijst die voldoet aan de meegegeven Direction dan geven we een referentie terug naar de bijhorende locatie (GoesToLocation). Wordt er geen exit gevonden en bereiken we dus het einde van de foreach lus dan verschijnt de tekst op het scherm en geven we een referentie terug naar de huidige locatie.
GameObjects als vereisten om exit te gebruiken
Stel nu dat we soms willen dat een bepaalde locatie pas bereikt kan worden indien de gebruiker reeds een bepaald GameObject in zijn bezit heeft. Hiervoor moeten we 2 zaken aanpassen:
We beschrijven in de Exit klasse welk object(en) nodig zijn om deze exit te mogen gebruiken
We controleren of de speler het GameObject heeft wanneer deze naar een nieuwe locatie wil gaan mbv de GetLocationOnMove() methode.
In deze ietwat knullige code tellen we dus of de speler alle GameObjecten in z’n inventory heeft (playerInventory) nodig om deze exit te gebruiken.
Deze methode TestPassCondition gebruiken we nu in de GetLocationOnMove()-methode in de Location klasse om te bepalen of de exit mag gebruikt worden. De methode wordt dan:
publicLocationGetLocationOnMove(Directions direction,List<GameObjects> playerInventory ){foreach (var exit in Exits) {if (exit.ExitDirection== direction) {if(exit.TestPassCondition(playerInventory))returnexit.GoesToLocation;else {Console.WriteLine("Je kan hier niet langs (je hebt niet alle vereiste items).");returnthis; } } }Console.WriteLine("Dat is geen geldige richting");returnthis;}
GameManager
Rest ons nu enkel nog de GameManager klasse te maken. Ruw gezien is deze als volgt:
De 3 publieke methoden DescribeLocation,VerwerkActie en ToonActies
Een instantievariabelen currentLocation die een referentie bijhoudt naar de huidige locatie van de speler
3 lijsten met daarin de objecten die de speler heeft (playerInventory), alle objecten in het spel (Objects) en alle locaties in het spel (GameLocation)
Een InitGame() methode waarin we alle gameobjecten, exits en locaties zullen aanmaken bij aanvang van het spel
Een bool Exit zodat de externe gameloop weet wanneer het spel gedaan is
We laten de speler dus toe door n,o,w,z in te typen dat gecontroleerd wordt naar welke nieuwe locatie zal gegaan worden. We passen hierbij de currentLocation property van de GameManager aan naar de, al dan niet nieuwe, locatie.
ToonActies()
publicvoidToonActies(){Console.WriteLine("Mogelijke acties: (typ bijvoorbeeld n indien u naar het noorden wil)");Console.WriteLine("n= noord");Console.WriteLine("o= oost");Console.WriteLine("z= zuid");Console.WriteLine("w= west");Console.WriteLine("e=exit");}
InitGame()
In deze methode definiëren nu de volledige spelinhoud. Wil je dus bijvoorbeeld dit spel uitbreiden met extra kamers en objecten, dan doe je dat in deze methode. Ter illustratie tonen we eerst hoe we 2 locaties aanmaken en deze aan elkaar hangen mbv de Exits (waarbij kamer één zich ten zuiden van kamer 2 bevindt)
privatevoidInitGame(){ //Maak LocatiesLocation l1 =newLocation() { Title ="De poort", Description ="Je staat voor een grote grijze poort die op een kier staat. Rondom je is prikkeldraad, je kan enkel naar het noorden, door de poort gaan. " };Location l2 =newLocation() { Title ="Receptie", Description ="De receptie....veel blijft er niet meer over van wat eens een bruisende omgeving was. Hier en daar zie je skeletten van , waarschijnlijk, enkele studenten. Een grote poort staat op een kier naar het zuiden. Je ziet twee deuren aan de westelijke en noordelijke zijde." }; //Place exitsl1.Exits.Add(newExit() { ExitDirection =Directions.North, GoesToLocation = l2 });l2.Exits.Add(newExit() { ExitDirection =Directions.South, GoesToLocation = l1 }); //Voeg locatie toeGameLocation.Add(l1);GameLocation.Add(l2); currentLocation = l1;}
Vergeet niet op het einde de 2 kamers toe te voegen aan de GameLocation lijst van de GameManager, alsook in te stellen wat de beginkamer is.
GameInit met GameObject als conditie om kamer in te kunnen
Stel dat we even later in een kamer een sleutel plaatsen die als conditie dient om een andere kamer te kunnen openen. We schrijven dan in de GameInit() methode:
Location l6 =newLocation() { Title ="Gang", Description ="Een brede gang waar makkelijk 5 mensen schouder aan schouder door kunnen. Zowel in het oosten als het westen zie je een deur." };Location l7 =newLocation() { Title ="Computerruimte", Description ="Eindelijk; je hebt het gehaald. De plek waar iedereen naar toe wil: het computerlabo!" }; //Place objects in roomsGameObjects keytol7 =newGameObjects() { Description ="Verroest en groot", Name ="Sleutel" };l5.ObjectsInRoom.Add(keytol7); //...l6.Exits.Add(newExit() { ExitDirection =Directions.West, GoesToLocation = l4 , NeedsObject=newList<GameObjects>(){keytol7}});l6.Exits.Add(newExit() { ExitDirection =Directions.East, GoesToLocation = l7 }); //Voeg locatie toe //..GameLocation.Add(l6);GameLocation.Add(l7);
Een volledige GameInit ter illustratie
We hebben nu de belangrijkste onderdelen geschreven. We tonen daarom een iets uitgebreider spel, demo zeg maar, waar in we alles gecombineerd in actie zien:
privatevoidInitGame(){ //Maak LocatiesLocation l1 =newLocation() { Title ="De poort", Description ="Je staat voor een grote grijze poort die op een kier staat. Rondom je is prikkeldraad, je kan enkel naar het noorden, door de poort gaan. " };Location l2 =newLocation() { Title ="Receptie", Description ="De receptie....veel blijft er niet meer over van wat eens een bruisende omgeving was. Hier en daar zie je skeletten van , waarschijnlijk, enkele studenten. Een grote poort staat op een kier naar het zuiden. Je ziet twee deuren aan de westelijke en noordelijke zijde." };Location l3 =newLocation() { Title ="Koffieruime", Description ="Je staat in de koffieruimte achter de receptie. Menig pralinetje is hier vroeger met veel gusto opgesmikkeld. Een lege pralinedoos is het enige bewijs dat het hier ooit gezellig was. Een deur is de enige uitgang uit deze kamer naar het oosten." };Location l4 =newLocation() { Title ="Tuin", Description ="Het is duidelijk herfst. Een kale boom en vele bruine bladeren op de grond...mistroosteriger kan eigenlijk niet. Je ziet een deur in het zuiden en in het westen en een grote klapdeur naar het noorden." };Location l5 =newLocation() { Title ="Cafetaria", Description ="Ooit was dit een bruisende locati: veel eten, geroezemoes en licht door de grote ruiten. Nu enkel stof en lege tafel. Enkel een klapdeur is zichtbaar naar het zuiden." };Location l6 =newLocation() { Title ="Gang", Description ="Een brede gang waar makkelijk 5 mensen schouder aan schouder door kunnen. Zowel in het oosten als het westen zie je een deur." };Location l7 =newLocation() { Title ="Computerruimte", Description ="Eindelijk; je hebt het gehaald. De plek waar iedereen naar toe wil: het computerlabo!" }; //Place objects in roomsGameObjects keytol7 =newGameObjects() { Description ="Verroest en groot", Name ="Sleutel" };l5.ObjectsInRoom.Add(keytol7); //Place exitsl1.Exits.Add(newExit() { ExitDirection =Directions.North, GoesToLocation = l2 });l2.Exits.Add(newExit() { ExitDirection =Directions.South, GoesToLocation = l1 });l2.Exits.Add(newExit() { ExitDirection =Directions.West, GoesToLocation = l3});l2.Exits.Add(newExit() { ExitDirection =Directions.North, GoesToLocation = l4 });l3.Exits.Add(newExit() { ExitDirection =Directions.East, GoesToLocation = l2 });l4.Exits.Add(newExit() { ExitDirection =Directions.South, GoesToLocation = l2 });l4.Exits.Add(newExit() { ExitDirection =Directions.West, GoesToLocation = l6 });l4.Exits.Add(newExit() { ExitDirection =Directions.North, GoesToLocation = l7 });l5.Exits.Add(newExit() { ExitDirection =Directions.South, GoesToLocation = l4 });l6.Exits.Add(newExit() { ExitDirection =Directions.West, GoesToLocation = l4 , NeedsObject=newList<GameObjects>(){keytol7}});l6.Exits.Add(newExit() { ExitDirection =Directions.East, GoesToLocation = l7 }); //needs key conditionl7.Exits.Add(newExit() { ExitDirection =Directions.East, GoesToLocation = l6 }); //Winning room //Voeg locatie toeGameLocation.Add(l1);GameLocation.Add(l2);GameLocation.Add(l3);GameLocation.Add(l4);GameLocation.Add(l5);GameLocation.Add(l6);GameLocation.Add(l7); currentLocation = l1;}
What’s missing? Veel!
Maar een eerste uitdaging zou kunnen zijn: hoe kunnen we de speler objecten van de grond laten oprapen en in de inventaris plaatsen? Dat raadsel laten we aan jou over over om op te lossen!