Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Objekte aus Textdatei erstellen (https://www.delphipraxis.net/175361-objekte-aus-textdatei-erstellen.html)

Acuaplano 16. Jun 2013 18:22

Delphi-Version: 5

Objekte aus Textdatei erstellen
 
Moin Moin,

Ich arbeite grad' an einem kleinen Spiel mit der Andorra 2D-Engine, wobei jedoch mein Problem eher Delphi bezogen ist. Das Problem liegt in der Mapgenerierung, die in Textdateien abgespeichert wird. Bis jetzt sah die Mapgenerierung so aus:
Es steht ein Raster, dass aus Zahlen besteht, in etwa so:
1;2,3;
5;2;7;
8;2;3;
Die Zahlen zeigen dabei die Bilder für den Maphintergrund an. Die einzelnen Bilder wurden zuvor in eine Liste mit den entsprechenden Nummern geladen. Anschließend werden beim Laden der Map für jede Zahl ein Objekt an der entsprechenden Stelle und passenden Bildchen erstellt. Funktionieren tut's wunderbar, nur reicht das noch nicht für eine richtige Map:
Wie stelle ich es an, dass Objekte von verschiedenen Klassen gebildet werden, abhängig von dem was in der Textdatei steht?
Beispiel (Mehrere Ebenen sind bereits möglich):
TBaum; 0; TBaum;
TBrett; 0; TNpc;
0; TBaum; THaus;

Dabei muss ne praktische Lösung her, da ja noch ne ganze Menge Objekte dazukommen könnten. Ich hatte mir schon gedacht, dass ich irgendwie alle Objekte in eine Liste reinpacke und dann bei der Generierung daraus gesucht wird... aber wie genau das gehen soll weiß ich auch nicht.

jaenicke 16. Jun 2013 18:49

AW: Objekte aus Textdatei erstellen
 
Warum benutzt du denn Textdateien? Hat das einen Grund?

Denn viel simpler wäre es, wenn du deinen Klassen jeweils die Methoden LoadFromStream und SaveToStream spendieren würdest, die das ganze binär speichern und laden. Dazu dann noch in der äußersten Ebene LoadFromFile und SaveToFile, die einen TFileStream aufmachen und an deine Streammethoden weitergeben, schon bist du fertig.

Das ist sehr viel einfacher, nur die Dateien sind manuell schlecht lesbar. Aber wenn das nicht wichtig ist, würde ich das eher so machen.

Acuaplano 16. Jun 2013 19:38

AW: Objekte aus Textdatei erstellen
 
Lesbar muss es nicht sein, dafür mit nem selbst gemachten Editor erstellbar, aber das sollte ja da auch kein Problem sein.
Falls ich das jetzt richtig verstanden habe (TFileStream ist Oberklasse von allen Objekten?), wird das auch nicht funktionieren, da ich die Objekte von einer anderen Klasse der Sprite Engine ableiten muss. Ansonsten müsste ich vielleicht große Teile der Engine umschreiben... und das trau ich mir nicht zu :D

Und um ehrlich zu sein arbeite ich auch nicht alleine daran. Ein paar andere und ich wollten einfach mal ein Spiel selber machen und ein paar Erfahrungen sammeln. Daher die simplen Texdateien :wink:.

jaenicke 16. Jun 2013 20:53

AW: Objekte aus Textdatei erstellen
 
Hier mal ein Beispiel:
Delphi-Quellcode:
unit UnitXYZ;

interface

uses
  Classes, SysUtils;

type
  TStringReaderWriter = class helper for TStream
    function ReadString: string;
    procedure WriteString(const AValue: string);
  end;

  TMyGameObject = class
  private
    type
      TExample = class
      private
        var
          FExampleString: string;
          FExampleInteger: LongInt;
        procedure SetExampleInteger(const Value: LongInt);
        procedure SetExampleString(const Value: string);
      public
        procedure SaveToStream(const ATarget: TStream);
        procedure LoadFromStream(const ASource: TStream);
        property ExampleString: string read FExampleString write SetExampleString;
        property ExampleInteger: LongInt read FExampleInteger write SetExampleInteger;
      end;
    var
      FInterestingString: string;
      FExampleObject: TExample;
    procedure SetInterestingString(const Value: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure SaveToStream(const ATarget: TStream);
    procedure LoadFromStream(const ASource: TStream);
    procedure SaveToFile(const AFilename: String);
    procedure LoadFromFile(const AFilename: String);
    property InterestingString: string read FInterestingString write SetInterestingString;
    property ExampleObject: TExample read FExampleObject;
  end;

implementation

{ TMyGameObject.TExample }

procedure TMyGameObject.TExample.LoadFromStream(const ASource: TStream);
begin
  FExampleString := ASource.ReadString;
  ASource.ReadBuffer(FExampleInteger, SizeOf(FExampleInteger));
end;

procedure TMyGameObject.TExample.SaveToStream(const ATarget: TStream);
begin
  ATarget.WriteString(FExampleString);
  ATarget.WriteBuffer(FExampleInteger, SizeOf(FExampleInteger));
end;

procedure TMyGameObject.TExample.SetExampleInteger(const Value: LongInt);
begin
  FExampleInteger := Value;
end;

procedure TMyGameObject.TExample.SetExampleString(const Value: string);
begin
  FExampleString := Value;
end;

{ TStringReaderWriter }

function TStringReaderWriter.ReadString: string;
var
  ResultString: AnsiString;
  StringSize: Integer;
begin
  Result := '';
  ReadBuffer(StringSize, SizeOf(StringSize));
  SetLength(ResultString, StringSize);
  ReadBuffer(Pointer(ResultString)^, StringSize);
  {$ifdef UNICODE}
  Result := Utf8ToString(ResultString);
  {$else}
  Result := Utf8Decode(ResultString);
  {$endif}
end;

procedure TStringReaderWriter.WriteString(const AValue: string);
var
  StringSize: Integer;
  StringToSave: AnsiString;
begin
  StringToSave := Utf8Encode(AValue);
  StringSize := Length(StringToSave);
  WriteBuffer(StringSize, SizeOf(StringSize));
  WriteBuffer(Pointer(StringToSave)^, StringSize);
end;

{ TMyGameObject }

constructor TMyGameObject.Create;
begin
  FExampleObject := TExample.Create;
end;

destructor TMyGameObject.Destroy;
begin
  FExampleObject.Free;
  inherited;
end;

procedure TMyGameObject.LoadFromFile(const AFilename: String);
var
  FileContents: TFileStream;
begin
  FileContents := TFileStream.Create(AFilename, fmOpenRead);
  try
    LoadFromStream(FileContents);
  finally
    FileContents.Free;
  end;
end;

procedure TMyGameObject.LoadFromStream(const ASource: TStream);
begin
  FInterestingString := ASource.ReadString;
  FExampleObject.LoadFromStream(ASource);
end;

procedure TMyGameObject.SaveToFile(const AFilename: String);
var
  FileContents: TFileStream;
begin
  FileContents := TFileStream.Create(AFilename, fmCreate);
  try
    SaveToStream(FileContents);
  finally
    FileContents.Free;
  end;
end;

procedure TMyGameObject.SaveToStream(const ATarget: TStream);
begin
  ATarget.WriteString(FInterestingString);
  FExampleObject.SaveToStream(ATarget);
end;

procedure TMyGameObject.SetInterestingString(const Value: string);
begin
  FInterestingString := Value;
end;

end.
Und benutzen kannst du das dann so:
Delphi-Quellcode:
var
  MyGameData: TMyGameObject;
begin
  if dlgOpen.Execute then
  begin
    MyGameData := TMyGameObject.Create;
    try
      MyGameData.LoadFromFile(dlgOpen.Filename);
      ShowMessage(MyGameData.InterestingString);
    finally
      MyGameData.Free;
    end;
  end;
Auf diese Weise ist nach außen immer alles gekapselt und du kannst die Objekte nach außen immer einfach benutzen ohne dich um die Interna zu kümmern.

Acuaplano 16. Jun 2013 21:16

AW: Objekte aus Textdatei erstellen
 
Okay, danke für die ausführliche Antwort!
Wir werden es dann mal so versuchen, aber das Hauptproblem ist ja immer noch vorhanden:

Ich versuch's mal anhand des aktuellen Codes zu erklären:

Delphi-Quellcode:
//Map bauen
    for i := 1 to reader.GetRasterX do  // 1 bis 32 (Breite des Rasters)
    begin
      for j := 1 to reader.GetRasterY do
      begin
        with TMapUB.Create(AdSpriteEngine) do
        begin
          //Map an Hand der Textdatei bauen
          Image := AdImageList1.Find(raster[j - 1, i - 1]); // *
          //Immer in 96 Abständen laden
          x := i * 96;
          y := j * 96;
        end;
      end;
    end;
Raster ist hierbei sozusagen die verarbeitete Textdatei als einfaches Array, wobei es dann als Beispiel '3' ausgeben würde und dann das entsprechende Bild ausgewählt werden würde.
Wir wollen ja aber nun, dass genau an dieser Stelle zwischen einzelnen Klassen statt einzelnen Bildern unterschieden wird.

Acuaplano 18. Jun 2013 14:47

AW: Objekte aus Textdatei erstellen
 
Ich würde es mir dann in etwa so etwa in der Art vorstellen:
Delphi-Quellcode:
raster[j - 1, i - 1].Create

Sodass das Programm eben das hier ausführen würde:
Delphi-Quellcode:
TBaum1.Create

jaenicke 18. Jun 2013 15:41

AW: Objekte aus Textdatei erstellen
 
Darauf passt das Factory-Pattern, d.h. eine Factory, bei der sich die Klassen z.B. selbst registrieren (z.B. in initialization der Units). Diese Factory kennt dann die Klassen und kann die auch erstellen, wenn du dort dann nach einer Klasse anfragst.

Acuaplano 18. Jun 2013 17:39

AW: Objekte aus Textdatei erstellen
 
Zitat:

Zitat von jaenicke (Beitrag 1218930)
Darauf passt das Factory-Pattern, d.h. eine Factory, bei der sich die Klassen z.B. selbst registrieren (z.B. in initialization der Units). Diese Factory kennt dann die Klassen und kann die auch erstellen, wenn du dort dann nach einer Klasse anfragst.

Ich hab mich jetzt etwas in das Thema eingelesen und werd's noch etwas tun, aber entweder hab ich noch nicht den richtigen Teil gefunden oder das verschiebt mein Problem nur...
Am Beispiel aus dem Internet:
Delphi-Quellcode:
if ASelector = 'Furniture' then
    Result := TFurniture.Create
  else if ASelector = 'Chair' then
    Result := TChair.Create
  else if ASelector = 'Cupboard' then
    Result := TCupboard.Create
  else if ASelector = 'Coffee Table' then
    Result := TCoffeeTable.Create
  else if ASelector = 'Kitchen Table' then
    Result := TKitchenTable.Create
  else if ASelector = 'Table' then
    Result := TTable.Create
Dieser Teil wird ja bei der Factory sozusagen "ausgelagert", wobei jedoch die if-anweisungen bleiben. Problem beim Spiel wird's aber sein, dass ganz viele Objekte dazukommen werden und 100 if-Anweisungen unpraktisch wären. Mein Wunsch wär es ja jetzt das auf ein paar Zeilen zu reduzieren.
Wie gesagt, ich bin noch grad' am Lesen.

stahli 18. Jun 2013 17:46

AW: Objekte aus Textdatei erstellen
 
Vielleicht mal etwas allgemeiner:

Man kann seine Klassen registrieren, damit Objekte dieser Klasse zur Laufzeit erzeugt werden können. Das ist aber etwas aufwendig.

Ungefähr läuft das dann so ab:
Delphi-Quellcode:
// nur smbolischer Code, die genauen Funktionen habe ich nicht im Kopf
VorbereitungFuerBaum;
VorbereitungFuerHaus;
RegisterClass(TBaum, THaus);
...
Baum := CreateObject(TBaum);
Haus := CreateObject(THaus);
(Such mal ggf. nach "Objekte nach Klassennamen erzeugen" o.ä.)


Einfacher kannst Du das natürlich auch selbst in die Hand nehmen:
Delphi-Quellcode:
function GetNewObject(aClassName: String): TObject;
begin
  if aClassName = 'TBaum' then
    Exit(TBaum.Create(Self));
  if aClassName = 'THaus' then
    Exit(THaus.Create(Self));
  if aClassName = 'Irgendwas' then
    Exit(TXYZ.Create(Self));
  Result := nil;
end;
Wenn alle Objekte von einer bestimmten Basisklasse abgeleitet werden, dann kann man geschickter Weise diese Basisklasse als Rückgabetyp der Funktion wählen.

Vielleicht nützt das als praktischer Ansatz.

Acuaplano 18. Jun 2013 19:54

AW: Objekte aus Textdatei erstellen
 
Zitat:

Zitat von stahli (Beitrag 1218936)
Vielleicht mal etwas allgemeiner:

Man kann seine Klassen registrieren, damit Objekte dieser Klasse zur Laufzeit erzeugt werden können. Das ist aber etwas aufwendig.

Ungefähr läuft das dann so ab:
Delphi-Quellcode:
// nur smbolischer Code, die genauen Funktionen habe ich nicht im Kopf
VorbereitungFuerBaum;
VorbereitungFuerHaus;
RegisterClass(TBaum, THaus);
...
Baum := CreateObject(TBaum);
Haus := CreateObject(THaus);
(Such mal ggf. nach "Objekte nach Klassennamen erzeugen" o.ä.)

Meinst Du damit das Thema mit GetClass & Co?
Das scheint nämlich ein guter Ansatz zu sein :). Jedenfalls hab ich mich eben auch noch daran versucht und nicht so ganz weit gekommen. Zunächst aber etwas zur "Basisklasse":
Die Hierarchie sieht im Moment so aus:

TImageSprite (vorgegeben durch Engine)
|
TObjekt
|
TUnbenutzbar
|
TBaum1 (hier würden dann auch die ganzen anderen Objekte stehen)

Ob das jetzt so sinnvoll ist weiß ich nicht, aber es beizubehalten wär schon ganz nett. Daher würde eine Basisklasse auch wegfallen. Man sollte aber wissen, dass alles ab TObjekt in einer anderen Unit ausgelagert ist.
Jetzt aber GetClass:
Was mache ich falsch? Wär das überhaupt so möglich?
Delphi-Quellcode:
CRef := GetClass('TBaum1');
Bei mir ist CRef immer leer (ohne direkte Registrierung der Klasse). Registrieren kann ich es nicht, weil er mir dann immer ne Fehlermeldung ausgibt, dass das keine "persistent class" ist. Aber laut dem Linkhier müsste es doch auch gehen, wenn die Klasse in der Unit deklariert wurde ("Form classes and component classes that are referenced in a form declaration (instance variables) are automatically registered when the form is loaded."), oder hab ich da was falsch verstanden?
Fragen über Fragen... Tut mir leid, wenn es jetzt ganz einfach ist, ich bin noch ziemlich unerfahren :lol:.

stahli 18. Jun 2013 20:33

AW: Objekte aus Textdatei erstellen
 
So, jetzt mit mehr Zeit etwas genauer.

Du kannst Klassen folgendermaßen registrieren (am Beispiel der TodPanel):

Delphi-Quellcode:
// Klassenunit
unit odPanel;

interface

type

  TodPanel = class(TPanel)
  ...
  end;

  TodPanelClass = class of TodPanel;

procedure Register; // wenn Du das Control für die IDE registrieren willst

implementation

procedure Register;
begin
  RegisterComponents('odControls', [TodPanel]);
end;

initialization

RegisterClasses([TodPanel]);

end.
Und dann zur Laufzeit:

Delphi-Quellcode:
FindClass('TodPanel');
odPanel := TodPanelClass(GetClass('TodPanel')).Create(Self);
In dieser Richtung solltest Du mal schauen.
Ach so, es hängt ggf. auch von Deiner Delphiversion ab.

Acuaplano 18. Jun 2013 21:11

AW: Objekte aus Textdatei erstellen
 
Ich nehme mal an, dass ich für meine Klassen stattdessen "RegisterClasses" nehmen soll.
Aber das Problem ist jetzt dabei, dass TImageSprite keine "persistent class" ist und ich die deshalb nicht registrieren kann. Ich versteh auch im Moment nicht wo jetzt der Sinn dahinter ist. Gäbe es denn ne andere Möglichkeit, als die Klassenvererbung zu ändern? Falls nicht, wovon müsst ich theoretisch TImageSprite ableiten?

jaenicke 18. Jun 2013 21:29

AW: Objekte aus Textdatei erstellen
 
Theoretisch von TPersistant, aber ich würde die Registrierung einfach bei einem eigenen Factory-Objekt machen wie schon geschrieben. Sprich:
Delphi-Quellcode:
MyFactory.RegisterClass(TBaum);
...
Und in MyFactory das ganze in ein TDictionary<string, TClass> werfen, wobei der Key der Klassenname als String der Klasse ist. Dann kannst du aus diesem Dictionary jederzeit über den Namen der Klasse die Klasse finden und kannst die entsprechend erzeugen.

Sir Rufo 18. Jun 2013 21:45

AW: Objekte aus Textdatei erstellen
 
@jaenicke :thumb:

So könnte die Klasse aussehen
Delphi-Quellcode:
unit Entity;

TEntity = class

end;

TEntityClass = class of TEntity;

TEntityFactory = class
class procedure RegisterClass( const AName : string; AClass : TEntityClass ); overload;
class procedure RegisterClass( AClass : TEntityClass ); overload;
class function Construct( const AName : string ) : TEntity;
// oder mit Generics
class function Construct<T : TEntityClass>( const AName : string ) : T;
end;
das Registrieren dann wie schon erwähnt
Delphi-Quellcode:
unit Baum;

uses
  Entity;

TBaum = class( TEntity )
end;

TBusch = class( TEntity )
end;

initialization

TEntityFactory.RegisterClass( 'Baum', TBaum );
TEntityFactory.RegisterClass( 'Busch', TBusch );

end.
und erzeugen dann mit
Delphi-Quellcode:
var
  LBaum : TEntity;
  LBusch : TEntity;

LBaum := TEntityFactory.Construct( 'Baum' );
LBusch := TEntityFactory.Construct( 'Busch' );

Acuaplano 20. Jun 2013 16:36

AW: Objekte aus Textdatei erstellen
 
Hab jetzt das mit dem "Dictionary" probiert. Nur heißt das bei Lazarus TFPGMap und hat bisschen andere Namen, wobei das in meinen Augen ansonsten wenig Unterschiede hat.
Eine Klasse einfügen funktioniert und auslesen geht glaube ich auch*.
Nur schmeißt der mir jetzt hier ne Fehlermeldung aus (External SIGSEGV):
Delphi-Quellcode:
Value := Dictionary.GetKeyData('Baum');
with TImageSprite(Dictionary.GetKeyData('Baum')).Create(AdSpriteEngine) do
begin
  x := i * 96;
  y := j * 96;
  z := 30;
end;
Ich persönlich würde ja einfach mal sagen, dass ich Zeile 2 einfach falsch geschrieben hab, aber als ich überprüfen wollte*, ob der überhaupt die Klasse ausliest kam mir der Inhalt von "Value" etwas verdächtig vor:
Code:
Value = TClass($00573390) = record{}
Ist das so schon richtig oder passt da das record nicht rein? Wäre ein record nicht eine Gruppierung aus Variablen, also keine Klasse?

Acuaplano 22. Jun 2013 15:43

AW: Objekte aus Textdatei erstellen
 
Okay, hat sich erledigt, ich hatte vergessen den "class of"-Befehl zu benutzen.
Delphi-Quellcode:
TUnbenutzbarClass = class of TUnbenutzbar

...
Delphi-Quellcode:
Dictionary := TFPGMap<String, TUnbenutzbarClass>.Create;

...
Delphi-Quellcode:
Value := Dictionary.GetKeyData('Baum');
TestObjekt := Value.Create(AdSpriteEngine);
Jedenfalls danke für die Hilfe! Wir werden uns dann später auch an den Streams und Factorys versuchen :D


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:52 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz