Форум программистов, компьютерный форум, киберфорум
Storm23
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

Задумал я сделать игрушку. День 6. HUD.

Запись от Storm23 размещена 01.04.2016 в 18:37
Показов 7615 Комментарии 7
Метки c#, crazy dev, games, hud, winforms

День 6. HUD.

На шестой день Бог создал человека. Поэтому и мы в этот день обязаны сделать что-то человеческое, а не через пень-колоду, как обычно.

Окей, будем потихоньку двигаться дальше. Если кто не в курсе, HUD - это Head-Up Display - информация выводимая поверх игрового мира. Обычно это карта, прицел, показатели здоровья, оружия и так далее.

Захотелось мне сделать HUD в стиле футуристик. Что-то вот типа такого:
Футуристик


Я бы не писал этот блог, если бы я просто взял графический редактор и нарисовал все что мне нужно. Это не наш путь. Тем более, что художник из меня никакой. Кроме того, реально это сделать сложно. Ведь HUD это не просто статичная картинка. Элементы должны двигаться, поддерживать эффекты, отображать информацию, меняться и так далее.

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

Версия 1

Сначала я подумал сделать набор специальных контролов. Кидаешь их на форму, двигаешь, растягиваешь, компонуешь. И не только подумал, но и сделал.

Выглядело это примерно так:

Нажмите на изображение для увеличения
Название: 0_25.png
Просмотров: 1108
Размер:	29.7 Кб
ID:	3717

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

И были еще невизуальные компоненты. Они использовались для добавления эффектов - свечения, раздвоения, отрисовки бордеров и заливки.

Все это я сделал, и оно даже работало. Но результат меня не порадовал. Слишком много проблем при таком подходе. Хоть он и выглядит простым, но на практике нарисовать таким образом что-то сложно. Не буду перечислять всех проблем, лишь некоторые. Трудно двигать маркеры по форме, VS к такой тонкой работе приспособлена мало. Трудно регулировать порядок отрисовки примитивов, z-ордер. Тяжело задавать все маркеры в свойствах других контролов. И так далее. В общем - неудобно.

Поэтому было решено отказаться от услуг редактора форм VS, и сделать свой (тот самый:).

Вы не поверите, но я переделывал этот редактор целиком - три раза. Хотя сегодня и первое апреля, но это не шутка.

Версия 2

Вторая версия выглядела так:

Нажмите на изображение для увеличения
Название: 0_21.png
Просмотров: 1127
Размер:	61.5 Кб
ID:	3718

Было создано отдельное приложение - редактор. На нем можно было размещать и двигать маркеры. Было дерево объектов, в котором можно было добавлять элементы. И была панель свойств, где можно было менять свойства этих объектов.

Сама идея отрисовки осталась почти такой же: были маркеры - опорные точки. И были так называемые "слои" - специальные объекты, которые соединяли точки линиями или делали эффекты. При этом к одному маркеры могли цепляться несколько слоев, что позволяло наслаивать их друг на друга.

Этот редактор был уже лучше предыдущего. Но он меня тоже не устроил.

Проблема заключалась в том, что для отрисовки более-менее приличного изображения нужно куча всяких настроек, объектов, свойств и так далее. И вот проблема как раз с панелью свойств. Даже та панель, что выше на рисунке - заняла у меня довольно много времени. А мне таких панелей нужно было понаделывать десятки. С десятками свойств. Это очень долго. При таком подходе я бы тратил время не на саму графику а на создание панелей со свойствами. А ведь я разрабатываю экспериментальные концепты, которые могут меняться. ТЗ у меня нет, все может вмиг поменяться. И делать каждый раз новую панель свойств с десятком полей - было выше моих сил. Реально.

В общем, я бы тратил время не на саму графику, а на обвязку. Фтопку такой редактор.

Версия 3

Тогда я сделал третью версию редактора, который наконец-то меня устроил, и я решил на нем остановиться.

Идея нового редактора заключалась в том, что для редактирования опорных точек мы как и прежде используем визуальные маркеры. А вот для всего остального - используем специально разработанный скриптовый язык.

Да-да, вот так все начиналось с графики, а перешло к синтаксическим анализаторам и текстовым редакторам.

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

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

