Initial commit

This commit is contained in:
2025-07-17 21:52:37 +03:00
commit 5d0e71c629
83 changed files with 4787 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
#include "Alarm.h"
namespace NGame {
void TAlarm::Set(TId id, std::uint32_t millis) {
auto& data = Alarms_[id];
data.Target = millis;
data.Current = 0;
}
void TAlarm::Unset(TId id) {
auto& data = Alarms_[id];
data.Target = 0;
data.Current = 0;
}
void TAlarm::Update(std::uint32_t delta) {
for (auto it = Alarms_.begin(); it != Alarms_.end(); ++it) {
auto& alarm = it->second;
if (alarm.Target) {
alarm.Current += delta;
}
if (alarm.Current >= alarm.Target) {
DueAlarms_.push(it->first);
}
}
}
bool TAlarm::Next(TId& id) {
bool repeat;
do {
repeat = false;
if (DueAlarms_.empty())
return false;
id = DueAlarms_.front();
DueAlarms_.pop();
auto& alarm = Alarms_[id];
if (alarm.Target) {
if (alarm.Current >= alarm.Target) {
alarm.Current -= alarm.Target;
}
if (alarm.Current >= alarm.Target) {
DueAlarms_.push(id);
}
} else {
alarm.Current = 0;
repeat = true;
}
} while (repeat);
return true;
}
} // namespace NGame
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Common.h"
#include <cstdint>
#include <queue>
#include <unordered_map>
namespace NGame {
class TAlarm {
public:
using TId = int;
void Set(TId id, std::uint32_t millis);
void Unset(TId id);
void Update(std::uint32_t delta);
bool Next(TId& id);
private:
struct TData {
std::uint32_t Target;
std::uint32_t Current;
};
std::unordered_map<TId, TData> Alarms_;
std::queue<TId> DueAlarms_;
};
}
+116
View File
@@ -0,0 +1,116 @@
#include "App.h"
#include <memory>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
namespace NGame {
TApp* TApp::Instance() {
static TApp* instance = nullptr;
if (!instance) {
instance = new TApp();
}
return instance;
}
TApp::TApp() {
FileManager_ = std::make_unique<TFileManager>("Data", "data.pak");
SurfaceManager_ = std::make_unique<TSurfaceManager>(*FileManager_);
RenderManager_ = std::make_unique<TRenderManager>(*SurfaceManager_, 320, 240, "SDL Game");
SpriteManager_ = std::make_unique<TSpriteManager>(*FileManager_, *RenderManager_);
EntityManager_ = std::make_unique<TEntityManager>(*RenderManager_, 1000 / 60);
FontManager_ = std::make_unique<TFontManager>(*SpriteManager_);
DigitManager_ = std::make_unique<TDigitManager>(*SpriteManager_);
}
TApp::~TApp() {
}
void TApp::Process() {
if (!RenderManager_->IsRunning()) {
#ifdef __EMSCRIPTEN__
emscripten_cancel_main_loop(); /* this should "kill" the app. */
#else
exit(0);
#endif
}
EntityManager_->Run();
RenderManager_->Run();
}
int TApp::Run() {
Process();
return 0;
}
TState& TApp::State() {
return State_;
}
const TState& TApp::State() const {
return State_;
}
TFileManager& TApp::FileManager() {
return *FileManager_;
}
TRenderManager& TApp::RenderManager() {
return *RenderManager_;
}
TEntityManager& TApp::EntityManager() {
return *EntityManager_;
}
TSurfaceManager& TApp::SurfaceManager() {
return *SurfaceManager_;
}
TSpriteManager& TApp::SpriteManager() {
return *SpriteManager_;
}
TFontManager& TApp::FontManager() {
return *FontManager_;
}
TDigitManager& TApp::DigitManager() {
return *DigitManager_;
}
const TFileManager& TApp::FileManager() const {
return *FileManager_;
}
const TRenderManager& TApp::RenderManager() const {
return *RenderManager_;
}
const TEntityManager& TApp::EntityManager() const {
return *EntityManager_;
}
const TSurfaceManager& TApp::SurfaceManager() const {
return *SurfaceManager_;
}
const TSpriteManager& TApp::SpriteManager() const {
return *SpriteManager_;
}
const TFontManager& TApp::FontManager() const {
return *FontManager_;
}
const TDigitManager& TApp::DigitManager() const {
return *DigitManager_;
}
} // namespace NGame
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "Common.h"
#include "DigitManager.h"
#include "EntityManager.h"
#include "FileManager.h"
#include "FontManager.h"
#include "RenderManager.h"
#include "SpriteManager.h"
#include "State.h"
#include "SurfaceManager.h"
#include <cstdint>
#include <memory>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL.h>
#include <unordered_map>
namespace NGame {
class TApp {
public:
static TApp* Instance();
int Run();
TState& State();
const TState& State() const;
TFileManager& FileManager();
TRenderManager& RenderManager();
TEntityManager& EntityManager();
TSurfaceManager& SurfaceManager();
TSpriteManager& SpriteManager();
TFontManager& FontManager();
TDigitManager& DigitManager();
const TFileManager& FileManager() const;
const TRenderManager& RenderManager() const;
const TEntityManager& EntityManager() const;
const TSurfaceManager& SurfaceManager() const;
const TSpriteManager& SpriteManager() const;
const TFontManager& FontManager() const;
const TDigitManager& DigitManager() const;
private:
TApp();
~TApp();
void Process();
private:
std::unique_ptr<TFileManager> FileManager_;
std::unique_ptr<TRenderManager> RenderManager_;
std::unique_ptr<TEntityManager> EntityManager_;
std::unique_ptr<TSurfaceManager> SurfaceManager_;
std::unique_ptr<TSpriteManager> SpriteManager_;
std::unique_ptr<TFontManager> FontManager_;
std::unique_ptr<TDigitManager> DigitManager_;
TState State_;
};
} // namespace NGame
+81
View File
@@ -0,0 +1,81 @@
#include "Common.h"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <string_view>
namespace NGame {
std::string ToUpper(const std::string_view& value) {
std::string result;
result.resize(value.size());
std::transform(value.begin(), value.end(), result.begin(), ::toupper);
return result;
}
std::string ToLower(const std::string_view& value) {
std::string result;
result.resize(value.size());
std::transform(value.begin(), value.end(), result.begin(), ::tolower);
return result;
}
std::string_view LeftTrim(const std::string_view& value) {
auto firstNonSpace = std::find_if(value.begin(), value.end(), [](char symbol) {
return !std::isspace(symbol);
});
auto length = std::distance(firstNonSpace, value.end());
return std::string_view(&*firstNonSpace, length);
}
std::string_view RightTrim(const std::string_view& value) {
auto lastNonSpace = std::find_if(value.rbegin(), value.rend(), [](char symbol) {
return !std::isspace(symbol);
});
auto index = value.size() - std::distance(value.rbegin(), lastNonSpace);
auto length = std::distance(value.begin(), value.begin() + index);
return std::string_view(&*value.begin(), length);
}
std::string_view Trim(const std::string_view& value) {
return LeftTrim(RightTrim(value));
}
std::string_view NextToken(std::string_view& value, const std::string_view& token) {
auto position = value.find(token);
auto result = value.substr(0, position);
if (position == std::string_view::npos) {
value = {};
} else {
value = value.substr(position + token.size());
}
return result;
}
bool RectContains(const Vec2i& position, const Vec2i& size, const Vec2i& point) {
if (point.X < position.X || point.X - position.X > size.X ||
point.Y < position.Y || point.Y - position.Y > size.Y) {
return false;
}
return true;
}
bool RectOverlaps(const Vec2i& positionA, const Vec2i& sizeA, const Vec2i& positionB, const Vec2i& sizeB) {
if (positionB.X + sizeB.X <= positionA.X ||
positionA.X + sizeA.X <= positionB.X ||
positionB.Y + sizeB.Y <= positionA.Y ||
positionA.Y + sizeA.Y <= positionB.Y) {
return false;
}
return true;
}
} // namespace NGame
+174
View File
@@ -0,0 +1,174 @@
#pragma once
#include <bitset>
#include <cmath>
#include <cstdint>
#include <string_view>
#include <string>
#include <type_traits>
#define GAME_UNUSED(x) (void)(x)
#define DELETE_COPY(NAME) \
NAME(const NAME&) = delete; \
NAME& operator=(const NAME&) = delete;
namespace NGame {
template<class... Ts>
struct TOverload : Ts... { using Ts::operator()...; };
template<class... Ts>
TOverload(Ts...) -> TOverload<Ts...>;
template<typename T>
struct Vec2 {
using TType = T;
Vec2<T>()
: X(), Y() {
}
Vec2<T>(T value)
: X(value), Y(value) {
}
Vec2<T>(T x, T y)
: X(x), Y(y) {
}
Vec2<T> operator+(const Vec2<T>& other) const {
Vec2<T> result(X + other.X, Y + other.Y);
return result;
}
Vec2<T>& operator+=(const Vec2<T>& other) {
X += other.X;
Y += other.Y;
return *this;
}
Vec2<T> operator-(const Vec2<T>& other) const {
Vec2<T> result(X - other.X, Y - other.Y);
return result;
}
Vec2<T>& operator-=(const Vec2<T>& other) {
X -= other.X;
Y -= other.Y;
return *this;
}
template<typename Q>
Vec2<T> operator*(const Vec2<Q>& other) const {
Vec2<T> result(X * other.X, Y * other.Y);
return result;
}
template<typename Q>
Vec2<T>& operator*=(const Vec2<Q>& other) {
X *= other.X;
Y *= other.Y;
return *this;
}
template<typename Q>
Vec2<T> operator*(const Q& other) const {
Vec2<T> result(X * other, Y * other);
return result;
}
template<typename Q>
Vec2<T>& operator*=(const Q& other) {
X *= other;
Y *= other;
return *this;
}
template<typename Q>
Vec2<T> operator/(const Vec2<Q>& other) const {
Vec2<T> result(X / other.X, Y / other.Y);
return result;
}
template<typename Q>
Vec2<T>& operator/=(const Vec2<Q>& other) {
X /= other.X;
Y /= other.Y;
return *this;
}
template<typename Q>
Vec2<T> operator/(const Q& other) const {
Vec2<T> result(X / other, Y / other);
return result;
}
template<typename Q>
Vec2<T>& operator/=(const Q& other) {
X /= other;
Y /= other;
return *this;
}
template<typename Q = T, typename = typename std::enable_if<std::is_floating_point<Q>::value>::type>
Q Length() const {
return std::sqrt(X * X + Y * Y);
}
template<typename Q = T, typename = typename std::enable_if<std::is_floating_point<Q>::value>::type>
Vec2<T> Normilize() const {
return *this / Length();
}
template<typename Q = T, typename = typename std::enable_if<!std::is_floating_point<Q>::value>::type>
float Length() const {
return std::sqrt(static_cast<float>(X * X + Y * Y));
}
bool operator==(const Vec2<T>& other) const {
return X == other.X && Y == other.Y;
}
bool operator!=(const Vec2<T>& other) const {
return X != other.X || Y != other.Y;
}
union {
struct {
T X;
T Y;
};
T Data[2];
};
};
using Vec2f = Vec2<float>;
using Vec2i = Vec2<std::int32_t>;
std::string ToUpper(const std::string_view& value);
std::string ToLower(const std::string_view& value);
std::string_view LeftTrim(const std::string_view& value);
std::string_view RightTrim(const std::string_view& value);
std::string_view Trim(const std::string_view& value);
std::string_view NextToken(std::string_view& value, const std::string_view& token);
bool RectContains(const Vec2i& position, const Vec2i& size, const Vec2i& point);
bool RectOverlaps(const Vec2i& positionA, const Vec2i& sizeA, const Vec2i& positionB, const Vec2i& sizeB);
} // namespace NGame
template<>
struct std::hash<NGame::Vec2i> {
std::size_t operator()(const NGame::Vec2i& key) const {
return ((static_cast<std::size_t>(key.X) * 73856093) ^
(static_cast<std::size_t>(key.Y) * 19349663));
}
};
template<>
struct std::hash<NGame::Vec2f> {
std::size_t operator()(const NGame::Vec2f& key) const {
return ((static_cast<std::size_t>(key.X) * 73856093) ^
(static_cast<std::size_t>(key.Y) * 19349663));
}
};
+36
View File
@@ -0,0 +1,36 @@
#include "DigitManager.h"
namespace NGame {
TDigitManager::TDigitManager(TSpriteManager& spriteManager)
: SpriteManager_(spriteManager) {
Digits_ = SpriteManager_.Get("Sprites/Digits.txt");
}
void TDigitManager::Draw(const Vec2i position, int value, int length, int point) {
Vec2i currentPosition = {position.X + length * GlyphSize.X, position.Y};
for (size_t remainder = length; remainder; --remainder, --point) {
if (value) {
auto digit = value % 10;
value /= 10;
if (point == 0) {
SpriteManager_.Draw(Digits_, digit + 11, currentPosition);
} else {
SpriteManager_.Draw(Digits_, digit, currentPosition);
}
} else {
if (point == 0) {
SpriteManager_.Draw(Digits_, 21, currentPosition);
} else {
SpriteManager_.Draw(Digits_, 10, currentPosition);
}
}
currentPosition.X -= GlyphSize.X;
}
}
} // namespace NGame
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Common.h"
#include "SpriteManager.h"
namespace NGame {
class TDigitManager {
public:
TDigitManager(TSpriteManager& spriteManager);
DELETE_COPY(TDigitManager)
void Draw(const Vec2i position, int value, int length, int point);
private:
TSpriteManager& SpriteManager_;
const Vec2i GlyphSize = {7, 12};
std::shared_ptr<TSpriteManager::TSprite> Digits_;
};
} // namespace NGame
+113
View File
@@ -0,0 +1,113 @@
#include "Entity.h"
namespace NGame {
TEntity::TEntity(TEntity::TId id)
: Id_(id) {
}
void TEntity::Tick(std::uint32_t delta) {
Alarm_.Update(delta);
TAlarm::TId alarmId;
while (Alarm_.Next(alarmId)) {
Alarm(alarmId);
}
Update(delta);
}
void TEntity::Input(SDL_Event* event) {
GAME_UNUSED(event);
}
void TEntity::Update(std::uint32_t delta) {
GAME_UNUSED(delta);
}
void TEntity::Alarm(TAlarm::TId id) {
GAME_UNUSED(id);
}
void TEntity::Draw() const {
}
void TEntity::SetPosition(Vec2i value) {
Position_ = value;
}
void TEntity::SetSize(Vec2i value) {
Size_ = value;
}
const Vec2i& TEntity::Position() const {
return Position_;
}
const Vec2i& TEntity::Size() const {
return Size_;
}
const Vec2i& TEntity::AckPosition() const {
return AckPosition_;
}
const Vec2i& TEntity::AckSize() const {
return AckSize_;
}
bool TEntity::HasChanges() const {
if (AckPosition_ != Position_ || AckSize_ != Size_)
return true;
return false;
}
void TEntity::AckChanges() {
AckPosition_ = Position_;
AckSize_ = Size_;
}
TEntity::TCollisionGroup TEntity::CollisionGroup() const {
return CollisionGroup_;
}
void TEntity::SetCollisionGroup(TCollisionGroup group) {
CollisionGroup_ = group;
}
float TEntity::MovementPerTick(std::uint32_t delta, float speed) {
return speed * (delta / 1000.0f);
}
Vec2f TEntity::MovementPerTick(std::uint32_t delta, Vec2f speed) {
return speed * (delta / 1000.0f);
}
std::pair<std::int32_t, float> TEntity::NextMovement(float speed, float fraction) {
std::pair<std::int32_t, float> result;
result.second = speed + fraction;
result.first = static_cast<std::int32_t>(result.second);
result.second -= result.first;
return result;
}
TEntity::TId TEntity::Id() const {
return Id_;
}
void TEntity::Remove() {
IsRemoved_ = true;
}
bool TEntity::IsRemoved() const {
return IsRemoved_;
}
bool TEntity::IsPersistent() const {
return IsPersistent_;
}
} // namespace NGame
+112
View File
@@ -0,0 +1,112 @@
#pragma once
#include "Common.h"
#include "Alarm.h"
#include "State.h"
#include <bitset>
#include <cstdint>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_render.h>
namespace NGame {
class TEntity {
public:
using TId = std::uint32_t;
using TCollisionGroup = std::bitset<32>;
TEntity(TEntity::TId id);
virtual ~TEntity() = default;
void Tick(std::uint32_t delta);
virtual void Input(SDL_Event* event);
virtual void Update(std::uint32_t delta);
virtual void Alarm(TAlarm::TId id);
virtual void Draw() const;
void SetPosition(Vec2i value);
void SetSize(Vec2i value);
const Vec2i& Position() const;
const Vec2i& Size() const;
const Vec2i& AckPosition() const;
const Vec2i& AckSize() const;
bool HasChanges() const;
void AckChanges();
TCollisionGroup CollisionGroup() const;
void SetCollisionGroup(TCollisionGroup group);
static float MovementPerTick(std::uint32_t delta, float speed);
static Vec2f MovementPerTick(std::uint32_t delta, Vec2f speed);
std::pair<Vec2i::TType, float> NextMovement(float speed, float fraction);
template<typename UnaryPredicate>
std::pair<Vec2f, std::pair<bool, bool>> MoveWithCondition(Vec2f speed, Vec2f fraction, UnaryPredicate p) {
auto distanceX = NextMovement(speed.X, fraction.X);
auto distanceY = NextMovement(speed.Y, fraction.Y);
auto directionFlags = std::make_pair(true, true);
Vec2f resultFraction(distanceX.second, distanceY.second);
Vec2i resultDistance(distanceX.first, distanceY.first);
Vec2i newPosition = Position() + resultDistance;
if (p(newPosition)) {
// There is no problem with the new position, we are done
SetPosition(newPosition);
} else {
// There might be some problem, move 1px at a time
Vec2i direction(1, 1);
if (resultDistance.X < 0) {
direction.X = -1;
}
if (resultDistance.Y < 0) {
direction.Y = -1;
}
resultDistance.X = abs(resultDistance.X);
resultDistance.Y = abs(resultDistance.Y);
for (Vec2i::TType i = 0; i < resultDistance.X; ++i) {
newPosition = Position() + Vec2i(direction.X, 0);
if (!p(newPosition)) {
resultFraction.X = 0;
directionFlags.first = false;
break;
}
SetPosition(newPosition);
}
for (Vec2i::TType i = 0; i < resultDistance.Y; ++i) {
newPosition = Position() + Vec2i(0, direction.Y);
if (!p(newPosition)) {
resultFraction.Y = 0;
directionFlags.second = false;
break;
}
SetPosition(newPosition);
}
}
return std::make_pair(resultFraction, directionFlags);
}
TEntity::TId Id() const;
void Remove();
bool IsRemoved() const;
bool IsPersistent() const;
protected:
TAlarm Alarm_;
private:
bool IsPersistent_ = false;
bool IsRemoved_ = false;
TEntity::TId Id_;
TCollisionGroup CollisionGroup_;
Vec2i Position_;
Vec2i Size_;
Vec2i AckPosition_;
Vec2i AckSize_;
};
} // namespace NGame
+144
View File
@@ -0,0 +1,144 @@
#include "EntityManager.h"
#include <algorithm>
#include <SDL2/SDL_timer.h>
namespace NGame {
TEntityManager::TEntityManager(TRenderManager& renderManager, std::size_t timestep)
: RenderManager_(renderManager), Timestep_(timestep) {
LastUpdateTick_ = SDL_GetTicks();
}
void TEntityManager::Run() {
Input();
Update();
Draw();
}
std::shared_ptr<TEntity> TEntityManager::Entity(TEntity::TId id) {
auto it = Entities_.find(id);
if (it != Entities_.end()) {
return it->second;
}
throw std::runtime_error("Could not find entity with id: " + std::to_string(id));
}
void TEntityManager::UpdateCollision(TEntity::TId id) {
auto entity = Entity(id);
UpdateCollision(entity);
}
void TEntityManager::UpdateCollision(std::shared_ptr<TEntity> entity) {
if (entity->HasChanges()) {
Gridmap_.Remove(entity->AckPosition(), entity->AckSize(), entity->Id());
Gridmap_.Add(entity->Position(), entity->Size(), entity->Id());
entity->AckChanges();
}
}
std::shared_ptr<TEntity> TEntityManager::MakeEntityByName(const std::string& name) {
if (EntityFactories_.find(name) == EntityFactories_.end()) {
throw std::runtime_error("can't create entity with name " + name);
}
auto entity = EntityFactories_[name](++IdCounter_);
Entities_[entity->Id()] = entity;
// Explicilty ack changes in entity colision boxes
Gridmap_.Add(entity->Position(), entity->Size(), entity->Id());
entity->AckChanges();
return entity;
}
bool TEntityManager::IsPlaceEmpty(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId) {
auto query = Gridmap_.Query(position, size);
return !std::any_of(query.begin(), query.end(), [&](auto id) {
auto entity = Entity(id);
if (id == ignoreId)
return false;
if (!(entity->CollisionGroup() & group).any())
return false;
return RectOverlaps(position, size, entity->Position(), entity->Size());
});
}
std::unordered_set<TEntity::TId> TEntityManager::CollisionList(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId) {
auto query = Gridmap_.Query(position, size);
for (auto it = query.begin(); it != query.end(); ) {
auto entity = Entity(*it);
if (entity->Id() == ignoreId || !(entity->CollisionGroup() & group).any()) {
it = query.erase(it);
} else {
if (RectOverlaps(position, size, entity->Position(), entity->Size())) {
++it;
} else {
it = query.erase(it);
}
}
}
return query;
}
void TEntityManager::Input() {
SDL_Event event;
while (RenderManager_.Input(&event)) {
std::for_each(Entities_.begin(), Entities_.end(), [&](auto& entity) {
entity.second->Input(&event);
});
}
}
void TEntityManager::Update() {
std::uint32_t currentTick = SDL_GetTicks();
std::size_t attempts = 5;
while (--attempts && (currentTick - LastUpdateTick_ > Timestep_)) {
LastUpdateTick_ += Timestep_;
std::queue<TEntity::TId> idForRemoval;
// Update each enitity
std::for_each(Entities_.begin(), Entities_.end(), [&](auto& pair) {
auto& entity = pair.second;
if (!entity->IsRemoved()) {
entity->Tick(Timestep_);
}
if (entity->IsRemoved()) {
// Explicitly remove collision
Gridmap_.Remove(entity->AckPosition(), entity->AckSize(), entity->Id());
idForRemoval.emplace(pair.first);
} else {
UpdateCollision(entity);
}
});
// Remove removed entities
while (!idForRemoval.empty()) {
Entities_.erase(idForRemoval.front());
idForRemoval.pop();
}
}
// Failsafe when we run out of attempts
if (!attempts) {
LastUpdateTick_ = currentTick;
}
}
void TEntityManager::Draw() {
std::for_each(Entities_.begin(), Entities_.end(), [&](auto& pair) {
auto& entity = pair.second;
entity->Draw();
});
}
} // namespace NGame
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include "Common.h"
#include "Entity.h"
#include "Gridmap.h"
#include "RenderManager.h"
namespace NGame {
class TEntityManager {
public:
TEntityManager(TRenderManager& renderManager, const std::size_t timestep = 1000 / 30);
DELETE_COPY(TEntityManager)
void Run();
std::shared_ptr<TEntity> Entity(TEntity::TId id);
void UpdateCollision(TEntity::TId id);
void UpdateCollision(std::shared_ptr<TEntity> entity);
template<typename T>
void RegisterEntity(const std::string& name) {
if (EntityFactories_.find(name) != EntityFactories_.end()) {
throw std::runtime_error("can't register duplicate entity with name " + name);
}
EntityFactories_[name] = &MakeEntityFactory<T>;
}
template<class T, class... Args>
std::shared_ptr<TEntity> MakeEntity(Args&&... args) {
auto entity = std::make_shared<T>(++IdCounter_, std::forward<Args>(args)...);
Entities_[entity->Id()] = entity;
// Explicilty ack changes in entity colision boxes
Gridmap_.Add(entity->Position(), entity->Size(), entity->Id());
entity->AckChanges();
return entity;
}
std::shared_ptr<TEntity> MakeEntityByName(const std::string& name);
bool IsPlaceEmpty(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId = {});
std::unordered_set<TEntity::TId> CollisionList(const Vec2i& position, const Vec2i& size, TEntity::TCollisionGroup group, TEntity::TId ignoreId = {});
private:
using TFactoryMethod = std::shared_ptr<TEntity>(*)(TEntity::TId);
void Input();
void Update();
void Draw();
template<typename T>
static std::shared_ptr<TEntity> MakeEntityFactory(TEntity::TId id) {
return std::make_shared<T>(id);
}
private:
TRenderManager& RenderManager_;
std::unordered_map<TEntity::TId, std::shared_ptr<TEntity>> Entities_;
std::unordered_map<std::string, TFactoryMethod> EntityFactories_;
std::uint32_t LastUpdateTick_;
TEntity::TId IdCounter_;
std::size_t Timestep_;
TGridmap Gridmap_;
};
} // namespace NGame
+99
View File
@@ -0,0 +1,99 @@
#include "FileManager.h"
#include <cstring>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <utility>
namespace NGame {
TFileManager::TFileManager(const std::string& dataDirectory, const std::string& archivePath, std::size_t cacheSize)
: DataDirectory_(dataDirectory), ArchivePath_(archivePath), Cache_(cacheSize) {
}
std::string TFileManager::Get(const std::string& path) {
if (Cache_.Contains(path)) {
return Cache_.Get(path);
}
auto getAttempt = FromFilesystem(path);
if (getAttempt.second) {
Cache_.Set(path, getAttempt.first);
return getAttempt.first;
}
getAttempt = FromArchive(path);
if (getAttempt.second) {
Cache_.Set(path, getAttempt.first);
return getAttempt.first;
}
throw std::runtime_error("can't find file " + path);
}
std::pair<std::string, bool> TFileManager::FromFilesystem(const std::string& path) {
auto finalPath = DataDirectory_ + "/" + path;
std::ifstream fileStream(finalPath, std::ios::in | std::ios::binary);
if (!fileStream.is_open()) {
return std::make_pair<std::string, bool>({}, false);
}
std::stringstream fileStringStream;
fileStringStream << fileStream.rdbuf();
return std::make_pair(fileStringStream.str(), true);
}
std::pair<std::string, bool> TFileManager::FromArchive(const std::string& path) {
std::ifstream archiveStream(ArchivePath_, std::ios::in | std::ios::binary);
if (!archiveStream.is_open()) {
return std::make_pair<std::string, bool>({}, false);
}
// Identify and validate PAK file
char identifier[4];
archiveStream.read(identifier, 4);
if (std::memcmp(identifier, "PACK", 4)) {
return std::make_pair<std::string, bool>({}, false);
}
std::uint32_t tocOffset = ReadInt32(archiveStream);
std::uint32_t tocSize = ReadInt32(archiveStream) / 64;
if (!tocSize || tocOffset < 12) {
return std::make_pair<std::string, bool>({}, false);
}
// Find requested file
archiveStream.seekg(tocOffset);
for (std::uint32_t index = 0; index < tocSize; ++index) {
char name[56];
archiveStream.read(name, 56);
std::uint32_t fileOffset = ReadInt32(archiveStream);
std::uint32_t fileSize = ReadInt32(archiveStream);
if (std::strncmp(name, path.c_str(), 56)) {
continue;
}
archiveStream.seekg(fileOffset);
std::string result(fileSize, 0);
archiveStream.read(result.data(), result.size());
return std::make_pair(result, true);
}
return std::make_pair<std::string, bool>({}, false);
}
std::uint32_t TFileManager::ReadInt32(std::ifstream& stream) {
unsigned char bytes[4];
stream.read(reinterpret_cast<char*>(bytes), 4);
return (bytes[0] << 0) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
} // namespace NGame
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "Common.h"
#include "LRU.h"
namespace NGame {
class TFileManager {
public:
TFileManager(const std::string& dataDirectory, const std::string& archivePath, std::size_t cacheSize = 4);
DELETE_COPY(TFileManager)
std::string Get(const std::string& path);
private:
std::pair<std::string, bool> FromFilesystem(const std::string& path);
std::pair<std::string, bool> FromArchive(const std::string& path);
std::uint32_t ReadInt32(std::ifstream& stream);
private:
const std::string DataDirectory_;
const std::string ArchivePath_;
TLRU<std::string, std::string> Cache_;
};
} // namespace NGame
+35
View File
@@ -0,0 +1,35 @@
#include "FontManager.h"
#include <iostream>
namespace NGame {
TFontManager::TFontManager(TSpriteManager& spriteManager)
: SpriteManager_(spriteManager) {
Fonts_[White] = SpriteManager_.Get("Fonts/White.txt");
Fonts_[Gray] = SpriteManager_.Get("Fonts/Gray.txt");
Fonts_[Red] = SpriteManager_.Get("Fonts/Red.txt");
Fonts_[Green] = SpriteManager_.Get("Fonts/Green.txt");
Fonts_[Blue] = SpriteManager_.Get("Fonts/Blue.txt");
Fonts_[Purple] = SpriteManager_.Get("Fonts/Purple.txt");
Fonts_[Gold] = SpriteManager_.Get("Fonts/Gold.txt");
Fonts_[Swamp] = SpriteManager_.Get("Fonts/Swamp.txt");
Fonts_[LightBlue] = SpriteManager_.Get("Fonts/LightBlue.txt");
}
void TFontManager::Draw(EColor color, const Vec2i position, const std::string& text) {
Vec2i currentPosition = position;
for (const auto& symbol : text) {
if (symbol >= ' ' && symbol <= '~') {
SpriteManager_.Draw(Fonts_[color], (unsigned char)symbol - ' ', currentPosition);
currentPosition += Vec2i(GlyphSize.X, 0);
} else if (symbol == '\n') {
currentPosition = Vec2i(position.X, currentPosition.Y + GlyphSize.Y);
} else if (symbol == '\t') {
currentPosition += Vec2i(GlyphSize.X * 4, 0);
}
}
}
} // namespace NGame
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "Common.h"
#include "SpriteManager.h"
namespace NGame {
class TFontManager {
public:
enum EColor {
White,
Gray,
Red,
Green,
Blue,
Purple,
Gold,
Swamp,
LightBlue,
SentinelMax
};
TFontManager(TSpriteManager& spriteManager);
DELETE_COPY(TFontManager)
void Draw(EColor color, const Vec2i position, const std::string& text);
private:
TSpriteManager& SpriteManager_;
const Vec2i GlyphSize = {5, 12};
std::array<std::shared_ptr<TSpriteManager::TSprite>, SentinelMax> Fonts_;
};
} // namespace NGame
+58
View File
@@ -0,0 +1,58 @@
#include "Gridmap.h"
#include <algorithm>
#include <unordered_set>
namespace NGame {
std::unordered_set<TEntity::TId> TGridmap::Query(const Vec2i& position, const Vec2i& size) const {
Vec2i cellPosition = position / CellSize_;
Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_);
std::unordered_set<TEntity::TId> result;
for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) {
for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) {
TId gridId = GridId(Vec2i(i, j));
const auto& cell = Grid_[gridId];
for (auto id : cell) {
result.insert(id);
}
}
}
return result;
}
void TGridmap::Remove(const Vec2i& position, const Vec2i& size, TEntity::TId id) {
Vec2i cellPosition = position / CellSize_;
Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_);
std::unordered_set<TEntity::TId> result;
for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) {
for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) {
TId gridId = GridId(Vec2i(i, j));
auto& cell = Grid_[gridId];
auto it = std::remove(cell.begin(), cell.end(), id);
cell.erase(it, cell.end());
}
}
}
void TGridmap::Add(const Vec2i& position, const Vec2i& size, TEntity::TId id) {
Vec2i cellPosition = position / CellSize_;
Vec2i cellEnd = cellPosition + ((size + (CellSize_ - 1)) / CellSize_);
std::unordered_set<TEntity::TId> result;
for (Vec2i::TType i = cellPosition.X; i <= cellEnd.X; ++i) {
for (Vec2i::TType j = cellPosition.Y; j <= cellEnd.Y; ++j) {
TId gridId = GridId(Vec2i(i, j));
Grid_[gridId].push_back(id);
}
}
}
TGridmap::TId TGridmap::GridId(const Vec2i& point) const {
return std::hash<Vec2i>{}(point) % Grid_.size();
}
} // namespace NGame
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "Common.h"
#include "Entity.h"
#include <array>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace NGame {
class TGridmap {
public:
std::unordered_set<TEntity::TId> Query(const Vec2i& position, const Vec2i& size) const;
void Remove(const Vec2i& position, const Vec2i& size, TEntity::TId id);
void Add(const Vec2i& position, const Vec2i& size, TEntity::TId id);
private:
using TId = size_t;
TId GridId(const Vec2i& point) const;
private:
std::array<std::vector<TEntity::TId>, 32768> Grid_;
const std::uint32_t CellSize_ = 128;
};
} // namespace NGame
+80
View File
@@ -0,0 +1,80 @@
#pragma once
#include <iostream>
#include <list>
#include <stdexcept>
#include <unordered_map>
namespace NGame {
template<typename Key, typename Value>
class TLRU {
public:
using TPair = std::pair<Key, Value>;
using TKey = Key;
using TValue = Value;
TLRU(std::size_t capacity)
: Capacity_(capacity) {
}
const Value& Get(const Key& key) const {
auto it = Map_.find(key);
if (it == Map_.end()) {
throw std::runtime_error("lru cache miss");
}
Cache_.splice(Cache_.begin(), Cache_, it->second);
return it->second->second;
}
void Set(const Key& key, const Value& value) {
Set(key, value, [](auto& value){});
}
template<typename UnaryPredicate>
void Set(const Key& key, const Value& value, UnaryPredicate p) {
auto it = Map_.find(key);
if (it != Map_.end()) {
p(*it->second);
Cache_.erase(it->second);
Map_.erase(it);
}
Cache_.push_front(std::make_pair(key, value));
Map_[key] = Cache_.begin();
while (Map_.size() > Capacity_) {
auto lastElement = --(Cache_.end());
p(*lastElement);
Map_.erase(lastElement->first);
Cache_.erase(lastElement);
}
}
bool Contains(const Key& key) const {
return Map_.find(key) != Map_.end();
}
std::size_t Size() const {
return Map_.size();
}
std::size_t Capacity() const {
return Capacity_;
}
void Clear() {
Cache_.clear();
Map_.clear();
}
private:
using TIterator = typename std::list<TPair>::iterator;
mutable std::list<TPair> Cache_;
mutable std::unordered_map<Key, TIterator> Map_;
std::size_t Capacity_;
};
} // namespace NGame
+65
View File
@@ -0,0 +1,65 @@
#include "App.h"
#include <cmath>
#include "RoomEntity.h"
#include <iostream>
#include <algorithm>
#include <cstring>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
static NGame::TApp *app = nullptr;
static void InitApp(void) {
app = NGame::TApp::Instance();
app->EntityManager().RegisterEntity<THero>("Hero");
app->EntityManager().RegisterEntity<TDirtEntity>("DirtEntity");
app->EntityManager().RegisterEntity<TGrassEntity>("GrassEntity");
app->EntityManager().RegisterEntity<TPlankEntity>("PlankEntity");
app->EntityManager().RegisterEntity<TStoneEntity>("StoneEntity");
app->EntityManager().RegisterEntity<TLadderEntity>("LadderEntity");
app->EntityManager().RegisterEntity<TExplosionEntity>("ExplosionEntity");
app->EntityManager().RegisterEntity<TKeyEntity>("KeyEntity");
app->EntityManager().RegisterEntity<TMineEntity>("MineEntity");
app->EntityManager().RegisterEntity<TCurseEntity>("CurseEntity");
app->EntityManager().RegisterEntity<TSpikeEntity>("SpikeEntity");
app->EntityManager().RegisterEntity<TEntranceEntity>("EntranceEntity");
app->EntityManager().RegisterEntity<TExitEntity>("ExitEntity");
app->EntityManager().RegisterEntity<TFloatingTextEntity>("FloatingTextEntity");
app->EntityManager().RegisterEntity<TBackgroundTiler>("BackgroundTiler");
app->EntityManager().RegisterEntity<TRoomEntity>("RoomEntity");
app->EntityManager().MakeEntityByName("RoomEntity");
app->EntityManager().MakeEntityByName("BackgroundTiler");
auto h = app->EntityManager().MakeEntityByName("Hero");
h->SetPosition({0, -64});
app->EntityManager().UpdateCollision(h);
}
static void MainLoop(void) {
if (app) {
app->Run();
} else {
InitApp();
}
}
int main(int argc, char** argv)
{
SDL_Init(SDL_INIT_VIDEO);
srand(time(NULL));
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(MainLoop, -1, true);
#else
while (true) { MainLoop(); }
#endif
return 0;
}
+348
View File
@@ -0,0 +1,348 @@
#include "RenderManager.h"
#include <SDL2/SDL_blendmode.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_render.h>
#include <stdexcept>
namespace NGame {
TRenderManager::TRenderManager(TSurfaceManager& surfaceManager, int width, int height, const std::string& title, std::size_t cacheSize)
: SurfaceManager_(surfaceManager), Cache_(cacheSize), Size_(width, height) {
Window_ = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_RESIZABLE);
if (!Window_) {
throw std::runtime_error("Can't create window");
}
Renderer_ = SDL_CreateRenderer(Window_, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!Renderer_) {
SDL_DestroyWindow(Window_);
throw std::runtime_error("Can't create renderer");
}
ScreenTexture_ = SDL_CreateTexture(Renderer_, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height);
if (!ScreenTexture_) {
SDL_DestroyRenderer(Renderer_);
SDL_DestroyWindow(Window_);
throw std::runtime_error("Can't create screen texture");
}
LightTexture_ = SDL_CreateTexture(Renderer_, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height);
if (!LightTexture_) {
SDL_DestroyTexture(ScreenTexture_);
SDL_DestroyRenderer(Renderer_);
SDL_DestroyWindow(Window_);
throw std::runtime_error("Can't create light texture");
}
SDL_SetTextureBlendMode(LightTexture_, SDL_BLENDMODE_MOD);
// Make window bigger
if (width < 640 && height < 480) {
SDL_SetWindowSize(Window_, 640, 480);
}
Reset();
}
std::shared_ptr<SDL_Texture> TRenderManager::Get(const std::string& path) {
if (Cache_.Contains(path)) {
return Cache_.Get(path);
}
std::shared_ptr<SDL_Surface> surface = SurfaceManager_.Get(path);
SDL_Texture* texture = SDL_CreateTextureFromSurface(Renderer_, surface.get());
if (!texture) {
std::runtime_error("Can't create texture from surface " + path);
}
std::shared_ptr<SDL_Texture> result(texture, [](SDL_Texture* texture) { SDL_DestroyTexture(texture); });
Cache_.Set(path, result);
return result;
}
void TRenderManager::SetLayer(ELayer layer) {
Layer_ = layer;
}
void TRenderManager::SetColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha) {
Commands_[Layer_].push_back(TColorCommand{red, green, blue, alpha});
}
void TRenderManager::SetTexture(const std::shared_ptr<SDL_Texture>& texture) {
if (ActiveTextures_[Layer_] != texture) {
Commands_[Layer_].push_back(TTextureCommand{texture});
ActiveTextures_[Layer_] = texture;
}
}
void TRenderManager::DrawRect(const Vec2i& position, const Vec2i& size, bool filled) {
Commands_[Layer_].push_back(TRectCommand{position, size, filled});
}
void TRenderManager::DrawLine(const Vec2i& start, const Vec2i& end) {
Commands_[Layer_].push_back(TLineCommand{start, end});
}
void TRenderManager::DrawSprite(const Vec2i& texturePosition, const Vec2i& textureSize, const Vec2i& targetPosition, const Vec2i& targetSize, SDL_RendererFlip flip) {
if (!ActiveTextures_[Layer_]) {
throw std::runtime_error("layer has not active texture");
}
Commands_[Layer_].push_back(TSpriteCommand{texturePosition, textureSize, targetPosition, targetSize, flip});
}
void TRenderManager::DrawGeometry(const SDL_Vertex* vertices, std::size_t verticesCount, const int* indices, std::size_t indicesCount) {
if (!ActiveTextures_[Layer_]) {
throw std::runtime_error("layer has not active texture");
}
Commands_[Layer_].push_back(TGeometryCommand{vertices, verticesCount, indices, indicesCount});
}
void TRenderManager::SetCamera(const Vec2i& position) {
Camera_ = position;
}
const Vec2i& TRenderManager::Camera() const {
return Camera_;
}
const Vec2i& TRenderManager::Size() const {
return Size_;
}
bool TRenderManager::IsLightEnabled() const {
return LightEnabled_;
}
void TRenderManager::EnableLight(bool value) {
LightEnabled_ = value;
}
void TRenderManager::SetDefaultLightColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue) {
DefaultLightColor_ = {red, green, blue, 255};
}
std::uint8_t TRenderManager::DefaultLightRed() const {
return DefaultLightColor_.Red;
}
std::uint8_t TRenderManager::DefaultLightGreen() const {
return DefaultLightColor_.Green;
}
std::uint8_t TRenderManager::DefaultLightBlue() const {
return DefaultLightColor_.Blue;
}
void TRenderManager::Run() {
if (!IsRunning()) {
return;
}
for (size_t layer = 0; layer < SentinelMax; ++layer) {
switch (layer) {
case ELayer::BackgroundGlow:
case ELayer::ForegroundGlow:
case ELayer::EffectsGlow:
if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_ADD)) {
std::runtime_error("can't set blendmode");
}
if (SDL_SetRenderTarget(Renderer_, ScreenTexture_)) {
std::runtime_error("can't set render target");
}
break;
case ELayer::Light:
if (!IsLightEnabled()) {
continue;
}
if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_BLEND)) {
std::runtime_error("can't set blendmode");
}
if (SDL_SetRenderTarget(Renderer_, LightTexture_)) {
std::runtime_error("can't set render target");
}
break;
default:
if (SDL_SetRenderDrawBlendMode(Renderer_, SDL_BlendMode::SDL_BLENDMODE_BLEND)) {
std::runtime_error("can't set blendmode");
}
if (SDL_SetRenderTarget(Renderer_, ScreenTexture_)) {
std::runtime_error("can't set render target");
}
break;
}
SDL_Texture* activeTexture = nullptr;
for (const auto& command : Commands_[layer]) {
auto drawOperation = TOverload {
[&](const TColorCommand& value) {
SDL_SetRenderDrawColor(Renderer_, value.Red, value.Green, value.Blue, value.Alpha);
},
[&](const TRectCommand& value) {
SDL_Rect rect;
rect.x = value.Position.X;
rect.y = value.Position.Y;
rect.w = value.Size.X;
rect.h = value.Size.Y;
if (layer != ELayer::Interface) {
rect.x -= Camera_.X;
rect.y -= Camera_.Y;
}
if (value.ShouldFill) {
SDL_RenderFillRect(Renderer_, &rect);
} else {
SDL_RenderDrawRect(Renderer_, &rect);
}
},
[&](const TLineCommand& value) {
if (layer != ELayer::Interface) {
SDL_RenderDrawLine(Renderer_, value.Start.X - Camera_.X, value.Start.Y - Camera_.Y, value.End.X - Camera_.X, value.End.Y - Camera_.Y);
} else {
SDL_RenderDrawLine(Renderer_, value.Start.X, value.Start.Y, value.End.X, value.End.Y);
}
},
[&](const TTextureCommand& value) {
activeTexture = value.Texture.get();
switch (layer) {
case ELayer::BackgroundGlow:
case ELayer::ForegroundGlow:
case ELayer::EffectsGlow:
if (SDL_SetTextureBlendMode(activeTexture, SDL_BlendMode::SDL_BLENDMODE_ADD)) {
std::runtime_error("can't set texture blend mode");
}
break;
default:
if (SDL_SetTextureBlendMode(activeTexture, SDL_BlendMode::SDL_BLENDMODE_BLEND)) {
std::runtime_error("can't set texture blend mode");
}
break;
}
},
[&](const TSpriteCommand& value) {
SDL_Rect sourceRect;
sourceRect.x = value.TexturePosition.X;
sourceRect.y = value.TexturePosition.Y;
sourceRect.w = value.TextureSize.X;
sourceRect.h = value.TextureSize.Y;
SDL_Rect destinationRect;
destinationRect.x = value.TargetPosition.X;
destinationRect.y = value.TargetPosition.Y;
destinationRect.w = value.TargetSize.X;
destinationRect.h = value.TargetSize.Y;
if (layer != ELayer::Interface) {
destinationRect.x -= Camera_.X;
destinationRect.y -= Camera_.Y;
}
SDL_RenderCopyEx(Renderer_, activeTexture, &sourceRect, &destinationRect, 0, NULL, value.Flip);
},
[&](const TGeometryCommand& value) {
if (layer == ELayer::Interface) {
SDL_RenderGeometry(Renderer_, activeTexture, value.Vertices, value.VerticesCount, value.Indices, value.IndicesCount);
} else {
VerticesBuffer_.clear();
for (std::size_t i = 0; i < value.VerticesCount; ++i) {
SDL_Vertex convertedVertex = value.Vertices[i];
convertedVertex.position.x -= Camera_.X;
convertedVertex.position.y -= Camera_.Y;
VerticesBuffer_.push_back(convertedVertex);
}
SDL_RenderGeometry(Renderer_, activeTexture, VerticesBuffer_.data(), VerticesBuffer_.size(), value.Indices, value.IndicesCount);
}
}
};
std::visit(drawOperation, command);
}
// Apply light
if (layer == ELayer::Light) {
SDL_SetRenderTarget(Renderer_, ScreenTexture_);
SDL_RenderCopy(Renderer_, LightTexture_, NULL, NULL);
}
}
// Keep logical resolution the same
int windowWidth, windowHeight;
SDL_GetWindowSize(Window_, &windowWidth, &windowHeight);
Vec2f scaleFactors = {float(windowWidth) / Size_.X, float(windowHeight) / Size_.Y};
float targetFactor = std::min(scaleFactors.X, scaleFactors.Y);
SDL_Rect destinationRect;
destinationRect.w = targetFactor * Size_.X;
destinationRect.h = targetFactor * Size_.Y;
destinationRect.x = (windowWidth - destinationRect.w) / 2;
destinationRect.y = (windowHeight - destinationRect.h) / 2;
SDL_SetRenderTarget(Renderer_, NULL);
SDL_RenderCopy(Renderer_, ScreenTexture_, NULL, &destinationRect);
SDL_RenderPresent(Renderer_);
Reset();
}
bool TRenderManager::Input(SDL_Event* event) {
bool result = SDL_PollEvent(event);
if (result && event->type == SDL_QUIT) {
Shutdown();
}
return result;
}
bool TRenderManager::IsRunning() {
return Window_ != nullptr;
}
void TRenderManager::Shutdown() {
if (IsRunning()) {
Reset();
SDL_DestroyTexture(LightTexture_);
SDL_DestroyTexture(ScreenTexture_);
SDL_DestroyRenderer(Renderer_);
SDL_DestroyWindow(Window_);
ScreenTexture_ = nullptr;
Renderer_ = nullptr;
Window_ = nullptr;
}
}
void TRenderManager::Reset() {
Layer_ = Tile;
ActiveTextures_.fill(nullptr);
for (auto& queue : Commands_) {
queue.clear();
}
SDL_Rect a;
SDL_RenderGetViewport(Renderer_, &a);
SDL_SetRenderDrawColor(Renderer_, 0, 0, 0, 0);
SDL_SetRenderTarget(Renderer_, NULL);
SDL_RenderClear(Renderer_);
SDL_SetRenderDrawColor(Renderer_, 0, 0, 0, 0);
SDL_SetRenderTarget(Renderer_, ScreenTexture_);
SDL_RenderClear(Renderer_);
if (IsLightEnabled()) {
SDL_SetRenderDrawColor(Renderer_, DefaultLightColor_.Red, DefaultLightColor_.Green, DefaultLightColor_.Blue, DefaultLightColor_.Alpha);
SDL_SetRenderTarget(Renderer_, LightTexture_);
SDL_RenderClear(Renderer_);
}
}
} // namespace NGame
+119
View File
@@ -0,0 +1,119 @@
#pragma once
#include "Common.h"
#include "LRU.h"
#include "SurfaceManager.h"
#include <array>
#include <memory>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_render.h>
#include <variant>
#include <vector>
namespace NGame {
class TRenderManager {
public:
enum ELayer {
Tile,
Background,
BackgroundGlow,
Foreground,
ForegroundGlow,
Effects,
EffectsGlow,
Light,
Interface,
SentinelMax
};
struct TColorCommand {
std::uint8_t Red;
std::uint8_t Green;
std::uint8_t Blue;
std::uint8_t Alpha;
};
struct TRectCommand {
Vec2i Position;
Vec2i Size;
bool ShouldFill;
};
struct TLineCommand {
Vec2i Start;
Vec2i End;
};
struct TTextureCommand {
std::shared_ptr<SDL_Texture> Texture;
};
struct TSpriteCommand {
Vec2i TexturePosition;
Vec2i TextureSize;
Vec2i TargetPosition;
Vec2i TargetSize;
SDL_RendererFlip Flip;
};
struct TGeometryCommand {
const SDL_Vertex* Vertices;
std::size_t VerticesCount;
const int* Indices;
std::size_t IndicesCount;
};
using TCommand = std::variant<TColorCommand, TRectCommand, TLineCommand, TTextureCommand, TSpriteCommand, TGeometryCommand>;
TRenderManager(TSurfaceManager& surfaceManager, int width = 640, int height = 480, const std::string& title = "Game", std::size_t cacheSize = 4);
DELETE_COPY(TRenderManager)
std::shared_ptr<SDL_Texture> Get(const std::string& path);
void SetLayer(ELayer layer);
void SetColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t alpha = 255);
void SetTexture(const std::shared_ptr<SDL_Texture>& texture);
void DrawRect(const Vec2i& position, const Vec2i& size, bool filled = false);
void DrawLine(const Vec2i& start, const Vec2i& end);
void DrawSprite(const Vec2i& texturePosition, const Vec2i& textureSize, const Vec2i& targetPosition, const Vec2i& targetSize, SDL_RendererFlip flip = SDL_FLIP_NONE);
void DrawGeometry(const SDL_Vertex* vertices, std::size_t verticesCount, const int* indices, std::size_t indicesCount);
void SetCamera(const Vec2i& position);
const Vec2i& Camera() const;
const Vec2i& Size() const;
bool IsLightEnabled() const;
void EnableLight(bool value);
void SetDefaultLightColor(std::uint8_t red, std::uint8_t green, std::uint8_t blue);
std::uint8_t DefaultLightRed() const;
std::uint8_t DefaultLightGreen() const;
std::uint8_t DefaultLightBlue() const;
void Run();
bool Input(SDL_Event* event);
bool IsRunning();
void Shutdown();
private:
void Reset();
private:
SDL_Window* Window_;
SDL_Renderer* Renderer_;
TSurfaceManager& SurfaceManager_;
TLRU<std::string, std::shared_ptr<SDL_Texture>> Cache_;
std::array<std::vector<TCommand>, SentinelMax> Commands_;
std::array<std::shared_ptr<SDL_Texture>, SentinelMax> ActiveTextures_;
SDL_Texture* ScreenTexture_;
SDL_Texture* LightTexture_;
TColorCommand DefaultLightColor_;
bool LightEnabled_ = false;
ELayer Layer_;
Vec2i Camera_;
Vec2i Size_;
std::vector<SDL_Vertex> VerticesBuffer_;
};
} // namespace NGame
+897
View File
@@ -0,0 +1,897 @@
#include "RoomEntity.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <sstream>
#include <vector>
#define ROOM_WIDTH 6
#define ROOM_HEIGHT 6
TDirtEntity::TDirtEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(TERRAIN_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt");
}
void TDirtEntity::Update(std::uint32_t delta) {
}
void TDirtEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 1, Position());
}
TGrassEntity::TGrassEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(TERRAIN_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt");
}
void TGrassEntity::Update(std::uint32_t delta) {
}
void TGrassEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 2, Position());
}
TPlankEntity::TPlankEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(TERRAIN_GROUP);
SetSize({16, 2});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Plank.txt");
}
void TPlankEntity::Update(std::uint32_t delta) {
}
void TPlankEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TStoneEntity::TStoneEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(TERRAIN_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt");
}
void TStoneEntity::Update(std::uint32_t delta) {
}
void TStoneEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TLadderEntity::TLadderEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(LADDER_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Ladder.txt");
}
void TLadderEntity::Update(std::uint32_t delta) {
}
void TLadderEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Light);
auto light = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Light.txt");
NGame::TApp::Instance()->SpriteManager().Draw(light, 0, Position() - (light->Frames[0].Size * 1 / 2), {1, 1});
}
TExplosionEntity::TExplosionEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(DAMAGE_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt");
}
void TExplosionEntity::Update(std::uint32_t delta) {
}
void TExplosionEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
void TExplosionEntity::Alarm(NGame::TAlarm::TId id) {
}
TKeyEntity::TKeyEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(ITEM_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Item.txt");
}
void TKeyEntity::Update(std::uint32_t delta) {
}
void TKeyEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TMineEntity::TMineEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Mine.txt");
}
void TMineEntity::Update(std::uint32_t delta) {
}
void TMineEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
void TMineEntity::Alarm(NGame::TAlarm::TId id) {
}
TCurseEntity::TCurseEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(ITEM_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Item.txt");
}
void TCurseEntity::Update(std::uint32_t delta) {
}
void TCurseEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TSpikeEntity::TSpikeEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(DAMAGE_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Spike.txt");
}
void TSpikeEntity::Update(std::uint32_t delta) {
}
void TSpikeEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TEntranceEntity::TEntranceEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(PASSAGE_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Passage.txt");
}
void TEntranceEntity::Update(std::uint32_t delta) {
}
void TEntranceEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TExitEntity::TExitEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetCollisionGroup(PASSAGE_GROUP);
SetSize({16, 16});
Sprite_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Passage.txt");
}
void TExitEntity::Update(std::uint32_t delta) {
}
void TExitEntity::Draw() const {
NGame::TApp::Instance()->RenderManager().SetLayer(NGame::TRenderManager::Background);
NGame::TApp::Instance()->SpriteManager().Draw(Sprite_, 0, Position());
}
TFloatingTextEntity::TFloatingTextEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
Alarm_.Set(0, 1000);
}
void TFloatingTextEntity::SetText(const std::string& text) {
Text_ = text;
}
void TFloatingTextEntity::SetColor(NGame::TFontManager::EColor color) {
Color_ = color;
}
void TFloatingTextEntity::Update(std::uint32_t delta) {
SetPosition({Position().X, Position().Y - 1});
}
void TFloatingTextEntity::Draw() const {
auto& renderManager = NGame::TApp::Instance()->RenderManager();
auto& fontManager = NGame::TApp::Instance()->FontManager();
renderManager.SetLayer(NGame::TRenderManager::Effects);
fontManager.Draw(Color_, Position(), Text_);
}
void TFloatingTextEntity::Alarm(NGame::TAlarm::TId id) {
Remove();
}
TRoomEntity::TRoomEntity(NGame::TEntity::TId id)
: NGame::TEntity(id) {
LoadTemplates();
GenerateLayout();
Remove();
}
void TRoomEntity::LoadTemplates() {
auto& fileManager = NGame::TApp::Instance()->FileManager();
ParseRooms(fileManager.Get("Templates/LR.txt"), LRRooms_);
ParseRooms(fileManager.Get("Templates/LRD.txt"), LRDRooms_);
ParseRooms(fileManager.Get("Templates/LRU.txt"), LRURooms_);
ParseRooms(fileManager.Get("Templates/LRUD.txt"), LRUDRooms_);
ParseRooms(fileManager.Get("Templates/Any.txt"), AnyRooms_);
}
void TRoomEntity::ParseRooms(const std::string& data, std::vector<TRoom>& rooms) {
std::istringstream inputStream(data);
TRoom currentRoom;
while (ParseRoom(inputStream, currentRoom)) {
rooms.push_back(currentRoom);
}
}
bool TRoomEntity::ParseRoom(std::istringstream& stream, TRoom& room) {
for (std::string line; std::getline(stream, line); ) {
auto action = NGame::Trim(std::string_view(line));
if (action == "Room") {
std::string rows[6];
std::getline(stream, rows[0]);
std::getline(stream, rows[1]);
std::getline(stream, rows[2]);
std::getline(stream, rows[3]);
std::getline(stream, rows[4]);
std::getline(stream, rows[5]);
std::memset(room.data, Empty, sizeof(room));
FillRow(rows[0], 0, room);
FillRow(rows[1], 1, room);
FillRow(rows[2], 2, room);
FillRow(rows[3], 3, room);
FillRow(rows[4], 4, room);
FillRow(rows[5], 5, room);
return true;
}
}
return false;
}
void TRoomEntity::FillRow(const std::string& data, std::size_t row, TRoom& room) {
for (std::size_t i = 0; i < 10 || i < data.size(); i++) {
switch (data[i]) {
case 'g': room.data[row * 10 + i] = Dirt; break;
case '1': case '2':
case '3': room.data[row * 10 + i] = LowDirt; break;
case '4': case '5':
case '6': room.data[row * 10 + i] = MidDirt; break;
case '7': case '8':
case '9': room.data[row * 10 + i] = HighDirt; break;
case 'G': room.data[row * 10 + i] = Grass; break;
case 'l': room.data[row * 10 + i] = Ladder; break;
case 'd': room.data[row * 10 + i] = Mine; break;
case 'D': room.data[row * 10 + i] = Spike; break;
case 'p': room.data[row * 10 + i] = Passage; break;
case 'P': room.data[row * 10 + i] = Plank; break;
case 'c': room.data[row * 10 + i] = Curse; break;
case 'b': room.data[row * 10 + i] = Baddy; break;
case 'u': room.data[row * 10 + i] = Utility; break;
default: break;
}
}
}
bool TRoomEntity::GoLeft(int* maze, NGame::Vec2i& position) {
if (position.X > 0 && maze[ROOM_WIDTH * position.Y + position.X - 1] != 0) {
return true;
}
if (position.X <= 0) {
maze[ROOM_WIDTH * position.Y + position.X] |= Down;
if (!GoDown(maze, position)) {
return false;
}
return GoRight(maze, position);
} else {
position.X--;
if (maze[ROOM_WIDTH * position.Y + position.X]) {
position.X++;
} else {
maze[ROOM_WIDTH * position.Y + position.X] = LeftRight;
}
}
return true;
}
bool TRoomEntity::GoRight(int* maze, NGame::Vec2i& position) {
if (position.X + 1 >= ROOM_WIDTH) {
maze[ROOM_WIDTH * position.Y + position.X] |= Down;
if (!GoDown(maze, position)) {
return false;
}
return GoLeft(maze, position);
} else {
position.X++;
if (maze[ROOM_WIDTH * position.Y + position.X]) {
position.X--;
} else {
maze[ROOM_WIDTH * position.Y + position.X] = LeftRight;
}
}
return true;
}
bool TRoomEntity::GoDown(int* maze, NGame::Vec2i& position) {
if (position.Y + 1 >= ROOM_HEIGHT) {
maze[ROOM_WIDTH * position.Y + position.X] |= Exit;
return false;
} else {
maze[ROOM_WIDTH * position.Y + position.X] |= Down;
position.Y++;
maze[ROOM_WIDTH * position.Y + position.X] = LeftRightUp;
}
return true;
}
void TRoomEntity::GenerateLayout() {
int maze[ROOM_HEIGHT * ROOM_WIDTH];
NGame::Vec2i position = {0, 0};
std::memset(maze, None, sizeof(maze));
position.X = rand() % ROOM_WIDTH;
maze[ROOM_WIDTH * position.Y + position.X] = LeftRight | Start;
// Generate rooms
while (true) {
int choice = rand() % 100;
if (choice < 33) {
if (!GoLeft(maze, position)) {
break;
}
} else if (choice < 66) {
if (!GoRight(maze, position)) {
break;
}
} else {
if (!GoDown(maze, position)) {
break;
}
}
}
for (int i = 0; i < ROOM_HEIGHT; i++) {
for (int j = 0; j < ROOM_WIDTH; j++) {
auto item = maze[i * ROOM_WIDTH + j];
GenerateRoom(maze, NGame::Vec2i(j, i));
}
}
}
void TRoomEntity::GenerateRoom(int* maze, const NGame::Vec2i& position) {
auto roomType = maze[ROOM_WIDTH * position.Y + position.X];
TRoom sourceRoom;
int randomRoomChance = rand() % 100;
SelectRoom(roomType & LeftRightUpDown, sourceRoom);
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 10; j++) {
auto tile = sourceRoom.data[i * 10 + j];
NGame::Vec2i entityPosition = {j * 16, i * 16};
entityPosition += NGame::Vec2i(position.X * 160, position.Y * 96);
std::shared_ptr<NGame::TEntity> entity;
switch (tile) {
default:
case Empty:
break;
case LowDirt:
if (rand() % 100 < 25) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity");
}
break;
case MidDirt:
if (rand() % 100 < 50) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity");
}
break;
case HighDirt:
if (rand() % 100 < 75) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity");
}
break;
case Dirt:
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("DirtEntity");
break;
case Grass:
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("GrassEntity");
break;
case Ladder:
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("LadderEntity");
break;
case Plank:
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("PlankEntity");
break;
case Mine:
if (NGame::TApp::Instance()->State().Variable("MoreMines").Bool()) {
if (rand() % 100 < 50) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("MineEntity");
}
} else {
if (rand() % 100 < 25) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("MineEntity");
}
}
break;
case Passage:
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("EntranceEntity");
break;
case Curse:
if (rand() % 100 < 25) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("CurseEntity");
}
break;
case Spike:
if (rand() % 100 < 75) {
entity = NGame::TApp::Instance()->EntityManager().MakeEntityByName("SpikeEntity");
}
break;
}
if (!entity) {
continue;
}
entity->SetPosition(entityPosition);
NGame::TApp::Instance()->EntityManager().UpdateCollision(entity);
}
}
}
void TRoomEntity::SelectRoom(int type, TRoom& room) {
std::vector<TRoom> selection;
if (type == LeftRight) {
selection.insert(selection.end(), LRRooms_.begin(), LRRooms_.end());
selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end());
selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end());
selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end());
room = selection[rand() % selection.size()];
} else if (type == LeftRightUp) {
selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end());
selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end());
room = selection[rand() % selection.size()];
} else if (type == LeftRightDown) {
selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end());
selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end());
room = selection[rand() % selection.size()];
} else if (type == LeftRightUpDown) {
room = LRUDRooms_[rand() % LRUDRooms_.size()];
} else {
selection.insert(selection.end(), AnyRooms_.begin(), AnyRooms_.end());
selection.insert(selection.end(), LRRooms_.begin(), LRRooms_.end());
selection.insert(selection.end(), LRDRooms_.begin(), LRDRooms_.end());
selection.insert(selection.end(), LRURooms_.begin(), LRURooms_.end());
selection.insert(selection.end(), LRUDRooms_.begin(), LRUDRooms_.end());
room = selection[rand() % selection.size()];
}
}
THero::THero(NGame::TEntity::TId id)
: NGame::TEntity(id) {
SetSize(NGame::Vec2i(4, 12));
SetPosition(NGame::Vec2i(0, 0));
NGame::TApp::Instance()->RenderManager().EnableLight(true);
NGame::TApp::Instance()->RenderManager().SetDefaultLightColor(0, 0, 0);
Alarm_.Set(0, 100);
}
void THero::Input(SDL_Event* event) {
switch (event->type) {
case SDL_KEYDOWN:
if (event->key.keysym.sym == SDLK_UP || event->key.keysym.sym == 'w') {
KeysHeld_[static_cast<int>(EKeys::Up)] = true;
KeysPressed_[static_cast<int>(EKeys::Up)] = true;
}
if (event->key.keysym.sym == SDLK_DOWN || event->key.keysym.sym == 's') {
KeysHeld_[static_cast<int>(EKeys::Down)] = true;
KeysPressed_[static_cast<int>(EKeys::Down)] = true;
}
if (event->key.keysym.sym == SDLK_LEFT || event->key.keysym.sym == 'a') {
KeysHeld_[static_cast<int>(EKeys::Left)] = true;
KeysPressed_[static_cast<int>(EKeys::Left)] = true;
}
if (event->key.keysym.sym == SDLK_RIGHT || event->key.keysym.sym == 'd') {
KeysHeld_[static_cast<int>(EKeys::Right)] = true;
KeysPressed_[static_cast<int>(EKeys::Right)] = true;
}
if (event->key.keysym.sym == 'z') {
KeysHeld_[static_cast<int>(EKeys::Z)] = true;
KeysPressed_[static_cast<int>(EKeys::Z)] = true;
}
if (event->key.keysym.sym == 'x') {
KeysHeld_[static_cast<int>(EKeys::X)] = true;
KeysPressed_[static_cast<int>(EKeys::X)] = true;
}
if (event->key.keysym.sym == 'c') {
KeysHeld_[static_cast<int>(EKeys::C)] = true;
KeysPressed_[static_cast<int>(EKeys::C)] = true;
}
if (event->key.keysym.sym == SDLK_LSHIFT) {
KeysHeld_[static_cast<int>(EKeys::Shift)] = true;
KeysPressed_[static_cast<int>(EKeys::Shift)] = true;
}
if (event->key.keysym.sym == SDLK_ESCAPE) {
KeysHeld_[static_cast<int>(EKeys::Escape)] = true;
KeysPressed_[static_cast<int>(EKeys::Escape)] = true;
}
break;
case SDL_KEYUP:
if (event->key.keysym.sym == SDLK_UP || event->key.keysym.sym == 'w') {
KeysHeld_[static_cast<int>(EKeys::Up)] = false;
}
if (event->key.keysym.sym == SDLK_DOWN || event->key.keysym.sym == 's') {
KeysHeld_[static_cast<int>(EKeys::Down)] = false;
}
if (event->key.keysym.sym == SDLK_LEFT || event->key.keysym.sym == 'a') {
KeysHeld_[static_cast<int>(EKeys::Left)] = false;
}
if (event->key.keysym.sym == SDLK_RIGHT || event->key.keysym.sym == 'd') {
KeysHeld_[static_cast<int>(EKeys::Right)] = false;
}
if (event->key.keysym.sym == 'z') {
KeysHeld_[static_cast<int>(EKeys::Z)] = false;
}
if (event->key.keysym.sym == 'x') {
KeysHeld_[static_cast<int>(EKeys::X)] = false;
}
if (event->key.keysym.sym == 'c') {
KeysHeld_[static_cast<int>(EKeys::C)] = false;
}
if (event->key.keysym.sym == SDLK_LSHIFT) {
KeysHeld_[static_cast<int>(EKeys::Shift)] = false;
}
if (event->key.keysym.sym == SDLK_ESCAPE) {
KeysHeld_[static_cast<int>(EKeys::Escape)] = false;
}
break;
}
}
void THero::Update(std::uint32_t delta) {
auto app = NGame::TApp::Instance();
for (bool repeatState = true; repeatState; ) {
repeatState = false;
switch (State_) {
case EState::Normal:
Speed_.X = 0;
Speed_.Y += MovementPerTick(delta, Gravity_);
if (KeysHeld_[static_cast<int>(EKeys::Right)]) {
Speed_.X = 100;
}
if (KeysHeld_[static_cast<int>(EKeys::Left)]) {
Speed_.X = -100;
}
if (KeysPressed_[static_cast<int>(EKeys::X)]) {
auto entity = app->EntityManager().MakeEntityByName("FloatingTextEntity");
auto targetEntity = dynamic_cast<TFloatingTextEntity*>(entity.get());
targetEntity->SetText("You died, LMAO");
targetEntity->SetColor(NGame::TFontManager::Red);
targetEntity->SetPosition(Position());
}
if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) {
if (KeysHeld_[static_cast<int>(EKeys::Up)] || KeysHeld_[static_cast<int>(EKeys::Down)]) {
State_ = EState::Climb;
continue;
}
}
if (!app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(-2, Size().Y), NGame::Vec2i(Size().X + 4, 1), TERRAIN_GROUP, Id())) {
if (KeysHeld_[static_cast<int>(EKeys::Down)]) {
if (KeysPressed_[static_cast<int>(EKeys::Z)]) {
IgnorePlanks_ = true;
Speed_.Y = 80;
State_ = EState::Fall;
continue;
}
} else {
if (KeysPressed_[static_cast<int>(EKeys::Z)]) {
Speed_.Y = -std::sqrt(2 * Gravity_ * (16 + (16 - Size().Y)));
State_ = EState::Jump;
continue;
}
}
}
if (MovementPerTick(delta, Speed_.Y) >= 1.0) {
State_ = EState::Fall;
continue;
}
break;
case EState::Jump:
Speed_.X = 0;
Speed_.Y += MovementPerTick(delta, Gravity_);
if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) {
if (KeysHeld_[static_cast<int>(EKeys::Up)] || KeysHeld_[static_cast<int>(EKeys::Down)]) {
State_ = EState::Climb;
continue;
}
}
if (KeysHeld_[static_cast<int>(EKeys::Right)]) {
Speed_.X = 100;
}
if (KeysHeld_[static_cast<int>(EKeys::Left)]) {
Speed_.X = -100;
}
if (Speed_.Y > 0) {
State_ = EState::Fall;
continue;
}
break;
case EState::Fall:
Speed_.X = 0;
Speed_.Y += MovementPerTick(delta, Gravity_);
if (!app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) {
if (KeysHeld_[static_cast<int>(EKeys::Up)] || KeysHeld_[static_cast<int>(EKeys::Down)]) {
State_ = EState::Climb;
continue;
}
}
if (KeysHeld_[static_cast<int>(EKeys::Right)]) {
Speed_.X = 100;
auto collisonIds = app->EntityManager().CollisionList(Position() + NGame::Vec2i(Size().X, 0), NGame::Vec2i(Size().X, 2), TERRAIN_GROUP, Id());
auto containsWall = std::any_of(collisonIds.begin(), collisonIds.end(), [&](TEntity::TId id) {
auto other = app->EntityManager().Entity(id);
if (dynamic_cast<TPlankEntity*>(other.get())) {
return false;
}
return true;
});
if (containsWall) {
if (app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(0, -Size().Y), NGame::Vec2i(Size().X * 2, Size().Y), TERRAIN_GROUP, Id())) {
State_ = EState::Hold;
continue;
}
}
}
if (KeysHeld_[static_cast<int>(EKeys::Left)]) {
Speed_.X = -100;
auto collisonIds = app->EntityManager().CollisionList(Position() - NGame::Vec2i(Size().X, 0), NGame::Vec2i(Size().X, 2), TERRAIN_GROUP, Id());
auto containsWall = std::any_of(collisonIds.begin(), collisonIds.end(), [&](TEntity::TId id) {
auto other = app->EntityManager().Entity(id);
if (dynamic_cast<TPlankEntity*>(other.get())) {
return false;
}
return true;
});
if (containsWall) {
if (app->EntityManager().IsPlaceEmpty(Position() - NGame::Vec2i(Size().X, Size().Y), NGame::Vec2i(Size().X * 2, Size().Y), TERRAIN_GROUP, Id())) {
State_ = EState::Hold;
continue;
}
}
}
if (!app->EntityManager().IsPlaceEmpty(Position() + NGame::Vec2i(0, Size().Y), NGame::Vec2i(Size().X, 1), TERRAIN_GROUP, Id())) {
State_ = EState::Normal;
continue;
}
break;
case EState::Climb:
if (app->EntityManager().IsPlaceEmpty(Position(), Size(), LADDER_GROUP)) {
State_ = EState::Normal;
repeatState = true;
continue;
}
Speed_ = {0, 0};
if (KeysHeld_[static_cast<int>(EKeys::Right)]) {
Speed_.X = 100;
}
if (KeysHeld_[static_cast<int>(EKeys::Left)]) {
Speed_.X = -100;
}
if (KeysHeld_[static_cast<int>(EKeys::Up)]) {
Speed_.Y = -100;
}
if (KeysHeld_[static_cast<int>(EKeys::Down)]) {
Speed_.Y = 100;
}
break;
case EState::Hold:
Speed_ = 0;
if (KeysPressed_[static_cast<int>(EKeys::Down)]) {
State_ = EState::Fall;
continue;
}
if (KeysPressed_[static_cast<int>(EKeys::Z)]) {
Speed_.Y = -std::sqrt(2 * Gravity_ * (16 + (16 - Size().Y)));
State_ = EState::Jump;
continue;
}
break;
}
}
// Check if we need to flip sprite
if (Speed_.X < 0) {
FaceLeft_ = 1;
} else if (Speed_.X > 0) {
FaceLeft_ = 0;
}
// Perform movement and collision
NGame::Vec2f resultSpeed = MovementPerTick(delta, Speed_);
auto moveResult = MoveWithCondition(resultSpeed, Fraction_, [&](const NGame::Vec2i& position) {
auto collisionList = app->EntityManager().CollisionList(position, Size(), TERRAIN_GROUP, Id());
if (collisionList.empty()) {
return true;
}
// Check collision for planks
for (auto& otherId : collisionList) {
auto other = app->EntityManager().Entity(otherId);
if (dynamic_cast<TPlankEntity*>(other.get())) {
// Are we ignoring planks, because we are dropping down?
if (IgnorePlanks_) {
continue;
}
// Ignore planks if we moving up
if (Speed_.Y < 0) {
continue;
}
// Ignore planks, that are already inside of us
if (NGame::RectOverlaps(Position(), Size(), other->Position(), other->Size())) {
continue;
}
// If all fails - we should be on top of the plank
return false;
} else {
return false;
}
}
return true;
});
Fraction_ = moveResult.first;
if (!moveResult.second.first) {
Speed_.X = 0;
}
if (!moveResult.second.second) {
Speed_.Y = 0;
}
// Set camera to track hero
app->RenderManager().SetCamera(Position() - app->RenderManager().Size() / 2);
// Reset pressed keys
IgnorePlanks_ = false;
std::memset(KeysPressed_, false, sizeof(KeysPressed_));
}
void THero::Draw() const {
auto& renderManager = NGame::TApp::Instance()->RenderManager();
auto& spriteManager = NGame::TApp::Instance()->SpriteManager();
renderManager.SetLayer(NGame::TRenderManager::Foreground);
auto hero = spriteManager.Get("Sprites/Hero.txt");
int spriteIndex = 0;
switch (State_) {
case EState::Normal: spriteIndex = 0; break;
case EState::Jump: spriteIndex = 2; break;
case EState::Fall: spriteIndex = 0; break;
case EState::Climb: spriteIndex = 5; break;
case EState::Hold: spriteIndex = 1; break;
}
if (State_ == EState::Normal && Speed_.X != 0 && AlternateRun_) {
spriteIndex = 3;
}
spriteManager.Draw(hero, spriteIndex, Position() - NGame::Vec2i((16 - Size().X) / 2, 16 - Size().Y), {(FaceLeft_?-1.0f:1.0f), 1.0f});
renderManager.SetLayer(NGame::TRenderManager::Light);
auto light = spriteManager.Get("Sprites/Light.txt");
spriteManager.Draw(light, 0, Position() - (light->Frames[0].Size * 4 / 2), {4, 4});
}
void THero::Alarm(NGame::TAlarm::TId id) {
if (id == 0) {
AlternateRun_ = !AlternateRun_;
}
}
TBackgroundTiler::TBackgroundTiler(NGame::TEntity::TId id)
: NGame::TEntity(id) {
Background_ = NGame::TApp::Instance()->SpriteManager().Get("Sprites/Wall.txt");
}
void TBackgroundTiler::Draw() const {
auto& renderManager = NGame::TApp::Instance()->RenderManager();
auto& spriteManager = NGame::TApp::Instance()->SpriteManager();
auto cameraPosition = renderManager.Camera();
auto tileOffset = cameraPosition;
tileOffset.X %= 16;
tileOffset.Y %= 16;
renderManager.SetLayer(NGame::TRenderManager::Tile);
for (int i = -1; i <= 320 / 16; ++i) {
for (int j = -1; j <= 240 / 16; ++j) {
spriteManager.Draw(Background_, 0, NGame::Vec2i(i * 16, j * 16) + cameraPosition - tileOffset);
}
}
}
+272
View File
@@ -0,0 +1,272 @@
#pragma once
#include "App.h"
#define TERRAIN_GROUP 0x01
#define LADDER_GROUP 0x02
#define DAMAGE_GROUP 0x04
#define ITEM_GROUP 0x08
#define PASSAGE_GROUP 0x10
#define PLANK_GROUP 0x20
class TDirtEntity : public NGame::TEntity {
public:
TDirtEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TGrassEntity : public NGame::TEntity {
public:
TGrassEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TPlankEntity : public NGame::TEntity {
public:
TPlankEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TStoneEntity : public NGame::TEntity {
public:
TStoneEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TLadderEntity : public NGame::TEntity {
public:
TLadderEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TExplosionEntity : public NGame::TEntity {
public:
TExplosionEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
virtual void Alarm(NGame::TAlarm::TId id) override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TKeyEntity : public NGame::TEntity {
public:
TKeyEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TMineEntity : public NGame::TEntity {
public:
TMineEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
virtual void Alarm(NGame::TAlarm::TId id) override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TCurseEntity : public NGame::TEntity {
public:
TCurseEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TSpikeEntity : public NGame::TEntity {
public:
TSpikeEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TEntranceEntity : public NGame::TEntity {
public:
TEntranceEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TExitEntity : public NGame::TEntity {
public:
TExitEntity(NGame::TEntity::TId id);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Sprite_;
};
class TFloatingTextEntity : public NGame::TEntity {
public:
TFloatingTextEntity(NGame::TEntity::TId id);
void SetText(const std::string& text);
void SetColor(NGame::TFontManager::EColor color);
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
virtual void Alarm(NGame::TAlarm::TId id) override;
private:
std::string Text_;
NGame::TFontManager::EColor Color_;
};
class TRoomEntity : public NGame::TEntity {
public:
TRoomEntity(NGame::TEntity::TId id);
private:
enum EType {
None,
Left = 0x0001,
Right = 0x0002,
Up = 0x0004,
Down = 0x0008,
Start = 0x0010,
Exit = 0x0020,
LeftRight = Left | Right,
LeftRightDown = Left | Right | Down,
LeftRightUp = Left | Right | Up,
LeftRightUpDown = Left | Right | Up | Down,
};
enum ETile {
Empty,
Baddy,
Curse,
Dirt,
Grass,
HighDirt,
Ladder,
LowDirt,
MidDirt,
Mine,
Passage,
Plank,
Spike,
Utility,
};
struct TRoom {
ETile data[60];
};
void LoadTemplates();
void ParseRooms(const std::string& data, std::vector<TRoom>& rooms);
bool ParseRoom(std::istringstream& stream, TRoom& room);
void FillRow(const std::string& data, std::size_t row, TRoom& room);
bool GoLeft(int* maze, NGame::Vec2i& position);
bool GoRight(int* maze, NGame::Vec2i& position);
bool GoDown(int* maze, NGame::Vec2i& position);
void GenerateLayout();
void GenerateRoom(int* maze, const NGame::Vec2i& position);
void SelectRoom(int type, TRoom& room);
private:
std::vector<TRoom> LRRooms_;
std::vector<TRoom> LRURooms_;
std::vector<TRoom> LRDRooms_;
std::vector<TRoom> LRUDRooms_;
std::vector<TRoom> AnyRooms_;
};
class THero : public NGame::TEntity {
public:
THero(NGame::TEntity::TId id);
enum class EKeys {
Left,
Right,
Up,
Down,
X,
Z,
C,
Shift,
Escape,
MaxSentinel
};
enum class EState {
Normal,
Jump,
Fall,
Climb,
Hold,
};
virtual void Input(SDL_Event* event) override;
virtual void Update(std::uint32_t delta) override;
virtual void Draw() const override;
virtual void Alarm(NGame::TAlarm::TId id) override;
private:
bool KeysPressed_[static_cast<int>(EKeys::MaxSentinel)] = {};
bool KeysHeld_[static_cast<int>(EKeys::MaxSentinel)] = {};
bool FaceLeft_ = false;
bool IgnorePlanks_ = false;
bool AlternateRun_ = false;
EState State_ = EState::Normal;
const float Gravity_ = 500.0;
NGame::Vec2f Speed_ = NGame::Vec2f(0.0, 0.0);
NGame::Vec2f Fraction_ = NGame::Vec2f(0.0, 0.0);
NGame::Vec2i Want_;
};
class TBackgroundTiler : public NGame::TEntity {
public:
TBackgroundTiler(NGame::TEntity::TId id);
virtual void Draw() const override;
private:
std::shared_ptr<NGame::TSpriteManager::TSprite> Background_;
};
+119
View File
@@ -0,0 +1,119 @@
#include "SpriteManager.h"
#include <SDL2/SDL_render.h>
#include <sstream>
namespace NGame {
TSpriteManager::TSpriteManager(TFileManager& fileManager, TRenderManager& renderManager, std::size_t cacheSize)
: FileManager_(fileManager), RenderManager_(renderManager), Cache_(cacheSize) {
}
std::shared_ptr<TSpriteManager::TSprite> TSpriteManager::Get(const std::string& path) {
if (Cache_.Contains(path)) {
return Cache_.Get(path);
}
auto result = std::make_shared<TSprite>(ParseInfoFile(path));
Cache_.Set(path, result);
return result;
}
void TSpriteManager::Draw(const std::shared_ptr<TSprite>& sprite, size_t frame, const Vec2i& position, const Vec2f& scale) {
const auto& frameData = sprite->Frames[frame];
int flip = 0;
Vec2i size = frameData.Size * scale;
if (size.X < 0) {
size.X = -size.X;
flip |= SDL_FLIP_HORIZONTAL;
}
if (size.Y < 0) {
size.Y = -size.Y;
flip |= SDL_FLIP_VERTICAL;
}
RenderManager_.SetTexture(sprite->Texture);
RenderManager_.DrawSprite(frameData.Position, frameData.Size, position, size, static_cast<SDL_RendererFlip>(flip));
}
TSpriteManager::TSprite TSpriteManager::ParseInfoFile(const std::string& path) {
std::string data = FileManager_.Get(path);
std::istringstream inputStream(data);
std::shared_ptr<SDL_Texture> currentTexture;
std::vector<TFrame> currentFrames;
for (std::string line; std::getline(inputStream, line); ) {
auto remainder = Trim(std::string_view(line));
auto action = RightTrim(NextToken(remainder, " "));
if (action == "Texture") {
auto textureString = Trim(NextToken(remainder, " "));
if (textureString.empty()) {
throw std::runtime_error("texture name should not be empty for " + path);
}
currentTexture = RenderManager_.Get(std::string(textureString));
} else if (action == "Frame") {
auto xString = Trim(NextToken(remainder, " "));
auto yString = Trim(NextToken(remainder, " "));
auto widthString = Trim(NextToken(remainder, " "));
auto heightString = Trim(NextToken(remainder, " "));
TFrame frame;
if (xString.empty() && yString.empty()) {
frame.Position.X = 0;
frame.Position.Y = 0;
} else {
frame.Position.X = std::stoi(std::string(xString));
frame.Position.Y = std::stoi(std::string(yString));
}
if (widthString.empty() && heightString.empty()) {
int width, height;
if (SDL_QueryTexture(currentTexture.get(), NULL, NULL, &width, &height)) {
throw std::runtime_error("can't query texture information for " + path);
}
frame.Size.X = width;
frame.Size.Y = height;
} else {
frame.Size.X = std::stoi(std::string(widthString));
frame.Size.Y = std::stoi(std::string(heightString));
}
if (xString.empty() || yString.empty() || widthString.empty() || heightString.empty()) {
throw std::runtime_error("frame should have x, y, width, height parameters for " + path);
}
currentFrames.push_back(frame);
}
}
if (!currentTexture) {
std::runtime_error("no texture for " + path);
}
if (currentFrames.empty()) {
TFrame frame;
int width, height;
if (SDL_QueryTexture(currentTexture.get(), NULL, NULL, &width, &height)) {
throw std::runtime_error("can't query texture information for " + path);
}
frame.Position.X = 0;
frame.Position.Y = 0;
frame.Size.X = width;
frame.Size.Y = height;
currentFrames.push_back(frame);
}
return {currentTexture, currentFrames};
}
} // namespace NGame
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "Common.h"
#include "FileManager.h"
#include "LRU.h"
#include "RenderManager.h"
namespace NGame {
class TSpriteManager {
public:
struct TFrame {
Vec2i Position;
Vec2i Size;
};
struct TSprite {
std::shared_ptr<SDL_Texture> Texture;
std::vector<TFrame> Frames;
};
TSpriteManager(TFileManager& fileManager, TRenderManager& renderManager, std::size_t cacheSize = 4);
DELETE_COPY(TSpriteManager)
std::shared_ptr<TSprite> Get(const std::string& path);
void Draw(const std::shared_ptr<TSprite>& sprite, size_t frame, const Vec2i& position, const Vec2f& scale = {1.0f, 1.0f});
private:
TSprite ParseInfoFile(const std::string& path);
private:
TFileManager& FileManager_;
TRenderManager& RenderManager_;
TLRU<std::string, std::shared_ptr<TSprite>> Cache_;
};
} // namespace NGame
+39
View File
@@ -0,0 +1,39 @@
#include "State.h"
#include <stdexcept>
namespace NGame {
TVariable& TState::Variable(const std::string_view& name) {
auto stringName = std::string(name);
auto iterator = Variables_.find(stringName);
if (iterator != Variables_.end()) {
return iterator->second;
} else {
return SetVariable(name, 0);
}
}
const TVariable& TState::Variable(const std::string_view& name) const {
auto stringName = std::string(name);
auto iterator = Variables_.find(stringName);
if (iterator != Variables_.end()) {
return iterator->second;
}
throw std::runtime_error("Attempting to access non-existing variable");
}
bool TState::ContainsVariable(const std::string_view& name) const {
auto stringName = std::string(name);
auto iterator = Variables_.find(stringName);
if (iterator != Variables_.end()) {
return true;
}
return false;
}
} // namespace NGame
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "Common.h"
#include "Variable.h"
#include <memory>
#include <stdexcept>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace NGame {
class TState {
public:
TVariable& Variable(const std::string_view& name);
const TVariable& Variable(const std::string_view& name) const;
bool ContainsVariable(const std::string_view& name) const;
template<typename T>
TVariable& SetVariable(const std::string_view& name, const T& value) {
auto stringName = std::string(name);
auto result = Variables_.emplace(std::make_pair(stringName, value));
if (result.second) {
return result.first->second;
}
throw std::runtime_error("Failed to set variable");
}
private:
std::unordered_map<std::string, TVariable> Variables_;
};
} // namespace NGame
+32
View File
@@ -0,0 +1,32 @@
#include "SurfaceManager.h"
#include <cstring>
#include <SDL2/SDL_rwops.h>
#include <SDL2/SDL_surface.h>
#include <stdexcept>
namespace NGame {
TSurfaceManager::TSurfaceManager(TFileManager& fileManager, std::size_t cacheSize)
: FileManager_(fileManager), Cache_(cacheSize) {
}
std::shared_ptr<SDL_Surface> TSurfaceManager::Get(const std::string& path) {
if (Cache_.Contains(path)) {
return Cache_.Get(path);
}
std::string data = FileManager_.Get(path);
SDL_RWops* rw = SDL_RWFromConstMem(data.data(), data.size());
SDL_Surface* surface = SDL_LoadBMP_RW(rw, 1);
if (!surface) {
std::runtime_error("Can't create surface from file " + path);
}
std::shared_ptr<SDL_Surface> result(surface, [](SDL_Surface* surface) { SDL_FreeSurface(surface); });
Cache_.Set(path, result);
return result;
}
} // namespace NGame
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "Common.h"
#include "LRU.h"
#include "FileManager.h"
#include <memory>
#include <SDL2/SDL_surface.h>
namespace NGame {
class TSurfaceManager {
public:
TSurfaceManager(TFileManager& fileManager, std::size_t cacheSize = 4);
DELETE_COPY(TSurfaceManager)
std::shared_ptr<SDL_Surface> Get(const std::string& path);
private:
TFileManager& FileManager_;
TLRU<std::string, std::shared_ptr<SDL_Surface>> Cache_;
};
} // namespace NGame
+83
View File
@@ -0,0 +1,83 @@
#include "Variable.h"
namespace NGame {
void TVariable::SetDouble(double value) {
Data_ = value;
}
void TVariable::SetString(const std::string& value) {
Data_ = value;
}
void TVariable::SetInt(int value) {
Data_ = value;
}
void TVariable::SetBool(bool value) {
Data_ = value;
}
double TVariable::Double() const {
const auto asDouble = TOverload {
[](int value) { return double(value); },
[](double value) { return value; },
[](const std::string value) {
try {
return std::stod(value);
} catch (...) {
return 0.0;
}
},
[](bool value) { return double(value); }
};
return std::visit(asDouble, Data_);
}
std::string TVariable::String() const {
const auto asString = TOverload {
[](int value) { return std::to_string(value); },
[](double value) { return std::to_string(value); },
[](const std::string value) { return value; },
[](bool value) { return std::to_string(value); }
};
return std::visit(asString, Data_);
}
int TVariable::Int() const {
const auto asInt = TOverload {
[](int value) { return value; },
[](double value) { return int(value); },
[](const std::string value) {
try {
return std::stoi(value);
} catch (...) {
return 0;
}
},
[](bool value) { return int(value); }
};
return std::visit(asInt, Data_);
}
bool TVariable::Bool() const {
const auto asBool = TOverload {
[](int value) { return value != 0; },
[](double value) { return value != 0; },
[](const std::string value) {
std::string caseFolded = ToUpper(Trim(value));
if (caseFolded == "TRUE") {
return true;
}
return false;
},
[](bool value) { return value; }
};
return std::visit(asBool, Data_);
}
} // namespace NGame
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "Common.h"
#include <variant>
#include <string>
namespace NGame {
class TVariable {
public:
TVariable() = default;
template<typename T>
TVariable(const T& value)
: Data_(value) {
}
void SetDouble(double value);
void SetString(const std::string& value);
void SetInt(int value);
void SetBool(bool value);
double Double() const;
std::string String() const;
int Int() const;
bool Bool() const;
private:
std::variant<double, std::string, int, bool> Data_;
};
} // namespace NGame