Compare commits

...

26 commits

Author SHA1 Message Date
c33f1528b6 Update README.md 2025-03-20 14:34:17 +01:00
d93544b846 adding more consts!!! 2025-03-18 23:23:42 +01:00
bb646ff009 remove assets folder 2025-03-18 23:20:43 +01:00
c58a00608d update .gitignore 2025-03-18 23:16:51 +01:00
e856e2c054 Fixed overlapping Quads 2025-03-18 23:15:45 +01:00
ea72294a3e Added Map Rendering 2025-03-18 23:00:42 +01:00
37a249dc62 Fixing loading problem
+ ImHex Pattern Code
2025-03-18 21:55:08 +01:00
72d1bf3bfb update gitignore 2025-03-18 21:51:44 +01:00
5372f2ecb0 Add Debug Preprocessor on linux 2025-03-18 15:05:08 +01:00
12f6c5fb59 Update README.md
fixing mistake
2025-03-17 20:10:23 +01:00
d4e83614fa Update README.md 2025-03-17 20:09:22 +01:00
7e00f5d327 Update README.md 2025-03-17 20:08:49 +01:00
f58d8a6280 update .gitignore 2025-03-17 20:06:18 +01:00
de72aead56 Switching to sfml 2025-03-17 20:04:47 +01:00
4536c62dad Setting Window flags right; adding animation 2025-03-16 15:24:31 +01:00
c0e3af64d6 add tilemap 2025-03-11 14:16:58 +01:00
bf0c7ab8ef update cmake for CPM.cmake 2025-03-10 20:18:12 +01:00
9f616e196c fixing sprites idk... 2025-02-13 00:49:56 +01:00
b1230534c5 stuff 2025-02-12 23:24:58 +01:00
2dbfa1b99e implement engine structure 2025-02-10 01:04:33 +01:00
fdebdd3ca2 Setup cammera2d and types 2025-02-07 11:46:09 +01:00
39abfaa978 formating; alyson licence; Camera2D 2025-02-07 01:10:15 +01:00
04d7c97e93 ignoring clion and idea directories 2025-02-07 00:05:52 +01:00
b7ddc3f292 Adding Sprite Animation (untested {no sprite sheet}) [running] 2025-02-07 00:03:37 +01:00
23a40216de switching to cmake; alyson setup 2025-02-06 22:47:32 +01:00
9504620227 Project Setup 2025-02-05 13:31:26 +01:00
13 changed files with 2037 additions and 69 deletions

87
.gitignore vendored
View file

