Spiele selbst entwickeln mit XNA, Teil 4: Optimierungstechniken

Was bisher geschah…

Teil 1 der Artikelreihe diente uns dazu, einen kurzen Überblick über das XNA Framework zu verschaffen. Das Ziel ist die Erstellung eines 2D Arena Shooters. Im Rahmen der Reihe werden wir aber auch immer wieder kleine Exkurse in die 3D Programmierung machen, um unser Wissen zu vertiefen.

In Teil 2 haben wir uns dann den Lebenszyklus eines XNA Games angesehen und uns mit Zufallsdatengenerierung beschäftigt. Mit dieser haben wir eine Fläche erzeugt und diese mit Hilfe des SpriteBatch gezeichnet.

Das Projekt “Fists of Fire” wurde danach in Teil 3 quasi offiziell gestartet und angelegt. Nach einem kurzen Exkurs in die mathematische Welt der Matrizen- und Vektorenrechnung, haben wir unsere erste Kamera implementiert. Zusätzliche Info gab´s noch zum Input Handling, der Content Pipeline und dem Szenenmanagement.

Ein paar Updates

Seit dem letzten Update ist zwar ein wenig Zeit vergangen, untätig sind wir aber nicht gewesen. In der Zwischenzeit hat es einige kleinere Änderungen und Refactorings am Code gegeben, die nachfolgend zusammengefasst sind. Wie immer bleibt einem das Studium des Codes natürlich nicht erspart, wenn man alles zu 100% verstehen möchte.

Die Ordnerstruktur wurde geändert und im Laufe der Refactorisierung geändert. Auch die Logikaufteilung zwischen den Szenen und dem GameManager hat sich geändert. Dieser ist nun nicht mehr verantwortlich für das Erstellen des Levels, sondern wird mit den fertigen Leveldaten initialisiert. Diese werden von einer eigenen Level Klasse bereitgestellt. Sämtliche Spiellogik, wie das Updaten der Komponenten, ist nun in den GameManager gewandert.

Damit wir endlich etwas von unserem Protagonisten sehen, zeichnen wir an der Stelle der Avatarposition nun ein schönes Sprite. Damit die Kamera etwas lebendiger wirkt, verzögern wir die Positionsanpassung der Kamera leicht und lassen sie so etwas “träge” wirken. Dazu verwenden wir die Lerp() Methode der Vector2 Klasse, welche einen Vektor einem anderen um einen gewissen Grad annähert.

camera.Position = Vector2.Lerp(cameraMoving.Position, direction, 0.07f);

Apropos Kamera: An dieser hat es ebenfalls zahlreiche Optimierungen gegeben. Da das neu Aufbauen von Matrizen wichtige Ressourcenzeit in Anspruch nimmt, entscheiden wir uns dafür, diese nicht mehr in jedem Update Aufruf durchzuführen, sondern nur noch dann, wenn sich etwas geändert hat. So bauen wir zum Beispiel die View Matrix nur auf, wenn sich die Position der Kamera geändert hat. Anbei ein Auszug des Codes:

public sealed class Camera2D : GameComponent, ICamera

{

bool updateViewRequired = false;

float zoom;

public float Zoom

{

get { return zoom; }

set

{

zoom = value;

UpdateZoomViewPort();

}

}

float rotation;

public float Rotation

{

get { return rotation; }

set

{

rotation = value;

UpdateRotation();

}

}

public Vector2 Position { get; private set; }

Viewport viewPort;

Vector3 viewPortCenter;

Matrix scaleMatrix;

Matrix rotationMatrix;

Matrix translationMatrix;

Matrix focusPointMatrix;

public Matrix View { get; private set; }

int zoomWidth;

int zoomHeight;

public Camera2D(Game game, float zoom)

: base(game)

{

}

public Camera2D(Game game)

: base(game)

{

UpdateViewport();

}

public void UpdateViewport()

{

viewPort = Game.GraphicsDevice.Viewport;

viewPortCenter = new Vector3(viewPort.Width * 0.5f, viewPort.Height * 0.5f, 0);

// optimiert für eine breite von 1680! wenn schmäler, dann mehr zoomen!

focusPointMatrix = Matrix.CreateTranslation(viewPortCenter);

Zoom = (float)Math.Round(viewPort.Width / 16.8 / 100, 2);

Rotation = -0.5f;

Position = Vector2.Zero;

UpdateTranslation();

updateViewRequired = true;

}

public void MoveBy(Vector2 direction)

{

Position += direction;

UpdateTranslation();

}

public void MoveTo(Vector2 position)

{

if(Vector2.Distance(position, Position) < 0.1f) return;

Position = position;

UpdateTranslation();

}

public override void Update(GameTime gameTime)

{

if (!updateViewRequired) return;

View = translationMatrix *

rotationMatrix *

scaleMatrix *

focusPointMatrix;

}

private void UpdateZoomViewPort()

{

zoomWidth = (int)(viewPort.Width * (1 / Zoom) * 1.5f);

zoomHeight = (int)(viewPort.Height * (1 / Zoom) * 2.5f);

scaleMatrix = Matrix.CreateScale(new Vector3(Zoom, Zoom, Zoom));

updateViewRequired = true;

}

private void UpdateRotation()

{

rotationMatrix = Matrix.CreateRotationZ(Rotation);

Up = Vector2.Transform(-Vector2.UnitY, Matrix.CreateRotationZ(-Rotation));

updateViewRequired = true;

}

private void UpdateTranslation()

{

translationMatrix = Matrix.CreateTranslation(new Vector3(-Position.X, -Position.Y, 0));

updateViewRequired = true;

}

public Vector2 Up

{

get; private set;

}

}

