Willkommen ~Gast!
Registrieren || Einloggen || Hilfe/FAQ || Staff
Probleme mit der Registrierung im Forum? Melde dich unter registerEin Bild.
Autor Beitrag
000
27.11.2007, 20:53
Exorretar



Hallo,
ich arbeite derzeit an einem kleinen Spiel, welches ich zu Übungszwecken
in Delphi umsetze. Zunächst einmal der Code:

Hauptunit:
unit uTD;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, uGegner, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;

var gegner : array[1..40] of TGegner;
Form1 : TForm1;

const formWidth = 800;
formHeight = 600;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.Width := formWidth;
Form1.Height := formHeight;
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
gegner[1] := TGegner.create(100,1);
gegner[1].image := TImage.create(Form1);
gegner[1].image.Picture := gegner[1].picture;
gegner[1].image.width := 32;
gegner[1].image.height := 32;
gegner[1].image.top := 32;
gegner[1].image.left := 32;
end;

end.

Unit uGegner

unit uGegner;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;

type
TGegner = class // Gegner erzeugen
public
image : TImage; // Bild Darstellung
picture : TPicture; // BMP Datei für Image Komponente
xCoord,yCoord : integer; // Position
health,armor : integer; // Lebenspunkte / Rüstung
procedure move(x,y : integer); // Zu Position Bewegen
procedure takeDamage(damage : integer); // Schaden Nehmen
constructor create(healthpoints,ruestung : integer); overload; // Neuen Gegner erzeugen & Bild laden
end;

implementation

procedure TGegner.move(x,y : integer);
begin
xCoord := x;
yCoord := y;
end;

procedure TGegner.takeDamage(damage : integer);
begin
if damage > armor then health := health - (damage-armor)
else health := health - 1;
if (health = 0) or (health < 0) then destroy;
end;

constructor TGegner.create(healthpoints,ruestung : integer);
begin
xCoord := 32;
yCoord := 800 div 2 - 32;
health := healthpoints;
armor := ruestung;
picture := TPicture.Create;
picture.LoadFromFile('graphics/enemy.bmp');

end;

end.

Wie dem ganzen zu entnehmen ist, soll beim drücken von Button1 (testweise)
ein 'Gegner' erzeugt werden und die zugehörige Image Komponente dargestellt werden. Dies ist allerdings nicht der Fall. Der Rest der Klasse wird korrekt erzeugt. Ich denke dass der Fehler soweit einzuschränken ist, dass in der Prozedur TForm1.ButtonClick etwas nicht stimmt.Attribute wie Gegner[1].image.Visible = true zu setzen habe ich bereits vergeblich versucht,es ändert sich nichts.

Hat jemand einen Vorschlag?

--

zum Seitenanfang zum Seitenende Profil || Suche
001
27.11.2007, 21:06
rawe



gegner[1].image.parent := Form1; ?

(image->Parent=Form1; funktioniert jedenfalls beim Borland C++ Builder und da kann man Pascal/Delphi-Code von den Befehlen her ~ 1:1 übernehmen)

--

O o
/¯¯/_o___o___o___o____o__
\__\¯¯o¯¯¯o¯¯¯¯¯¯o¯¯¯¯¯¯o
i'm doctoroctogonnafuckinggonnapus blaaaaaaaaaaargggggg-


Dieser Beitrag wurde am 27.11.2007 um 21:14 von rawe bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
002
27.11.2007, 21:10
Exorretar



Ich werds gleich mal versuchen , aber durch
gegner[1].image := TImage.create(Form1); passiert dieser Schritt doch schon automatisch :/

Edit : Du bist mein Held :>

--


Dieser Beitrag wurde am 27.11.2007 um 21:11 von Exorretar bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
003
27.11.2007, 23:56
hausi



Du hast das Parent / Owner Konzept der VCL falsch verstanden. Owner gibt an, wer für die Freigabe des Objektes zuständig ist. Wenn du Create(Form1) machst, wird beim schliessen vom Form1 auch das Bild freigegeben. Den Parent musst du allerdings immer manuell setzen, damit eine Komponente gezeichnet wird, da der Parent ja auch z.B. ein Panel sein kann.

--

zum Seitenanfang zum Seitenende Profil || Suche
004
28.11.2007, 23:15
Exorretar



So, gibt ein weiteres Problem , und ich bin mit meinem Latinum am Ende, nach einer Stunde Lösungsansätze für so ein kleines Problem zu suchen frustiert massiv.

Wie immer erstmal der (relevante) Code:

Unit TGegner