В этой модели ректора я отказался от отдельных классов для линий, окружностей, заливки, эффектов и так далее. А сделал все это методами одного класса. Хоть это не очень ООП-шно, но зато я теперь могу сосредоточится именно на графике а не на жонглировании классами. Класс Marker я оставил, поскольку он завязан на интерфейс, и пользователь по-прежнему его двигает по экрану.

Центральный класс библиотеки называется незамысловато: Engine. Он делает почти все - парсит скрипт, компилит его и отрисовывает.

Происходит это так: Engine содержит метод void Parse(string text). На вход подается текст скрипта. Engine парсит его, используя простой синтаксис <команда> <набор параметров>. Одна команда - в одной строке. Пример скрипта:

Пример скрипта

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
m1 = 35;23
m2 = 35;56
m3 = 67;56
m4 = 74;49
m5 = 74;23
m6 = 74;34
 
TEXT  = Speed
TEXT2 = P34
 
//frame
poly m1 m2 m3 m4 m5
FillOpacity = 100
draw
fill
 
//text1
FillOpacity = 150
FillColor   =  0;251;251
FontSize    = 9
text m1 TEXT
fill
 
//text2
FillOpacity = 250
FillColor   =  149;255;255
FontSize    = 15
TextAlign   = BottomLeft
text m6 TEXT2
fill


Далее, каждая строка скрипта превращается в делегат. Например вот метод, обрабатывающий команду scale:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        private Action Scale(string args)
        {
            var ar = new Args(this, args);
 
            if (ar.Markers.Count < 1)
                throw new Exception("Expected one marker");
 
            if (ar.Floats.Count < 1)
                throw new Exception("Expected one float value");
 
            return () =>
            {
                var scale = ar.Floats[0]();
                var c = ar[0];
                Graphics.TranslateTransform(c.X, c.Y);
                Graphics.ScaleTransform(scale, scale);
                Graphics.TranslateTransform(-c.X, -c.Y);
            };
        }
Обратите внимание - на вход метода передаются строковые параметры из скрипта, а на выходе метод возвращает делегат Action.

Распарсив каждую строку, Engine складывает сегенерированные делегаты в список List<Action>, который по сути представляет собой скрипт в откомпилированном виде. Поскольку мы генерируем делегаты внутри Engine, то замыкания автоматически захватывают переменные и поля класса Engine, что позволяет им манипулировать ими в дальнейшем.

Теперь мы можем вызвать скрипт на выполнение методом Engine.Draw. Он выглядит очень просто:

C#
1
2
3
4
5
6
7
8
9
10
        List<Action> compiled;
        Graphics Graphics;
 
        public void Draw(Graphics gr)
        {
            Graphics = gr;
 
            foreach (var act in compiled)
                act();
        }
Как видим, выполнение скрипта заключается просто в последовательном вызове сгенерированных делегатов. При этом, парсинг текста скрипта произошел только один раз - при вызове Pasre. А все остальные вызовы Draw и выполнение скрипта происходит уже без участия текста, то есть очень быстро. Таким образом мы получили что-то типа компилятора.

Класс Engine также содержит открытые словари такого типа:
C#
1
2
3
4
        public Dictionary<string, PointF> Markers { get; private set; }
        public Dictionary<string, Color> Colors { get; private set; }
        public Dictionary<string, float> Floats { get; private set; }
        public Dictionary<string, string> Strings { get; private set; }
Они содержат параметры. К параметрам, с одной стороны - может обращаться скрипт, делегаты которого захватили объект engine, с другой стороны к этим параметрам может обращаться вызывающая программа, которая создала Engine. Таким образом происходит передача данных от вызывающей программы к скрипту и наоборот.

Например метод отрисовки лейбы, в которой показывается скорость корабля выглядит примерно так:

C#
1
2
3
4
5
6
7
8
9
10
        //отрисовка лейбы скорости
        private static void DrawSpeed(Graphics gr)
        {
            var e = engines["label"];//получение Engine для лейбы, словарь engines содержит предварительно откомпилированные скрипты
            var p = e.Markers["m1"];//получаем координаты маркера m1
            gr.TranslateTransform(Rect.Left + 5 - p.X, Rect.Bottom - 205 - p.Y);//передвигаем graphics для отрисовки в нужном месте
            e.Strings["TEXT"] = "speed";//задаем параметр TEXT для скрипта
            e.Strings["TEXT2"] = Game.Player.Velocity.Length().ToString();//задаем параметр TEXT2 для скрипта
            e.Draw(gr);//отрисовываем скрипт на нашем Graphics
        }
