VozDuh
59 subscribers
8 photos
26 videos
11 links
Это канал по разработке игр, вдохновлённый другим ТГ каналом, в этом канале я буду показывать как разрабатываю игру с открытым кодом
Download Telegram
В игре всё ещё нету одного из самых важных для такого типа игр, структур!
Структуры это очень просто, но, мне хочется сохранять структуры в файле, это значит, что мне нужно сделать:
1. Синтаксис файла структур.
2. Парсер для этого файла.

Для синтаксиса я выбрал простенький вариант:
Переменные: "name: tile".
Сама структура: "[[tile, tile], [tile, tile]]".

А с парсером всё намного сложнее, было бы если бы я не использовал недавно сделанную мною библиотеку Va, эта библиотека позволяет создавать как простые парсеры так и полноценные языки программирования, но второе нас сейчас не нужно.
Название библиотеки произошло от обрезания другого названия Valang (Value Language), интерпретируемый язык программирования, но по факту обёртка для C#.

Ладно, начну уже о структурах:
public class Structure
{
private static ITile[][] tiles;
private static readonly CompileStyle main = null;

static Structure()
{
Solution mainSolution = new Solution();

main = new CompileStyle(
new(
new TokenStyle[]
{
new TokenStyle(TokenType.Keyword),
new TokenStyle(":", TokenType.Special),
new TokenStyle(TokenType.Keyword),
},
(CompileStyleDelegate)
((sln, toks) =>
{
if (!sln.TryAdd(toks[0].Text, new DataStruct(VValueType.String, toks[2].data)))
throw new VaException(toks[1].line, $"Already have same variable.");
})
),
new(
new TokenStyle[]
{
new TokenStyle(TokenType.BoxBracket)
},
(CompileStyleDelegate)
((sln, toks) =>
{
Token[][] rows = toks[0].tokens[0].Split(new TokenStyle(",", TokenType.Special));
tiles = new ITile[rows.Length][];
for (int i = 0; i < rows.Length; i++)
{
Token[][] cols = rows[i][0].tokens[0].Split(new TokenStyle(",", TokenType.Special));
tiles[i] = new ITile[cols.Length];
for (int j = 0; j < cols.Length; j++)
tiles[i][j] = Tiles.Get<ITile>(sln.TryGet(cols[j][0].Text, out DataStruct data) ? data.Str : cols[j][0].Text)();
}
})
)
);
}

Тут я создаю парсер, парсер это просто "стиль компиляции", он создаётся из массива структур которые содержат в себе стиль линии и функцию.
👍1
Продолжу, тут уже идёт не статичная часть структуры:
    private readonly ITile[][] data;

public Structure(string code)
{
Token[][] tokens = Compiler.GetTokens(code);
Compiler.ParseStyle(new Solution(), main, tokens);
data = tiles;
}

public void Spawn(Map map, int x, int y)
{
for (int i = 0; i < data.Length; i++)
for (int j = 0; j < data[i].Length; j++)
map.SetTile(data[i][j], x+j, y+i);
}
}

В конструкторе мы по факту парсим стиль компиляции который создали заранее, дальше идёт единственная функция Spawn, она спавнит структуру на карте.

