forum.boolean.name

forum.boolean.name (http://forum.boolean.name/index.php)
-   Проекты C++ (http://forum.boolean.name/forumdisplay.php?f=56)
-   -   [TrueHorror] - разработка (http://forum.boolean.name/showthread.php?t=17293)

Mr_F_ 10.11.2014 22:16

Ответ: [TrueHorror] - разработка
 
просто рекаст может быть оверкиллом для указанной ситуации.

mr.DIMAS 11.11.2014 00:17

Ответ: [TrueHorror] - разработка
 
В общем после курения википедии понял что мне отлично подходит алгоритм Дейкстры, поиск пути в графе. Веса ребер, получается, это расстояния между вершинами( то бишь - расстояние между вейпоинтами в игре ). Вершины графа придется ставить и связывать вручную для всех путей. Веса ребер будут считаться автоматически. Кто имел дело с этим алгоритмом? Есть у него "подводные камни"?

pax 11.11.2014 00:22

Ответ: [TrueHorror] - разработка
 
A* это алгоритм Дейкстры с эвристикой http://ru.wikipedia.org/wiki/%D0%90%...D0%BA%D0%B0_A*

Я бы не стал связывать вручную вершины, а сделал бы из каждой вершины рейкаст в сторону ближайших вершин. Если нет препятствий, то соединял бы.

cahekp 12.11.2014 10:22

Ответ: [TrueHorror] - разработка
 
Mine.exe : "Ошибка при запуске приложения (0xc000007b). Для выхода из приложения нажмите кнопку "OK"".
Что делать?

tirarex 12.11.2014 12:13

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от cahekp (Сообщение 289214)
Mine.exe : "Ошибка при запуске приложения (0xc000007b). Для выхода из приложения нажмите кнопку "OK"".
Что делать?

OpenAl.dll (или как его там) удали , мне помогало.

mr.DIMAS 12.11.2014 15:28

Ответ: [TrueHorror] - разработка
 
В общем сделал поиск пути по Дейкстре - мне он показался проще для понимания.

Если кому нужен код - он ЗДЕСЬ
Написан на C++11, если кто будет компилировать\запускать, то результат смотреть через отладчик - ибо мне было лень делать вывод в консольку. В скором времени запилю его в игру.



pax 12.11.2014 19:38

Ответ: [TrueHorror] - разработка
 
AStar очень простой, и работает быстрее Дейкстры, т.к. имеет направление поиска (функцию эвристики). http://www.policyalmanac.org/games/a...torial_rus.htm

Цитата:

1) Добавляем стартовую клетку в открытый список.

2) Повторяем следующее:

a) Ищем в открытом списке клетку с наименьшей стоимостью F. Делаем ее текущей клеткой.

b) Помещаем ее в закрытый список. (И удаляем с открытого)

c) Для каждой из соседних 8-ми клеток ...

Если клетка непроходимая или она находится в закрытом списке, игнорируем ее. В противном случае делаем следующее.

Если клетка еще не в открытом списке, то добавляем ее туда. Делаем текущую клетку родительской для это клетки. Расчитываем стоимости F, G и H клетки.

Если клетка уже в открытом списке, то проверяем, не дешевле ли будет путь через эту клетку. Для сравнения используем стоимость G. Более низкая стоимость G указывает на то, что путь будет дешевле. Эсли это так, то меняем родителя клетки на текущую клетку и пересчитываем для нее стоимости G и F. Если вы сортируете открытый список по стоимости F, то вам надо отсортировать свесь список в соответствии с изменениями.

d) Останавливаемся если:

Добавили целевую клетку в открытый список, в этом случае путь найден.
Или открытый список пуст и мы не дошли до целевой клетки. В этом случае путь отсутствует.
3) Сохраняем путь. Двигаясь назад от целевой точки, проходя от каждой точки к ее родителю до тех пор, пока не дойдем до стартовой точки. Это и будет наш путь.

mr.DIMAS 12.11.2014 20:28

Ответ: [TrueHorror] - разработка
 
Код в студию. Именно код, где на вход дается граф, начало и конец а на выходе массив точек, представляющий путь из начала в конец.

Мне не нужна скорость( да и я чет сомневаюсь в особом приросте производительности, учитывая что процессоры сейчас намного мощнее ), у меня всего один враг. И еще граф у меня разреженный, не двумерный массив.

Кароч не вижу смысла переходить на A*, когда я уже прикрутил код, данный выше, к игре.

Mr_F_ 12.11.2014 21:15

Ответ: [TrueHorror] - разработка
 
Вложений: 1
A* это тот же Дейкстра, но быстрее (меньше проверяет)

Дейкстра:


А*


Прикладываю сорс либы, которую я юзал в своём эксперименте по поиску путей, на который потом забил.
То что нужно вроде в CalculateGlobalPath

cahekp 12.11.2014 21:23

Ответ: [TrueHorror] - разработка
 
Вложений: 1
Цитата:

Сообщение от tirarex (Сообщение 289215)
OpenAl.dll (или как его там) удали , мне помогало.

А мне не помогло. :( При запуске говорит, что файл "OpenAL32.dll" не найден. Сайт openal.org выдает только короткое "coming (back) soon". Пришлось гуглить файл отдельно. Спустя минут 15 нашел нужный. Прикрепляю к посту на случай, если у кого возникнет такая же проблема, как у меня.

mr.DIMAS 12.11.2014 21:29

Ответ: [TrueHorror] - разработка
 
В следующем билде будет лежать установщик OpenAL'a.

pax 12.11.2014 22:36

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 289226)
Код в студию. Именно код, где на вход дается граф, начало и конец а на выходе массив точек, представляющий путь из начала в конец.

Вообще писал так, чтобы можно было и другие алгоритмы поиска использовать, поэтому есть отдельно Waypoint (на карте) и AStarNode - содержит стоимость пути, эвристику и приоритет точки.

PHP код:

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Collections;

// класс вейпоинта
public class Waypoint
{
        public 
Vector3 position;
        public List<
WayPointLinknearPoints = new List<WayPointLink>(); // связи
        
public int index// индекс вейпоинта в массиве вейпоинтов
}

// связь с заранее посчитанным расстоянием до соседней точки
public class WayPointLink
{
        public 
Waypoint waypoint;
        public 
float distance;
}


