Так же я забыл показать вспомогательные типы для лучей:
Дальше идёт реализация, не думаю, что всем интересно будет читать что-то типо такого: вот тут я вот это поменял на это, а тут вот это на это.
А ещё я поменял название FVector2 на Vec2.
ВВЕРХ
/// <summary>
/// </summary>
/// <returns>True to break.</returns>
public delegate bool RaycastDelegate(Collider collider, Vec2 point, Vec2 normal, float distance);
public delegate void RaycastMapDelegate(Vec2 point, Vec2 normal, float distance);
public record struct RaycastHitInfo(Collider collider, Vec2 point, Vec2 normal, float distance) : IComparable<RaycastHitInfo>
{
public int CompareTo(RaycastHitInfo o)
{
if (o.distance < distance) return 1;
return 0;
}
}
Дальше идёт реализация, не думаю, что всем интересно будет читать что-то типо такого: вот тут я вот это поменял на это, а тут вот это на это.
А ещё я поменял название FVector2 на Vec2.
ВВЕРХ
👍4
В игре всё ещё нету одного из самых важных для такого типа игр, структур!
Структуры это очень просто, но, мне хочется сохранять структуры в файле, это значит, что мне нужно сделать:
1. Синтаксис файла структур.
2. Парсер для этого файла.
Для синтаксиса я выбрал простенький вариант:
Переменные: "name: tile".
Сама структура: "[[tile, tile], [tile, tile]]".
А с парсером всё намного сложнее, было бы если бы я не использовал недавно сделанную мною библиотеку Va, эта библиотека позволяет создавать как простые парсеры так и полноценные языки программирования, но второе нас сейчас не нужно.
Название библиотеки произошло от обрезания другого названия Valang (Value Language), интерпретируемый язык программирования, но по факту обёртка для C#.
Ладно, начну уже о структурах:
Тут я создаю парсер, парсер это просто "стиль компиляции", он создаётся из массива структур которые содержат в себе стиль линии и функцию.
Структуры это очень просто, но, мне хочется сохранять структуры в файле, это значит, что мне нужно сделать:
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
Продолжу, тут уже идёт не статичная часть структуры:
В конструкторе мы по факту парсим стиль компиляции который создали заранее, дальше идёт единственная функция Spawn, она спавнит структуру на карте.
Теперь можно создавать структуры таким образом:
Их так-же можно загружать из файлов, но сейчас не об этом, в InGameState.Update я добавляю строки:
И теперь можно создавать тестовую структуру по нажатию кнопки [T].
Так-же была добавлена новая плитка, плитка камня, она в два раза крепче чем тестовая плитка которая ранее была единственной.
ВВЕРХ
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
Всего день прошёл а я тут как тут, добавил стены, генерацию, и светотень!
На самом деле свет это очень важный аспект игры, если его не добавить то получится, что мы видим всё и всегда.
Но, для начала нужно разобрать, что там с стенами, стены это просто новый слой плиток, для уточнения того, что мы оперируем именно плиткой-стеной я решил использовать bool top.
После чего я добавил это в реализацию карты, сделал возможность правой клавиши у ближне-бойного оружия и добавил проверку на нажатие правой клавиши в кирку.
Теперь можно перейти к структурами, они тоже имеют стены, значит мне нужно поменять их парсер, я решил использовать такой вариант: "name: wall tile".
Вот как-бы и всё о стенах.
Продолжаем, поговорим о бог-, кхм, генерации, давайте подумаем, как же я реализую шум перлина?
Напишу самостоятельно? Нееет...
Украду C++ код с википедии, перепишу его на C# и добавлю возможность сидов? Даа!
Как вы уже поняли я украл код шум перлина с википедии:
На самом деле свет это очень важный аспект игры, если его не добавить то получится, что мы видим всё и всегда.
Но, для начала нужно разобрать, что там с стенами, стены это просто новый слой плиток, для уточнения того, что мы оперируем именно плиткой-стеной я решил использовать 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();
}
Тут мы инициализируем всё что нужно.
Дальше идёт функция рисования:
Тут мы проходим по всей матрице, делаем её полностью чёрной, рассчитываем свет а потом рисуем треугольники, я использую не 2 треугольника на 1 квадратик а 4 треугольника на ромб, это нужно чтобы освещение было мягким.
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 которая выглядит так:
В функции я использую алгоритм Брезенхэма чтобы пройтись по линии от одной позиции до другой.
Ещё в коде видно структуру Light, она выглядит так:
Так - же плитки теперь реализуют интерфейс для светотени:
Теперь в игре можно создавать и прозрачные плитки и не очень.
ВВЕРХ
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; }
}
Теперь в игре можно создавать и прозрачные плитки и не очень.
ВВЕРХ
Telegram
VozDuh
Всего день прошёл а я тут как тут, добавил стены, генерацию, и светотень!
На самом деле свет это очень важный аспект игры, если его не добавить то получится, что мы видим всё и всегда.
Но, для начала нужно разобрать, что там с стенами, стены это просто новый…
На самом деле свет это очень важный аспект игры, если его не добавить то получится, что мы видим всё и всегда.
Но, для начала нужно разобрать, что там с стенами, стены это просто новый…
👍1
Какие биомы добавить в игру первыми?
Выберу 3 самых популярных ответа.
Выберу 3 самых популярных ответа.
Anonymous Poll
47%
Особый биом. (как искажения или кримзон в Terraria или мясная планета в Starbound)
30%
Пустыня.
37%
Снежный биом.
20%
Каменная равнина.
40%
Лес.
30%
Океан.
13%
Равнина.
17%
Джунгли.
33%
Болото.
13%
Саванна.
Победили:
Особый биом.
Лес.
Болото.
Равнина всё равно будет, её можно сделать как и лес но, без деревьев, их кстати нужно сделать.
Особый биом.
Лес.
Болото.
Равнина всё равно будет, её можно сделать как и лес но, без деревьев, их кстати нужно сделать.
👍4
This media is not supported in your browser
VIEW IN TELEGRAM
Игра слишком разрослась чтобы вести прежний девлог, скорее всего я прейду к простому перечислению изменений:
- Перерисована анимация ног персонажа.
- Переписана система инвентаря.
- Новый GUI игрока.
- Деревья.
- Мульти-плитки.
- Плитки-референсы.
- Загрузка контента через атрибуты.
- GUI элемент Image.
- GUI элемент SwitchButtons.
- Переписана система добавлений/удалений GUI.
- Ловля багов в релизном билде и вывод ошибок на экран.
- Генерация мира.
- Биомы.
- Менеджер существ.
- Система сохранения.
- Система настроек
- Атрибут в/из байт.
- Солнечный свет.
- Смена дня и ночи, пока просто свет меняется.
- Перерисована анимация ног персонажа.
- Переписана система инвентаря.
- Новый 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