@ -1,68 +1,19 @@
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# ---> CMake
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json
.bake_cache
.DS_Store
.vscode
.vs
gcov
bin
build
.cache
.idea
cmake-**/
.kdev4
*.kdev4
assets/
assets/**
*.cymf
*.png
*.bmp
*.jpg
test.**

66
CMakeLists.txt Normal file
View file

@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 3.20)
project(colysis
LANGUAGES CXX
VERSION 0.0.1
)
# Set C/C++ standards
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Set compiler flags
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Generate compile commands (useful for IDEs)
set(USE_FOLDERS ON) # Organize targets into folders (for IDEs)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) # Build static libraries by default
include(cmake/CPM.cmake)
# sfml
CPMAddPackage(
NAME sfml
GITHUB_REPOSITORY SFML/SFML
GIT_TAG 3.0.0
)
# # flecs
# CPMAddPackage(
# NAME flecs
# GITHUB_REPOSITORY SanderMertens/flecs
# GIT_TAG v4.0.4
# )
include(cmake/utils.cmake)
# Get files in src/ and add them to the executable
find_files(colysis_src src cpp hpp cxx hxx c h)
add_executable(colysis
${colysis_src}
)
target_link_libraries(colysis
SFML::Audio
SFML::Graphics
SFML::Window
SFML::System
)
target_compile_definitions(colysis PUBLIC "$<$<CONFIG:DEBUG>:_DEBUG>")
target_include_directories(colysis PUBLIC include)
# put the assets folder path as a C preprocessor define
set(ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/assets/")
target_compile_definitions(colysis PRIVATE ASSETS_PATH="${ASSETS_PATH}")
message(STATUS "Assets path : ${ASSETS_PATH}")
# put_targets_into_folder(FOLDER "vendor/flecs" TARGETS flecs::flecs flecs::flecs_static)

View file

@ -1,2 +1,36 @@
# colysis
# Colysis
A simple game written in C++ and using SFML.
## Building
To build the project, you need to have [CMake](https://cmake.org/) installed.
### Linux
```sh
mkdir build
cd build
cmake ..
make
```
### Windows
```sh
mkdir build
cd build
cmake -G "Visual Studio 17 2022" ..
cmake --build . --config Release
```
### macOS
```sh
mkdir build
cd build
cmake -G "Xcode" ..
cmake --build . --config Release
```
## Running
After building, you can run the game by executing the `colysis` executable.
## License
The game will be open source *but* without the game assets. \
So you will be able to make your own game from colysis but the assets can only be purchased.

1289
cmake/CPM.cmake Normal file

File diff suppressed because it is too large Load diff

33
cmake/utils.cmake Normal file
View file

@ -0,0 +1,33 @@
function(put_targets_into_folder)
set(oneValueArgs FOLDER)
set(multiValueArgs TARGETS)
cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
foreach(target ${ARGS_TARGETS})
# Check if target exists
if (NOT TARGET ${target})
message(FATAL_ERROR "${target} target not found")
endif()
# Get the actual target (if it is aliased)
get_target_property(actual_target ${target} ALIASED_TARGET)
if (NOT actual_target)
set(actual_target ${target})
endif()
# Set the folder property for the target
set_target_properties(${actual_target} PROPERTIES FOLDER ${ARGS_FOLDER})
endforeach()
endfunction()
function(find_files var_name path)
set(sources)
foreach(ext ${ARGN})
file(GLOB_RECURSE files "${path}/*.${ext}")
list(APPEND sources ${files})
endforeach()
set(${var_name} ${${var_name}} ${sources} PARENT_SCOPE)
endfunction()

23
src/Enemy/Enemy.cpp Normal file
View file

@ -0,0 +1,23 @@
#include "Enemy.hpp"
#include <ctgmath>
Enemy::Enemy(sf::Vector2f position) : rectangle(sf::Vector2f{100, 100})
{
rectangle.move(position);
rectangle.setFillColor(sf::Color::Red);
}
void Enemy::look_at(sf::Vector2f target)
{
float dx = target.x - rectangle.getPosition().x;
float dy = target.y - rectangle.getPosition().y;
float angle = atan2(dy, dx);
rectangle.setRotation(sf::degrees(angle));
}
void Enemy::draw(sf::RenderTarget& target, const sf::RenderStates states) const
{
target.draw(rectangle, states);
}

13
src/Enemy/Enemy.hpp Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include <SFML/Graphics.hpp>
class Enemy : public sf::Drawable
{
sf::RectangleShape rectangle;
public:
explicit Enemy(sf::Vector2f position);
void look_at(sf::Vector2f target);
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
};

51
src/Game.cpp Normal file
View file

@ -0,0 +1,51 @@
#include "Game.hpp"
Game::Game(sf::RenderWindow& window) : window(window)
{
}
#include <iostream>
void Game::run()
{
#ifdef _DEBUG
std::cout << "Loading map... Version: " MAP_VERSION_STRING << std::endl;
const auto start = std::chrono::system_clock::now();
#endif
auto [message, code] = map.load(ASSETS_PATH "/test.cymf");
#ifdef _DEBUG
const auto end = std::chrono::system_clock::now();
const auto milliseconds = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Loading took: " << milliseconds << std::endl;
#endif
map.setPosition(sf::Vector2f{20, 20});
if (code != 0)
{
std::cout << message << std::endl;
return;
}
#ifdef _DEBUG
map.debug();
#endif
while (window.isOpen())
{
while (const auto event = window.pollEvent())
{
if (event->is<sf::Event::Closed>())
{
window.close();
}
}
// Render game
window.clear({0x20, 0x20, 0x20,0xff});
window.draw(map);
window.display();
}
}

19
src/Game.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <SFML/Graphics.hpp>
#include "Enemy/Enemy.hpp"
#include "Map/Map.hpp"
#include <vector>
class Game
{
Map map;
public:
explicit Game(sf::RenderWindow& window);
void run();
private:
sf::RenderWindow& window;
};

262
src/Map/Map.cpp Normal file
View file

@ -0,0 +1,262 @@
#include "Map.hpp"
#include <fstream>
bool operator==(uint32_t lhs, TileType rhs) {
return lhs == static_cast<uint32_t>(rhs);
}
bool operator&(uint8_t lhs, MapFlags rhs)
{
return lhs & static_cast<uint8_t>(rhs);
}
bool operator==(int lhs, TileType rhs)
{
return lhs == static_cast<int>(rhs);
}
auto operator<=>(int lhs, TileType rhs)
{
return lhs <=> static_cast<int>(rhs);
}
MapError Map::populate_vertex_array() {
if (!tiles) return {"Map: [Loading] Tilemap is not loaded", 2};
atlas.vertices.setPrimitiveType(sf::PrimitiveType::Triangles);
atlas.vertices.resize(map_size.x * map_size.y * 6);
const auto xw = static_cast<float>(atlas.tile_size.x);
const auto yw = static_cast<float>(atlas.tile_size.y);
const auto tex_w = static_cast<float>(atlas.tile_size.x);
const auto tex_h = static_cast<float>(atlas.tile_size.y);
for (uint32_t x = 0; x < map_size.y; x++) {
for (uint32_t y = 0; y < map_size.z; y++)
{
const int32_t tile = tiles[x + y * map_size.y];
if (tile < -atlas.wall_tiles || tile > atlas.other_tiles) return {"Map: [Loading] Invalid tile! Tile type does not have a texture; ID: " + std::to_string(tile), 3};
// map tile type to atlas
/*
* Atlas:
* +++++
* +++--
* ---##
*
* + = wall
* # = special
* - = other
*
* wall_tiles = 8
* other_tiles = 5
* special_tiles = 2
*
* Tile:
* -1 <= wall
* 0 == special @ .type == tile_type
* 1 >= other
*/
if (tile == 0) return {"Map: [Loading] [Not Implemented!] Tile is a special tile", 3};
// atlas tile index
const uint32_t atlas_tile = atlas.wall_tiles + (tile - 1);
if (atlas_tile >= atlas.wall_tiles + atlas.other_tiles) return {"Map: [Loading] Invalid tile! Tile type does not have a texture", 3};
// add the vertices
const auto xf = static_cast<float>(x * atlas.tile_size.x);
const auto yf = static_cast<float>(y * atlas.tile_size.y);
const uint32_t tile_x = atlas_tile % atlas.tile_count.x;
const uint32_t tile_y = atlas_tile / atlas.tile_count.x;
const auto tex_x = static_cast<float>(tile_x * atlas.tile_size.x);
const auto tex_y = static_cast<float>(tile_y * atlas.tile_size.y);
sf::Vertex tl = {{xf, yf}, sf::Color::White, {tex_x, tex_y}};
sf::Vertex tr = {{xf + xw, yf}, sf::Color::White, {tex_x + tex_w, tex_y}};
sf::Vertex bl = {{xf, yf + yw}, sf::Color::White, {tex_x, tex_y + tex_h}};
sf::Vertex br = {{xf + xw, yf + yw}, sf::Color::White, {tex_x + tex_w, tex_y + tex_h}};
atlas.vertices.append(tl);
atlas.vertices.append(tr);
atlas.vertices.append(bl);
atlas.vertices.append(bl);
atlas.vertices.append(tr);
atlas.vertices.append(br);
}
}
return {"Map: [Loading] Success", 0};
}
void Map::LoadDefaultTilemap() {
atlas.wall_tiles = 0;
atlas.other_tiles = 1;
atlas.special_tiles = 0;
atlas.tile_count = {1, 1};
atlas.tile_size = {16, 16};
if (!atlas.texture.loadFromFile(ASSETS_PATH "/default_tilemap.png")) {
throw std::runtime_error("Map: [Loading] Could not load default tilemap -> Fatal");
}
}
Map::Map(): atlas() {
}
#include <iostream>
#include <cstring>
#include <filesystem>
MapError Map::load(const std::filesystem::path& path)
{
// opening the file and checking the magic number
std::ifstream file(path, std::ios::binary | std::ios::in);
if (!file.is_open()) {
return {"Map: [Loading] Could not open file: " + path.string(), 1};
}
char magic[4];
file.read(magic, 4);
if (std::memcmp(magic, "CYMF", 4) != 0) {
return {"Map: [Loading] Invalid magic number: " + std::string(magic, 4) + " (expected CYMF)", 1};
}
// reading the version
uint8_t version[3];
file.read(reinterpret_cast<char*>(version), 3);
if (version[0] != MAP_VERSION_MAJOR || version[1] > MAP_VERSION_MINOR)
{
return {"Map: [Loading] Invalid version; Current: " MAP_VERSION_STRING, 1};
}
// reading the flags
uint8_t flags;
file.read(reinterpret_cast<char*>(&flags), 1);
// flag CustomTilemap -> load the tilemap
if (flags & MapFlags::CustomTilemap)
{
uint32_t filepath_buffer_size;
file.read(reinterpret_cast<char*>(&filepath_buffer_size), 4);
char* filepath_buffer = new char[filepath_buffer_size];
file.read(filepath_buffer, filepath_buffer_size);
return {"Map: [Loading] [Not Implemented] Custom Tilemap is not implemented. Tilemap file: " + std::string(filepath_buffer, filepath_buffer_size), 1};
delete[] filepath_buffer;
}
else {
LoadDefaultTilemap();
}
// load the map info
uint32_t map_info[3];
file.read(reinterpret_cast<char*>(map_info), 12);
map_size = sf::Vector3<size_t>(static_cast<size_t>(map_info[0])*map_info[1], map_info[0], map_info[1]);
// load the tiles
tiles = new int[map_info[0] * map_info[1]];
for (int i = 0; i < map_info[0] * map_info[1]; i++)
{
// read the special if needed
file.read(reinterpret_cast<char*>(&tiles[i]), 4);
if (tiles[i] == TileType::Special)
{
MapSpecial special{};
file.read(reinterpret_cast<char*>(&special), sizeof(MapSpecial));
// get position
uint32_t tile_x = i % map_info[0];
uint32_t tile_y = i / map_info[0];
specials.emplace_back(std::pair<sf::Vector2u, MapSpecial>({tile_x, tile_y}, special));
}
}
// load the player spawn
uint32_t t_player_spawn[2];
file.read(reinterpret_cast<char*>(t_player_spawn), 8);
player_spawn = sf::Vector2u{t_player_spawn[0], t_player_spawn[1]};
if (player_spawn.x >= map_size.y || player_spawn.y >= map_size.z)
{
return {"Map: [Loading] Player: Spawn out of bounds", 2};
}
int player_tile = tiles[player_spawn.x + player_spawn.y * map_size.x];
if (player_tile <= TileType::Special)
{
return {"Map: [Loading] Player: Spawn is not a valid tile (invalid: wall, special; valid: Floor)", 2};
}
// load all the enemy spawns
uint32_t number_enemy_spawns;
file.read(reinterpret_cast<char*>(&number_enemy_spawns), 4);
if (number_enemy_spawns > map_size.x)
{
return {"Map: [Loading] Enemies: Too many enemy spawns (More than tile in the map)", 3};
}
for (int i = 0; i < number_enemy_spawns; i++)
{
uint32_t t_enemy_spawn[2];
file.read(reinterpret_cast<char*>(t_enemy_spawn), 8);
if (t_enemy_spawn[0] >= map_size.x || t_enemy_spawn[1] >= map_size.y)
{
return {"Map: [Loading] Enemy: Spawn out of bounds", 4};
}
if (tiles[t_enemy_spawn[0] + t_enemy_spawn[1] * map_size.y] <= TileType::Special)
{
return {"Map: [Loading] Enemy: Spawn is not a valid tile (invalid: wall, special; valid: Floor)", 4};
}
if (t_enemy_spawn[0] == player_spawn.x && t_enemy_spawn[1] == player_spawn.y)
{
return {"Map: [Loading] Enemy: Spawn is the same as the player spawn", 4};
}
enemy_spawns.emplace_back(t_enemy_spawn[0], t_enemy_spawn[1]);
}
// Everything went well
return populate_vertex_array();
}
sf::Vector2u Map::getSize() const
{
return {static_cast<uint32_t>(map_size.y), static_cast<uint32_t>(map_size.z)};
}
void Map::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
if (!tiles) return;
// apply the transform
states.transform *= getTransform();
// apply the tileset texture
states.texture = &atlas.texture;
// draw the vertex array
target.draw(atlas.vertices, states);
}
#ifdef _DEBUG
void Map::debug()
{
std::cout << "Map Debug" << std::endl;
std::cout << "\tSize : " << map_size.x << ", " << map_size.y << ", " << map_size.z << std::endl;
std::cout << "\tTiles : " << tiles << std::endl;
for (auto& special : specials)
{
std::cout << "\t\tSpecial : " << special.first.x << ", " << special.first.y << std::endl;
std::cout << "\t\t\tType : " << special.second.type << std::endl;
std::cout << "\t\t\tPortal : " << special.second.portal_end[0] << ", " << special.second.portal_end[1] << std::endl;
}
std::cout << "\t\tEnemy Spawns : " << enemy_spawns.size() << std::endl;
for (auto& spawn : enemy_spawns)
{
std::cout << "\t\t\tSpawn : " << spawn.x << ", " << spawn.y << std::endl;
}
std::cout << "\t\tPlayer Spawn : " << player_spawn.x << ", " << player_spawn.y << std::endl;
}
#endif