Теперь немного о синтаксисе языка и поддерживаемых командах.

Команды делятся на операторы присвоения и на все остальные. Присвоения задают парметры класса Engine или переменные для общения с внешней программой. Параметры определяют такие вещи как цвет бордера, цвет заливки, толщина линий, размер шрифта и так далее. Одному и тому же параметру можно присваивать разные значения в процессе выполнения скрипта. Все команды отрисовки, следующие после задания параметра будут выполняться с данным значением (это кстати удобное отличие от предыдущей версии редактора, в котором нужно было задавать параметры для каждого объекта отдельно).

Для более удобной работы с параметрами есть две специальные команды: save и restore. Команда save запоминает в стеке все текущие настройки графики, а команда restore - возвращает их из стека. Таким образом, если отдельный фрагмент скрипта хочет поменять параметры, он предварительно может вызвать save, сделать изменения параметров рисования, отрисоваться и затем вернуть все параметры в прежнее значение, вызвав restore. Это очень удобно - не нужно заботиться о том что один кусок скрипта испортит отрисовку другого куска. Кроме параметров, команды save и restore также запоминают и восстанавливают матрицу трансформации для Graphics. Это значит что если мы сделали например вращение Graphics, то команда resore вернет состояние графикса в прежнее состояние и остальная часть будет отрисовываться уже без вращения.

Далее, для отрисовки используется два типа команд - одни просто соединяют маркеры линиями и фактически просто создают GraphicsPath. Это команды poly, circle, rays, text. Смысл которых - понятен из названия. Команда poly - задает набор маркеров, которые соединяются линией (полигон). Это может выглядеть так:
Code
1
poly m1 m2 m3 //треугольник
Эта команда задает GraphicsPath для отрисовки треугольника с вершинами в точках m1 m2 и m3. Кроме того, команда poly может содержать еще один числовой параметр. Если он указан и отличен от нуля, то рисуется не полигон а сгаженная кривая, а параметр определяет ее tension, то есть гладкость:
Code
1
poly m1 m2 m3 0.6//кривая между точками m1 m2 и m3
Аналогично работают остальные команды: circle - создает окружности или сегменты окружностей, rays - создает радиально расходящиеся лучи, text - генерирует текст.

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

Таким образом, простейший скрипт, рисующий линию, выглядит так:

Code
1
2
poly m1 m2 //соединяем линией маркеры m1 и m2
draw //отрисовываем
Результат
Нажмите на изображение для увеличения
Название: 0_29.png
Просмотров: 1022
Размер:	2.5 Кб
ID:	3719


Скрипт Hello world будет выглядеть так:

Code
1
2
text m1 Hello world!
fill
Этот скрипт выводит текст "Hello world!" возле точки m1. Для того , чтобы он заработал - не забудьте поставить маркер m1 в окне редактора.

Результат
Нажмите на изображение для увеличения
Название: 0_28.png
Просмотров: 806
Размер:	3.6 Кб
ID:	3720


Несмотря на то, что команд задания графики всего 4, на самом деле они могут иметь много параметров - как числовых так и маркеров, и в зависимости от них работать по разному. Например, команда text может принимать один маркер (тогда она рисует текст просто возле точки), два маркера (тогда текст рисует вдоль линии, соединяющей макеры), три маркера (тогда текст рисуется внутри прямоугольника, натянутого на три точки). Кроме того, на выполнение команды влияют текущие параметры, например параметр TextAlign - задающий выравнивание текста, FontSize - размер шрифта и так далее.

Далее, самая многочисленная группа команд - это команды трансформаций. Они могут смещать, вращать, масштабировать и двигать графику и отдельные маркеры. Среди этих команд такие: rotate, offset, scale, move, rotateMarker, scaleMarker, offsetMarker, moveMarker. Смысл понятен из названия. Я не буду подробно останавливаться на каждом из них. Их действие можно понять из примеров скриптов, приведенных внизу страницы.

Опишу только простое перемещение графики:

Code
1
offset 100 0
Все что будет рисоваться после этой команды, будет смещено на 100 пикселов по горизонтали, относительно начального положения маркеров. Вернуть исходное состояние можно вызвав обратное перемещение offset -100 0, либо вызвав команду restore.

Результат
Нажмите на изображение для увеличения
Название: 0_30.png
Просмотров: 711
Размер:	2.1 Кб
ID:	3721


