[Tutorial/Doom3] zufällige Spawnpoints + zufällige Gegner

MacX

Light Guard
Ich bin mir nicht sicher, ob es diese Thematik hier schon einmal irgendwo gab, aber ein D3-Tutorial habe ich jetzt nicht dazu gefunden.

Ich will auch vorneweg klarstellen, dass hier in diesem Tutorial mit "zufällig" eine zufällige Auswahl von vorher definierten Zielen und Monstern gemeint ist.

Der Eine oder Andere hatte vielleicht schon einmal daran gedacht, das Spielerlebnis in seiner Mod zu randomisieren. In diesem Tutorial möchte ich demonstrieren, wie man das ganz einfach umsetzen kann.

Zuallererst brauchen wir eine kleine Testmap mit einem trigger_multiple und ein drei Spawnpoints. Der trigger_multiple ist keine Pflicht. Man kann stattdessen auch einen normalen trigger_once nehmen, wenn das Ereignis nur einmal ausgeführt werden soll. Für die Festlegung der Spawnpoints benötigen wir die Entität target_null. Deren Einsatzzweck mag ursprünglich ein anderer gewesen sein, aber für unser Vorhaben eignet es sich auch ganz gut.


Abbildung 1

Über die Tastenkombination STRG+K verbinden wir den trigger_multiple mit den drei target_null. Alternativ kann man die Targets auch direkt als key/value-Paar beim Trigger eintragen.

se8zaaxj.png

Abbildung 2

So weit, so gut. Unsere möglichen Spawnpoints sind definiert. Jetzt müssen wir festlegen, welche Arten von Monstern gespawned werden können. Hierfür greifen wir auf eine ältere Arbeit eines Benutzers von Doom3Worldhttp://www.doom3world.org/phpbb2/viewtopic.php?f=1&t=7140 zurück. Wir legen zunächst die Anzahl der Monstertypen fest. Wir möchten für unsere Testmap eine Auswahl von drei Monstertypen haben. Die target_null bekommen jeweils den Schlüssel num_monster_types mit dem Wert 3.
Des Weiteren bekommt jeder target_null drei Schlüssel monsterX mit den Namen der Monster als Werte. Das X soll für unseren Fall durch einen Wert von 0-2 ersetzt werden. Daraus ergibt sich monster0, monster1, monster2.
Als Monstertypen wählen wir monster_zombie_fat, monster_zombie_maint und monster_demon_tick.

uhzxus69.png

Abbildung 3

Zwei Schlüssel müssen wir jetzt nur noch zum trigger_multiple hinzufügen. Dazu kommen wir am Ende. Jetzt schreiben wir erst einmal unser random target spawning-Skript.

Die Funktion, die der Trigger später aufrufen soll, nennen wir trigger_randomtarget.
Gleich als erstes lassen wir uns über das Skript-Event randomTarget ein Target des Triggers geben, welches zufällig ausgesucht wird. Das sieht wie folgt aus:
Code:
entity target = $trigger_multiple_1.randomTarget(0);
Das Skript-Event randomTarget kann als Parameter noch ein Target bekommen, das ignoriert werden soll. Das brauchen wir jetzt aber nicht, darum übergeben wir eine 0.
Als nächstes greifen wir auf den Wert des Schlüssels num_monster_types vom eben erhaltenen Target zu. Es wird ein Zufallswert im Bereich von 0 bis num_monster_types - 1 ermittelt.
Code:
float rand = int( sys.random( target.getIntKey( "num_monster_types" ) ) );
Der Aufruf von int( ... ) ist notwendig, damit wir einen ganzzahligen Wert erhalten. Die möglichen Werte sind nach unserer Vorgabe also 0, 1 und 2.