116
src/Map/Map.hpp Normal file
View file

@ -0,0 +1,116 @@

/******************************************************************
* ==============================================================
* Map File Format
* ==============================================================
*
* int8_t magic[4] = 'CYMF' (Colysis Map File) : 4 bytes
* uint8_t version[3] : major, minor, patch
*
* uint8_t flags
* if (flags):
* case MapFlags::CustomTilemap:
* uint32_t filepath_buffer_size
* char filepath_buffer[filepath_buffer_size]
* uint32_t tile_size[2] : width, height
* uint32_t tile_count[2] : width, height
*
* uint32_t map_size[3] : x, y, specials
* int32_t tiles[x * y + specials * (sizeof(MapSpecial)/4)] : tilemap data
* // a special tile has a type and its tilemap data
* if tiles[i] == Special:
* uint8_t type
* MapSpecial data
* else:
* uint8_t tile
*
* uint32_t player_spawn[2] : x, y
* uint32_t number_enemy_spawns
* uint32_t enemy_spawns[number_enemy_spawns][2] : x, y
*
*******************************************************************/
#pragma once
#include <SFML/Graphics.hpp>
#define STR(name) #name
#define STR_VALUE(name) STR(name)
#define MAP_VERSION_MAJOR 0
#define MAP_VERSION_MINOR 1
#define MAP_VERSION_PATCH 0
#define MAP_VERSION (MAP_VERSION_MAJOR << 16 | MAP_VERSION_MINOR << 8 | MAP_VERSION_PATCH)
#define MAP_VERSION_STRING STR_VALUE(MAP_VERSION_MAJOR) "." STR_VALUE(MAP_VERSION_MINOR) "." STR_VALUE(MAP_VERSION_PATCH)
struct MapSpecial
{
int type;
uint32_t portal_end[2];
};
enum class MapFlags : uint8_t
{
None = 0,
CustomTilemap = 1 << 0,
UNDEFINED = 1 << 1,
};
enum class TileType : int
{
Wall = -1,
Special = 0,
Other = 1,
};
struct MapError
{
std::string message;
int code;
};
class Map : public sf::Drawable, public sf::Transformable
{
static_assert(sizeof(MapSpecial)%4 == 0);
struct {
sf::Vector2u tile_size;
sf::Vector2u tile_count;
uint32_t wall_tiles;
uint32_t other_tiles;
uint32_t special_tiles;
sf::Texture texture;
sf::VertexArray vertices;
} atlas;
sf::Vector3<size_t> map_size; // width * height, width, height
int *tiles = nullptr;
std::vector<std::pair<sf::Vector2u, MapSpecial>> specials;
std::vector<sf::Vector2u> enemy_spawns;
sf::Vector2u player_spawn;
MapError populate_vertex_array();
void LoadDefaultTilemap();
public:
Map();
MapError load(const std::filesystem::path& path);
sf::Vector2u getSize() const;
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
#ifdef _DEBUG
void debug();
#endif
};