Fists of Fire bietet mittlerweile übrigens zwei Kameras: Eine bewegte Kamera, welche dem Protagonisten folgt und eine statische Kamera, mit der man die ganze Welt auf einmal sehen kann. Die Verwaltung der Kameras geschieht, wenig überraschend, über einen CameraManager. Der Code dafür ist sehr einfach, und wird daher hier im Artikel erst mal ausgespart. Außerdem richtet sich der Zoom Level der Kamera nun nach der Bildschirmbreite. So wird bei kleineren Bildschirmauflösungen auch weiter hinaus gezoomt. Damit garantieren wir, dass das Spiel auf unterschiedlichen Auflösungen gleich aussieht.

Auch die einfärbigen Tiles weichen nun Texturen. Beim Betrachten des Codes wird auffallen, dass die Texturen dabei alle in einer einzigen Datei abgelegt sind. Der Grund dafür ist, dass der SpriteBatch dafür optimiert ist, von einem einzigen Texturfile zu arbeiten. Um mehrere unterschiedliche Grafiken zu verwenden, kann man eine Textur also in ein Raster aufteilen und dann mit unterschiedlichen “Ausschnitten” daraus arbeiten. Aus diesem Grund bietet der SpriteBatch auch einen Draw() Aufruf, den man ein Source Rectangle angeben kann. Es ist effektiver als zwischen den Draw() Aufrufen die Textur zu wechseln. Grundsätzlich sollte die Anzahl der verwendeten Texturfiles innerhalb eines SpriteBatch niedrig gehalten werden.

"Wo gehts hier nach Britannia?" – Unser Avatar

Zugegeben, es sind nicht die schönsten Texturen und auch die Ränder sind noch etwas hart. Dazu schauen wir uns aber in späteren Teilen noch Techniken an. Das Rahmenprogramm ist aber schon mal vorhanden und das ist das wichtigste.

Auch die versprochene Partikelengine ist schon im Code eingebaut und benötigt nur noch den einen oder anderen Feinschliff. Aus Platzgründen verschiebe ich die Erklärungen dazu aber auf den nächsten Teil.

Zusammengefasst: Im Code hat sich seit dem letzten Mal wirklich viel getan. Aber bitte nicht abschrecken lassen, auch wenn die Code Dokumentation momentan noch etwas karg ist, ich verspreche das nachzubessern! Für Fragen abseits der Artikel stehe ich ebenso jederzeit bereit. Jedenfalls bietet der Code einige Überraschungen und das Spiel noch ein paar Features, die ich nicht erwähnt habe!

Wichtigstes zuerst: Minimap

Ja ok, schon klar. Minimaps sind eventuell nicht die wichtigsten Features eines Games. ABER: Möchte man jederzeit das Terrain testen und kontrollieren können, dann bietet sich eine schnelle Implementierung einer praktischen Minimap doch an!

