diff options
Diffstat (limited to 'src')
87 files changed, 2860 insertions, 9 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0090188..b60a0cd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,14 +3,38 @@ cmake_minimum_required(VERSION 3.28)  set(CMAKE_C_STANDARD 11)  set(CMAKE_CXX_STANDARD 20)  set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - -# enable debug features  set(CMAKE_BUILD_TYPE Debug) -add_compile_definitions(DEBUG)  project(crepe C CXX) -add_executable(main) +find_package(SDL2 REQUIRED) +find_package(SDL2_image REQUIRED) +find_package(SoLoud REQUIRED) +find_package(GTest REQUIRED) + +add_library(crepe SHARED) +add_executable(test_main EXCLUDE_FROM_ALL) + +target_include_directories(crepe +	PUBLIC SYSTEM INTERFACE . +) + +target_link_libraries(crepe +	PRIVATE soloud +	PUBLIC SDL2 +	PUBLIC SDL2_image +)  add_subdirectory(crepe) +add_subdirectory(test) +add_subdirectory(example) + +install( +	TARGETS crepe +	FILE_SET HEADERS DESTINATION include/crepe +) +target_link_libraries(test_main +	PRIVATE gtest_main +	PUBLIC crepe +) diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp new file mode 100644 index 0000000..8a2a11c --- /dev/null +++ b/src/crepe/Asset.cpp @@ -0,0 +1,16 @@ +#include <filesystem> + +#include "Asset.h" + +using namespace crepe; + +Asset::Asset(const std::string & src) { +	// FIXME: restore this +	// this->src = std::filesystem::canonical(src); +	this->src = src; +	this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); +} + +const std::istream & Asset::read() { return this->file; } + +const char * Asset::canonical() { return this->src.c_str(); } diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h new file mode 100644 index 0000000..0cb5834 --- /dev/null +++ b/src/crepe/Asset.h @@ -0,0 +1,33 @@ +#pragma once + +#include <fstream> +#include <iostream> +#include <string> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate and canonicalize paths to game asset files, and + * should *always* be used when retrieving files from disk. + */ +class Asset { +public: +	/** +	 * \param src  Unique identifier to asset +	 */ +	Asset(const std::string & src); + +public: +	//! Get an input stream to the contents of this resource +	const std::istream & read(); +	//! Get the canonical path to this resource +	const char * canonical(); + +private: +	std::string src; +	std::ifstream file; +}; + +} // namespace crepe diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 392b7d7..d938eb8 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,3 +1,38 @@ -target_sources(main PUBLIC -	main.cpp +target_sources(crepe PUBLIC +	Asset.cpp +	Sound.cpp +	SoundContext.cpp +	Particle.cpp +	ParticleSystem.cpp +	SDLApp.cpp +	ComponentManager.cpp +	Component.cpp +	ScriptSystem.cpp +	PhysicsSystem.cpp +	CollisionSystem.cpp +	Collider.cpp +	SDLContext.cpp + +	RenderSystem.cpp  ) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES +	Asset.h +	Sound.h +	SoundContext.h +	SDLContext.h +	ComponentManager.h +	ComponentManager.hpp +	Component.h +	System.h +	ScriptSystem.h +	PhysicsSystem.h +	CollisionSystem.h +	Collider.h +	SDLContext.h +	RenderSystem.h +) + +add_subdirectory(api) +add_subdirectory(util) + diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp new file mode 100644 index 0000000..13a3f33 --- /dev/null +++ b/src/crepe/Collider.cpp @@ -0,0 +1,5 @@ +#include "Collider.h" + +using namespace crepe; + +Collider::Collider(uint32_t gameObjectId) : Component(gameObjectId) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h new file mode 100644 index 0000000..68a7d1d --- /dev/null +++ b/src/crepe/Collider.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Component.h" + +namespace crepe { + +class Collider : public Component { +public: +	Collider(uint32_t game_object_id); + +	int size; +}; + +} // namespace crepe diff --git a/src/crepe/CollisionSystem.cpp b/src/crepe/CollisionSystem.cpp new file mode 100644 index 0000000..55e0fdc --- /dev/null +++ b/src/crepe/CollisionSystem.cpp @@ -0,0 +1,7 @@ +#include "CollisionSystem.h" + +using namespace crepe; + +CollisionSystem::CollisionSystem() {} + +void CollisionSystem::update() {} diff --git a/src/crepe/CollisionSystem.h b/src/crepe/CollisionSystem.h new file mode 100644 index 0000000..1e9f1aa --- /dev/null +++ b/src/crepe/CollisionSystem.h @@ -0,0 +1,11 @@ +#pragma once + +namespace crepe { + +class CollisionSystem { +public: +	CollisionSystem(); +	void update(); +}; + +} // namespace crepe diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp new file mode 100644 index 0000000..358ce31 --- /dev/null +++ b/src/crepe/Component.cpp @@ -0,0 +1,5 @@ +#include "Component.h" + +using namespace crepe; + +Component::Component(uint32_t id) : game_object_id(id), active(true) {} diff --git a/src/crepe/Component.h b/src/crepe/Component.h new file mode 100644 index 0000000..bc44865 --- /dev/null +++ b/src/crepe/Component.h @@ -0,0 +1,22 @@ +#pragma once + +#include <cstdint> + +namespace crepe { + +class ComponentManager; + +class Component { +protected: +	friend class crepe::ComponentManager; +	Component(uint32_t id); + +public: +	virtual ~Component() = default; + +public: +	uint32_t game_object_id; +	bool active; +}; + +} // namespace crepe diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp new file mode 100644 index 0000000..8bde33a --- /dev/null +++ b/src/crepe/ComponentManager.cpp @@ -0,0 +1,29 @@ +#include "ComponentManager.h" +#include "util/log.h" + +using namespace crepe; + +ComponentManager & ComponentManager::get_instance() { +	static ComponentManager instance; +	return instance; +} + +void ComponentManager::delete_all_components_of_id(uint32_t id) { +	// Loop through all the types (in the unordered_map<>) +	for (auto & [type, componentArray] : components) { +		// Make sure that the id (that we are looking for) is within the boundaries of the vector<> +		if (id < componentArray.size()) { +			// Clear the components at this specific id +			componentArray[id].clear(); +		} +	} +} + +void ComponentManager::delete_all_components() { +	// Clear the whole unordered_map<> +	this->components.clear(); +} + +ComponentManager::ComponentManager() { dbg_trace(); } + +ComponentManager::~ComponentManager() { dbg_trace(); } diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h new file mode 100644 index 0000000..2b5e1df --- /dev/null +++ b/src/crepe/ComponentManager.h @@ -0,0 +1,62 @@ +#pragma once + +#include <cstdint> +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "Component.h" + +namespace crepe { + +class ComponentManager { +public: +	// Singleton +	static ComponentManager & get_instance(); +	ComponentManager(const ComponentManager &) = delete; +	ComponentManager(ComponentManager &&) = delete; +	ComponentManager & operator=(const ComponentManager &) = delete; +	ComponentManager & operator=(ComponentManager &&) = delete; + +public: +	//! Add a component of a specific type +	template <typename T, typename... Args> +	T & add_component(uint32_t id, Args &&... args); +	//! Deletes all components of a specific type and id +	template <typename T> +	void delete_components_by_id(uint32_t id); +	//! Deletes all components of a specific type +	template <typename T> +	void delete_components(); +	//! Deletes all components of a specific id +	void delete_all_components_of_id(uint32_t id); +	//! Deletes all components +	void delete_all_components(); + +	//! Get a vector<> of all components at specific type and id +	template <typename T> +	std::vector<std::reference_wrapper<T>> +	get_components_by_id(uint32_t id) const; +	//! Get a vector<> of all components of a specific type +	template <typename T> +	std::vector<std::reference_wrapper<T>> get_components_by_type() const; + +private: +	ComponentManager(); +	virtual ~ComponentManager(); + +	/* +	 * The std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> below might seem a bit strange, let me explain this structure: +	 * The std::unordered_map<> has a key and value. The key is a std::type_index and the value is a std::vector. So, a new std::vector will be created for each new std::type_index. +	 * The first std::vector<> stores another vector<>. This first vector<> is to bind the entity's id to a component. +	 * The second std::vector<> stores unique_ptrs. Each component can be gathered via an unique_ptr. This second vector<> allows multiple components of the same std::type_index for one entity (id). +	 */ +	std::unordered_map<std::type_index, +					   std::vector<std::vector<std::unique_ptr<Component>>>> +		components; +}; + +} // namespace crepe + +#include "ComponentManager.hpp" diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/ComponentManager.hpp new file mode 100644 index 0000000..9b07f13 --- /dev/null +++ b/src/crepe/ComponentManager.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include <type_traits> + +#include "ComponentManager.h" + +namespace crepe { + +template <class T, typename... Args> +T & ComponentManager::add_component(uint32_t id, Args &&... args) { +	using namespace std; + +	static_assert(is_base_of<Component, T>::value, +				  "add_component must recieve a derivative class of Component"); + +	// Determine the type of T (this is used as the key of the unordered_map<>) +	type_index type = typeid(T); + +	// Check if this component type is already in the unordered_map<> +	if (components.find(type) == components.end()) { +		//If not, create a new (empty) vector<> of vector<unique_ptr<Component>> +		components[type] = vector<vector<unique_ptr<Component>>>(); +	} + +	// Resize the vector<> if the id is greater than the current size +	if (id >= components[type].size()) { +		// Initialize new slots to nullptr (resize does automatically init to nullptr) +		components[type].resize(id + 1); +	} + +	// Create a new component of type T (arguments directly forwarded). The +	// constructor must be called by ComponentManager. +	T * instance = new T(id, forward<Args>(args)...); +	// store its unique_ptr in the vector<> +	components[type][id].push_back(unique_ptr<T>(instance)); + +	return *instance; +} + +template <typename T> +void ComponentManager::delete_components_by_id(uint32_t id) { +	using namespace std; + +	// Determine the type of T (this is used as the key of the unordered_map<>) +	type_index type = typeid(T); + +	// Find the type (in the unordered_map<>) +	if (components.find(type) != components.end()) { +		// Get the correct vector<> +		vector<vector<unique_ptr<Component>>> & component_array +			= components[type]; + +		// Make sure that the id (that we are looking for) is within the boundaries of the vector<> +		if (id < component_array.size()) { +			// Clear the whole vector<> of this specific type and id +			component_array[id].clear(); +		} +	} +} + +template <typename T> +void ComponentManager::delete_components() { +	// Determine the type of T (this is used as the key of the unordered_map<>) +	std::type_index type = typeid(T); + +	if (components.find(type) == components.end()) return; + +	components[type].clear(); +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::get_components_by_id(uint32_t id) const { +	using namespace std; + +	// Determine the type of T (this is used as the key of the unordered_map<>) +	type_index type = typeid(T); + +	// Create an empty vector<> +	vector<reference_wrapper<T>> component_vector; + +	if (components.find(type) == components.end()) return component_vector; + +	// Get the correct vector<> +	const vector<vector<unique_ptr<Component>>> & component_array +		= components.at(type); + +	// Make sure that the id (that we are looking for) is within the boundaries of the vector<> +	if (id >= component_array.size()) return component_vector; + +	// Loop trough the whole vector<> +	for (const unique_ptr<Component> & component_ptr : component_array[id]) { +		// Cast the unique_ptr to a raw pointer +		T * casted_component = static_cast<T *>(component_ptr.get()); + +		if (casted_component == nullptr) continue; + +		// Add the dereferenced raw pointer to the vector<> +		component_vector.push_back(*casted_component); +	} + +	return component_vector; +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::get_components_by_type() const { +	using namespace std; + +	// Determine the type of T (this is used as the key of the unordered_map<>) +	type_index type = typeid(T); + +	// Create an empty vector<> +	vector<reference_wrapper<T>> component_vector; +	// Set the id to 0 (the id will also be stored in the returned vector<>) +	// uint32_t id = 0; + +	// Find the type (in the unordered_map<>) +	if (components.find(type) == components.end()) return component_vector; + +	// Get the correct vector<> +	const vector<vector<unique_ptr<Component>>> & component_array +		= components.at(type); + +	// Loop through the whole vector<> +	for (const vector<unique_ptr<Component>> & component : component_array) { +		// Loop trough the whole vector<> +		for (const unique_ptr<Component> & component_ptr : component) { +			// Cast the unique_ptr to a raw pointer +			T * casted_component = static_cast<T *>(component_ptr.get()); + +			// Ensure that the cast was successful +			if (casted_component == nullptr) continue; + +			// Pair the dereferenced raw pointer and the id and add it to the vector<> +			component_vector.emplace_back(ref(*casted_component)); +		} + +		// Increase the id (the id will also be stored in the returned vector<>) +		//++id; +	} + +	// Return the vector<> +	return component_vector; +} + +} // namespace crepe diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp new file mode 100644 index 0000000..4810e80 --- /dev/null +++ b/src/crepe/Particle.cpp @@ -0,0 +1,20 @@ +#include "Particle.h" + +using namespace crepe; + +Particle::Particle() { this->active = false; } + +void Particle::reset(float lifespan, Position position, Position velocity) { +	this->time_in_life = 0; +	this->lifespan = lifespan; +	this->position = position; +	this->velocity = velocity; +	this->active = true; +} + +void Particle::update(float deltaTime) { +	time_in_life += deltaTime; +	position.x += velocity.x * deltaTime; +	position.y += velocity.y * deltaTime; +	if (time_in_life >= lifespan) this->active = false; +} diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h new file mode 100644 index 0000000..21e691d --- /dev/null +++ b/src/crepe/Particle.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Position.h" + +namespace crepe { + +class Particle { +public: +	Position position; +	// FIXME: `Position` is an awkward name for a 2D vector. See FIXME comment in +	// api/Transform.h for fix proposal. +	Position velocity; +	float lifespan; +	bool active; + +	Particle(); +	void reset(float lifespan, Position position, Position velocity); +	void update(float deltaTime); +	float time_in_life; +}; + +} // namespace crepe diff --git a/src/crepe/ParticleSystem.cpp b/src/crepe/ParticleSystem.cpp new file mode 100644 index 0000000..af6c550 --- /dev/null +++ b/src/crepe/ParticleSystem.cpp @@ -0,0 +1,62 @@ +#include <cmath> +#include <ctime> + +#include "api/ParticleEmitter.h" + +#include "ComponentManager.h" +#include "ParticleSystem.h" + +using namespace crepe; + +ParticleSystem::ParticleSystem() : elapsed_time(0.0f) {} + +void ParticleSystem::update() { +	ComponentManager & mgr = ComponentManager::get_instance(); +	std::vector<std::reference_wrapper<ParticleEmitter>> emitters +		= mgr.get_components_by_type<ParticleEmitter>(); +	float delta_time = 0.10; +	for (ParticleEmitter & emitter : emitters) { +		float update_amount = 1 / static_cast<float>(emitter.emission_rate); +		for (float i = 0; i < delta_time; i += update_amount) { +			emit_particle(emitter); +		} +		for (size_t j = 0; j < emitter.particles.size(); j++) { +			if (emitter.particles[j].active) { +				emitter.particles[j].update(delta_time); +			} +		} +	} +} + +void ParticleSystem::emit_particle(ParticleEmitter & emitter) { +	Position initial_position = {emitter.position.x, emitter.position.y}; +	float random_angle = 0.0f; +	if (emitter.max_angle < emitter.min_angle) { +		random_angle = ((emitter.min_angle +						 + (std::rand() +							% (static_cast<uint32_t>(emitter.max_angle + 360 +													 - emitter.min_angle + 1)))) +						% 360); +	} else { +		random_angle = emitter.min_angle +					   + (std::rand() +						  % (static_cast<uint32_t>(emitter.max_angle +												   - emitter.min_angle + 1))); +	} +	float angle_in_radians = random_angle * (M_PI / 180.0f); +	float random_speed_offset = (static_cast<float>(std::rand()) / RAND_MAX) +									* (2 * emitter.speed_offset) +								- emitter.speed_offset; +	float velocity_x +		= (emitter.speed + random_speed_offset) * std::cos(angle_in_radians); +	float velocity_y +		= (emitter.speed + random_speed_offset) * std::sin(angle_in_radians); +	Position initial_velocity = {velocity_x, velocity_y}; +	for (size_t i = 0; i < emitter.particles.size(); i++) { +		if (!emitter.particles[i].active) { +			emitter.particles[i].reset(emitter.end_lifespan, initial_position, +									   initial_velocity); +			break; +		} +	} +} diff --git a/src/crepe/ParticleSystem.h b/src/crepe/ParticleSystem.h new file mode 100644 index 0000000..ad96eb0 --- /dev/null +++ b/src/crepe/ParticleSystem.h @@ -0,0 +1,18 @@ +#pragma once + +#include "api/ParticleEmitter.h" + +namespace crepe { + +class ParticleSystem { +public: +	ParticleSystem(); +	void update(); + +private: +	void emit_particle(ParticleEmitter & emitter); //emits a new particle + +	float elapsed_time; //elapsed time since the last emission +}; + +} // namespace crepe diff --git a/src/crepe/PhysicsSystem.cpp b/src/crepe/PhysicsSystem.cpp new file mode 100644 index 0000000..16f4c10 --- /dev/null +++ b/src/crepe/PhysicsSystem.cpp @@ -0,0 +1,62 @@ +#include <iostream> + +#include "api/Force.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" + +#include "ComponentManager.h" +#include "PhysicsSystem.h" + +using namespace crepe; +using namespace crepe::api; + +PhysicsSystem::PhysicsSystem() {} + +void PhysicsSystem::update() { +	ComponentManager & mgr = ComponentManager::get_instance(); +	std::vector<std::reference_wrapper<Rigidbody>> rigidbodies +		= mgr.get_components_by_type<Rigidbody>(); +	std::vector<std::reference_wrapper<Transform>> transforms +		= mgr.get_components_by_type<Transform>(); + +	for (Rigidbody & rigidbody : rigidbodies) { + +		switch (rigidbody.body_type) { +			case BodyType::DYNAMIC: +				for (Transform & transform : transforms) { +					if (transform.game_object_id == rigidbody.game_object_id) { +						rigidbody.velocity_x = 0; +						rigidbody.velocity_y = 0; +						std::vector<std::reference_wrapper<Force>> forces +							= mgr.get_components_by_id<Force>( +								rigidbody.game_object_id); +						rigidbody.velocity_y +							+= rigidbody.gravity_scale * 1 * rigidbody.mass; + +						for (Force & force : forces) { +							rigidbody.velocity_x += force.force_x; +							rigidbody.velocity_y += force.force_y; +						} + +						std::cout << "before transform.postion.x " +								  << transform.position.x << std::endl; +						std::cout << "before transform.postion.y " +								  << transform.position.y << std::endl; +						transform.position.x += rigidbody.velocity_x; +						transform.position.y += rigidbody.velocity_y; +						std::cout << "after transform.postion.x " +								  << transform.position.x << std::endl; +						std::cout << "after transform.postion.y " +								  << transform.position.y << std::endl; +					} +				} +				break; +			case BodyType::KINEMATIC: +				break; //(scripts) +			case BodyType::STATIC: +				break; //(unmoveable objects) +			default: +				break; +		} +	} +} diff --git a/src/crepe/PhysicsSystem.h b/src/crepe/PhysicsSystem.h new file mode 100644 index 0000000..33b4072 --- /dev/null +++ b/src/crepe/PhysicsSystem.h @@ -0,0 +1,11 @@ +#pragma once + +namespace crepe { + +class PhysicsSystem { +public: +	PhysicsSystem(); +	void update(); +}; + +} // namespace crepe diff --git a/src/crepe/Position.h b/src/crepe/Position.h new file mode 100644 index 0000000..f84b63d --- /dev/null +++ b/src/crepe/Position.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe { + +struct Position { +	float x = 0.0; +	float y = 0.0; +}; + +} // namespace crepe diff --git a/src/crepe/RenderSystem.cpp b/src/crepe/RenderSystem.cpp new file mode 100644 index 0000000..fae93f0 --- /dev/null +++ b/src/crepe/RenderSystem.cpp @@ -0,0 +1,42 @@ +#include <functional> +#include <vector> + +#include "api/Sprite.h" +#include "api/Transform.h" +#include "util/log.h" + +#include "ComponentManager.h" +#include "RenderSystem.h" +#include "SDLContext.h" + +using namespace crepe; +using namespace crepe::api; + +RenderSystem::RenderSystem() { dbg_trace(); } + +RenderSystem::~RenderSystem() { dbg_trace(); } + +RenderSystem & RenderSystem::get_instance() { +	static RenderSystem instance; +	return instance; +} + +void RenderSystem::update() { + +	ComponentManager & mgr = ComponentManager::get_instance(); + +	std::vector<std::reference_wrapper<Sprite>> sprites +		= mgr.get_components_by_type<Sprite>(); + +	SDLContext & render = SDLContext::get_instance(); +	render.clear_screen(); + +	for (const Sprite & sprite : sprites) { +		std::vector<std::reference_wrapper<Transform>> transforms +			= mgr.get_components_by_id<Transform>(sprite.game_object_id); +		for (const Transform & transform : transforms) { +			render.draw(sprite, transform); +		} +	} +	render.present_screen(); +} diff --git a/src/crepe/RenderSystem.h b/src/crepe/RenderSystem.h new file mode 100644 index 0000000..4b910a4 --- /dev/null +++ b/src/crepe/RenderSystem.h @@ -0,0 +1,17 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +class RenderSystem : public System { + +public: +	static RenderSystem & get_instance(); +	void update(); + +private: +	RenderSystem(); +	~RenderSystem(); +}; +} // namespace crepe diff --git a/src/crepe/SDLApp.cpp b/src/crepe/SDLApp.cpp new file mode 100644 index 0000000..c6ddeaa --- /dev/null +++ b/src/crepe/SDLApp.cpp @@ -0,0 +1,71 @@ +#include <iostream> + +#include "SDLApp.h" + +SDLApp::SDLApp(int window_width, int window_height) +	: window_width(window_width), window_height(window_height), window(nullptr), +	  renderer(nullptr) {} + +// FIXME: why is there clean_up and ~SDLApp? +SDLApp::~SDLApp() { clean_up(); } + +bool SDLApp::initialize() { +	if (SDL_Init(SDL_INIT_VIDEO) != 0) { +		// FIXME: throw exception +		std::cerr << "SDL Initialization Error: " << SDL_GetError() +				  << std::endl; +		return false; +	} + +	window = SDL_CreateWindow("Particle System", SDL_WINDOWPOS_CENTERED, +							  SDL_WINDOWPOS_CENTERED, window_width, +							  window_height, SDL_WINDOW_SHOWN); +	if (!window) { +		// FIXME: throw exception +		std::cerr << "Window Creation Error: " << SDL_GetError() << std::endl; +		return false; +	} + +	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); +	if (!renderer) { +		// FIXME: throw exception +		std::cerr << "Renderer Creation Error: " << SDL_GetError() << std::endl; +		return false; +	} + +	return true; +} + +void SDLApp::handle_events(bool & running) { +	SDL_Event event; +	while (SDL_PollEvent(&event)) { +		if (event.type == SDL_QUIT) { +			running = false; +		} +	} +} + +void SDLApp::clear_screen() { +	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); +	SDL_RenderClear(renderer); +} + +void SDLApp::present_screen() { SDL_RenderPresent(renderer); } + +void SDLApp::draw_square(int x, int y, int size) { +	SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); +	SDL_Rect rect = {x, y, size, size}; +	SDL_RenderFillRect(renderer, &rect); +} + +SDL_Texture * square_texture = nullptr; // Load this with an image or create it + +void SDLApp::clean_up() { +	if (renderer) { +		SDL_DestroyRenderer(renderer); +	} +	if (window) { +		SDL_DestroyWindow(window); +	} +	SDL_Quit(); +} diff --git a/src/crepe/SDLApp.h b/src/crepe/SDLApp.h new file mode 100644 index 0000000..e67947b --- /dev/null +++ b/src/crepe/SDLApp.h @@ -0,0 +1,26 @@ +#pragma once + +#include <SDL2/SDL.h> + +#include "api/ParticleEmitter.h" + +class SDLApp { +public: +	SDLApp(int window_width, int window_height); +	~SDLApp(); + +	bool initialize(); +	void handle_events(bool & running); +	void clear_screen(); +	void present_screen(); +	void draw_square(int x, int y, int size); +	void clean_up(); +	void draw_particles(const std::vector<crepe::ParticleEmitter> & emitters); +	void draw_multiple_squares(const std::vector<SDL_Rect> & squares); + +private: +	int window_width; +	int window_height; +	SDL_Window * window; +	SDL_Renderer * renderer; +}; diff --git a/src/crepe/SDLContext.cpp b/src/crepe/SDLContext.cpp new file mode 100644 index 0000000..8bc5bc6 --- /dev/null +++ b/src/crepe/SDLContext.cpp @@ -0,0 +1,158 @@ +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_render.h> +#include <SDL2/SDL_surface.h> +#include <SDL2/SDL_video.h> +#include <cmath> +#include <cstddef> +#include <iostream> + +#include "api/Sprite.h" +#include "api/Texture.h" +#include "api/Transform.h" +#include "util/log.h" + +#include "SDLContext.h" + +using namespace crepe; + +SDLContext & SDLContext::get_instance() { +	static SDLContext instance; +	return instance; +} + +void SDLContext::handle_events(bool & running) { +	SDL_Event event; +	while (SDL_PollEvent(&event)) { +		if (event.type == SDL_QUIT) { +			running = false; +		} +	} +} + +SDLContext::~SDLContext() { +	dbg_trace(); + +	if (this->game_renderer != nullptr) +		SDL_DestroyRenderer(this->game_renderer); + +	if (this->game_window != nullptr) { +		SDL_DestroyWindow(this->game_window); +	} + +	// TODO: how are we going to ensure that these are called from the same +	// thread that SDL_Init() was called on? This has caused problems for me +	// before. +	IMG_Quit(); +	SDL_Quit(); +} + +void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer); } + +SDLContext::SDLContext() { +	dbg_trace(); +	// FIXME: read window defaults from config manager + +	if (SDL_Init(SDL_INIT_VIDEO) < 0) { +		// FIXME: throw exception +		std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() +				  << std::endl; +		return; +	} + +	this->game_window = SDL_CreateWindow( +		"Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, +		1920, 1080, SDL_WINDOW_SHOWN); +	if (!this->game_window) { +		// FIXME: throw exception +		std::cerr << "Window could not be created! SDL_Error: " +				  << SDL_GetError() << std::endl; +	} + +	this->game_renderer +		= SDL_CreateRenderer(this->game_window, -1, SDL_RENDERER_ACCELERATED); +	if (!this->game_renderer) { +		// FIXME: throw exception +		std::cerr << "Renderer could not be created! SDL_Error: " +				  << SDL_GetError() << std::endl; +		SDL_DestroyWindow(this->game_window); +		return; +	} + +	int img_flags = IMG_INIT_PNG; +	if (!(IMG_Init(img_flags) & img_flags)) { +		// FIXME: throw exception +		std::cout << "SDL_image could not initialize! SDL_image Error: " +				  << IMG_GetError() << std::endl; +	} +} + +void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer); } + +void SDLContext::draw(const api::Sprite & sprite, +					  const api::Transform & transform) { + +	static SDL_RendererFlip render_flip +		= (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) +							  | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + +	int w, h; +	SDL_QueryTexture(sprite.sprite_image->texture, NULL, NULL, &w, &h); +	// needs maybe camera for position +	SDL_Rect dstrect = { +		.x = static_cast<int>(transform.position.x), +		.y = static_cast<int>(transform.position.y), +		.w = static_cast<int>(w * transform.scale), +		.h = static_cast<int>(h * transform.scale), +	}; + +	double degrees = transform.rotation * 180 / M_PI; +	SDL_RenderCopyEx(this->game_renderer, sprite.sprite_image->texture, NULL, +					 &dstrect, degrees, NULL, render_flip); +} + +/* +SDL_Texture * SDLContext::setTextureFromPath(const char * path, SDL_Rect & clip, +											 const int row, const int col) { +	dbg_trace(); + +	SDL_Surface * tmp = IMG_Load(path); +	if (!tmp) { +		std::cerr << "Error surface " << IMG_GetError << std::endl; +	} + +	clip. +		w = tmp->w / col; +	clip.h = tmp->h / row; + +	SDL_Texture * CreatedTexture +		= SDL_CreateTextureFromSurface(this->game_renderer, tmp); + +	if (!CreatedTexture) { +		std::cerr << "Error could not create texture " << IMG_GetError +				  << std::endl; +	} +	SDL_FreeSurface(tmp); + +	return CreatedTexture; +} +*/ + +SDL_Texture * SDLContext::texture_from_path(const char * path) { +	dbg_trace(); + +	SDL_Surface * tmp = IMG_Load(path); +	if (!tmp) { +		std::cerr << "Error surface " << IMG_GetError << std::endl; +	} +	SDL_Texture * created_texture +		= SDL_CreateTextureFromSurface(this->game_renderer, tmp); + +	if (!created_texture) { +		std::cerr << "Error could not create texture " << IMG_GetError +				  << std::endl; +	} +	SDL_FreeSurface(tmp); + +	return created_texture; +} diff --git a/src/crepe/SDLContext.h b/src/crepe/SDLContext.h new file mode 100644 index 0000000..4d9c1bc --- /dev/null +++ b/src/crepe/SDLContext.h @@ -0,0 +1,52 @@ +#pragma once + +#include <SDL2/SDL_render.h> +#include <SDL2/SDL_video.h> + +#include "api/Sprite.h" +#include "api/Transform.h" + +#include "RenderSystem.h" + +namespace crepe::api { +class Texture; +} + +namespace crepe { + +class SDLContext { + +public: +	// singleton +	static SDLContext & get_instance(); +	SDLContext(const SDLContext &) = delete; +	SDLContext(SDLContext &&) = delete; +	SDLContext & operator=(const SDLContext &) = delete; +	SDLContext & operator=(SDLContext &&) = delete; + +	//TODO decide events wouter? + +private: +	void handle_events(bool & running); + +private: +	SDLContext(); +	virtual ~SDLContext(); + +private: +	friend class api::Texture; +	SDL_Texture * texture_from_path(const char *); +	//SDL_Texture* setTextureFromPath(const char*, SDL_Rect& clip, const int row, const int col); + +private: +	friend class RenderSystem; +	void draw(const api::Sprite &, const api::Transform &); +	void clear_screen(); +	void present_screen(); + +private: +	SDL_Window * game_window = nullptr; +	SDL_Renderer * game_renderer = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/ScriptSystem.cpp b/src/crepe/ScriptSystem.cpp new file mode 100644 index 0000000..171b490 --- /dev/null +++ b/src/crepe/ScriptSystem.cpp @@ -0,0 +1,47 @@ +#include <forward_list> +#include <functional> +#include <vector> + +#include "api/BehaviorScript.h" +#include "api/Script.h" +#include "util/log.h" + +#include "ComponentManager.h" +#include "ScriptSystem.h" + +using namespace std; +using namespace crepe; +using namespace crepe::api; + +ScriptSystem::ScriptSystem() { dbg_trace(); } +ScriptSystem::~ScriptSystem() { dbg_trace(); } + +ScriptSystem & ScriptSystem::get_instance() { +	static ScriptSystem instance; +	return instance; +} + +void ScriptSystem::update() { +	using namespace std; +	dbg_trace(); + +	forward_list<Script *> scripts = this->get_scripts(); +	for (Script * script : scripts) script->update(); +} + +forward_list<Script *> ScriptSystem::get_scripts() { +	forward_list<Script *> scripts = {}; +	ComponentManager & mgr = ComponentManager::get_instance(); +	vector<reference_wrapper<BehaviorScript>> behavior_scripts +		= mgr.get_components_by_type<BehaviorScript>(); + +	for (auto behavior_script_ref : behavior_scripts) { +		BehaviorScript & behavior_script = behavior_script_ref.get(); +		if (!behavior_script.active) continue; +		Script * script = behavior_script.script.get(); +		if (script == nullptr) continue; +		scripts.push_front(script); +	} + +	return scripts; +} diff --git a/src/crepe/ScriptSystem.h b/src/crepe/ScriptSystem.h new file mode 100644 index 0000000..1f472a0 --- /dev/null +++ b/src/crepe/ScriptSystem.h @@ -0,0 +1,26 @@ +#pragma once + +#include <forward_list> + +#include "System.h" + +namespace crepe::api { +class Script; +} + +namespace crepe { + +class ScriptSystem : public System { +public: +	static ScriptSystem & get_instance(); +	void update(); + +private: +	ScriptSystem(); +	~ScriptSystem(); + +private: +	std::forward_list<api::Script *> get_scripts(); +}; + +} // namespace crepe diff --git a/src/crepe/Sound.cpp b/src/crepe/Sound.cpp new file mode 100644 index 0000000..64fa281 --- /dev/null +++ b/src/crepe/Sound.cpp @@ -0,0 +1,60 @@ +#include "util/log.h" + +#include "Sound.h" +#include "SoundContext.h" + +using namespace crepe; + +Sound::Sound(std::unique_ptr<Asset> res) { +	dbg_trace(); +	this->load(std::move(res)); +} + +Sound::Sound(const char * src) { +	dbg_trace(); +	this->load(std::make_unique<Asset>(src)); +} + +void Sound::load(std::unique_ptr<Asset> res) { +	this->sample.load(res->canonical()); +} + +void Sound::play() { +	SoundContext & ctx = SoundContext::get_instance(); +	if (ctx.engine.getPause(this->handle)) { +		// resume if paused +		ctx.engine.setPause(this->handle, false); +	} else { +		// or start new sound +		this->handle = ctx.engine.play(this->sample, this->volume); +		ctx.engine.setLooping(this->handle, this->looping); +	} +} + +void Sound::pause() { +	SoundContext & ctx = SoundContext::get_instance(); +	if (ctx.engine.getPause(this->handle)) return; +	ctx.engine.setPause(this->handle, true); +} + +void Sound::rewind() { +	SoundContext & ctx = SoundContext::get_instance(); +	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; +	ctx.engine.seek(this->handle, 0); +} + +void Sound::set_volume(float volume) { +	this->volume = volume; + +	SoundContext & ctx = SoundContext::get_instance(); +	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; +	ctx.engine.setVolume(this->handle, this->volume); +} + +void Sound::set_looping(bool looping) { +	this->looping = looping; + +	SoundContext & ctx = SoundContext::get_instance(); +	if (!ctx.engine.isValidVoiceHandle(this->handle)) return; +	ctx.engine.setLooping(this->handle, this->looping); +} diff --git a/src/crepe/Sound.h b/src/crepe/Sound.h new file mode 100644 index 0000000..917b57e --- /dev/null +++ b/src/crepe/Sound.h @@ -0,0 +1,81 @@ +#pragma once + +#include <memory> +#include <soloud/soloud.h> +#include <soloud/soloud_wav.h> + +#include "Asset.h" + +namespace crepe { + +class Sound { +public: +	/** +	 * \brief Pause this sample +	 * +	 * Pauses this sound if it is playing, or does nothing if it is already +	 * paused. The playhead position is saved, such that calling \c play() after +	 * this function makes the sound resume. +	 */ +	void pause(); +	/** +	 * \brief Play this sample +	 * +	 * Resume playback if this sound is paused, or start from the beginning of +	 * the sample. +	 * +	 * \note This class only saves a reference to the most recent 'voice' of this +	 * sound. Calling \c play() while the sound is already playing causes +	 * multiple instances of the sample to play simultaniously. The sample +	 * started last is the one that is controlled afterwards. +	 */ +	void play(); +	/** +	 * \brief Reset playhead position +	 *  +	 * Resets the playhead position so that calling \c play() after this function +	 * makes it play from the start of the sample. If the sound is not paused +	 * before calling this function, this function will stop playback. +	 */ +	void rewind(); +	/** +	 * \brief Set playback volume / gain +	 * +	 * \param volume  Volume (0 = muted, 1 = full volume) +	 */ +	void set_volume(float volume); +	/** +	 * \brief Get playback volume / gain +	 * +	 * \return Volume +	 */ +	float get_volume() const { return this->volume; } +	/** +	 * \brief Set looping behavior for this sample +	 * +	 * \param looping  Looping behavior (false = one-shot, true = loop) +	 */ +	void set_looping(bool looping); +	/** +	 * \brief Get looping behavior +	 * +	 * \return true if looping, false if one-shot +	 */ +	bool get_looping() const { return this->looping; } + +public: +	Sound(const char * src); +	Sound(std::unique_ptr<Asset> res); + +private: +	void load(std::unique_ptr<Asset> res); + +private: +	SoLoud::Wav sample; +	SoLoud::handle handle; + +	float volume = 1.0f; +	bool looping = false; +}; + +} // namespace crepe diff --git a/src/crepe/SoundContext.cpp b/src/crepe/SoundContext.cpp new file mode 100644 index 0000000..72047d2 --- /dev/null +++ b/src/crepe/SoundContext.cpp @@ -0,0 +1,20 @@ +#include "util/log.h" + +#include "SoundContext.h" + +using namespace crepe; + +SoundContext & SoundContext::get_instance() { +	static SoundContext instance; +	return instance; +} + +SoundContext::SoundContext() { +	dbg_trace(); +	engine.init(); +} + +SoundContext::~SoundContext() { +	dbg_trace(); +	engine.deinit(); +} diff --git a/src/crepe/SoundContext.h b/src/crepe/SoundContext.h new file mode 100644 index 0000000..d3123d2 --- /dev/null +++ b/src/crepe/SoundContext.h @@ -0,0 +1,26 @@ +#pragma once + +#include <soloud/soloud.h> + +#include "Sound.h" + +namespace crepe { + +class SoundContext { +private: +	SoundContext(); +	virtual ~SoundContext(); + +	// singleton +	static SoundContext & get_instance(); +	SoundContext(const SoundContext &) = delete; +	SoundContext(SoundContext &&) = delete; +	SoundContext & operator=(const SoundContext &) = delete; +	SoundContext & operator=(SoundContext &&) = delete; + +private: +	SoLoud::Soloud engine; +	friend class Sound; +}; + +} // namespace crepe diff --git a/src/crepe/System.h b/src/crepe/System.h new file mode 100644 index 0000000..ecbb7f5 --- /dev/null +++ b/src/crepe/System.h @@ -0,0 +1,22 @@ +#pragma once + +namespace crepe { + +class System { +public: +	static System & get_instance(); +	virtual void update() = 0; + +protected: +	System() {}; +	virtual ~System() {}; + +private: +	// singleton +	System(const System &) = delete; +	System(System &&) = delete; +	System & operator=(const System &) = delete; +	System & operator=(System &&) = delete; +}; + +} // namespace crepe diff --git a/src/crepe/api/AssetManager.cpp b/src/crepe/api/AssetManager.cpp new file mode 100644 index 0000000..560df6c --- /dev/null +++ b/src/crepe/api/AssetManager.cpp @@ -0,0 +1,17 @@ +#include "util/log.h" + +#include "AssetManager.h" + +using namespace crepe::api; + +AssetManager & AssetManager::get_instance() { +	static AssetManager instance; +	return instance; +} + +AssetManager::~AssetManager() { +	dbg_trace(); +	this->asset_cache.clear(); +} + +AssetManager::AssetManager() { dbg_trace(); } diff --git a/src/crepe/api/AssetManager.h b/src/crepe/api/AssetManager.h new file mode 100644 index 0000000..3e72a49 --- /dev/null +++ b/src/crepe/api/AssetManager.h @@ -0,0 +1,35 @@ +#pragma once + +#include <any> +#include <memory> +#include <string> +#include <unordered_map> + +namespace crepe::api { + +class AssetManager { + +private: +	std::unordered_map<std::string, std::any> asset_cache; + +private: +	AssetManager(); +	virtual ~AssetManager(); + +public: +	AssetManager(const AssetManager &) = delete; +	AssetManager(AssetManager &&) = delete; +	AssetManager & operator=(const AssetManager &) = delete; +	AssetManager & operator=(AssetManager &&) = delete; + +	static AssetManager & get_instance(); + +public: +	template <typename asset> +	std::shared_ptr<asset> cache(const std::string & file_path, +								 bool reload = false); +}; + +} // namespace crepe::api + +#include "AssetManager.hpp" diff --git a/src/crepe/api/AssetManager.hpp b/src/crepe/api/AssetManager.hpp new file mode 100644 index 0000000..468724c --- /dev/null +++ b/src/crepe/api/AssetManager.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "AssetManager.h" + +namespace crepe::api { + +template <typename asset> +std::shared_ptr<asset> AssetManager::cache(const std::string & file_path, +										   bool reload) { +	auto it = asset_cache.find(file_path); + +	if (!reload && it != asset_cache.end()) { +		return std::any_cast<std::shared_ptr<asset>>(it->second); +	} + +	std::shared_ptr<asset> new_asset +		= std::make_shared<asset>(file_path.c_str()); + +	asset_cache[file_path] = new_asset; + +	return new_asset; +} + +} // namespace crepe::api diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..35b8d83 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,23 @@ +#include <memory> + +#include "../Sound.h" + +#include "AudioSource.h" + +using namespace crepe::api; + +AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) { +	this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip)); +} + +void AudioSource::play() { return this->play(false); } + +void AudioSource::play(bool looping) { +	this->sound->set_looping(looping); +	this->sound->play(); +} + +void AudioSource::stop() { +	this->sound->pause(); +	this->sound->rewind(); +} diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..7980212 --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,41 @@ +#pragma once + +#include <memory> + +#include "../Asset.h" +#include "../Component.h" + +namespace crepe { +class Sound; +} + +namespace crepe::api { + +//! Audio source component +class AudioSource : Component { +public: +	AudioSource(std::unique_ptr<Asset> audio_clip); +	virtual ~AudioSource() = default; + +public: +	//! Start or resume this audio source +	void play(); +	void play(bool looping); +	//! Stop this audio source +	void stop(); + +public: +	//! Sample file location +	std::unique_ptr<Asset> audio_clip; +	//! TODO: ????? +	bool play_on_awake; +	//! Repeat the current audio clip during playback +	bool loop; +	//! Normalized volume (0.0 - 1.0) +	float volume; + +private: +	std::unique_ptr<crepe::Sound> sound; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/crepe/api/BehaviorScript.cpp diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h new file mode 100644 index 0000000..6133cc8 --- /dev/null +++ b/src/crepe/api/BehaviorScript.h @@ -0,0 +1,35 @@ +#pragma once + +#include <memory> + +#include "../Component.h" + +namespace crepe { +class ScriptSystem; +class ComponentManager; +} // namespace crepe + +namespace crepe::api { + +class Script; + +class BehaviorScript : public Component { +protected: +	friend class crepe::ComponentManager; +	using Component::Component; + +public: +	virtual ~BehaviorScript() = default; + +public: +	template <class T> +	BehaviorScript & set_script(); + +protected: +	friend class crepe::ScriptSystem; +	std::unique_ptr<Script> script = nullptr; +}; + +} // namespace crepe::api + +#include "BehaviorScript.hpp" diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp new file mode 100644 index 0000000..2a3502f --- /dev/null +++ b/src/crepe/api/BehaviorScript.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <type_traits> + +#include "../util/log.h" + +#include "BehaviorScript.h" +#include "Script.h" + +namespace crepe::api { + +template <class T> +BehaviorScript & BehaviorScript::set_script() { +	static_assert(std::is_base_of<Script, T>::value); +	dbg_trace(); +	Script * s = new T(); +	s->parent = this; +	this->script = std::unique_ptr<Script>(s); +	return *this; +} + +} // namespace crepe::api diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt new file mode 100644 index 0000000..0bb1263 --- /dev/null +++ b/src/crepe/api/CMakeLists.txt @@ -0,0 +1,31 @@ +target_sources(crepe PUBLIC +	# AudioSource.cpp +	BehaviorScript.cpp +	Script.cpp +	GameObject.cpp +	Rigidbody.cpp +	Force.cpp +	ParticleEmitter.cpp +	Transform.cpp +	Color.cpp +	Texture.cpp +	AssetManager.cpp +	Sprite.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES +	# AudioSource.h +	BehaviorScript.h +	Config.h +	Script.h +	Script.hpp +	GameObject.h +	GameObject.hpp +	Rigidbody.h +	Sprite.h +	Point.h +	Color.h +	Texture.h  +	AssetManager.h  +	AssetManager.hpp +) diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h new file mode 100644 index 0000000..762574b --- /dev/null +++ b/src/crepe/api/CircleCollider.h @@ -0,0 +1,13 @@ +#pragma once +#include "../Collider.h" + +namespace crepe::api { + +class CircleCollider : public Collider { +public: +	CircleCollider(uint32_t game_object_id, int radius) +		: Collider(game_object_id), radius(radius) {} +	int radius; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Color.cpp b/src/crepe/api/Color.cpp new file mode 100644 index 0000000..fb5bd1a --- /dev/null +++ b/src/crepe/api/Color.cpp @@ -0,0 +1,34 @@ +#include "Color.h" + +using namespace crepe::api; + +Color Color::white = Color(255, 255, 255, 0); +Color Color::red = Color(255, 0, 0, 0); +Color Color::green = Color(0, 255, 0, 0); +Color Color::blue = Color(0, 0, 255, 0); +Color Color::black = Color(0, 0, 0, 0); +Color Color::cyan = Color(0, 255, 255, 0); +Color Color::yellow = Color(255, 255, 0, 0); +Color Color::magenta = Color(255, 0, 255, 0); + +// FIXME: do we really need double precision for color values? +Color::Color(double red, double green, double blue, double alpha) { +	this->a = alpha; +	this->r = red; +	this->g = green; +	this->b = blue; +}; + +const Color & Color::get_white() { return Color::white; }; + +const Color & Color::get_red() { return Color::red; }; +const Color & Color::get_green() { return Color::green; }; +const Color & Color::get_blue() { return Color::blue; }; + +const Color & Color::get_black() { return Color::black; }; + +const Color & Color::get_cyan() { return Color::cyan; }; + +const Color & Color::get_yellow() { return Color::yellow; }; + +const Color & Color::get_magenta() { return Color::magenta; }; diff --git a/src/crepe/api/Color.h b/src/crepe/api/Color.h new file mode 100644 index 0000000..e818de4 --- /dev/null +++ b/src/crepe/api/Color.h @@ -0,0 +1,37 @@ +#pragma once + +namespace crepe::api { + +class Color { + +	// FIXME: can't these colors be defined as a `static constexpr const Color` +	// instead? + +public: +	Color(double red, double green, double blue, double alpha); +	static const Color & get_white(); +	static const Color & get_red(); +	static const Color & get_green(); +	static const Color & get_blue(); +	static const Color & get_cyan(); +	static const Color & get_magenta(); +	static const Color & get_yellow(); +	static const Color & get_black(); + +private: +	double r; +	double g; +	double b; +	double a; + +	static Color white; +	static Color red; +	static Color green; +	static Color blue; +	static Color cyan; +	static Color magenta; +	static Color yellow; +	static Color black; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Config.h b/src/crepe/api/Config.h new file mode 100644 index 0000000..8a7f268 --- /dev/null +++ b/src/crepe/api/Config.h @@ -0,0 +1,40 @@ +#pragma once + +#include "../util/log.h" + +namespace crepe::api { + +class Config { +private: +	Config() = default; + +public: +	~Config() = default; + +public: +	//! Retrieve handle to global Config instance +	static Config & get_instance() { +		static Config instance; +		return instance; +	} + +public: +	//! Logging-related settings +	struct { +		/** +		 * \brief Log level +		 * +		 * Only messages with equal or higher priority than this value will be +		 * logged. +		 */ +		util::LogLevel level = util::LogLevel::INFO; +		/** +		 * \brief Colored log output +		 * +		 * Enables log coloring using ANSI escape codes. +		 */ +		bool color = true; +	} log; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Force.cpp b/src/crepe/api/Force.cpp new file mode 100644 index 0000000..e359adc --- /dev/null +++ b/src/crepe/api/Force.cpp @@ -0,0 +1,21 @@ +#include <cmath> + +#include "Force.h" + +namespace crepe::api { + +Force::Force(uint32_t game_object_id, uint32_t magnitude, uint32_t direction) +	: Component(game_object_id) { +	// TODO: A standard angle unit should be established for the entire engine +	// and assumed to be the default everywhere. Only conversion functions should +	// explicitly contain the unit (i.e. `deg_to_rad()` & `rad_to_deg()`) + +	// Convert direction from degrees to radians +	float radian_direction = static_cast<float>(direction) * (M_PI / 180.0f); +	force_x = static_cast<int32_t>( +		std::round(magnitude * std::cos(radian_direction))); +	force_y = static_cast<int32_t>( +		std::round(magnitude * std::sin(radian_direction))); +} + +} // namespace crepe::api diff --git a/src/crepe/api/Force.h b/src/crepe/api/Force.h new file mode 100644 index 0000000..8da9a00 --- /dev/null +++ b/src/crepe/api/Force.h @@ -0,0 +1,17 @@ +#pragma once + +#include <cstdint> + +#include "../Component.h" + +namespace crepe::api { + +class Force : public Component { +public: +	Force(uint32_t game_object_id, uint32_t magnitude, uint32_t direction); + +	int32_t force_x; +	int32_t force_y; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp new file mode 100644 index 0000000..b167187 --- /dev/null +++ b/src/crepe/api/GameObject.cpp @@ -0,0 +1,7 @@ +#include "GameObject.h" + +using namespace crepe::api; +using namespace std; + +GameObject::GameObject(uint32_t id, string name, string tag, int layer) +	: id(id), name(name), tag(tag), active(true), layer(layer) {} diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h new file mode 100644 index 0000000..57508c5 --- /dev/null +++ b/src/crepe/api/GameObject.h @@ -0,0 +1,24 @@ +#pragma once + +#include <cstdint> +#include <string> + +namespace crepe::api { + +class GameObject { +public: +	GameObject(uint32_t id, std::string name, std::string tag, int layer); + +	template <typename T, typename... Args> +	T & add_component(Args &&... args); + +	uint32_t id; +	std::string name; +	std::string tag; +	bool active; +	int layer; +}; + +} // namespace crepe::api + +#include "GameObject.hpp" diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp new file mode 100644 index 0000000..3c7e867 --- /dev/null +++ b/src/crepe/api/GameObject.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../ComponentManager.h" + +#include "GameObject.h" + +namespace crepe::api { + +template <typename T, typename... Args> +T & GameObject::add_component(Args &&... args) { +	auto & mgr = ComponentManager::get_instance(); +	return mgr.add_component<T>(this->id, std::forward<Args>(args)...); +} + +} // namespace crepe::api diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp new file mode 100644 index 0000000..0b3a9ee --- /dev/null +++ b/src/crepe/api/ParticleEmitter.cpp @@ -0,0 +1,37 @@ +#include <ctime> +#include <iostream> + +#include "Particle.h" +#include "ParticleEmitter.h" + +using namespace crepe; + +ParticleEmitter::ParticleEmitter(uint32_t game_object_id, +								 uint32_t max_particles, uint32_t emission_rate, +								 uint32_t speed, uint32_t speed_offset, +								 uint32_t angle, uint32_t angleOffset, +								 float begin_lifespan, float end_lifespan) +	: Component(game_object_id), max_particles(max_particles), +	  emission_rate(emission_rate), speed(speed), speed_offset(speed_offset), +	  position{0, 0}, begin_lifespan(begin_lifespan), +	  end_lifespan(end_lifespan) { +	std::srand( +		static_cast<uint32_t>(std::time(nullptr))); // initialize random seed +	std::cout << "Create emitter" << std::endl; +	// FIXME: Why do these expressions start with `360 +`, only to be `% 360`'d +	// right after? This does not make any sense to me. +	min_angle = (360 + angle - (angleOffset % 360)) % 360; +	max_angle = (360 + angle + (angleOffset % 360)) % 360; +	position.x = 400; // FIXME: what are these magic values? +	position.y = 400; +	for (size_t i = 0; i < max_particles; i++) { +		this->particles.emplace_back(); +	} +} + +ParticleEmitter::~ParticleEmitter() { +	std::vector<Particle>::iterator it = this->particles.begin(); +	while (it != this->particles.end()) { +		it = this->particles.erase(it); +	} +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h new file mode 100644 index 0000000..2e2e95b --- /dev/null +++ b/src/crepe/api/ParticleEmitter.h @@ -0,0 +1,42 @@ +#pragma once + +#include <cstdint> +#include <vector> + +#include "Component.h" +#include "Particle.h" + +namespace crepe { + +class ParticleEmitter : public Component { +public: +	ParticleEmitter(uint32_t game_object_id, uint32_t max_particles, +					uint32_t emission_rate, uint32_t speed, +					uint32_t speed_offset, uint32_t angle, uint32_t angleOffset, +					float begin_lifespan, float end_lifespan); +	~ParticleEmitter(); + +	//! position of the emitter +	Position position; +	//! maximum number of particles +	uint32_t max_particles; +	//! rate of particle emission +	uint32_t emission_rate; +	//! base speed of the particles +	uint32_t speed; +	//! offset for random speed variation +	uint32_t speed_offset; +	//! min angle of particle emission +	uint32_t min_angle; +	//! max angle of particle emission +	uint32_t max_angle; +	//! begin Lifespan of particle (only visual) +	float begin_lifespan; +	//! begin Lifespan of particle +	float end_lifespan; + +	//! collection of particles +	std::vector<Particle> particles; +}; + +} // namespace crepe diff --git a/src/crepe/api/Point.h b/src/crepe/api/Point.h new file mode 100644 index 0000000..b47b7e6 --- /dev/null +++ b/src/crepe/api/Point.h @@ -0,0 +1,11 @@ +#pragma once + +namespace crepe::api { + +class Point { +public: +	double x; +	double y; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp new file mode 100644 index 0000000..ebf9fb9 --- /dev/null +++ b/src/crepe/api/Rigidbody.cpp @@ -0,0 +1,8 @@ +#include "Rigidbody.h" + +using namespace crepe::api; + +Rigidbody::Rigidbody(uint32_t game_object_id, int mass, int gravity_scale, +					 BodyType bodyType) +	: Component(game_object_id), mass(mass), gravity_scale(gravity_scale), +	  body_type(bodyType) {} diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h new file mode 100644 index 0000000..6079a76 --- /dev/null +++ b/src/crepe/api/Rigidbody.h @@ -0,0 +1,30 @@ +#pragma once + +#include <cstdint> + +#include "../Component.h" + +namespace crepe::api { + +// FIXME: can't this enum be defined inside the class declaration of Rigidbody? +enum class BodyType { +	//! Does not move (e.g. walls, ground ...) +	STATIC, +	//! Moves and responds to forces (e.g. player, physics objects ...) +	DYNAMIC, +	//! Moves but does not respond to forces (e.g. moving platforms ...) +	KINEMATIC, +}; + +class Rigidbody : public Component { +public: +	Rigidbody(uint32_t game_object_id, int mass, int gravity_scale, +			  BodyType body_type); +	int32_t velocity_x; +	int32_t velocity_y; +	int mass; +	int gravity_scale; +	BodyType body_type; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..5016ed0 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,3 @@ +#include "Script.h" + +using namespace crepe::api; diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h new file mode 100644 index 0000000..59e6ec0 --- /dev/null +++ b/src/crepe/api/Script.h @@ -0,0 +1,38 @@ +#pragma once + +#include <vector> + +namespace crepe { +class ScriptSystem; +} + +namespace crepe::api { + +class BehaviorScript; + +class Script { +	friend class crepe::ScriptSystem; + +protected: +	virtual void init() {} +	virtual void update() {} +	// NOTE: additional *events* (like unity's OnDisable and OnEnable) should be +	// implemented as member methods in derivative user script classes and +	// registered in init(), otherwise this class will balloon in size with each +	// added event. + +protected: +	template <typename T> +	T & get_component(); + +	template <typename T> +	std::vector<std::reference_wrapper<T>> get_components(); + +private: +	friend class crepe::api::BehaviorScript; +	BehaviorScript * parent = nullptr; +}; + +} // namespace crepe::api + +#include "Script.hpp" diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp new file mode 100644 index 0000000..8004fe3 --- /dev/null +++ b/src/crepe/api/Script.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "../ComponentManager.h" + +#include "BehaviorScript.h" +#include "Script.h" + +namespace crepe::api { + +template <typename T> +T & Script::get_component() { +	std::vector<std::reference_wrapper<T>> all_components +		= this->get_components<T>(); +	if (all_components.size() < 1) throw nullptr; // TODO + +	return all_components.back().get(); +} + +template <typename T> +std::vector<std::reference_wrapper<T>> Script::get_components() { +	ComponentManager & mgr = ComponentManager::get_instance(); +	return mgr.get_components_by_id<T>(this->parent->game_object_id); +} + +} // namespace crepe::api diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp new file mode 100644 index 0000000..806f147 --- /dev/null +++ b/src/crepe/api/Sprite.cpp @@ -0,0 +1,20 @@ +#include <cstdint> +#include <memory> + +#include "api/Texture.h" +#include "util/log.h" + +#include "Component.h" +#include "Sprite.h" + +using namespace std; +using namespace crepe; +using namespace crepe::api; + +Sprite::Sprite(uint32_t id, shared_ptr<Texture> image, const Color & color, +			   const FlipSettings & flip) +	: Component(id), color(color), flip(flip), sprite_image(image) { +	dbg_trace(); +} + +Sprite::~Sprite() { dbg_trace(); } diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h new file mode 100644 index 0000000..b06125e --- /dev/null +++ b/src/crepe/api/Sprite.h @@ -0,0 +1,32 @@ +#pragma once + +#include <SDL2/SDL_rect.h> +#include <cstdint> +#include <memory> + +#include "api/Color.h" +#include "api/Texture.h" + +#include "Component.h" + +namespace crepe::api { + +struct FlipSettings { +	bool flip_x = 1; +	bool flip_y = 1; +}; + +class Sprite : public Component { + +public: +	Sprite(uint32_t game_id, std::shared_ptr<Texture> image, +		   const Color & color, const FlipSettings & flip); +	~Sprite(); +	std::shared_ptr<Texture> sprite_image; +	Color color; +	FlipSettings flip; +	uint8_t sorting_in_layer; +	uint8_t order_in_layer; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp new file mode 100644 index 0000000..481ef7c --- /dev/null +++ b/src/crepe/api/Texture.cpp @@ -0,0 +1,32 @@ +#include <SDL2/SDL_render.h> + +#include "util/log.h" + +#include "Asset.h" +#include "SDLContext.h" +#include "Texture.h" + +using namespace crepe::api; +using namespace std; + +Texture::Texture(unique_ptr<Asset> res) { +	dbg_trace(); +	this->load(std::move(res)); +} + +Texture::Texture(const char * src) { +	dbg_trace(); +	this->load(make_unique<Asset>(src)); +} + +Texture::~Texture() { +	dbg_trace(); +	if (this->texture != nullptr) { +		SDL_DestroyTexture(this->texture); +	} +} + +void Texture::load(unique_ptr<Asset> res) { +	SDLContext & ctx = SDLContext::get_instance(); +	this->texture = ctx.texture_from_path(res->canonical()); +} diff --git a/src/crepe/api/Texture.h b/src/crepe/api/Texture.h new file mode 100644 index 0000000..f8481e3 --- /dev/null +++ b/src/crepe/api/Texture.h @@ -0,0 +1,33 @@ +#pragma once + +// FIXME: this header can't be included because this is an API header, and SDL2 +// development headers won't be bundled with crepe. Why is this facade in the +// API namespace? +#include <SDL2/SDL_render.h> +#include <memory> + +#include "Asset.h" + +namespace crepe { +class SDLContext; +} + +namespace crepe::api { + +class Texture { + +public: +	Texture(const char * src); +	Texture(std::unique_ptr<Asset> res); +	~Texture(); + +private: +	void load(std::unique_ptr<Asset> res); + +private: +	SDL_Texture * texture = nullptr; + +	friend class crepe::SDLContext; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp new file mode 100644 index 0000000..3b218bc --- /dev/null +++ b/src/crepe/api/Transform.cpp @@ -0,0 +1,15 @@ +#include <cstdint> + +#include "api/Point.h" +#include "util/log.h" + +#include "Component.h" +#include "Transform.h" + +using namespace crepe::api; + +Transform::Transform(uint32_t game_id, const Point & point, double rot, double scale) +	: Component(game_id), position(point), rotation(rot), scale(scale) { +	dbg_trace(); +} +Transform::~Transform() { dbg_trace(); } diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h new file mode 100644 index 0000000..c451c16 --- /dev/null +++ b/src/crepe/api/Transform.h @@ -0,0 +1,27 @@ +#pragma once + +#include <cstdint> + +#include "api/Point.h" + +#include "Component.h" + +namespace crepe::api { + +class Transform : public Component { +	// FIXME: What's the difference between the `Point` and `Position` +	// classes/structs? How about we replace both with a universal `Vec2` that +	// works similar (or the same) as those found in GLSL? + +public: +	Transform(uint32_t id, const Point &, double, double); +	~Transform(); +	//! Translation (shift) +	Point position; +	//! Rotation, in radians +	double rotation; +	//! Multiplication factor +	double scale; +}; + +} // namespace crepe::api diff --git a/src/crepe/main.cpp b/src/crepe/main.cpp deleted file mode 100644 index 8e9a184..0000000 --- a/src/crepe/main.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include <stdio.h> - -int main() { printf("Hello World!\n"); } diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt new file mode 100644 index 0000000..bbeaad9 --- /dev/null +++ b/src/crepe/util/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources(crepe PUBLIC +	color.cpp +	log.cpp +	fmt.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES +	color.h +	log.h +	fmt.h +) + diff --git a/src/crepe/util/color.cpp b/src/crepe/util/color.cpp new file mode 100644 index 0000000..a7bbc81 --- /dev/null +++ b/src/crepe/util/color.cpp @@ -0,0 +1,91 @@ +#include <cstdarg> + +#include "../api/Config.h" +#include "color.h" +#include "fmt.h" + +using namespace crepe::util; +using namespace std; + +static constexpr const char * RESET_CODE = "\e[0m"; + +const string LogColor::str(const string & content) { +	auto & cfg = api::Config::get_instance(); +	string out = content; +	if (cfg.log.color) out = this->code + out; +	if (content.size() == 0) return out; +	if (cfg.log.color) out = out + RESET_CODE; +	return out; +} + +const char * LogColor::c_str(const char * content) { +	this->final = this->str(content == NULL ? "" : content); +	return this->final.c_str(); +} + +const char * LogColor::fmt(const char * fmt, ...) { +	va_list args; +	va_start(args, fmt); +	string content = va_stringf(args, fmt); +	va_end(args); +	return this->c_str(content.c_str()); +} + +LogColor & LogColor::add_code(unsigned int code) { +	this->code += stringf("\e[%dm", code); +	return *this; +} + +LogColor & LogColor::reset() { +	this->code = RESET_CODE; +	return *this; +} + +LogColor & LogColor::fg_black(bool bright) { +	return this->add_code(bright ? 90 : 30); +} +LogColor & LogColor::fg_red(bool bright) { +	return this->add_code(bright ? 91 : 31); +} +LogColor & LogColor::fg_green(bool bright) { +	return this->add_code(bright ? 92 : 32); +} +LogColor & LogColor::fg_yellow(bool bright) { +	return this->add_code(bright ? 93 : 33); +} +LogColor & LogColor::fg_blue(bool bright) { +	return this->add_code(bright ? 94 : 34); +} +LogColor & LogColor::fg_magenta(bool bright) { +	return this->add_code(bright ? 95 : 35); +} +LogColor & LogColor::fg_cyan(bool bright) { +	return this->add_code(bright ? 96 : 36); +} +LogColor & LogColor::fg_white(bool bright) { +	return this->add_code(bright ? 97 : 37); +} +LogColor & LogColor::bg_black(bool bright) { +	return this->add_code(bright ? 100 : 40); +} +LogColor & LogColor::bg_red(bool bright) { +	return this->add_code(bright ? 101 : 41); +} +LogColor & LogColor::bg_green(bool bright) { +	return this->add_code(bright ? 102 : 42); +} +LogColor & LogColor::bg_yellow(bool bright) { +	return this->add_code(bright ? 103 : 43); +} +LogColor & LogColor::bg_blue(bool bright) { +	return this->add_code(bright ? 104 : 44); +} +LogColor & LogColor::bg_magenta(bool bright) { +	return this->add_code(bright ? 105 : 45); +} +LogColor & LogColor::bg_cyan(bool bright) { +	return this->add_code(bright ? 106 : 46); +} +LogColor & LogColor::bg_white(bool bright) { +	return this->add_code(bright ? 107 : 47); +} diff --git a/src/crepe/util/color.h b/src/crepe/util/color.h new file mode 100644 index 0000000..91e1abe --- /dev/null +++ b/src/crepe/util/color.h @@ -0,0 +1,51 @@ +#pragma once + +#include <string> + +namespace crepe::util { + +class LogColor { +public: +	LogColor() = default; + +public: +	//! get color code as c-style string (or color content string) +	const char * c_str(const char * content = NULL); +	//! color printf-style format string +	const char * fmt(const char * fmt, ...); +	//! get color code as stl string (or color content string) +	const std::string str(const std::string & content = ""); + +public: +	//! reset color to default foreground and background color +	LogColor & reset(); + +public: +	LogColor & fg_black(bool bright = false); +	LogColor & fg_red(bool bright = false); +	LogColor & fg_green(bool bright = false); +	LogColor & fg_yellow(bool bright = false); +	LogColor & fg_blue(bool bright = false); +	LogColor & fg_magenta(bool bright = false); +	LogColor & fg_cyan(bool bright = false); +	LogColor & fg_white(bool bright = false); + +public: +	LogColor & bg_black(bool bright = false); +	LogColor & bg_red(bool bright = false); +	LogColor & bg_green(bool bright = false); +	LogColor & bg_yellow(bool bright = false); +	LogColor & bg_blue(bool bright = false); +	LogColor & bg_magenta(bool bright = false); +	LogColor & bg_cyan(bool bright = false); +	LogColor & bg_white(bool bright = false); + +private: +	LogColor & add_code(unsigned int code); + +private: +	std::string code = ""; +	std::string final = ""; +}; + +} // namespace crepe::util diff --git a/src/crepe/util/fmt.cpp b/src/crepe/util/fmt.cpp new file mode 100644 index 0000000..8ef1164 --- /dev/null +++ b/src/crepe/util/fmt.cpp @@ -0,0 +1,33 @@ +#include <cstdarg> +#include <cstdio> +#include <string> + +#include "fmt.h" + +using namespace std; + +string crepe::util::va_stringf(va_list args, const char * fmt) { +	va_list args_copy; +	va_copy(args_copy, args); + +	size_t sz = vsnprintf(NULL, 0, fmt, args_copy) + 1; +	char * msg = (char *) malloc(sz); +	va_end(args_copy); + +	vsnprintf(msg, sz, fmt, args); + +	string out = msg; +	free(msg); + +	va_end(args); + +	return out; +} + +string crepe::util::stringf(const char * fmt, ...) { +	va_list args; +	va_start(args, fmt); +	string out = va_stringf(args, fmt); +	va_end(args); +	return out; +} diff --git a/src/crepe/util/fmt.h b/src/crepe/util/fmt.h new file mode 100644 index 0000000..44c426f --- /dev/null +++ b/src/crepe/util/fmt.h @@ -0,0 +1,10 @@ +#pragma once + +#include <string> + +namespace crepe::util { + +std::string va_stringf(va_list args, const char * fmt); +std::string stringf(const char * fmt, ...); + +} // namespace crepe::util diff --git a/src/crepe/util/log.cpp b/src/crepe/util/log.cpp new file mode 100644 index 0000000..6bcc4ae --- /dev/null +++ b/src/crepe/util/log.cpp @@ -0,0 +1,53 @@ +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <string> + +#include "../api/Config.h" +#include "fmt.h" +#include "log.h" + +using namespace crepe::util; +using namespace std; + +string log_prefix(LogLevel level) { +	switch (level) { +		case LogLevel::TRACE: +			return LogColor().fg_white().str("[TRACE]") + " "; +		case LogLevel::DEBUG: +			return LogColor().fg_magenta().str("[DEBUG]") + " "; +		case LogLevel::INFO: +			return LogColor().fg_blue().str("[INFO]") + " "; +		case LogLevel::WARNING: +			return LogColor().fg_yellow().str("[WARN]") + " "; +		case LogLevel::ERROR: +			return LogColor().fg_red().str("[ERROR]") + " "; +	} +	return ""; +} + +static void log(LogLevel level, const string msg) { +	auto & cfg = crepe::api::Config::get_instance(); +	if (level < cfg.log.level) return; + +	string out = log_prefix(level) + msg; +	if (!out.ends_with("\n")) out += "\n"; + +	// TODO: also log to file or smth +	printf("%s", out.c_str()); +	fflush(stdout); +} + +void crepe::util::logf(const char * fmt, ...) { +	va_list args; +	va_start(args, fmt); +	log(LogLevel::DEBUG, va_stringf(args, fmt)); +	va_end(args); +} + +void crepe::util::logf(LogLevel level, const char * fmt, ...) { +	va_list args; +	va_start(args, fmt); +	log(level, va_stringf(args, fmt)); +	va_end(args); +} diff --git a/src/crepe/util/log.h b/src/crepe/util/log.h new file mode 100644 index 0000000..308ba96 --- /dev/null +++ b/src/crepe/util/log.h @@ -0,0 +1,40 @@ +#pragma once + +// allow user to disable debug macros +#ifndef CREPE_DISABLE_MACROS + +#include "color.h" + +// utility macros +#define _crepe_logf_here(level, format, ...) \ +	crepe::util::logf( \ +		level, "%s" format, \ +		crepe::util::LogColor().fg_white(false).fmt( \ +			"%s (%s:%d)", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__), \ +		__VA_ARGS__) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) \ +	_crepe_logf_here(crepe::util::LogLevel::DEBUG, ": " fmt, __VA_ARGS__) +#define dbg_log(str) \ +	_crepe_logf_here(crepe::util::LogLevel::DEBUG, "%s: " str, "") +#define dbg_trace() _crepe_logf_here(crepe::util::LogLevel::TRACE, "%s", "") +// NOLINTEND + +#endif + +namespace crepe::util { + +enum LogLevel { +	TRACE, +	DEBUG, +	INFO, +	WARNING, +	ERROR, +}; + +void logf(const char * fmt, ...); +void logf(enum LogLevel level, const char * fmt, ...); + +} // namespace crepe::util diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt new file mode 100644 index 0000000..fea6f60 --- /dev/null +++ b/src/example/CMakeLists.txt @@ -0,0 +1,26 @@ +# all examples +add_custom_target(examples) + +# add_example(target_name [SOURCES...]) +function(add_example target_name) +	# if SOURCES is not specified +	if(NOT ARGV1) +		# A .cpp file with target_name exists, and should be used +		set(sources ${target_name}.cpp) +	else() +		set(sources ${ARGV}) +	endif() + +	add_executable(${target_name} EXCLUDE_FROM_ALL ${sources}) +	target_link_libraries(${target_name} PUBLIC crepe) +	add_dependencies(examples ${target_name}) +endfunction() + +add_example(audio_internal) +# add_example(components_internal) +add_example(script) +add_example(log) +add_example(rendering) +add_example(asset_manager) +add_example(particle) +add_example(physics) diff --git a/src/example/asset_manager.cpp b/src/example/asset_manager.cpp new file mode 100644 index 0000000..7e15d1f --- /dev/null +++ b/src/example/asset_manager.cpp @@ -0,0 +1,39 @@ + + +#include <crepe/Sound.h> +#include <crepe/api/AssetManager.h> +#include <crepe/api/Texture.h> + +using namespace crepe; +using namespace crepe::api; +int main() { + +	// this needs to be called before the asset manager otherwise the destructor +	// of sdl is not in the right order +	{ Texture test("../asset/texture/img.png"); } +	// FIXME: make it so the issue described by the above comment is not possible +	// (i.e. the order in which internal classes are instantiated should not +	// impact the way the engine works). + +	auto & mgr = AssetManager::get_instance(); + +	{ +		// TODO: [design] the Sound class can't be directly included by the user as +		// it includes SoLoud headers. +		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); +		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); +		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); + +		auto img = mgr.cache<Texture>("../asset/texture/img.png"); +		auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); +	} + +	{ +		auto bgm = mgr.cache<Sound>("../mwe/audio/bgm.ogg"); +		auto sfx1 = mgr.cache<Sound>("../mwe/audio/sfx1.wav"); +		auto sfx2 = mgr.cache<Sound>("../mwe/audio/sfx2.wav"); + +		auto img = mgr.cache<Texture>("../asset/texture/img.png"); +		auto img1 = mgr.cache<Texture>("../asset/texture/second.png"); +	} +} diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp new file mode 100644 index 0000000..1199e2d --- /dev/null +++ b/src/example/audio_internal.cpp @@ -0,0 +1,45 @@ +/** \file + *  + * Standalone example for usage of the internal \c Sound class. + */ + +#include <crepe/Sound.h> +#include <crepe/util/log.h> + +#include <chrono> +#include <thread> + +using namespace crepe; +using namespace std; +using namespace std::chrono_literals; +using std::make_unique; + +int main() { +	dbg_trace(); + +	auto bgm = Sound("../mwe/audio/bgm.ogg"); +	auto sfx1 = Sound("../mwe/audio/sfx1.wav"); +	auto sfx2 = Sound("../mwe/audio/sfx2.wav"); +	auto sfx3 = Sound("../mwe/audio/sfx3.wav"); + +	bgm.play(); + +	// play each sample sequentially +	this_thread::sleep_for(500ms); +	sfx1.play(); +	this_thread::sleep_for(500ms); +	sfx2.play(); +	bgm.pause(); +	this_thread::sleep_for(500ms); +	sfx3.play(); +	bgm.play(); +	this_thread::sleep_for(500ms); + +	// play all samples simultaniously +	sfx1.play(); +	sfx2.play(); +	sfx3.play(); +	this_thread::sleep_for(1000ms); + +	return 0; +} diff --git a/src/example/components_internal.cpp b/src/example/components_internal.cpp new file mode 100644 index 0000000..3c68974 --- /dev/null +++ b/src/example/components_internal.cpp @@ -0,0 +1,61 @@ +/** \file + *  + * Standalone example for usage of the internal ECS + */ + +#include <cassert> +#include <chrono> + +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> + +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> + +#include <crepe/util/log.h> + +using namespace crepe::api; +using namespace crepe; +using namespace std; + +#define OBJ_COUNT 100000 + +int main() { +	dbg_trace(); + +	auto & mgr = ComponentManager::get_instance(); + +	auto start_adding = chrono::high_resolution_clock::now(); + +	GameObject * game_object[OBJ_COUNT]; + +	for (int i = 0; i < OBJ_COUNT; ++i) { +		game_object[i] = new GameObject(i, "Name", "Tag", 0); + +		game_object[i]->add_component<Sprite>("test"); +		game_object[i]->add_component<Rigidbody>(0, 0, i); +	} + +	auto stop_adding = chrono::high_resolution_clock::now(); + +	auto sprites = mgr.get_components_by_type<Sprite>(); +	for (auto sprite : sprites) { +		assert(true); +	} + +	auto stop_looping = chrono::high_resolution_clock::now(); + +	for (int i = 0; i < OBJ_COUNT; ++i) { +		delete game_object[i]; +	} + +	auto add_time = chrono::duration_cast<chrono::microseconds>(stop_adding +																- start_adding); +	auto loop_time = chrono::duration_cast<chrono::microseconds>(stop_looping +																 - stop_adding); +	printf("add time:  %ldus\n", add_time.count()); +	printf("loop time: %ldus\n", loop_time.count()); + +	return 0; +} diff --git a/src/example/log.cpp b/src/example/log.cpp new file mode 100644 index 0000000..3bc6d80 --- /dev/null +++ b/src/example/log.cpp @@ -0,0 +1,24 @@ +/** \file + *  + * Standalone example for usage of the logging functions + */ + +#include <crepe/api/Config.h> +#include <crepe/util/log.h> + +using namespace crepe; +using namespace crepe::util; + +int main() { +	auto & cfg = api::Config::get_instance(); +	// make sure all log messages get printed +	cfg.log.level = util::LogLevel::TRACE; + +	dbg_trace(); +	dbg_logf("cfg.log.color is equal to %d", cfg.log.color); +	logf(LogLevel::INFO, "info message!"); +	logf(LogLevel::WARNING, "very scary warning"); +	logf(LogLevel::ERROR, "fatal error!!!"); + +	return 0; +} diff --git a/src/example/particle.cpp b/src/example/particle.cpp new file mode 100644 index 0000000..53fa213 --- /dev/null +++ b/src/example/particle.cpp @@ -0,0 +1,102 @@ +#include "Particle.h" +#include "ParticleSystem.h" +#include "SDLApp.h" +#include "api/ParticleEmitter.h" +#include <chrono> +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> +#include <crepe/api/GameObject.h> +#include <iostream> +#include <thread> + +using namespace crepe::api; +using namespace crepe; +using namespace std; + +const int WINDOW_WIDTH = 800; +const int WINDOW_HEIGHT = 600; + +int main(int argc, char * argv[]) { +	SDLApp app(WINDOW_WIDTH, WINDOW_HEIGHT); + +	if (!app.initialize()) { +		cerr << "Failed to initialize SDLApp." << endl; +		return 1; +	} + +	GameObject * game_object[1]; +	game_object[0] = new GameObject(0, "Name", "Tag", 0); + +	// FIXME: all systems are singletons, so this shouldn't even compile. +	ParticleSystem particle_system; + +	unsigned int max_particles = 100; // maximum number of particles +	unsigned int emission_rate = 10; // particles created per second +	unsigned int speed = 50; // base speed of particles +	unsigned int speed_offset = 10; // random offset for particle speed +	unsigned int angle = 270; // base angle of particle emission +	unsigned int angle_offset = 30; // random offset for particle angle +	float begin_lifespan = 0.0f; // beginning lifespan of particles +	float end_lifespan = 6.0f; // ending lifespan of particles + +	// Vector to hold all the emitters +	// vector<ParticleEmitter> emitters; +	game_object[0]->add_component<ParticleEmitter>( +		max_particles, emission_rate, speed, speed_offset, angle, angle_offset, +		begin_lifespan, end_lifespan); + +	// Loop to create 1000 emitters +	// for (unsigned int i = 0; i < 1000; ++i) { +	//     ParticleEmitter emitter(maxParticles, emissionRate, speed, speedOffset, angle, angleOffset, beginLifespan, endLifespan); + +	//     // Set a position for each emitter, modifying the position for demonstration +	//     emitter.m_position = {static_cast<float>(200 + (i % 100)), static_cast<float>(200 + (i / 100) * 10)}; // Adjust position for each emitter + +	//     emitters.push_back(emitter); // Add the emitter to the vector +	// } +	float delta_time = 0.1f; +	bool running = true; +	cout << "start loop " << endl; +	while (running) { +		app.handle_events(running); + +		// Start timing +		auto start = chrono::high_resolution_clock::now(); + +		// POC CODE +		particle_system.update(); +		// POC CODE + +		// End timing +		auto end = chrono::high_resolution_clock::now(); +		chrono::duration<float, milli> duration +			= end - start; // get duration in milliseconds + +		cout << "Update took " << duration.count() << " ms" << endl; +		app.clear_screen(); + +		start = chrono::high_resolution_clock::now(); +		// render particles using the draw_square method from SDLApp +		ComponentManager & mgr = ComponentManager::get_instance(); +		std::vector<std::reference_wrapper<ParticleEmitter>> emitters +			= mgr.get_components_by_type<ParticleEmitter>(); +		for (const ParticleEmitter & emitter : emitters) { +			for (const Particle & particle : emitter.particles) { +				if (particle.active) +					app.draw_square(particle.position.x, particle.position.y, +									5); // draw each particle +			} +		} + +		app.present_screen(); +		end = chrono::high_resolution_clock::now(); +		duration = end - start; // get duration in milliseconds + +		cout << "screen took " << duration.count() << " ms" << endl; + +		this_thread::sleep_for(chrono::milliseconds(20)); // simulate ~50 FPS +	} + +	app.clean_up(); +	return 0; +} diff --git a/src/example/physics.cpp b/src/example/physics.cpp new file mode 100644 index 0000000..db69b9b --- /dev/null +++ b/src/example/physics.cpp @@ -0,0 +1,30 @@ +#include "PhysicsSystem.h" +#include <chrono> +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> +#include <crepe/api/Force.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Transform.h> +#include <iostream> +#include <thread> + +using namespace crepe::api; +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { +	PhysicsSystem physics_system; +	GameObject * game_object[2]; +	game_object[1] = new GameObject(2, "Name", "Tag", 0); // not found not used +	game_object[0] = new GameObject(5, "Name", "Tag", 0); +	Point point = { +		.x = 0, +		.y = 0, +	}; +	game_object[0]->add_component<Transform>(point, 0, 0); +	game_object[0]->add_component<Rigidbody>(1, 1, BodyType::DYNAMIC); +	game_object[0]->add_component<Force>(1, 0); +	physics_system.update(); +	return 0; +} diff --git a/src/example/rendering.cpp b/src/example/rendering.cpp new file mode 100644 index 0000000..1bf448c --- /dev/null +++ b/src/example/rendering.cpp @@ -0,0 +1,71 @@ + + +#include <crepe/ComponentManager.h> +#include <crepe/RenderSystem.h> +#include <crepe/api/GameObject.h> +#include <crepe/util/log.h> + +#include <crepe/api/AssetManager.h> +#include <crepe/api/Color.h> +#include <crepe/api/Point.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> + +#include <chrono> +#include <memory> + +using namespace std; +using namespace crepe; +using namespace crepe::api; + +int main() { + +	dbg_trace(); + +	auto obj = GameObject(0, "name", "tag", 0); +	auto obj1 = GameObject(1, "name", "tag", 0); +	auto obj2 = GameObject(2, "name", "tag", 0); + +	auto & mgr = AssetManager::get_instance(); +	// Normal adding components +	{ +		Color color(0, 0, 0, 0); +		Point point = { +			.x = 0, +			.y = 0, +		}; +		obj.add_component<Transform>(point, 1, 1); +		obj.add_component<Sprite>( +			make_shared<Texture>("../asset/texture/img.png"), color, +			FlipSettings{true, true}); +	} + +	{ +		Color color(0, 0, 0, 0); +		Point point = { +			.x = 500, +			.y = 0, +		}; +		obj1.add_component<Transform>(point, 0, 0.1); +		auto img = mgr.cache<Texture>("../asset/texture/second.png"); +		obj1.add_component<Sprite>(img, color, FlipSettings{true, true}); +	} + +	{ +		Color color(0, 0, 0, 0); +		Point point = { +			.x = 800, +			.y = 0, +		}; +		//obj.add_component<Transform>(point, 0, 0.1); +		auto img = mgr.cache<Texture>("../asset/texture/second.png"); +		obj2.add_component<Sprite>(img, color, FlipSettings{true, true}); +	} + +	auto & sys = crepe::RenderSystem::get_instance(); +	auto start = std::chrono::steady_clock::now(); +	while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { +		sys.update(); +	} +} diff --git a/src/example/script.cpp b/src/example/script.cpp new file mode 100644 index 0000000..cda9591 --- /dev/null +++ b/src/example/script.cpp @@ -0,0 +1,54 @@ +/** \file + *  + * Standalone example for usage of the script component and system + */ + +#include <crepe/ComponentManager.h> +#include <crepe/ScriptSystem.h> +#include <crepe/util/log.h> + +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Config.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> + +using namespace crepe; +using namespace crepe::api; +using namespace std; + +// Unrelated stuff that is not part of this POC +int _ = [] () { +	// Show dbg_trace() output +	auto & cfg = api::Config::get_instance(); +	cfg.log.level = util::LogLevel::TRACE; + +	return 0; // satisfy compiler +}(); + + + +// User-defined script: +class MyScript : public Script { +	void update() { +		// Retrieve component from the same GameObject this script is on +		Transform & test = get_component<Transform>(); +		dbg_logf("Transform(%.2f, %.2f)", test.position.x, test.position.y); +	} +}; + +int main() { +	// Create game object with Transform and BehaviorScript components +	auto obj = GameObject(0, "name", "tag", 0); +	obj.add_component<Transform>(Point { .x = 1.2, .y = 3.4, }, 0, 0); +	obj.add_component<BehaviorScript>().set_script<MyScript>(); + +	// Get ScriptSystem singleton instance (this would normally be done from the +	// game loop) +	auto & sys = ScriptSystem::get_instance(); +	// Update all scripts. This should result in MyScript::update being called +	sys.update(); + +	return EXIT_SUCCESS; +} + diff --git a/src/makefile b/src/makefile new file mode 100644 index 0000000..3f74a2a --- /dev/null +++ b/src/makefile @@ -0,0 +1,8 @@ +.PHONY: FORCE + +FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') +format: FORCE +	# clang-tidy -p build/compile_commands.json --fix-errors $(FMT) + +# TODO: re-enable linter after 2024-11-10 + diff --git a/src/readme.md b/src/readme.md new file mode 100644 index 0000000..15fa6f3 --- /dev/null +++ b/src/readme.md @@ -0,0 +1,34 @@ +# engine source + +This folder contains the crêpe engine source files, unit tests, and some toy +examples. The only target built by default by the CMakeLists.txt in this folder +is the crêpe shared library object. + +Examples (using Ninja): + +``` +$ cmake -B build -G Ninja +$ cmake --build build +``` + +Unit tests can be built by explicitly specifying the target `test_main` when +running the build command: + +``` +$ cmake --build build --target test_main +``` + +Each source file in the example/ folder corresponds to a CMake target as well +(all examples can be built at once by specifying the `examples` target): + +``` +$ cmake --build build --target audio_internal script +``` + +For installing crêpe system-wide after building (install must be run with +elevated privileges): + +``` +# cmake --install build +``` + diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000..0d316d6 --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(test_main PUBLIC +	dummy.cpp +	# audio.cpp +) + diff --git a/src/test/audio.cpp b/src/test/audio.cpp new file mode 100644 index 0000000..d6ff689 --- /dev/null +++ b/src/test/audio.cpp @@ -0,0 +1,10 @@ +#include <gtest/gtest.h> + +using namespace std; +using namespace std::chrono_literals; + +// using namespace crepe; + +// TODO: mock internal audio class + +TEST(audio, play) { ASSERT_TRUE(true); } diff --git a/src/test/dummy.cpp b/src/test/dummy.cpp new file mode 100644 index 0000000..a00a9c6 --- /dev/null +++ b/src/test/dummy.cpp @@ -0,0 +1,3 @@ +#include <gtest/gtest.h> + +TEST(dummy, foo) { ASSERT_TRUE(1); }  |