Jump to content

Добро пожаловать на проект
Weekly Open Games

Weekly Open Games — это проект для людей, которым интересен хардкорный тип игры. Мы стараемся максимально эффективно использовать структуру, слабые и сильные стороны, а также технику и вооружение различных сторон конфликта, но не ставим перед собой цель провести 100% сбалансированного боестолкновения. Командная игра, командное взаимодействие — фундамент нашего проекта.
О проекте WOG Как начать играть Правила серверов
Patrego

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

Recommended Posts

*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 триггера, прикреплённых к игроку.

Edited by Patrego

Share this post


Link to post
Share on other sites

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

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

Share this post


Link to post
Share on other sites

@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 секунд. Возможно можно что-то улучшить, но я хз :)

Edited by Patrego

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.