Arma 3 – Spawn infini d’unités

Une question qui est souvent posée, comment faire spawn des ias, véhicules ou autre. L’arrière pensé étant souvent de gérer une vague quasi infinie.

Pour répondre direct, l’infini c’est le mal ! Mais il est possible de l’apprivoiser avec quelques règles.

Faire apparaitre une unité

Commençons par la base, faire spawn une unité.

  • createGroup pour créer un groupe
  • createUnit pour créer une ia
  • createVehicle pour créer un objet, véhicules certes mais aussi des bâtiments, ou tout autre objet.
  • createAgent pour créer un agent, qui est un ia simplifié, les animaux par exemple (poulet, serpent, …). Permet de prendre moins de performance, et d’en faire apparaitre d’autant plus.

Dans cet article, nous n’utiliserons que les 2 premiers, mais il est toujours utile de savoir que les autres existent, même s’il y a peu de chance que vous utilisiez les agents.

Pour créer notre unité, il nous faut dans tous les cas créer un groupe, pour ça rien de plus simple :

_group = createGroup east

A noter que la documentation précise qu’il y a une limite de groupe à 288.
Ainsi qu’une nouvelle commande qui permet de demander la suppression des groupes vides :

_group = createGroup [east, true];

Pour l’unité il y a plusieurs syntaxes mais restons simple pour le moment

« B_RangeMaster_F » createUnit [position player, _group];

On crée une unité du camp Opfor juste à côté de nous, avec le classname « B_RangeMaster_F »
Facile non ?
Pour vous test je vous conseille quand même de créer une unité de votre camp pour ne pas qu’elle vous tire dessus.

« B_RangeMaster_F » createUnit [position player, group player];

Classname quésako ?

On est content de savoir créer une unité, mais comment choisir celle qu’on désire ?
Le classname c’est l’identifiant unique d’un objet, que ce soit le modèle d’une maison, d’un objet, … ou d’une unité.

Dans Eden (l’éditeur de mission) vous avez déjà pu remarquer une petite fenêtre noire quand vous restiez quelques secondes sur un objet (unité, véhicule, objet, …)
Il montre deux choses : le nom et le classname.

Pas très pratique, de devoir le recopier ?
Si vous poser votre unité (ou objet) vous pouvez clicker droit > Journal > Classes.
Rien ne va se passer, mais vous pourrez alors coller (CTRL + V) directement le classname.

Pour la petite explication, chaque mods fait un peu à sa sauce la structure de leur classname, mais le plus souvent il est respecté :

  • rhsusf_m113d_usarmy_M240
  • B_soldier_UGV_02_Demining_F
  • CUP_I_LR_Transport_AAF
  • Le mod d’où provient l’objet _ L’objet en question (en anglais) _ Des paramètres (le skin, l’équipement, une variante, … )

Maintenant qu’on sait récupérer l’identifiant de ce qu’on veut créer, par exemple pour le porteur de munitions (ammo Bearer)

« B_Soldier_A_F » createUnit [position player, group player];

Il reste à faire apparaitre une escouade complète !

Spawn d’un groupe !

Tout un groupe, ce n’est pas très compliqué avec un petit ForEach

{
    // Faire quelque chose avec _x
} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];

Dans l’exemple qui ne fait strictement rien, je vous montre la structure de la commande, à savoir qu’on passe à la fin un tableau

[« B_Soldier_A_F », « B_soldier_AAR_F », « B_engineer_F »]

Qui va être lu élément par élément, par le contenu du script

{
    // Faire quelque chose avec _x
}

En commentaire la variable _x dont je parle, contient l’élément du tableau qu’on est entrain de gérer.
La première itération on aura : _x = « B_Soldier_A_F »
La seconde _x = « B_soldier_AAR_F »
etc…

Testons avec ma commande préférée pour débugger systemChat qui va afficher dans le chat (par défaut en bas à gauche de l’écran) ce qu’on lui passera en paramètre.

{
    systemChat _x;
} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];

On a bien chaque élément un par un du tableau et dans l’ordre.

Pour faire spawn un groupe, il faut quand même penser qu’ils sont tous dans le même groupe !

_group = createGroup [blufor, true];
{
    _x createUnit [position player, _group];
} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];

Relisons tranquillement, j’imagine que vous avez déjà compris ce qui se passait.

_group = createGroup [blufor, true];

