Voor veel beginnende indie-ontwikkelaars wordt code-optimalisatie bijna een tweede gedachte. Het wordt gepasseerd ten gunste van motoren of kaders, of kan worden beschouwd als een meer 'geavanceerde' techniek buiten hun bereik. Er zijn echter optimalisatiemethoden die op meer basale manieren kunnen worden gebruikt, waardoor uw code efficiënter en op meer systemen kan werken. Laten we een paar basiscode-optimalisaties bekijken om u op weg te helpen.
Het is niet ongebruikelijk voor indie-ontwikkelaars om de optimalisatiemethoden van grotere bedrijven na te streven. Het hoeft niet per se slecht te zijn, maar het is een goede manier om jezelf gek te maken als je je game wilt optimaliseren tot voorbij het punt van nuttig rendement. Een slimme tactiek om bij te houden hoe effectief uw optimalisaties zijn, is om uw doelgroepdemografie te segmenteren en te kijken naar de soorten specificaties van hun machines. Benchmarken van je spel tegen de computers en consoles die je potentiële spelers gebruiken, zal helpen om een balans te houden tussen optimalisatie en geestelijke gezondheid.
Afgezien daarvan zijn er nogal wat optimalisaties die bijna altijd kunnen worden gebruikt om je spel beter te laten presteren. De meeste van deze zijn systeemagnostisch (en sommige engines en frameworks houden daar al rekening mee), dus ik zal enkele pseudocodevoorbeelden opnemen om je op de juiste manier uit te schakelen. Laten we kijken.
Vaak afgehandeld door motoren en soms zelfs GPU's zelf, is het minimaliseren van de hoeveelheid verwerkingskracht die van objecten op het scherm uitgaat uiterst belangrijk. Voor uw eigen builds is het een goed idee om te beginnen met het scheiden van uw objecten in twee "lagen" in essentie: de eerste is de grafische weergave ervan en de tweede zijn de gegevens en functies (zoals de locatie). Wanneer een object buiten het scherm valt, hoeven we niet langer de resources te besteden die het weergeven en moeten we in plaats daarvan kiezen voor tracking. Door het volgen van dingen, zoals locatie en staat, met variabelen, worden de benodigde bronnen teruggebracht tot slechts een fractie van de initiële kosten.
Voor games met een groot aantal objecten of objecten die zwaar zijn voor de gegevens, kan het zelfs nuttig zijn om nog een stap verder te gaan door afzonderlijke updateroutines te maken, een instelling in te stellen voor bijwerken terwijl het object op het scherm staat en de andere voor schermvullende weergave . Door op deze manier een meer geavanceerde scheiding in te stellen, kan het systeem voorkomen dat een aantal animaties, algoritmen en andere updates moet worden uitgevoerd die onnodig kunnen zijn wanneer het object is verborgen.
Hier is een pseudocode-voorbeeld van een objectklasse met behulp van vlaggen en locatievebeperkingen:
Object NPC Int locationX, locationY; // huidige locatie van het object op een tweed vlak Function drawObject () // een functie om uw object te tekenen, te worden aangeroepen in uw scherm update lus // functie die controleert of het object zich binnen de huidige weergavepoort bevindt Functie pollObjectDraw (array currentViewport [minX, minY, maxX, maxY]) // als deze zich binnen de viewport bevindt, retourneer dan dat deze kan worden getekend If (this.within (currentViewport)) Return true; Anders Return false;
Hoewel dit voorbeeld is vereenvoudigd, kunnen we pollen of het object zelfs wordt weergegeven voordat het wordt getekend, waardoor we een redelijk vereenvoudigde functie kunnen uitvoeren in plaats van een volledige tekenreeks. Om functies naast grafische oproepen te scheiden, kan het nodig zijn om een extra buffer te gebruiken, bijvoorbeeld een functie die alles bevat wat een speler mogelijk binnenkort kan zien, in plaats van wat ze momenteel alleen kunnen zien.
Afhankelijk van de engine of het framework dat u gebruikt, kunt u gewoonlijk objecten bijwerken op elk frame of "aankruisen". Als je dat doet, kan een processor behoorlijk snel worden belast, dus om die last te verlichten, willen we waar mogelijk oproepen op elk frame verminderen.
Het eerste dat we willen scheiden, is het renderen van functies. Deze oproepen zijn doorgaans zeer intensief, dus het integreren van een oproep die ons kan vertellen wanneer de visuele eigenschappen van een object zijn gewijzigd, kan het renderen drastisch verminderen.
Om nog een stap verder te gaan, kunnen we een tijdelijk scherm voor onze objecten gebruiken. Door de voorwerpen rechtstreeks naar deze tijdelijke container te laten trekken, kunnen we ervoor zorgen dat ze alleen worden getekend wanneer dat nodig is.
Vergelijkbaar met de eerste optimalisatie hierboven vermeld, introduceert de start-iteratie van onze code een eenvoudige polling:
Object NPC boolean hasChanged; // stel deze vlag in op true wanneer een wijziging wordt aangebracht in de functie object // die terugkomt of Functie pollObjectChanged (return hasChanged ();
Tijdens elk frame kunnen we nu, in plaats van een aantal functies uit te voeren, zien of het zelfs nodig is. Hoewel deze implementatie ook eenvoudig is, kan deze al enorme winst opleveren in de efficiëntie van je spel, vooral als het gaat om statische items en langzaam bijwerkende objecten zoals een HUD.
Om dit verder te brengen in je eigen spel, kan het breken van de vlag in meerdere kleinere componenten nuttig zijn voor het segmenteren van functionaliteit. U kunt bijvoorbeeld vlaggen hebben voor een gegevenswijziging en een grafische wijziging vindt afzonderlijk plaats.
Dit is een optimalisatie die al sinds de begintijd van gamingsystemen wordt gebruikt. Het bepalen van de compromissen tussen live berekeningen en opzoeken van waarden kan de verwerkingstijden drastisch verminderen. Een bekend gebruik in de gamegeschiedenis is het opslaan van de waarden van trigonometriefuncties in tabellen, omdat het in de meeste gevallen efficiënter was om een grote tafel op te slaan en eruit te halen dan de berekeningen direct uit te voeren en extra druk uit te oefenen op de CPU.
In moderne computers hoeven we zelden de keuze te maken tussen het opslaan van resultaten en het uitvoeren van een algoritme. Er zijn echter nog steeds situaties waarin het gebruik van de gebruikte bronnen kan worden beperkt, waardoor andere functies kunnen worden opgenomen zonder een systeem te overbelasten.
Een eenvoudige manier om met de implementatie hiervan te beginnen, is door veelgebruikte berekeningen of stukjes berekeningen in uw spel te identificeren: hoe groter de berekening, hoe beter. Het eenmalig uitvoeren van steeds terugkerende stukjes algoritme en het opslaan ervan kan vaak aanzienlijke hoeveelheden verwerkingskracht besparen. Zelfs het isoleren van deze onderdelen in specifieke spellussen kan helpen om de prestaties te optimaliseren.
Bijvoorbeeld, in veel top-down shooters zijn er vaak grote groepen vijanden die hetzelfde gedrag vertonen. Als er 20 vijanden zijn, die elk langs een boog bewegen, in plaats van elke beweging afzonderlijk te berekenen, is het efficiënter om de resultaten van het algoritme op te slaan. Hierdoor kan het worden aangepast op basis van de startpositie van elke vijand.
Om te bepalen of deze methode nuttig is voor je spel, kun je benchmarking gebruiken om het verschil in gebruikte bronnen tussen live berekening en gegevensopslag te vergelijken.
Hoewel dit meer speelt in het gebruik van slapende bronnen, met zorgvuldige gedachten voor uw objecten en algoritmen, kunnen we taken stapelen op een manier die de efficiëntie van onze code verhoogt.
Om de gevoeligheid voor luiheid in uw eigen software te gaan gebruiken, moet u eerst scheiden welke taken in uw spel niet tijdkritisch zijn of kunnen worden berekend voordat ze nodig zijn. Het eerste gebied dat moet zoeken naar code die in deze categorie valt, is functionaliteit die strikt gerelateerd is aan de atmosfeer van het spel. Weersystemen die geen interactie hebben met geografie, visuele achtergrondeffecten en achtergrondaudio kunnen gemakkelijk in de niet-actieve berekening worden geplaatst.
Behalve items die strikt atmosferisch zijn, zijn gegarandeerde berekeningen een ander type berekening dat in niet-actieve ruimten kan worden geplaatst. Berekeningen van kunstmatige intelligentie die ongeacht de interactie met de speler zullen plaatsvinden (of omdat ze geen rekening houden met de speler, of waarschijnlijk nog geen spelersinteractie vereisen) kunnen efficiënter worden gemaakt, evenals berekende bewegingen, zoals scripted evenementen.
Het creëren van een systeem dat gebruik maakt van nietsdoen doet zelfs meer dan het toestaan van hogere efficiëntie - het kan worden gebruikt voor het schalen van "eye candy". Bijvoorbeeld, op een low-end tuig, misschien een speler ervaart gewoon een vanille-versie van de gameplay. Als ons systeem echter niet-werkende frames detecteert, kunnen we dit gebruiken om extra deeltjes, grafische gebeurtenissen en andere atmosferische aanpassingen toe te voegen om de game iets meer zwier te geven.
Om dit te implementeren, gebruikt u de functionaliteit die beschikbaar is in uw favoriete engine, framework of taal om te meten hoeveel van de CPU wordt gebruikt. Stel binnen uw code vlaggen in die het gemakkelijk maken om te controleren hoeveel "extra" verwerkingskracht beschikbaar is, en stel vervolgens uw subsystemen in om naar deze vlag te kijken en dienovereenkomstig te handelen.
Door deze methoden te combineren, is het mogelijk om uw code aanzienlijk efficiënter te maken. Met die efficiëntie komt de mogelijkheid om meer functies toe te voegen, op meer systemen te draaien en een meer solide ervaring voor de spelers te garanderen.
Hebt u makkelijk te implementeren code-optimalisaties die u regelmatig gebruikt? Laat het me weten over hen!