Перейти к содержанию
Patrego

Скрипт респавна цивилов в триггере (но есть нюанс)

Рекомендуемые сообщения

*UPD: Рабочий вариант в комментариях внизу.

Дисклеймер: я только начал изучать скриптовый язык SQF. Если у вас есть способ сделать это проще и эффектней, прошу поделитесь мыслями/кодом. Это не пример рабочего кода, это описание проблемы, с которой я столкнулся.

Цель: написать скрипт, который будет отслеживать передвижение игрока по карте и спавнить цивилов на заранее обозначенных позициях в поселениях, как только игрок приблизится к поселению на 250 метров, а также удалять этих цивилов, когда игрок от поселения на 250 метров отойдёт.

Первое, что приходит в голову - создать в каждом поселении по триггеру и повесить код с респавном цивилов на них. В чём проблема: на карте около 40 поселений - это 40 триггеров, каждый из которых обновляется раз в 0.5 секунд. Это слишком затратно.

Спойлер

Как это могло бы выглядеть (синим отмечены триггеры):

122.jpg.229f150a96ff769935689ede817ecf49.jpg

Что я хочу, так это создать триггер и прикрепить его к игроку, чтобы триггер перемещался вместе с ним. А в каждом поселении мы разместим объекты типа LOGIC (почему именно их, напишу дальше). Как только игрок приблизится к объекту LOGIC на 250 метров - триггер сработает (сработает условие: объект LOGIC внутри триггера) и сработает наш код по спавну цивилов.

Спойлер

123.jpg.540baca0c690a98a4ea3558d97aa01c8.jpg

Код:

// Создаём триггер, настраиваем размер, прикрепляем триггер к игроку
tr_playerOne = createTrigger ["EmptyDetector", position player];
tr_playerOne attachTo [player, [0,0,0],"Pelvis"];
tr_playerOne setTriggerArea [250, 250, 0, false, 1000];
// Почему LOGIC? У нас на выбор есть: "EAST", "WEST", "GUER", "CIV", "LOGIC", "ANY", "ANYPLAYER",
// "STATIC", "VEHICLE", "GROUP", "LEADER", "MEMBER".
// Из всего этого мы можем использовать только LOGIC, 
// ведь всё остальное и так будет присутствовать на карте и ошибочно триггерить наш тригер.
tr_playerOne setTriggerActivation ["LOGIC", "PRESENT", true];
// при активации вызываем функцию спавнющую цивилов, а при деактивации - удаляющую заспавненных цивилов.
tr_playerOne setTriggerStatements ["this", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];

// сюда добавятся активные цивилы (которые были заспавненны, но ещё не были удалены)
activeCivArrayOne = [];

// это массивы с цивилами. Каждый массив содержит цивилов из одного конкретного поселения.
civArray_1 = [
  	// "название модельки, позиция, азимут на который ориентирован цив (его direction)"
	["1", [883.64,2749.38,0], 337]
];

civArray_2 = [
	["2", [879.059,2739.4,0], 20]
];

respawnCivReadyFnc = {
    // список наших LOGIC-ов, которые попадают в триггер; название триггера (в нашем случае просто 'one')
	params ["_pointsList", "_trigger"];
	
  	// для всех LOGIC-ов попавших в триггер:
	{
  		// узнаём название LOGIC-а, попавшего в триггер. Все LOGIC-и имеют названия типа: point_*номер*
		_pointStr = format ["%1", _x];
  		// узнаём номер поинта
		_pointNum = parseNumber (_pointStr trim ['point_', 1]);
  		// вызываем след. функцию передавая ей номер поинта и название триггера
		[_pointNum, 'one'] call respawnCivCompileFnc;
	} forEach _pointsList;
};

