Fixed some bugs and added examples.

This commit is contained in:
Kapendev 2024-03-09 16:00:22 +02:00
parent ad1130a4bc
commit 6223da2a21
6 changed files with 308 additions and 155 deletions

View file

@ -5,21 +5,16 @@ It focuses on providing a simple and easy-to-understand foundation for building
The game engine is currently under development and is not yet ready for use.
```d
/// A hello world example.
/// A hello-world example.
import popka.basic;
void main() {
openWindow(640, 480);
lockResolution(320, 180);
while (isWindowOpen) {
drawDebugText("Hello world!", mousePosition);
if (Keyboard.f11.isPressed) {
toggleFullscreen();
}
if (Keyboard.esc.isPressed) {
closeWindow();
}
drawDebugText("Hello world!");
}
freeWindow();
}
@ -56,7 +51,7 @@ This guide outlines the steps to install Popka using Git and the Dub package man
3. **Compile example:**
Once the installation is complete, you should be able to compile the provided hello world example by running:
Once the installation is complete, you should be able to compile the provided example by running:
```bash
dub run

View file

@ -9,7 +9,8 @@ module popka.core.color;
enum {
blank = Color(),
black = Color(0),
gray = Color(220),
lightGray = Color(220),
darkGray = Color(110),
white = Color(255),
red = Color(255, 0, 0),
green = Color(0, 255, 0),

66
example/coins.d Normal file
View file

@ -0,0 +1,66 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// A collect-the-coins example.
module popka.example.coins;
import popka.basic;
void runExample() {
openWindow(640, 480);
lockResolution(320, 180);
// The game variables.
auto player = Rect(resolution * Vec2(0.5), Vec2(16));
auto playerSpeed = Vec2(120);
auto coins = FlagList!Rect();
auto coinSize = Vec2(8);
auto maxCoinCount = 8;
// Create the coins.
foreach (i; 0 .. maxCoinCount) {
auto maxPosition = resolution - coinSize;
auto coin = Rect(randf * maxPosition.x, randf * maxPosition.y, coinSize.x, coinSize.y);
coins.append(coin);
}
while (isWindowOpen) {
// Move the player.
auto playerDirection = Vec2();
if (Keyboard.left.isDown) {
playerDirection.x = -1;
}
if (Keyboard.right.isDown) {
playerDirection.x = 1;
}
if (Keyboard.up.isDown) {
playerDirection.y = -1;
}
if (Keyboard.down.isDown) {
playerDirection.y = 1;
}
player.position += playerDirection * playerSpeed * Vec2(deltaTime);
// Check if the player is touching some coins.
foreach (id; coins.ids) {
if (coins[id].hasIntersection(player)) {
coins.remove(id);
}
}
// Draw the game.
foreach (coin; coins.items) {
drawRect(coin, darkGray);
}
drawRect(player, lightGray);
if (coins.length == 0) {
drawDebugText("You collected all the coins!");
} else {
drawDebugText("Coins: {}/{}".fmt(maxCoinCount - coins.length, maxCoinCount));
}
}
// Free all the game resources.
coins.free();
freeWindow();
}

18
example/hello.d Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// A hello-world example.
module popka.example.hello;
import popka.basic;
void runExample() {
openWindow(640, 480);
lockResolution(320, 180);
while (isWindowOpen) {
drawDebugText("Hello world!");
}
freeWindow();
}

View file

@ -12,6 +12,8 @@ import popka.core.basic;
struct DialogueUnit {
alias Kind = char;
enum kindChars = "-#*@>|";
enum : Kind {
pause = '-',
comment = '#',
@ -32,26 +34,34 @@ struct DialogueUnit {
}
return false;
}
bool isValid() {
return isOneOf(kindChars);
}
}
struct Dialogue {
List!DialogueUnit units;
size_t index;
size_t unitIndex;
this(const(char)[] path) {
read(path);
load(path);
}
DialogueUnit* now() {
return &units[index];
DialogueUnit now() {
return units[unitIndex];
}
void update() {
if (units.length != 0 && index < units.length - 1) {
index += 1;
if (units.length != 0 && unitIndex < units.length - 1) {
unitIndex += 1;
}
}
bool canUpdate() {
return unitIndex < units.length && units[unitIndex].kind != DialogueUnit.pause;
}
void free() {
foreach (ref unit; units.items) {
unit.content.free();
@ -59,30 +69,69 @@ struct Dialogue {
units.free();
}
void read(const(char)[] path) {
void parse(const(char)[] text) {
free();
units.append(DialogueUnit(List!char(), DialogueUnit.pause));
auto file = readText(path);
const(char)[] view = file.items;
auto lineNumber = 0;
auto isFirstLine = true;
auto view = text;
while (view.length != 0) {
auto line = skipLine(view);
auto line = trim(skipLine(view));
if (line.length == 0) {
continue;
}
lineNumber += 1;
if (lineNumber == 1 && line[0] == DialogueUnit.pause) {
auto content = trimStart(line[1 .. $]);
auto kind = line[0];
if (isFirstLine && kind == DialogueUnit.pause) {
isFirstLine = false;
continue;
}
units.append(DialogueUnit(List!char(trimStart(line[1 .. $])), line[0]));
auto unit = DialogueUnit(List!char(), kind);
if (unit.isValid) {
unit.content = List!char(content);
units.append(unit);
} else {
free();
return;
}
}
if (units.items[$ - 1].kind != DialogueUnit.pause) {
units.append(DialogueUnit(List!char(), DialogueUnit.pause));
}
return;
}
void load(const(char)[] path) {
auto file = readText(path);
parse(file.items);
file.free();
}
}
unittest {}
unittest {
auto text = "
# This is a comment.
> Actor1
| First line.
| Second line.
> Actor2
| First line.
- Pause
* Point
> Actor3
| This is a loop.
@ Point
";
import popka.core.fmt;
auto dialogue = Dialogue();
dialogue.parse(text);
dialogue.update();
while (dialogue.canUpdate) {
auto unit = dialogue.now;
if (unit.isOneOf(">|")) {
println(unit.kind, " | ", unit.content.items);
}
dialogue.update();
}
}

View file

@ -12,13 +12,15 @@ import popka.game.pixeloid;
public import popka.core.basic;
enum {
popkaDefaultFPS = 60,
popkaDefaultBackgroundColor = Color(0x2A, 0x36, 0x3A),
popkaFullscreenWaitTime = 0.125f,
popkaFontSpacing = Vec2(1.0f, 14.0f),
rayFontSpacing = Vec2(1.0f, 14.0f),
}
PopkaState popkaState;
enum defaultFPS = 60;
enum defaultBackgroundColor = Color(0x2A, 0x36, 0x3A);
enum defaultDebugFontSpacing = Vec2(1.0f, 14.0f);
enum defaultDebugFontColor = lightGray;
enum fullscreenWaitTime = 0.125f;
enum rayFontSpacing = Vec2(1.0f, 14.0f);
enum Flip : ubyte {
none,
@ -32,24 +34,28 @@ enum Filter : ubyte {
linear,
}
// NOTE: It might be a good idea to put all the global data into a structure.
struct PopkaState {
bool isWindowOpen;
bool isDrawing;
Color backgroundColor;
bool popkaState;
Color popkaBackgroundColor;
Font popkaFont;
DrawOptions popkaFontOptions;
bool popkaFPSFlag;
bool popkaCursorFlag;
Font debugFont;
Vec2 debugFontSpacing;
DrawOptions debugFontOptions;
View popkaView;
float popkaViewWidth = 320.0f;
float popkaViewHeight = 180.0f;
bool popkaViewLockFlag;
bool popkaViewUnlockFlag;
View view;
Vec2 bufferViewSize;
bool isLockResolutionQueued;
bool isUnlockResolutionQueued;
Vec2 popkaFullscreenLastWindowSize;
float popkaFullscreenTime = 0.0f;
bool popkaFullscreenFlag;
// TODO: Change the names of this members.
// TODO: Change how this members work.
Vec2 popkaFullscreenLastWindowSize;
float popkaFullscreenTime = 0.0f;
bool popkaFullscreenFlag;
bool popkaFPSFlag;
bool popkaCursorFlag;
}
struct Sprite {
ray.Texture2D data;
@ -59,7 +65,7 @@ struct Sprite {
}
bool isEmpty() {
return data.id == 0;
return data.id <= 0;
}
float width() {
@ -99,7 +105,7 @@ struct Font {
}
bool isEmpty() {
return data.texture.id == 0;
return data.texture.id <= 0;
}
float size() {
@ -122,12 +128,16 @@ struct Font {
struct View {
ray.RenderTexture2D data;
this(Vec2 size) {
load(size);
}
this(float width, float height) {
load(width, height);
}
bool isEmpty() {
return data.texture.id == 0;
return data.texture.id <= 0;
}
float width() {
@ -146,9 +156,13 @@ struct View {
return Rect(size);
}
void load(float width, float height) {
void load(Vec2 size) {
free();
data = ray.LoadRenderTexture(cast(int) width, cast(int) height);
data = ray.LoadRenderTexture(cast(int) size.x, cast(int) size.y);
}
void load(float width, float height) {
load(Vec2(width, height));
}
void free() {
@ -478,133 +492,145 @@ void randomize() {
randomize(randi);
}
void openWindow(float width, float height, const(char)[] title = "Popka", Color color = popkaDefaultBackgroundColor) {
void openWindow(float width, float height, const(char)[] title = "Popka", Color color = defaultBackgroundColor) {
ray.SetConfigFlags(ray.FLAG_VSYNC_HINT | ray.FLAG_WINDOW_RESIZABLE);
ray.SetTraceLogLevel(ray.LOG_ERROR);
ray.InitWindow(cast(int) width, cast(int) height, toStrz(title));
ray.SetWindowMinSize(cast(int) (width * 0.25f), cast(int) (height * 0.25f));
ray.SetExitKey(ray.KEY_NULL);
lockFPS(popkaDefaultFPS);
popkaState = true;
popkaBackgroundColor = color;
popkaFullscreenLastWindowSize = Vec2(width, height);
popkaFont = LoadPixeloidFont();
popkaFontOptions.color = gray;
lockFPS(defaultFPS);
popkaState.isWindowOpen = true;
popkaState.backgroundColor = color;
popkaState.popkaFullscreenLastWindowSize = Vec2(width, height);
popkaState.debugFont = LoadPixeloidFont();
popkaState.debugFontSpacing = defaultDebugFontSpacing;
popkaState.debugFontOptions.color = defaultDebugFontColor;
}
void closeWindow() {
popkaState = false;
popkaState.isWindowOpen = false;
}
void freeWindow() {
popkaView.free();
popkaState.view.free();
ray.CloseWindow();
}
bool isWindowOpen() {
static auto isFirstCall = true;
auto result = !(ray.WindowShouldClose() || !popkaState);
if (result) {
if (isFirstCall) {
// Begin drawing.
if (isResolutionLocked) {
ray.BeginTextureMode(popkaView.data);
} else {
ray.BeginDrawing();
}
ray.ClearBackground(toRay(popkaBackgroundColor));
isFirstCall = false;
if (ray.WindowShouldClose() || !popkaState.isWindowOpen) {
return false;
}
if (!popkaState.isDrawing) {
if (isResolutionLocked) {
ray.BeginTextureMode(popkaState.view.data);
} else {
// End drawing.
if (isResolutionLocked) {
auto minSize = popkaView.size;
auto maxSize = windowSize;
auto ratio = maxSize / minSize;
auto minRatio = min(ratio.x, ratio.y);
auto targetSize = minSize * Vec2(minRatio);
auto targetPos = maxSize * Vec2(0.5f) - targetSize * Vec2(0.5f);
ray.EndTextureMode();
ray.BeginDrawing();
ray.ClearBackground(ray.BLACK);
ray.DrawTexturePro(
popkaView.data.texture,
ray.Rectangle(0.0f, 0.0f, minSize.x, -minSize.y),
ray.Rectangle(
ratio.x == minRatio ? targetPos.x : floor(targetPos.x),
ratio.y == minRatio ? targetPos.y : floor(targetPos.y),
ratio.x == minRatio ? targetSize.x : floor(targetSize.x),
ratio.y == minRatio ? targetSize.y : floor(targetSize.y),
),
ray.Vector2(0.0f, 0.0f),
0.0f,
ray.WHITE,
);
ray.EndDrawing();
} else {
ray.EndDrawing();
}
// Check if the resolution was locked or unlocked.
if (popkaViewLockFlag) {
popkaView.load(popkaViewWidth, popkaViewHeight);
popkaViewLockFlag = false;
}
if (popkaViewUnlockFlag) {
popkaView.free();
popkaViewUnlockFlag = false;
}
// Begin drawing.
if (isResolutionLocked) {
ray.BeginTextureMode(popkaView.data);
} else {
ray.BeginDrawing();
}
ray.ClearBackground(toRay(popkaBackgroundColor));
// Fullscreen code to fix a bug on KDE.
if (popkaFullscreenFlag) {
popkaFullscreenTime += deltaTime;
if (popkaFullscreenTime >= popkaFullscreenWaitTime) {
popkaFullscreenTime = 0.0f;
popkaFullscreenFlag = false;
ray.ToggleFullscreen();
if (!isFullscreen) {
auto size = popkaFullscreenLastWindowSize;
auto screen = screenSize;
ray.SetWindowSize(cast(int) size.x, cast(int) size.y);
ray.SetWindowPosition(cast(int) (screen.x * 0.5f - size.x * 0.5f), cast(int) (screen.y * 0.5f - size.y * 0.5f));
}
ray.BeginDrawing();
}
ray.ClearBackground(toRay(popkaState.backgroundColor));
} else {
// End drawing.
if (isResolutionLocked) {
auto minSize = popkaState.view.size;
auto maxSize = windowSize;
auto ratio = maxSize / minSize;
auto minRatio = min(ratio.x, ratio.y);
auto targetSize = minSize * Vec2(minRatio);
auto targetPos = maxSize * Vec2(0.5f) - targetSize * Vec2(0.5f);
ray.EndTextureMode();
ray.BeginDrawing();
ray.ClearBackground(ray.BLACK);
ray.DrawTexturePro(
popkaState.view.data.texture,
ray.Rectangle(0.0f, 0.0f, minSize.x, -minSize.y),
ray.Rectangle(
ratio.x == minRatio ? targetPos.x : floor(targetPos.x),
ratio.y == minRatio ? targetPos.y : floor(targetPos.y),
ratio.x == minRatio ? targetSize.x : floor(targetSize.x),
ratio.y == minRatio ? targetSize.y : floor(targetSize.y),
),
ray.Vector2(0.0f, 0.0f),
0.0f,
ray.WHITE,
);
ray.EndDrawing();
} else {
ray.EndDrawing();
}
// The lockResolution and unlockResolution queue.
if (popkaState.isLockResolutionQueued) {
popkaState.view.load(popkaState.bufferViewSize);
popkaState.isLockResolutionQueued = false;
}
if (popkaState.isUnlockResolutionQueued) {
popkaState.view.free();
popkaState.isUnlockResolutionQueued = false;
}
// Fullscreen code to fix a bug on KDE.
if (popkaState.popkaFullscreenFlag) {
popkaState.popkaFullscreenTime += deltaTime;
if (popkaState.popkaFullscreenTime >= fullscreenWaitTime) {
popkaState.popkaFullscreenTime = 0.0f;
popkaState.popkaFullscreenFlag = false;
ray.ToggleFullscreen();
if (!isFullscreen) {
auto size = popkaState.popkaFullscreenLastWindowSize;
auto screen = screenSize;
ray.SetWindowSize(cast(int) size.x, cast(int) size.y);
ray.SetWindowPosition(cast(int) (screen.x * 0.5f - size.x * 0.5f), cast(int) (screen.y * 0.5f - size.y * 0.5f));
}
}
}
// Begin drawing.
if (isResolutionLocked) {
ray.BeginTextureMode(popkaState.view.data);
} else {
ray.BeginDrawing();
}
ray.ClearBackground(toRay(popkaState.backgroundColor));
}
return result;
popkaState.isDrawing = true;
return true;
}
bool isFPSLocked() {
return popkaFPSFlag;
return popkaState.popkaFPSFlag;
}
void lockFPS(uint target) {
ray.SetTargetFPS(target);
popkaFPSFlag = true;
popkaState.popkaFPSFlag = true;
}
void unlockFPS() {
ray.SetTargetFPS(0);
popkaFPSFlag = false;
popkaState.popkaFPSFlag = false;
}
bool isResolutionLocked() {
return !popkaView.isEmpty;
return !popkaState.view.isEmpty;
}
void lockResolution(Vec2 size) {
if (popkaState.isWindowOpen && !popkaState.isDrawing) {
popkaState.view.load(size);
} else {
popkaState.bufferViewSize = size;
popkaState.isLockResolutionQueued = true;
popkaState.isUnlockResolutionQueued = false;
}
}
void lockResolution(float width, float height) {
popkaViewWidth = width;
popkaViewHeight = height;
popkaViewLockFlag = true;
lockResolution(Vec2(width, height));
}
void unlockResolution() {
popkaViewUnlockFlag = true;
if (popkaState.isWindowOpen && !popkaState.isDrawing) {
popkaState.view.free();
} else {
popkaState.isUnlockResolutionQueued = true;
popkaState.isLockResolutionQueued = false;
}
}
bool isFullscreen() {
@ -612,10 +638,10 @@ bool isFullscreen() {
}
void toggleFullscreen() {
if (!popkaFullscreenFlag) {
popkaFullscreenFlag = true;
if (!popkaState.popkaFullscreenFlag) {
popkaState.popkaFullscreenFlag = true;
if (!isFullscreen) {
popkaFullscreenLastWindowSize = windowSize;
popkaState.popkaFullscreenLastWindowSize = windowSize;
auto size = screenSize;
auto screen = screenSize;
ray.SetWindowSize(cast(int) size.x, cast(int) size.y);
@ -625,17 +651,17 @@ void toggleFullscreen() {
}
bool isCursorHidden() {
return popkaCursorFlag;
return popkaState.popkaCursorFlag;
}
void hideCursor() {
ray.HideCursor();
popkaCursorFlag = true;
popkaState.popkaCursorFlag = true;
}
void showCursor() {
ray.ShowCursor();
popkaCursorFlag = false;
popkaState.popkaCursorFlag = false;
}
Vec2 screenSize() {
@ -669,7 +695,7 @@ float windowHeight() {
Vec2 resolution() {
if (isResolutionLocked) {
return popkaView.size;
return popkaState.view.size;
} else {
return windowSize;
}
@ -686,11 +712,11 @@ float resolutionHeight() {
Vec2 mousePosition() {
if (isResolutionLocked) {
auto window = windowSize;
auto minRatio = min(window.x / popkaView.width, window.y / popkaView.height);
auto targetSize = popkaView.size * Vec2(minRatio);
auto minRatio = min(window.x / popkaState.view.width, window.y / popkaState.view.height);
auto targetSize = popkaState.view.size * Vec2(minRatio);
return Vec2(
clamp((ray.GetMouseX() - (window.x - targetSize.x) * 0.5f) / minRatio, 0.0f, popkaView.width),
clamp((ray.GetMouseY() - (window.y - targetSize.y) * 0.5f) / minRatio, 0.0f, popkaView.height),
(ray.GetMouseX() - (window.x - targetSize.x) * 0.5f) / minRatio,
(ray.GetMouseY() - (window.y - targetSize.y) * 0.5f) / minRatio,
);
} else {
return Vec2(ray.GetMouseX(), ray.GetMouseY());
@ -943,7 +969,5 @@ void drawText(Font font, Vec2 spacing, const(char)[] text, Vec2 position, DrawOp
}
void drawDebugText(const(char)[] text, Vec2 position = Vec2(8.0f)) {
drawText(popkaFont, popkaFontSpacing, text, position, popkaFontOptions);
drawText(popkaState.debugFont, popkaState.debugFontSpacing, text, position, popkaState.debugFontOptions);
}
unittest {}