Um eine Minimap zu zeichen, nehmen wir die erzeugten Leveldaten und bauen eine kleine Version der Gesamtkarte in Graustufen zusammen. Da sich die Welt nicht ändert, erzeugen wir uns eine Textur2D, die wir auf eine andere, schöne “Karten” Textur drüberzeichnen. Um den “Rand” der Spielwelt auch besser zu erkennen, zeichnen wir nur die dunkel umrandete Landmasse. Der knifflige Teil dabei ist, den zweidimensionalen Array an Tiledaten auf einen eindimensionalen Array an Color Werten umzurechnen.

Der wesentliche Teil wird dabei von der BuildMinimap() Methode erledigt. Diese ruft die SetPixel() und WritePixels() Methoden auf. Wichtig ist dabei noch, dass die Textur erst zum Schluss erzeugt wird, da das verwendete GraphicsDevice eine Referenz auf jede Textur hat und auch monitort. Entsprechende Zeichenoperationen dauern dadurch länger und sollten daher nur gebatcht durchgeführt werden. Wie das geht, zeigt am Besten der Code.

public class Minimap : GameComponent

{

Rectangle basePos;

Rectangle mapPos;

Texture2D baseTexture;

Texture2D mapTexture;

Texture2D arrowTexture;

float[,] map;

byte water;

const int factor = 3;

Color borderColor = Color.DarkGray;

public Minimap(Game game, Level data) : base(game)

{

map = data.TerrainValues;

water = data.WaterLine;

}

public override void Initialize()

{

base.Initialize();

baseTexture = Game.Content.Load<Texture2D>(“Textures/map”);

arrowTexture = Game.Content.Load<Texture2D>(“Textures/quad”);

if (mapTexture != null)

mapTexture.Dispose();

mapTexture = new Texture2D(Game.GraphicsDevice,

GameConstants.GridSize * factor, GameConstants.GridSize * factor);

BuildMinimap();

basePos = new Rectangle(

(Game.GraphicsDevice.Viewport.Width / 2) – (baseTexture.Width / 2),

(Game.GraphicsDevice.Viewport.Height / 2) – (baseTexture.Height / 2),

baseTexture.Width, baseTexture.Height);

var offsetW = (baseTexture.Width – mapTexture.Width) / 2;

var offsetH = (baseTexture.Height – mapTexture.Height) / 2;

mapPos = new Rectangle(basePos.X + offsetW, basePos.Y + offsetH, mapTexture.Width, mapTexture.Height);

}

public void Draw(SpriteBatch batch, Vector2 position)

{

batch.Draw(baseTexture, basePos, Color.White);

batch.Draw(mapTexture, mapPos, Color.White);

// draw player

batch.Draw(arrowTexture, Translate(position), Color.Red);

}

void BuildMinimap()

{

Color[] pixels = new Color[GameConstants.GridSize * GameConstants.GridSize * factor * factor];

for (int x = 0; x < GameConstants.GridSize; x++) // columns

{

for (int y = 0; y < GameConstants.GridSize; y++) // rows

{

byte value = (byte)map[x, y];

if (value <= water)

continue;

Color px = TerrainHelper.GetGrayscale(water, value);

SetPixel(pixels, x, y, px);

// upper

if (x > 0 && (byte)map[x - 1, y] <= water)

SetPixel(pixels, x – 1, y, borderColor);

// lower

if (x < GameConstants.GridSize – 1 && (byte)map[x + 1, y] <= water)

SetPixel(pixels, x + 1, y, borderColor);

// left

if (y > 0 && (byte)map[x, y - 1] <= water)

SetPixel(pixels, x, y – 1, borderColor);

// right

if (y < GameConstants.GridSize – 1 && (byte)map[x, y + 1] <= water)

SetPixel(pixels, x, y + 1, borderColor);

}

}

WritePixels(mapTexture, pixels);

}

Vector2 Translate(Vector2 world)

{

var pt = TerrainHelper.ToTilePos(world);

return new Vector2(mapPos.X + (pt.X * factor), mapPos.Y + (pt.Y * factor));

}

static void SetPixel(Color[] pixels, int gridX, int gridY, Color color)

{

int rowSize = GameConstants.GridSize * factor;

for (int ry = 0; ry < factor; ry++)

{

for (int rx = 0; rx < factor; rx++)

{

int x = (gridX * factor) + rx;

int y = (gridY * factor) + ry;

pixels[x + (y * rowSize)] = color;

}

}

}

static void WritePixels(Texture2D texture, Color[] pixels)

{

// definieren ein Rechteck in der richtigen Größe

Rectangle rect = new Rectangle(0, 0, GameConstants.GridSize * factor, GameConstants.GridSize * factor);

// und zeichnen die Farbe in der Größe des Rechtecks sie an die richtige Stelle auf der blanken Textur

texture.SetData<Color>(0, rect, pixels, 0, rect.Width * rect.Height);

}

}