On crée le groupe en dehors de notre boucle pour ne pas la créer autant des fois qu’il y a d’unité, mais bien une seule et unique fois avant.

_x createUnit [position player, _group];

On crée une unité avec le classname provenant du tableau via _x.
Le position player permet de récupérer notre position actuelle. On aurait pu la récupérer en dehors de la boucle une fois pour toute via une variable.
Enfin _group étant la variable qui contient le groupe créé en dehors de notre boucle pour bien prendre toujours le même.

A noter que la première unité ajoutée dans le groupe sera le leader, aussi quand vous gérez vos spawn si vous voulez qu’une unité soit votre chef de groupe.

La position de notre joueur c’est bien, mais bon pour une mission, on veut faire des renforts. Pas terrible si on doit les faire apparaitre directement sur les joueurs.

Un marqueur c’est pratique

Pourquoi utiliser un marqueur ? Il est possible d’utiliser des positions exactes, mais ce n’est pas très pratique si vous devez faire évoluer votre mission.

Le but étant que vous puissiez changer facilement votre mission, je ne vous demande pas d’en mettre autant que moi :

Le but est de vous comprendre, dans mon exemple
Les marqueurs JOIN >|< sont fait pour spécifier des waypoints précis.
Les marqueurs START (le rond avec la flèche vers le haut), sont des spawn
Les marqueurs PICK UP (rond avec la flèche traversant), sont fait pour des waypoints de véhicules spécifiques.
Les marqueurs zone sont prévu pour waypoints et/ou des spawn random.

A chacun de créer sa propre grammaire selon vos besoins !

Petit rappel pour faire un marqueur zone :

Quand vous ajoutez un marqueur, pensez à mettre un nom clair dans mon cas :

Ma grammaire étant : Tous les marqueurs commencent par mkr_ puis le type de et le Xeme de ce type

Ainsi je peux récupérer la position du marqueur dans mon script via getMarkerPos

_mkr_pos = getMarkerPos « mkr_spawn_2 »;

Dans le cas d’un marqueur zone, on peut récupérer la position centrale de la même manière, mais puisqu’on a une zone autant prendre une position aléatoire dans la zone via BIS_fnc_randomPosTrigger

_random_mkr_pos = « mkr_patrol_inf_1 » call BIS_fnc_randomPosTrigger;

Dans les deux cas le marqueur est passé via son nom en tant que string ( « marqueur » ) et non en tant que variable ( marqueur ), c’est une différence importante.
Dans le nom de la commande on a bien « Trigger » alors qu’on travaille avec un marqueur, parce que le marqueur de zone a de grandes similitudes que les triggers.

Dans votre script vous aurez donc à utiliser la position souhaitée et non plus celle du joueur :
Pensez à mettre à jour « mkr_spawn_2 » à mettre selon votre marqueur

_group = createGroup [blufor, true];
_mkr_pos = getMarkerPos "mkr_spawn_2";
{
    _x createUnit [_mkr_pos, _group];
} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];

On est content on a un groupe qui apparait au complet… mais sans aucun ordre… pas terrible, comment peut-on donner un waypoint/ordre à un groupe ?

Donner un ordre à un groupe

Plusieurs méthodes, … je devrais même dire trop de possibilité.
Prenons les cas les plus simples :

Mouvement simple

La commande move est la plus simple qui puisse, on demande au groupe d’aller à une position.

_group move (getMarkerPos « mkr_move_1 »);

Par contre, on ne spécifie pas s’il faut qu’ils soient alertés, d’y aller en marchant ou courant, …

SAD – Seek and destroy

Ce qu’on utilise le plus souvent pour demander à un groupe de se diriger vers une région pour détecter et attaquer les joueurs
Je ne détaillerais pas tous les paramètres, à vous de lire la documentation pour votre besoin ou alors de demander un article sur le sujet.
Nous allons utiliser les commandes pour créer un waypoint au complet : addWaypoint
Pensez à mettre à jour « mkr_spawn_2 » et « mkr_move_1 » à mettre selon vos marqueurs

// On créer rapidement une unité, ayant besoin du groupe de l'unité
_group = createGroup [blufor, true];
"B_Soldier_A_F" createUnit [getMarkerPos "mkr_spawn_2", _group];


// On récupère la position du marqueur
_wp_pos = getMarkerPos "mkr_move_1";
// 5m de rayon afin de générer la position du waypoint, aléatoirement à partir de la position du marqueur
_wp_radius = 5;

