Merge pull request 'v2' (#1) from v2 into main

Reviewed-on: #1
This commit is contained in:
Alfie King 2025-06-16 02:16:45 +00:00
commit a5e34055ce
26 changed files with 550 additions and 788 deletions

3
.gitignore vendored
View File

@ -1,3 +1,2 @@
.vscode
build
main.x86_64
build

View File

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2024 acetheking987
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,13 +0,0 @@
# bootleg-game-engine
A bootleg "game engine" in c++, using SDL2 for rendering and input handling.
## Dependencies
- SDL2
- SDL2_ttf
- SDL2_mixer
## Building
```bash
$ make
```

BIN
assets/bird.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 MiB

59
include/assets.hpp Normal file
View File

@ -0,0 +1,59 @@
#ifndef ASSETS_HPP
#define ASSETS_HPP
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <unordered_map>
#include <string>
// Structs
struct Rect {
int x;
int y;
int width;
int height;
Rect(int x = 0, int y = 0, int width = 0, int height = 0)
: x(x), y(y), width(width), height(height) {}
Rect(const SDL_Rect& rect)
: x(rect.x), y(rect.y), width(rect.w), height(rect.h) {}
};
struct Tile {
Rect srcRect;
std::string assetName;
};
// Classes
class AssetManager {
public:
AssetManager();
~AssetManager();
void load(const std::string& name, const std::string& filePath);
SDL_Surface* getAsset(const std::string& name) const;
void unloadAsset(const std::string& name);
void clearAssets();
private:
std::unordered_map<std::string, SDL_Surface*> assets;
};
struct TileMap {
public:
TileMap(AssetManager& assetManager);
~TileMap();
void addTile(const std::string& name, const Tile& tile);
Tile getTile(const std::string& name) const;
SDL_Surface* getTileAsset(const std::string& name) const;
Rect getTileRect(const std::string& name) const;
void removeTile(const std::string& name);
void clear();
private:
std::unordered_map<std::string, Tile> tiles;
AssetManager& assetManager;
};
#endif

View File

@ -1,30 +0,0 @@
#include "input.h"
#include "text.h"
#include "sprite.h"
#include "vector.h"
#include <SDL.h>
#include <string>
#include <vector>
#include "sound.h"
class Engine
{
private:
const char* ctitle;
int frameStart = 0;
bool soundInitialized = false;
public:
Input input;
int targetFrameRate = 60;
SDL_Window *window;
SDL_Renderer *renderer;
Engine(std::string title, int width, int height);
void initSound();
void clear(SDL_Color color);
void startFrame();
void render();
void update();
~Engine();
};

50
include/engine.hpp Normal file
View File

@ -0,0 +1,50 @@
#ifndef RENDER_HPP
#define RENDER_HPP
// Include necessary headers
#include "assets.hpp"
#include "input.hpp"
#include <SDL3/SDL.h>
// Structs
struct Color {
Uint8 r;
Uint8 g;
Uint8 b;
Uint8 a;
Color(Uint8 r = 0, Uint8 g = 0, Uint8 b = 0, Uint8 a = 255) : r(r), g(g), b(b), a(a) {}
};
// Classes
class Engine {
public:
Engine(const char* windowTitle, int windowWidth, int windowHeight, int windowFlags);
Engine(const char* windowTitle, int windowWidth, int windowHeight)
: Engine(windowTitle, windowWidth, windowHeight, 0) {}
~Engine();
void targetFPS(int fps);
void startFrame();
void clearScreen(const Color& color);
void draw(SDL_Surface* surface, Rect destRect);
void draw(SDL_Surface* surface, Rect srcRect, Rect destRect);
void draw(Tile tile, Rect destRect);
void draw(SDL_Surface* surface, Vector2 pos) { draw(surface, Rect(pos.x, pos.y, surface->w, surface->h)); }
void draw(SDL_Surface* surface, Rect srcRect, Vector2 pos) { draw(surface, srcRect, Rect(pos.x, pos.y, srcRect.width, srcRect.height)); }
void draw(Tile tile, Vector2 pos) { draw(tile, Rect(pos.x, pos.y, tile.srcRect.width, tile.srcRect.height)); }
void present();
Input input; // Input handler
AssetManager assets; // Asset manager
private:
SDL_Window* window;
SDL_Renderer* renderer;
int targetFPSValue = 60; // Target frames per second
Uint64 frameStartTime = 0; // Start time of the current frame
Uint64 frameEndTime = 0; // End time of the current frame
Uint64 frameDuration = 0; // Duration of the current frame in milliseconds
};
#endif

View File

@ -1,61 +0,0 @@
#include <SDL.h>
#include <map>
#include <string>
#include <vector>
enum CBUTTONS
{
DPAD_UP = SDL_CONTROLLER_BUTTON_DPAD_UP,
DPAD_DOWN = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
DPAD_LEFT = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
DPAD_RIGHT = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
A = SDL_CONTROLLER_BUTTON_A,
B = SDL_CONTROLLER_BUTTON_B,
X = SDL_CONTROLLER_BUTTON_X,
Y = SDL_CONTROLLER_BUTTON_Y,
BACK = SDL_CONTROLLER_BUTTON_BACK,
GUIDE = SDL_CONTROLLER_BUTTON_GUIDE,
START = SDL_CONTROLLER_BUTTON_START,
LEFTSTICK = SDL_CONTROLLER_BUTTON_LEFTSTICK,
RIGHTSTICK = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
LEFTSHOULDER = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
RIGHTSHOULDER = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
};
struct Controller
{
SDL_GameController* controller;
std::string name;
std::map<Uint8, bool> buttons;
Sint16 leftStickX = 0;
Sint16 leftStickY = 0;
Sint16 rightStickX = 0;
Sint16 rightStickY = 0;
Sint16 leftTrigger = 0;
Sint16 rightTrigger = 0;
};
struct Mouse
{
int x;
int y;
int wheel;
std::map<int, bool> buttons;
};
class Input
{
private:
SDL_Event event;
public:
bool exit;
std::map<int, Controller> controllers;
std::map<SDL_Keycode, bool> activeKeys;
Mouse mouse;
Input();
void update();
bool keysPressed(std::vector<SDL_Keycode> keys);
bool controllerPressed(std::vector<Uint8> buttons, int controller = 0);
};

45
include/input.hpp Normal file
View File

@ -0,0 +1,45 @@
#ifndef INPUT_HPP
#define INPUT_HPP
#include <SDL3/SDL.h>
#include <unordered_map>
#include <vector>
// Structs
struct Vector2 {
int x;
int y;
Vector2(int x = 0, int y = 0) : x(x), y(y) {}
};
// Classes
class Input {
public:
Input();
~Input();
void handleEvents();
bool quitRequested() const;
bool isKeyPressed(SDL_Scancode key);
bool isKeyPressed(std::vector<SDL_Scancode> keys);
bool hasKeyBeenPressed(SDL_Scancode key);
bool hasKeyBeenPressed(std::vector<SDL_Scancode> keys);
bool isMouseButtonPressed(Uint8 button);
bool isMouseButtonPressed(std::vector<Uint8> buttons);
bool hasMouseButtonBeenPressed(Uint8 button);
bool hasMouseButtonBeenPressed(std::vector<Uint8> buttons);
Vector2 getMousePosition() const;
private:
SDL_Event event;
std::unordered_map<SDL_Scancode, bool> keyStates;
std::unordered_map<SDL_Scancode, bool> keyFallingEdgeStates;
std::unordered_map<Uint8, bool> mouseButtonStates;
std::unordered_map<Uint8, bool> mouseButtonFallingEdgeStates;
Vector2 mousePosition;
bool quitRequestedFlag = false;
};
#endif

View File

@ -1,26 +0,0 @@
#include <SDL_mixer.h>
class Music
{
private:
Mix_Music* music;
public:
Music(const char* path);
void play(int loops = -1);
void pause();
void stop();
void resume();
void setVolume(int volume);
~Music();
};
class Effect
{
private:
Mix_Chunk* effect;
public:
Effect(const char* path);
void setVolume(int volume);
void play(int loops = 0, int channel = -1);
~Effect();
};

View File

@ -1,68 +0,0 @@
#include <SDL.h>
#include <map>
#include <string>
#include <vector>
#include "vector.h"
#pragma once
class Sprite
{
private:
std::map<std::string, SDL_Rect> frames;
SDL_Surface* surface;
SDL_Texture* Texture;
int_vec2 size;
public:
SDL_Rect dstrect;
int_vec2 scale = { 1, 1 };
double angle = 0;
Sprite(SDL_Renderer* renderer, SDL_Surface* surface);
void draw(SDL_Renderer* renderer, std::string frame, bool autoRect = true, bool flip = false);
void addFrame(std::string name, SDL_Rect rect);
void move(int_vec2 position);
void setPos(int_vec2 position);
int_vec2 getSize(std::string frame);
void autorect(std::string frame);
~Sprite();
};
class Animation
{
private:
std::vector<std::string> frames;
Sprite* sprite;
int frameDuration = 0;
int frameCounter = 0;
std::string currentFrameName;
public:
bool end = false;
int frameDirection = 1;
int frameIndex = 0;
bool loop = true;
bool pingpong = false;
Animation(std::map<std::string, SDL_Rect> frames, Sprite* sprite, int frameDuration);
void update();
void draw(SDL_Renderer* renderer, bool autoRect = true, bool flip = false);
void reset();
};
class AnimationManager
{
private:
std::map<std::string, Animation*> animations;
std::map<std::string, bool> flip;
std::map<std::string, bool> autoRect;
std::string currentAnimation;
bool playing = false;
Sprite* sprite;
public:
AnimationManager(Sprite* sprite);
void addAnimation(std::string name, std::map<std::string, SDL_Rect> animation, int frameDuration = 10, bool flip = false, bool autoRect = true, bool loop = true, bool pingpong = false);
void setAnimation(std::string name);
void playAnimation(std::string name);
void update();
void draw(SDL_Renderer* renderer);
};

View File

@ -1,17 +0,0 @@
#include <SDL.h>
#include <SDL_ttf.h>
#include <string>
class Text
{
private:
SDL_Surface* surface;
SDL_Texture* texture;
TTF_Font* font;
SDL_Color color;
SDL_Rect rect;
public:
Text(TTF_Font* font, SDL_Color color);
void draw(SDL_Renderer* renderer, std::string text, int x, int y);
};

View File

@ -1,58 +0,0 @@
// prevent multiple inclusion
#pragma once
// class for 2D float vector
class float_vec2
{
// public members
public:
// x and y coordinates
float x, y;
// operator functions
float_vec2 operator+(float_vec2 vec)
{
return { x + vec.x, y + vec.y };
}
float_vec2 operator*(float v)
{
return { x * v, y * v };
}
float_vec2 operator*(float_vec2 vec)
{
return { x * vec.x, y * vec.y };
}
float_vec2 operator-(float_vec2 vec)
{
return { x - vec.x, y - vec.y };
}
float_vec2 operator-(float v)
{
return { x - v, y - v };
}
};
// class for 2D integer vector
class int_vec2
{
// public members
public:
// x and y coordinates
int x, y;
// operator functions
int_vec2 operator+(int_vec2 vec)
{
return { x + vec.x, y + vec.y };
}
int_vec2 operator*(int v)
{
return { x * v, y * v };
}
int_vec2 operator*(int_vec2 vec)
{
return { x * vec.x, y * vec.y };
}
int_vec2 operator-(int_vec2 vec)
{
return { x - vec.x, y - vec.y };
}
};

View File

@ -1,20 +1,39 @@
# Compiler and linker settings
CXX = g++
LDFLAGS = `sdl2-config --libs` -l SDL2_ttf -l SDL2_mixer
CXXFLAGS = `sdl2-config --cflags` -pedantic -O2 -std=c++17 -lX11 -lstdc++fs -I include
LDFLAGS = -lSDL3 -lSDL3_image
CXXFLAGS = -pedantic -std=c++17 -I include -I /usr/include/SDL3_image -I /usr/include/SDL3
# Source and object files
SRC = $(wildcard src/*.cpp)
OBJ = $(addprefix build/, $(notdir $(SRC:.cpp=.o)))
# Target executable
TARGET = build/x86_64/main.x86_64
all: dirs $(TARGET)
# Default target
all: clean dirs $(TARGET)
# development target with debugging
dev: CXXFLAGS += -g
dev: all
# Release target
release: CXXFLAGS += -O3
release: all
# Create build directory if it doesn't exist
dirs:
@mkdir -p build/x86_64
# Build target
$(TARGET): $(OBJ)
@$(CXX) -o $@ $^ $(LDFLAGS)
$(CXX) -o $@ $^ $(LDFLAGS)
# Build object files
build/%.o: src/%.cpp
@$(CXX) -c -o $@ $< $(CXXFLAGS)
$(CXX) -c -o $@ $< $(CXXFLAGS)
# Clean up build files
clean:
@rm -f $(OBJ) $(TARGET)
@rm -f $(OBJ) $(TARGET)
@echo "Cleaned up build files."

4
readme.md Normal file
View File

@ -0,0 +1,4 @@
# Bootleg Game Engine V2
## Overview
This is a lightweight "game engine", designed to be simple and easy to use for small projects. It provides basic functionality for rendering, input handling, and game loop management.

93
src/assets.cpp Normal file
View File

@ -0,0 +1,93 @@
#include "assets.hpp"
AssetManager::AssetManager() {
}
AssetManager::~AssetManager() {
clearAssets();
}
void AssetManager::load(const std::string& name, const std::string& filePath) {
SDL_Surface* surface = IMG_Load(filePath.c_str());
if (!surface) {
SDL_Log("Failed to load image %s: %s", filePath.c_str(), SDL_GetError());
}
assets[name] = surface;
}
SDL_Surface* AssetManager::getAsset(const std::string& name) const {
auto it = assets.find(name);
if (it != assets.end()) {
return it->second;
}
SDL_Log("Asset %s not found", name.c_str());
return nullptr;
}
void AssetManager::unloadAsset(const std::string& name) {
auto it = assets.find(name);
if (it != assets.end()) {
SDL_DestroySurface(it->second);
assets.erase(it);
}
SDL_Log("Asset %s not found", name.c_str());
}
void AssetManager::clearAssets() {
for (auto& pair : assets) {
SDL_DestroySurface(pair.second);
}
assets.clear();
}
TileMap::TileMap(AssetManager& assetManager)
: assetManager(assetManager) {
}
TileMap::~TileMap() {
clear();
}
void TileMap::addTile(const std::string& name, const Tile& tile) {
tiles[name] = tile;
}
Tile TileMap::getTile(const std::string& name) const {
auto it = tiles.find(name);
if (it != tiles.end()) {
return it->second;
}
SDL_Log("Tile %s not found", name.c_str());
return Tile(); // Return an empty Tile
}
SDL_Surface* TileMap::getTileAsset(const std::string& name) const {
auto it = tiles.find(name);
if (it != tiles.end()) {
return assetManager.getAsset(it->second.assetName);
}
SDL_Log("Tile %s not found", name.c_str());
return nullptr;
}
Rect TileMap::getTileRect(const std::string& name) const {
auto it = tiles.find(name);
if (it != tiles.end()) {
return Rect(it->second.srcRect);
}
SDL_Log("Tile %s not found", name.c_str());
return Rect(); // Return an empty Rect
}
void TileMap::removeTile(const std::string& name) {
auto it = tiles.find(name);
if (it != tiles.end()) {
tiles.erase(it);
} else {
SDL_Log("Tile %s not found", name.c_str());
}
}
void TileMap::clear() {
tiles.clear();
}

View File

@ -1,78 +1,138 @@
#include "engine.h"
#include "engine.hpp"
Engine::Engine(std::string title, int width, int height)
{
// convert title to char*
ctitle = title.c_str();
Engine::Engine(const char* windowTitle, int windowWidth, int windowHeight, int windowFlags) {
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("SDL could not initialize! SDL_Error: %s", SDL_GetError());
return;
}
// Initialize SDL and TFF;
TTF_Init();
SDL_Init(SDL_INIT_EVERYTHING);
// Create the window
window = SDL_CreateWindow(windowTitle, windowWidth, windowHeight, windowFlags);
if (!window) {
SDL_Log("Window could not be created! SDL_Error: %s", SDL_GetError());
return;
}
// Create window and renderer
window = SDL_CreateWindow(
ctitle,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
width,
height,
SDL_WINDOW_SHOWN
);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// Create the renderer
renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) {
SDL_Log("Renderer could not be created! SDL_Error: %s", SDL_GetError());
return;
}
}
void Engine::initSound()
{
// Initialize sound
Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
soundInitialized = true;
Engine::~Engine() {
// Destroy the asset manager
assets.clearAssets();
// Destroy the renderer
if (renderer) {
SDL_DestroyRenderer(renderer);
}
// Destroy the window
if (window) {
SDL_DestroyWindow(window);
}
// Quit SDL subsystems
SDL_Quit();
}
void Engine::clear(SDL_Color color)
{
// Set color
void Engine::clearScreen(const Color& color) {
// Set the draw color
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
// Clear screen
// Clear the screen
SDL_RenderClear(renderer);
}
void Engine::startFrame()
{
// Start frame
frameStart = SDL_GetTicks();
}
void Engine::render()
{
// Render screen
void Engine::present() {
// Present the renderer
SDL_RenderPresent(renderer);
if (1000 / targetFrameRate > SDL_GetTicks() - frameStart)
{
SDL_Delay(1000 / targetFrameRate - (SDL_GetTicks() - frameStart));
// Calculate frame duration
frameEndTime = SDL_GetTicks();
frameDuration = frameEndTime - frameStartTime;
// Calculate delay to maintain target FPS
if (targetFPSValue > 0) {
Uint32 targetFrameTime = 1000 / targetFPSValue;
if (frameDuration < targetFrameTime) {
SDL_Delay(targetFrameTime - frameDuration);
}
}
}
void Engine::update()
{
// Update input
input.update();
}
Engine::~Engine()
{
// Free renderer and window
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
// If sound is initialized, quit Mix
if (soundInitialized)
{
Mix_Quit();
void Engine::draw(SDL_Surface* surface, Rect destRect) {
// Draw a surface to the renderer at the specified destination rectangle
if (!surface) {
SDL_Log("Cannot draw null surface");
return;
}
// Quit SDL and TTF
SDL_Quit();
TTF_Quit();
SDL_FRect sdlDestRect = {
static_cast<float>(destRect.x),
static_cast<float>(destRect.y),
static_cast<float>(destRect.width),
static_cast<float>(destRect.height)
};
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (!texture) {
SDL_Log("Failed to create texture from surface: %s", SDL_GetError());
return;
}
SDL_RenderTexture(renderer, texture, nullptr, &sdlDestRect);
SDL_DestroyTexture(texture);
}
void Engine::draw(SDL_Surface* surface, Rect srcRect, Rect destRect) {
// Draw a surface to the renderer with specified source and destination rectangles
if (!surface) {
SDL_Log("Cannot draw null surface");
return;
}
SDL_FRect sdlSrcRect = {
static_cast<float>(srcRect.x),
static_cast<float>(srcRect.y),
static_cast<float>(srcRect.width),
static_cast<float>(srcRect.height)
};
SDL_FRect sdlDestRect = {
static_cast<float>(destRect.x),
static_cast<float>(destRect.y),
static_cast<float>(destRect.width),
static_cast<float>(destRect.height)
};
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (!texture) {
SDL_Log("Failed to create texture from surface: %s", SDL_GetError());
return;
}
SDL_RenderTexture(renderer, texture, &sdlSrcRect, &sdlDestRect);
SDL_DestroyTexture(texture);
}
void Engine::draw(Tile tile, Rect destRect) {
// Draw a tile to the renderer at the specified destination rectangle
SDL_Surface* surface = assets.getAsset(tile.assetName);
if (!surface) {
SDL_Log("Tile asset %s not found", tile.assetName.c_str());
return;
}
draw(surface, tile.srcRect, destRect);
}
void Engine::startFrame() {
// Start the frame timer
frameStartTime = SDL_GetTicks();
}
void Engine::targetFPS(int fps) {
// Set the target frames per second
targetFPSValue = fps;
}

View File

@ -1,133 +0,0 @@
// Includes
#include "engine.h"
// Variables
SDL_Surface* assets;
Music* music;
Engine *engine;
// Functions
// Quit function
void quit()
{
// Free assets
SDL_FreeSurface(assets);
delete engine;
// Exit program
exit(0);
}
// Main function
int main(int argc, char* argv[])
{
engine = new Engine("Game", 800, 600);
engine->initSound();
engine->targetFrameRate = 60;
// Load assets
assets = SDL_LoadBMP("example_assets/sprites.bmp");
music = new Music("example_assets/crystal_zone.mp3");
// frame map
std::map<std::string, SDL_Rect> idle = {
{ "idle", { 76, 456, 68, 128 } },
};
std::map<std::string, SDL_Rect> front = {
{ "front", { 4, 456, 68, 125 } },
{ "front2", { 76, 456, 68, 128 } },
{ "front3", { 148, 456, 68, 125 } }
};
std::map<std::string, SDL_Rect> back = {
{ "back", { 662, 456, 68, 125 } },
{ "back2", { 734, 456, 68, 128 } },
{ "back3", { 806, 456, 68, 125 } }
};
std::map<std::string, SDL_Rect> walk = {
{ "walk", { 220, 456, 68, 124 } },
{ "walk2", { 292, 456, 73, 127 } },
{ "walk3", { 369, 456, 68, 124 } }
};
std::map<std::string, SDL_Rect> test = {
{ "test", { 4, 2095, 68, 59 } },
{ "test2", { 76, 2095, 68, 59 } }
};
// Create sprite and animation
Sprite* sprite = new Sprite(engine->renderer, assets);
AnimationManager* animations = new AnimationManager(sprite);
animations->addAnimation("idle", idle, 10, false, true, false, false);
animations->addAnimation("walk_left", walk, 10, false, true, true, false);
animations->addAnimation("walk_right", walk, 10, true, true, true, false);
animations->addAnimation("walk_back", back, 10, false, true, true, false);
animations->addAnimation("walk_front", front, 10, false, true, true, true);
animations->addAnimation("test", test, 20, false, true, false, false);
animations->setAnimation("idle");
// Play music
music->play();
// Main loop
while (true)
{
// Start time
engine->startFrame();
// Update input
engine->update();
// Exit if window s closed
if (engine->input.exit || engine->input.activeKeys[SDLK_ESCAPE])
{
quit();
}
if (engine->input.activeKeys[SDLK_d] || engine->input.controllers[0].buttons[CBUTTONS::DPAD_RIGHT] || engine->input.controllers[0].leftStickX > 8000)
{
animations->setAnimation("walk_right");
sprite->move({ 1, 0 });
}
if (engine->input.activeKeys[SDLK_a] || engine->input.controllers[0].buttons[CBUTTONS::DPAD_LEFT] || engine->input.controllers[0].leftStickX < -8000)
{
animations->setAnimation("walk_left");
sprite->move({ -1, 0 });
}
if (engine->input.activeKeys[SDLK_w] || engine->input.controllers[0].buttons[CBUTTONS::DPAD_UP] || engine->input.controllers[0].leftStickY < -8000)
{
animations->setAnimation("walk_back");
sprite->move({ 0, -1 });
}
if (engine->input.activeKeys[SDLK_s] || engine->input.controllers[0].buttons[CBUTTONS::DPAD_DOWN] || engine->input.controllers[0].leftStickY > 8000)
{
animations->setAnimation("walk_front");
sprite->move({ 0, 1 });
}
if (engine->input.activeKeys[SDLK_SPACE] || engine->input.controllers[0].buttons[CBUTTONS::A])
{
animations->playAnimation("test");
}
if (!engine->input.keysPressed({ SDLK_w, SDLK_a, SDLK_s, SDLK_d }) && engine->input.controllers[0].leftStickX < 8000 && engine->input.controllers[0].leftStickX > -8000 &&
engine->input.controllers[0].leftStickY < 8000 && engine->input.controllers[0].leftStickY > -8000 &&
!engine->input.controllerPressed({ CBUTTONS::DPAD_UP, CBUTTONS::DPAD_DOWN, CBUTTONS::DPAD_LEFT, CBUTTONS::DPAD_RIGHT }))
{
animations->setAnimation("idle");
}
// Update animation
animations->update();
// Clear screen
engine->clear({ 100, 150, 255, 255 });
// Draw sprite
animations->draw(engine->renderer);
// Update screen
engine->render();
}
return 0;
}

View File

@ -1,78 +1,112 @@
#include "input.h"
#include "input.hpp"
Input::Input() {
Input::Input()
{
SDL_Init(SDL_INIT_JOYSTICK);
exit = false;
}
void Input::update()
{
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_CONTROLLERBUTTONDOWN:
controllers[event.cbutton.which].buttons[event.cbutton.button] = true;
break;
case SDL_CONTROLLERBUTTONUP:
controllers[event.cbutton.which].buttons[event.cbutton.button] = false;
break;
case SDL_MOUSEBUTTONDOWN:
mouse.buttons[event.button.button] = true;
break;
case SDL_MOUSEBUTTONUP:
mouse.buttons[event.button.button] = false;
break;
case SDL_KEYDOWN:
activeKeys[event.key.keysym.sym] = true;
break;
case SDL_KEYUP:
activeKeys.erase(event.key.keysym.sym);
break;
case SDL_CONTROLLERDEVICEADDED:
controllers[SDL_NumJoysticks() - 1] = {
SDL_GameControllerOpen(SDL_NumJoysticks() - 1),
SDL_GameControllerName(SDL_GameControllerOpen(SDL_NumJoysticks() - 1))
};
printf("Controller added: %s\n", controllers[SDL_NumJoysticks() - 1].name.c_str());
break;
case SDL_QUIT:
exit = true;
break;
Input::~Input() {
}
void Input::handleEvents() {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
quitRequestedFlag = true;
break;
case SDL_EVENT_KEY_DOWN:
if (keyStates[event.key.scancode] == false) {
keyFallingEdgeStates[event.key.scancode] = true;
}
keyStates[event.key.scancode] = true;
break;
case SDL_EVENT_KEY_UP:
keyStates[event.key.scancode] = false;
keyFallingEdgeStates[event.key.scancode] = false;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (mouseButtonStates[event.button.button] == false) {
mouseButtonFallingEdgeStates[event.button.button] = true;
}
mouseButtonStates[event.button.button] = true;
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
mouseButtonStates[event.button.button] = false;
mouseButtonFallingEdgeStates[event.button.button] = false;
break;
case SDL_EVENT_MOUSE_MOTION:
mousePosition.x = static_cast<int>(event.motion.x);
mousePosition.y = static_cast<int>(event.motion.y);
break;
}
for (auto& controller : controllers)
{
controller.second.leftStickX = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_LEFTX);
controller.second.leftStickY = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_LEFTY);
controller.second.rightStickX = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_RIGHTX);
controller.second.rightStickY = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_RIGHTY);
controller.second.leftTrigger = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
controller.second.rightTrigger = SDL_GameControllerGetAxis(controller.second.controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
}
SDL_GetMouseState(&mouse.x, &mouse.y);
mouse.wheel = event.wheel.y;
}
}
bool Input::keysPressed(std::vector<SDL_Keycode> keys)
{
for (auto key : keys)
{
if (activeKeys[key])
{
bool Input::quitRequested() const {
return quitRequestedFlag;
}
bool Input::isKeyPressed(SDL_Scancode key) {
auto it = keyStates.find(key);
return it != keyStates.end() && it->second;
}
bool Input::isKeyPressed(std::vector<SDL_Scancode> keys) {
for (const auto& key : keys) {
if (isKeyPressed(key)) {
return true;
}
}
return false;
}
bool Input::controllerPressed(std::vector<Uint8> buttons, int controller)
{
for (auto button : buttons)
{
if (controllers[controller].buttons[button])
{
bool Input::isMouseButtonPressed(Uint8 button) {
auto it = mouseButtonStates.find(button);
return it != mouseButtonStates.end() && it->second;
}
bool Input::isMouseButtonPressed(std::vector<Uint8> buttons) {
for (const auto& button : buttons) {
if (isMouseButtonPressed(button)) {
return true;
}
}
return false;
}
Vector2 Input::getMousePosition() const {
return mousePosition;
}
bool Input::hasKeyBeenPressed(SDL_Scancode key) {
auto it = keyFallingEdgeStates.find(key);
if (it != keyFallingEdgeStates.end() && it->second) {
keyFallingEdgeStates[key] = false;
return true;
}
return false;
}
bool Input::hasKeyBeenPressed(std::vector<SDL_Scancode> keys) {
for (const auto& key : keys) {
if (hasKeyBeenPressed(key)) {
return true;
}
}
return false;
}
bool Input::hasMouseButtonBeenPressed(Uint8 button) {
auto it = mouseButtonFallingEdgeStates.find(button);
if (it != mouseButtonFallingEdgeStates.end() && it->second) {
mouseButtonFallingEdgeStates[button] = false;
return true;
}
return false;
}
bool Input::hasMouseButtonBeenPressed(std::vector<Uint8> buttons) {
for (const auto& button : buttons) {
if (hasMouseButtonBeenPressed(button)) {
return true;
}
}

57
src/main.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "engine.hpp"
// Main function
int main(int argc, char* argv[]) {
// Create the engine with a window title and dimensions
Engine engine("SDL3 Game Engine", 800, 600);
TileMap tileMap(engine.assets);
// Load bird asset
engine.assets.load("bird", "assets/bird.png");
tileMap.addTile("bird0", Tile{{0, 0, 34, 24}, "bird"});
tileMap.addTile("bird1", Tile{{34, 0, 34, 24}, "bird"});
tileMap.addTile("bird2", Tile{{68, 0, 34, 24}, "bird"});
int currentBirdIndex = 0;
// bird position
Vector2 birdPosition(0, 0);
while (!engine.input.quitRequested()) {
// Start a new frame
engine.startFrame();
// Handle input events
engine.input.handleEvents();
// check for movement keys
if (engine.input.isKeyPressed(SDL_SCANCODE_D)) {
birdPosition.x += 1; // Move right
}
if (engine.input.isKeyPressed(SDL_SCANCODE_A)) {
birdPosition.x -= 1; // Move left
}
if (engine.input.isKeyPressed(SDL_SCANCODE_W)) {
birdPosition.y -= 1; // Move up
}
if (engine.input.isKeyPressed(SDL_SCANCODE_S)) {
birdPosition.y += 1; // Move down
}
// Change bird sprite on space key press
if (engine.input.hasKeyBeenPressed(SDL_SCANCODE_SPACE)) {
currentBirdIndex = (currentBirdIndex + 1) % 3; // Cycle through bird sprites
}
// Clear the screen with a color
engine.clearScreen({0, 0, 0}); // Black color
// Draw the bird asset at a specific position
engine.draw(tileMap.getTile("bird" + std::to_string(currentBirdIndex)), birdPosition);
// Present the rendered frame
engine.present();
}
return 0;
}

View File

@ -1,56 +0,0 @@
#include "sound.h"
Music::Music(const char* path)
{
music = Mix_LoadMUS(path);
}
void Music::play(int loops)
{
Mix_PlayMusic(music, loops);
}
void Music::pause()
{
Mix_PauseMusic();
}
void Music::stop()
{
Mix_HaltMusic();
}
void Music::resume()
{
Mix_ResumeMusic();
}
void Music::setVolume(int volume)
{
Mix_VolumeMusic(volume);
}
Music::~Music()
{
Mix_FreeMusic(music);
}
Effect::Effect(const char* path)
{
effect = Mix_LoadWAV(path);
}
void Effect::play(int loops, int channel)
{
Mix_PlayChannel(channel, effect, loops);
}
void Effect::setVolume(int volume)
{
Mix_VolumeChunk(effect, volume);
}
Effect::~Effect()
{
Mix_FreeChunk(effect);
}

View File

@ -1,170 +0,0 @@
#include "sprite.h"
Sprite::Sprite(SDL_Renderer *renderer, SDL_Surface *surface)
{
Texture = SDL_CreateTextureFromSurface(renderer, surface);
dstrect = { 0, 0, surface->w, surface->h };
}
Sprite::~Sprite()
{
SDL_DestroyTexture(Texture);
}
void Sprite::draw(SDL_Renderer* renderer, std::string frame, bool autoRect, bool flip)
{
if (frames.find(frame) == frames.end())
{
printf("Frame not found\n");
return;
}
if (autoRect)
{
dstrect = { dstrect.x, dstrect.y, frames[frame].w, frames[frame].h };
}
SDL_SetTextureAlphaMod(Texture, 255);
dstrect.w *= scale.x;
dstrect.h *= scale.y;
SDL_RenderCopyEx(renderer, Texture, &frames[frame], &dstrect, angle, NULL, flip ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
}
void Sprite::addFrame(std::string name, SDL_Rect rect)
{
frames[name] = rect;
}
void Sprite::setPos(int_vec2 position)
{
dstrect.x = position.x;
dstrect.y = position.y;
}
void Sprite::move(int_vec2 position)
{
dstrect.x += position.x;
dstrect.y += position.y;
}
int_vec2 Sprite::getSize(std::string frame)
{
size = { frames[frame].w, frames[frame].h };
return size;
}
void Sprite::autorect(std::string frame)
{
dstrect = { dstrect.x, dstrect.y, frames[frame].w, frames[frame].h };
}
Animation::Animation(std::map<std::string, SDL_Rect> frames, Sprite* sprite, int frameDuration)
{
this->sprite = sprite;
this->frameDuration = frameDuration;
for (auto const& x : frames)
{
this->frames.push_back(x.first);
this->sprite->addFrame(x.first, x.second);
}
currentFrameName = this->frames[0];
}
void Animation::reset()
{
frameIndex = 0;
frameDirection = 1;
currentFrameName = frames[0];
end = false;
}
void Animation::update()
{
frameCounter++;
end = false;
if (frameCounter >= frameDuration)
{
frameCounter = 0;
if (pingpong)
{
frameIndex += frameDirection;
if (frameIndex >= frames.size() - 1 || frameIndex <= 0)
{
if (!loop)
{
frameDirection = 0;
end = true;
} else {
frameDirection *= -1;
}
}
}
else
{
frameIndex++;
if (frameIndex >= frames.size())
{
if (!loop)
{
frameIndex = frames.size() - 1;
end = true;
} else {
frameIndex = 0;
}
}
}
currentFrameName = frames[frameIndex];
}
}
void Animation::draw(SDL_Renderer* renderer, bool autoRect, bool flip)
{
sprite->draw(renderer, currentFrameName, autoRect, flip);
}
AnimationManager::AnimationManager(Sprite* sprite)
{
this->sprite = sprite;
}
void AnimationManager::addAnimation(std::string name, std::map<std::string, SDL_Rect> frames, int frameDuration, bool flip, bool autoRect, bool loop, bool pingpong)
{
animations[name] = new Animation(frames, sprite, frameDuration);
this->flip[name] = flip;
this->autoRect[name] = autoRect;
animations[name]->loop = loop;
animations[name]->pingpong = pingpong;
}
void AnimationManager::setAnimation(std::string name)
{
if (!playing) {
currentAnimation = name;
}
}
void AnimationManager::update()
{
animations[currentAnimation]->update();
if (playing && animations[currentAnimation]->end)
{
playing = false;
}
}
void AnimationManager::draw(SDL_Renderer* renderer)
{
animations[currentAnimation]->draw(renderer, autoRect[currentAnimation], flip[currentAnimation]);
}
void AnimationManager::playAnimation(std::string name)
{
if (!playing) {
animations[name]->loop = false;
animations[name]->reset();
setAnimation(name);
playing = true;
}
}

View File

@ -1,17 +0,0 @@
#include "text.h"
Text::Text(TTF_Font* font, SDL_Color color)
{
this->font = font;
this->color = color;
}
void Text::draw(SDL_Renderer* renderer, std::string text, int x, int y)
{
surface = TTF_RenderText_Solid(font, text.c_str(), color);
texture = SDL_CreateTextureFromSurface(renderer, surface);
rect = { x, y, surface->w, surface->h };
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_FreeSurface(surface);
SDL_DestroyTexture(texture);
}