Der rote Punkt (im Bild ganz unten eher mittig) zeigt unsere aktuelle Weltposition. Praktisch!

Weniger Frust dank Culling

Einer der wichtigsten Punkte jedes Spiels ist die Performance. Läuft das Spiel nicht flüssig oder bricht die Framerate an bestimmten Stellen ein, kann das sehr frustrierend für den Spieler sein. Aus diesem Grund sollte ein Spiel immer mit Performance im Hintergrund programmiert werden. Beispielsweise ist es nicht notwendig alle Spielobjekte in der Welt auch zu rendern. Um Rechenzeit zu sparen, reicht es aus, nur Objekte zu rendern, welche sich innerhalb des Sichtbereichs des Spielers befinden. Diese Technik nennt man Frustum Culling und gehört zu den wichtigsten Optimierungsverfahren moderner Engines.

Für 3D Spiele bietet das XNA Framework zu diesem Zwecke eine eigene Klasse, nämlich BoundingFrustum. Da sich die Form und Ausrichtung dieses Frustums nach den View und Projection Matrizen der Kamera richten, sollte die Kamera um diese Eigenschaft erweitert werden, welche ein BoundingFrustum erzeugt und stets aktuell bereitstellt. Für unser 2D Game verwenden wir allerdings ein einfaches Rectangle, welches den sichtbaren Weltbereich anbietet. Das Rectangle bietet viele Methoden, um festzustellen, ob ein Point oder Vector2 innerhalb des Bereichs ist. Da sich dieser Bereich mit der Position der Kamera verändert, muss es permanent aktualisiert werden. Wir benennen die Property ViewPlane und aktualisieren sie in der Update() Methode der Kamera, wie nachfolgend gezeigt:

public Rectangle ViewPlane { get; private set; }

public override void Update(GameTime gameTime)

{

if (!updateViewRequired) return;

View = translationMatrix *

rotationMatrix *

scaleMatrix *

focusPointMatrix;

ViewPlane = new Rectangle(

(int)(Position.X – (zoomWidth * 0.5)),

(int)(Position.Y – (zoomHeight * 0.5)),

zoomWidth,

zoomHeight);

}

Pro Tipp: Die BoundingFrustum Klasse verfügt über eine Contains() und Intersects() Methode mit jeweils mehreren Überladungen. Als Eingabeparameter können ein Vector3, BoundingSphere, BoundingBox oder auch ein anderes BoundingFrustum Objekt verwendet werden. Die Intersects() Methode liefert mit true oder false dabei direkt das Ergebnis der Überprüfung, Contains() dagegen gibt ein Objekt vom Typ der Enumeration ContainmentType zurück. Dieses kann einen der folgenden Werte annehmen:

  • Disjoint, keine Überlappung
  • Contains, komplette Überlappung
  • Intersects, teilweise Überlappung

Ähnliche Prinzipien kann man auch auf andere Bereichen der Spiellogik anwenden. Befinden sich zum Beispiel sehr viele Objekte im Spiel, die auf Kollisionen überprüft werden müssen, dann kann sich eine andauernde Überprüfung sämtlicher Objekte sehr schnell auf die Performance des Spiels niederschlagen. Generell sollten so wenig Kollisionsabfragen wie möglich durchgeführt werden, um die Performance nicht zu beeinträchtigen. Es müssen daher Wege gefunden werden, die Anzahl der zu überprüfenden Objekte sinnvoll zu reduzieren.

Baumkunde

Weitere Möglichkeiten, Objekte aus dem Prozess des Renderns und von Kollisionsabfragen auszuschließen, sind die OcTree und QuadTree Verfahren. Beide Techniken haben die Idee zur Grundlage, die Spielwelt in kleinere Bereiche aufzuteilen, um dann nur noch Objekte in den betroffenen Bereichen durchlaufen und überprüfen zu müssen.

Die Unterschiede der beiden Verfahren liegen in der Art der Unterteilung sowie in der Anzahl der Bereiche, in die unterteilt wird. Das OcTree Verfahren unterteilt die Spielwelt dabei anfänglich in eine definierte Anzahl gleichgroßer Würfel. Danach werden die Spielobjekte den entsprechenden Würfeln zugeordnet.

