basic movement

This commit is contained in:
2026-03-14 22:27:54 +01:00
parent b19f595daf
commit 2983b919cd
24 changed files with 368 additions and 57 deletions

View 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);
}
}

View 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);
}
}
}

View File

@@ -5,26 +5,33 @@
namespace Game::AGame {
void Player::start() {
mSound = Object::Sound("../resources/example.wav", Object::Format::WAV);
mSound.play();
//mSound.play();
int 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.y = h / 2.f - (mTex->getHeight() / 2.f * mTransform.scaleY * 0.25f);
mTransform.rotation = 0.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 Player::update(float deltaTime) {
if (!mIsActive) return;
//LOG("Updated Player");
//mTransform.x += 1.f; // Move right at a constant speed for testing
mTransform.rotation += 1.f; // Rotate clockwise for testing
//LOG(mName << " position: " << mTransform.x << ' ' << mTransform.y);
//LOG("DeltaTime: " << deltaTime);
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
//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);
// 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;
}
}

View File

@@ -5,6 +5,7 @@ namespace Game {
void GameManager::run(std::stop_token stopToken) {
using namespace std::chrono_literals;
LOG("GameManager thread started");
Object::Camera::getInstance().setPosition(0.f, 0.f); // Start with camera at (0, 0)
mLastUpdate = clock::now(); // Get the update
@@ -18,11 +19,12 @@ namespace Game {
const auto frameStart = std::chrono::steady_clock::now();
try {
State::GameState::getInstance().withEntitiesLocked([seconds](auto& entities) {
for (auto& entity : entities) {
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
for (auto* entity : entities) {
if (entity) {
entity->update(seconds);
}
});
}
} catch (const std::exception& e) {
ERROR("Exception in GameManager thread: " << e.what());
}

View File

@@ -4,6 +4,8 @@
#include <object/entity.hpp>
#include <object/transform.hpp>
#include <game/agame/player.hpp>
#include <game/agame/background.hpp>
#include <game/agame/camcontroller.hpp>
#include <renderer/renderer.hpp>
#include <renderer/texture.hpp>
#include <renderer/font.hpp>
@@ -14,7 +16,10 @@ int main() {
Window::Window window = Window::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};
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>("Player2", std::make_shared<Game::Renderer::Font>("../resources/roboto.ttf", window.getRenderer()->getSDLRenderer(), 128, "Roboto"), t1));

26
src/object/camera.cpp Normal file
View 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;
}
}

View File

@@ -1,6 +1,9 @@
#include <object/entity.hpp>
#include <renderer/renderer.hpp>
#include <renderer/texture.hpp>
#include <object/camera.hpp>
#include <window/window.hpp>
#include <cmath>
namespace Game::Object {
Entity::~Entity() = default;
@@ -35,32 +38,64 @@ namespace Game::Object {
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
float w, h;
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
if (!mTex->isTiled()) {
float w, h;
SDL_GetTextureSize(mTex->getSDLTexture(), &w, &h);
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.h = h * mTransform.scaleY * mScaleConstant;
SDL_FRect dst;
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 * UNIVERSAL_SCALE_COEFFICIENT;
// Top-left origin
dst.x = mTransform.x;
dst.y = mTransform.y;
// 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;
SDL_FPoint center;
center.x = dst.w / 2.f;
center.y = dst.h / 2.f;
SDL_FPoint center;
center.x = dst.w / 2.f;
center.y = dst.h / 2.f;
SDL_RenderTextureRotated(
renderer->getSDLRenderer(),
mTex->getSDLTexture(),
nullptr,
&dst,
mTransform.rotation,
&center,
SDL_FLIP_NONE
);
SDL_RenderTextureRotated(
renderer->getSDLRenderer(),
mTex->getSDLTexture(),
nullptr,
&dst,
mTransform.rotation,
&center,
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
}
}
}
}

View File

@@ -128,6 +128,8 @@ namespace Game::Object {
}
mAudioStream = nullptr;
}
LOG("Destroyed Sound");
}
void Sound::play() {

View File

@@ -2,6 +2,8 @@
#include <utils.hpp>
#include <state/gamestate.hpp>
#include <object/entity.hpp>
#include <window/window.hpp>
#include <object/camera.hpp>
namespace Game::Renderer {
Renderer::Renderer() : mRenderer(nullptr) {}
@@ -39,12 +41,20 @@ namespace Game::Renderer {
void Renderer::renderFrame() {
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 {
Game::State::GameState::getInstance().withEntitiesLocked([this](auto& entities) {
for (auto& entity : entities) {
entity->render(this);
auto entities = Game::State::GameState::getInstance().getEntitiesSnapshot(true);
for (auto* entity : entities) {
if (entity) {
entity->render(this, config);
}
});
}
} catch (const std::exception& e) {
ERROR("Exception while rendering frame: " << e.what());
}

View File

@@ -13,11 +13,49 @@ namespace Game::State {
}
void GameState::addEntity(std::unique_ptr<Object::Entity> entity) {
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
mEntities.push_back(std::move(entity));
Object::Entity* addedEntity = nullptr;
{
std::lock_guard<std::mutex> lock(mMutex); // Lock the mutex for thread safety
mEntities.push_back(std::move(entity));
addedEntity = mEntities.back().get();
}
Object::Entity* addedEntity = mEntities.back().get();
addedEntity->start(); // Initialize the entity immediately after insertion.
addedEntity->start(); // Initialize the entity after insertion without holding the GameState lock.
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;
}
}

View File

@@ -1,6 +1,8 @@
#include <window/window.hpp>
namespace Game::Window {
std::mutex Window::sMutex;
Window::Window() : mWindow(nullptr), mRenderer(), mGameManager(), mRunning(false) { }
Window::~Window() {
@@ -64,19 +66,28 @@ namespace Game::Window {
void Window::run() {
SDL_Event event;
while (mRunning) {
SDL_PumpEvents();
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
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);
/*
auto entities = State::GameState::getInstance().getEntitiesRef();
for (auto& entity : *entities) {
entity->update();
}*/
// Notify entities of the window resize so they can adjust if necessary
int newWidth, newHeight;
SDL_GetWindowSizeInPixels(mWindow, &newWidth, &newHeight);
auto entities = State::GameState::getInstance().getEntitiesSnapshot();
for (auto* entity : entities) {
if (entity) {
entity->onWindowResized(newWidth, newHeight);
}
}
}
}
mRenderer.renderFrame();
SDL_Delay(1000 / mTargetFPS); // Delay to cap the frame rate to the target FPS