Roguelike-Diary (WIP ...)

Spiele, Demos, Grafikzeug und anderes unterhaltendes.
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Roguelike-Diary (WIP ...)

Beitrag von diceman »

Alles klar, danke! :)
Wie gesagt, ich habe den Codeblock kopiert, etwas für meine Programm-Struktur angepasst, und war einfach happy, daß es funktionierte.
Dann war das also nur ein Platzhalter!

//EDIT:
Ich bilde mir ein, daß ich mir über die Jahre einen recht sauberen, deskriptiven Programmier-Stil erarbeitet habe, aber es ist halt auch nur EIN Weg, der nach Rom führt; Programmcode von anderen Leuten zu lesen UND zu verstehen UND konsktruktiv für mich zu nutzen, das war schon immer einer meiner Defizite, bzw. ein Schwachpunkt, an dem ich arbeite; denn wenn ich schon etwas übernehme, will ich auch etwas daraus lernen!
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
dige
Beiträge: 1182
Registriert: 08.09.2004 08:53

Re: Roguelike-Diary (WIP ...)

Beitrag von dige »

Macht Spaß hier mitzulesen. Weiter so! :)
"Papa, mein Wecker funktioniert nicht! Der weckert immer zu früh."
Benutzeravatar
RSBasic
Admin
Beiträge: 8022
Registriert: 05.10.2006 18:55
Wohnort: Gernsbach
Kontaktdaten:

Re: Roguelike-Diary (WIP ...)

Beitrag von RSBasic »

+1
Aus privaten Gründen habe ich leider nicht mehr so viel Zeit wie früher. Bitte habt Verständnis dafür.
Bild
Bild
Benutzeravatar
mk-soft
Beiträge: 3695
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Roguelike-Diary (WIP ...)

Beitrag von mk-soft »

++1
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Roguelike-Diary (WIP ...)

Beitrag von diceman »

Actors, Baby!

*********** VIDEO ***********
Roguelike Diary 02: Actors
*******************************