Habt ihr schon eine Ahnung, wofür der Zufallswert benötigt wird? Ja, genau! Für die Auswahl eines der im target_null angegeben Monstertypen.
Code:
string monster_type = target.getKey( "monster" + rand );
Der String monster_type sollte jetzt den Namen eines, der von uns vorgegebenen, Monstertypen enthalten.
Zur Auslagerung der Spawn-Funktionalität rufen wir jetzt eine von uns definierte Funktion spawn_monster auf, die als Parameter den Namen des Monstertypen und die Position, an der das Monster gespawned werden soll, bekommt. Über target.getOrigin() holen wir uns die Position des am Anfang ermittelten target_null, damit wir an dieser Stelle unser Monster spawnen können.
Code:
spawn_monster( monster_type, target.getOrigin() );
In unserer spawn_monster-Funktion, müssen wir am Anfang die Spawn-Argumente setzen, die unter anderem festlegen, wie das Monster erscheinen soll.
Code:
sys.setSpawnArg( "origin", target.getOrigin() );
sys.setSpawnArg( "hide", 1 );
sys.setSpawnArg( "neverdormant", 1 );
sys.setSpawnArg( "teleport", 1 );
Was die einzelnen Spawn-Argumente genau bedeuten, könnt ihr zum Beispiel in der Datei monster_default.def im def-Ordner (base/def) nachlesen.
So, wir nähern uns dem Ende.
Wir verwenden nun das Skript-Event spawn, um unser Monster zu erstellen.
Code:
entity monster = sys.spawn( monster_type );
Jetzt haben wir im Grunde den Zustand, den man hat, wenn man ein Monster fest in der Map platziert hätte. Es ist unsichtbar, und soll beim Triggern teleportiert werden.
Bevor wir nun das Monster triggern, überprüfen wir noch, ob sich an der Position, an der es erscheinen soll, nicht schon ein anderes Objekt befindet.
Code:
if( monster.canBecomeSolid() ) {
	sys.trigger( monster );
} else {
	monster.remove();
}
Wenn alles ok ist, können wir nun das Monster triggern, ansonsten wird es wieder entfernt.
Verblüffend, wie einfach das alles funktioniert, nicht wahr?

Jetzt müssen wir unserem trigger_multiple nur noch den Aufruf von trigger_randomtarget mitgeben. Um ein ununterbrochenes Spawnen zu verhinden, packen wir auch gleich noch eine Verzögerung von 5 Sekunden dazu.

64j8cdun.png

Abbildung 4

Et voilà. Wir sind fertig. Jetzt könnt ihr eure Map kompilieren und starten (sofern ein info_player_start und etwas Licht darin zu finden ist).

Das komplette Skript seht ihr noch mal hier:
Code:
void spawn_monster( string monster_type, vector origin )
{
	sys.setSpawnArg( "origin", origin );
	sys.setSpawnArg( "hide", 1 );
	sys.setSpawnArg( "neverdormant", 1 );
	sys.setSpawnArg( "teleport", 1 );

	entity monster = sys.spawn( monster_type );

	if( monster.canBecomeSolid() ) {
		sys.trigger( monster );
	} else {
		monster.remove();
	}	
}

void trigger_randomtarget()
{
	entity target = $trigger_multiple_1.randomTarget(0);
	float rand = int( sys.random( target.getIntKey( "num_monster_types" ) ) );

	string monster_type = target.getKey( "monster" + rand );

	spawn_monster( monster_type, target.getOrigin() );
}

void main()
{
}

Es gibt sicher noch die eine oder andere Option, die man im Skript einbauen könnte. Es wäre auch denkbar das Ganze unabhängig von einem bestimmten Trigger zu gestalten. Wir ihr im Skript seht, wird direkt auf die Entität trigger_multiple_1 zugegriffen. Mit Skript-Objekten wäre das nicht auf einen Trigger begrenzt. Aber dazu vielleicht mehr in einem anderen Tutorial.

Im Anhang findet ihr noch eine Testmap. Das PK4-Archiv müsst ihr nur in den base-Ordner legen und die Map per "map randomtarget.map" starten. Die gelbe Markierung am Boden zeigt euch, wo der trigger_multiple liegt. Wenn ihr immer wieder über die Markierung geht, wird ein Monster nach dem eben beschriebenen Verfahren gespawned.

Ich stehe für Fragen und Verbesserungsvorschläge zur Verfügung.

Siehe auch:
Free monster spawning script


Anhang:
View attachment randomtarget.pk4
 
Last edited:

Tombery

Crash
Das ist eine richtig geniale Technik und ein super Tutorial, genau das was man sich immer gewünscht hatte, vielen dank Macx!
Muss ich bald umbedingt mal ausprobieren, besonders mit dem Trigger_one lässt sich viel mehr anstellen.
 

MacX

Light Guard
Danke für das Lob. Ich will noch mal anmerken, dass die Verwendung von target_null-Entitäten nur ein Vorschlag ist. Im verlinkten Thread "free monster spawning script" wird beispielsweise ein func_static verwendet. Es ist relativ egal, was man nun benutzt. Es kommt im Wesentlichen auf die benutzerdefinierten Keys (num_monster_types und monsterX) an.
 
Top