respawnCivCompileFnc = {
	params ["_civArrayNum", "_trigger"];
	_code = "";
	
  	// вызываем функцию спавнющую цивилов, передавая ей в аргументы массив с цивилами определённого поинта и имя триггера
  	// все массивы с цивилами имеют названия типа: civArray_*номер*. civArray_1 будет принадлежать point_1, civArray_2 - point_2 и т.д.
	if (_trigger == "one") then {
		_code = compile (format ["
			[civArray_%1, 'one'] call respawnCivFnc;
		", _civArrayNum]);	
	};

	call _code;
};

respawnCivFnc = {
	params ["_civArray", "_trigger"];
	
  	// спавним цивов из переданного массива и заносим их в массив activeCivArrayOne 
    //(это нужно, чтобы знать каких именно цивов нужно удалить, когда произойдет деактивация триггера)
	{
		_gr = createGroup civilian;
		_civ = _gr createUnit [
			format ["C_CUPAFRCIV_Civ_%1_01", ((_civArray select _forEachIndex) select 0)],
			((_civArray select _forEachIndex) select 1),
			[],
			0,
			"FORM"
		];
		_civ setFormDir ((_civArray select _forEachIndex) select 2);

		if (_trigger == 'one') then {
			activeCivArrayOne pushBack _civ;
		};
	} forEach _civArray;
};

// При деактивации триггера (когда игрок отходит на 250 метров от LOGIC-объекта) - удаляем всех цивов из массива activeCivArrayOne
deleteCivFnc = {
	params ["_trigger"];

	if (_trigger == 'one') then {
		{
			deleteVehicle _x;
		} forEach activeCivArrayOne;
		// после удаления обнуляем массив активных цивов
		activeCivArrayOne = [];
	};
};

Это работает. Но есть проблема: иногда случается такая ситуация, когда один LOGIC-объект расположен слишком близко к другому (это случается когда поселения находятся рядом друг с другом). В таком случае наш триггер перекрывается полем действия сразу двух LOGIC-объектов, или другими словами, наш триггер триггерится сразу на два LOGIC-а.

image.png.2cf1c62d43daa687feacd2bd1d8912fa.png

Что происходит в таком случае? Триггер срабатывает на один из них, а на другой - нет. Точнее он срабатывает на оба. Но условие активации (респавн цивов) происходит только один раз - для одного LOGIC-а. Другой же LOGIC просто игнорируется. И даже больше. Если был активирован левый (на картинке) LOGIC, а правый был проигнорирован, то даже если игрок сместится вправо - на территорию правого LOGIC-а, полностью уйдя с территории левого - то ничего не произойдёт. Точнее не произойдет спавна цивов на правом LOGIC-е, произойдёт лишь удаление цивов с территории левого. Что же делать? Ну, мне пришла идея - вместо одного триггера, висячего на игроке, сделать два триггера, висящих на игроке. И тут главный вопрос: как заставить эти два триггера работать обособленно друг от друга. Чтобы первый триггер стриггерился на левый LOGIC, а второй - на правый? Я много вариантов перебрал и не нашёл ни одного рабочего. Точнее, я нашел один вариант, но его сложно считать рабочим. Код:

// Итак, что изменилось?
tr_playerOne = createTrigger ["EmptyDetector", position player];
tr_playerOne attachTo [player, [0,0,0],"Pelvis"];
tr_playerOne setTriggerArea [250, 250, 0, false, 1000];
tr_playerOne setTriggerActivation ["LOGIC", "PRESENT", true];
tr_playerOne setTriggerStatements ["this", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];

// теперь у нас два триггера
tr_playerTwo = createTrigger ["EmptyDetector", position player];
tr_playerTwo attachTo [player, [0,0,0],"Pelvis"];
tr_playerTwo setTriggerArea [250, 250, 0, false, 1000];
// Единственный "рабочий" способ, который я нашёл, - задать второму триггеру что-то другое, чем LOGIC. 
// Например WEST (любой юнит стороны синих).
tr_playerTwo setTriggerActivation ["WEST", "PRESENT", true];
tr_playerTwo setTriggerStatements ["this", "[thisList, 'two'] call respawnCivReadyFnc", "['two'] call deleteCivFnc"];

activeCivArrayOne = [];
// У нас теперь два активных массива - каждый для своего триггера.
activeCivArrayTwo = [];

civArray_1 = [
	["1", [883.64,2749.38,0], 337]
];

civArray_2 = [
	["2", [879.059,2739.4,0], 20]
];

respawnCivReadyFnc = {
	params ["_pointsList", "_trigger"];

	_pointStr = format ["%1", _pointsList select 0];
	_pointNum = parseNumber (_pointStr trim ['point_', 1]);
	
  	// теперь мы проверяем, какой триггер вызвал функцию и передаём имя этого триггера дальше 
  	// (чтобы знать, в какой массив заносить активных цивов)
	if (_trigger == "one") then {
		[_pointNum, 'one'] call respawnCivCompileFnc;
	};

	if (_trigger == "two") then {
		[_pointNum, 'two'] call respawnCivCompileFnc;
	};
};

respawnCivCompileFnc = {
	params ["_civArrayNum", "_trigger"];
	_code = "";
	
  	// тут тоже передаём имя массива
	if (_trigger == "one") then {
		_code = compile (format ["
			[civArray_%1, 'one'] call respawnCivFnc;
		", _civArrayNum]);	
	};

	if (_trigger == "two") then {
		_code = compile (format ["
			[civArray_%1, 'two'] call respawnCivFnc;
		", _civArrayNum]);	
	};

	call _code;
};

respawnCivFnc = {
	params ["_civArray", "_trigger"];

	{
		_gr = createGroup civilian;
		_civ = _gr createUnit [
			format ["C_CUPAFRCIV_Civ_%1_01", ((_civArray select _forEachIndex) select 0)],
			((_civArray select _forEachIndex) select 1),
			[],
			0,
			"FORM"
		];
		_civ setFormDir ((_civArray select _forEachIndex) select 2);
		
      	// после спавна цива, заносим его в массив активных цивов, в зависимости от имени триггера
		if (_trigger == 'one') then {
			activeCivArrayOne pushBack _civ;
		};

		if (_trigger == 'two') then {
			activeCivArrayTwo pushBack _civ;
		};
	} forEach _civArray;
};

deleteCivFnc = {
	params ["_trigger"];
  	
  	// при удалении также проверяем имя триггера, чтобы знать из какого активного массива удалять цивов при деактивации триггера.
	if (_trigger == 'one') then {
		{
			deleteVehicle _x;
		} forEach activeCivArrayOne;
		activeCivArrayOne = [];
	};

	if (_trigger == 'two') then {
		{
			deleteVehicle _x;
		} forEach activeCivArrayTwo;
		activeCivArrayTwo = [];
	};
};
Спойлер

Для демонстрации я установил размер триггеров на 10x10 метров и разместил их таким образом, чтобы они слегка перекрывали друг-друга. Первым point-ом у нас является LOGIC-объект, вторым - юнит WEST, которого я сделал невидимым и отключил для него коллизию. Для цивов я установил координаты в массиве, совпадающие с координатами point-ов. В результате мы получаем то, что хотели: цивы спавнятся на заданных в массиве координатах, причём спавнятся оба цива, не смотря на перекрытие областей. Этого я и хочу добиться, но только без использования WEST-стороны (и любой другой стороны, я хочу чтобы было два LOGIC-а).

С одной стороны это работает, но с другой - полностью лишает нас возможности использовать WEST (синих) юнитов на карте, ведь на них триггер также будет ошибочно срабатывать. Возможно есть смысл заменить WEST на CIV. Но я надеюсь, что найдётся знаток, который подскажет как сделать это всё "по уму". Мне кажется, что решение кроется в настройке условия триггера:

tr_playerOne setTriggerStatements ["ВОЗМОЖНО РЕШЕНИЕ КРОЕТСЯ ЗДЕСЬ?", "[thisList, 'one'] call respawnCivReadyFnc", "['one'] call deleteCivFnc"];

Я много чего перепробовал, клепал различные переменные, пытался запоминать "активный триггер" и т.п. Но ничего не помогло. Подскажите, ребята, как сделать респавн цивов, на заданных позициях, при подходе к поселению на ~200-250 метров, с возможным перекрытием этих областей (поселений) друг-другом, а также удаление заспавленных цивов при отходе игрока от поселения. И всё это, используя 1-2 триггера, прикреплённых к игроку.

Изменено пользователем Patrego

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В целом напоминает старый дейз на А2, должно работать. Но у нас тут 21 век и бисы давно запилили свои модули (достаточно неплохие, сам юзал их на миссиях на соседнем проекте): https://community.bistudio.com/wiki/Arma_3:_Civilian_Presence

В том же патче что и модули цивилом бисы родили вот это: https://community.bistudio.com/wiki/createAgent Думаю стоит поэкспериментировать с агентами, тк они создают значительно меньшую нагрузку. Единственное придется их поведение как то захардкодить

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

@BendeR Спасибо за ответ, натолкнуло на некоторые мысли.
Мне не очень подходит модуль Civilian_Presence, т.к. он создаёт практически неуправляемых цивилов, которых сложно настраивать под себя. К тому же, как я понял из описания, их также нужно синхронизировать с триггерами для спавна/деспавна в конкретном радиусе от позиции игрока (для каждого создавать свой триггер).

Я сменил подход и решил вопрос иначе:

Мы расставляем LOGIC-объекты в поселениях, как и раньше:

Спойлер

123.jpg.e706126402851cd0a6f275009fac563a.jpg

Теперь у нас всего один триггер, прикрепленный к игроку. Этот триггер постоянно собирает все LOGIC-и, находящиеся в триггере, в массив "активных" LOGIC-ов. С этим массивом мы и работаем с помощью цикла while. Цикл while "следит" за массивом активных LOGIC-ов, спавня/деспавня цивов. Код:

repeatFlag = true;

tr_playerOne = createTrigger ["EmptyDetector", position player];
tr_playerOne attachTo [player, [0,0,0],"Pelvis"];
// настраиваем размер триггера (кол-во метров от игрока до LOGIC-а для спавна/деспавна цивов)
tr_playerOne setTriggerArea [250, 250, 0, false, 1000];
tr_playerOne setTriggerActivation ["LOGIC", "PRESENT", true];
tr_playerOne setTriggerStatements ["this && repeatFlag", "pointsArray = [];{if (['point_', (str _x)] call BIS_fnc_inString) then 
{pointsArray pushBackUnique _x;};} forEach thisList; repeatFlag = false;", "repeatFlag = true;"];

// Если UNIT - то создаёт цивов как юнитов; Если AGENT - как агентов
civType = 'UNIT';
// civType = 'AGENT';

// Массивы с модельками,координатами,направлением цивов для каждого LOGIC-а
// LOGIC-у с именем point_1 соответствует массив civArray_1 и т.д.
civArray_1 = [
	["C_CUPAFRCIV_Civ_1_01", [1170.57,2716.23,0], 124]
];
civArray_2 = [
	["C_CUPAFRCIV_Civ_2_01", [1164,2714.12,0], 20]
];
civArray_3 = [
	["C_CUPAFRCIV_Civ_3_01", [1165.31,2720.09,0], 221]
];


pointsArray = [];
activePoints = [];

spawnCivFnc = {
	{
		if (!(_x in activePoints)) then {
			activePoints pushBack _x;
			_pointStr = format ["%1", _x];
			_pointNum = parseNumber (_pointStr trim ['point_', 1]);

			_code = "";

			_code = compile (format ["
				[civArray_%1, %1] call createCivFnc;
			", _pointNum]);

			call _code;
		};
	} forEach pointsArray;

	{
		if (!(_x in pointsArray)) then {
			_pointStr = format ["%1", _x];
			_pointNum = parseNumber (_pointStr trim ['point_', 1]);
			
			_code = "";

			_code = compile (format ["
				{
					deleteVehicle _x;
				} forEach point_%1_activeCivs;
			", _pointNum]);

			call _code;

			activePoints deleteAt (activePoints find _x);
		};
	} forEach activePoints;
};

createCivFnc = {
	params ["_civArray", "_pointNum"];

	{	
		switch (civType) do {
			case "AGENT": {
				_civ = createAgent [
					format ["%1", ((_civArray select _forEachIndex) select 0)],
					((_civArray select _forEachIndex) select 1),
					[],
					0,
					"NONE"
				];
				_civ setDir ((_civArray select _forEachIndex) select 2);

				_code = compile (format ["
					if (isNil 'point_%1_activeCivs') then {
						point_%1_activeCivs = [];
					};

					point_%1_activeCivs pushBack _civ;
				", _pointNum]);

				call _code;
			};
			case "UNIT": {
				_gr = createGroup civilian;
				_civ = _gr createUnit [
					format ["%1", ((_civArray select _forEachIndex) select 0)],
					((_civArray select _forEachIndex) select 1),
					[],
					0,
					"FORM"
				];
				_civ setFormDir ((_civArray select _forEachIndex) select 2);

				_code = compile (format ["
					if (isNil 'point_%1_activeCivs') then {
						point_%1_activeCivs = [];
					};

					point_%1_activeCivs pushBack _civ;
				", _pointNum]);

				call _code;
			};
		};
	} forEach _civArray;
};

while {true} do {
	call spawnCivFnc;

	// sleep 2; - интервал в секундах, для проверки LOGIC-ов в триггере
	// можно поставить меньше, если размер триггера будет меньше
	sleep 2;
};

С таким подходом нам не важно, сколько LOGIC-ов будет накладываться друг на друга. Всё должно работать корректно. Демонстрация работы с тремя, наложенными друг на друга, LOGIC-ами:

Спойлер

Для демонстрации я установил размер триггера 10x10. Координаты цивилов соответствуют координатам LOGIC-ов. Для удобства я сделал задержку в цикле while равной 0.5 секунд, вместо 2 секунд, и расставил таблички на координатах LOGIC-ов. В правом верхнем углу выводится список LOGIC-ов, находящихся в триггере (в thisList-е). Несмотря на перекрытие LOGIC-ов, цивы спавнятся/деспавнятся исправно.

Это можно использовать, если вам нужно расставить конкретных цивилов на конкретные места и иметь возможность делать что-то с ними дальше из скрипта (добавить поведение или навесить addAction-ы и прочее). Цивы будут спавниться и деспавниться в зависимости от расстояния от LOGIC-ов до игрока. По идее это более оптимизированный способ, чем создавать 30+ триггеров, которые будут обновляться каждые 0.5 секунд. Возможно можно что-то улучшить, но я хз :)

Изменено пользователем Patrego

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти

  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу

×

Важная информация

Мы разместили cookie-файлы на ваше устройство, чтобы помочь сделать этот сайт лучше. Вы можете изменить свои настройки cookie-файлов, или продолжить без изменения настроек.