Ein praktisches Beispiel für die sinnvolle Anwendung wäre ein Raumschiffspiel. Der Spieler muss mit einem Raumschiff durch ein Feld voller Asteroiden fliegen. Dabei sollen allerdings nicht immer sämtliche Asteroiden im Spiel gerendert oder auf Kollisionen mit dem Raumschiff überprüft werden. Nur Asteroiden, die sich im selben Würfel wie das Raumschiff befinden, sind Kandidaten für eine Kollisionsüberprüfung. Außerdem brauchen nur die Asteroiden gerendert werden, welche sich im BoundingFrustum des Spielers befinden. Hier wiederum brauchen nicht alle Asteroiden überprüft werden, sondern nur die, die sich in den Würfeln im Sichtbereich des Raumschiffs befinden.

Ein QuadTree ist nicht nur etwas einfacher zu implementieren, er eignet sich auch auf Grund seiner Eigenschaft, rein zweidimensional ausgerichtet zu sein, wesentlich besser für Fists of Fire. Jeder Würfel wird dabei in vier kleinere geteilt. Das QuadTree Verfahren eignet sich typischerweise für die Aufteilung von Terrain, wogegen das OcTree Verfahren für die gesamte Spielwelt eingesetzt werden kann.

Im Fall von Fists Of Fire rendern wir nur noch die Tiles, und was sich auf Ihnen befindet, die auch sichtbar sind. Auch in einer 3D Engine kann aber Anwendung dafür gefunden werden. Ein Beispiel dafür wäre ein Rennspiel. Wechselnde Szenerien am Rand der Rennstrecke können ausladende Landschaften mit Wäldern und Seen, aber auch Häuserschluchten mit einer Vielzahl an Gebäudevariationen sein. Da sich Rennspiele hauptsächlich auf einer horizontalen Ebene bewegen, lässt sich dieses Terrain auf eine zweidimensionale Ebene simplifizieren. Dadurch wird die Anwendung eines QuadTree Verfahrens ermöglicht.

Die Anwendungsmöglichkeiten beider Verfahren sind vielfältig und eine Kombination ist durchaus üblich. Möglich, dass im fertigen Spiel beide Verfahren zur Anwendung kommen werden, momentan ist aber noch keines der beiden implementiert. Die Zeit wird zeigen, ab wann eine Optimierung sinnvoll notwendig ist, wir werden dann wieder auf dieses Kapitel zurückkehren wenn Bedarf besteht. Wichtig ist zunächst nur, die theoretische Grundlage dieser Verfahren zu kennen, da sie bei größeren Projekten von elementarer Bedeutung sind.

Kleiner Exkurs: Batching

Dieses Kapitel richtet sich hauptsächlich an die erfahreneren Entwickler unter euch und findet sonst im weiteren Projektablauf keine Anwendung. Es geht um eine Vertiefung des Wissens über Optimierungstechniken für 3D Engines. Wer sich dafür nicht interessiert kann getrost zum Abschluss skippen. Es wird außerdem ein bisschen mehr Grundwissen als bisher transportiert vorausgesetzt.

Hauptverantwortlich für einen Großteil der Performance eines 3D Games ist die Anzahl der Aufrufe, bei dem Vertex Daten des Modells vom Computerspeicher in den Grafikkartenspeicher transportiert werden. Dies geschieht üblicherweise in der Model.Draw() Methode und ist eine vergleichsweise langsame Operation. Der populäre Grafikkartenhersteller NVidia stellt fest, dass die Geschwindigkeit der Übertragung dieser Datenpakete, sogenannte Batches, stark CPU abhängig sind. Bei diesen Batches wird die Hauptarbeit von der CPU erledigt und die Power der Grafikkarte bleibt ungenutzt, da diese ja auf die Datenpakete warten muss. NVidia empfiehlt daher weniger und dafür größere Batches. Dies entlastet die CPU und gibt der Grafikkarte mehr zu tun. Dazu nennt NVidia im Jahr 2003 folgende Faustformel:

  • Ziel GHz: Taktfrequenz der CPU des Zielsystems.
  • CPU Zeit: Ein Wert zwischen 0 und 1 der der Prozentanzahl der Verfügbarkeit von Prozessorzeit der CPU entspricht.
  • Ziel Framerate: Die gewünschten Frames pro Sekunde.

