VozDuh
59 subscribers
8 photos
26 videos
11 links
Это канал по разработке игр, вдохновлённый другим ТГ каналом, в этом канале я буду показывать как разрабатываю игру с открытым кодом
Download Telegram
Channel created
Channel photo updated
Здравствуйте, я Сергей и в этом канале мы будем постепенно разрабатывать игру с открытым кодом на C# с библиотекой Monogame.
Цель проекта создать игру похожую на Terraria и Starbound.
Пока есть только сырой TODOlist, но вскоре я начну полноценную разработку игры.
VozDuh pinned «Ссылки: GitHub, Itch.io. Путь проекта: Новость №0 - Знакомство Новость №1 - Мир | Видео Новость №2 - Физика | Видео Новость №3 - Авто-тайлы | Видео Новость №4 - Персонаж | Видео Новость №5 - GUI | Видео Новость №6 - Инвентарь | Видео Новость №7 Кирка | Видео…»
Вот и первый пост о игре, стартовая точка.
Создал карту, плитки и чанки.

Для начала я создал 2 интерфейса:

public interface ITile
{
string Name { get; }
string Description { get; }
byte Hardness { get; }
void Changed(IMap map, int x, int y, TileData data);
void Update(IMap map, int x, int y, TileData data);
void Start(IMap map, int x, int y, TileData data);
void Draw(IMap map, int x, int y, Vector2 drawPosition, TileData data);
byte[] GetData();
}

И интерфейс IMap:
    public interface IMap
{
bool TryPlaceTile(ITile tile, int x, int y);
bool TryPlaceTile(ITile tile, (int x, int y) position);
bool PlaceTile(ITile tile, int x, int y);
bool PlaceTile(ITile tile, (int x, int y) position);
bool TrySetTile(ITile tile, int x, int y);
bool TrySetTile(ITile tile, (int x, int y) position);
void SetTile(ITile tile, int x, int y);
void SetTile(ITile tile, (int x, int y) position);
TileData GetTile(int x, int y);
TileData GetTile((int x, int y) position);
(int x, int y) World2Cell(float x, float y);
(int x, int y) World2Cell(Vector2 position);
Vector2 Cell2World(int x, int y);
Vector2 Cell2World((int x, int y) position);
}

В этом интерфейсе есть:
TryPlaceTile - ставит плитку если вокруг есть хотя бы одна плитка и место куда ставиться пусто.
PlaceTile - ставит плитку если вокруг есть хотя бы одна плитка.
TrySetTile - ставит плитку если место куда ставиться пусто.
SetTile - ставит плитку.
GetTile - получает данные о плитке.
World2Cell - конвертирует из мировых координат в координаты клетки.
Cell2World - конвертирует из координат клетки в мировые координаты.

После чего была создана структура:
    public struct TileData
{
public byte Health { get; init; }
public byte[] StateData { get; init; }
public ITile Tile { get; init; }

public TileData()
{
Health = 0;
StateData = null;
Tile = null;
}

public TileData(byte health, ITile tile) : this()
{
Health = health;
StateData = tile.GetData();
Tile = tile;
}
}

В ней находятся данные для обработки ITile'ом.

Немного настроил проект добавив более приятный инпут с мыши и клавиатуры.
Так-же добавил камеру которую сделал на самом деле уже давно а передвижение сделано при помощи строчек:
if (Mouse.RightPressed)
Start = camera.Position + Mouse.GUIPosition;
if (Mouse.RightDown)
camera.Position = Start - Mouse.GUIPosition;


Теперь в игре можно рисовать плитками которые я оптимизировал как смог!
🔥2
Как физический движок я решил использовать Aether.Physics2D, порыскав в документации я нашёл как создать менеджер и добавил его в код с помощью строк:
private World world;

protected override void Initialize()
{
...
world = new World(new FVector2(0, 90));
world.Add(aabb);
...
}
protected override void Update(GameTime gameTime)
{
...
world.Step(Time.Delta);
...
}

