mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-23 21:19:32 +00:00
Refactor unit bar drawing (#9763)
This fixes an issue where the bar borders would always be one pixel wide, regardless of the zoom level or game render scaling which made the bars stand out badly amongst the pixel art. This brings us closer to the old look from the surface-drawn days. This is also an experiment with using normalized, fractional coordinates for rendering instead of absolute screen coordinates. I'd like to take this approach in more places, so this is a good proof of concept.
This commit is contained in:
parent
10d8c54c7c
commit
3f746900fe
@ -85,6 +85,13 @@ void draw::fill(const color_t& c)
|
||||
draw::fill(c.r, c.g, c.b, c.a);
|
||||
}
|
||||
|
||||
void draw::fill(const SDL_FRect& rect, const color_t& c)
|
||||
{
|
||||
DBG_D << "sub-pixel fill";
|
||||
SDL_SetRenderDrawColor(renderer(), c.r, c.g, c.b, c.a);
|
||||
SDL_RenderFillRectF(renderer(), &rect);
|
||||
}
|
||||
|
||||
void draw::fill(const SDL_Rect& area)
|
||||
{
|
||||
DBG_D << "fill " << area;
|
||||
|
@ -78,6 +78,8 @@ void fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
||||
void fill(uint8_t r, uint8_t g, uint8_t b);
|
||||
void fill(const color_t& color);
|
||||
|
||||
void fill(const SDL_FRect& rect, const color_t& color);
|
||||
|
||||
/**
|
||||
* Fill an area.
|
||||
*
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "draw.hpp"
|
||||
#include "formatter.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "global.hpp"
|
||||
#include "halo.hpp"
|
||||
#include "log.hpp"
|
||||
#include "map/location.hpp"
|
||||
@ -86,55 +87,92 @@ std::unique_ptr<image::locator> get_playing_ally_orb_image(orb_status os)
|
||||
+ allied_color + ")~RC(magenta>" + status_color + ")");
|
||||
}
|
||||
|
||||
void draw_bar(int xpos, int ypos, int bar_height, double filled, const color_t& col)
|
||||
struct energy_bar
|
||||
{
|
||||
// Magic width number
|
||||
static constexpr unsigned int bar_width = 4;
|
||||
/** Bar height in pixels. */
|
||||
int bar_h{};
|
||||
|
||||
static constexpr color_t bar_color_bg{0, 0, 0, 80};
|
||||
static constexpr color_t bar_color_border{213, 213, 213, 200};
|
||||
/** The extent to which the bar is filled, ranging [0, 1]. */
|
||||
float filled{};
|
||||
|
||||
// We used to use an image for the bar instead of drawing it procedurally. Its x,y position
|
||||
// within the file was 19,13, so we offset the origin by that much to make it line up with
|
||||
// the crowns as before. Should probably compensate for this better in the future.
|
||||
const point offset = display::scaled_to_zoom(point{19, 13});
|
||||
/** Color for the bar's filled area. */
|
||||
color_t fill_color{};
|
||||
|
||||
// Full bar dimensions.
|
||||
rect bar_rect = display::scaled_to_zoom({
|
||||
xpos + offset.x,
|
||||
ypos + offset.y,
|
||||
bar_width,
|
||||
bar_height
|
||||
});
|
||||
/** Initial coordinates for the first bar drawn. */
|
||||
static constexpr point def_origin{14, 13};
|
||||
|
||||
// Bar dimensions should not overflow 80% of the scaled hex dimensions.
|
||||
// The 80% comes from an approximation of the length of a segment drawn
|
||||
// inside a regular hexagon that runs parallel to its outer left side.
|
||||
bar_rect.w = std::clamp<int>(bar_rect.w, 0, display::hex_size() * 0.80 - offset.x);
|
||||
bar_rect.h = std::clamp<int>(bar_rect.h, 0, display::hex_size() * 0.80 - offset.y);
|
||||
/** Default width for all bars. */
|
||||
static constexpr int def_w = 4;
|
||||
|
||||
filled = std::clamp<double>(filled, 0.0, 1.0);
|
||||
const int unfilled = static_cast<std::size_t>(bar_rect.h * (1.0 - filled));
|
||||
/** Distance between the top left corner of subsequent bars. */
|
||||
static constexpr int spacing = def_w + 1;
|
||||
|
||||
// Filled area dimensions.
|
||||
const rect fill_rect {
|
||||
bar_rect.x,
|
||||
bar_rect.y + unfilled,
|
||||
bar_rect.w,
|
||||
bar_rect.h - unfilled
|
||||
/** Background color for the unfilled bar area. */
|
||||
static constexpr color_t background_color{0, 0, 0, 80};
|
||||
|
||||
/** Border color surrounding the whole bar. */
|
||||
static constexpr color_t border_color{213, 213, 213, 200};
|
||||
|
||||
/** Returns the height of the bar in pixels. */
|
||||
static constexpr auto get_height(int size, double scaling)
|
||||
{
|
||||
return int(size * scaling);
|
||||
};
|
||||
|
||||
// Tinted background.
|
||||
draw::fill(bar_rect, bar_color_bg);
|
||||
/** Returns the relative amount of the bar to be filled. */
|
||||
static constexpr auto get_filled(int size, int max)
|
||||
{
|
||||
return std::clamp(float(size) / max, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
// Filled area.
|
||||
draw::fill(fill_rect, col);
|
||||
/** Returns the fill color with the appropriate alpha depending on the focused state. */
|
||||
static constexpr auto get_color(const color_t& c, bool focused)
|
||||
{
|
||||
return color_t{c.r, c.g, c.b, float_to_color(focused ? 1.0 : 0.8)};
|
||||
}
|
||||
};
|
||||
|
||||
// Bar outline.
|
||||
draw::rect(bar_rect, bar_color_border);
|
||||
}
|
||||
void draw_bar(int index, const energy_bar& data, const rect& bounds)
|
||||
{
|
||||
// All absolute bar coordinates are normalized relative to the standard 72px hex.
|
||||
using game_config::tile_size;
|
||||
|
||||
SDL_FPoint p1{
|
||||
float(energy_bar::def_origin.x + energy_bar::spacing * index) / tile_size,
|
||||
float(energy_bar::def_origin.y ) / tile_size,
|
||||
};
|
||||
|
||||
// If the top of the bar sits 13px from the top of the scaled hex rect, the bottom
|
||||
// of the bar should extend no closer than 13px from the bottom.
|
||||
SDL_FPoint p2{
|
||||
std::min(p1.x + float(data.def_w) / tile_size, 1.0f - p1.x),
|
||||
std::min(p1.y + float(data.bar_h) / tile_size, 1.0f - p1.y)
|
||||
};
|
||||
|
||||
// Full bar dimensions
|
||||
const SDL_FRect bar_rect = sdl::precise_subrect(bounds, p1, p2);
|
||||
draw::fill(bar_rect, energy_bar::border_color);
|
||||
|
||||
// Size of a single pixel relative to the standard hex
|
||||
const float one_pixel = 1.0f / tile_size;
|
||||
|
||||
SDL_FPoint bg1{ p1.x + one_pixel, p1.y + one_pixel };
|
||||
SDL_FPoint bg2{ p2.x - one_pixel, p2.y - one_pixel };
|
||||
|
||||
// Full inner dimensions
|
||||
const SDL_FRect inner_rect = sdl::precise_subrect(bounds, bg1, bg2);
|
||||
draw::fill(inner_rect, energy_bar::background_color);
|
||||
|
||||
SDL_FPoint fill1{ 0.0f, 1.0f - data.filled };
|
||||
SDL_FPoint fill2{ 1.0f, 1.0f };
|
||||
|
||||
// Filled area, relative to the bottom of the inner bar area
|
||||
const SDL_FRect fill_rect = sdl::precise_subrect(inner_rect, fill1, fill2);
|
||||
draw::fill(fill_rect, data.fill_color);
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
unit_drawer::unit_drawer(display& thedisp)
|
||||
: disp(thedisp)
|
||||
, dc(disp.context())
|
||||
@ -171,24 +209,11 @@ void unit_drawer::redraw_unit(const unit& u) const
|
||||
bool hidden = u.get_hidden();
|
||||
bool is_flying = u.is_flying();
|
||||
map_location::direction facing = u.facing();
|
||||
int hitpoints = u.hitpoints();
|
||||
int max_hitpoints = u.max_hitpoints();
|
||||
|
||||
bool can_recruit = u.can_recruit();
|
||||
bool can_advance = u.can_advance();
|
||||
|
||||
int experience = u.experience();
|
||||
int max_experience = u.max_experience();
|
||||
|
||||
color_t hp_color=u.hp_color();
|
||||
color_t xp_color=u.xp_color();
|
||||
|
||||
const bool is_selected_hex = selected_or_reachable(loc);
|
||||
|
||||
// Override the filled area's color's alpha.
|
||||
hp_color.a = (loc == mouse_hex || is_selected_hex) ? 255u : float_to_color(0.8);
|
||||
xp_color.a = hp_color.a;
|
||||
|
||||
if(hidden || is_blindfolded || !u.is_visible_to_team(viewing_team_ref, show_everything)) {
|
||||
ac.clear_haloes();
|
||||
if(ac.anim_) {
|
||||
@ -321,7 +346,7 @@ void unit_drawer::redraw_unit(const unit& u) const
|
||||
// All the various overlay textures to draw with the HP/XP bars
|
||||
std::vector<texture> textures;
|
||||
|
||||
if(orb_img != nullptr) {
|
||||
if(orb_img) {
|
||||
textures.push_back(image::get_texture(*orb_img));
|
||||
}
|
||||
|
||||
@ -337,39 +362,44 @@ void unit_drawer::redraw_unit(const unit& u) const
|
||||
}
|
||||
};
|
||||
|
||||
const std::vector<std::string> overlays_abilities = u.overlays_abilities();
|
||||
for(const std::string& ov : overlays_abilities) {
|
||||
for(const std::string& ov : u.overlays_abilities()) {
|
||||
if(texture tex = image::get_texture(ov)) {
|
||||
textures.push_back(std::move(tex));
|
||||
}
|
||||
};
|
||||
|
||||
disp.drawing_buffer_add(drawing_layer::unit_bar, loc, [=,
|
||||
textures = std::move(textures),
|
||||
adj_y = adjusted_params.y,
|
||||
//origin = point{xsrc + xoff, ysrc + yoff + adjusted_params.y},
|
||||
bar_hp_height = static_cast<int>(max_hitpoints * u.hp_bar_scaling()),
|
||||
bar_xp_height = static_cast<int>(max_experience * u.xp_bar_scaling() / std::max<int>(u.level(), 1))
|
||||
](const rect& d) {
|
||||
const point origin { d.x + xoff, d.y + yoff + adj_y };
|
||||
const bool bar_focus = (loc == mouse_hex || is_selected_hex);
|
||||
std::vector<energy_bar> bars;
|
||||
|
||||
for(const texture& tex : textures) {
|
||||
draw::blit(tex, display::scaled_to_zoom({origin.x, origin.y, tex.w(), tex.h()}));
|
||||
}
|
||||
if(u.max_hitpoints() > 0) {
|
||||
bars.AGGREGATE_EMPLACE(
|
||||
energy_bar::get_height(u.max_hitpoints(), u.hp_bar_scaling()),
|
||||
energy_bar::get_filled(u.hitpoints(), u.max_hitpoints()),
|
||||
energy_bar::get_color(u.hp_color(), bar_focus)
|
||||
);
|
||||
}
|
||||
|
||||
if(max_hitpoints > 0) {
|
||||
// Offset slightly to make room for the XP bar
|
||||
const int hp_offset = static_cast<int>(-5 * display::get_zoom_factor());
|
||||
if(u.experience() > 0 && u.can_advance()) {
|
||||
bars.AGGREGATE_EMPLACE(
|
||||
energy_bar::get_height(u.max_experience(), u.xp_bar_scaling() / std::max(u.level(), 1)),
|
||||
energy_bar::get_filled(u.experience(), u.max_experience()),
|
||||
energy_bar::get_color(u.xp_color(), bar_focus)
|
||||
);
|
||||
}
|
||||
|
||||
double filled = static_cast<double>(hitpoints) / static_cast<double>(max_hitpoints);
|
||||
draw_bar(origin.x + hp_offset, origin.y, bar_hp_height, filled, hp_color);
|
||||
}
|
||||
disp.drawing_buffer_add(drawing_layer::unit_bar, loc,
|
||||
[textures = std::move(textures), bars = std::move(bars), shift = point{xoff, yoff + adjusted_params.y}](
|
||||
const rect& dest) {
|
||||
const rect shifted = dest.shifted_by(shift);
|
||||
|
||||
if(experience > 0 && can_advance) {
|
||||
double filled = static_cast<double>(experience) / static_cast<double>(max_experience);
|
||||
draw_bar(origin.x, origin.y, bar_xp_height, filled, xp_color);
|
||||
}
|
||||
});
|
||||
for(const texture& tex : textures) {
|
||||
draw::blit(tex, shifted);
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i < bars.size(); ++i) { // bar bar bar
|
||||
draw_bar(i, bars[i], shifted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth unit movements from terrain of different elevation.
|
||||
|
Loading…
x
Reference in New Issue
Block a user