Еще есть специальная команда timer, которая автоматически меняет значения переменных во времени. Она позволяет делать анимации с автоматическим движением заданных фрагментов изображения.

Теперь немного о эффектах, реализованых в движке.
На данный момент реализовано два эффекта - Halo и Repeat.

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

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

Кликните здесь для просмотра всего текста
Нажмите на изображение для увеличения
Название: 0_31.png
Просмотров: 716
Размер:	5.8 Кб
ID:	3724


Аналогично реализован эффект Repeat. Он тоже отрисовывает контур несколько раз, но смещая его каждый раз на некое расстояние.

Теперь о редакторе в целом. Выглядит он вот так:

Нажмите на изображение для увеличения
Название: 0_26.png
Просмотров: 1126
Размер:	200.4 Кб
ID:	3722

Сделана подсветка синтаксиса, IntelliSense, динамическая компиляция при изменении текста. Ошибки выводятся в нижнем статус баре. Для текстового редактора использовался FCTB.

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

Также, редактор поддерживает просмотр результата, так, как он будет выглядеть в целевом приложении (F5).

Когда редактор сохраняет файл скрипта, он автоматически вставляет в него координаты маркеров, выставленных на панели маркеров.

Ниже приведен полный код редактора, а также набор скриптов-примеров, и тестовое приложение, подключающее скрипты и отображающее в своем окне.

Внутри нашей игры это выглядит примерно таким образом:

Нажмите на изображение для увеличения
Название: 0_24.png
Просмотров: 1294
Размер:	209.6 Кб
ID:	3723

Интерфейс HUD пока не дорисован. Кстати, кто умеет рисовать и у кого есть идеи оформления в стиле futuristic - могут помочь в написании скриптов HUD. Присылайте скрипты.

Теперь что касается интеграции HUD в игру.

Поскольку моя цель не просто сделать приложение, а сделать высокоэффективное и быстрое приложение, то все уперлось в быстродействие.

Сначала я внедрил отрисовку HUD прямо в цикл отрисовки основной графики игры. Но поскольку этот цикл работает с высоким быстродействием - 65 fps, то HUD начал его подтормаживать и я получил снижение до 40 fps, а может и ниже, если нагрузить HUD элементами. Это меня не очень устроило, потому что в шутере FPS отрисовки очень важен, и я не хочу его снижать.

Тут нужно работать в двух направлениях - либо оптимизировать движок HUD, либо вынести отрисовку HUD из главного цикла отрисовки. Оптимизацией движка HUD я займусь чуть позже, а вот саму отрисовку я все таки вынес в отдельный поток.

Работает это так. В цикле отрисовки графики не вызывается отрисовка из Engine, а отрисовывается буферный битмап. В то время, как этот битмап рисуется, Engine, отрисовывает себя в другом битмапе, в отдельном потоке. Потом буферы меняются местами, и все повторяется. Классическая двойная буферизация.

Я не использовал встроенный BufferedGraphics потому что он кривоватый во фреймворке и использует неправильный формат пикселей. В общем - не годится.

Поэтому был написан собственный класс для двойной буферизации:

DoubleGraphBuffer
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    /// <summary>
    /// Double bufferred graphics
    /// </summary>
    public class DoubleGraphBuffer
    {
        private Bitmap[] buffers = new Bitmap[2];
        private int iCurrent;
        public readonly object SyncObject = new object();
 
        /// <summary>
        /// Draw buffer
        /// </summary>
        public void Draw(Graphics gr, Point p)
        {
            lock(SyncObject)
                gr.DrawImage(buffers[iCurrent], p);
        }
 
        /// <summary>
        /// Graphics to draw
        /// </summary>
        public Graphics Graphics
        {
            get
            {
                 return Graphics.FromImage(buffers[iCurrent ^ 1]);
            }
        }
 
        /// <summary>
        /// Swap buffers. Call this when you finished drawing in Graphics.
        /// </summary>
        public void SwapBuffers()
        {
            lock (SyncObject)
                iCurrent ^= 1;
        }
 
        public Size Size { get; private set; }
 
        public DoubleGraphBuffer()
        {
            buffers[0] = new Bitmap(1, 1);
            buffers[1] = new Bitmap(1, 1);
            Size = new Size(1, 1);
        }
 
        /// <summary>
        /// Call to define size of buffers
        /// </summary>
        /// <param name="size"></param>
        public void Build(Size size)
        {
            lock(SyncObject)
            if(buffers[0].Size != size)
            {
                buffers[0].Dispose();
                buffers[1].Dispose();
 
                buffers[0] = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppPArgb);
                buffers[1] = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppPArgb);
 
                Size = size;
            }
        }
    }