Es gibt keine xPlayer,yPlayer-Koordinaten mehr, stattdessen ist JEDES Objekt ein Actor, inklusive dem Spieler (Actor vom Typ #monsterActor).
Ich mache keinen Unterschied mehr zwischen Türen, Schatztruhen und Monstern! Das hat den Vorteil, daß ich so recht einfach ein Agent System basteln kann, welches ein stabiles Fundament für Emergent Gameplay legt: jeder Actor kennt seine DisplaySprite()-Parameter (actor()\type, actor()\subtype[0], actor()\subtype[1], usw), x/y-Koordinaten, ob er Kollision hat, ob er Sichtlinie blockt, ob er sich bewegen kann, ob er eine KI hat, usw. Oder anders ausgedrückt: Wenn eine Tür Lebenspunkte hat, kann man sie auch mit Gewalt aufbrechen. :D

Dazu braucht es einen *player-Pointer, mit dem ich gezielt die Parameter des Spieler-Actors abrufen kann.

Ich arbeite mit 4 verschiedenen Arrays:
• worldMap(#xMax,#yMax)
• collisionMap(#xMax,#yMax)
• binaryMap(#xMax,#yMax)
• bresenMap(#xMax,#yMax)
Die worldMap() beinhaltet die Level-Architektur (0 = Mauer, 1 = freies Feld) und wird u.a. in der Zeichenroutine benutzt um den jeweiligen Ausschnitt rund um die gegenwärtige KameraCenter-Position zu zeichnen; Felder außerhalb des Viewports werden erst gar nicht angesteuert.
Die collisionMap() beinhaltet Informationen über Kollisionen: 0 = keine Kollision, 1 = Kollision. Das ist notwendig, da es Actors mit wechselnden States gibts, z.B. offene und geschlossene Türen. Jedesmal wenn sich ein Actor bewegt (Spieler/Monster), wird das aktuelle Feld in der collisionMap() um 1 herabgesetzt, und das Zielfeld kriegt +1. Das ergibt Sinn, wenn ich z.B. fliegende Monster haben möchte, die sich auf ein Feld bewegen dürfen, wo sich bereits ein Actor mit Kollision befindet. Jetzt hat dieses Feld in der collisonMap() +2; und wenn das Monster weiterfliegt, wird das Startfeld um 1 herabgesetzt und verliert dabei seine Kollision nicht (2-1 = 1)! Actors, mit denen man gar nicht interagiert, z.B. Bäume oder Säulen, haben Kollision -1, d.h. ich kann so gezielt das rote Interaktions-Rechteck ausblenden, wenn die Maus auf ein solches Feld bewegt wird.
Die binaryMap() ist von Relevanz, wenn bei LoS-Berechnungen und Pfadsuche der gegenwärtige World State in 0en und 1en übersetzt wird: mit CopyArray() wird zunächst der Inhalt der worldMap() kopiert, und anschließend mit ForEach actor() alle Actor-Informationen von Relevanz (Kollision bzw. Sichtlinie blockend) zur binaryMap() hinzugefügt. Es gibt zwei Prozeduren, die eine binaryMap() auslesen und verarbeiten können: getPath(...) und getBresenham(...).
Die bresenMap() enthält Informationen über Sichtlinie und LoS-blockende Felder, und wird nach jedem Schritt aktualisiert.

Außerdem habe ich eine sogenannte actorFactory() - das ist eine Prozedur, in der Actors erstellt werden. Warum ist notwendig?
Ich muß beim Zeichnen von Actors auf die "Zeichentiefe" achten - z.B. muß ein Monster, welches in einer Tür steht, immer sichtbar bleiben, d.h. die Tür muß immer zuerst gezeichnet werden. Und um dies möglichst elegant und effizient zu lösen, dafür habe ich die actorFactory(): Jeder Actor hat einen Parameter drawPriority. Actors mit drawPriority = 0 werden immer zuerst gezeichnet. Und damit ich nicht in meiner linkedList hin und herspringen muß, was verdammt viel Zeit bräuchte, werden die Actors gleich bei Erstellung nach dem Parameter drawPriority sortiert; ich muß also beim Zeichnen die actorList() nur einmal von vorne nach hinten abklappern, und führe trotzdem alle Zeichenoperationen in korrekter Reihenfolge durch:

Code: Alles auswählen

Procedure actorFactory(type,subtype0,drawPriority,visible,collision,blockLoS,x,y)
	Define a
	Dim subtype(#actorSubtypeMax)
	subtype(0) = subtype0
	
	LastElement(actor())
	If ListSize(actor()) <> #Null And actor()\drawPriority > drawPriority
	;funktioniert, weil <>#Null-Abfrage zuerst abgearbeitet wird (kein "Invalid Memory Access" bei leerer Liste)
		ForEach actor()
			If actor()\drawPriority >= drawPriority
				InsertElement(actor())
				Break
			EndIf
		Next
	Else
		AddElement(actor())
	EndIf
	actor()\type = type
	For a = 0 To #actorSubtypeMax
		actor()\subtype[a] = subtype(a)
	Next
	actor()\drawPriority = drawPriority
	actor()\visible = visible
	actor()\collision = collision
	actor()\blockLoS = blockLoS
	actor()\x = x
	actor()\y = y
	
	locatePlayer()
EndProcedure
Anschließend muß ich den *player-Pointer zurück auf den Spieler-Actor setzen, da nach addElement() die Speicheradresse nicht mehr gültig ist:

Code: Alles auswählen

Procedure locatePlayer()
	ForEach actor()
		If actor()\type = #monsterActor And actor()\subtype[0] = 0
			*player = @actor()
			ProcedureReturn
		EndIf
	Next
EndProcedure
Und ich muß in der Lage sein, erstellte Elemente wieder zu löschen, ohne daß meine Actor-Library "kaputt" geht ...
Dazu rufe ich killActor(@actor()) auf - so kann ich gewährleisten, daß im Anschluß der *player-Pointer wieder hergestellt wird:

Code: Alles auswählen

Procedure killActor(*deletePointer)
	ChangeCurrentElement(actor(),*deletePointer)
	DeleteElement(actor())
	locatePlayer()
EndProcedure
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Jan125
Beiträge: 31
Registriert: 23.06.2013 06:26
Computerausstattung: Nicht lachen. Atom Z3775, 2GiB RAM, Win8.1.

Re: Roguelike-Diary (WIP ...)

Beitrag von Jan125 »

Pointer sind schön und elegant, bei meinem ersten PureBasic-Project hatte ich einfach 'nen unstrukturierten, mehrdimensionalen Array für Alles...

Zu locatePlayer():
Weiß jetzt nicht, wie dein Code aufgebaut ist, aber ab einer bestimmten ListSize könnte das etwas dauern, den Spieler zu finden (vor allen Dingen wenn du das ganze via SortList() sortierst).
Wenn du den Pointer vom Spieler als Variable speicherst brauchst du diese Prozedur nicht wirklich, Speicherpositionen bleiben gleich.

EDIT: Siehe Post von Bisonte.
Zuletzt geändert von Jan125 am 11.03.2018 13:06, insgesamt 1-mal geändert.
Wer braucht schon Unicode? PB5.24LTS
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Roguelike-Diary (WIP ...)

Beitrag von diceman »

Danke für Feedback. :-)
Gib mir mal kurz ein Beispiel, was du mit "pointer als Variable" meinst! Würde sehr gerne auf locatePlayer() verzichten!

Um List-Search mache ich mir derzeit keine Probleme - in meinem Blitzbasic-Projekt hatte ich wesentlich schlechter optimierte Routinen, und ins Gewicht gefallen ist das nicht; und von der Geschwindigkeit her tun sich Blitz und PB nicht viel - wie gesagt, das wird ein turn-based Game. Und wenn ich später mal mehrere Dungeon-Ebenen haben möchte, habe ich eh vor, beim Verlassen und Betreten die Ebenen-Objekte auf Festplatte zwischenzuspeichern, bzw. zu laden, und nur mit "lokalen" Variablen und Objekten zu arbeiten.
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
Bisonte
Beiträge: 2427
Registriert: 01.04.2007 20:18

Re: Roguelike-Diary (WIP ...)

Beitrag von Bisonte »

Ich denke er meint, dass Du den Pointer *player, der wohl auf ein ListenElement zeigt, in einer Variable zwischenspeicherst,
damit du nicht den Player in der List suchen musst.

Die Adresse eines ListenElements bleibt immer gleich. egal was du damit anstellst (ausser du löscht das Element).

Also bei der Erstellung des Players einfach : Player = *player und dann brauchst du nur noch, wenn das Element gebraucht wird :
*player = Player .... Ohne suchen in der Liste.
PureBasic 6.04 LTS (Windows x86/x64) | Windows10 Pro x64 | Asus TUF X570 Gaming Plus | R9 5900X | 64GB RAM | GeForce RTX 3080 TI iChill X4 | HAF XF Evo | build by vannicom​​
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Roguelike-Diary (WIP ...)

Beitrag von diceman »

Hmm, ich glaube ich habe da gerade einen Denkfehler, bzw. habe irgendwie Mist in meinem Übungscode gebaut ... :?
Jetzt gerade nochmal alles durchgetestet, und es scheint in der Tat so zu sein, daß der *pointer auf das Element erhalten bleibt, ganz egal ob ich Elemente aus der Liste lösche oder zufüge, oder an welcher Stelle. ChangeCurrentElement(foo(),*pointer) wechselt weiterhin aufs korrekte Element.
Dann wäre also die ganz locatePlayer()-Prozedur Quatsch, bzw. überflüssig. Yay. Und, ja, der *pointer ist Global und sollte, wenn möglich, auch bestehen bleiben - auf den muß ich ja quasi die ganze Zeit zugreifen, weil er den Ausgangspunkt für die drawStuff()-Routine gibt.
Danke für den Hinweis!

//EDIT:
Habe gerade mal testweise eine Map mit 1000x1000 Feldern erstellt und 22k actor()-Elementen ... selbst im Debug-Mode sind keine Lags zu vermerken; Bewegung von Punkt zu Punkt ist weiterhin (almost) instant. Yay. <) Es zahlt sich aus, daß ich die Pfadsuche nicht nach jedem Schritt neu aufrufe, sondern nur einmal beim Linksklick, anschließend die Pfadknoten in einer Linked-List zwischenspeichere, und dann, solange die Maustaste gedrückt bleibt, immer den nächsten Knoten aufrufe.

//EDIT:
Bei einer worldMap mit 2500*2500 Feldern und 135k actor()-Elementen siehts schon etwas anders aus, da gerät das Programm ins Schwitzen. Aber solche Dimensionen wären ja auch schwachsinnig, wollte ich das ernsthaft implementieren. ;-) 100x100 hat sich gut bewährt, und davon wird auch nur maximal ein Fünftel mit begehbaren Feldern gefüllt sein. Später kommen ja noch die KI-Modelle der Monster hinzu, die nach jedem Schritt aktualisiert werden wollen.
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Antworten