basic movement
This commit is contained in:
20
include/game/agame/background.hpp
Normal file
20
include/game/agame/background.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <object/entity.hpp>
|
||||||
|
#include <renderer/texture.hpp>
|
||||||
|
#include <renderer/font.hpp>
|
||||||
|
#include <object/sound.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
class Background : public Object::Entity {
|
||||||
|
using Object::Entity::Entity;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~Background() override = default;
|
||||||
|
void start() override;
|
||||||
|
void update(float deltaTime) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Object::Sound mSound;
|
||||||
|
};
|
||||||
|
}
|
||||||
26
include/game/agame/camcontroller.hpp
Normal file
26
include/game/agame/camcontroller.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <object/entity.hpp>
|
||||||
|
#include <renderer/texture.hpp>
|
||||||
|
#include <renderer/font.hpp>
|
||||||
|
#include <object/sound.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
class CamController : public Object::Entity {
|
||||||
|
using Object::Entity::Entity;
|
||||||
|
|
||||||
|
public:
|
||||||
|
~CamController() override = default;
|
||||||
|
void start() override;
|
||||||
|
void update(float deltaTime) override;
|
||||||
|
void onWindowResized(int newWidth, int newHeight) override {
|
||||||
|
mScreenW = newWidth;
|
||||||
|
mScreenH = newHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float mSpeed = 200.f; // Pixels per second
|
||||||
|
int mScreenW, mScreenH;
|
||||||
|
int mEdgeTolerance = 200;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,5 +16,6 @@ namespace Game::AGame {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Object::Sound mSound;
|
Object::Sound mSound;
|
||||||
|
float mSpeed = 200.f; // Pixels per second
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <object/camera.hpp>
|
||||||
|
|
||||||
namespace Game {
|
namespace Game {
|
||||||
using clock = std::chrono::steady_clock;
|
using clock = std::chrono::steady_clock;
|
||||||
@@ -22,7 +23,7 @@ namespace Game {
|
|||||||
void setTargetUpdatesPerSecond(int target) { mTargetUpdatesPerSecond = target; }
|
void setTargetUpdatesPerSecond(int target) { mTargetUpdatesPerSecond = target; }
|
||||||
int getTargetUpdatesPerSecond() { return mTargetUpdatesPerSecond; }
|
int getTargetUpdatesPerSecond() { return mTargetUpdatesPerSecond; }
|
||||||
private:
|
private:
|
||||||
int mTargetUpdatesPerSecond = 60;
|
int mTargetUpdatesPerSecond = TARGET_UPDATE_RATE;
|
||||||
clock::time_point mLastUpdate;
|
clock::time_point mLastUpdate;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
24
include/object/camera.hpp
Normal file
24
include/object/camera.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace Game::Object {
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
Camera() = default;
|
||||||
|
DISABLE_COPY_AND_MOVE(Camera)
|
||||||
|
~Camera() = default;
|
||||||
|
|
||||||
|
static Camera& getInstance();
|
||||||
|
|
||||||
|
void setPosition(float x, float y);
|
||||||
|
void getPosition(float& x, float& y) const;
|
||||||
|
void move(float deltaX, float deltaY);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::mutex mMutex;
|
||||||
|
float mX = 0.0f;
|
||||||
|
float mY = 0.0f;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <renderer/renderer.hpp>
|
||||||
|
|
||||||
namespace Game::Renderer {
|
namespace Game::Renderer {
|
||||||
class Renderer;
|
class Renderer;
|
||||||
@@ -24,9 +25,12 @@ namespace Game::Object {
|
|||||||
Entity& operator=(Entity&&) noexcept;
|
Entity& operator=(Entity&&) noexcept;
|
||||||
virtual ~Entity() = 0;
|
virtual ~Entity() = 0;
|
||||||
|
|
||||||
|
// Start is called when the entity is spawned
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
|
// Update is called every update cycle; deltaTime is the time (in seconds) since the last update call
|
||||||
virtual void update(float deltaTime) = 0;
|
virtual void update(float deltaTime) = 0;
|
||||||
void render(Game::Renderer::Renderer* renderer);
|
virtual void onWindowResized(int newWidth, int newHeight) {} // Called when the window is resized, with the new width and height in pixels
|
||||||
|
void render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config);
|
||||||
|
|
||||||
// Setters and getters
|
// Setters and getters
|
||||||
void setTexture(std::shared_ptr<Game::Renderer::Texture> tex) { mTex = tex; }
|
void setTexture(std::shared_ptr<Game::Renderer::Texture> tex) { mTex = tex; }
|
||||||
@@ -37,12 +41,15 @@ namespace Game::Object {
|
|||||||
std::string getName() { return mName; }
|
std::string getName() { return mName; }
|
||||||
Transform* getTransform() { return &mTransform; }
|
Transform* getTransform() { return &mTransform; }
|
||||||
bool isActive() { return mIsActive; }
|
bool isActive() { return mIsActive; }
|
||||||
|
int getZIndex() const { return mZIndex; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string mName;
|
std::string mName;
|
||||||
std::shared_ptr<Game::Renderer::Texture> mTex;
|
std::shared_ptr<Game::Renderer::Texture> mTex;
|
||||||
Transform mTransform;
|
Transform mTransform;
|
||||||
bool mIsActive;
|
bool mIsActive;
|
||||||
|
int mZIndex = 0; // For rendering order; higher zIndex renders on top of lower zIndex
|
||||||
|
float mTiledScale = 1.f; // Only used if the texture is tiled, to determine how much to scale the texture when rendering (since the entire texture is rendered as a single tile, this is necessary to be able to have different sized tiles)
|
||||||
private:
|
private:
|
||||||
float mScaleConstant = 0.25f;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ namespace Game::Object {
|
|||||||
float x, y;
|
float x, y;
|
||||||
float rotation; // In degrees, clockwise
|
float rotation; // In degrees, clockwise
|
||||||
float scaleX, scaleY;
|
float scaleX, scaleY;
|
||||||
|
float adjustedScaleX() const { return scaleX * UNIVERSAL_SCALE_COEFFICIENT; }
|
||||||
|
float adjustedScaleY() const { return scaleY * UNIVERSAL_SCALE_COEFFICIENT; }
|
||||||
} Transform;
|
} Transform;
|
||||||
|
|
||||||
constexpr Transform DEFAULT_TRANSFORM{0.f, 0.f, 0.f, 1.f, 1.f};
|
constexpr Transform DEFAULT_TRANSFORM{0.f, 0.f, 0.f, 1.f, 1.f};
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
|
|
||||||
namespace Game::Renderer {
|
namespace Game::Renderer {
|
||||||
|
typedef struct {
|
||||||
|
float camX;
|
||||||
|
float camY;
|
||||||
|
int screenW;
|
||||||
|
int screenH;
|
||||||
|
} RendererConfig;
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer();
|
Renderer();
|
||||||
|
|||||||
@@ -19,9 +19,13 @@ namespace Game::Renderer {
|
|||||||
std::string getId();
|
std::string getId();
|
||||||
float getWidth();
|
float getWidth();
|
||||||
float getHeight();
|
float getHeight();
|
||||||
|
bool isTiled() { return mIsTiled; }
|
||||||
|
void setTiled(bool tiled) { mIsTiled = tiled; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SDL_Texture* mTex;
|
SDL_Texture* mTex;
|
||||||
std::string mId;
|
std::string mId;
|
||||||
|
private:
|
||||||
|
bool mIsTiled = false; // Whether the texture is a tileset that should be rendered as a single tile or not
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <object/entity.hpp>
|
#include <object/entity.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -18,6 +19,10 @@ namespace Game::State {
|
|||||||
fn(mEntities);
|
fn(mEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sort(); // Sort entities by zIndex for correct rendering order
|
||||||
|
Object::Entity* getEntityByName(const std::string& name); // Get an entity by name, returns nullptr if no entity with the name exists
|
||||||
|
std::vector<Object::Entity*> getEntitiesSnapshot(bool sortByZIndex = false); // Get a stable snapshot of entity pointers for iteration outside the lock
|
||||||
|
|
||||||
// Update entity at index, by REFERENCE.
|
// Update entity at index, by REFERENCE.
|
||||||
Object::Entity* getAtIndex(size_t at);
|
Object::Entity* getAtIndex(size_t at);
|
||||||
// Add an entity to the gamestate.
|
// Add an entity to the gamestate.
|
||||||
|
|||||||
@@ -35,3 +35,6 @@
|
|||||||
SDL_GetTicks() / 1000.f
|
SDL_GetTicks() / 1000.f
|
||||||
|
|
||||||
#define PI 3.14159265358979323846f
|
#define PI 3.14159265358979323846f
|
||||||
|
#define UNIVERSAL_SCALE_COEFFICIENT 0.25f
|
||||||
|
#define TARGET_FPS 60
|
||||||
|
#define TARGET_UPDATE_RATE 60
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <game/gamemanager.hpp>
|
#include <game/gamemanager.hpp>
|
||||||
#include <audio/audio.hpp>
|
#include <audio/audio.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
class Window {
|
class Window {
|
||||||
@@ -25,18 +26,20 @@ namespace Game::Window {
|
|||||||
void setTargetFPS(int fps) { mTargetFPS = fps; }
|
void setTargetFPS(int fps) { mTargetFPS = fps; }
|
||||||
int getTargetFPS() { return mTargetFPS; }
|
int getTargetFPS() { return mTargetFPS; }
|
||||||
|
|
||||||
static SDL_Window* getSDLWindowBackend() { return sWindowBackend; }
|
static SDL_Window* getSDLWindowBackend() { std::scoped_lock lock(sMutex); return sWindowBackend; }
|
||||||
|
|
||||||
Renderer::Renderer* getRenderer() { return &mRenderer; }
|
Renderer::Renderer* getRenderer() { std::scoped_lock lock(mMutex); return &mRenderer; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
mutable std::mutex mMutex;
|
||||||
|
static std::mutex sMutex;
|
||||||
static inline SDL_Window* sWindowBackend = nullptr;
|
static inline SDL_Window* sWindowBackend = nullptr;
|
||||||
SDL_Window* mWindow;
|
SDL_Window* mWindow;
|
||||||
Renderer::Renderer mRenderer;
|
Renderer::Renderer mRenderer;
|
||||||
Game::GameManager mGameManager;
|
Game::GameManager mGameManager;
|
||||||
std::jthread mGameThread;
|
std::jthread mGameThread;
|
||||||
bool mRunning;
|
bool mRunning;
|
||||||
int mTargetFPS = 60;
|
int mTargetFPS = TARGET_FPS;
|
||||||
size_t mFrameCount = 0;
|
size_t mFrameCount = 0;
|
||||||
std::chrono::steady_clock::time_point mLastFPSTime;
|
std::chrono::steady_clock::time_point mLastFPSTime;
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
resources/bgtest.png
Normal file
BIN
resources/bgtest.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
31
src/game/agame/background.cpp
Normal file
31
src/game/agame/background.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include <game/agame/background.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
void Background::start() {
|
||||||
|
mZIndex = -1; // Ensure background renders behind other entities
|
||||||
|
mTex->setTiled(true); // Set the background texture to be tiled
|
||||||
|
mTiledScale = 0.5f;
|
||||||
|
int w, h;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
|
||||||
|
mTransform.scaleX *= 10.f;
|
||||||
|
mTransform.scaleY *= 10.f;
|
||||||
|
|
||||||
|
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
|
|
||||||
|
LOG("W: " << w << " H: " << h);
|
||||||
|
|
||||||
|
mSound.~Sound();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Background::update(float deltaTime) {
|
||||||
|
if (!mIsActive) return;
|
||||||
|
//mTransform.rotation += 1.f; // Rotate clockwise for testing
|
||||||
|
//mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||||
|
//mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||||
|
|
||||||
|
//Object::Camera::getInstance().move(1.f, 0.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/game/agame/camcontroller.cpp
Normal file
41
src/game/agame/camcontroller.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include <game/agame/camcontroller.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
#include <object/camera.hpp>
|
||||||
|
#include <state/gamestate.hpp>
|
||||||
|
|
||||||
|
namespace Game::AGame {
|
||||||
|
void CamController::start() {
|
||||||
|
mTex = nullptr; // No texture
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &mScreenW, &mScreenH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CamController::update(float deltaTime) {
|
||||||
|
if (!mIsActive) return;
|
||||||
|
Object::Entity* player = Game::State::GameState::getInstance().getEntityByName("Player"); // We get the pointer every frame, otherwise we might get screwed by vector reallocs
|
||||||
|
if (!player) return; // If the player doesn't exist, don't do anything
|
||||||
|
|
||||||
|
float playerX = player->getTransform()->x;
|
||||||
|
float playerY = player->getTransform()->y;
|
||||||
|
float camX, camY;
|
||||||
|
Object::Camera::getInstance().getPosition(camX, camY);
|
||||||
|
|
||||||
|
// Apply tolerance from the edges of the screen, so the camera doesn't immediately start moving when the player moves a little bit
|
||||||
|
float leftBound = camX - mScreenW / 2.f + mEdgeTolerance;
|
||||||
|
float rightBound = camX + mScreenW / 2.f - mEdgeTolerance;
|
||||||
|
float topBound = camY - mScreenH / 2.f + mEdgeTolerance;
|
||||||
|
float bottomBound = camY + mScreenH / 2.f - mEdgeTolerance;
|
||||||
|
|
||||||
|
if (playerX < leftBound) {
|
||||||
|
Object::Camera::getInstance().move(-mSpeed * deltaTime, 0.f);
|
||||||
|
} else if (playerX > rightBound) {
|
||||||
|
Object::Camera::getInstance().move(mSpeed * deltaTime, 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerY < topBound) {
|
||||||
|
Object::Camera::getInstance().move(0.f, -mSpeed * deltaTime);
|
||||||
|
} else if (playerY > bottomBound) {
|
||||||
|
Object::Camera::getInstance().move(0.f, mSpeed * deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,26 +5,33 @@
|
|||||||
namespace Game::AGame {
|
namespace Game::AGame {
|
||||||
void Player::start() {
|
void Player::start() {
|
||||||
mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
|
mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
|
||||||
mSound.play();
|
//mSound.play();
|
||||||
|
|
||||||
int w, h;
|
int w, h;
|
||||||
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &w, &h);
|
||||||
|
|
||||||
mTransform.x = w / 2.f - (mTex->getWidth() / 2.f * mTransform.scaleX * 0.25f); // Start in the middle of the screen
|
mTransform.x -= mTex->getWidth() * mTransform.adjustedScaleX() / 2.f;
|
||||||
mTransform.y = h / 2.f - (mTex->getHeight() / 2.f * mTransform.scaleY * 0.25f);
|
mTransform.y -= mTex->getHeight() * mTransform.adjustedScaleY() / 2.f;
|
||||||
mTransform.rotation = 0.f;
|
|
||||||
|
|
||||||
LOG("W: " << w << " H: " << h);
|
LOG("W: " << w << " H: " << h);
|
||||||
|
|
||||||
|
mSound.~Sound();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::update(float deltaTime) {
|
void Player::update(float deltaTime) {
|
||||||
if (!mIsActive) return;
|
if (!mIsActive) return;
|
||||||
//LOG("Updated Player");
|
//mTransform.rotation += 1.f; // Rotate clockwise for testing
|
||||||
//mTransform.x += 1.f; // Move right at a constant speed for testing
|
//mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||||
mTransform.rotation += 1.f; // Rotate clockwise for testing
|
//mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
||||||
//LOG(mName << " position: " << mTransform.x << ' ' << mTransform.y);
|
|
||||||
//LOG("DeltaTime: " << deltaTime);
|
//Object::Camera::getInstance().move(1.f, 0.f);
|
||||||
mTransform.scaleX = 1.f + 1.f * std::sin(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
|
||||||
mTransform.scaleY = 1.f + 0.5f * std::cos(RUNNING_TIME() / 0.5f); // Pulsate scale for testing
|
// Simple movement
|
||||||
|
const bool* state = SDL_GetKeyboardState(nullptr);
|
||||||
|
|
||||||
|
if (state[SDL_SCANCODE_W]) mTransform.y -= mSpeed * deltaTime;
|
||||||
|
if (state[SDL_SCANCODE_S]) mTransform.y += mSpeed * deltaTime;
|
||||||
|
if (state[SDL_SCANCODE_A]) mTransform.x -= mSpeed * deltaTime;
|
||||||
|
if (state[SDL_SCANCODE_D]) mTransform.x += mSpeed * deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace Game {
|
|||||||
void GameManager::run(std::stop_token stopToken) {
|
void GameManager::run(std::stop_token stopToken) {
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
LOG("GameManager thread started");
|
LOG("GameManager thread started");
|
||||||
|
Object::Camera::getInstance().setPosition(0.f, 0.f); // Start with camera at (0, 0)
|
||||||
|
|
||||||
mLastUpdate = clock::now(); // Get the update
|
mLastUpdate = clock::now(); // Get the update
|
||||||
|
|
||||||
@@ -18,11 +19,12 @@ namespace Game {
|
|||||||
const auto frameStart = std::chrono::steady_clock::now();
|
const auto frameStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
State::GameState::getInstance().withEntitiesLocked([seconds](auto& entities) {
|
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
|
||||||
for (auto& entity : entities) {
|
for (auto* entity : entities) {
|
||||||
|
if (entity) {
|
||||||
entity->update(seconds);
|
entity->update(seconds);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
ERROR("Exception in GameManager thread: " << e.what());
|
ERROR("Exception in GameManager thread: " << e.what());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <object/entity.hpp>
|
#include <object/entity.hpp>
|
||||||
#include <object/transform.hpp>
|
#include <object/transform.hpp>
|
||||||
#include <game/agame/player.hpp>
|
#include <game/agame/player.hpp>
|
||||||
|
#include <game/agame/background.hpp>
|
||||||
|
#include <game/agame/camcontroller.hpp>
|
||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
#include <renderer/texture.hpp>
|
#include <renderer/texture.hpp>
|
||||||
#include <renderer/font.hpp>
|
#include <renderer/font.hpp>
|
||||||
@@ -14,7 +16,10 @@ int main() {
|
|||||||
Window::Window window = Window::Window();
|
Window::Window window = Window::Window();
|
||||||
window.init(1280, 720, "Game Window");
|
window.init(1280, 720, "Game Window");
|
||||||
|
|
||||||
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::CamController>("Camera Controller", nullptr, Object::DEFAULT_TRANSFORM));
|
||||||
|
|
||||||
//Object::Transform t1{100.f, 100.f, 0.f, 1.f, 1.f};
|
//Object::Transform t1{100.f, 100.f, 0.f, 1.f, 1.f};
|
||||||
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::Background>("BG", std::make_shared<Game::Renderer::Texture>("../resources/bgtest.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
|
||||||
State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player", std::make_shared<Game::Renderer::Texture>("../resources/missing_texture.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
|
State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player", std::make_shared<Game::Renderer::Texture>("../resources/missing_texture.png", window.getRenderer()->getSDLRenderer()), Object::DEFAULT_TRANSFORM));
|
||||||
//State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1));
|
//State::GameState::getInstance().addEntity(std::make_unique<AGame::Player>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1));
|
||||||
|
|
||||||
|
|||||||
26
src/object/camera.cpp
Normal file
26
src/object/camera.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include <object/camera.hpp>
|
||||||
|
|
||||||
|
namespace Game::Object {
|
||||||
|
Camera& Camera::getInstance() {
|
||||||
|
static Camera instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::setPosition(float x, float y) {
|
||||||
|
std::scoped_lock lock(mMutex);
|
||||||
|
mX = x;
|
||||||
|
mY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::getPosition(float& x, float& y) const {
|
||||||
|
std::scoped_lock lock(mMutex);
|
||||||
|
x = mX;
|
||||||
|
y = mY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::move(float deltaX, float deltaY) {
|
||||||
|
std::scoped_lock lock(mMutex);
|
||||||
|
mX += deltaX;
|
||||||
|
mY += deltaY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#include <object/entity.hpp>
|
#include <object/entity.hpp>
|
||||||
#include <renderer/renderer.hpp>
|
#include <renderer/renderer.hpp>
|
||||||
#include <renderer/texture.hpp>
|
#include <renderer/texture.hpp>
|
||||||
|
#include <object/camera.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace Game::Object {
|
namespace Game::Object {
|
||||||
Entity::~Entity() = default;
|
Entity::~Entity() = default;
|
||||||
@@ -35,19 +38,20 @@ namespace Game::Object {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Entity::render(Game::Renderer::Renderer* renderer) {
|
void Entity::render(Game::Renderer::Renderer* renderer, Game::Renderer::RendererConfig config) {
|
||||||
if (!mIsActive || !mTex) return; // Don't render if not active or if there's no texture
|
if (!mIsActive || !mTex) return; // Don't render if not active or if there's no texture
|
||||||
|
|
||||||
|
if (!mTex->isTiled()) {
|
||||||
float w, h;
|
float w, h;
|
||||||
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
|
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
|
||||||
|
|
||||||
SDL_FRect dst;
|
SDL_FRect dst;
|
||||||
dst.w = w * mTransform.scaleX * mScaleConstant; // 1.f is HUGE, so this is just a constant to make the default scale more reasonable
|
dst.w = w * mTransform.scaleX * UNIVERSAL_SCALE_COEFFICIENT; // 1.f is HUGE, so this is just a constant to make the default scale more reasonable
|
||||||
dst.h = h * mTransform.scaleY * mScaleConstant;
|
dst.h = h * mTransform.scaleY * UNIVERSAL_SCALE_COEFFICIENT;
|
||||||
|
|
||||||
// Top-left origin
|
// Top-left origin; Account for camera position (center the camera on the screen)
|
||||||
dst.x = mTransform.x;
|
dst.x = mTransform.x - config.camX + config.screenW / 2.f;
|
||||||
dst.y = mTransform.y;
|
dst.y = mTransform.y - config.camY + config.screenH / 2.f;
|
||||||
|
|
||||||
SDL_FPoint center;
|
SDL_FPoint center;
|
||||||
center.x = dst.w / 2.f;
|
center.x = dst.w / 2.f;
|
||||||
@@ -62,5 +66,36 @@ namespace Game::Object {
|
|||||||
¢er,
|
¢er,
|
||||||
SDL_FLIP_NONE
|
SDL_FLIP_NONE
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Tiled rendering - render the texture repeatedly to fill the area defined by the entity's transform
|
||||||
|
// We assume that we always render scaleX by scaleY tiles, and that the texture should be rendered at its original size (i.e., the scaleX and scaleY of the entity only affect how many times the texture is tiled, not the size of each tile)
|
||||||
|
float tileW, tileH;
|
||||||
|
SDL_GetTextureSize(mTex->getSDLTexture(), &tileW, &tileH);
|
||||||
|
|
||||||
|
SDL_FRect dst;
|
||||||
|
dst.w = tileW * mTiledScale; // Tile size is the original texture size multiplied by the universal scale coefficient (ignoring the entity's scale, since that only affects how many times the texture is tiled, not the size of each tile)
|
||||||
|
dst.h = tileH * mTiledScale;
|
||||||
|
|
||||||
|
// Top-left origin; Account for camera position (center the camera on the screen)
|
||||||
|
dst.x = mTransform.x - config.camX + config.screenW / 2.f;
|
||||||
|
dst.y = mTransform.y - config.camY + config.screenH / 2.f;
|
||||||
|
|
||||||
|
for (int i = 0; i < mTransform.scaleX; i++) {
|
||||||
|
for (int j = 0; j < mTransform.scaleY; j++) {
|
||||||
|
SDL_RenderTextureRotated(
|
||||||
|
renderer->getSDLRenderer(),
|
||||||
|
mTex->getSDLTexture(),
|
||||||
|
nullptr,
|
||||||
|
&dst,
|
||||||
|
mTransform.rotation,
|
||||||
|
nullptr, // No rotation center since each tile is rendered independently
|
||||||
|
SDL_FLIP_NONE
|
||||||
|
);
|
||||||
|
dst.y += dst.h; // Move down for the next tile in the column
|
||||||
|
}
|
||||||
|
dst.y = mTransform.y - config.camY + config.screenH / 2.f; // Reset y to the top of the column
|
||||||
|
dst.x += dst.w; // Move right for the next column of tiles
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +128,8 @@ namespace Game::Object {
|
|||||||
}
|
}
|
||||||
mAudioStream = nullptr;
|
mAudioStream = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG("Destroyed Sound");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sound::play() {
|
void Sound::play() {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include <utils.hpp>
|
#include <utils.hpp>
|
||||||
#include <state/gamestate.hpp>
|
#include <state/gamestate.hpp>
|
||||||
#include <object/entity.hpp>
|
#include <object/entity.hpp>
|
||||||
|
#include <window/window.hpp>
|
||||||
|
#include <object/camera.hpp>
|
||||||
|
|
||||||
namespace Game::Renderer {
|
namespace Game::Renderer {
|
||||||
Renderer::Renderer() : mRenderer(nullptr) {}
|
Renderer::Renderer() : mRenderer(nullptr) {}
|
||||||
@@ -39,12 +41,20 @@ namespace Game::Renderer {
|
|||||||
void Renderer::renderFrame() {
|
void Renderer::renderFrame() {
|
||||||
mClear();
|
mClear();
|
||||||
|
|
||||||
|
float camX, camY;
|
||||||
|
Object::Camera::getInstance().getPosition(camX, camY);
|
||||||
|
int screenW, screenH;
|
||||||
|
SDL_GetWindowSizeInPixels(Window::Window::getSDLWindowBackend(), &screenW, &screenH);
|
||||||
|
// Pass the config to avoid wasting time recalculating it for every entity, since it's not gonna change during the frame
|
||||||
|
RendererConfig config{ camX, camY, screenW, screenH };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Game::State::GameState::getInstance().withEntitiesLocked([this](auto& entities) {
|
auto entities = Game::State::GameState::getInstance().getEntitiesSnapshot(true);
|
||||||
for (auto& entity : entities) {
|
for (auto* entity : entities) {
|
||||||
entity->render(this);
|
if (entity) {
|
||||||
|
entity->render(this, config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
ERROR("Exception while rendering frame: " << e.what());
|
ERROR("Exception while rendering frame: " << e.what());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,49 @@ namespace Game::State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameState::addEntity(std::unique_ptr<Object::Entity> entity) {
|
void GameState::addEntity(std::unique_ptr<Object::Entity> entity) {
|
||||||
|
Object::Entity* addedEntity = nullptr;
|
||||||
|
{
|
||||||
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
|
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
|
||||||
mEntities.push_back(std::move(entity));
|
mEntities.push_back(std::move(entity));
|
||||||
|
addedEntity = mEntities.back().get();
|
||||||
|
}
|
||||||
|
|
||||||
Object::Entity* addedEntity = mEntities.back().get();
|
addedEntity->start(); // Initialize the entity after insertion without holding the GameState lock.
|
||||||
addedEntity->start(); // Initialize the entity immediately after insertion.
|
|
||||||
LOG("Added entity '" << addedEntity->getName() << "' to GameState");
|
LOG("Added entity '" << addedEntity->getName() << "' to GameState");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameState::sort() {
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
|
||||||
|
std::sort(mEntities.begin(), mEntities.end(), [](const std::unique_ptr<Object::Entity>& a, const std::unique_ptr<Object::Entity>& b) {
|
||||||
|
return a->getZIndex() < b->getZIndex();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object::Entity* GameState::getEntityByName(const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
|
||||||
|
for (const auto& entity : mEntities) {
|
||||||
|
if (entity->getName() == name) {
|
||||||
|
return entity.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr; // Return nullptr if no entity with the name exists
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Object::Entity*> GameState::getEntitiesSnapshot(bool sortByZIndex) {
|
||||||
|
std::vector<Object::Entity*> snapshot;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
snapshot.reserve(mEntities.size());
|
||||||
|
for (const auto& entity : mEntities) {
|
||||||
|
snapshot.push_back(entity.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortByZIndex) {
|
||||||
|
std::sort(snapshot.begin(), snapshot.end(), [](Object::Entity* a, Object::Entity* b) {
|
||||||
|
return a->getZIndex() < b->getZIndex();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <window/window.hpp>
|
#include <window/window.hpp>
|
||||||
|
|
||||||
namespace Game::Window {
|
namespace Game::Window {
|
||||||
|
std::mutex Window::sMutex;
|
||||||
|
|
||||||
Window::Window() : mWindow(nullptr), mRenderer(), mGameManager(), mRunning(false) { }
|
Window::Window() : mWindow(nullptr), mRenderer(), mGameManager(), mRunning(false) { }
|
||||||
|
|
||||||
Window::~Window() {
|
Window::~Window() {
|
||||||
@@ -64,19 +66,28 @@ namespace Game::Window {
|
|||||||
void Window::run() {
|
void Window::run() {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (mRunning) {
|
while (mRunning) {
|
||||||
|
SDL_PumpEvents();
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_EVENT_QUIT) {
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle other events (e.g., keyboard, mouse) here
|
// Window resize event - update the renderer's viewport
|
||||||
}
|
if (event.type == SDL_EVENT_WINDOW_RESIZED) {
|
||||||
|
std::scoped_lock lock(mMutex);
|
||||||
|
SDL_SetRenderViewport(mRenderer.getSDLRenderer(), nullptr);
|
||||||
|
|
||||||
/*
|
// Notify entities of the window resize so they can adjust if necessary
|
||||||
auto entities = State::GameState::getInstance().getEntitiesRef();
|
int newWidth, newHeight;
|
||||||
for (auto& entity : *entities) {
|
SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);
|
||||||
entity->update();
|
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
|
||||||
}*/
|
for (auto* entity : entities) {
|
||||||
|
if (entity) {
|
||||||
|
entity->onWindowResized(newWidth, newHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mRenderer.renderFrame();
|
mRenderer.renderFrame();
|
||||||
SDL_Delay(1000 / mTargetFPS); // Delay to cap the frame rate to the target FPS
|
SDL_Delay(1000 / mTargetFPS); // Delay to cap the frame rate to the target FPS
|
||||||
|
|||||||
Reference in New Issue
Block a user