Initial commit
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user