refactor(item): decouple items from block types

The item system now supports an ItemKind and property, allowing item JSON to declare a type instead of assuming every item is a block. BlockManager was moved to its own header, redesigned around concurrent hash maps, and exposes id_from_name(). Texture and UI code now key items by ItemID, while block placement resolves the block type from the item registry.
This commit is contained in:
2026-08-04 16:14:00 +08:00
parent 2358cfd9a4
commit 0259ca71ce
28 changed files with 436 additions and 323 deletions

View File

@@ -105,4 +105,5 @@ target_sources(${PROJECT_NAME}
gameplay/systems/wander_ai_system.cpp
tools/json_utils.cpp
gameplay/item_manager.cpp
gameplay/block_manager.cpp
)

View File

@@ -2,6 +2,7 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/gameplay/item_manager.hpp"
#include "Cubed/localization.hpp"
#include "Cubed/tools/arg_parser.hpp"
@@ -74,6 +75,7 @@ void App::init(int argc, char** argv) {
Localization::instance().load_language(
m_game_config.get("language", default_value));
}
BlockManager::init();
ItemManager::instance().init();
m_window.init(m_argument);
m_window.imgui_init();
@@ -81,7 +83,7 @@ void App::init(int argc, char** argv) {
Logger::info("Window Init Success");
m_audio.init();
BlockManager::init();
m_renderer.init();
Logger::info("Renderer Init Success");
// MapTable::init_map();

View File

@@ -1,191 +0,0 @@
#include "Cubed/gameplay/block.hpp"
#include "Cubed/localization.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/toml.utils.hpp"
#include <filesystem>
namespace fs = std::filesystem;
using namespace std::string_literals;
using namespace Cubed::TOML;
namespace {
std::string block_data_dir = ASSETS_PATH + "data/block"s;
} // namespace
namespace Cubed {
const std::vector<BlockData>& BlockManager::datas() {
ASSERT(is_init);
return m_datas;
}
unsigned BlockManager::sums() {
ASSERT(is_init);
return m_datas.size();
}
unsigned BlockManager::cross_plane_sum() {
ASSERT(is_init);
return m_cross_plane_map.size();
}
const std::string& BlockManager::name_form_id(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].name;
}
return m_datas[id].name;
}
std::string BlockManager::local_name(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return tr(m_datas[0].name_key);
}
return tr(m_datas[id].name_key);
}
bool BlockManager::is_gas(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_gas;
}
return m_datas[id].is_gas;
}
bool BlockManager::is_liquid(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_liquid;
}
return m_datas[id].is_liquid;
}
bool BlockManager::is_cross_plane(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_cross_plane;
}
return m_datas[id].is_cross_plane;
}
bool BlockManager::is_transparent(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_transparent;
}
return m_datas[id].is_transparent;
}
bool BlockManager::is_passable(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_passable;
}
return m_datas[id].is_passable;
}
bool BlockManager::is_discard(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_discard;
}
return m_datas[id].is_discard;
}
bool BlockManager::is_blend(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_blend;
}
return m_datas[id].is_blend;
}
bool BlockManager::is_transitional(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].is_transitional;
}
return m_datas[id].is_transitional;
}
float BlockManager::roughness(BlockType id) {
if (id >= sums()) {
Logger::error("Id {}, is Over The Max Id", id, sums() - 1);
return m_datas[0].roughness;
}
return m_datas[id].roughness;
}
void BlockManager::init() {
fs::path data_path{block_data_dir};
for (auto entry : fs::recursive_directory_iterator(data_path)) {
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().filename() == "template.toml") {
continue;
}
toml::table block;
try {
block = toml::parse_file(entry.path().string());
} catch (const toml::parse_error& err) {
Logger::error("Load Block Data {} Fail, Parser Error {}",
entry.path().string(), err.what());
ASSERT(false);
}
auto id = block["id"].value<int>();
if (id == std::nullopt) {
Logger::error("Very Serious Error, Block Id Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto name = block["name"].value<std::string>();
if (name == std::nullopt) {
Logger::error("Very Serious Error, Block Name Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto is_liquid = safe_get_value(block, "is_liquid", false);
auto is_passable = safe_get_value(block, "is_passable", false);
auto is_cross_plane = safe_get_value(block, "is_cross_plane", false);
auto is_transparent = safe_get_value(block, "is_transparent", false);
auto is_gas = safe_get_value(block, "is_gas", false);
auto is_discard = safe_get_value(block, "is_discard", false);
auto is_blend = safe_get_value(block, "is_blend", false);
auto is_transitional = safe_get_value(block, "is_transitional", false);
auto roughness = safe_get_value(block, "roughness", 1.0);
auto name_key = safe_get_value(block, "name_key", "Unknow_key");
m_datas.emplace_back(*name, *name_key, *id, *is_liquid, *is_passable,
*is_cross_plane, *is_transparent, *is_gas,
*is_discard, *is_blend, *is_transitional,
static_cast<float>(*roughness));
}
std::sort(
m_datas.begin(), m_datas.end(),
[](const BlockData& a, const BlockData& b) { return a.id < b.id; });
set_up_cross_plane_map();
is_init = true;
}
BlockType BlockManager::cross_plane_index(BlockType id) {
auto it = m_cross_plane_map.find(id);
if (it == m_cross_plane_map.end()) {
Logger::error("Can't Find Cross Plane Id {}", id);
ASSERT(false);
throw std::out_of_range{"Can't Find Cross Plane Id" +
std::to_string(id)};
}
return it->second;
}
void BlockManager::set_up_cross_plane_map() {
unsigned cur_id = 0;
for (const auto& data : m_datas) {
if (data.is_cross_plane) {
m_cross_plane_map[data.id] = cur_id;
cur_id++;
}
}
}
} // namespace Cubed

View File

@@ -0,0 +1,211 @@
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/localization.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/toml.utils.hpp"
#include <filesystem>
namespace fs = std::filesystem;
using namespace std::string_literals;
using namespace Cubed::TOML;
namespace {
std::string block_data_dir = ASSETS_PATH + "data/block"s;
} // namespace
namespace Cubed {
unsigned BlockManager::sums() {
ASSERT(is_init);
return m_datas.size();
}
unsigned BlockManager::cross_plane_sum() {
ASSERT(is_init);
return m_cross_plane_map.size();
}
const std::string& BlockManager::name_form_id(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.name;
}
return c->second.name;
}
std::string BlockManager::local_name(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.name_key;
}
return tr(c->second.name_key);
}
bool BlockManager::is_gas(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_gas;
}
return c->second.is_gas;
}
bool BlockManager::is_liquid(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_liquid;
}
return c->second.is_liquid;
}
bool BlockManager::is_cross_plane(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_cross_plane;
}
return c->second.is_cross_plane;
}
bool BlockManager::is_transparent(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_transparent;
}
return c->second.is_transparent;
}
bool BlockManager::is_passable(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_passable;
}
return c->second.is_passable;
}
bool BlockManager::is_discard(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_discard;
}
return c->second.is_discard;
}
bool BlockManager::is_blend(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_blend;
}
return c->second.is_blend;
}
bool BlockManager::is_transitional(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.is_transitional;
}
return c->second.is_transitional;
}
float BlockManager::roughness(BlockType id) {
cacc c;
if (!m_datas.find(c, id)) {
ASSERT(false);
return EMPTY.roughness;
}
return c->second.roughness;
}
void BlockManager::init() {
fs::path data_path{block_data_dir};
for (auto entry : fs::recursive_directory_iterator(data_path)) {
if (!entry.is_regular_file()) {
continue;
}
if (entry.path().filename() == "template.toml") {
continue;
}
toml::table block;
try {
block = toml::parse_file(entry.path().string());
} catch (const toml::parse_error& err) {
Logger::error("Load Block Data {} Fail, Parser Error {}",
entry.path().string(), err.what());
ASSERT(false);
}
auto id = block["id"].value<int>();
if (id == std::nullopt) {
Logger::error("Very Serious Error, Block Id Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto name = block["name"].value<std::string>();
if (name == std::nullopt) {
Logger::error("Very Serious Error, Block Name Not Find !!!, Please "
"Check The Block Data Integrity");
std::abort();
}
auto is_liquid = safe_get_value(block, "is_liquid", false);
auto is_passable = safe_get_value(block, "is_passable", false);
auto is_cross_plane = safe_get_value(block, "is_cross_plane", false);
auto is_transparent = safe_get_value(block, "is_transparent", false);
auto is_gas = safe_get_value(block, "is_gas", false);
auto is_discard = safe_get_value(block, "is_discard", false);
auto is_blend = safe_get_value(block, "is_blend", false);
auto is_transitional = safe_get_value(block, "is_transitional", false);
auto roughness = safe_get_value(block, "roughness", 1.0);
auto name_key = safe_get_value(block, "name_key", "Unknow_key");
BlockData data{
*name, *name_key, static_cast<BlockType>(*id),
*is_liquid, *is_passable, *is_cross_plane,
*is_transparent, *is_gas, *is_discard,
*is_blend, *is_transitional, static_cast<float>(*roughness)};
if (!m_datas.emplace(static_cast<BlockType>(*id), std::move(data))) {
Logger::error("Block Type {} already exist!", *id);
}
}
set_up_cross_plane_map();
is_init = true;
}
BlockType BlockManager::cross_plane_index(BlockType id) {
CrossPlaneMap::const_accessor c;
if (!m_cross_plane_map.find(c, id)) {
Logger::error("Can't Find Cross Plane Id {}", id);
ASSERT(false);
throw std::out_of_range{"Can't Find Cross Plane Id" +
std::to_string(id)};
}
return c->second;
}
BlockType BlockManager::id_from_name(const std::string& name) {
IDMap::const_accessor c;
if (m_id_map.find(c, name)) {
return c->second;
}
Logger::error("BlockManager: Can't fin Block {}", name);
ASSERT(false);
return 0;
}
void BlockManager::set_up_cross_plane_map() {
unsigned cur_id = 0;
for (const auto& [id, data] : m_datas) {
if (data.is_cross_plane) {
m_cross_plane_map.emplace(data.id, cur_id);
cur_id++;
}
}
}
} // namespace Cubed

View File

@@ -1,5 +1,6 @@
#include "Cubed/gameplay/item_manager.hpp"
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
@@ -63,27 +64,41 @@ void ItemManager::add(const std::filesystem::path& path) {
if (doc.HasMember("texture")) {
data.path = doc["texture"].GetString();
}
if (doc.HasMember("type")) {
std::string type = doc["type"].GetString();
data.kind = get_item_kind(type);
}
acc a;
if (m_map.emplace(a, data.id, std::move(data))) {
if (!m_id_map.emplace(a->second.name, a->first)) {
Logger::error("ItemManager: Can't Insterd {} {} to id map",
a->second.name, a->first);
}
if (a->second.kind == ItemKind::BLOCK) {
BlockType b = BlockManager::id_from_name(a->second.name);
m_block_to_id_map.emplace(b, a->first);
}
} else {
Logger::error("ItemManager: Can't Insterd {} to map", path.string());
}
}
const ItemData& ItemManager::get_item_data(std::string_view key) const {
IDMap::const_accessor cacc;
ItemID id = 0;
if (m_id_map.find(cacc, key)) {
id = cacc->second;
} else {
Logger::error("Can't Find key {} in id map", key);
ASSERT(false);
return EMPTY;
{
IDMap::const_accessor cacc;
if (m_id_map.find(cacc, key)) {
id = cacc->second;
} else {
Logger::error("Can't Find key {} in id map", key);
ASSERT(false);
return EMPTY;
}
}
return get_item_data(id);
}
const ItemData& ItemManager::get_item_data(ItemID id) const {
@@ -102,5 +117,5 @@ const ItemData& ItemManager::get(std::string_view key) {
const ItemData& ItemManager::get(ItemID id) {
return instance().get_item_data(id);
}
ItemID ItemManager::size() { return instance().m_map.size(); }
} // namespace Cubed

View File

@@ -3,9 +3,10 @@
#include "Cubed/audio/audio_engine.hpp"
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/hitbox_manager.hpp"
#include "Cubed/gameplay/item_manager.hpp"
namespace Cubed {
namespace {
@@ -406,7 +407,10 @@ void LocalPlayer::place_block(float dt) {
}
}
if (m_mouse_state.right) {
auto type = m_hotbar[m_selected_hotbar].type;
auto& data = ItemManager::get(m_hotbar[m_selected_hotbar].id);
auto* t = std::get_if<BlockType>(&data.property);
ASSERT(t);
auto type = *t;
if (type != 0) {
glm::ivec3 near_pos = m_look_block->pos + m_look_block->normal;
if (!m_world.is_solid(near_pos)) {
@@ -683,7 +687,7 @@ void LocalPlayer::init(std::string_view name) {
});
for (int i = 0; i < 10; i++) {
m_hotbar[i].type = i;
m_hotbar[i].id = i;
}
}

View File

@@ -2,10 +2,11 @@
#include "Cubed/config.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/gameplay/item_manager.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/shader_tools.hpp"
namespace {
constexpr int BLOCK_SIZE = 16;
constexpr int BLOCK_NORMAL_SIZE = 128;
@@ -69,7 +70,7 @@ const Texture* TextureManager::get_pbr_texture() const {
return m_normal_texture_array.get();
}
const std::vector<std::unique_ptr<Texture>>&
const TextureManager::ItemTextureMap&
TextureManager::get_item_textures() const {
return m_item_textures;
}
@@ -121,23 +122,20 @@ void TextureManager::load_block_texture(unsigned id) {
}
}
void TextureManager::load_block_item_texture(unsigned id) {
void TextureManager::load_item_texture() {
for (ItemID i = 0; i < ItemManager::size(); ++i) {
auto& item = ItemManager::get(i);
auto data = Tools::load_image_data(item.path);
std::unique_ptr<Texture> texture =
std::make_unique<Texture>(TextureType::TEXTURE_2D);
texture->tex_image_2d(TextureFormat::RGBA8, TextureFormat::RGBA,
GL_UNSIGNED_BYTE, data.data, BLOCK_ITEM_SIZE,
BLOCK_ITEM_SIZE);
ASSERT_MSG(id < BlockManager::sums(), "Exceed the max block sum limit");
std::string name{BlockManager::name_form_id(id)};
texture->set_nearest();
std::string path = "texture/item/block/" + name + ".png";
auto data = Tools::load_image_data(path);
std::unique_ptr<Texture> texture =
std::make_unique<Texture>(TextureType::TEXTURE_2D);
texture->tex_image_2d(TextureFormat::RGBA8, TextureFormat::RGBA,
GL_UNSIGNED_BYTE, data.data, BLOCK_ITEM_SIZE,
BLOCK_ITEM_SIZE);
texture->set_nearest();
m_item_textures.push_back(std::move(texture));
m_item_textures.try_emplace(item.id, std::move(texture));
}
}
void TextureManager::load_cross_plane_texture(unsigned id) {
@@ -224,7 +222,7 @@ void TextureManager::init_block() {
BLOCK_NORMAL_SIZE, BLOCK_NORMAL_SIZE, BlockManager::sums() * 6);
for (unsigned i = 0; i < BlockManager::sums(); i++) {
load_block_texture(i);
load_block_item_texture(i);
load_pbr_texture(i);
}

View File

@@ -1,6 +1,7 @@
#include "Cubed/ui/inventory_ui.hpp"
#include "Cubed/app.hpp"
#include "Cubed/gameplay/block_manager.hpp"
#include "Cubed/scene/scene_manager.hpp"
#include "Cubed/scene/world_scene.hpp"
#include "Cubed/ui/column_layout.hpp"
@@ -20,21 +21,26 @@ void InventoryUI::init() {
auto& column = back->add_child<ColumnLayout>();
column.set_anchor(Anchor::CENTER);
column.set_child_anchor(ColumnLayoutAnchor::LEFT);
auto& block_textures = texture_manager.get_item_textures();
auto sum = block_textures.size();
auto& item_textures = texture_manager.get_item_textures();
auto sum = item_textures.size();
{
auto& row_layout = column.add_child<RowLayout>();
auto row = &row_layout;
for (size_t i = 1; i < sum; ++i) {
if ((i - 1) % 10 == 0) {
size_t i = 0;
for (auto& [id, texture] : item_textures) {
if (i % 10 == 0) {
auto& r = column.add_child<RowLayout>();
row = &r;
}
auto& slot = row->add_child<ItemSlot>();
slot.set_default_background(texture_manager);
slot.set_scale(5.0f);
slot.set_item(i, block_textures[i].get());
slot.set_item(id, texture.get());
m_slots.emplace_back(&slot);
++i;
}
for (size_t i = 1; i < sum; ++i) {
}
}
{
@@ -62,10 +68,12 @@ void InventoryUI::init() {
auto& item = row.add_child<ItemSlot>();
item.set_default_background(texture_manager);
item.set_scale(5.0f);
if (h.type == 0) {
if (h.id == 0) {
item.set_item(0, nullptr);
} else {
item.set_item(h.type, block_textures[h.type].get());
auto it = item_textures.find(h.id);
ASSERT(it != item_textures.end());
item.set_item(h.id, it->second.get());
}
m_hotbar.emplace_back(&item);
}
@@ -89,7 +97,7 @@ void InventoryUI::update(float dt) {
void InventoryUI::update_item_info() {
auto show_item_info = [this](ItemSlot* slot) {
if (slot && !m_selected_image->has_texture()) {
auto type = slot->block();
auto type = slot->id();
if (type != 0) {
m_item_info->set_text(BlockManager::local_name(type))
.set_visible(true);
@@ -117,16 +125,17 @@ bool InventoryUI::handle_mouse_button_event(const MouseButtonEvent& e) {
if (e.action == KeyAction::PRESS && e.key == MouseKey::LEFT_BUTTON) {
auto& texture_manager = m_scene.scene_manager().app().texture_manager();
auto& blocks = texture_manager.get_item_textures();
auto& item_textures = texture_manager.get_item_textures();
if (!m_selected_image->has_texture()) {
{
auto slot = get_hovered_slot();
if (slot) {
m_selected_block = slot->block();
if (m_selected_block != 0) {
m_selected_image->set_texture(
blocks[m_selected_block].get(), true);
m_selected_id = slot->id();
if (m_selected_id != 0) {
auto it = item_textures.find(m_selected_id);
ASSERT(it != item_textures.end());
m_selected_image->set_texture(it->second.get(), true);
m_selected_image->set_visible(true);
return true;
}
@@ -135,10 +144,11 @@ bool InventoryUI::handle_mouse_button_event(const MouseButtonEvent& e) {
{
auto [slot, pos] = get_hovered_hotbar_slot();
if (slot) {
m_selected_block = slot->block();
if (m_selected_block != 0) {
m_selected_image->set_texture(
blocks[m_selected_block].get(), true);
m_selected_id = slot->id();
if (m_selected_id != 0) {
auto it = item_textures.find(m_selected_id);
ASSERT(it != item_textures.end());
m_selected_image->set_texture(it->second.get(), true);
m_hotbar[pos]->set_item(0, nullptr);
auto& player = m_scene.client_world().get_player();
player.set_hotbar(pos, {0, 0});
@@ -154,10 +164,11 @@ bool InventoryUI::handle_mouse_button_event(const MouseButtonEvent& e) {
auto [slot, pos] = get_hovered_hotbar_slot();
if (slot) {
auto& player = m_scene.client_world().get_player();
player.set_hotbar(pos, {m_selected_block, 1});
if (m_selected_block != 0) {
m_hotbar[pos]->set_item(m_selected_block,
blocks[m_selected_block].get());
player.set_hotbar(pos, {m_selected_id, 1});
if (m_selected_id != 0) {
auto it = item_textures.find(m_selected_id);
ASSERT(it != item_textures.end());
m_hotbar[pos]->set_item(m_selected_id, it->second.get());
}
}
return true;

View File

@@ -19,9 +19,9 @@ ItemSlot& ItemSlot::set_scale(float scale) {
update_border();
return *this;
}
ItemSlot& ItemSlot::set_item(BlockType id, const Texture* texture) {
ItemSlot& ItemSlot::set_item(ItemID id, const Texture* texture) {
m_foreground->set_texture(texture, false);
m_block_type = id;
m_id = id;
return *this;
}
float ItemSlot::width() const {
@@ -77,6 +77,6 @@ bool ItemSlot::handle_mouse_move_event(const MouseMoveEvent& e) {
return Widget::handle_mouse_move_event(e);
}
BlockType ItemSlot::block() const { return m_block_type; }
ItemID ItemSlot::id() const { return m_id; }
bool ItemSlot::hovered() const { return m_hovered; }
} // namespace Cubed

View File

@@ -131,7 +131,7 @@ void WorldUIManager::update_hotbar() {
auto hotbar = player.get_hotbar();
size_t selected = player.selected_hotbar();
for (size_t i = 0; i < LocalPlayer::HOTBAR_SUM; ++i) {
auto type = hotbar[i].type;
auto type = hotbar[i].id;
if (selected == i) {
m_hotbar_slot[i]->set_border_visale(true);
} else {
@@ -140,7 +140,9 @@ void WorldUIManager::update_hotbar() {
if (type == 0) {
m_hotbar_slot[i]->set_item(0, nullptr);
} else {
m_hotbar_slot[i]->set_item(type, item_texture[type].get());
auto it = item_texture.find(type);
ASSERT(it != item_texture.end());
m_hotbar_slot[i]->set_item(type, it->second.get());
}
}
}