Das Ergebnis ist die Anzahl der möglichen Batches pro Frame. Ein aktuelles Beispiel wäre ein Zielsystem mit 3 GHz CPU, einer verfügbaren Rechenzeit von 70% und einer erzielten Framerate von 60 Frames pro Sekunde. Um die genannten Vorgaben zu erfüllen, sind also laut dieser Formel maximal 875 Batches pro Frame erlaubt. Um zu überprüfen ob eine Optimierung der Batchanzahl notwendig ist, sollte man daher grob die maximale Anzahl der Model.Draw() Befehle pro Frame abschätzen.

Muss man die Anzahl reduzieren, kann das sogenannte Batching zur Anwendung kommen. Zusammengefasst versteht man darunter eine Technik, die Vertex Daten mehrerer Modelle in einen sogenannten VertexBuffer zusammenfassen. Ein VertexBuffer hält dabei eine direkte Referenz auf den Grafikkartenspeicher. Um den Befehl zum Verarbeiten der Daten zu geben, wird dem GraphicsDevice der VertexBuffer als Datenquelle angegeben. Dadurch werden die Daten nur einmal an den Grafikkartenspeicher transportiert und in weiterer Folge nur noch die bereits im Grafikkartenspeicher vorhandenen Daten an die GPU übergeben.

Zu beachten ist dabei natürlich, dass es sich hierbei um Daten handeln muss, die sich nicht häufig ändern, da bei jeder Änderung der VertexBuffer neu erstellt werden muss. Für sich häufig ändernde Vertex Daten bietet das XNA Framework die Klasse DynamicVertexBuffer an. Ein DynamicVertexBuffer legt Daten statt auf der Grafikkarte in schnelleren Bereichen des normalen Arbeitsspeichers ab.

Die Implementierung von Batching ist aufwändiger als andere Techniken, da sie tiefergehendes Verständnis für Vertices und deren Indizierung voraussetzt. Für ein korrektes Anwenden ist außerdem noch ein IndexBuffer notwendig, der die Reihenfolge der zu zeichnenden Vertices angibt. Dem GraphicsDevice muss mit Hilfe einer VertexDeclaration außerdem noch die Beschaffenheit der Vertex Daten bekannt gegeben werden.

Next Up: Round one, Fight!

Nach der Theorie, stürzen wir uns wieder voll in die Game Mechanik, verwenden unsere Partikelengine und spawnen unsere ersten Gegner. Beide machen kombiniert gleich doppelt soviel Spaß, nämlich wenn man Gegner richtig effektvoll umhauen kann. Deshalb spendieren wir unserem Protagonisten noch einen netten Feuerball. Damit unsere Gegner aber nicht allzu doof aus der Wäsche gucken, bekommen sie ebenfalls eine primitive AI verpasst.

Verfasst von am 31. Mai 2012. Abgelegt unter Spiele selbst entwickeln. Du kannst jedem Kommentar zu diesem Artikel folgen durch RSS 2.0. Du kannst kommentieren oder zu diesem Artkel trackbacken