// On créer le waypoint
_wp = _group addWaypoint [_wp_pos, _wp_radius];
// On spécifie le type de waypoint, ici recherche et destruction, pouvant être une liste assez longue (MOVE, DESTROY, CYCLE, ...)
_wp setWaypointType "SAD";
// La vitesse de déplacement (UNCHANGED, LIMITED, NORMAL, FULL), full étant en courant, alors que Limited étant en marchant.
_wp setWaypointSpeed "FULL";
// Comportement (UNCHANGED, CARELESS, SAFE, AWARE, COMBAT, STEALTH)
_wp setWaypointBehaviour "COMBAT";

// "NO CHANGE" (No change)
// "BLUE" (Never fire)
// "GREEN" (Hold fire - defend only)
// "WHITE" (Hold fire, engage at will)
// "YELLOW" (Fire at will)
// "RED" (Fire at will, engage at will) 
_wp setWaypointCombatMode "RED";
// La formation du groupe (NO CHANGE, RANDOM, COLUMN, STAG COLUMN, WEDGE, ECH LEFT, ECH RIGHT, VEE, LINE, FILE, DIAMOND)
_wp setWaypointFormation "NO CHANGE";
// La distance à partir du points généré (soit ici aléatoirement dans les 5m autour de la position du marqueur)
_wp setWaypointCompletionRadius 30;
// Permet d'ajouter un temps avant que le groupe ne se déplace
_wp setWaypointTimeout [0,0,0];
// Permet d'ajouter une condition avant de faire mouvement et un script à exécuter quand c'est bon
_wp setWaypointStatements ["true", ""];

Notre cas est simple, le groupe s’alerte, et fonce en courant aussi vite que possible sur la position en étant prêt à combattre !

Tout ça c’est bon, reste encore à gérer correctement le spawn en multijoueur, puisque tel quel si on lance le script chez tout le monde, il y aura un spawn PAR joueurs (et serveur)
Donc pour 12 joueurs, on aura 13 groupes de créés (12 joueurs + 1 serveur)

Serveur / client ?

Si vous préparez une mission multijoueur, il faudra passer par le serveur et non via vos joueurs.
Si vous voulez enclencher votre script via un trigger, vérifiez bien que vous ne le lancez que côté serveur !

Après dans la partie On Activation, vous pouvez écrire ce que vous voulez.

Pour des scripts plus longs, par exemple des renforts prévus dans un fichier très long.
L’exemple d’une zone où les joueurs commencent et dont ils sont sensés rester jusqu’à la fin pour défendre la zone, on peut prévoir tous les spawns de renforts entre coupé de SLEEP, pour mettre des temps d’attentes entre deux renforts.

Dans ce cas ou d’autres ne lancez pas vos spawn via init.sqf qui serait lancé chez les joueurs aussi mais bien dans initServer.sqf.
Comme son nom l’indique, n’est exécuté que sur le serveur.

Infinie ?

J’ai dit dangereux ? Oui comme tout ce qui utilise l’infinie !

Pour détecter le joueur le plus proche, et envoyer le groupe sur eux, j’utilise une fonction de @mystery (Merci à lui) que voici :

GDC_fnc_lucyGetNearestPlayer = {
	// Author: Mystery
	// Description:
	// Return the nearest player of a position
	// Parameter(s):
	// 	0 : ARRAY - The position to test
	// Returns:
	// An array which contains: [Nearest player, distance]

	params["_arg_pos"];
	private["_nearest_player", "_nearest_distance", "_current_distance", "_current_players"];

	_arg_pos = _this select 0;

	_nearest_distance = 1000000; // 1000km max distance... you should find nearest player;)
	if (isMultiplayer) then {_current_players = playableUnits;} else {_current_players = switchableUnits;};

	{
		_current_distance = _x distance _arg_pos;
		if (_current_distance < _nearest_distance) then {
			_nearest_player = _x;
			_nearest_distance = _current_distance;
		};
	} forEach _current_players;

	[_nearest_player, _nearest_distance];
};

Voici le code, j’espère que les commentaires sont assez parlant, même s’il y a quelques astuces

