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

Patrego

Пользователи
  • Публикаций

    2
  • Зарегистрирован

  • Посещение

Сообщения, опубликованные Patrego


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


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

×

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

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