XNA Programmier Stil

DuffCola

Mitglied
Hallo.
Ich habe seit ein paar Tagen mit C# XNA angefangen.
Ich habe auch schon das eine oder andere 2d Game gemacht.
Nur jetzt Frage ich mich in welchem Programmierstil sollte man komplexere Spiele machen?
Ich meine z.B. lege ich bis jetzt für jeden Gegner / Gegenstand(Kiste, Items...) immer eine Klasse an.

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace Cartoon_Wars.Castle
{
    class Castle
    {
        // Eigenschaften
        private SpriteEffects sprite_effects;
        private Texture2D texture;
        private Vector2 position;
        private Color color;

        // Konstruktor
        public Castle(bool site)
        {
            position = new Vector2(0, 0);
            color = Color.White;
        }

        // Skelett
        public void Initialize()
        {

        }
        public void LoadContent(ContentManager content_manager)
        {
            texture = content_manager.Load<Texture2D>("Castle");
        }
        public void UnloadContent()
        {

        }
        public void Update(GameTime game_time)
        {
        // Physik usw...
        }
        public void Draw(SpriteBatch sprite_batch)
        {
            sprite_batch.Draw(texture, position, color);
        }
    }
}

Und dann erstelle ich ein Objekt dieser Klasse:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Cartoon_Wars
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private Castle.Castle castle_1;   

        public Game1()
        {
            castle_1 = new Castle.Castle(true);

            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
        protected override void Initialize()
        {
            IsMouseVisible = true;

            graphics.PreferredBackBufferWidth = 1600;
            graphics.PreferredBackBufferHeight = 900;
            graphics.ApplyChanges();

            base.Initialize();

            // Klassen
            castle_1.Initialize();
        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
            // Klassen
            castle_1.LoadContent(Content);
        }
        protected override void UnloadContent()
        {
            // Klassen
            castle_1.UnloadContent();
        }
        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            // Klassen
            castle_1.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            //Klassen
            castle_1.Draw(spriteBatch);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

Ist das so Richtig, oder sollte ich in meinem Programmierstil etwas ändern?
 

DuffCola

Mitglied
Mh.
So wirklich weiterhelfen tut es nicht.
Kennt jemand ein gutes Buch (Wenn Möglich auf Deutsch), dass unter anderem auf dieses Thema eingeht?

Oder vielleicht auch Anfänger Tutorials die das ganze genauer erklären?

Trotzdem vielen Dank für die schnelle Antwort.
 

Cromon

Erfahrenes Mitglied
Inwiefern hilft dir das nicht weiter? Da wird ein mögliches System beschrieben, wie man das machen kann.

Grundsätzlich willst du ja ein System um deine Objekte verwalten zu können. Bevor du sie aber verwalten kannst musst du dir erst mal Gedanken darüber machen was du überhaupt für eine Modellierung willst.

Eine Möglichkeit wäre es zum Beispiel einen vererbungsbasierten Ansatz zu wählen. Du hast eine Klasse/Interface welche grundsätzlich ein Objekt in deinem Spiel repräsentiert. Da kannst du alle Infos reinpacken die unabhängig davon sind, was für ein Objekt es ist. Ein Beispiel:
C#:
public interface GameObject
{
   ulong GUID { get; }
   string ObjectName {get; }

   void renderObject();
}

Nun hast du zum Beispiel einerseits Objekte die an eine Position gebunden sind (spieler, npc, usw) und Objekte, die keine Position haben (items usw)

C#:
public interface PositionObject : GameObject
{
   Vector3 Position { get; }
}

public interface NonPositionObject : GameObject
{
}

Anschliessend hast du zum Beispiel eine Unterscheidung zwischen einem statischen und einem dynamischen Objekt.
C#:
public abstract class DynamicObject : PositionObject
{
    protected Vector3 Velocity, Acceleration;
}

public abstract class StaticObject : PositionObject
{
}

Ein dynamisches Objekt wiederum könnte nun eine Einheit oder aber zum Beispiel ein Hindernis (sofern die sich bewegen können, sonst wären sie ein StaticObject) sein
C#:
public abstract class Unit : DynamicObject
{
   protected int Health, Mana, Money;
}

public abstract class Obstacle : DynamicObject
{
   protected float Height, Density;
}

Und zu guter Letzt könntest du Unit nun noch aufteilen in Player und NPC
C#:
public abstract class Player : Unit
{
   public abstract void SendChatMessage(string message);
}

public abstract class NPC : Unit
{
   public abstract void RequestAIChange(AIType newAI);
}

Nun wären jetzt entsprechende Gegnerklassen zum Beispiel dann jeweils Implemetationen der NPC-Klasse und würden diese einfach entsprechend für ihre Klasse erweitern und so weiter.

Auf diese Weise hättest du nun mal ein Layout für deine Objekte. Einen Nachteil dieser Variante siehst du vermutlich bereits: Es führt zu einer (unternehmerisch gesprochen) steilen Hierarchie, was durchaus unübersichtlich werden kann. Dies ist in diesem Falle jedoch nicht so schlimm weil die Vererbungsschritte logisch und auch leicht einsehbar sind.

Bevor ich jetzt auf eine weitere Methode eingehe wollen wir uns anschauen was wir jetzt damit machen.

Auch hier hast du wieder mehrere Konzepte, die du anwenden kannst. Ein bekanntes und oft eingesetztes ist das Manager-Prinzip. Du hast zentral einen Manager der eine Menge an Objekten von einem gewissen Typen - wie es der Name sagt - managt indem er ein Interface für die anderen Teile deines Spiels darstellt.

Ein Grundgerüst mit dem obigen Ansatz wäre dann etwas in der Art:
C#:
public class ObjectManager
{
   private List<GameObject> gameObjectList = new List<GameObject>();
   private Dictionary<ulong, GameObject> gameObjectDict = new Dictionary<ulong, GameObject>();

   public void addObject(GameObject obj) {
      if(gameObjectDict.ContainsKey(obj.GUID))
           return;

      gameObjectDict.Add(obj.GUID, obj);
      gameObjectList.Add(obj);
   }

   public void removeObject(GameObject obj) {
      if(gameObjectDict.ContainsKey(obj.GUID) == false)
         return; // oder exception

      gameObjectDict.Remove(obj.GUID);
      gameObjectList.Remove(obj);
   }

   public GameObject getObjectByGUID(ulong guid) {
      if(gameObjectDict.ContainsKey(guid) == false)
         throw new ArgumentException("guid not found in object list.");

      return gameObjectDict[guid];
   }

   public void renderObjects() {
      foreach(var obj in gameObjectList)
         obj.renderObject();
   }
}

Soweit so gut, ein letzter wichtiger Punkt den brauchst um mal ein Grundgerüst zu haben wäre ein entsprechendes Prinzip um Objekte zu erstellen. Ein Ansatz wäre über das Factory-Prinzip, das würde zur sinnvollen Erklärung und mit Beispielen etwas den Rahmen sprengen, gibt dazu aber ganz viel Literatur. Du kannst deine Objekte auch einfach manuell ganz normal erstellen im ObjectManager.

Dein Spiel interagiert nun nicht mehr mit den Objekten direkt, sondern mit dem ObjectManager und weiss selbst überhaupt nicht, was es überhaupt für Objekte hat. Der ObjectManager bietet dann natürlich i.d.R. im fertigen Zustand mehr Funktionalitäten an als nur Objekte hinzufügen, Objekte entfernen.

Wie oben versprochen möchte ich dir noch kurz einen weiteren möglichen Ansatz zeigen wie du deine Objekte strukturieren kannst: Das Entity Component System

Die Idee ist hier, dass gerade bei grossen Projekten mit sehr vielen verschiedenen Objekttypen mit unterschiedlichen Eigenschaften die entstehende, doch sehr komplexe Vererbungshierarchie störend ist und durch ein anderes Konzept ersetzt wird. Die Vererbung wird auf eine Stufe reduziert, jedes Objekt ist einfach eine Entity und jede Entity hat ein Set von Komponenten. Jede Komponente ist wiederum nichts weiter als eine Implementation eines kleinen Stücks des Spiels.

Einige Beispiele:
C#:
public enum ComponentMessageType
{
   PositionChanged,
   CollisionDetected,
}

public class ComponentMessage
{
     public Component Sender { get; set; }
     public List<object> Arguments { get; set; }
     public ComponentMessageType Type { get; set; }
}

public class Component
{
   protected void fireMessage(ComponentMessage message) {
      if(OnMessage != null)
         OnMessage(message);
   }

   public event Action<ComponentMessage> OnMessage;
};

public class PositionComponent : Component
{
    private Vector3 mPosition = Vector3.Zero;

    private void onPositionChanged() {
       ComponentMessage message = new ComponentMessage() {
          Sender = this,
          Arguments = new List<object>() { mPosition },
          Type = ComponentMessageType.PositionChanged
       }

       fireMessage(message);
    }    

    public Vector3 Position { get { return mPosition; } set { mPosition = value; onPositionChanged(); } }
}

public class PhysicsSystem
{
   public static void checkCollision(Entity e1, Entity e2)
   {
      PositionComponent pos1 = e1.getComponent<PositionComponent>();
      PositionComponent pos2 = e2.getComponent<PositionComponent>();

      PhysicsComponent physics1 = e1.getComponent<PhysicsComponent>();
      PhysicsComponent physics2 = e2.getComponent<PhysicsComponent>();

      if(pos1 == null || pos2 == null || physics1 == null || physics2 == null)
         return;

      if(pos1.Position == pos2.Position) {
         physics1.signalCollision(e2);
         physics2.signalCollision(e1);
      }
   }
}

public class PhysicsComponent : Component
{
   public void signalCollision(Entity e2) {
      ComponentMessage msg = new ComponentMessage() {
         Sender = this,
         Arguments = new List<object>() { e2 },
         Type = ComponentMessageType.CollisionDetected;
      }

      fireMessage(msg);
   }
}

public class Entity
{
   public Component getComponent<T>() where T : Component {
      var selection = mComponents.OfType<T>();
      if(selection.Count() == 0)
         return null;

      return selection.First();
   }

   public ulong GUID { get; protected set; }
  
   protected List<Component> mComponents = new List<Component>();
}

public class Player : Entity
{
   public Player() {
      GUID = GUIDCounter.nextGUID();
      mComponents = new List<Component>() {
         new PositionComponent(),
         new PhysicsComponent()
      };
   }
}

Auch das würde jetzt natürlich noch wesentlich komplexer werden, aber ich denke du verstehst in welche Richtung es geht.

Viele Grüsse
Cromon
 

DuffCola

Mitglied
WOW.
Danke.
Mit so einer guten Antwort habe ich nicht gerechnet :D
Nochmal vielen Dank.
Das hilft mir schon deutlich weiter!
 

Neue Beiträge