Follow along with the video below to see how to install our site as a web app on your home screen.
Примечание: This feature may not be available in some browsers.
Это если на серве 500+ человек, и все розкиданы по всему миру, то это же писос скоко потоков будет. У меня есть теория на этот счет.Сам не вникал, но судя по всему - создается. Но происходит это исключительно для объектов в радиусе видимости.
На самом деле интересный вопрос, поэтому если неправ, - интересно узнать и самому)
Ахренеть, почти так как я придумал, я думал поднять поток каждому квадрату, который проверял бы наличие в нем игроков, если игроков несколько, то высчитывался бы минимальные и максимальные координаты радиуса игроков, добавлять эти координаты в массив текущего квадрата(и соседних, если радиус их затрагивает), и уже поток квадрата относительно координатам радиуса рисовал бы мобов, раз в какое то время проверял бы видимых мобов, нет ли у них желания пройтись или разовнятся, а уже заагреному мобу поднимать отдельный поток(или всех заагреных мобов в радиусе обрабатывать одним потоком , не решил)Могу описать принцип работы с тех времен, когда анализировали украденную версию С4 (или ц5 точно уже не помню) написанную на Си. Как работает на джаве - не вкурсе, может быть аналогично, хотя сомнительно.
Карта разбита на определенные области. Монстры каждой области при старте сервака начинают грузится в память, по очередности.
После полной загрузки, начинается периодический запуск (тик) просчета областей.
Каждая область имеет состояние активности. Если в этой области не находится ни одного игрока - то область помечается не активной. Если область не активна то все просчеты монстров в ней не выполняются. Монстры не двигаются, ничего не делают, ничего не происходит. Даже респа мобов нет.
Если область активна - то от каждого игрока в данной области вычисляется радиус до монстра (возможно бьется на дополнительные области), если радиус вписывается в видимость монстра - то монстру ставится в цель ваш ид персонажа. Дальше идет работа алгоритма порядка ид-ов цели, поиск пути к координатам, и атака вас если координаты сблизились. Так же запоминается время добавление вашего ид в список. Если монстр не смог вас настигнуть за 2 минуты (вроде там было 2) то ваш ид убирается из списка монстра, и монстр идет на изначальную точку с каждым тиком расчета. Конечно, если вы его троните - то ваш ид опять попадает в список целей. Видимость у монстров сделана довольно тупо - попадание радиус, и не важно вы стоите за стеной, домом или хоть в клан холе.
Если хп монстра падает до 0, то мобу ставится определенный статус, и выставляется временная метка респа. Помню важный момент, монстр остается в той же ячейке памяти, только изменяется его состояние. Если последующие циклы обработки монстров попадают на время респа меньше текущего - то мобу выставляется статус и респится моб.
Про сокеты. Каждое действие, например монстр задумал сделать пару шагов, попадет в определенный пул действий текущего цикла. Все игроки которые подключены к серверу, получают пакеты из этого пула в пределах определенного радиуса. И вы тем самым видите как монстр пошел. Но вы ничего не сможете увидеть дальше радиуса, потому что сервер вам просто ничего не сообщает более. Логично изменяя свои координаты вы начинаете получить другие действия из пула.
Приблизительно так выходит если говорит простыми словами. Технически конечно намного сложнее...
Насколько помню тик нпц всеравно отрабатывает всегда и нпц по прежнему пытает выполнить свои действия но они фильтруются в случаи если квадрат не активен.Монстры не двигаются, ничего не делают, ничего не происходит.
public void setXYZ(int x, int y, int z)
{
_x = World.validCoordX(x);
_y = World.validCoordY(y);
_z = World.validCoordZ(z);
World.addVisibleObject(this, null);
}
public class ActivateTask extends RunnableImpl
{
private boolean _isActivating;
public ActivateTask(boolean isActivating)
{
_isActivating = isActivating;
}
@Override
public void runImpl() throws Exception
{
if(_isActivating)
World.activate(WorldRegion.this);
else
World.deactivate(WorldRegion.this);
}
}
public static void addVisibleObject(GameObject object, Creature dropper)
{
if(object == null || !object.isVisible())
return;
final WorldRegion region = getRegion(object);
final WorldRegion currentRegion = object.getCurrentRegion();
if(currentRegion == region)
return;
int x1, y0, y1, z0, z1;
if(currentRegion == null) // Новый обьект (пример - игрок вошел в мир, заспаунился моб, дропнули вещь)
{
// Добавляем обьект в список видимых
object.setCurrentRegion(region);
region.addObject(object);
// Показываем обьект в текущем и соседних регионах
// Если обьект игрок, показываем ему все обьекты в текущем и соседних регионах
x1 = validX(region.getX() + 1);
y0 = validY(region.getY() - 1);
y1 = validY(region.getY() + 1);
z0 = validZ(region.getZ() - 1);
z1 = validZ(region.getZ() + 1);
for(int x = validX(region.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
getRegion(x, y, z).addToPlayers(object, dropper);
}
else
// Обьект уже существует, перешел из одного региона в другой
{
currentRegion.removeObject(object); // Удаляем обьект из старого региона
object.setCurrentRegion(region);
region.addObject(object); // Добавляем обьект в список видимых
// Убираем обьект из старых соседей.
int rx = region.getX();
int ry = region.getY();
int rz = region.getZ();
x1 = validX(currentRegion.getX() + 1);
y0 = validY(currentRegion.getY() - 1);
y1 = validY(currentRegion.getY() + 1);
z0 = validZ(currentRegion.getZ() - 1);
z1 = validZ(currentRegion.getZ() + 1);
for(int x = validX(currentRegion.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
if(!isNeighbour(rx, ry, rz, x, y, z))
getRegion(x, y, z).removeFromPlayers(object);
// Показываем обьект, но в отличие от первого случая - только для новых соседей.
x1 = validX(region.getX() + 1);
y0 = validY(region.getY() - 1);
y1 = validY(region.getY() + 1);
z0 = validZ(region.getZ() - 1);
z1 = validZ(region.getZ() + 1);
rx = currentRegion.getX();
ry = currentRegion.getY();
rz = currentRegion.getZ();
for(int x = validX(region.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
if(!isNeighbour(rx, ry, rz, x, y, z))
getRegion(x, y, z).addToPlayers(object, dropper);
}
}
/**
* Удаляет обьект из текущего региона
* @param object обьект для удаления
*/
public static void removeVisibleObject(GameObject object)
{
if(object == null || object.isVisible())
return;
final WorldRegion currentRegion;
if((currentRegion = object.getCurrentRegion()) == null)
return;
object.setCurrentRegion(null);
currentRegion.removeObject(object);
final int x1 = validX(currentRegion.getX() + 1);
final int y0 = validY(currentRegion.getY() - 1);
final int y1 = validY(currentRegion.getY() + 1);
final int z0 = validZ(currentRegion.getZ() - 1);
final int z1 = validZ(currentRegion.getZ() + 1);
for(int x = validX(currentRegion.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
getRegion(x, y, z).removeFromPlayers(object);
}
public void addObject(GameObject obj)
{
if(obj == null)
return;
lock.lock();
try
{
GameObject[] objects = _objects;
GameObject[] resizedObjects = new GameObject[_objectsCount + 1];
System.arraycopy(objects, 0, resizedObjects, 0, _objectsCount);
objects = resizedObjects;
objects[_objectsCount++] = obj;
_objects = resizedObjects;
if(obj.isPlayer())
if(_playersCount++ == 0)
{
if(_activateTask != null)
_activateTask.cancel(false);
//активируем регион и соседние регионы через секунду
_activateTask = ThreadPoolManager.getInstance().schedule(new ActivateTask(true), 1000L);
}
}
finally
{
lock.unlock();
}
}
public void removeObject(GameObject obj)
{
if(obj == null)
return;
lock.lock();
try
{
GameObject[] objects = _objects;
int index = -1;
for(int i = 0; i < _objectsCount; i++)
{
if(objects[i] == obj)
{
index = i;
break;
}
}
if(index == -1) //Ошибочная ситуация
return;
_objectsCount--;
GameObject[] resizedObjects = new GameObject[_objectsCount];
objects[index] = objects[_objectsCount];
System.arraycopy(objects, 0, resizedObjects, 0, _objectsCount);
_objects = resizedObjects;
if(obj.isPlayer())
if(--_playersCount == 0)
{
if(_activateTask != null)
_activateTask.cancel(false);
//деактивируем регион и соседние регионы через минуту
_activateTask = ThreadPoolManager.getInstance().schedule(new ActivateTask(false), 60000L);
}
}
finally
{
lock.unlock();
}
}
public static void activate(WorldRegion currentRegion)
{
final int x1 = validX(currentRegion.getX() + 1);
final int y0 = validY(currentRegion.getY() - 1);
final int y1 = validY(currentRegion.getY() + 1);
final int z0 = validZ(currentRegion.getZ() - 1);
final int z1 = validZ(currentRegion.getZ() + 1);
for(int x = validX(currentRegion.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
getRegion(x, y, z).setActive(true);
}
public static void deactivate(WorldRegion currentRegion)
{
final int x1 = validX(currentRegion.getX() + 1);
final int y0 = validY(currentRegion.getY() - 1);
final int y1 = validY(currentRegion.getY() + 1);
final int z0 = validZ(currentRegion.getZ() - 1);
final int z1 = validZ(currentRegion.getZ() + 1);
for(int x = validX(currentRegion.getX() - 1); x <= x1; x++)
for(int y = y0; y <= y1; y++)
for(int z = z0; z <= z1; z++)
if(isNeighborsEmpty(getRegion(x, y, z)))
getRegion(x, y, z).setActive(false);
}
/**
* Активация региона, включить или выключить AI всех NPC в регионе
*
* @param activate - переключатель
*/
void setActive(boolean activate)
{
if(!_isActive.compareAndSet(!activate, activate))
return;
NpcInstance npc;
for(GameObject obj : this)
{
if(!obj.isNpc())
continue;
npc = (NpcInstance) obj;
if(npc.getAI().isActive() != isActive())
if(isActive())
{
npc.getAI().startAITask();
npc.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
npc.startRandomAnimation();
}
else if(!npc.getAI().isGlobalAI())
{
npc.stopRandomAnimation();
npc.getAI().stopAITask();
npc.getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE);
}
}
}
это кривизна клиента.видно как анимация у мобов и нпц начинает хромать
Не думаю что хорошая идея предоставлять каждому квадрату по потоку.Ахренеть, почти так как я придумал, я думал поднять поток каждому квадрату, который проверял бы наличие в нем игроков, если игроков несколько, то высчитывался бы минимальные и максимальные координаты радиуса игроков, добавлять эти координаты в массив текущего квадрата(и соседних, если радиус их затрагивает), и уже поток квадрата относительно координатам радиуса рисовал бы мобов, раз в какое то время проверял бы видимых мобов, нет ли у них желания пройтись или разовнятся, а уже заагреному мобу поднимать отдельный поток(или всех заагреных мобов в радиусе обрабатывать одним потоком , не решил)
Как мне сказал один человек: "У них энтерпрайз, им нужно не шибко большим коллективом за не шибко большие бабки сделать ТЗ. и они его делают."это кривизна клиента.
В клиенте можно найти такие жуткие косяки и костыли, видно древние времена, и лепили видимо как могли и похоже даже разные разрабы.
Пример, переделывая модели, встречаю необьяснимую кривизну, смотрим скелет:
bip01
+-Bip01_Pelvis
+-bip01_spine
+--Bip01_Spine1
+---bip01_spine2
+----Bip01_Neck
Вроде обычный скелет человека, но куда повернут вектор Bip01_Spine1 - вообще хз куда, и от него перпендикулярно Bip01_Spine1 - бред же. Потом смотрим таз, оказывается низ таза Bip01_Pelvis весами связан с bip01_spine - бред полный. В итоге как разрабы выкрутились - вектор bip01_spine во всех анимациях имеет ориентацию 1, что не влияет ни на что. Для меня загадка - зачем было городить этот кастыль, если можно было просто повернуть правильно кость спины и не связывать ее с тазом.
Потом когда доделывал анимацию - идем по фреймам, например, воина файтера - bip01 всегда находится в координатах 0-фрейма, идет изменение ориентации других костей - чем выполняется походка человека.
Теперь смотрим темную ельфийку - шо за фигня тут творится - bip01 скачется в каждой фрейме по позиции чтоб визуально не было видно не поподанение ориентации других костей на общую сцену. Это что лепил уже другой разраб? И лепил так чтоб приняли визуально, и пофигу что внутри творится.
Смотрим Камаеля - прям красотище, повторные фреймы сжаты, позиция не пляшет, четко подогнаны фреймы, просто минимум затрат на выполнение тех же действий. Видимо их делал уже опытный разраб набивший руку.
Имел ввиду, каждому квадрату, в котором находятся люди, у меня нету опыта в этой области, но если 4 потока способны обрабатывать онлайн больше 1к, то по потоку на квадрат действительно тупо выделять, думаю из сурсом это можно еще отконтролировать, аля "нахрена поднимать 4 потока, если на серве 10 человек"Не думаю что хорошая идея предоставлять каждому квадрату по потоку.
Если брать тот же ПТС то там стандартный пул на обработку мира в 4 потока, но может расширятся до количества ядер.
Дак это же хорошо Чем больше информации по теме, тем лучше, особенно когда не охота разбираться в сурсах )Интересное у нас тут комьюнити, задаёшь простой вроде бы вопрос, а на тебя вываливается экспертное мнение доброй половины форума
Насчёт потоков кста, высокая производительность достигается же не количеством потоков, а их распределённой нагрузкой. Основной сервер и все основные менеджеры/управляющие механизмы крутятся на основном потоке. А остальные потоки ты нагружаешь по мере возможности/необходимости.Имел ввиду, каждому квадрату, в котором находятся люди, у меня нету опыта в этой области, но если 4 потока способны обрабатывать онлайн больше 1к, то по потоку на квадрат действительно тупо выделять, думаю из сурсом это можно еще отконтролировать, аля "нахрена поднимать 4 потока, если на серве 10 человек"
Java. Мир по квадратам.В мире много мобов и нпц, как они живут со стороны сервера? Не создается же отдельный поток для каждого моба и нпц. По какой технологии они двигаются?