Skip to content

Commit

Permalink
Merge branch 'topic/tutorial' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRealKaup committed Dec 7, 2024
2 parents f76f7ec + 1e2af9d commit 0ba0331
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 66 deletions.
34 changes: 17 additions & 17 deletions documentation/tutorial/1-introduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,42 @@
*/

/*
Welcome to the official KTech tutorial!
This is my attempt at teaching a subject while putting everything into practice in an interactive fashion. Hopefully, by the end of this tutorial you'll not only know every aspect of KTech, but also have experience with every aspect of KTech. The latter can be as important as the former.
When you come across something you don't know (that isn't about to be explained), pause, learn it, and only then come back; don't frustrate yourself and waste your time. I assume you know how to use the terminal and program C++ code, and that you cloned KTech's git repository to work on.
The tutorial is separated into chapters, each one is a C++ source file that usually contains a heavily-commented example of functional KTech code (this file, "1-introduction.cpp", is the very first chapter). You should read the tutorial linearly if you are new to KTech; literally read the individual files from top to bottom, and the chapters in an ascending order.
Let's start by getting things up and running; building, and running.
Within this directory there's an additional file named "premake5.lua" - it is the configuration file for the command line tool Premake that KTech uses to generate build files. This particular configuration file will generate the build files for the entire tutorial and all its chapters at once. I will not teach you how to use Premake as it already has a fine documentation at https://premake.github.io/docs/, though knowing how to script it is not needed right now; we just want to run the configuration file we already have.
Here I assume you are running GNU/Linux. Comprehensive instructions for Windows and Visual Studio don't exist yet.
First, generate build files. Ensure you have Premake installed on your system and run the following command from the root directory of your cloned repository ("gmake2" for GNU make files):
`$ premake5 gmake2`
Premake will read the "premake5.lua" configuration file present in the repository's root, which will in turn read the additional configuration files in KTech's and the tutorial's directories. The result is a directory named "build/" containing the build files for everything.
Secondly, build. To generate binary files, run `make` on the GNU makefiles that were generated in the "build/" directory by Premake, as so:
Premake will read the "premake5.lua" configuration file present in the repository's root first, which will in turn read the additional configuration files in "ktech/", "examples/", and "documentation/tutorial/" directories. The result is a directory named "build/" containing the build files for everything (the static library, the game examples, and this tutorial's source files).
Secondly, build. To generate binary files, run `make` on the GNU makefiles that were generated in the "build/" directory by Premake, like so:
`$ make -C build/`
`make` will build KTech as a static library, then this file and the following files in the tutorial as individual console applications. The result are executable binaries in "build/bin/".
`make` will build KTech as a static library, then the game examples, this file and the following files in the tutorial as individual console applications. The result are executable binaries in "build/bin/".
Lastly, run the following command to run the binary that was built out of this source file:
`$ ./build/bin/1-introduction`
And now you are running the program written in the end of this source file. Each chapter of the tutorial should have its own binary file.
This has built the entire code of the tutorial now, but you may modify code later on. To update the binary files you should simply rebuild like was done in the second command.
This has built the entire code of the tutorial now, but you are encouraged to modify code later on. To update the binary files you should simply rebuild, that is, run the second command.
As you can see below, this file literally contains C++ code. Continue reading linearly.
As you can see below, this file literally contains C++ code. Continue reading linearly:
*/

// "ktech/ktech.hpp" is KTech's main header file that you would normally include in your game. It will give you access to everything except the optional widgets (UI elements; we'll cover them later).
#include "../../ktech/ktech.hpp"

// With KTech, you still define your game's entry function.
int main()
// With KTech, you still define your game's entry point.
auto main() -> int
{
// Create an engine instance; we'll dive deeper into this in the next chapter.
KTech::Engine engine(KTech::UPoint(9, 9), 24);
Expand Down Expand Up @@ -98,7 +96,9 @@ int main()
engine.output.Print();
}
else if (engine.output.ShouldPrintThisTick())
{
engine.output.Print();
}

engine.time.WaitUntilNextTick();
}
Expand Down
114 changes: 70 additions & 44 deletions documentation/tutorial/2-world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@

#include "../../ktech/ktech.hpp"

int main ()
auto main() -> int
{
/*
Before initializing any game objects and such, we need a `KTech::Engine` class instance.
Before creating any game objects, rendering them and such, we need a `KTech::Engine` class instance.
The `Engine` class contains everything needed to manage a game and its world structures: input, output, collision, memory and time "engine components". Most of these engine components do their work completely on their own. They all initialize themselves when `Engine` is constructed and terminate when `Engine` is destructed, so you don't need to worry about them. Still, knowing about their existence comes in useful when looking for some engine function of variable you need, because virtually always, you are going to find it in one of the engine component classes.
The `Engine` class contains everything needed to manage a game and its world structures: input, output, collision, memory and time "engine components". These engine components are all initialized in `Engine`, and are accessible through your `Engine` instance. They hold many useful functions and variables (as well as do background tasks), so being aware of the different engine components is helpful to effectively find what you need.
*/

// Create an `Engine` instance:
KTech::Engine engine(
KTech::UPoint(20, 20), // The game's image (or "viewport") size in terminal cells, which should ideally fit within the terminal's viewport.
KTech::UPoint(20, 20), // The game's viewport size, in terminal cells.
24 // The ticks-per-second (TPS) limit; we'll cover this soon.
);
// You might find it convenient to instantiate `Engine` outside `main()`, especially when working on a larger-scale game. Feel free to do so; it doesn't matter to `Engine`. Beware, though, not to unintentionally create multiple `Engine` instances, as doing so would make some of the duplicate engine components fight each other and cause random and unexpected behavior.
Expand All @@ -41,105 +41,131 @@ int main ()
Map UI `KTech::Map` stores `KTech::Layer`s and `KTech::Camera`s.
|_______ | `KTech::Layer` stores `KTech::Object`s.
v v v `KTech::UI` stores `KTech::Widget`s.
Layer Camera Widget `KTech::Widget` can store additional (nested) `KTech::Widget`s.
| | ^
v |_|
Layer Camera Widget
|
v
Object
*/

// Let's start with the easiest one to explain: `Object`.
// All world structures' constructors require a reference to an `Engine` instance, which is why you have to create an `Engine` first. `Object` is no exception:
KTech::Object object1(
engine, // `Engine` reference.
KTech::Point(5, 5) // Position the `Object` at X=5, Y=5.
engine, // Parent `Engine` reference.
KTech::Point(5, 5) // Position the `Object` at X=5, Y=5.
);

/*
World structures need this `Engine` reference to register themselves at its memory component, and access it later to communicate with other world structures. The memory engine component assigns world structures a serializable `KTech::ID` structure so they can store, access and call each other. You need not worry now about how `ID`s work, but know that you are going to use them a lot, and that they are the reason the first parameter of all world structure constructors is a required `Engine` reference.
World structures need this `Engine` reference to register themselves at its memory component, and access it later to communicate with other world structures. The memory engine component assigns world structures a serializable `KTech::ID` so they can store, access and call each other. You need not worry now about how `ID`s work, but know that they are how world structures are usually referenced, and that they are the reason the first parameter of all world structure constructors is a required `Engine` reference.
If you are wondering: early on in KTech's past, world structures would communicate with each other via memory addresses (pointers). It was worse in terms of memory safety, and it wasn't serializable (which in turn means incompatible with multiplayer).
*/

/*
Our `Object` is empty, so let's give it `Texture`s.
`Texture`s are 2D terminal bitmaps. Because they are terminal-apt, instead of the usual pixels you are familiar with, `Texture`s are composed of terminal cells, specifically, the `KTech::CellA` structure, which comprises a character, foreground (character) color and background color.
`Object`s store their `Texture`s as instances within a vector of `Texture`s by the name of `m_textures`. First we need to create blank `Texture`s by setting the vector to a size, and then draw on them.
`Object`s store their `Texture`s as instances within a vector of `Texture`s by the name of `Object::m_textures`. First we need to create blank `Texture`s by setting the vector to a size, and then draw on them.
*/

// Currently we plan `object1` to have a single `Texture`, so we'll size `m_textures` to 1.
object1.m_textures.resize(1);
// Now access `Object:m_textures[0]` (that is, the first `Texture`), and make it a simple rectangle:
object1.m_textures[0].Simple( // `Texture::Simple()` is one of `Texture`'s pseudo-constructor functions. We'll explore all of them later in the tutorial.
KTech::UPoint(3, 3), // 3 by 3 square, but it's worth noting that terminal cells (depending on the terminal's font) usually aren't square themselves (but more like narrow rectangles), so it's likely this `Texture` won't end up being an exact square when printed.
KTech::CellA( // As we've said, this is the structure `Texture`s are based on. `Texture::Simple()` accepts a singular `CellA` value, because it creates a uniform rectangle.
'@', // Character.
KTech::RGBA(255, 0, 255, 255), // Pink foreground color (the character's color).
KTech::RGBA(0, 255, 0, 255) // Blue background color (the cell's color behind the character).

// Now access `Object:m_textures[0]` (that is, its first `Texture`), and make it a simple rectangle:
object1.m_textures[0].Simple( // (1)
KTech::UPoint(3, 3), // (2)
KTech::CellA( // (3)
'@', // (4)
KTech::RGBA(255, 0, 255, 255), // (5)
KTech::RGBAColors::blue // (6)
),
KTech::Point(0, 0) // Position, relative to the `Texture`'s parent `Object`. Extremely useful when adding multiple `Texture`s to an `Object`.
KTech::Point(0, 0) // (7)
);

/*
Task: add another `Texture` to `object1`, using the `Texture::Simple()` pseudo-constructor. Don't forget to increase the size of `m_textures`.
Let's look at the previous function's parameters in greater detail:
1: `Texture::Simple()` is one of `Texture` design functions. We'll explore all of them later in the tutorial.
2: 3 by 3 square, but it's worth noting that terminal cells (depending on the terminal's font) usually aren't square themselves (but more like narrow rectangles), so it's likely that this `Texture` won't end up being an exact square when printed.
We've already came across a lot of KTech's "basic structures" (like `Point` and `CellA`). Let's quickly examine them all now before proceeding:
3: As we've said, this "`CellA`" is the structure `Texture`s are based on. `Texture::Simple()` accepts a single `CellA` value, because it creates a uniform rectangle.
- `KTech::Point` - 2D point made of signed integers. Usually represents a position.
- `KTech::UPoint` - like `Point` but made of unsigned integers (instead of signed integers). Usually represents a size.
- `KTech::RGB` - RGB (red, green, blue) color (24 bit depth).
- `KTech::RGBA` - like `RGB`, but has an additional alpha channel for transparency effect.
- `KTech::Cell` - terminal cell made of a `char`, a foreground `RGB` color (character's color), and a background `RGB` color (cell's color behind the character).
- `KTech::CellA` - like `Cell` but made of a `RGBA`s (instead of `RGB`s).
4: Character.
5: Pink foreground color (the character's color).
6: Blue background color (the cell's color behind the character). The `KTech::RGBAColors` namespace includes some predefined `RGBA` values.
An `Object` needs to be placed in a `Layer`, otherwise it's located nowhere and deemed inaccessible, meaning other `Object`s can't interact with it and `Camera`s can't render it.
7: Position, relative to the `Texture`'s parent `Object`. Extremely useful when adding multiple `Texture`s to an `Object`.
A `Layer` contains `Object`s so they can physically interact with each other by means of collision (a topic for later).
Don't forget the Doxygen API reference, which includes information on all function parameters and variables in KTech.
*/

/*
We've already came across a lot of KTech's "basic structures" (like `Point` and `CellA`). Let's quickly examine them all before proceeding:
- `KTech::Point` - 2D vector, mostly used to store positions and directions.
- `KTech::UPoint` - Unsigned 2D vector, mostly used to store sizes and 2D indexes.
- `KTech::RGB` - 24-bit reg-green-blue color, able of representing 16,777,216 (2^24) different colors.
- `KTech::RGBA` - Like `RGB`, but also has an alpha channel representing transparency.
- `KTech::Cell` - Terminal cell comprising `RGB` foreground (character) color, `RGB` background color, and an ASCII character.
- `KTech::CellA` - Like `Cell`, but with `RGBA` foreground and background colors, instead of `RGB`.
*/

/*
An `Object` needs to be placed in a `KTech::Layer`, otherwise it's located nowhere and deemed inaccessible, meaning, other `Object`s can't interact with it and `Camera`s (shown later in this chapter) can't render it.
A `Layer` contains `Object`s so they can physically interact with each other by means of collision (a topic for a later chapter).
*/

// Create a `Layer` (it only needs an `Engine` reference):
KTech::Layer layer1(engine);

// And add `object1` to it:
layer1.AddObject(
object1.m_id // All world structures store their personal `ID` in a member named `m_id` (e.g. `Object::m_id`, `Layer::m_id`...).
object1.m_id // All world structures store their personal `ID` in a member named `m_id` (e.g. `Object::m_id`, `Layer::m_id`, `Camera::m_id`...).
);

/*
Adding a child world structure to a parent world structure can be done in multiple ways:
Adding a child world structure, to a parent world structure, can be done in multiple ways:
1. From the child's constructor (if the parent was created first), e.g.:
- From the child's constructor (if the parent was created first), e.g.:
`KTech::Object object1(engine, layer1.m_id);`
- Using the parent's "adder" function, e.g.:
2. Using the parent's "adder" function, e.g.:
`layer1.AddObject(object1.m_id);`
- Using the child's "enterer" functions, e.g.:
`object1.EnterLayer(layer1.m_id);`
All methods result the same.
3. Using the child's "enterer" function, e.g.:
`object1.EnterLayer(layer1.m_id);`
All methods result the same, so the choice of which to use should be based on your personal preference.
*/

/*
`Layer`s and `Camera`s should be added to a `KTech::Map`. The option to have multiple `Layer`s with `Object`s in a `Map` means two major things:
`Layer`s and `Camera`s should be added to a `Map`. The option to have multiple `Layer`s with `Object`s in a `Map` means two major things:
1. You can control the order `Object`s in a `Map` are rendered: `Layer`s are rendered in the order they were added to the `Map`, meaning, `Object`s in a `Layer` added later will appear on top of `Object`s in a `Layer` added earlier.
- You can control the order `Object`s in a `Map` are rendered: `Layer`s are rendered in the order they were added to the `Map`, meaning `Object`s in a `Layer` added later will appear on top of `Object`s in a `Layer` added earlier.
- You can control which `Object`s can collide with each other by splitting them into `Layer`s.
2. You can control which `Object`s can collide with each other by splitting them into `Layer`s.
In a sense, `Layer`s are like physical "levels" or "floors" stacked on top of each other on the Z axis; `Object`s move on the X and Y axes so they can collide only with other `Object`s from the same Z level (i.e. `Layer`), while `Camera`s view the `Map` from the top of the Z axis so they can see all `Object`s from all `Layer`s.
*/

// Create a `Map`:
KTech::Map map1(engine);

// Create a `Camera`:
KTech::Camera camera1(
engine,
map1.m_id, // Add `camera1` to `map1`.
KTech::Point(0, 0), // Position the `Camera` at X=0, Y=0 (world's origin).
engine.output.resolution // `Engine::output` is the output engine component instance, and `Output::resolution` is the image size given to `Engine`'s constructor earlier.
map1.m_id, // Add `camera1` to `map1`.
KTech::Point(0, 0), // Position the `Camera` at X=0, Y=0 (world's origin).
engine.output.resolution // `Engine::output` is the output engine component instance, and `Output::resolution` is the same viewport size we gave to `Engine`'s constructor earlier.
);

// Add `layer1` to `map1`:
layer1.EnterMap(map1.m_id);

Expand Down
Loading

0 comments on commit 0ba0331

Please sign in to comment.