public static class 
AStar
{
    
// разные эвристики

    
public static float DistanceHeuristic(Waypoint wp1Waypoint wp2)
    {
        return (
wp1.position wp2.position).magnitude;
    }

    public static 
float Distance2DHeuristic(Waypoint wp1Waypoint wp2)
    {
        var 
vec wp1.position wp2.position;
        var 
vec2 = new Vector2(vec.xvec.z);
        return 
vec2.magnitude;
    }

    public static 
float SqrDistanceHeuristic(Waypoint wp1Waypoint wp2)
    {
        return (
wp1.position wp2.position).sqrMagnitude;
    }

    public static 
float SqrDistance2DHeuristic(Waypoint wp1Waypoint wp2)
    {
        var 
vec wp1.position wp2.position;
        var 
vec2 = new Vector2(vec.xvec.z);
        return 
vec2.sqrMagnitude;
    }

    public static 
float SumHeuristic(Waypoint wp1Waypoint wp2)
    {
        var 
vec wp1.position wp2.position;
        return 
Mathf.Abs(vec.x) + Mathf.Abs(vec.y) + Mathf.Abs(vec.z);
    }

    public static 
float Sum2DHeuristic(Waypoint wp1Waypoint wp2)
    {
        var 
vec wp1.position wp2.position;
        return 
Mathf.Abs(vec.x) + Mathf.Abs(vec.z);
    }


    
// собственно функция поиска, получает весь массив точек, начальную и конечную точки
    // возвращает путь
    
public static List<WaypointFindRoute(List<WaypointwaypointsWaypoint startWaypoint end,
        
Func<WaypointWaypointfloatheuristic null)
    {
        if(
heuristic == null)
        {
            
heuristic DistanceHeuristic;
        }

        
// открытые поинты, сортированные по приоритету (с учетом пути и эвристики)
        
var opendSet = new List<AStarNode>();
        
// дополнительная коллекция для быстрого поиска 
        
var opendSetById = new Dictionary<intAStarNode>();

        
// уже просмотренные точки
        
var closedSet = new Dictionary<intAStarNode>();

        
// стартовая точка
        
var startNode = new AStarNode(start);
        
startNode.0;
        
startNode.heuristic(startNode.waypointend);
        
startNode.startNode.startNode.h;

        
// добавляем ее в список точек для просмотра 
        
opendSet.Add(startNode);
        
opendSetById.Add(startNode.waypoint.indexstartNode);

        
// пока есть точки в открытом списке выполняем поиск
        
while (opendSet.Count 0)
        {
            
// достаем последнюю точку (ближайшую до целевой)
            
var opendSet[opendSet.Count 1];

            
// если это конечная точка, то строим маршрут
            
if (x.waypoint == end)
            {
                return 
ConstructRoute(x);
            }

            
// переносим точку в список просмотренных
            
opendSet.RemoveAt(opendSet.Count 1);
            
opendSetById.Remove(x.waypoint.index);
            
closedSet.Add(x.waypoint.indexx);

            
// просматриваем все точки, которые рядом с точкой, которую мы обрабатываем 
            
foreach (var yWp in x.waypoint.nearPoints)
            {
                
// если точка уже просмотрена, то пропускаем
                
if (closedSet.ContainsKey(yWp.waypoint.index)) continue;

                
// 
                
var wp waypoints[yWp.waypoint.index];
                
AStarNode y;

                
//расчетная стоимость пути до этой точки из обрабатываемой
                
var tentativeGScore x.yWp.distance;
                
                
// необходимость изменить родителя просматриваемой соседней точки и пересчитать параметры, 
                // при условии что стоимость пути до нее меньше или при первом добавлении
                
var tentativeIsBetter false;

                
// если точна не существует в открытом списке, создаем точку и добавляем в список открытых
                
if (!opendSetById.ContainsKey(wp.index))
                {
                    
= new AStarNode(wp);
                    
добавляем точку в сортированный список по дистанции
                    InsertSorted
(opendSety);
                    
opendSetById.Add(y.waypoint.indexy);
                    
tentativeIsBetter true;
                }
                else 
                {
                    
// точка уже добавлена в открытый список
                    
opendSetById[wp.index];
                    
// если стоимость меньше, то меняем родительскую точку и пересчитываем параметры
                    
if (tentativeGScore y.g
                    {
                        
tentativeIsBetter true;
                    }
                }

                if (!
tentativeIsBetter) continue;


                
// обновление точки
                
y.from x;
                
y.tentativeGScore// стоимость пути
                
y.heuristic(y.waypointend); // эвристика данной точки
                
y.y.y.h// приоритет 

                // сортировка массива по приоритету
                
UpdateSorted(opendSety);
            }

        }
        return 
null;
    }

    
// функция перемещает точку по массиву используя ее приоритет (не сортирует остальные точки)
    // писал давно, не помню точно как работает, но наиболее приортитетные точки в конце списка,
    // из конца дешевле убирать точки, не придется сдвигать весь массив
    
private static void UpdateSorted(List<AStarNodeopendSetAStarNode aStarNode)
    {
        var 
index opendSet.IndexOf(aStarNode);
        if (
index >= 0)
        {
            var 
isDown false;
            for (
int i index 1opendSet.Count; ++i)
            {
                if (
opendSet[i].>= opendSet[1].f)
                {
                    var 
temp opendSet[i];
                    
opendSet[i] = opendSet[1];
                    
opendSet[1] = temp;
                    
isDown true;
                }
                else
                {
                    break;
                }
            }

            if (!
isDown)
            {
                for (
int i index 1> -1; --i)
                {
                    if (
opendSet[1].opendSet[i].f)
                    {
                        break;
                    }

                    var 
temp opendSet[i];
                    
opendSet[i] = opendSet[1];
                    
opendSet[1] = temp;
                }
            }
        }
        else
        {
            
// не существует элемента в списке
        
}
    }

    
// добавляет точку в массив открытых точек в позицию с учетом приоритета
    
private static void InsertSorted(List<AStarNodeopendSetAStarNode aStarNode)
    {
        for (
int i 0opendSet.Count; ++i)
        {
            if (!(
aStarNode.opendSet[i].f)) continue;

            
opendSet.Insert(iaStarNode);
            return;
        }

        
opendSet.Add(aStarNode);
    }

    
// функция строит путь
    
private static List<WaypointConstructRoute(AStarNode end)
    {
        var 
route = new List<Waypoint>();

        var 
wp end;
        
route.Add(wp.waypoint);
        while (
wp.from != null && wp != wp.from)
        {
            
wp wp.from;
            
route.Add(wp.waypoint);
        }
        if (
route.Count>0)
            
route.RemoveAt(route.Count-1);
        
route.Reverse();

        return 
route;
    }

    public class 
AStarNode
    
{
        public 
readonly Waypoint waypoint// связь с точкой на карте
        
public float g// стоимость
        
public float h// эвристика
        
public float f;  // приоритет

        
public AStarNode from// откуда в эту точку посчитана стоимость

        
public AStarNode(Waypoint wp)
        {
            
waypoint wp;
        }
    }



mr.DIMAS 20.11.2014 19:01

Ответ: [TrueHorror] - разработка
 
Сделал стелс режим. На втором уровне теперь ходит главный злодей. Собственно протестируйте стелс режим и про баги расскажите.

Стелс-режим - [C]

Соответственно добавлен индикатор видимости, типа как в скайриме\обле.

Первая встреча с главным злодеем поможет вам построить кирпичный дом.

Есть один баг пока-что, злодей не сохраняется - то есть если загрузиться он будет в том же положении что и перед загрузкой. Это я поправлю когда сделаю ему нормальные мозги.
СКАЧАТЬ

St_AnGer 20.11.2014 20:09

Ответ: [TrueHorror] - разработка
 
Попробовал. Сразу - не подружился с камнем во втором уровне (на первом перекрёстке). Он выкинул меня за пределы уровня. Быть может, я его обидел, тем что хотел сдвинуть... А если на этом же перекрёстке пойти налево и перелезть через тележку так же улетишь с карты. И ещё, игрок съезжает с наклонных поверхностей (коэфициент трения низкий наверно).

mr.DIMAS 20.11.2014 22:06

Ответ: [TrueHorror] - разработка
 
Вложений: 1
Небольшой пачт, устраняет баги в поведении.

pax 21.11.2014 00:16

Ответ: [TrueHorror] - разработка
 
Поиграл первый раз. Появился в лесу, пошел в ту сторону, в которую изначально смотрел. Уперся в воздух. Увидел вверху красный текст. Попробовал посмотреть по сторонам. Пошел в другую сторону, шел долго, потом понял что можно бегать. При беге сильно неестественно камера меняет перспективу имхо. Забежал в шахту, сзади что-то обвалилось, загрузился наверное второй уровень. Иду вперед, на столе что-то слишком белое для темного туннеля, записку не прочитал, что-то взял со стола и пошел дальше. Меня убивает какой-то черт. Красный экран, сверху надпись говорит что надо найти друзей. Но я мертвый, не могу их искать дальше и камера постоянно крутится. Не понял как начать сначала, можно только продолжить смотреть на свою смерть...

mr.DIMAS 21.11.2014 00:19

Ответ: [TrueHorror] - разработка
 
Загрузка\сохранение тебе в помощь. F5\F9 по умолчанию.

pax 21.11.2014 00:20

Ответ: [TrueHorror] - разработка
 
В меню не нашел...

mr.DIMAS 21.11.2014 08:07

Ответ: [TrueHorror] - разработка
 
Ок. Сделаю отдельные пункты в меню. И сохранение в разные слоты. Со второго уровня игру придется проходить аккуратно, иначе будут постоянно убивать.

mr.DIMAS 22.11.2014 20:15

Ответ: [TrueHorror] - разработка
 
Если кто помнит, у меня с новым булетом были проблемы - персонаж постоянно "дрожал", была в общем ебала с физикой. Так вот виновата была автоматическая оптимизация с SSE. Новый булет компилится с ключами /arch:SSE2 или /arch:SSE. Стоило выключить SSE, все стало чудесно - никакой тряски и прочей дури. В то же время в новом булете есть ручная оптимизиция с SSE, так что скорость работы осталась прежней. Если чо, компилятор от 2012 студии с Update 1. Видимо это platform-specific bug какой-то. Гугление ни к чему не привело. Подсказал про это Samodelkin, за что ему спасибо.

tirarex 22.11.2014 23:48

Ответ: [TrueHorror] - разработка
 
Проводов и шашек не хватает , половина обьектов под землей либо в воздухе, врага видел 1 раз...

https://www.dropbox.com/s/a3pivnsiri...49.12.png?dl=0

mr.DIMAS 23.11.2014 00:18

Ответ: [TrueHorror] - разработка
 
Цитата:

Проводов и шашек не хватает
Все на месте. Искать нужно лучше. Я раскидал объекты по уровню.

Цитата:

половина обьектов под землей либо в воздухе
Либо так, либо тормоза физики.
Цитата:

врага видел 1 раз...
Удачно?

tirarex 23.11.2014 11:08

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 289609)
Все на месте. Искать нужно лучше. Я раскидал объекты по уровню.


Либо так, либо тормоза физики.
Удачно?

У меня не было багов физики пока о них кто то не отписался и ты не начал ее портить :-)

