Fixed setup bug and changed chat to dialogue again.

This commit is contained in:
Kapendev 2024-08-13 22:55:15 +03:00
parent 4ccf2f9c89
commit fbd84bf531
19 changed files with 159 additions and 153 deletions

View file

@ -16,7 +16,7 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);
``` ```
> [!WARNING] > [!WARNING]
@ -55,19 +55,19 @@ The final line modifies the default app.d and dub.json files, downloads raylib,
* assets: This folder is used to store game assets. * assets: This folder is used to store game assets.
* web: This folder is used for exporting to the web. * web: This folder is used for exporting to the web.
Once the installation is complete, you should be able to compile/run with: Once the installation is complete, run the following command:
```bash ```bash
dub run dub run
``` ```
You can pass `offline` to the script if you don't want to download raylib. If everything is set up correctly, a window will appear showing the message "Hello world!".
For more info about exporting to web, read [this](#web-support). To avoid downloading raylib, pass `offline` to the script.
## Documentation ## Documentation
For an initial understanding, the [examples](examples) folder and the [engine.d](source/popka/game/engine.d) file can be a good starting point. For an initial understanding, the [examples](examples) folder can be a good starting point.
You can also read the [TOUR.md](TOUR.md) file for a more in-depth overview. For a more detailed overview, check the [TOUR.md](TOUR.md) file.
## Attributes and BetterC Support ## Attributes and BetterC Support
@ -76,9 +76,8 @@ If you encounter errors with BetterC, try using the `-i` flag.
## Web Support ## Web Support
For exporting to web, your project needs to be compatible with BetterC. To export a game to the web, the game must be compatible with BetterC.
The [web](web) folder contains a helper script to assist with the web export process. The [web](web) folder contains a helper script to assist with the web export process.
If you use DUB, you can run the script with:
```bash ```bash
dub run popka:web dub run popka:web

49
TOUR.md
View file

@ -1,12 +1,8 @@
# Tour # Tour (WIP)
> [!WARNING]
> I am still working on this.
## Understanding the Code ## Understanding the Code
Let's get started with Popka by creating a simple game that displays the classic message "Hello world!". To begin, open the main file of your project and copy-paste the following code:
Open your app.d file and paste the following code:
```d ```d
import popka; import popka;
@ -21,10 +17,11 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);
``` ```
Let's see how everything works: This code will create a window that displays the message "Hello world!".
Here is a breakdown of how this code works:
1. Game Loop 1. Game Loop
@ -36,9 +33,9 @@ Let's see how everything works:
``` ```
This function is the main loop of the game. This function is the main loop of the game.
It runs every frame, and in this example, it draws the message "Hello world!" on the window. It runs every frame and, in this example, draws the message "Hello world!".
The `return false` statement tells the game to keep running. The `return false` statement indicates that the game should continue running.
If true is returned, then the program will stop running. If `true` were returned, the game would stop.
2. Game Start 2. Game Start
@ -47,27 +44,25 @@ Let's see how everything works:
lockResolution(320, 180); lockResolution(320, 180);
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360);
``` ```
This function is the starting point of the game. This function is the starting point of the game.
It runs only once, and in this example, it locks the game resolution to 320 pixels wide and 180 pixels tall. It runs only once and, in this example, locks the game resolution to 320 pixels wide and 180 pixels tall.
The `updateWindow!gameLoop()` call starts the game loop. The `updateWindow!gameLoop` call starts the main game loop.
3. Mixin 3. Mixin
```d ```d
mixin addGameStart!(gameStart, 640, 360) mixin callGameStart!(gameStart, 640, 360)
``` ```
The line makes sure the `gameStart` function runs when your game starts, This line sets up the `gameStart` function to run when the game starts
and in this example, it creates a game window that is 640 pixels wide and 360 pixels tall. and, in this example, creates a game window that is 640 pixels wide and 360 pixels tall.
In essence, a Popka game typically relies on two key functions: In essence, a Popka game typically relies on two key functions:
* A game loop function. * A loop function.
* A game start function. * A start function.
## Drawing ## Drawing
@ -75,18 +70,18 @@ Popka provides a set of drawing functions.
While drawing is not pixel-perfect by default, you can enable pixel-perfect drawing by calling the `togglePixelPerfect` function. While drawing is not pixel-perfect by default, you can enable pixel-perfect drawing by calling the `togglePixelPerfect` function.
```d ```d
// Basic Drawing Functions
void drawRect(Rect area, Color color = white); void drawRect(Rect area, Color color = white);
void drawVec2(Vec2 point, float size, Color color = white); void drawVec2(Vec2 point, float size, Color color = white);
void drawCirc(Circ area, Color color = white); void drawCirc(Circ area, Color color = white);
void drawLine(Line area, float size, Color color = white); void drawLine(Line area, float size, Color color = white);
void drawTexture(Texture texture, Vec2 position, Rect area, DrawOptions options = DrawOptions()); void drawTexture(Texture texture, Vec2 position, Rect area, DrawOptions options = DrawOptions());
void drawTexture(Texture texture, Vec2 position, DrawOptions options = DrawOptions()); void drawTexture(Texture texture, Vec2 position, DrawOptions options = DrawOptions());
void drawRune(Font font, Vec2 position, dchar rune, DrawOptions options = DrawOptions()); void drawRune(Font font, Vec2 position, dchar rune, DrawOptions options = DrawOptions());
void drawText(Font font, Vec2 position, IStr text, DrawOptions options = DrawOptions()); void drawText(Font font, Vec2 position, IStr text, DrawOptions options = DrawOptions());
void drawDebugText(IStr text, Vec2 position = Vec2(8.0f), DrawOptions options = DrawOptions()); void drawDebugText(IStr text, Vec2 position = Vec2(8.0f), DrawOptions options = DrawOptions());
// Tile Map Drawing Functions
void drawTile(Texture texture, Vec2 position, int tileID, Vec2 tileSize, DrawOptions options = DrawOptions()); void drawTile(Texture texture, Vec2 position, int tileID, Vec2 tileSize, DrawOptions options = DrawOptions());
void drawTileMap(Texture texture, Vec2 position, TileMap tileMap, Camera camera, DrawOptions options = DrawOptions()); void drawTileMap(Texture texture, Vec2 position, TileMap tileMap, Camera camera, DrawOptions options = DrawOptions());
``` ```
@ -95,5 +90,11 @@ void drawTileMap(Texture texture, Vec2 position, TileMap tileMap, Camera camera,
Functions that start with the word load/save will always try to read/write from/to the assets folder. Functions that start with the word load/save will always try to read/write from/to the assets folder.
These functions handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems. These functions handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems.
For instance, `loadText("levels/level5.txt")` and `loadText("levels\\level5.txt")` will function identically on any operating system.
Also, if you need text data for just a single frame, consider using the `loadTempText` function. ```d
loadText("levels/level5.txt");
loadText("levels\\level5.txt");
```
Both of these calls will function identically on any operating system.
Also, if text is needed for only a single frame, use the `loadTempText` function.

View file

@ -30,4 +30,4 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -66,4 +66,4 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -1,15 +1,15 @@
/// This example shows how to use the Popka chat system. /// This example shows how to use the Popka dialogue system.
import popka; import popka;
// The game variables. // The game variables.
auto chat = Chat(); auto dialogue = Dialogue();
auto script = " auto script = "
# This is a comment. # This is a comment.
! choiceCount ! choiceCount
* menuPoint * menuPoint
^ Select first choice. ^ Select second choice. ^ End chat. ^ Select first choice. ^ Select second choice. ^ End dialogue.
* choice1 * choice1
> Bob > Bob
@ -34,34 +34,34 @@ auto script = "
bool gameLoop() { bool gameLoop() {
// Update the game. // Update the game.
if (chat.canUpdate) { if (dialogue.canUpdate) {
if (chat.hasChoices) { if (dialogue.hasChoices) {
auto keys = digitChars[1 .. 1 + chat.choices.length]; auto keys = digitChars[1 .. 1 + dialogue.choices.length];
foreach (i, key; keys) { foreach (i, key; keys) {
if (key.isPressed) { if (key.isPressed) {
chat.pick(i); dialogue.pick(i);
break; break;
} }
} }
} else if (Keyboard.space.isPressed) { } else if (Keyboard.space.isPressed) {
chat.update(); dialogue.update();
} }
} }
// Draw the chat. // Draw the dialogue.
if (chat.hasChoices) { if (dialogue.hasChoices) {
foreach (i, choice; chat.choices) { foreach (i, choice; dialogue.choices) {
auto choicePosition = Vec2(8, 8 + i * 14); auto choicePosition = Vec2(8, 8 + i * 14);
drawDebugText("{}".format(i + 1), choicePosition); drawDebugText("{}".format(i + 1), choicePosition);
drawDebugText(" | {}".format(choice), choicePosition); drawDebugText(" | {}".format(choice), choicePosition);
} }
} else if (chat.canUpdate) { } else if (dialogue.canUpdate) {
drawDebugText("{}: {}".format(chat.actor, chat.text)); drawDebugText("{}: {}".format(dialogue.actor, dialogue.text));
} else { } else {
drawDebugText("The chat has ended."); drawDebugText("The dialogue has ended.");
} }
// Draw the game info/ // Draw the game info.
auto infoPosition = Vec2(8, resolution.y - 2 - 14 * 2); auto infoPosition = Vec2(8, resolution.y - 2 - 14 * 2);
drawRect(Rect(0, resolution.y * 0.8, resolution.x, resolution.y), gray1); drawRect(Rect(0, resolution.y * 0.8, resolution.x, resolution.y), gray1);
drawDebugText("Press 1, 2 or 3 to select a choice.", infoPosition); drawDebugText("Press 1, 2 or 3 to select a choice.", infoPosition);
@ -71,11 +71,11 @@ bool gameLoop() {
void gameStart() { void gameStart() {
lockResolution(320, 180); lockResolution(320, 180);
// Parse the chat script of the game. // Parse the dialogue script of the game.
// The first update makes the chat go to the first available line. // The first update makes the dialogue go to the first available line.
chat.parse(script); dialogue.parse(script);
chat.update(); dialogue.update();
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -56,4 +56,4 @@ void gameStart() {
atlas.free(); atlas.free();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -1,18 +1,18 @@
/// This example serves as a classic hello-world program, introducing the fundamental structure of a Popka program. /// This example serves as a classic hello-world program, introducing the fundamental structure of a Popka program.
import popka; import popka;
// The game loop. This is called every frame. // The loop function. This is called every frame.
// If true is returned, then the game will stop running. // If true is returned, then the game will stop.
bool gameLoop() { bool gameLoop() {
drawDebugText("Hello world!"); drawDebugText("Hello world!");
return false; return false;
} }
// The game start. This is one time. // The start function. This is called once.
void gameStart() { void gameStart() {
lockResolution(320, 180); lockResolution(320, 180);
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
// Creates a main function that calls the given function and creates a game window that is 640 pixels wide and 360 pixels tall. // Creates a main function that calls the given function and creates a game window that is 640 pixels wide and 360 pixels tall.
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -41,4 +41,4 @@ void gameStart() {
atlas.free(); atlas.free();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -79,4 +79,4 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);

View file

@ -10,7 +10,7 @@ enum header = "// ---
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// ---"; // ---";
int main(string[] args) { int main(string[] args) {

View file

@ -96,7 +96,7 @@ lib*
enum defaultAppContent = `import popka; enum defaultAppContent = `import popka;
bool gameLoop() { bool gameLoop() {
draw("Hello world!"); drawDebugText("Hello world!");
return false; return false;
} }
@ -105,7 +105,7 @@ void gameStart() {
updateWindow!gameLoop(); updateWindow!gameLoop();
} }
mixin addGameStart!(gameStart, 640, 360); mixin callGameStart!(gameStart, 640, 360);
`; `;
/// Check if path exists and print an error message if needed. /// Check if path exists and print an error message if needed.

View file

@ -3,20 +3,20 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/// The `chat` module provides a simple and versatile dialogue system. /// The `dialogue` module provides a simple and versatile dialogue system.
module popka.chat; module popka.dialogue;
import popka.engine; import popka.engine;
public import joka; public import joka;
@safe @nogc nothrow: @safe @nogc nothrow:
enum ChatUnitKindChars = ".#*@>|^!+-$"; enum DialogueUnitKindChars = ".#*@>|^!+-$";
enum ChatUnitKind { enum DialogueUnitKind {
pause = '.', pause = '.',
comment = '#', comment = '#',
point = '*', point = '*',
@ -30,18 +30,19 @@ enum ChatUnitKind {
command = '$', command = '$',
} }
struct ChatUnit { struct DialogueUnit {
LStr text; LStr text;
ChatUnitKind kind; DialogueUnitKind kind;
@safe @nogc nothrow: @safe @nogc nothrow:
void free() { void free() {
text.free(); text.free();
this = DialogueUnit();
} }
} }
struct ChatValue { struct DialogueValue {
LStr name; LStr name;
long value; long value;
@ -49,14 +50,15 @@ struct ChatValue {
void free() { void free() {
name.free(); name.free();
this = DialogueValue();
} }
} }
alias ChatCommandRunner = void function(IStr[] args); alias DialogueCommandRunner = void function(IStr[] args);
struct Chat { struct Dialogue {
List!ChatUnit units; List!DialogueUnit units;
List!ChatValue values; List!DialogueValue values;
IStr text; IStr text;
IStr actor; IStr actor;
Sz unitIndex; Sz unitIndex;
@ -67,20 +69,20 @@ struct Chat {
return units.length == 0; return units.length == 0;
} }
bool isKind(ChatUnitKind kind) { bool isKind(DialogueUnitKind kind) {
return unitIndex < units.length && units[unitIndex].kind == kind; return unitIndex < units.length && units[unitIndex].kind == kind;
} }
bool hasChoices() { bool hasChoices() {
return isKind(ChatUnitKind.menu); return isKind(DialogueUnitKind.menu);
} }
bool hasArgs() { bool hasArgs() {
return isKind(ChatUnitKind.command); return isKind(DialogueUnitKind.command);
} }
bool hasEnded() { bool hasEnded() {
return isKind(ChatUnitKind.pause); return isKind(DialogueUnitKind.pause);
} }
bool canUpdate() { bool canUpdate() {
@ -93,7 +95,7 @@ struct Chat {
auto length = 0; auto length = 0;
auto temp = hasChoices ? units[unitIndex].text.items : ""; auto temp = hasChoices ? units[unitIndex].text.items : "";
while (temp.length != 0) { while (temp.length != 0) {
buffer[length] = temp.skipValue(ChatUnitKind.menu).trim(); buffer[length] = temp.skipValue(DialogueUnitKind.menu).trim();
length += 1; length += 1;
} }
return buffer[0 .. length]; return buffer[0 .. length];
@ -121,7 +123,7 @@ struct Chat {
if (point.length == 0) { if (point.length == 0) {
foreach (i; unitIndex + 1 .. units.length) { foreach (i; unitIndex + 1 .. units.length) {
auto unit = units[i]; auto unit = units[i];
if (unit.kind == ChatUnitKind.point) { if (unit.kind == DialogueUnitKind.point) {
unitIndex = i; unitIndex = i;
break; break;
} }
@ -129,7 +131,7 @@ struct Chat {
} else { } else {
foreach (i; 0 .. units.length) { foreach (i; 0 .. units.length) {
auto unit = units[i]; auto unit = units[i];
if (unit.kind == ChatUnitKind.point && unit.text.items == point) { if (unit.kind == DialogueUnitKind.point && unit.text.items == point) {
unitIndex = i; unitIndex = i;
break; break;
} }
@ -140,7 +142,7 @@ struct Chat {
void jump(Sz i) { void jump(Sz i) {
auto currPoint = 0; auto currPoint = 0;
foreach (j, unit; units.items) { foreach (j, unit; units.items) {
if (unit.kind == ChatUnitKind.point) { if (unit.kind == DialogueUnitKind.point) {
if (currPoint == i) { if (currPoint == i) {
unitIndex = j; unitIndex = j;
break; break;
@ -161,7 +163,7 @@ struct Chat {
update(); update();
} }
void run(ChatCommandRunner runner) { void run(DialogueCommandRunner runner) {
runner(args); runner(args);
update(); update();
} }
@ -172,24 +174,24 @@ struct Chat {
unitIndex += 1; unitIndex += 1;
text = units[unitIndex].text.items; text = units[unitIndex].text.items;
final switch (units[unitIndex].kind) { final switch (units[unitIndex].kind) {
case ChatUnitKind.line, ChatUnitKind.menu, ChatUnitKind.command, ChatUnitKind.pause: { case DialogueUnitKind.line, DialogueUnitKind.menu, DialogueUnitKind.command, DialogueUnitKind.pause: {
break; break;
} }
case ChatUnitKind.comment, ChatUnitKind.point: { case DialogueUnitKind.comment, DialogueUnitKind.point: {
update(); update();
break; break;
} }
case ChatUnitKind.actor: { case DialogueUnitKind.actor: {
actor = text; actor = text;
update(); update();
break; break;
} }
case ChatUnitKind.jump: { case DialogueUnitKind.jump: {
jump(text); jump(text);
update(); update();
break; break;
} }
case ChatUnitKind.variable: { case DialogueUnitKind.variable: {
auto variableIndex = -1; auto variableIndex = -1;
auto view = text; auto view = text;
auto name = trim(skipValue(view, '=')); auto name = trim(skipValue(view, '='));
@ -206,7 +208,7 @@ struct Chat {
} }
// Create variable if it does not exist. // Create variable if it does not exist.
if (variableIndex < 0) { if (variableIndex < 0) {
auto variable = ChatValue(); auto variable = DialogueValue();
variable.name.append(name); variable.name.append(name);
values.append(variable); values.append(variable);
variableIndex = cast(int) values.length - 1; variableIndex = cast(int) values.length - 1;
@ -236,7 +238,7 @@ struct Chat {
update(); update();
break; break;
} }
case ChatUnitKind.plus, ChatUnitKind.minus: { case DialogueUnitKind.plus, DialogueUnitKind.minus: {
auto variableIndex = -1; auto variableIndex = -1;
auto name = text; auto name = text;
// Find if variable exists. // Find if variable exists.
@ -250,7 +252,7 @@ struct Chat {
if (variableIndex < 0) { if (variableIndex < 0) {
assert(0, "TODO: A variable that does not exist it an error for now."); assert(0, "TODO: A variable that does not exist it an error for now.");
} }
if (units[unitIndex].kind == ChatUnitKind.plus) { if (units[unitIndex].kind == DialogueUnitKind.plus) {
values[variableIndex].value += 1; values[variableIndex].value += 1;
} else { } else {
values[variableIndex].value -= 1; values[variableIndex].value -= 1;
@ -268,7 +270,7 @@ struct Chat {
return Fault.invalid; return Fault.invalid;
} }
units.append(ChatUnit(LStr(), ChatUnitKind.pause)); units.append(DialogueUnit(LStr(), DialogueUnitKind.pause));
auto isFirstLine = true; auto isFirstLine = true;
auto view = script; auto view = script;
while (view.length != 0) { while (view.length != 0) {
@ -280,20 +282,20 @@ struct Chat {
auto kind = line[0]; auto kind = line[0];
if (isFirstLine) { if (isFirstLine) {
isFirstLine = false; isFirstLine = false;
if (kind == ChatUnitKind.pause) { if (kind == DialogueUnitKind.pause) {
continue; continue;
} }
} }
if (isValidChatUnitKind(kind)) { if (isValidDialogueUnitKind(kind)) {
auto realKind = cast(ChatUnitKind) kind; auto realKind = cast(DialogueUnitKind) kind;
units.append(ChatUnit(LStr(text), realKind)); units.append(DialogueUnit(LStr(text), realKind));
} else { } else {
clear(); clear();
return Fault.invalid; return Fault.invalid;
} }
} }
if (units.items[$ - 1].kind != ChatUnitKind.pause) { if (units.items[$ - 1].kind != DialogueUnitKind.pause) {
units.append(ChatUnit(LStr(), ChatUnitKind.pause)); units.append(DialogueUnit(LStr(), DialogueUnitKind.pause));
} }
return Fault.none; return Fault.none;
} }
@ -323,8 +325,8 @@ struct Chat {
} }
} }
bool isValidChatUnitKind(char c) { bool isValidDialogueUnitKind(char c) {
foreach (kind; ChatUnitKindChars) { foreach (kind; DialogueUnitKindChars) {
if (c == kind) { if (c == kind) {
return true; return true;
} }
@ -332,19 +334,19 @@ bool isValidChatUnitKind(char c) {
return false; return false;
} }
Result!Chat toChat(IStr script) { Result!Dialogue toDialogue(IStr script) {
auto value = Chat(); auto value = Dialogue();
auto fault = value.parse(script); auto fault = value.parse(script);
if (fault) { if (fault) {
value.free(); value.free();
} }
return Result!Chat(value, fault); return Result!Dialogue(value, fault);
} }
Result!Chat loadChat(IStr path) { Result!Dialogue loadDialogue(IStr path) {
auto temp = loadTempText(path); auto temp = loadTempText(path);
if (temp.isNone) { if (temp.isNone) {
return Result!Chat(temp.fault); return Result!Dialogue(temp.fault);
} }
return toChat(temp.unwrap()); return toDialogue(temp.unwrap());
} }

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/// The `engine` module functions as a lightweight 2D game engine. /// The `engine` module functions as a lightweight 2D game engine.
@ -25,7 +25,7 @@ ray.Camera2D _toRay(Camera camera) {
); );
} }
/// Returns a random integer between 0 and float.max (inclusive). /// Returns a random integer between 0 and int.max (inclusive).
@trusted @trusted
int randi() { int randi() {
return ray.GetRandomValue(0, int.max); return ray.GetRandomValue(0, int.max);
@ -37,7 +37,7 @@ float randf() {
return ray.GetRandomValue(0, cast(int) float.max) / cast(float) cast(int) float.max; return ray.GetRandomValue(0, cast(int) float.max) / cast(float) cast(int) float.max;
} }
/// Sets the seed for the random number generator to something specific. /// Sets the seed of the random number generator to the given value.
@trusted @trusted
void randomize(int seed) { void randomize(int seed) {
ray.SetRandomSeed(seed); ray.SetRandomSeed(seed);
@ -48,19 +48,19 @@ void randomize() {
randomize(randi); randomize(randi);
} }
/// Converts a world point to a screen point based on the given camera. /// Converts a world position to a screen position based on the given camera.
@trusted @trusted
Vec2 toScreenPosition(Vec2 point, Camera camera) { Vec2 toScreenPosition(Vec2 position, Camera camera) {
return toPopka(ray.GetWorldToScreen2D(point.toRay(), camera._toRay())); return toPopka(ray.GetWorldToScreen2D(position.toRay(), camera._toRay()));
} }
/// Converts a screen point to a world point based on the given camera. /// Converts a screen position to a world position based on the given camera.
@trusted @trusted
Vec2 toWorldPosition(Vec2 point, Camera camera) { Vec2 toWorldPosition(Vec2 position, Camera camera) {
return toPopka(ray.GetScreenToWorld2D(point.toRay(), camera._toRay())); return toPopka(ray.GetScreenToWorld2D(position.toRay(), camera._toRay()));
} }
/// Returns the default font. This font should not be freed. /// Returns the default Popka font. This font should not be freed.
@trusted @trusted
Font dfltFont() { Font dfltFont() {
auto result = ray.GetFontDefault().toPopka(); auto result = ray.GetFontDefault().toPopka();
@ -69,6 +69,7 @@ Font dfltFont() {
return result; return result;
} }
/// Returns an absolute path to the assets folder.
IStr assetsPath() { IStr assetsPath() {
return engineState.assetsPath.items; return engineState.assetsPath.items;
} }
@ -78,21 +79,21 @@ IStr toAssetsPath(IStr path) {
} }
/// Loads a text file from the assets folder and returns its contents as a list. /// Loads a text file from the assets folder and returns its contents as a list.
/// Can handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems. /// Can handle both forward slashes and backslashes in file paths.
Result!LStr loadText(IStr path) { Result!LStr loadText(IStr path) {
return readText(path.toAssetsPath()); return readText(path.toAssetsPath());
} }
/// Loads a text file from the assets folder and returns its contents as a slice. /// Loads a text file from the assets folder and returns its contents as a slice.
/// The slice can be safely used until this function is called again. /// The slice can be safely used until this function is called again.
/// Can handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems. /// Can handle both forward slashes and backslashes in file paths.
Result!IStr loadTempText(IStr path) { Result!IStr loadTempText(IStr path) {
auto fault = readTextIntoBuffer(path.toAssetsPath(), engineState.tempText); auto fault = readTextIntoBuffer(path.toAssetsPath(), engineState.tempText);
return Result!IStr(engineState.tempText.items, fault); return Result!IStr(engineState.tempText.items, fault);
} }
/// Loads an image file from the assets folder. /// Loads an image file (PNG) from the assets folder.
/// Can handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems. /// Can handle both forward slashes and backslashes in file paths.
@trusted @trusted
Result!Texture loadTexture(IStr path) { Result!Texture loadTexture(IStr path) {
auto value = ray.LoadTexture(path.toAssetsPath().toCStr().unwrapOr()).toPopka(); auto value = ray.LoadTexture(path.toAssetsPath().toCStr().unwrapOr()).toPopka();
@ -106,20 +107,21 @@ Result!Viewport loadViewport(int width, int height) {
} }
@trusted @trusted
/// Loads a font file (TTF) from the assets folder.
/// Can handle both forward slashes and backslashes in file paths.
Result!Font loadFont(IStr path, uint size, const(dchar)[] runes = []) { Result!Font loadFont(IStr path, uint size, const(dchar)[] runes = []) {
auto value = ray.LoadFontEx(path.toAssetsPath.toCStr().unwrapOr(), size, cast(int*) runes.ptr, cast(int) runes.length).toPopka(); auto value = ray.LoadFontEx(path.toAssetsPath.toCStr().unwrapOr(), size, cast(int*) runes.ptr, cast(int) runes.length).toPopka();
return Result!Font(value, value.isEmpty.toFault(Fault.cantFind)); return Result!Font(value, value.isEmpty.toFault(Fault.cantFind));
} }
/// Saves a text file to the assets folder. /// Saves a text file to the assets folder.
/// Can handle both forward slashes and backslashes in file paths, ensuring compatibility across operating systems. /// Can handle both forward slashes and backslashes in file paths.
Fault saveText(IStr path, IStr text) { Fault saveText(IStr path, IStr text) {
return writeText(path.toAssetsPath(), text); return writeText(path.toAssetsPath(), text);
} }
/// Opens the game window with the given size and title. /// Opens a window with the given size and title.
/// This function does not work if the window is already open, because Popka only works with one window. /// You should avoid calling this function manually.
/// Usually you should avoid calling this function manually.
@trusted @trusted
void openWindow(int width, int height, IStr title = "Popka") { void openWindow(int width, int height, IStr title = "Popka") {
if (ray.IsWindowReady) { if (ray.IsWindowReady) {
@ -135,7 +137,7 @@ void openWindow(int width, int height, IStr title = "Popka") {
engineState.fullscreenState.lastWindowSize = Vec2(width, height); engineState.fullscreenState.lastWindowSize = Vec2(width, height);
} }
/// Updates the game window every frame with the specified loop function. /// Updates the window every frame with the given loop function.
/// This function will return when the loop function returns true. /// This function will return when the loop function returns true.
@trusted @trusted
void updateWindow(alias loopFunc)() { void updateWindow(alias loopFunc)() {
@ -229,52 +231,49 @@ void updateWindow(alias loopFunc)() {
} }
} }
/// Closes the game window. /// Closes the window.
/// Usually you should avoid calling this function manually. /// You should avoid calling this function manually.
@trusted @trusted
void closeWindow() { void closeWindow() {
if (!ray.IsWindowReady) { if (!ray.IsWindowReady) {
return; return;
} }
engineState.tempText.free(); engineState.free();
engineState.assetsPath.free();
engineState.viewport.free();
ray.CloseAudioDevice(); ray.CloseAudioDevice();
ray.CloseWindow(); ray.CloseWindow();
engineState = EngineState();
} }
/// Sets the window background color to the given color.
void setBackgroundColor(Color color) { void setBackgroundColor(Color color) {
engineState.backgroundColor = color; engineState.backgroundColor = color;
} }
/// Returns true if the FPS of the game is locked. /// Returns true if the FPS is locked.
bool isFpsLocked() { bool isFpsLocked() {
return engineState.flags.isFpsLocked; return engineState.flags.isFpsLocked;
} }
/// Locks the FPS of the game to a specific value. /// Locks the FPS to the given value.
@trusted @trusted
void lockFps(int target) { void lockFps(int target) {
engineState.flags.isFpsLocked = true; engineState.flags.isFpsLocked = true;
ray.SetTargetFPS(target); ray.SetTargetFPS(target);
} }
/// Unlocks the FPS of the game. /// Unlocks the FPS.
@trusted @trusted
void unlockFps() { void unlockFps() {
engineState.flags.isFpsLocked = false; engineState.flags.isFpsLocked = false;
ray.SetTargetFPS(0); ray.SetTargetFPS(0);
} }
/// Returns true if the resolution of the game is locked. /// Returns true if the resolution is locked.
bool isResolutionLocked() { bool isResolutionLocked() {
return !engineState.viewport.isEmpty; return !engineState.viewport.isEmpty;
} }
/// Locks the resolution of the game to a specific value. /// Locks the resolution to the given value.
@trusted @trusted
void lockResolution(int width, int height) { void lockResolution(int width, int height) {
if (!engineState.flags.isUpdating) { if (!engineState.flags.isUpdating) {
@ -287,7 +286,7 @@ void lockResolution(int width, int height) {
} }
} }
/// Unlocks the resolution of the game. /// Unlocks the resolution.
void unlockResolution() { void unlockResolution() {
if (!engineState.flags.isUpdating) { if (!engineState.flags.isUpdating) {
engineState.viewport.free(); engineState.viewport.free();
@ -311,7 +310,6 @@ bool isCursorHidden() {
} }
/// Hides the system cursor. /// Hides the system cursor.
/// This function works only on desktop.
@trusted @trusted
void hideCursor() { void hideCursor() {
engineState.flags.isCursorHidden = true; engineState.flags.isCursorHidden = true;
@ -319,7 +317,6 @@ void hideCursor() {
} }
/// Shows the system cursor. /// Shows the system cursor.
/// This function works only on desktop.
@trusted @trusted
void showCursor() { void showCursor() {
engineState.flags.isCursorHidden = false; engineState.flags.isCursorHidden = false;
@ -327,14 +324,12 @@ void showCursor() {
} }
/// Returns true if the window is in fullscreen mode. /// Returns true if the window is in fullscreen mode.
/// This function works only on desktop.
@trusted @trusted
bool isFullscreen() { bool isFullscreen() {
return ray.IsWindowFullscreen(); return ray.IsWindowFullscreen();
} }
/// Changes the state of the fullscreen mode of the window. /// Changes the state of the fullscreen mode of the window.
/// This function works only on desktop.
@trusted @trusted
void toggleFullscreen() { void toggleFullscreen() {
version(WebAssembly) { version(WebAssembly) {
@ -355,6 +350,7 @@ bool isPixelPerfect() {
return engineState.flags.isPixelPerfect; return engineState.flags.isPixelPerfect;
} }
/// Changes the state of the pixel perfect mode of the window.
void togglePixelPerfect() { void togglePixelPerfect() {
engineState.flags.isPixelPerfect = !engineState.flags.isPixelPerfect; engineState.flags.isPixelPerfect = !engineState.flags.isPixelPerfect;
} }
@ -760,7 +756,7 @@ void drawDebugText(IStr text, Vec2 position = Vec2(8.0f), DrawOptions options =
drawText(dfltFont, position, text, options); drawText(dfltFont, position, text, options);
} }
mixin template addGameStart(alias startFunc, int width, int height, IStr title = "Popka") { mixin template callGameStart(alias startFunc, int width, int height, IStr title = "Popka") {
version (D_BetterC) { version (D_BetterC) {
pragma(msg, "Popka is using the C main function."); pragma(msg, "Popka is using the C main function.");
extern(C) extern(C)

View file

@ -3,13 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
// The `popka`` module imports all of its submodules.
module popka; module popka;
public import popka.chat; public import popka.dialogue;
public import popka.engine; public import popka.engine;
public import popka.tilemap; public import popka.tilemap;
public import popka.types; public import popka.types;

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/// The `ray` module provides access to the raylib library. /// The `ray` module provides access to the raylib library.

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/********************************************************************************************** /**********************************************************************************************

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/********************************************************************************************** /**********************************************************************************************

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/// The `tilemap` module provides a simple and fast tile map. /// The `tilemap` module provides a simple and fast tile map.

View file

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com // Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka // Project: https://github.com/Kapendev/popka
// Version: v0.0.14 // Version: v0.0.15
// --- // ---
/// The `types` module defines all the types used within the `engine` module. /// The `types` module defines all the types used within the `engine` module.
@ -170,6 +170,15 @@ struct EngineState {
Color backgroundColor; Color backgroundColor;
LStr tempText; LStr tempText;
LStr assetsPath; LStr assetsPath;
@safe @nogc nothrow:
void free() {
viewport.free();
tempText.free();
assetsPath.free();
this = EngineState();
}
} }
struct DrawOptions { struct DrawOptions {