Привет! А я тут чуть ли не помер от мысли об этом посте...
Так уж вышло, что инвентарь - не простая штука, а именно:
- Добавляя инвентарь ты добавляешь GUI который плотно связан с игровым процессом.
- Добавляешь предметы, а предметы в моей игре могут находиться в мире как объекты.
И как вы понимаете это немного, но! У Monogame есть проблема:
Шрифты которые он нам предлагает - говно... Да вот так кратко.
Так что я добавил SpriteFontPlus для нормального текста в игре.
Ну, теперь давайте о игре поговорим уже...
Для начала я резделил игру на гейм стейты и добавил копание на правую клавишу, но это не интересно, показывать не буду.
И давайте о том, что же я сделал и что я буду показывать:
Немного поменялся интерфейс предмета:
Тут всё для новых механик которые будут в игре.
Так уж вышло, что инвентарь - не простая штука, а именно:
- Добавляя инвентарь ты добавляешь GUI который плотно связан с игровым процессом.
- Добавляешь предметы, а предметы в моей игре могут находиться в мире как объекты.
И как вы понимаете это немного, но! У Monogame есть проблема:
Шрифты которые он нам предлагает - говно... Да вот так кратко.
Так что я добавил SpriteFontPlus для нормального текста в игре.
Ну, теперь давайте о игре поговорим уже...
Для начала я резделил игру на гейм стейты и добавил копание на правую клавишу, но это не интересно, показывать не буду.
И давайте о том, что же я сделал и что я буду показывать:
Немного поменялся интерфейс предмета:
public interface IItem
{
string Name { get; }
string Description { get; }
int MaxCount { get; }
Sprite ItemSprite { get; }
void Use(ITransform transform, ref int count);
void With(ITransform transform, ref byte armsState, ref float armsRotation);
}
Тут всё для новых механик которые будут в игре.
👍1
Теперь о предметах:
Это предмет, он падает и подбирается игроком но вы могли увидеть тут совсем нам не знакомый класс Core.
public class Item : Entity
{
public const float getItemDistance = Map.tileSize * 4;
public (IItem, int) item;
private FVector2 velocity;
private FVector2 position;
private readonly Player player;
public Item((IItem, int) item, FVector2 position) : base()
{
this.item = item;
this.position = position;
velocity = new FVector2(Random.Float(-10, 10), Random.Float(-55, -30));
player = Core.GetEntity<Player>();
}
public override void Draw() => SDraw.Rect(item.Item1.ItemSprite, position);
public override void Update()
{
bool collided = false;
Core.world.RayCast(
(fixture, point, normal, fraction) =>
{
if (fixture.Body.Tag is IMap)
{
position = point + normal * 0.1f;
velocity.X = 0;
if (normal.Y != 0)
velocity.Y = 0;
collided = true;
return 0;
}
return -1;
}, position, position + velocity * Time.Delta);
if (FVector2.Distance(player.transform.Position, position) < getItemDistance)
{
int count = player.inventory.Add(item.Item1, item.Item2);
if (count != 0)
item.Item2 = count;
else
Remove();
}
if (!collided) position += velocity * Time.Delta;
velocity.Y += InGameState.gravity * Time.Delta;
}
}
Это предмет, он падает и подбирается игроком но вы могли увидеть тут совсем нам не знакомый класс Core.
👍1
Не волнуйтесь, этот класс это просто контейнер с переменными и некоторыми функциями для менеджмента существ:
public static class Core
{
public static Sprite[] icons;
public static Sprite OnInventoryIcon => icons[1];
public static Sprite OffInventoryIcon => icons[0];
public static Button.Style buttonStyle;
public static Window.Style windowStyle;
public static DynamicSpriteFontScaled font;
public static (Action draw, Action update) entities = (() => { }, () => { });
public static World world;
public static IMap map;
public static Camera camera;
public static Action criticalGuiDraw = () => { };
public static T GetEntity<T>()
where T : Entity
{
foreach (var entity in entities.update.GetInvocationList())
{
if (entity.Target is T t) return t;
}
return null;
}
public static void AddEntity(Action draw, Action update)
{
entities.update += update;
entities.draw += draw;
}
public static void RemoveEntity(Action draw, Action update)
{
entities.update -= update;
entities.draw -= draw;
}
}
👍1
Теперь о GUI, в нём всё тоже самое но только поменялись эти функции а с ними и конструктор:
public FVector2 GetAnchoredPosition(FVector2 point, FRectangle rectangle) => rectangle.Location + (rectangle.Size - this.rectangle.Size) * Anchor + point;
public void Draw() => BaseDraw(new FRectangle(FVector2.Zero, Camera.WorldViewport));
public void BaseDraw(FRectangle rectangle)
{
FRectangle rect = this.rectangle.Size == FVector2.Zero ? rectangle : new FRectangle(GetAnchoredPosition(this.rectangle.Location, rectangle), this.rectangle.Size);
Draw(rect);
DrawAction(rect);
}
public virtual void Draw(FRectangle rectangle)
{
}
public void Update() => BaseUpdate(new FRectangle(FVector2.Zero, Camera.WorldViewport));
public void BaseUpdate(FRectangle rectangle)
{
FRectangle rect = this.rectangle.Size == FVector2.Zero ? rectangle : new FRectangle(GetAnchoredPosition(this.rectangle.Location, rectangle), this.rectangle.Size);
Update(rect);
if (MouseOn)
{
Delegate[] list = UpdateAction.GetInvocationList();
bool breaked = false;
for (int i = list.Length - 1; i >= 0; i--)
if (list[i].Target is GUIElement target)
{
target.MouseOn = false;
if (breaked) continue;
list[i].DynamicInvoke(rect);
if (target.MouseOn) breaked = true;
}
}
}
public virtual void Update(FRectangle rectangle)
{
if (MouseOn = (Parent?.MouseOn ?? true) && rectangle.Contains(Mouse.GUIPosition))
Mouse.OnGUI = true;
}
👍1
Раз уж со всеми проблемами разобрались то давайте переходить к инвентарю:
public class InventoryContainer : IInventory
{
public (IItem item, int count)[,] items = new (IItem item, int count)[5, 5];
const int slotSize = 10;
GUIElement on, off, window;
public InventoryContainer(GUIElement GUI)
{
// Код вырезан по причине того, что обьясняется отдельно
}
public void DrawSelected()
{
if (Selected.item == null) return;
SDraw.Rect(Selected.item.ItemSprite, Mouse.GUIPosition);
SDraw.Text(Core.font, $"{Selected.count}개", Mouse.GUIPosition);
}
public (IItem item, int count) Selected { get; private set; }
public void Get(int x, int y)
{
var item = items[x, y];
if (Selected.item != item.item)
{
items[x, y] = Selected;
Selected = item;
}
else
{
int count = item.count += Selected.count;
if (count > item.item.MaxCount) Selected = (Selected.item, count - item.item.MaxCount);
else Selected = default;
}
}
public int Add(IItem item, int count)
{
int _x, _y;
for (_x = 0; _x < items.GetLength(0); _x++)
for (_y = 0; _y < items.GetLength(1); _y++)
if (items[_x, _y].item == item)
{
int i = items[_x, _y].count += count;
if (i > item.MaxCount)
{
count = i - item.MaxCount;
items[_x, _y].count = item.MaxCount;
}
else return 0;
}
for (_x = 0; _x < items.GetLength(0); _x++)
for (_y = 0; _y < items.GetLength(1); _y++)
{
if (items[_x, _y].item == null)
{
if (count > item.MaxCount)
{
items[_x, _y] = (item, item.MaxCount);
count -= item.MaxCount;
}
else
{
items[_x, _y] = (item, count);
return 0;
}
}
}
return count;
}
public void Remove(IItem item, int count)
{
for (int x = 0; x < items.GetLength(0); x++)
for (int y = 0; y < items.GetLength(1); y++)
if (items[x, y].item == item)
{
count -= items[x, y].count;
if (count < 0)
items[x, y].count = -count;
else
items[x, y] = default;
}
}
public bool Contains(IItem item, int count)
{
int counter = 0;
for (int x = 0; x < items.GetLength(0); x++)
for (int y = 0; y < items.GetLength(1); y++)
if (items[x, y].item == item)
counter += items[x, y].count;
return counter >= count;
}
}
Простая реализация IInventory с интерфейсом который создаётся этими строками:
Тут создаётся кнопка нажимая на которую у нас открывается инвентарь а сама кнопка исчезает
Дальше:
Создаём окно с кнопками инвентаря, тут так-же видно, что я использую корейский символ в тексте, шрифт от Monogame не поддерживает такое из коробки и приходится идти путями окольными, благо кто-то добрый вывел нас из темени этих дебрей светом от великолепного SpriteFontPlus!
on = new Button(null, new FVector2(0, 0), new FRectangle(0, 0, slotSize, slotSize), () =>
{
on.Remove();
window.Remove();
GUI.Add(off);
Core.criticalGuiDraw -= DrawSelected;
}, Core.buttonStyle, Core.OnInventoryIcon);
Тут создаётся кнопка нажимая на которую у нас открывается инвентарь а сама кнопка исчезает
Дальше:
window = new Window(null, new FVector2(0, 0), new FRectangle(slotSize + 1, 0, items.GetLength(0) * (slotSize + 1) + 3, items.GetLength(1) * (slotSize + 1) + 3), Core.windowStyle);
for (int x = 0; x < items.GetLength(0); x++)
for (int y = 0; y < items.GetLength(1); y++)
{
int _x = x, _y = y;
new Button(window, new FVector2(0, 0), new FRectangle(
x * (slotSize + 1) + 2,
y * (slotSize + 1) + 2,
slotSize, slotSize),
() => Get(_x, _y), Core.buttonStyle,
(rectangle) =>
{
if (items[_x, _y].item == null) return;
SDraw.Rect(items[_x, _y].item.ItemSprite, rectangle.Center);
SDraw.Text(Core.font, $"{items[_x, _y].count}개", rectangle.Center);
});
}
Создаём окно с кнопками инвентаря, тут так-же видно, что я использую корейский символ в тексте, шрифт от Monogame не поддерживает такое из коробки и приходится идти путями окольными, благо кто-то добрый вывел нас из темени этих дебрей светом от великолепного SpriteFontPlus!
👍1
Кхм, что-то меня занесло, продолжаем:
Тут создаётся кнопка для удаления инвентаря и добавления кнопки.
Так-же в игрока были добавлены строки:
Поля:
В конструкторе:
При рисовании:
И в обновлении:
И теперь у нас есть инвентарь, предметы и копание.
ВВЕРХ
off = new Button(GUI, new FVector2(0, 0), new FRectangle(0, 0, slotSize, slotSize), () =>
{
off.Remove();
GUI.Add(on);
GUI.Add(window);
Core.criticalGuiDraw += DrawSelected;
if (Selected.item != null) Add(Selected.item, Selected.count);
Selected = default;
}, Core.buttonStyle, Core.OffInventoryIcon);
Тут создаётся кнопка для удаления инвентаря и добавления кнопки.
Так-же в игрока были добавлены строки:
Поля:
public readonly InventoryContainer inventory;
private float armsRotation;
private byte inArm;
private byte armsState;
В конструкторе:
inventory = new InventoryContainer(GUI);
При рисовании:
inventory.items[inArm, 0].item?.With(transform, ref armsState, ref armsRotation);
И в обновлении:
(IItem item, int count) = inventory.items[inArm, 0];
item?.Use(transform, ref count);
if (count <= 0)
inventory.items[inArm, 0] = default;
else
inventory.items[inArm, 0] = (item, count);
Core.camera.Position = transform.Position;
if (Keyboard.IsPressed(Keys.D1)) inArm = 0;
if (Keyboard.IsPressed(Keys.D2)) inArm = 1;
if (Keyboard.IsPressed(Keys.D3)) inArm = 2;
if (Keyboard.IsPressed(Keys.D4)) inArm = 3;
if (Keyboard.IsPressed(Keys.D5)) inArm = 4;
if (Keyboard.IsPressed(Keys.Q))
{
new Item(inventory.items[inArm, 0], transform.Position);
inventory.items[inArm, 0] = (null, 0);
}
И теперь у нас есть инвентарь, предметы и копание.
ВВЕРХ
👍1
Ну, я заболел, мне хреново, ввожу в курс дела чтобы потом не было вопросов, мне прям очень плохо, даже говорить больно не говоря о мыслительных процессах.
Я вернулся, сделал кирку и лук а так-же воду, но разделю это всё на отдельные посты.
А ещё я не буду вам всё объяснять дотошно а то я иногда меняю кучу всяких вещей в разных класса но итогом изменений практически нету...
Давайте начнём с кирки, тут всё просто:
Для начала я сделал класс для оружий ближнего боя, он в принципе не сложный.
При нажатии мыши ждать X время и менять угол замаха на противоположный после удара.
А ещё я не буду вам всё объяснять дотошно а то я иногда меняю кучу всяких вещей в разных класса но итогом изменений практически нету...
Давайте начнём с кирки, тут всё просто:
Для начала я сделал класс для оружий ближнего боя, он в принципе не сложный.
При нажатии мыши ждать X время и менять угол замаха на противоположный после удара.
public class MeleeWeapon : IItem
{
public string Name => throw new NotImplementedException();
public string Description => throw new NotImplementedException();
public int MaxCount => 1;
public Sprite ItemSprite { get; init; }
public bool Flip => false;
public bool Animated => true;
private readonly float swingAngle;
private readonly float swingPerSecond;
private readonly byte state;
private readonly float offset;
private readonly float attackOffset;
private readonly Sprite sprite;
private static bool side;
public MeleeWeapon(float swingAngle, float swingPerSecond, byte state, float offset, float attackOffset, Sprite sprite, Sprite item)
{
this.swingAngle = swingAngle;
this.swingPerSecond = swingPerSecond;
this.state = state;
this.offset = offset;
this.attackOffset = attackOffset;
this.sprite = sprite;
ItemSprite = item;
}
public void Use(ITransform transform, ref byte armsState, ref float armLRotation, ref float armRRotation, ref int count, ref float timer, ArmData armData)
{
armsState = state;
FVector2 basePosition = transform.Local2World(new FVector2(0, -4));
FVector2 lposition = basePosition - Mouse.Position;
lposition.Normalize();
float baseAngle = MathHelper.ToDegrees(MathF.Atan2(lposition.Y, lposition.X));
float angle = baseAngle + (side ? swingAngle : -swingAngle) + 90;
FVector2 armPosition = basePosition + FVector2.UpOf(angle) * offset;
FVector2 p = transform.Local2World(new FVector2(2, -4)) - armPosition;
armLRotation = MathHelper.ToDegrees(MathF.Atan2(p.Y, p.X)) + 90;
p = transform.Local2World(new FVector2(-2, -4)) - armPosition;
armRRotation = MathHelper.ToDegrees(MathF.Atan2(p.Y, p.X)) + 90;
if (Mouse.LeftDown)
{
timer += Time.Delta * swingPerSecond;
while (timer >= 1)
{
FVector2 attackPosition = basePosition - lposition * attackOffset;
Attack(attackPosition);
Effects.slashMedium.Spawn(attackPosition, baseAngle + 180);
side = !side;
timer--;
}
}
else
{
timer = 0;
}
armData.Set(
("angle", angle),
("armPosition", armPosition)
);
}
public void With(ITransform transform, byte armsState, float armLRotation, float armRRotation, ArmData armData)
{
armData.Get(out FVector2 position, "armPosition");
armData.Get(out float angle, "angle");
SDraw.Rect(sprite, position, angle + 90, 1, 0, Origin.Zero);
}
public virtual void Attack(FVector2 point)
{
}
}
Теперь о самой кирке:
Просто используем класс оружия ближнего боя но во время удара копаем.
Просто используем класс оружия ближнего боя но во время удара копаем.
public class Pickaxe : MeleeWeapon
{
private readonly float power;
private readonly float radius;
public Pickaxe(float power, float radius, float swingAngle, float swingPerSecond, byte state, float offset, float attackOffset, Sprite sprite, Sprite item) : base(swingAngle, swingPerSecond, state, offset, attackOffset, sprite, item)
{
this.power = power;
this.radius = radius;
}
public override void Attack(FVector2 point)
{
Core.map.MineTile(Core.map.World2Cell(point), power, radius);
}
}
А вот и лук, тут уже всё сложнее:
Если при отжатой левой клавишей мыши анимация находиться на кадре больше нулевого - запускай стрелу.
Если при отжатой левой клавишей мыши анимация находиться на кадре больше нулевого - запускай стрелу.
public class Bow : IItem
{
public string Name => throw new NotImplementedException();
public string Description => throw new NotImplementedException();
public int MaxCount => 1;
public Sprite ItemSprite { get; init; }
public bool Flip => false;
public bool Animated => true;
private readonly Func<Projectile> projectile;
private readonly (float min, float max) arrowOffset;
private readonly float offset;
private readonly float framerateScale;
private readonly float power;
private readonly Sprite[] sprites;
public Bow(Func<Projectile> projectile, (float min, float max) arrowOffset, float offset, float power, Sprite[] sprites, Sprite item, float framerateScale = 1)
{
this.projectile = projectile;
this.arrowOffset = arrowOffset;
this.offset = offset;
this.framerateScale = framerateScale;
this.power = power;
this.sprites = sprites;
ItemSprite = item;
}
public void Use(ITransform transform, ref byte armsState, ref float armLRotation, ref float armRRotation, ref int count, ref float timer, ArmData armData)
{
int frame = 0;
FVector2 basePosition = transform.Local2World(new FVector2(2, -4));
FVector2 position = basePosition - Mouse.Position;
armLRotation = MathHelper.ToDegrees(MathF.Atan2(position.Y, position.X)) + 90;
position.Normalize();
sprites.AnimationEnd(out frame, SpriteHelpers.frameRate * framerateScale, ref timer);
if (Mouse.LeftUp)
{
if (frame > 0)
projectile().Spawn(basePosition, armLRotation + 90, power * timer);
frame = 0;
timer = 0;
}
FVector2 rp = transform.Local2World(new FVector2(-2, -4)) - (basePosition - position * arrowOffset.max);
position = basePosition - position * MathHelper.Lerp(arrowOffset.max, arrowOffset.min, frame / (float)(sprites.Length-1));
armRRotation = MathHelper.ToDegrees(MathF.Atan2(rp.Y, rp.X)) + 90;
armsState = Player.GetState(frame, 1);
armData.Set(
("frame", frame),
("position", position)
);
}
public void With(ITransform transform, byte armsState, float armLRotation, float armRRotation, ArmData armData)
{
armData.Get(out int frame, "frame");
armData.Get(out FVector2 position, "position");
FVector2 offset = FVector2.UpOf(armLRotation) * this.offset;
SDraw.Rect(sprites[frame], transform.Local2World(new FVector2(2, -4)) + offset, armLRotation + 90);
SDraw.Rect(projectile().sprite, position + offset, armLRotation + 90, 1, 0, Origin.Zero);
}
}
This media is not supported in your browser
VIEW IN TELEGRAM
Тут стрельба из лука и копание киркой.
👍1
А вот пост о воде.
Для начала рассмотрим конструктор и поля:
cells - клетки воды, значение 0 - пусто, -1 - стенка.
render - клетки воды но сглаженные меж кадрами.
liquid - спрайты для авто-тайла воды.
timer - таймер для обновления мира.
Не думаю, что width и height требуют объяснений.
Дальше идёт конструктор который собирает мир с помощью лямбды генератора, потом буду заменять на отельный класс WordGenerator.
Дальше идут функции для проверок:
CanFlow - проверка на то можно ли в клетку плыть. (в данном случае именно плыть ибо это либо просто движение либо подводное передвижение)
CanPush - проверка на то можно ли в клетку выкинуть часть себя.
Move - движение в клетку, возвращает ту часть которая не смогла переместиться.
Для начала рассмотрим конструктор и поля:
cells - клетки воды, значение 0 - пусто, -1 - стенка.
render - клетки воды но сглаженные меж кадрами.
liquid - спрайты для авто-тайла воды.
timer - таймер для обновления мира.
Не думаю, что width и height требуют объяснений.
Дальше идёт конструктор который собирает мир с помощью лямбды генератора, потом буду заменять на отельный класс WordGenerator.
public class WaterWorld
{
public float[,] cells;
public readonly float[,] render;
private readonly Sprite[] liquid;
private readonly int width;
private readonly int height;
private float timer = 0;
private const float time = .01f;
public WaterWorld(Sprite sprite, int width, int height, Func<int, int, bool> generator)
{
cells = new float[width, height];
render = new float[width, height];
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
render[i, j] = cells[i, j] = generator(i, j) ? 0 : -1;
}
}
liquid = sprite.Split(4, 4, 1);
this.width = width;
this.height = height;
}
...
Дальше идут функции для проверок:
CanFlow - проверка на то можно ли в клетку плыть. (в данном случае именно плыть ибо это либо просто движение либо подводное передвижение)
CanPush - проверка на то можно ли в клетку выкинуть часть себя.
Move - движение в клетку, возвращает ту часть которая не смогла переместиться.
...
public bool CanFlow(float other)
{
if (other == -1) return false;
if (other >= 1) return false;
return true;
}
public bool CanPush(float other)
{
if (other == -1) return false;
if (other >= .5) return false;
return true;
}
public float Move(float pop, float r, ref float other)
{
float sum = r + pop;
if (sum > 1)
{
other += 1 - r;
return sum - 1;
}
other += pop;
return 0;
}
...
Дальше идёт две функции обновления:
Update - обновляет таймер и если время таймера больше чем возможное время то пока оно не будет меньше вызывать Tick.
Сделано чтобы вода на всех устройствах лилась одинаково.
Tick - обновление каждой клетки
Update - обновляет таймер и если время таймера больше чем возможное время то пока оно не будет меньше вызывать Tick.
Сделано чтобы вода на всех устройствах лилась одинаково.
Tick - обновление каждой клетки
...
public void Update()
{
timer += Time.Delta;
while (timer >= time)
{
Tick();
timer -= time;
}
}
public void Tick()
{
float[,] mask = new float[width, height];
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
float self = cells[i, j];
if (self == -1)
{
mask[i, j] = -1;
continue;
}
if (self == 0) continue;
bool
b_l = false,
b_r = false,
b_d = false,
b_u = false;
float l = i == 0 ? -1 : cells[i-1, j];
float r = i == width-1 ? -1 : cells[i+1, j];
float d = j == height-1 ? -1 : cells[i, j+1];
float u = j == 0 ? -1 : cells[i, j-1];
b_d = CanFlow(d);
if (!CanPush(d))
{
b_l = l < self && CanFlow(l);
b_r = r < self && CanFlow(r);
if (b_l && b_r && r != l)
{
if (l > r)
b_l = false;
else
b_r = false;
}
}
if (self > .5)
{
b_u = CanPush(u);
}
int c = 1;
if (b_l) c++;
if (b_r) c++;
if (b_d) c++;
if (b_u) c++;
float sum = self / c;
if (c == 2 && b_d)
{
self = Move(self, d, ref mask[i, j + 1]);
}
else
{
self = sum;
if (b_l) self += Move(sum, l, ref mask[i - 1, j]);
if (b_r) self += Move(sum, r, ref mask[i + 1, j]);
if (b_d) self += Move(sum, d, ref mask[i, j + 1]);
if (b_u) self += Move(sum, u, ref mask[i, j - 1]);
}
mask[i, j] += self;
}
}
for (int j = 0; j < height; j++)
for (int i = 0; i < width; i++)
{
if (mask[i, j] == -1) render[i, j] = -1;
else render[i, j] = MathHelper.Lerp(render[i, j], mask[i, j], .1f);
}
cells = mask;
}
...
И наконец функция Draw рисует каждую клетку которая может быть видна а код авто-тайлинга взят из класса AutoTile:
public void Draw()
{
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
{
if (render[i, j] <= 0.1) continue;
float l = i == 0 ? -1 : render[i - 1, j];
float r = i == width - 1 ? -1 : render[i + 1, j];
float d = j == height - 1 ? -1 : render[i, j + 1];
float u = j == 0 ? -1 : render[i, j - 1];
byte res = 5;
bool left = l != -1 && l <= 0.1, right = r != -1 && r <= 0.1,
down = u != -1 && u <= 0.1, up = d != -1 && d <= 0.1;
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;
SDraw.Rect(liquid[res], new Color(1f, 1, 1, render[i, j]), new FVector2(i, j) * Map.tileSize, 0, 1, 0, Origin.Zero, Origin.Zero);
}
}
}
👍1
Media is too big
VIEW IN TELEGRAM
Графическое обновление!
В основном тут поменялись спрайты но как вы видите на видео вода сделана явно не спрайтами.
Это треугольники, я не смогу показать как я сделал её ибо код разошёлся на 1000 строк в основном его занимает массив правил тип которого сам по себе может ввести в ступор неподготовленного человека:
В основном тут поменялись спрайты но как вы видите на видео вода сделана явно не спрайтами.
Это треугольники, я не смогу показать как я сделал её ибо код разошёлся на 1000 строк в основном его занимает массив правил тип которого сам по себе может ввести в ступор неподготовленного человека:
(bool lu, bool u, bool ru, bool l, bool r, bool ld, bool d, bool rd, (FVector2 a, FVector2 b, FVector2 c, byte at, byte bt, byte ct, FVector2 ac, FVector2 bc, FVector2 cc)[])[]
👍3