203 lines
6.5 KiB
C++
203 lines
6.5 KiB
C++
|
|
#include "render/sprites.h"
|
|||
|
|
|
|||
|
|
#include "core/math.h"
|
|||
|
|
|
|||
|
|
namespace
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
// Занятая часть строки y для ромба 32x16.
|
|||
|
|
void DiamondRow(int y, int& x0, int& x1)
|
|||
|
|
{
|
|||
|
|
const int k = (y < TILE_HALF_H) ? y : (TILE_H - 1 - y);
|
|||
|
|
const int width = 4 * (k + 1);
|
|||
|
|
x0 = TILE_HALF_W - width / 2;
|
|||
|
|
x1 = x0 + width;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Обводка по силуэту: любой непрозрачный пиксель, у которого есть прозрачный
|
|||
|
|
// 4-сосед (или край спрайта), перекрашивается в outline.
|
|||
|
|
void OutlineSilhouette(Sprite& s, uint32_t outline)
|
|||
|
|
{
|
|||
|
|
Sprite copy = s;
|
|||
|
|
for (int y = 0; y < s.h; ++y)
|
|||
|
|
{
|
|||
|
|
for (int x = 0; x < s.w; ++x)
|
|||
|
|
{
|
|||
|
|
if (AlphaOf(copy.Get(x, y)) == 0) continue;
|
|||
|
|
const bool border =
|
|||
|
|
x == 0 || y == 0 || x == s.w - 1 || y == s.h - 1 ||
|
|||
|
|
AlphaOf(copy.Get(x - 1, y)) == 0 || AlphaOf(copy.Get(x + 1, y)) == 0 ||
|
|||
|
|
AlphaOf(copy.Get(x, y - 1)) == 0 || AlphaOf(copy.Get(x, y + 1)) == 0;
|
|||
|
|
if (border) s.Set(x, y, outline);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Sprite MakeFloor(uint32_t base)
|
|||
|
|
{
|
|||
|
|
Sprite s;
|
|||
|
|
s.Init(TILE_W, TILE_H, 0);
|
|||
|
|
s.ax = TILE_HALF_W;
|
|||
|
|
s.ay = TILE_HALF_H;
|
|||
|
|
|
|||
|
|
const uint32_t edge = ShadeColor(base, 0.78f);
|
|||
|
|
const uint32_t fleck = ShadeColor(base, 1.18f);
|
|||
|
|
|
|||
|
|
for (int y = 0; y < TILE_H; ++y)
|
|||
|
|
{
|
|||
|
|
int x0 = 0, x1 = 0;
|
|||
|
|
DiamondRow(y, x0, x1);
|
|||
|
|
for (int x = x0; x < x1; ++x)
|
|||
|
|
{
|
|||
|
|
uint32_t c = base;
|
|||
|
|
if (x < x0 + 2 || x >= x1 - 2) c = edge; // рант тайла
|
|||
|
|
else if (((x * 7 + y * 13) % 23) == 0) c = fleck; // «металлическая» рябь
|
|||
|
|
s.Set(x, y, c);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Sprite MakeWall(uint32_t top)
|
|||
|
|
{
|
|||
|
|
Sprite s;
|
|||
|
|
s.Init(TILE_W, WALL_H, 0);
|
|||
|
|
s.ax = TILE_HALF_W;
|
|||
|
|
s.ay = TILE_HALF_H + TILE_H; // низ блока садится на ромб пола
|
|||
|
|
|
|||
|
|
const uint32_t left = ShadeColor(top, 0.62f);
|
|||
|
|
const uint32_t right = ShadeColor(top, 0.80f);
|
|||
|
|
|
|||
|
|
// верхняя грань
|
|||
|
|
for (int y = 0; y < TILE_H; ++y)
|
|||
|
|
{
|
|||
|
|
int x0 = 0, x1 = 0;
|
|||
|
|
DiamondRow(y, x0, x1);
|
|||
|
|
for (int x = x0; x < x1; ++x) s.Set(x, y, top);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// вертикальные грани: от нижней кромки ромба вниз на высоту тайла
|
|||
|
|
for (int x = 0; x < TILE_W; ++x)
|
|||
|
|
{
|
|||
|
|
int bottom = -1;
|
|||
|
|
for (int y = 0; y < TILE_H; ++y)
|
|||
|
|
if (AlphaOf(s.Get(x, y)) != 0) bottom = y;
|
|||
|
|
if (bottom < 0) continue;
|
|||
|
|
|
|||
|
|
const uint32_t side = (x < TILE_HALF_W) ? left : right;
|
|||
|
|
for (int y = bottom + 1; y <= bottom + TILE_H && y < WALL_H; ++y)
|
|||
|
|
s.Set(x, y, side);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
OutlineSilhouette(s, ShadeColor(top, 0.35f));
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Капсула с обводкой и бликом.
|
|||
|
|
Sprite MakeCapsule(int w, int h, float radius, uint32_t body)
|
|||
|
|
{
|
|||
|
|
Sprite s;
|
|||
|
|
s.Init(w, h, 0);
|
|||
|
|
s.ax = w / 2;
|
|||
|
|
s.ay = h - 2; // «ноги» чуть выше нижнего края
|
|||
|
|
|
|||
|
|
const uint32_t outline = ShadeColor(body, 0.35f);
|
|||
|
|
const uint32_t hilite = ShadeColor(body, 1.35f);
|
|||
|
|
const uint32_t shadow = ShadeColor(body, 0.72f);
|
|||
|
|
|
|||
|
|
const float cx = (w - 1) * 0.5f;
|
|||
|
|
const Vec2 a{cx, radius - 0.5f};
|
|||
|
|
const Vec2 b{cx, h - radius - 0.5f};
|
|||
|
|
|
|||
|
|
for (int y = 0; y < h; ++y)
|
|||
|
|
{
|
|||
|
|
for (int x = 0; x < w; ++x)
|
|||
|
|
{
|
|||
|
|
const float d = DistancePointToSegment({float(x), float(y)}, a, b);
|
|||
|
|
if (d > radius) continue;
|
|||
|
|
if (d > radius - 1.0f) { s.Set(x, y, outline); continue; }
|
|||
|
|
|
|||
|
|
uint32_t c = body;
|
|||
|
|
if (float(x) < cx - 0.5f && y < h / 2) c = hilite;
|
|||
|
|
else if (float(x) > cx + 1.0f) c = shadow;
|
|||
|
|
s.Set(x, y, c);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Боец ближнего боя: та же капсула, но шире в плечах и с клинком у бедра.
|
|||
|
|
// На 480x270 класс обязан читаться силуэтом — цвет уже занят номером агента.
|
|||
|
|
Sprite MakeMeleeCapsule(int w, int h, float radius, uint32_t body)
|
|||
|
|
{
|
|||
|
|
Sprite s = MakeCapsule(w, h, radius, body);
|
|||
|
|
|
|||
|
|
const uint32_t steel = RGBA(196, 206, 222);
|
|||
|
|
const uint32_t edge = RGBA(240, 246, 255);
|
|||
|
|
|
|||
|
|
// Клинок: короткая вертикаль вдоль правого края силуэта, с бликом.
|
|||
|
|
const int bx = w - 2;
|
|||
|
|
for (int y = 2; y < h - 4; ++y)
|
|||
|
|
{
|
|||
|
|
s.Set(bx, y, steel);
|
|||
|
|
if (y < h / 2) s.Set(bx, y, edge);
|
|||
|
|
}
|
|||
|
|
// Гарда, чтобы клинок не читался как случайная полоска.
|
|||
|
|
s.Set(bx - 1, h - 5, steel);
|
|||
|
|
s.Set(bx, h - 5, steel);
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Стрелок с пистолетом: та же капсула, но уже в плечах и с коротким стволом
|
|||
|
|
// у пояса. Класс оружия обязан читаться силуэтом — цвет уже занят номером.
|
|||
|
|
Sprite MakePistolCapsule(int w, int h, float radius, uint32_t body)
|
|||
|
|
{
|
|||
|
|
Sprite s = MakeCapsule(w, h, radius, body);
|
|||
|
|
|
|||
|
|
const uint32_t steel = RGBA(150, 156, 168);
|
|||
|
|
const uint32_t dark = RGBA(70, 74, 84);
|
|||
|
|
|
|||
|
|
// Коротышка-ствол: две пиксельных полоски у бедра, с рукоятью вниз.
|
|||
|
|
const int bx = w - 2;
|
|||
|
|
const int by = h - 7;
|
|||
|
|
s.Set(bx, by, steel);
|
|||
|
|
s.Set(bx - 1, by, steel);
|
|||
|
|
s.Set(bx, by + 1, dark);
|
|||
|
|
s.Set(bx, by + 2, dark);
|
|||
|
|
return s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} // namespace
|
|||
|
|
|
|||
|
|
uint32_t ShadeColor(uint32_t c, float k)
|
|||
|
|
{
|
|||
|
|
const float r = Clampf(float(c & 0xFF) * k, 0.0f, 255.0f);
|
|||
|
|
const float g = Clampf(float((c >> 8) & 0xFF) * k, 0.0f, 255.0f);
|
|||
|
|
const float b = Clampf(float((c >> 16) & 0xFF) * k, 0.0f, 255.0f);
|
|||
|
|
return RGBA(uint8_t(r), uint8_t(g), uint8_t(b), AlphaOf(c));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void SpriteSet::Generate()
|
|||
|
|
{
|
|||
|
|
floorA = MakeFloor(RGBA(50, 54, 62));
|
|||
|
|
floorB = MakeFloor(RGBA(42, 46, 53));
|
|||
|
|
wall = MakeWall(RGBA(78, 84, 96));
|
|||
|
|
|
|||
|
|
// 5 различимых цветов — по одному на агента
|
|||
|
|
static const uint32_t agentCols[NUM_AGENTS] = {
|
|||
|
|
RGBA(80, 200, 220),
|
|||
|
|
RGBA(232, 176, 72),
|
|||
|
|
RGBA(116, 204, 116),
|
|||
|
|
RGBA(212, 116, 200),
|
|||
|
|
RGBA(116, 148, 240),
|
|||
|
|
};
|
|||
|
|
for (int i = 0; i < NUM_AGENTS; ++i)
|
|||
|
|
{
|
|||
|
|
agent[i] = MakeCapsule(10, 14, 4.0f, agentCols[i]);
|
|||
|
|
melee[i] = MakeMeleeCapsule(12, 15, 4.6f, agentCols[i]);
|
|||
|
|
pistol[i] = MakePistolCapsule(9, 13, 3.6f, agentCols[i]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
target = MakeCapsule(12, 16, 5.0f, RGBA(120, 44, 44));
|
|||
|
|
}
|