procedure TGegner.followPath( p : TPath ; var leben : integer ; count : integer);
var x,y : integer;
begin
if (ziel > p.length) then
begin
dec(leben);
self.image.Visible := false;
destroy; // < Hier hängt sich das Programm auf, soll heissen, beim auskommentieren der Zeile läuft es einwandfrei.
end
else
begin
x := p.path[ziel].x;
y := p.path[ziel].y;
if xCoord < x then inc(xCoord);
if xCoord > x then dec(xCoord);
if yCoord < y then inc(yCoord);
if yCoord > y then dec(yCoord);
if (xCoord = x) and (yCoord = y) then inc(ziel);
updateGegner;
end;
end;

Destroy ist wie man erkennen kann dabei der Standard-Destruktor. Da durch die Bedingung in der IF Abfrage das ganze keinesfalls auf nil verweist, bringt
eine .Free Anweisung auch nichts, selbes Resultat.

Erklärung am Rande : ein 'TGegner' folgt einem 'TPath' bestehend aus einem Array von 'TWaypoints' und soll sich beim erreichen des letzten existierenden Wegpunktes selber zerstören.

Hauptunit

procedure TForm1.Timer1Timer(Sender: TObject);
var i : integer;
begin
for i := 1 to 40 do if Gegner[i] <> nil then Gegner[i].followPath(pfad,leben,i);
lbLeben.Caption := concat('Lifes : ',inttostr(leben));
end;

Das ist die einzige Prozedur ausserhalb die die Methode aufruft.
Die 'Count' Variable könnt ihr dabei ausser acht lassen, die wird wieder entfernt da sie mittlerweile unnötig geworden ist.
Hat jemand einen Lösungsansatz ? ;)

--


Dieser Beitrag wurde am 28.11.2007 um 23:24 von Exorretar bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
005
29.11.2007, 16:34
Exorretar



Niemand eine Lösung parat?

--

zum Seitenanfang zum Seitenende Profil || Suche
006
29.11.2007, 16:49
hausi



Dein Problem ist warscheinlich (lässt sich ohne den kompletten Code nicht definitiv sagen) wieder ein Verständnis und auch ein kleines Kommunikationsproblem.
Das Kommunikationsproblem: Ich gehe davon aus, dass sich das Programm nicht in der Zeile mit Destroy aufhängt, sondern später crasht.
Das Verständnisproblem: Wenn du Destroy aufrufst, wird das Memory freigegeben. Wenn du allerdings noch Variablen auf dieses Objekt hast, dann zeigen diese weiterhin auf den alten Ort und laufen solange problemlos, bis dieses Memory wieder verwendet wird.
Was ich damit sagen will: Die folgende Zeile:
Quellcode:for i := 1 to 40 do if Gegner[i] <> nil then Gegner[i].followPath(pfad,leben,i); wird auch für einen bereits freigegebenen Gegner noch true zurückliefern, da die Referenz nicht nil ist. Du müsstest dafür SysUtils.FreeAndNil verwenden oder die Referenz manuell auf nil setzen. Ich würde dir auf jeden Fall empfehlen, die Freigabe von Objekten wenn immer möglich ausserhalb des eigenen Codes zu machen, da sonst in speziellen Fällen auch der Stack zerschossen werden könnte.

--

zum Seitenanfang zum Seitenende Profil || Suche
007
29.11.2007, 16:59
Exorretar



Ich werds mal versuchen umzusetzen, allerdings habe ich das manuelle nil-setzen auch schon versucht mit dem selben Resultat.

Edit1: Freeandnil -> Selbes Resultat.

Jetzt könnte ich ja noch versuchen das ganze ausserhalb der Klasseneigenen Prozedur aufzurufen ...

Edit2 : Jup , so klappt es,wieder unter Verwendung von freeandnil.
Nur scheint freeandnil nicht alle Komponenten der Klasse zu entfernen, die TImage Komponente etwa wird weiterhin danach angezeigt.Naja,muss ich das halt manuell machen.Danke für die Hilfe ;)

--


Dieser Beitrag wurde am 29.11.2007 um 17:15 von Exorretar bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
008
29.11.2007, 17:51
hausi



Du solltest dir mal einige OOP-Basis-Tuts durchlesen. Korrekt währe es so:
Quellcode:destructor TGegner.Destroy; override;
begin
  image.Free; // Der Gegner gibt das Bild frei, wenn er selbst freigegeben wird
end;

constructor TGegner.Create(...);
begin
  image:= TImage.Create(nil); // Da der Gegner selbst für das Freigeben verantwortlich ist, kann der Owner auf nil gesetzt werden
end;

// verwendung:
gegner:= TGegner.Create(...);
...
FreeAndNil(gegner); // damit nicht später ausversehen auf den bereits freigegebenen Gegner zugegriffen wird.
Was sicher nie klappen wird ist FreeAndNil(self), denn das würde nur self.Free; self:= nil; machen, was die Referenz von Aussen bestehen lässt.