- Удачно?
да, спокойно прошел около него и пошел дальше.

А проводов вообще нет ! 1 на столе лежал , я всю комнату облазил но их нет , я все же склоняюсь к варианту их падения в еб*ня.

mr.DIMAS 23.11.2014 12:06

Ответ: [TrueHorror] - разработка
 
Цитата:

я всю комнату облазил но их нет
Лал. Провода почти все на втором этаже в ответвлениях. И они не могут упасть - они зафиксированы.

tirarex 23.11.2014 13:19

Ответ: [TrueHorror] - разработка
 
Я думал все как раньше , в кучках =)

Поднялся по лестнице а дальше шел вперед , провалился под землю , полет нормальный.

https://www.dropbox.com/s/4mmjbvybkq...19.02.png?dl=0

mr.DIMAS 23.11.2014 13:27

Ответ: [TrueHorror] - разработка
 
Проваливания - это очередные приколы булета. Попробую в этом месте что-нибудь сделать.

pozitiffcat 29.11.2014 23:30

Ответ: [TrueHorror] - разработка
 
Под линуха есть сборки? Нет возможности под окнами пощупать

mr.DIMAS 29.11.2014 23:36

Ответ: [TrueHorror] - разработка
 
движок жи на директе, только на виртуалке если попробовать, хотя нет - игра требует ps 3.0 .