Это позволило оставить FPS главной отрисовки на приемлемом уровне (60 fps) и вынести отрисовку HUD в отдельный поток и с более низким FPS (HUD обновляется не чаще 20 fps). Это хорошо, потому что HUD совсем не обязательно отрисовывать по 60 раз в секунду. Он меняется относительно медленно в процессе игры, и потому его можно обновлять не чаще 20 fps.

Исходник и бинарник редактора, примеры скриптов здесь.

Текущая версия игры здесь (бинарник) и здесь (исходник).

Метки c#, crazy dev, games, hud, winforms
Размещено в C#, WinForms
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 7
Комментарии
  1. Старый комментарий
    Стал разбираться в исходниках, но наткнулся на несколько проблем:
    Во-первых, я не нашел в архиве, собственно, самого главного-исходника библиотеки FControls.dll. Может, вы его забыли положить? Или я жестко туплю.
    Во-вторых, существует проблема с точками(видимо, из-за локалей), из-за чего у меня запустился только один(первый) из тестовый скриптов, который не содержит точек. Для остальных строка с точками "имела неверный формат", а место, где это происходит, я не вижу, ибо оно внутри библиотеки
    Запись от EvilFromHell размещена 02.04.2016 в 03:14 EvilFromHell вне форума
  2. Старый комментарий
    Да, и игра у меня, видимо по той же причине, выглядит не так, как должна согласно статье: из всего, что видно на скрине, у меня отображается только выбор оружия.
    Запись от EvilFromHell размещена 02.04.2016 в 03:26 EvilFromHell вне форума
  3. Старый комментарий
    Аватар для Storm23
    Ох, прошу прощения, еще думал про локаль, пока писал и потом забыл
    Да, это из-за того, что в скрипте числа с разделителем точка.
    Сейчас исправил, скачайте заново.
    Вложил исходники FControls, а также обновил ссылки на игру, сейчас HUD должен нормально, полностью открываться.
    Запись от Storm23 размещена 02.04.2016 в 12:22 Storm23 вне форума
  4. Старый комментарий
    Аватар для Phoelix
    Прикольная вещь! Жаль продолжения нету(((
    Запись от Phoelix размещена 31.05.2016 в 12:48 Phoelix вне форума
  5. Старый комментарий
    Аватар для ashsvis
    Нельзя ли объяснить разницу между командами: offset и move?
    Запись от ashsvis размещена 20.02.2019 в 17:51 ashsvis вне форума
  6. Старый комментарий
    Аватар для Storm23
    Нельзя ли объяснить разницу между командами: offset и move?
    offset - просто сдвиг координат
    move - это команда относится к анимации. Она сдвигает графику вдоль path

    Например вот такой скрипт двигает окружность вдоль сторон треугольника:
    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    m1 = 280;232
    m2 = 347;103
    m3 = 399;237
    m4 = 235;437
     
    //задаем таймер
    TIME = 0
    timer TIME 0.2 0 1
     
    //задаем путь для анимации
    poly m1 m2 m3 
    //двигаем последующую графику вдоль пути (по таймеру TIME)
    move TIME
     
    //отрисовываем окружность
    circle m4 20
    fill
    Запись от Storm23 размещена 20.02.2019 в 23:32 Storm23 вне форума
  7. Старый комментарий
    Аватар для ashsvis
    Спасибо. Мне очень нравится Ваша идея компиляции скриптов!
    Запись от ashsvis размещена 21.02.2019 в 18:45 ashsvis вне форума
 
Новые блоги и статьи
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Видео: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
Калькулятор для расчета родства
russiannick 07.08.2026
1. Задача: Создать калькулятор для расчета родства. Родственных связей существует 8 ступеней, такие как: p - отец P - мать q - муж Q - жена b - брат B - сестра s - сын S - дочь
Мир по моей воле
kumehtar 07.08.2026
Когда-то кажется, что всё просто. Ты весь такой светлый. Причиняешь добро. Борешься за справедливость в этом тёмном мире. Потом начинаешь замечать одну неприятную вещь. Почти каждый хороший. . .
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru