Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
41c80bc
Setting up GitHub Classroom Feedback
github-classroom[bot] Nov 9, 2023
dc08916
Adding degree function to mathlib and updating cmake
RysanDeluna Nov 15, 2023
2ba6c52
Update CMAKE - issue with rendering or player movement
RysanDeluna Nov 20, 2023
8549d68
Update CMAKE
RysanDeluna Nov 22, 2023
76f4bc6
Solved the problem by changing the window name; scene 2 implementation
RysanDeluna Nov 22, 2023
eb23ffe
Implementation of Scene3 and finishing touches.
RysanDeluna Nov 27, 2023
84c116b
Beginning of final game project, initial setup, built PlayerMoveCompo…
RysanDeluna Nov 30, 2023
9398aa9
Movement timer does not reset anymore on collision
RysanDeluna Nov 30, 2023
abca4f8
Drawing Grid
RysanDeluna Nov 30, 2023
233f186
PLayer movement through the grid.
RysanDeluna Nov 30, 2023
b7caa89
Adjustments on LevelSystem for auto calculating the tile size dependi…
RysanDeluna Dec 2, 2023
2f88761
Implementation of neighbors function in Graph; Added method for findi…
RysanDeluna Dec 3, 2023
bf3c6ba
BFS working for pathfinding.
RysanDeluna Dec 4, 2023
221fbdd
stable version with pathfinding done.
RysanDeluna Dec 4, 2023
4b1c64b
Pursuer Component, lvl1, lvl2
RysanDeluna Dec 11, 2023
7a46aa1
Pursuer Component, lvl1, lvl2
RysanDeluna Dec 11, 2023
81aa4fe
HurtComponent plus Pursuer, lvl2, changing scenes
RysanDeluna Dec 11, 2023
bac0c3e
Collectable component
RysanDeluna Dec 12, 2023
d31cb91
Collectable component and increasespeed method on actor movement comp…
RysanDeluna Dec 12, 2023
92d8111
Collectable component and increasespeed method on actor movement comp…
RysanDeluna Dec 12, 2023
77d8346
LVL1 done
RysanDeluna Dec 12, 2023
03a9187
LVLs 2 and 3 done
RysanDeluna Dec 12, 2023
5292a75
LVL4 attempt, kill component done and LVL4 layout done
RysanDeluna Dec 12, 2023
d3aff17
All lvls done.
RysanDeluna Dec 13, 2023
385806d
MenuScene with buttons
RysanDeluna Dec 13, 2023
0570f88
GuideScene
RysanDeluna Dec 13, 2023
d8986fe
Guide Scene, Menu Scene and Cheat for skipping lvls
RysanDeluna Dec 13, 2023
d788152
Upload GDD
RysanDeluna Dec 13, 2023
36bf8d0
Update and rename GDD.md to README.md
RysanDeluna Dec 13, 2023
4f1800a
Fixed typo in guide scene.
RysanDeluna Dec 14, 2023
8f28e6e
Fixed typo in guide scene.
RysanDeluna Dec 14, 2023
b020008
Merge remote-tracking branch 'origin/master'
RysanDeluna Dec 14, 2023
ba31b66
Update README.md
RysanDeluna Dec 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 148 additions & 23 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,55 +1,180 @@
cmake_minimum_required(VERSION 3.21)
project(CMakeSFMLProject LANGUAGES CXX)
# Compiler flags
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /EHsc" CACHE INTERNAL "" FORCE)
elseif(APPLE)
message("hello apple")
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.9 CACHE STRING "")
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(PRACTICAL_7_PLATFORMER)
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.x)
FetchContent_MakeAvailable(SFML)


# B2D - Box2D physics library
add_subdirectory("lib/b2d/Box2D")
#include_directories("lib/b2d/Box2D/")
set(B2D_INCS "lib/b2d/Box2D/")
link_directories("${CMAKE_BINARY_DIR}/lib/sfml/lib")
FetchContent_Declare(
sfml
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.6.x
)
FetchContent_MakeAvailable(sfml)
# populate the lib directory with the sfml libraries
FetchContent_GetProperties(sfml) # can I get cmake to list out the names of the properties? A: yes, use the following line: get_cmake_property(_variableNames VARIABLES)
if(NOT sfml_POPULATED)
FetchContent_Populate(sfml)
add_subdirectory(${sfml_SOURCE_DIR} ${sfml_BINARY_DIR})
endif()
get_cmake_property(_variableNames VARIABLES)
list(FILTER _variableNames INCLUDE REGEX "sfml")
message("variableNames: ${_variableNames}")

foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

message ("SFML_SOURCE_DIR: ${sfml_SOURCE_DIR}")

message ("SFML_SOURCE_DIR: ${sfml_SOURCE_DIR}")

message("SFML_BINARY_DIR: ${sfml_BINARY_DIR}")


# The following downloads box2d into the build directory and builds it
FetchContent_Declare(box2d
GIT_REPOSITORY https://github.com/dooglz/Box2D.git
GIT_TAG v2.3.1)

# We need finer control over the build of box2d so we set the following variables
set(BOX2D_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
if(NOT box2d_POPULATED)
FetchContent_Populate(box2d)
add_subdirectory(${box2d_SOURCE_DIR}/Box2D ${box2d_BINARY_DIR})
endif()

get_cmake_property(_variableNamesbox VARIABLES)
list(FILTER _variableNamesbox INCLUDE REGEX "box")
message("_variableNamesbox: ${_variableNamesbox}")

foreach (_variableName ${_variableNamesbox})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

#### setup Directories ####
#Main output directory
set(OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/")
# Ouput all DLLs from all libs into main build folder
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY})

add_custom_target(copy_resources ALL COMMAND ${CMAKE_COMMAND}
-E copy_directory
"${PROJECT_SOURCE_DIR}/res"
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
)

# _________________________________________________________________________________________________________
# -- The alternative was to use submodule (for both sfml and box2d - below) from github and use that to download and populate the directory - We went with the more "straightforward" approach of using fetchcontent
#### Add External Dependencies ####
# SFML - graphics library
#add_subdirectory("lib/sfml" EXCLUDE_FROM_ALL)
#include_directories("lib/sfml/include")
#set(SFML_INCS "lib/sfml/include")
#link_directories("${CMAKE_BINARY_DIR}/lib/sfml/lib") # Does fetchcontent do this for me? A: yes, but I need to tell it to do so by adding the following line: FetchContent_MakeAvailable(sfml)

# B2D - Box2D phyics library
#add_subdirectory("lib/b2d/Box2D" EXCLUDE_FROM_ALL)
#include_directories("lib/b2d/Box2D/")
#set(B2D_INCS "lib/b2d/Box2D/")
#link_directories("${CMAKE_BINARY_DIR}/lib/sfml/lib")
# _________________________________________________________________________________________________________

# The following allows us to tell the compiler where the header files are for box2D. This is normally managed by fetchcontent but we need to do it manually for box2d - one of the problems in the build
set (B2D_INCS "${box2d_SOURCE_DIR}/Box2D") # note that fetchcontent will download the box2d source code into the box2d_SOURCE_DIR

#### Add Engine and our own library projects####

set (SFML_INCS "${sfml_SOURCE_DIR}/include") # note that fetchcontent will download the sfml source code into the sfml_SOURCE_DIR

set (lib_box2d "${box2d_BINARY_DIR}/Box2D/libBox2D.a") # Again, not ideal, but we need to tell the compiler where the box2d library is. This is normally managed by fetchcontent but we need to do it manually for box2d - one of the problems in the build


file(GLOB_RECURSE RESOURCE_FILES "res/*.*")
## Engine ##
####
file(GLOB_RECURSE SOURCE_FILES engine/*.cpp engine/*.h)
add_library(lib_engine STATIC ${SOURCE_FILES} ${RESOURCE_FILES})
target_include_directories(lib_engine INTERFACE "${CMAKE_SOURCE_DIR}/engine/" PUBLIC SYSTEM ${B2D_INCS} )
target_link_libraries(lib_engine lib_tile_level_loader lib_maths lib_ecm Box2D sfml-graphics sfml-audio)

## Maths lib
add_library(lib_maths INTERFACE)
target_sources(lib_maths INTERFACE "${CMAKE_SOURCE_DIR}/lib_maths/maths.h")
target_include_directories(lib_maths INTERFACE "${CMAKE_SOURCE_DIR}/lib_maths" INTERFACE ${SFML_INCS})

## Tile loader lib
file(GLOB_RECURSE SOURCE_FILES lib_tile_level_loader/*.cpp lib_tile_level_loader/*.h)
add_library(lib_tile_level_loader STATIC ${SOURCE_FILES})
add_library(lib_tile_level_loader STATIC ${SOURCE_FILES} ${RESOURCE_FILES})
target_include_directories(lib_tile_level_loader INTERFACE "${CMAKE_SOURCE_DIR}/lib_tile_level_loader/" )
target_link_libraries(lib_tile_level_loader lib_maths sfml-graphics)

# Maths lib
add_library(lib_maths INTERFACE)
target_sources(lib_maths INTERFACE "${CMAKE_SOURCE_DIR}/lib_maths/maths.h")
target_include_directories(lib_maths INTERFACE "${CMAKE_SOURCE_DIR}/lib_maths" SYSTEM INTERFACE ${SFML_INCS})

## ECM lib
file(GLOB_RECURSE SOURCE_FILES lib_ecm/*.cpp lib_ecm/*.h)
add_library(lib_ecm STATIC ${SOURCE_FILES})
target_include_directories(lib_ecm INTERFACE "${CMAKE_SOURCE_DIR}/lib_ecm" )
target_link_libraries(lib_ecm PRIVATE lib_maths)

#### Add Practical Projects####

#@@-

## 6 - Platformer
file(GLOB_RECURSE SOURCES lab_7_platformer/*.cpp practical_6_platformer/*.h)
file(GLOB_RECURSE CMPNTS lab_7_platformer/components/*.cpp practical_6_platformer/components/*.h)
file(GLOB_RECURSE SCENES lab_7_platformer/scenes/*.cpp practical_6_platformer/scenes/*.h)
add_executable(PRACTICAL_7_PLATFORMER ${SOURCES} ${RESOURCE_FILES})
source_group("components" FILES ${CMPNTS})
source_group("resources" FILES ${RESOURCE_FILES})


target_link_libraries(PRACTICAL_7_PLATFORMER lib_engine)
set(EXECUTABLES ${EXECUTABLES} PRACTICAL_7_PLATFORMER)


if (WIN32 AND BUILD_SHARED_LIBS)
add_custom_command(TARGET PRACTICAL_7_PLATFORMER POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:PRACTICAL_7_PLATFORMER> $<TARGET_FILE_DIR:PRACTICAL_7_PLATFORMER> COMMAND_EXPAND_LISTS)
endif()

add_dependencies(PRACTICAL_7_PLATFORMER copy_resources)


set_target_properties(PRACTICAL_7_PLATFORMER
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$(Configuration)
)

file(GLOB_RECURSE SOURCES game_project/*.cpp game_project/*.h)
file(GLOB_RECURSE CMPNTS game_project/components/*.cpp game_project/components/*.h)
file(GLOB_RECURSE SCENES game_project/scenes/*.cpp game_project/scenes/*.h)
file(GLOB_RECURSE AI game_project/ai/*.cpp game_project/ai/*.h)
add_executable(GAME_PROJECT ${SOURCES} ${RESOURCE_FILES})
source_group("components" FILES ${CMPNTS})
source_group("resources" FILES ${RESOURCE_FILES})
source_group("ai" FILES ${AI})

target_link_libraries(GAME_PROJECT lib_engine)
set(EXECUTABLES ${EXECUTABLES} GAME_PROJECT)


add_executable(CMakeSFMLProject src/main.cpp src/platformer.cpp)
target_link_libraries(CMakeSFMLProject PRIVATE sfml-graphics)
target_compile_features(CMakeSFMLProject PRIVATE cxx_std_17)
if (WIN32 AND BUILD_SHARED_LIBS)
add_custom_command(TARGET CMakeSFMLProject POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:CMakeSFMLProject> $<TARGET_FILE_DIR:CMakeSFMLProject> COMMAND_EXPAND_LISTS)
add_custom_command(TARGET GAME_PROJECT POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_RUNTIME_DLLS:PRACTICAL_7_PLATFORMER> $<TARGET_FILE_DIR:GAME_PROJECT> COMMAND_EXPAND_LISTS)
endif()

install(TARGETS CMakeSFMLProject)
add_dependencies(GAME_PROJECT copy_resources)


set_target_properties(GAME_PROJECT
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$(Configuration)
)
#@@-
3 changes: 2 additions & 1 deletion engine/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void Engine::Render(RenderWindow& window) {

void Engine::Start(unsigned int width, unsigned int height,
const std::string& gameName, Scene* scn) {
RenderWindow window(VideoMode(width, height), gameName);
RenderWindow window(VideoMode(width,height), gameName);
_gameName = gameName;
_window = &window;
Renderer::initialise(window);
Expand Down Expand Up @@ -175,6 +175,7 @@ sf::Vector2u Engine::getWindowSize() { return _window->getSize(); }

sf::RenderWindow& Engine::GetWindow() { return *_window; }


namespace timing {
// Return time since Epoc
long long now() {
Expand Down
1 change: 1 addition & 0 deletions engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Engine {
static void Render(sf::RenderWindow& window);
};


namespace timing {
// Return time since Epoc
long long now();
Expand Down
112 changes: 112 additions & 0 deletions game_project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Game Design Document

## Introduction

This document describes the game Tile Running, which is designed to be a simple, 2D grid game, constructed and designed to study different types of AI in a grid-like environment while playing a 'mouse and rat' game. The game employs a grid environment with different levels, each one with its own objectives, enemies, AI algorithms, collectible items, and memory and scene management.

## Technology

The game was developed with PC's in mind, but it can be easily ported to other platforms as it programmed in C++. The Game Engine was built above the SFML library, which provided the foundation for I/O handling. The Game Engine consists of:

- **System Renderer**: controls the presentation of elements present within a scene;
- **System Resources**: responsable to import external assets, as fonts, sounds and textures.
- **Engine**: handles initialization of the systems and scenes.
- **Level Tile Loader**: builds the level layout based on a text file that represents the map, e.g. walls, objectives, player spawn, enemies spawn etc.
- **Maths library**: extension of SFML namespace, utilized for making mathematical operations with 2D vectors.
- **Entity/Component Library**: the basis for game systems and interactions between different elements by providing a framework in which each *entity* behaves and acts based on its *components*. The Fly-Weight pattern is implemented on this library, as each entity is constructed based on its own characteristics.

The artwork is entirely generated by simple shapes provided by SFML, that being squares, rectangles and circles. These can be quickly adapted to comport external assets by using the **Resources**+**Renderer** systems.

## Backstory

After stealing chemical compounds from a pharmaceutical company in a plain 2D world, **Green Square** (you) need to run away with it avoiding getting caught by the smart **Graph Search Force** dressed in Magenta. Sadly, **Green** is not very good handling hazardous liquids, what can leave **Yellow** squared slimes behind it, who are very toxic! Meanwhile, it is useful to get **Blue Circle** water on the way, so **Green** gets faster.

## Objective

- Get all blue collectibles and reach the red square while avoiding getting caught by either the Yellow or Magenta squares.


## Gameplay

The game works above a grid-like environment with top-down view of the tiles, in which every character can only move to the neighbour tile. The movement can be only up, down, left or right at a time.

All the tiles are passable, the only exception being the walls and enemies. **Green** is not so resistant, so it will instantly die by touching an enemy or hazardous slime. Speaking of, the two different enemies work as it's described bellow:

- **Magenta**: will go after Green or Water by the best path found on the grid.
- **Yellow**: they will follow Green's pace and path, being always behind it. This type of enemy can kill the Magenta.

The collectibles are circular, blue tiles that they give more speed to Green and the Magenta, while also unlocking the door to the next level. So it is important to collect as much as you can before the Magenta do!

## Levels

There are five levels programmed and they were made on a text file as well as programmed on the source code.

1. **Introduction**: a straight forward road to the Red tile, enemies around the player and they will start to move after a few seconds.
2. **Labyrinth**: One Magenta lurks behind one of the labyrinth walls. The Red is by the end of it.
3. **Hazardous Smiles and Water**: Just like the first level, but now the enemies are Yellows and Green needs to drink Water.
4. **Another Labyrinth**: In another labyrinth, Green needs to grab water while avoid the Yellow smiles.
5. **Altogether**: The last level and final challenge, the Magenta track down Green and now it needs to deal with both the Magenta as well as the Yellow, while thirsty!


## Controls

The commands are implemented within the player's movement component and additional keys specific to each scene are implemented within it's update function.

- UP/W: goes north;
- LEFT/A: goes west;
- RIGHT/D: goes east;
- DOWN/S: goes down;
- N: skips level (cheat code if needed).


## GUI/In-game menu

The GUI is implemented only in the main menu with the usage of buttons. These were programmed using a component that controls input and changes from scenes that connects to the Main Menu.


## Artwork

The artwork is generated entirely by mathematical function within the Tile Level Loader, that generated the sprites utilized for drawing the levels. Shapes were based on the SFML library.

# Development Process

The beginning of the development process surrounded understanding the "in's and out's" of the framework as well as how the algorithms for the AI worked. For this, the first step was drafting a class diagram that could explain how the classes interacts with each other. It can be seen bellow:

![image](https://github.com/GameDevCPP/platformer-RysanDeluna/assets/82891214/a509729d-c6e8-4cd7-92cb-7e4a2ffd0472)

The diagram shows how the framework was first developed to the Platformer game, but it gives a general idea of how the different systems interact with each other, as well as how the entity/component design works.
This was the base for what would become TILE RUNNING.

## Regarding the AI

By studying how an pathfinding algorithm would work, it was decided that the game would be better suited in a grid-like map, as the graph implementation would be much easier.
As the tiles are not weighted, the Dijsktra Algorithm or the A* were not considered.

Furthermore, this type of AI guided the decision for approaching a top-down view of the grid and the construction of the levels, which are made with how the AI works.

The AI code is mainly inside the components relevant to them, but the graph implementation is found within an AI directory.

## Additions to the framework

Some components were added beyond what was given by the framework. These are:

- BFS_AI: adds to the ActorMovementComponent by implementing the breadth first search and making the movements based on the path found.
- Collectables: component responsable to delete the entity and power other entities with the ActorMovementComponent
- Kill: this component makes the owner entity to kill another determined type of entity, auto-destroying itself in the process.
- Pursuer_AI: when an Entity owns this component, it will mimic the movements made by another entity; a better of making this work is spawning the parent on the same tile as the mimicking target.

Besides the components development, some changes were made on the already given source codes.

- Tile Loader:
- Now the tiles possess a outline color and empty tiles have a gray outline;
- A function that returns the grid coordinates of a float position;
- The optimise function is only active to the WALL and END tiles.

- Text Component:
- It is positioned wherever it's parent is.

## Levels Development

The levels follow the rule of first introducing a simple mechanic, followed by a much harder experience with that single mechanic. For example, the first level introduces the Type 1 enemy, while the second level is a maze with that kind of enemy. The same is done for further mechanics.

The mazes were based on the [dungeon generator made by Donjon](https://donjon.bin.sh/d20/dungeon/) with alterations that would make a better gaming experience.
47 changes: 47 additions & 0 deletions game_project/ai/Graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Created by rysan on 03/12/23.
//

#include "Graph.h"

// GRAPH ---------------------------------------------------------------
Graph::Graph(uint w, uint h) : _width(w), _height(h) {}

std::array<sf::Vector2f, 4> Graph::DIRS = { sf::Vector2f{1.f, 0.f}, // Right
sf::Vector2f{-1.f, 0.f}, // Left
sf::Vector2f{0.f, 1.f}, // Down
sf::Vector2f{0.f, -1.f} // UP
};

uint Graph::getHeight() { return _height; }
uint Graph::getWidth() { return _width; }

void Graph::setHeight(uint h) { _height = h; }
void Graph::setWidth(uint w) { _width = w; }

bool Graph::passable(sf::Vector2f coord) const
{
return ls::getTile(sf::Vector2ul(coord)) != ls::WALL;
}

bool Graph::in_bounds(sf::Vector2f coord) const
{
return coord.x >= 0 && coord.x <= ls::getWidth()
&& coord.y >= 0 && coord.y <= ls::getHeight();
}

std::vector<sf::Vector2ul> Graph::neighbors(sf::Vector2ul me) const
{
std::vector<sf::Vector2ul> neighbors;

for (auto dir : DIRS)
{
auto next = sf::Vector2f{me.x + dir.x, me.y + dir.y};
if (in_bounds(next) && passable(next))
neighbors.push_back(sf::Vector2ul(next));
}
if (int(me.x + me.y) % 2 == 0) std::reverse(neighbors.begin(), neighbors.end());

return neighbors;
}
// GRAPH ---------------------------------------------------------------
Loading