кроссплатформу можно будет сделать когда будет готова игра

mr.DIMAS 30.11.2014 21:33

Ответ: [TrueHorror] - разработка
 
Вложений: 2
Наконец-то сделал нормальную отрисовку текста. Для заполнения атласа буквами используется последняя версия FreeType. Атлас заполняется только английскими и русскими буквами + знаки пунктуации. После чего все рисуется квадами. В общем результатом я доволен, ибо текст выглядит отлично. Кому интересно как это все работает, то в подписи есть ссылка на гитхаб, там файлы BitmapFont.cpp и TextRenderer.cpp
Пока что не сделал батчинг текста в один вершинный буфер, вскоре это исправлю.

mr.DIMAS 02.12.2014 00:42

Ответ: [TrueHorror] - разработка
 
У меня какие-то наркоманские мысли появились по поводу ускорения отрисовки.

1) Суем вершинные\индексные буферы N объектов в один большой вершинный\индексный подходящего размера
2) Пилим вершинный шейдер в котором определяем массив юниформ матриц количеством N
3) Для каждой вершины k-го объекта запиливаем дополнительный параметр указывающий на номер матрицы трансформации для этого объекта( те матрицы что в шейдере ), можно засунуть в D3DCOLOR - то бишь цвет.
4) В шейдере - берем этот "цвет" - выковыриваем из него номер матрицы, которой эту вершину нужно трансформировать, и затем трансформируем.
5) Имеем N объектов отрисованных за один DIP.
6) ...
7) PROFIT!!!

Хз, скорее всего это уже придумано или вообще не реализуемо, просто поделился своими мыслями. Кароч я упоролся или такое возможно?

Mr_F_ 02.12.2014 00:47

Ответ: [TrueHorror] - разработка
 
Цитата:

1) Суем вершинные\индексные буферы N объектов в один большой вершинный\индексный подходящего размера
2) Пилим вершинный шейдер в котором определяем массив юниформ матриц количеством N
3) Для каждой вершины k-го объекта запиливаем дополнительный параметр указывающий на номер матрицы трансформации для этого объекта( те матрицы что в шейдере ), можно засунуть в D3DCOLOR - то бишь цвет.
4) В шейдере - берем этот "цвет" - выковыриваем из него номер матрицы, которой эту вершину нужно трансформировать, и затем трансформируем.
5) Имеем N объектов отрисованных за один DIP.
так и делался инстансинг, до того как появился хардварный инстансинг. в старых демках ксорса было.

ежели объекты статические, то и вовсе матрицы не нужны, а просто взял склеил всё в огромные куски.

mr.DIMAS 02.12.2014 00:52

Ответ: [TrueHorror] - разработка
 
Даст прирост производительности? Стоит ли запиливать такое в движок?

А как тогда решать вопрос с текстурами? Я обрисовал случай, когда все объекты имеют разные текстуры. Кароч видимо не прокатит такая метода, или прокатит только с группировкой объектов по текстурам.

Mr_F_ 02.12.2014 03:16

Ответ: [TrueHorror] - разработка
 
Вложений: 1
Цитата:

Даст прирост производительности? Стоит ли запиливать такое в движок?
но это нужно если у тебя тебя динамики много в игре. я пока вроде не наблюдал - пяток бочек можно и отдельными дипами нарисовать, разницы не увидишь.
Цитата:

А как тогда решать вопрос с текстурами?
в атлас сувать придётся-с. а здесь много заморочек, если нужен тайлинг, но вполне реализуемо. в последнем обновлении Faded так и сделано (скрин старый, щас там DXT1, а не 5, и занимать площадь стараюсь получше).

mr.DIMAS 02.12.2014 10:47

Ответ: [TrueHorror] - разработка
 
Можно обойтись и без атласов, просто перед рендером сгруппировать сюрфейсы по текстурам, хз как у тебя сделано, а у меня при загрузке модели она автоматически разбивается на сюрфейсы, где каждому сюрфейсу соответствует одна текстура. Конечно таким образом за один дип отрисовать не получится, но дипов будет ровно столько сколько текстур( у меня нет системы материалов, поэтому мне проще ). Даже простая сортировка сюрфейсов по текстурам перед отрисовкой дает неплохой прирост производительности за счет снижения смен текстур. В общем я попробую сделать и скажу че из это вышло.

Mr_F_ 02.12.2014 12:57

Ответ: [TrueHorror] - разработка
 
Цитата:

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

mr.DIMAS 06.12.2014 18:36

Ответ: [TrueHorror] - разработка
 
Как правильно организовать перемещение персонажа? Сейчас у меня метод "в лоб". Создаем капсулу вокруг игрока и задаем ей линейную скорость в нужно направлении, отсюда всякие косяки в виде плохого перемещения по лестницам и наклонным поверхностям( даже когда трение выставлено в ноль ). Поэтому появилась идейка сделать перемещение на рейкастинге. Замысел такой: бросаем луч вниз под игроком, находим точку пересечения и перемещаем в нее игрока, затем кидаем еще луч перед игроком на небольшом расстоянии от него тоже вниз но с начальной точкой чуть выше точки пересечения первого луча( то бишь - высота шага ), если есть пересечение то можно сделать шаг -> делаем интерполяцию между двумя полученными точками. В принципе, такой подход работает и довольно неплохо. Но как делают труЪ пацаны?

Mr_F_ 06.12.2014 18:51

Ответ: [TrueHorror] - разработка
 
Цитата:

но с начальной точкой чуть выше точки пересечения первого луча( то бишь - высота шага )
а если типа стол стоит?
по-твоему получается что можно идти сквозь стол, т.к. лучи все проверяться под столом будут.

ARA 06.12.2014 18:57

Ответ: [TrueHorror] - разработка
 
Цитата:

Но как делают труЪ пацаны?
Делать коллайдеры лестниц и всех непонятных мест для столкновений с гг в виде наклонённой сплошной плоскости.

Вован 07.12.2014 01:12