10 Antworten zu Spiele selbst entwickeln mit XNA, Teil 4: Optimierungstechniken

  1. Pingback: A rather “silent” month « urban escapism

  2. sehr interessant! am wochenende werd ich das mal probieren, “nachzustellen”!

    (bitte, bitte lass den character in der art und mach einen richtigen proll-shooter! das würd mir sooo taugen! :) )

  3. Leider ist der Sourcecode nicht mehr verfügbar…

    • der ist leider nicht mehr verfügbar. sorry dafür! und danke für den hinweis! wir haben mal die links entfernt.

      zusätzlich ist unser code-highlighter broken. deswegen ist der code in den artikeln jetzt unformatiert zu sehen.

  4. Oh, das ist aber schade. Vielleicht hat Herr Berghold ja die Möglichkeit den Sourcecode auf Github.com oder ähnlichen portalen verfügbar zu machen? Wenn ihr mir jemand den sourcecode zukommen lässt, könnte ich das auch erledigen.

  5. Thomas Berghold

    Hallo! Ja, hatte nicht soviele downloads, daher hab ichs vom skydrive genommen :) Dachte, nachdem dies hier auch gar nicht mehr aufrufbar ist von der hauptseite, dass da ohnehin nichts mehr kommen würde.

    Naja hab ich mich getäuscht!

    Grundsätzlich kann ich dir den code zukommen lassen, da aber XNA mittlerweile gestorben ist, würde ich den code zuerst gerne auf monogame porten, was ja 1:1 xna ist.

    http://monogame.codeplex.com/

    Da die Reihe eingestellt wurde, wird es hier keien neuen Artikel mehr dazu geben, vielleicht kann ich Peter aber dazu gewinnen, sollte das monogame update fertig sein, das nochmal in die News zu posten. Ansonsten können wir uns ja auch per mail austauschen.

  6. Naja, gestorben im Sinne von, wird nicht weiterentwickelt – ok, aber es gibt noch keine Alternative für die XBOX. Daher glaube ich, bleibt XNA auch noch eine weile am leben. Monogame ist ziemlich teuer wenn man auf andere plattformen gehen möchte. Vielleicht wird Microsoft ja doch irgend wann wieder eine managed Version einer plattformübergreifenden Lösung anbieten… C++/DirectX ist keine Alternative für mich, dann lieber SharpDX.

  7. Thomas Berghold

    Da kann man sicher drüber diskutieren,…

    ..ich persönlcih finds auch sehr schade. Für die neue XBOX denk ich wird man an C++ nicht vorbeikommen. XBLA hat wohl auch keine große Zukunft mehr, aber mal abwarten.

    XNA für Windows Phone gibt es auch nur bis zu 7.1, wer gerne gegen WIndows Phone 8 kompilieren möchte, muss auf C++ oder HTML/JS zurückgreifen.

    @Monogame: deswegen, weil es zumindest auf Windows gratis ist und die MS welt sehr gut abdeckt (Windows, Windows Store, Windows Phone 7, windows phone 8). Ist auch die Variante die Microsoft selbst empfiehlt (anstatt XNA).

    Habe darüber in einem persönlichen Face to Face Meeting mit Brandon Bray, seines Zeichens Group Program Manager der Managed Languages bei Microsoft, gesprochen. Der hat mir mit der Blume auch genau diese Info gegeben und Monogame nahe gelegt :)

    Sicher kostet Monogame für andere Plattformen – Unity, Sunburn, .. kosten alle was. Nicht vergessen, mit XNA hat man nicht ja mal die Möglichkeit dazu.

    Monogame ist unter der Haube SharpDX.

    Es gibt auch einige SharpDX open source game Libraries… aber naja. Monogame ist halt schon sehr reif.

    Ansonsten kannst du dir auch noch Wave Engine ansehen:
    http://www.waveengine.net/

  8. Dem kann ich nur zustimmen, aber mir ging es dabei mehr um die Aussage XNA sei tot. Grade Monogame beweist einducksvoll das die Idee von XNA so tot ja gar nicht sein kann, da die Basis ein XNA vergleichbares Framework ist.

    Vielleicht sollte der Vollständigkeit halber auch noch ANX (http://anxframework.codeplex.com/) hier erwähnt werden, welches sich ebenfalls dieser Aufgabe gewidmet hat und behaubtet ein wesentlich bessers Grundgerüst anzubieten.

    Nun, welche Alternative nacher das Rennen macht wird man sehen, aber ich mache mir mehr Sorgen darum das so viele aufgehöhrt haben Artikel zum XNA Universum zu veröffentlichen, so als wäre durch die Aufgabe durch Microsoft auch kein einziger Interessent mehr für dieses Konzept da. Ich finde kaum bis gar keine aktuellen Blogs mehr zum Thema, ganz so, als würde es die alternativen Frameworks nicht geben und man nun wieder im stillen Kämmerlein arbeiten muss.

    Ich glaube Microsoft ist sich über diese Tragweite ihrer Entscheidung XNA endgültig fallen zu lassen nicht bewusst gewesen und hat sich auch damit (mal wieder) mehr Feinde als Freunde gemacht. Eine riesen Community ist damit quasi verdampft und wo sind die jetzt, was machen die nun? Kann mir doch keier erzählen das die jetzt alle C++ und DirectX lernen, wie grauenvoll, das geht garantiert schief, da man im gleichen Zeitraum nur minimale Erfolge feiern kann. C++ Entwicklung ist ein mühsames und sehr Zeitaufwändiges entwickeln. Kein Wunder das die “großen” Spieleschmieden auf Engines setzen die dann zum großen Teil über Skripte gesteuert werden, da sich das niemand in C++ antun möchte. Bei C# kann ich alles in C# machen und es ist dabei sogar schnell genug. Aber wer weis vielleicht hat ja Microsoft auch schon Pläne C# sterben zu lassen…

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>