31
src/main.cpp Normal file
View file

@ -0,0 +1,31 @@
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "Game.hpp"
#include <iostream>
int main()
{
sf::RenderWindow window(sf::VideoMode({800, 450}), "Colysis", sf::Style::Close | sf::Style::Titlebar);
window.setMinimumSize(sf::Vector2u{800, 450});
try {
Game game(window);
game.run();
}
catch (const std::runtime_error& e)
{
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
}
catch (...)
{
std::cerr << "Unknown error" << std::endl;
}
std::flush(std::cerr);
return 0;
}

80
utils/MapFile.hexpat Normal file
View file

@ -0,0 +1,80 @@
#pragma endian little
struct Version {
u8 major;
u8 minor;
u8 patch;
};
bitfield OptionFlags {
bool CustomeTilemap : 1;
};
struct vec2<T> {
T x,y;
}[[single_color]];
struct TilemapInfo {
vec2<u32> dimentions;
vec2<u32> tile_size;
vec2<u32> tile_count;
u32 wall_tiles;
u32 other_tiles;
u32 special_tiles;
};
struct Options {
OptionFlags flags;
if (flags.CustomeTilemap) {
u32 filepath_length;
char filepath[filepath_length];
TilemapInfo info;
}
};
struct SpecialData {
u32 data;
};
struct Tile {
u32 type;
if (type == 0) {
SpecialData special;
}
}[[single_color]];
struct Map
{
u32 width;
u32 height;
u32 specials;
Tile tiles[width * height];
};
struct Spawn {
u32 x,y;
}[[single_color]];
struct EnemySpawns {
u32 count;
Spawn spawns[count];
};
import std.io as io;
char magic[4] @$;
if (magic != "CYMF") {
io::warning("Magic does not match: \"" + magic + "\"; Expected: \"CYMF\"");
}
Version version @$ [[single_color]];
Options options @$;
Map map @$;
Spawn player @$ [[single_color]];
EnemySpawns enemies @$;