Ответ: [TrueHorror] - разработка
 
L.D.M.T. Выкладывал исходники для Blitz3D и среди прочих, лежал ничем не примечательный - fpstest. Вот в нём, идеальное управление, мне так кажется. Думаю, тебе не составит труда адаптировать его под C++

Вот кусок.
Цитата:

Function UpdatePlayer()

;look
mxspd=MouseXSpeed()*0.25
myspd=MouseYSpeed()*0.25
MoveMouse GraphicsWidth()/2,GraphicsHeight()/2
pitch=pitch+myspd
yaw=yaw-mxspd
If pitch<-90 Then pitch=-90
If pitch>90 Then pitch=90
RotateEntity camera,pitch,yaw,0
RotateEntity player,0,yaw,0

;movement
If KeyDown(200)
vz=vz+0.05
ElseIf KeyDown(208)<-- Это скобка.
vz=vz-0.05
EndIf
vz=vz*0.9
If KeyDown(203)
vx=vx-0.05
ElseIf KeyDown(205)
vx=vx+0.05
EndIf
vx=vx*0.9

;steps and floor collisions
pick=LinePick (EntityX(player),EntityY(player),EntityZ(player),0 ,-playerheight,0)
If pick
PositionEntity player,EntityX(player),PickedY()+playerheight,Enti tyZ(player)
vy=0
EndIf
vy=vy-0.2
vy=vy*0.9


;move player pivot
MoveEntity player,vx,0,vz
TranslateEntity player,0,vy,0

;move camera + player meshes smoothly
PositionEntity camera,EntityX(player),EntityY(camera),EntityZ(pla yer)
TranslateEntity camera,0,(EntityY(player)-(EntityY(camera)-cameraheight))*0.1,0
Весь.
;fps level, graphics & code by rob cummings ([email protected])
;unoptimised

;framerate variables
Const FPS=85
Const debug=0

;collision variables
Global col_level=1,col_player=2,col_bullet=3

;player variables
Global pitch#,yaw#,mxspd#,myspd#,vx#,vy#,vz#,camera,playe r,gun,pick,gunsight,guntarget,hand

;other vars
Global firetex,firetexpos#,bob#,target,scorchsprite,plasm asprite,shockwavesprite,recoil#

Global cameraheight#=3.5 ;height of player eyes
Global playerheight#=4 ;height of collision sphere
Global playerradius#=2 ;radius of collision sphere

;types
Type plasma
Field ent,rot#
End Type

Type scorch
Field ent,life#
End Type

Type shockwave
Field ent,life#,size#
End Type

;display
AppTitle "FPS Example"
Text 20,2,"Choose mode by pressing a number key:"
Text 20,32,"(1) 800,600 windowed "
Text 20,48,"(2) 800,600 fullscreen"
Text 20,64,"(3) 1024,768 fullscreen"
Text 20,80,"(4) 1600,1200 fullscreen"
Repeat
If KeyDown(2) Or debug Then xres=800:yres=600:mode=2:Exit
If KeyDown(3) Then xres=800:yres=600:mode=1:Exit
If KeyDown(4) Then xres=1024:yres=768:mode=1:Exit
If KeyDown(5) Then xres=1600:yres=1200:mode=1:Exit
VWait
Forever
Graphics3D xres,yres,0,mode
HidePointer

;setup
camera=CreateCamera()
CameraRange camera,0.2,400
CameraFogMode camera,1
CameraFogColor camera,140,140,120
CameraFogRange camera,-100,800

;hud
tex=LoadTexture("target.png",2)
TextureBlend tex,2
target=CreateSprite()
EntityTexture target,tex
EntityFX target,1
EntityOrder target,-1
EntityBlend target,3
EntityParent target,camera
MoveEntity target,0,0,12
EntityAlpha target,0.8

;player
player=CreatePivot()
hand=CreatePivot()
EntityParent hand,camera
MoveEntity hand,.8,-.8,.8

;gun
gun=LoadAnimMesh("gun.b3d")
firetex=LoadTexture("fury.png")
For i=1 To CountChildren(gun)
If EntityName(GetChild(gun,i))="red" EntityTexture GetChild(gun,i),firetex
Next
gunsight=CreatePivot()
EntityParent gunsight,camera
MoveEntity gunsight,-2,2,500
guntarget=CreatePivot()

;weapon effects - plasma
tex=LoadTexture("plasma.png",2)
TextureBlend tex,2
plasmasprite=CreateSprite()
EntityTexture plasmasprite,tex
EntityFX plasmasprite,1
EntityBlend plasmasprite,3
EntityAlpha plasmasprite,0.8
ScaleSprite plasmasprite,1,2
HideEntity plasmasprite

;weapon effects - scorch
tex=LoadTexture("scorch.png",2)
;TextureBlend tex,2
scorchsprite=CreateSprite()
EntityTexture scorchsprite,tex
EntityFX scorchsprite,1
EntityAlpha scorchsprite,0.8
ScaleSprite scorchsprite,1,2
SpriteViewMode scorchsprite,2
HideEntity scorchsprite


;weapon effects - shockwave
tex=LoadTexture("shockwave.png",2)
TextureBlend tex,2
shockwavesprite=CreateSprite()
EntityTexture shockwavesprite,tex
EntityFX shockwavesprite,1
EntityBlend shockwavesprite,3
EntityAlpha shockwavesprite,0.8
ScaleSprite shockwavesprite,1,2
SpriteViewMode shockwavesprite,2
HideEntity shockwavesprite

;level
level=LoadAnimMesh("final.b3d")

;level collisions
EntityType level,col_level,1