Теперь можно создавать структуры таким образом:
test = new Structure(@"
i:ignore;
a:air;
s:stone;

[
[i,s,s,s,i],
[s,a,a,a,s],
[s,a,a,a,s],
[s,a,a,a,s],
[i,s,s,s,i]
];
");

Их так-же можно загружать из файлов, но сейчас не об этом, в InGameState.Update я добавляю строки:
if (Keyboard.IsPressed(Keys.T))
{
var (x, y) = map.World2Cell(Mouse.Position);
Structures.test.Spawn(map, x, y);
}

И теперь можно создавать тестовую структуру по нажатию кнопки [T].

Так-же была добавлена новая плитка, плитка камня, она в два раза крепче чем тестовая плитка которая ранее была единственной.

ВВЕРХ
👍1
Рубрика WIP, прикольные баги и другие аспекты которых вы никогда не увидите в отполированной версии игры!
Прошу написать о том, нравится ли вам такое, впервые так делаю.
👍5
Всего день прошёл а я тут как тут, добавил стены, генерацию, и светотень!
На самом деле свет это очень важный аспект игры, если его не добавить то получится, что мы видим всё и всегда.

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

Теперь можно перейти к структурами, они тоже имеют стены, значит мне нужно поменять их парсер, я решил использовать такой вариант: "name: wall tile".
Вот как-бы и всё о стенах.

Продолжаем, поговорим о бог-, кхм, генерации, давайте подумаем, как же я реализую шум перлина?
Напишу самостоятельно? Нееет...
Украду C++ код с википедии, перепишу его на C# и добавлю возможность сидов? Даа!
Как вы уже поняли я украл код шум перлина с википедии:
public static class Noise
{
private static float Interpolate(float a0, float a1, float w)
{
return (a1 - a0) * w + a0;
}

private static Vec2 RandomGradient(uint seed, int ix, int iy)
{
uint w = 8 * sizeof(uint);
uint s = w / 2;
uint a = (uint)ix, b = (uint)iy;
a *= 3284157443; a += seed; b ^= a << (int)s | a >> (int)w - (int)s;
b *= 1911520717; b += seed; a ^= b << (int)s | b >> (int)w - (int)s;
a *= 2048419325;
float random = a * (3.14159265f / ~(~0u >> 1));
Vec2 v;
v.X = MathF.Cos(random); v.Y = MathF.Sin(random);
return v;
}

private static float DotGridGradient(uint seed, int ix, int iy, float x, float y)
{
Vec2 gradient = RandomGradient(seed, ix, iy);

float dx = x - ix;
float dy = y - iy;

return (dx * gradient.X + dy * gradient.Y);
}

public static float Perlin(uint seed, float x, float y)
{
int x0 = (int)MathF.Floor(x);
int x1 = x0 + 1;
int y0 = (int)MathF.Floor(y);
int y1 = y0 + 1;

float sx = x - x0;
float sy = y - y0;

float n0, n1, ix0, ix1, value;

n0 = DotGridGradient(seed, x0, y0, x, y);
n1 = DotGridGradient(seed, x1, y0, x, y);
ix0 = Interpolate(n0, n1, sx);

n0 = DotGridGradient(seed, x0, y1, x, y);
n1 = DotGridGradient(seed, x1, y1, x, y);
ix1 = Interpolate(n0, n1, sx);

value = Interpolate(ix0, ix1, sy);
return 0.5f + value / 2;
}
}
👍2
А теперь о тенях, для неё я создал ту-же реализацию Create/Remove как и в физике но вместо возврата коллайдера идёт возврат индекса который потом используется для следующих методов:
    public void SetPosition(uint lit, float x, float y)
{
lights[lit].x = x;
lights[lit].y = y;
}

public void SetPosition(uint lit, Vec2 position) => SetPosition(lit, position.X, position.Y);

public void SetIntensity(uint lit, float intensity) => lights[lit].intensity = intensity;
public void SetRadius(uint lit, float radius) => lights[lit].radius = radius;

При изменении размера экрана игры нужно создавать "матрицу теней" для нового размера, для этого идёт функция:
    public void Resize()
{
(width, height) = map.World2Cell(camera.WorldViewport);
width += 3; height += 2;
tiles = new Vector3[width, height];
}

И теперь о конструкторе с некоторыми данными:
    private readonly Light[] lights = new Light[255];

private readonly Camera camera;
private readonly IMap map;
private Vector3[,] tiles;
private int x, y, width, height;

private Batch batch = new Batch(SDraw.spriteBatch.GraphicsDevice);

public ShadowMatrix(IMap map, Camera camera)
{
this.map = map;
this.camera = camera;

Resize();
}

Тут мы инициализируем всё что нужно.
Дальше идёт функция рисования:
public void Draw()
{
(this.x, this.y) = map.World2Cell(camera.Position - camera.WorldViewport / 2);
this.x--; this.y--;

int x, y, _x, _y;

for (x = 0; x < width; x++)
for (y = 0; y < height; y++)
tiles[x, y] = Vector3.Zero;

foreach (Light p in lights)
{
if (!p.available) continue;
p.Generate();
for (_x = p.rectangle.X; _x <= p.rectangle.X + p.rectangle.Width; _x++)
for (_y = p.rectangle.Y; _y <= p.rectangle.Y + p.rectangle.Height; _y++)
{
x = _x - this.x;
y = _y - this.y;
if (x < 0 || x >= width || y < 0 || y >= height) continue;

tiles[x, y] += p.color
* (1 - Ray(_x, _y, map.World2Cell(p.x, p.y), p.intensity) / p.intensity) // shadow
* MathF.Max(0, 1 - new Vec2(p.x / Map.tileSize - _x, p.y / Map.tileSize - _y).Length() / p.radius) * p.intensity; // saturation
}
}
batch.Begin(PrimitiveType.TriangleList, width * height * 12, camera.GetViewMatrix());
batch.BlendState = multiplyBlend;
for (x = 0; x < width; x++)
for (y = 0; y < height; y++)
{
_x = this.x + x;
_y = this.y + y;
Vec2
c = map.Cell2World(_x, _y),
l = map.Cell2World(_x - 1, _y),
r = map.Cell2World(_x + 1, _y),
b = map.Cell2World(_x, _y - 1),
t = map.Cell2World(_x, _y + 1);
Color
rc = new Color(tiles[x == width - 1 ? width - 1 : x + 1, y]),
lc = new Color(tiles[x == 0 ? 0 : x - 1, y]),
bc = new Color(tiles[x, y == 0 ? 0 : y - 1]),
tc = new Color(tiles[x, y == height - 1 ? height - 1 : y + 1]),
cc = new Color(tiles[x, y]);

batch.Color = lc;
batch.Vertex(l);
batch.Color = tc;
batch.Vertex(t);
batch.Color = cc;
batch.Vertex(c);

batch.Color = lc;
batch.Vertex(l);
batch.Color = bc;
batch.Vertex(b);
batch.Color = cc;
batch.Vertex(c);

batch.Color = rc;
batch.Vertex(r);
batch.Color = tc;
batch.Vertex(t);
batch.Color = cc;
batch.Vertex(c);

batch.Color = rc;
batch.Vertex(r);
batch.Color = bc;
batch.Vertex(b);
batch.Color = cc;
batch.Vertex(c);
}
batch.End();
}

Тут мы проходим по всей матрице, делаем её полностью чёрной, рассчитываем свет а потом рисуем треугольники, я использую не 2 треугольника на 1 квадратик а 4 треугольника на ромб, это нужно чтобы освещение было мягким.
👍2
Тут так-же видна функция Ray которая выглядит так:
    public float Ray(int x0, int y0, (int, int) p, float power)
{
var (x1, y1) = p;
float f = 0;

int dx = x1 - x0;
int dy = y1 - y0;

float sd = new Vec2(Math.Abs(dx), Math.Abs(dy)).Length();

float x_incr = dx / sd;
float y_incr = dy / sd;
float x = x0;
float y = y0;
IShadowTile tile;

for (int i = 0; i < sd - 1; i++)
{
y += y_incr;
x += x_incr;
tile = map.GetTile(true, (int)Math.Clamp(MathF.Round(x), 0, map.FullWidth - 1), (int)Math.Clamp(MathF.Round(y), 0, map.FullHeight - 1)).Tile;
if (tile?.ShadowAvailable ?? false)
{
f += tile.ShadowIntensity;
if (f >= power)
{
return power;
}
}
}
return f;
}

В функции я использую алгоритм Брезенхэма чтобы пройтись по линии от одной позиции до другой.

Ещё в коде видно структуру Light, она выглядит так:
public struct Light
{
public bool available;
public float x, y;
public Vector3 color;
public float intensity, radius;
public Rectangle rectangle;
public void Generate()
{
int s = (int)MathF.Ceiling(radius);
int fs = (int)MathF.Ceiling(radius * 2);
rectangle = new Rectangle((int)(x / Map.tileSize) - s - 1, (int)(y / Map.tileSize) - s - 1, fs + 2, fs + 2);
}
}


Так - же плитки теперь реализуют интерфейс для светотени:
public interface IShadowTile
{
bool ShadowAvailable { get; }
float ShadowIntensity { get; }
}


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

ВВЕРХ
👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Видео прилагается.
👍3
Возможность отключения сглаживания, можно будет менять режимы освещения в настройках.
👍5
Опрос кончится через 2 дня.
Победили:
Особый биом.
Лес.
Болото.
Равнина всё равно будет, её можно сделать как и лес но, без деревьев, их кстати нужно сделать.
👍4
Кажется, что это была не аскорбинка...
😁7👀3
Извиняюсь, что так долго нет постов, сейчас немного занят, держите спрайт будущего инвентаря в WIP.
👍7
This media is not supported in your browser
VIEW IN TELEGRAM
Игра слишком разрослась чтобы вести прежний девлог, скорее всего я прейду к простому перечислению изменений:

- Перерисована анимация ног персонажа.
- Переписана система инвентаря.
- Новый GUI игрока.
- Деревья.
- Мульти-плитки.
- Плитки-референсы.
- Загрузка контента через атрибуты.
- GUI элемент Image.
- GUI элемент SwitchButtons.
- Переписана система добавлений/удалений GUI.
- Ловля багов в релизном билде и вывод ошибок на экран.
- Генерация мира.
- Биомы.
- Менеджер существ.
- Система сохранения.
- Система настроек
- Атрибут в/из байт.
- Солнечный свет.
- Смена дня и ночи, пока просто свет меняется.
👍9
This media is not supported in your browser
VIEW IN TELEGRAM
Оптимизация в играх это хорошо, по этой простой причине я решил, что пора чуток ею заняться, вот какие изменения последовали за этим:

- Физика вынесена в отдельный поток.
- Расчёт освещения вынесен в отдельный поток.
- Баг с тем, что некоторые потоки остаются в устройстве. 😐

Ну и немного по картинке: сделал чтобы плитки пропускали немного света, мне кажется, что так красивее.
🔥7👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Слайдеры, они являются очень важным аспектом игры, ведь слайдер будет в меню крафта, настройках и многом другом, да кстати, следующим я покажу именно меню крафта, а пока наслаждайтесь этим разнообразием слайдеров и ждите.
🔥5
This media is not supported in your browser
VIEW IN TELEGRAM
Меню крафта откроет мне ленивый способ дать игрокам много новых возможностей, на видео я продемонстрировал нововведения:

- Само меню крафта.
- Сохранение предметов в мире.
- Выбрасывание предметов из инвентаря, оно было частично реализовано и до этого, но предметы сразу же всасывались обратно.
🔥4💩1
This media is not supported in your browser
VIEW IN TELEGRAM
Долгое время спустя, я неожиданно появляюсь, делаю пост и исчезаю на 1-4 недели, но зато какие посты:

- Моддинг.
- Скриптинг на Lua и JavaScript.
- Модовые плитки.
- Модовые авто-плитки.
- Генерация пещер.
- Написал новые шумы на C++.
- Написал основу для мультиплеера, сейчас она существует в виде библиотеки на гитхабе https://github.com/Voz-Duh/Tendeos.Network, но поддерживает всё, что мне нужно.

Прошу посмотреть в комментарии всех, кому интересно как выглядит код мода-примера.
👍101👎1🔥1😱1