// On sécurise l'asynchrone, vu qu'on va utiliser une boucle infinie et un sleep
[] spawn {
	// Camp des ias à faire spawn
	UNIT_SIDE = independent;

	// Boucle INFINIE c'est mal ! Ce serait mieux avec une variable pouvant y mettre fin
	while { true } do {
		
		// On utilise ici 2 marqueurs zones, on en sélectionne un aléatoirement
		_random_marker = ["mkr_spawn_1", "mkr_spawn_2"] call BIS_fnc_selectRandom;
		// On récupère une position aléatoire dans le marqueur zone
		_random_pos = _random_marker call BIS_fnc_randomPosTrigger;
		
		// On récupère la position du joueur le plus proche
		_nearestPlayer = [_random_pos] call GDC_fnc_lucyGetNearestPlayer;
		_nearest = getPos (_nearestPlayer select 0);
		

		// On spawn le groupe
		_spawn_group = createGroup [UNIT_SIDE, true];
		_mkr_pos = getMarkerPos _random_marker;
		{
			_x createUnit [_mkr_pos, _spawn_group];
		} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];
	   
		
		// On ajoute le waypoint
		_wp_pos = _nearest;
		_wp_radius = 5;

		_wp = _spawn_group addWaypoint [_wp_pos, _wp_radius];
		_wp setWaypointType "SAD";
		_wp setWaypointSpeed "FULL";
		_wp setWaypointBehaviour "COMBAT";
		_wp setWaypointCombatMode "RED";
		_wp setWaypointFormation "NO CHANGE";
		_wp setWaypointCompletionRadius 30;
		_wp setWaypointTimeout [0,0,0];
		_wp setWaypointStatements ["true", ""];
		
		// Attente avant de faire spawn un autre groupe (entre 60 et 40+60 = 100sec)
		sleep ((random 40) + 60.0);
	};
};

Pour les astuces :

_random_marker = [« mkr_spawn_1 », « mkr_spawn_2 »] call BIS_fnc_selectRandom;

Permet de récupérer une valeur aléatoire dans le tableau [« mkr_spawn_1 », « mkr_spawn_2 »]. Dans ce cas on a deux marqueurs via des strings ( entre guillements «  )

_nearestPlayer = [_random_pos] call GDC_fnc_lucyGetNearestPlayer;
_nearest = getPos (_nearestPlayer select 0);

En utilisant le script de @mystery on peut récupérer le joueur le plus proche soit _nearestPlayer select 0, le premier élément puisque la méthode renvoie un tableau : [_nearest_player, _nearest_distance]

Le gros problème du script actuel, étant qu’on fait spawn de manière infinie sans aucune sécurité… autant dire qu’au bout d’un moment le serveur va avoir du mal à faire tourner. Les ias vont commencer à faire du surplace et potentiellement un crash à la fin.

Pool d’unités et sécurité

Plusieurs manières de faire, mais dans tous les cas limités les spawn via Spawn !

// On sécurise l'asynchrone, vu qu'on va utiliser une boucle infinie et un sleep
[] spawn {
	// Camp des ias à faire spawn
	UNIT_SIDE = independent;
	// Maximum d'unité à faire spawn
	MAX_UNITS = 10;
	// Distance minimale à laquelle on fait spawn les ias (400m pour du combat urbain peut suffire)
	MINIMUM_DISTANCE = 400;
	// Distance maximale à laquelle on fera spawn les ias (1,6km) si audessus on concidère qu'ils sont trop loin
	MAXIMUM_DISTANCE = 1600;

	// Boucle INFINIE c'est mal ! Ce serait mieux avec une variable pouvant y mettre fin
	while { true } do {
		
		// On vérifie qu'on a pas trop d'unité déjà spawn
		while {{alive _x AND side _x == UNIT_SIDE AND _x getVariable "spawned"} count allUnits < MAX_UNITS} do {
			// On utilise ici 2 marqueurs zones, on en sélectionne un aléatoirement
			_random_marker = ["mkr_spawn_1", "mkr_spawn_2"] call BIS_fnc_selectRandom;
			// On récupère une position aléatoire dans le marqueur zone
			_random_pos = _random_marker call BIS_fnc_randomPosTrigger;
			
			// On récupère la position du joueur le plus proche
			_nearestPlayer = [_random_pos] call GDC_fnc_lucyGetNearestPlayer;
			_nearest = getPos (_nearestPlayer select 0);
			
			// On évite de faire spawn trop proche des joueurs ou trop loin
			if ((_nearestPlayer select 1 > MINIMUM_DISTANCE) AND (_nearestPlayer select 1 < MAXIMUM_DISTANCE)) then {

				// On spawn le groupe
				_spawn_group = createGroup [UNIT_SIDE, true];
				_mkr_pos = getMarkerPos _random_marker;
				{
					_unit = _x createUnit [_mkr_pos, _spawn_group];
					_unit setVariable ["spawned", true];
				} forEach ["B_Soldier_A_F", "B_soldier_AAR_F", "B_engineer_F"];
			   
				
				// On ajoute le waypoint
				_wp_pos = _nearest;
				_wp_radius = 5;

				_wp = _spawn_group addWaypoint [_wp_pos, _wp_radius];
				_wp setWaypointType "SAD";
				_wp setWaypointSpeed "FULL";
				_wp setWaypointBehaviour "COMBAT";
				_wp setWaypointCombatMode "RED";
				_wp setWaypointFormation "NO CHANGE";
				_wp setWaypointCompletionRadius 30;
				_wp setWaypointTimeout [0,0,0];
				_wp setWaypointStatements ["true", ""];
				
				// Petite attente avant d'envoyer un autre groupe
				sleep 4.0;
			};
		};
		
		// Attente un peu plus longue avant de revérifier qu'il y a assez de place pour refaire spawn un nouveau groupe
		sleep (random 40) + 60.0;
	};
};