;remove collision on sky (a hack cos I didn't want to go back and change the level)
sky=FindChild(GetChild(level,1),"sky glow")
EntityType sky,0

;set up picking - this is because shooting and stair climbing needs pickable geometry. You can use picks for other stuff too
ent = level ; specify your level
While ent
EntityPickMode ent,2
ent = NextChild(ent)
Wend


;other collisions
EntityType player,col_player
EntityRadius player,playerradius,playerradius/2
Collisions col_player,col_level,2,2
PositionEntity player,100,10,-65
ResetEntity player
Collisions col_bullet,col_level,2,1



;mainloop
period=1000/FPS
time=MilliSecs()-period
While Not KeyHit(1)
Repeat
elapsed=MilliSecs()-time
Until elapsed

ticks=elapsed/period
tween#=Float(elapsed Mod period)/Float(period)

For k=1 To ticks
time=time+period
If k=ticks Then CaptureWorld

UpdateWorld
UpdatePlayer
UpdatePlasma
UpdateScorch
UpdateShockwave

Next

RenderWorld
Flip
Wend
End

Function UpdatePlayer()

;look
mxspd=MouseXSpeed()*0.25
myspd=MouseYSpeed()*0.25
MoveMouse GraphicsWidth()/2,GraphicsHeight()/2
pitch=pitch+myspd
yaw=yaw-mxspd
If pitch<-90 Then pitch=-90
If pitch>90 Then pitch=90
RotateEntity camera,pitch,yaw,0
RotateEntity player,0,yaw,0

;movement
If KeyDown(200)
vz=vz+0.05
ElseIf KeyDown(208)
vz=vz-0.05
EndIf
vz=vz*0.9
If KeyDown(203)
vx=vx-0.05
ElseIf KeyDown(205)
vx=vx+0.05
EndIf
vx=vx*0.9

;steps and floor collisions
pick=LinePick (EntityX(player),EntityY(player),EntityZ(player),0 ,-playerheight,0)
If pick
PositionEntity player,EntityX(player),PickedY()+playerheight,Enti tyZ(player)
vy=0
EndIf
vy=vy-0.2
vy=vy*0.9


;move player pivot
MoveEntity player,vx,0,vz
TranslateEntity player,0,vy,0

;move camera + player meshes smoothly
PositionEntity camera,EntityX(player),EntityY(camera),EntityZ(pla yer)
TranslateEntity camera,0,(EntityY(player)-(EntityY(camera)-cameraheight))*0.1,0


;gun
x1#=EntityX(gun,1)
y1#=EntityY(gun,1)
z1#=EntityZ(gun,1)
x2#=EntityX(gunsight,1)
y2#=EntityY(gunsight,1)
z2#=EntityZ(gunsight,1)
pick=LinePick(x1,y1,z1,x2-x1,y2-y1,z2-z1)
If pick
PositionEntity guntarget,PickedX(),PickedY(),PickedZ()
EndIf

PositionEntity gun,EntityX(hand,1),EntityY(hand,1)-((Cos(bob)*Sqr(vx*vx+vz*vz))*.2),EntityZ(hand,1)
MoveEntity gun,0,0,recoil#
PointEntity gun,guntarget
PositionTexture firetex,firetexpos,firetexpos
firetexpos=firetexpos+0.01
bob=bob+4
recoil=recoil*.9

;firing code
If MouseHit(1) ShootPlasma() : recoil=-.6

End Function

Function ShootPlasma()
p.plasma=New plasma
p\ent=CopyEntity(plasmasprite)
PositionEntity p\ent,EntityX(gun),EntityY(gun),EntityZ(gun)
PointEntity p\ent,guntarget
MoveEntity p\ent,0,0,-1
p\rot=Rand(360)
EntityType p\ent,col_bullet
End Function

Function UpdatePlasma()
For p.plasma=Each plasma
MoveEntity p\ent,0,0,2.5
RotateSprite p\ent,p\rot
p\rot=p\rot+20
If EntityCollided(p\ent,col_level)

;find angle of collision
nx#=0
ny#=0
nz#=0
num#=CountCollisions(p\ent)
For i=1 To num
nx=nx+CollisionNX(p\ent,i)
ny=ny+CollisionNY(p\ent,i)
nz=nz+CollisionNZ(p\ent,i)
x#=x#+CollisionX(p\ent,i)
y#=y#+CollisionY(p\ent,i)
z#=z#+CollisionZ(p\ent,i)
Next
nx=nx/num
ny=ny/num
nz=nz/num
x=x/num
y=y/num
z=z/num




;scorch
s.scorch=New scorch
s\ent=CopyEntity(scorchsprite)
s\life#=0.8
RotateSprite s\ent,Rand(360)
temp#=1+Rnd(2)
ScaleSprite s\ent,temp#,temp#
PositionEntity s\ent,x,y,z
AlignToVector s\ent,-nx,-ny,-nz,0
MoveEntity s\ent,0,0,-0.5

;shockwave
w.shockwave=New shockwave
w\ent=CopyEntity(shockwavesprite)
w\life#=2
w\size=0.2
RotateSprite w\ent,Rand(360)
PositionEntity w\ent,x,y,z
RotateEntity w\ent,EntityPitch(s\ent),EntityYaw(s\ent),0
MoveEntity w\ent,0,0,-0.5


FreeEntity p\ent
Delete p
EndIf
Next
End Function

Function UpdateScorch()
For s.scorch=Each scorch
EntityAlpha s\ent,s\life
s\life=s\life-0.005
If s\life<0
FreeEntity s\ent
Delete s
EndIf
Next
End Function

Function UpdateShockwave()
For s.shockwave=Each shockwave
EntityAlpha s\ent,s\life
ScaleSprite s\ent,s\size,s\size
s\life=s\life-0.06
s\size=s\size+0.1
If s\life<0
FreeEntity s\ent
Delete s
EndIf
Next
End Function

Function NextChild(ent)
If CountChildren(ent)>0
Return GetChild(ent,1)
EndIf

Local foundunused=False
Local foundent = 0, parent,sibling
While foundunused=False And ent<>0
parent = GetParent(ent)
If parent<>0
If CountChildren(parent)>1
If GetChild(parent,CountChildren(parent))<>ent
For siblingcnt = 1 To CountChildren(parent)
sibling = GetChild(parent,siblingcnt)
If sibling=ent
foundunused = True
foundent = GetChild(parent,siblingcnt+1)
EndIf
Next
EndIf
EndIf
EndIf
ent = parent
Wend
Return foundent
End Function


А вообще, игра нормально идёт. Умудрился провалиться со второго этажа сквозь пол, а так всё норм. Cтелс как в Skyrim, темно как Alone in the Dark, страшно как в кино, только графику подправить бы. Ты игры делаешь и ракеты запускаешь, мне бы твои таланты...

mr.DIMAS 14.12.2014 00:59

Ответ: [TrueHorror] - разработка
 
Исправил много багов и поправил прозводительность. В общем в каждой части рендера было много CreateStateBlock() и таким образом я доверял стейты директу, как выянилось после профайлинга на этих стейтблоках падает до 15 процентов производительности, выпилив их в взял контроль над стейтами в свои руки я повысили производительность на 20 процентов( убрав лишние изменения стейтов - у меня Pure-device ). Сделал выбор с вертикальной синхронизацией или без. Максимальный фпс на моей машине - 90 к\с в шахте( тени от спотов и FXAA ).

Так вот тут сразу вопрос. Обычно я считал дельта-тайминг и просто домножал все значения на него чтобы игра шла нормально при любом фпс. Когда фпс от 30 до 60 кадров то все нормас работает, когда выходит из этих границ начинается ебала. В общем игра начинает работать не стабильно и физика тупить начинает. Как труЪ пацаны делают дельта тайминг? Читал это , но мне тамошние подходы не помогают.

pax 14.12.2014 03:35

Ответ: [TrueHorror] - разработка
 
Я бы так сделал (в Unity ограничение по времени работы физики на кадр задается):

PHP код:

var physicsStep 1f/100f;  // 100 раз в секунду обновлять физику
var maxPhysicsTimePerFrame 0.15f// максимальное время работы физики за кадр
var lastTime Now();
var 
physicsTimer 0// накопитель времени физики

while(true){
   var 
currentTime Now();
   var 
deltaTime currentTime lastTime;
   
lastTime currentTime;

   
physicsTimer += deltaTime;

   
Update(deltaTime); 
   
Render();

   var 
physicsSteps = (int)(physicsTimer physicsStep); // число шагов физики
   
physicsTimer -= physicsStep*physicsSteps// сбрасываем таймер физики (оставляем остаток)

   
var physicsWorkTime 0;
   var 
physicsStartTimeNow(); 

   for(
int i 0physicsStepsi++){
      
UpdatePhysics(physicsStep); // обновление физики
      
var physicsEndTime Now();
      
physicsWorkTime += physicsEndTime physicsStartTime;
      
physicsStartTime physicsEndTime;

      
// ограничение по времени работы физики
      
if(physicsWorkTime >= maxPhysicsTimePerFrame )
         break;
   }



pax 14.12.2014 13:35

Ответ: [TrueHorror] - разработка
 
Я тут подумал, скорее всего Render() в самый конец надо бы поставить, после цикла физики... и в Unity 50 раз по умолчанию в секунду физика работает, т.е. шаг физики 0,02 с (1/50)

mr.DIMAS 14.12.2014 14:01

Ответ: [TrueHorror] - разработка
 
Попробую так сделать.

Вопрос теперь по графическому конвейру. Вызов SetRenderState( D3DRS_COLORWRITEENABLE, 0 ) выключает запись цвета в буфер кадра, исключает ли он при этом работу пиксельного шейдера? Иными словами, при выключенной записи в буфер кадра будет ли работать пиксельный шейдер?

Mr_F_ 14.12.2014 14:10

Ответ: [TrueHorror] - разработка
 
Цитата:

Иными словами, при выключанной записи в буфер кадра будет ли работать пиксельный шейдер?
не факт.
советуют ставить NULL на PS: http://www.gamedev.net/topic/641257-...pass-worth-it/

не факт, потому что, к примеру, ты можешь делать clip/discard в шейдере, что повлияет на глубину тоже.
мне думается, что умный драйвер может не выполнять шейдеры, если дискарда точно нет, и запись выключена, но я не проверял.

mr.DIMAS 14.12.2014 14:20

Ответ: [TrueHorror] - разработка
 
Я просто оптимизациями занимаюсь, и пока довольно успешно( ибо уже выжал 30% ). Так вот деферед у меня организован хорошо, но есть один момент.

Код:

для каждого источника света
{
    ставим простейшие шейдеры для отрисовки в трафарет
    рисуем  ограничивающий меш в трафарет
    ставим шейдеры для фулскрин квада( пиксельный отвечает за освещение )
    рисуем квад
}

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

bool boundingVolumeRender;
...
if( boundingVolumeRender )
  return float4( 1, 1, 1, 1 );
else
  calculateLighting

получается в пиксельном шейдере статическое ветвление, оно практически бесплатно, вершинный шейдер тоже не проблема, ибо разница только в матрицах - для ограничивающего объема это worldViewProjection, а для квада это просто projection.

mr.DIMAS 14.12.2014 15:14

Ответ: [TrueHorror] - разработка
 
2pax, плохо работает, физику все равно начинает шатать, что аж улетаю в небеса. буду сидеть разбираться, может найду выход из этой тупой ситуации.

нашел это - http://bulletphysics.org/mediawiki-1...ical_Game_Loop
попробую реализовать

mr.DIMAS 14.12.2014 16:03

Ответ: [TrueHorror] - разработка
 
Наконец-то все работает так как должно. Нужно было сразу загуглить bullet physics low fps. :-D

В общем одной проблемой меньше

pax 14.12.2014 16:23

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 290628)
2pax, плохо работает, физику все равно начинает шатать, что аж улетаю в небеса. буду сидеть разбираться, может найду выход из этой тупой ситуации.

:pardon: я предположил как работает физика Unity. В Unity есть еще FixedUpdate (для применения сил к телам), он срабатывает ровно столько же раз, сколько и обновление физики, т.е. перед каждым UpdatePhysics(physicsStep) из моего примера, а Update используется для логики игры. Но это было всего-лишь мое предположение :) как оно устроено.

mr.DIMAS 14.12.2014 16:30

Ответ: [TrueHorror] - разработка
 
Все равно спасибо за то, что помогаешь.


теперь в меню Авторы, "Особые благодарности" на одного человека больше

MiXaeL 14.12.2014 23:22

Ответ: [TrueHorror] - разработка
 
У тебя физика и рендер в одном потоке?
Теоретически физика может крутиться на отдельном ядре с ~фиксированным числом циклов в секунду, синхронизируясь каждый кадр с графикой и юзеринпутом.

mr.DIMAS 14.12.2014 23:46

Ответ: [TrueHorror] - разработка
 
Физика и рендер в одном потоке, не хочется проблем с многопоточностью.
Да и к тому же уже все работает как надо.

tirarex 15.12.2014 21:43

Ответ: [TrueHorror] - разработка
 
Попробовал на своем планшете (Intel z3735F 1.8Ghz) 13 фпс , не играбельно. Может дистанцию прорисовки ограничивать ?

mr.DIMAS 15.12.2014 23:37

Ответ: [TrueHorror] - разработка
 
̶т̶ы̶ ̶б̶ы̶ ̶е̶щ̶е̶ ̶н̶а̶ ̶т̶а̶п̶к̶е̶ ̶п̶о̶п̶р̶о̶б̶о̶в̶а̶л̶ ̶с̶ы̶г̶р̶а̶т̶ь̶
у мобильных устройств говеная память, слишком медленная, поэтому деферед не дает на них особых преимуществ.
ну и к тому же я сейчас работаю над производительностью
кстати при новом главном цикле, играбельно даже при 10 фпс - атвичаю.