Потом добавил просто коллайдер который будет падать:
aabb = new Body();
aabb.BodyType = BodyType.Dynamic;
var r = aabb.CreateRectangle(13, 20, 0, FVector2.Zero);
r.Restitution = 0.2f;


В чанках добавил поле:
private Fixture[] fixtures;

И написал функцию которая использует принцип квадро-дерева для создания коллизии.
public void UpdateColision(Body body, int x, int y)
{
Queue<Rectangle> rectangles = new Queue<Rectangle>();
rectangles.Enqueue(new Rectangle(0, 0, chunkSize, chunkSize));
Queue<Rectangle> result = new Queue<Rectangle>();

int j, i;
while (rectangles.Count != 0)
{
Rectangle rectangle = rectangles.Dequeue();
for (i = rectangle.X; i < rectangle.X + rectangle.Width; i++)
{
for (j = rectangle.Y; j < rectangle.Y + rectangle.Height; j++)
{
if (tiles[i, j].Tile == null)
{
if (rectangle.Width > 1)
{
int w = rectangle.Width / 2,
h = rectangle.Height / 2;
rectangles.Enqueue(new Rectangle(rectangle.X, rectangle.Y, w, h));
rectangles.Enqueue(new Rectangle(rectangle.X + w, rectangle.Y, w, h));
rectangles.Enqueue(new Rectangle(rectangle.X, rectangle.Y + h, w, h));
rectangles.Enqueue(new Rectangle(rectangle.X + w, rectangle.Y + h, w, h));
}
goto SKIP;
}
}
}
result.Enqueue(rectangle);
SKIP:;
}

fixtures = new Fixture[result.Count];
while (result.Count != 0)
{
Rectangle rectangle = result.Dequeue();
fixtures[result.Count] = body.CreateRectangle(
rectangle.Width * tileSize, rectangle.Height * tileSize,
1,
new FVector2(
x * chunkSize + rectangle.X + rectangle.Width / 2f,
y * chunkSize + rectangle.Y + rectangle.Height / 2f
) * tileSize);
}
}


Добавил обновление чанков в SetTile:
c.UpdateColision(body, chunk.x, chunk.y);


Ну и для удобства сделал структуру FVector2 которая на автомате конвертируется в Vector2 из физ. движка в Vector2 из XNA и наоборот.

Теперь у нас есть коллизия и бедный человечек который на видео падает головой вниз!
🔥1
Демонстрация квадро-древесной коллизии.
Места с не объединяющимися плитками - стыки чанков.
🔥3
Благодаря предыдущим подготовкам я создал автотайлы очень быстро:
public class AutoTile : ITile
{
private readonly Sprite[] sprites;

public AutoTile(byte health, Sprite sprite)
{
sprites = sprite.Split(4, 4, 1);
this.health = health;
}

public string Name => throw new System.NotImplementedException();

public string Description => throw new System.NotImplementedException();

public byte Health => health;
public byte health;

public byte Hardness => throw new System.NotImplementedException();

public void Changed(IMap map, int x, int y, TileData data)
{
data[0] = UpdateTile(map, x, y);
}

public void Draw(IMap map, int x, int y, FVector2 drawPosition, TileData data)
{
SDraw.Rect(sprites[data[0]], drawPosition, 0, 1, 0, SpriteEffects.None, SDraw.Origin.Zero, SDraw.Origin.Zero);
}

public byte[] GetData() => new byte[] { 0 };

public void Start(IMap map, int x, int y, TileData data)
{
data[0] = UpdateTile(map, x, y);
}

// ВНИМАНИЕ!!! Ужасный иф-о код который спасёт вам пару кадров, но всё равно простите за такой ужас
public byte UpdateTile(IMap map, int x, int y)
{
byte res = 5;
bool left = map.GetTile(x-1, y).Tile == null, right = map.GetTile(x+1, y).Tile == null,
down = map.GetTile(x, y-1).Tile == null, up = map.GetTile(x, y+1).Tile == null;
bool lr = left && right,
du = down && up;

if (up) res = 9;
if (down) res = 1;

if (right) res = 6;
if (left) res = 4;

if (left && up) res = 8;
if (right && up) res = 10;
if (left && down) res = 0;
if (right && down) res = 2;

if (lr) res = 7;
if (du) res = 13;

if (lr && up) res = 11;
if (lr && down) res = 3;

if (du && left) res = 12;
if (du && right) res = 14;

if (lr && du) res = 15;

return res;
}

public void Update(IMap map, int x, int y, TileData data)
{
}

public void Use()
{
throw new System.NotImplementedException();
}

public byte With()
{
throw new System.NotImplementedException();
}
}