Quelques sécurités ajoutés :

while {{alive _x AND side _x == UNIT_SIDE AND _x getVariable « spawned »} count allUnits < MAX_UNITS} do {

Une ligne un peu longue qui va récupérer allUnits qui contient TOUTES les unités existantes (autant joueurs que IAs)

{alive _x AND side _x == UNIT_SIDE AND _x getVariable « spawned »}

On va compter count le nombre d’unités parmis le AllUnits qui sont vivantes (Alive), dans le camps qu’on souhaite side _x == UNIT_SIDE et enfin on veut que l’unité ait une variable « spawned » à true, qu’on verra plus tard.
Retenez que c’est pour les différencier des renforts qui aurait pu être mis via Eden directement, ou d’un autre pool d’unités.

if ((_nearestPlayer select 1 > MINIMUM_DISTANCE) AND (_nearestPlayer select 1 < MAXIMUM_DISTANCE)) then {

On utilise la distance renvoyée par la fonction _nearestPlayer select 1 pour vérifier qu’on est dans une fourchette acceptable pour faire spawn nos unités entre 400m et 1600m.
Pour du combat urbain, ça peut largement suffire, mais à adapter bien sûr.

_unit setVariable [« spawned », true];

Enfin dans la création des unités on ajoute une variable pour les différencier des autres, comme mentionné plus haut.
A noter que spécifier la variable sur le leader n’aurait pas suffit puisqu’il peut mourir.
Mais dans le cas où on voudrait faire un pool non pas d’unités mais de groupes, on pourrait mettre la variable sur le groupe.

Point performance

A utiliser avec parcimonie, une infinité d’ias veut dire des perfs qui descendent en flèche autant côté serveur en gérant un grand nombre d’entité, que côté client pour les charger et les afficher.

Dans l’ordre de ce qui va prendre de plus en plus de performance

  • Agent
  • Ia
  • Groupe de 2 ias
  • 2 groupes de 1 ia chacuns
  • Véhicule terrestre
  • Véhicule aérien

Sans passer par des chiffres qui seraient variables du mode de jeu (solo, multi, serveur dédié, headless client, …). On peut expliquer une partie.

Retenez que pour un groupe, seul le chef va déterminer comment réaliser un ordre, par exemple le calcul de waypoints pour rejoindre une direction.
Le reste du groupe va le suivre, et donc avoir des calculs très simplifiés.
Alors que deux groupes, chacun vont avoir leurs propres calculs complexes.

Pour un véhicule il y a plus d’éléments à prendre en compte, et pour un avion… le déplacement en 3d plutôt qu’en 2d, j’imagine que c’est assez clair.

Conclusion

De grandes possibilités une fois maîtrisé, mais toujours bien réfléchir si c’est bien utile.

Si vos joueurs reçoivent des assauts infinis, ce n’est pas qu’un soucis de performance, mais c’est d’autant plus de blessure ou de morts, c’est d’autant plus de balles perdues… mais c’est aussi l’impossibilité de bouger, ou en tout cas être ralentit.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.