--

zum Seitenanfang zum Seitenende Profil || Suche
009
29.11.2007, 21:43
Exorretar



Wie ich es hasse, dass man selber kleine Fehler u.U so schlecht entdeckt :/

Ein neues Problem ist aufgetaucht, allerdings dürfte diesmal lediglich ein Logikfehler vorhanden sein. First of all, wie immer , der Code, diesmal übersichtlich als img:

Hier wird das Zielsystem der Towers verwaltet.
Das ganze klappt soweit auch -> nur aber immer für einen Tower gleichzeitig, die Position im array ist egal und auch wieviele Towers vorhanden sind.Dadurch denke ich ist es auch unnötig die anderen Klassen/Funktionen zu Posten, da der Fehler ja definitiv hier liegen muss.

Nach X-fachen durchchecken ist mir immer noch keine Fehlerquelle aufgefallen.
Wieder jemand einen Ansatz ?

Edit: Durch probieren habe ich rausgefunden, dass beim ERSTEN zu tötenden gegner noch alle Towers problemlos funktionieren,danach nur noch der erste.

Edit2: Ah , Problem mittlerweile gefunden. Wenn 1 Turm einen Gegner zerstört,während die anderen Türme diesen aber noch als Ziel haben, arbeiten diese nicht mehr.

--


Dieser Beitrag wurde am 29.11.2007 um 22:52 von Exorretar bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
010
29.11.2007, 23:01
hausi



Debuggen ist hier die wohl einzige Lösung. Wir kennen deinen Code nicht und können dir also höchstwahrscheinlich auch nicht sagen, weshalb das Problem auftritt und wodurch es verursacht wird.
Ausserdem würde ich dir stark empfehlen, dich ein wenig damit zu beschäftigen, den Code etwas schöner zu schreiben und logische Operatoren zu verwenden. Als Beispiel die letzte for-Schlaufe:
Quellcode:var
  t: TTower;
  e: TGegner;
// ...
for i:= 1 to 20 do begin
  t:= towers[i];
  if Assigned(t) and (t.ziel > 0) then begin // dritte Bedingung sollte unnötig sein
    e:= gegner[t.ziel];
    sounds[3].Play;
    if not e.killshot(t.dmg) then begin
      t.fireonenemy(e, t.dmg);
    end else begin
      e.hp.Free; // Immer Free verwenden
      e.image.Free; // Und wie oben schon geschrieben gehören diese Frees in den Destructor!
      FreeAndNil(e);
      t.ziel:= 0;
    end;
  end;
end;
So ist der Code viel besser lesbar -> es passieren weniger Fehler. Ausserdem läuft das ganze performanter und ist leichter erweiterbar / wartbar.

Übrigens: Falls du weiterhin meine gut gemeinten Hinweise zur Verbesserung deines Codes so ignorieren solltest, werde ich in Zukunft nicht mehr antworten.

--

zum Seitenanfang zum Seitenende Profil || Suche
011
03.12.2007, 13:14
Exorretar



@ Hausi: Ich ignoriere deine Vorschläge doch nicht, ich versuche sie doch mit einzuarbeiten.

Kurze Frage: In C++ gibt es eine Funktion, um die Millisekunden die seit Programmstart vergangen sind, aufzurufen. Genau so etwas bräuchte ich nun für Delphi, um das Timing hinzubekommen. Die TTimer sind in der Hinsicht sehr unzuverlässig, da bspl. ein Timer mit dem Intervall 1 (1ms) nicht in jeder Millisekunde ausgelöst wird. GetTime liefert nur die komplette Systemzeit, was nicht mal so ein Problem wäre, wenn er nicht nur Sekunden anstatt auch Millisekunden liefern würde.

Edit: Ich versuche es mal mit MilliSecondsBetween.

--


Dieser Beitrag wurde am 03.12.2007 um 13:15 von Exorretar bearbeitet.
zum Seitenanfang zum Seitenende Profil || Suche
012
03.12.2007, 14:38
KhanRKerensky



Unter Delphi müsste es eigentlich auch GetTickCount geben. Damit kann man eigentlich auch Zeiten messen. Damit hat man eine Auflösung in die Millisekunden, was allerdings nicht heißt, das das Teil auch auf Millisekunden genau ist. Ich mein das man dort alle 15ms einen neuen Wert bekommen kann.

Wenns genauer sein soll, dann google mal nach QueryPerformanceCounter. Lässt sich nicht ganz so einfach benutzen ist dafür genauer als du es brauchen wirst.

--

"[...] you're going to burn in a very special level of Hell. A level they reserve for child molesters and people who talk at the theater." - Book

zum Seitenanfang zum Seitenende Profil || Suche