А так-же я создал IItem для будущего инвентаря:
public interface IItem
{
string Name { get; }
string Description { get; }
void Use();
byte With();
}

В нём есть функции:
Use - использование предмета, зелье будет выпито а меч создаст атаку.
With - с помощью него будут рисоваться предметы в руках возвращает стейт рук.
Потом так-же будет передаваться ITransform а возвращаемый byte станет ref значением.

ITransform - ещё не готов но он будет содержать функции:
Vector2 World2Local(Vector2 position);
Vector2 Local2World(Vector2 position);
float World2Local(float degrees);
float Local2World(float degrees);


Кстати, в коде вы можете заметить такие классы как:
SDraw и Sprite.
SDraw - расшифровывается как StaticDraw, это класс для того чтобы не передавать всё время SpriteBatch, содержит функции для рисования спрайтов:
Rect - рисует спрайт.
Text - рисует текст.
RectXLine - рисует спрайт растянутый по оси X от точки А до точки Б.
RectYLine - рисует спрайт растянутый по оси Y от точки А до точки Б.
RectXArrow - рисует спрайт растянутый по оси X от точки А до точки Б с спрайтом на точке Б.
RectYArrow - рисует спрайт растянутый по оси Y от точки А до точки Б с спрайтом на точке Б.
А Sprite это простая структура:
public struct Sprite
{
public Texture2D Texture { get; init; }

public int Width => Texture.Width;
public int Height => Texture.Height;

public Rectangle Rect { get; init; }

public Sprite(Sprite sprite, Rectangle rect)
{
Texture = sprite.Texture;
Rect = new Rectangle(rect.Location + sprite.Rect.Location, rect.Size);
}

public Sprite(Texture2D texture)
{
Texture = texture;
Rect = new Rectangle(0, 0, texture.Width, texture.Height);
}

public Sprite(Texture2D texture, Rectangle rect)
{
Texture = texture;
Rect = rect;
}

public Sprite[] Split(int columns, int rows, int padding = 0, int ignore = 0)
{
int subTextureWidth = (Rect.Width - (columns - 1) * padding) / columns;
int subTextureHeight = (Rect.Height - (rows - 1) * padding) / rows;

Sprite[] subTextures = new Sprite[rows * columns - ignore];

for (int row = 0; row < rows; row++)
{
for (int col = 0; col < columns; col++)
{
if (row * columns + col >= rows * columns - ignore) break;

int x = col * (subTextureWidth + padding),
y = row * (subTextureHeight + padding);
Rectangle subTextureRect = new Rectangle(x, y, subTextureWidth, subTextureHeight);

subTextures[row * columns + col] = new Sprite(this, subTextureRect);
}
}

return subTextures;
}

public void SplitX(int x, out Sprite a, out Sprite b)
{
a = new Sprite(this, new Rectangle(Rect.X, Rect.Y, Rect.Width, x));
b = new Sprite(this, new Rectangle(Rect.X, x, Rect.Width, Rect.Height - x));
}

public void SplitY(int y, out Sprite a, out Sprite b)
{
a = new Sprite(this, new Rectangle(Rect.X, Rect.Y, Rect.Width, y));
b = new Sprite(this, new Rectangle(Rect.X, y, Rect.Width, Rect.Height - y));
}

public static implicit operator Texture2D(Sprite sprite) => sprite.Texture;
}


Ничего от вас не скрываю!
👍2😁1
Проверка комментариев.