tirarex 16.12.2014 08:24

Ответ: [TrueHorror] - разработка
 
Я очень надеюсь!
А устройство не такое и слабое , хл2,портал2 тянет почти без лагов.

mr.DIMAS 18.12.2014 22:46

Ответ: [TrueHorror] - разработка
 
Новая версия. Переделал главный цикл, что обеспечило возможность играть без проблем при низких фпс. Добавил возможность включать\выключать Vsync( в файле mine.cfg ) - изначально vsync выключен. Улучшил мозги боту. Добавил ему фонарь на бошку чтоб мог палить игрока, когда тот прячется в тени. Немного продлил третий уровень. В основном же потратил кучу времени на переписывание ГУИ.

Что я хочу: чтоб написали средний фпс, и описали играбельность при низких фпс - для этого я оставил в меню включение теней от поинлайтов - теней не будет - а вот шадоумапа строится - поэтому в шахте будет в районе 25000 дипов - что даст нехилую нагрузку на гпу и снизит фпс до неприличных значений. Тени от поинтов я не буду вообще вставлять в проект, они очень жручие а оптимизировать мне их впадлу.

Также добавил DXT текстуры, что позволило сжать все текстуры до 65 мб.

И еще: теперь можно выглядывать из-за угла - по умолчанию клавиши [Q] [E], 'Использовать' переставил на [R]
Из багов что есть: хдр не работает.

СКАЧАТЬ

Mr_F_ 18.12.2014 22:53

Ответ: [TrueHorror] - разработка
 
Цитата:

поэтому в шахте будет в районе 25000 дипов
da ty ohuel?
на ПК 4к - критическое число.

mr.DIMAS 18.12.2014 23:00

Ответ: [TrueHorror] - разработка
 
я знаю, читай внимательней:
Цитата:

для этого я оставил в меню включение теней от поинлайтов
значит их можно врубать по желанию, тоесть для тестирования игры на мощном железе при низких фпс, больше ни для чего. кстати обычно количество дипов у меня около 120

tirarex 18.12.2014 23:06

Ответ: [TrueHorror] - разработка
 
15-20фпс на улице
2-15 в шахте
Предметы под землей (фонари)
Дядя заметил меня идущего в присядке через стену и начал орать...
Бег на низких фпс превращается в судороги.
intel z3735f

Mr_F_ 18.12.2014 23:28

Ответ: [TrueHorror] - разработка
 
Цитата:

кстати обычно количество дипов у меня около 120
сколько же у тебя поинтов с тенями, что становится 25к? что-то здесь не так. или им куллинг не считается? у них же дистанция обычно маленькая к тому же.

mr.DIMAS 18.12.2014 23:32

Ответ: [TrueHorror] - разработка
 
25 штук, по шесть фейсов кубмапы получается: 25*6* ~120 = ~19000
на третьем уровне вообще 50 источников света
я тени от них не буду юзать, так что все равно сколько там дипов

pozitiffcat 18.12.2014 23:51

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от mr.DIMAS (Сообщение 290836)
Новая версия. Переделал главный цикл, что обеспечило возможность играть без проблем при низких фпс. Добавил возможность включать\выключать Vsync( в файле mine.cfg ) - изначально vsync выключен. Улучшил мозги боту. Добавил ему фонарь на бошку чтоб мог палить игрока, когда тот прячется в тени. Немного продлил третий уровень. В основном же потратил кучу времени на переписывание ГУИ.

Что я хочу: чтоб написали средний фпс, и описали играбельность при низких фпс - для этого я оставил в меню включение теней от поинлайтов - теней не будет - а вот шадоумапа строится - поэтому в шахте будет в районе 25000 дипов - что даст нехилую нагрузку на гпу и снизит фпс до неприличных значений. Тени от поинтов я не буду вообще вставлять в проект, они очень жручие а оптимизировать мне их впадлу.

Также добавил DXT текстуры, что позволило сжать все текстуры до 65 мб.

И еще: теперь можно выглядывать из-за угла - по умолчанию клавиши [Q] [E], 'Использовать' переставил на [R]
Из багов что есть: хдр не работает.

СКАЧАТЬ

Венды нету ((( может под вайном запустится.

LLI.T.A.L.K.E.R. 19.12.2014 00:07

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от tirarex (Сообщение 290680)
Попробовал на своем планшете (Intel z3735F 1.8Ghz) 13 фпс , не играбельно. Может дистанцию прорисовки ограничивать ?

Сижу за старым ЭВМ, нет возможности узнать.

Не подскажете как это он на планшете игру запускал? Это *.exe игра?

pozitiffcat 19.12.2014 00:08

Ответ: [TrueHorror] - разработка
 
Цитата:

Сообщение от LLI.T.A.L.K.E.R. (Сообщение 290846)
Сижу за старым ЭВМ, нет возможности узнать.

Не подскажете как это он на планшете игру запускал? Это *.exe игра?

вендозный планшет

pozitiffcat 19.12.2014 00:52

Ответ: [TrueHorror] - разработка
 
Запустил на ноуте. Очень круто, играл ночью, сейчас, обосрался. Завалил первый же моб. Супер круто, понравилось. На ноуте неудобно играть - забил. ФПС 15, ноут слабенький.


Часовой пояс GMT +4, время: 02:59.

vBulletin® Version 3.6.5.
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Перевод: zCarot