Examples work again.

This commit is contained in:
Kapendev 2024-08-13 09:59:40 +03:00
parent 4850a99236
commit d2d269b658
11 changed files with 150 additions and 130 deletions

View file

@ -7,7 +7,7 @@ It focuses on providing a simple foundation for building 2D games.
import popka;
bool gameLoop() {
draw("Hello world!");
drawDebugText("Hello world!");
return false;
}

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT
/// This example shows how to use the camera structure of Popka.
import popka;
// The game variables.
@ -12,19 +11,20 @@ auto cameraSpeed = Vec2(120);
bool gameLoop() {
// Move the camera.
cameraTarget += wasd * cameraSpeed * deltaTime;
camera.followPosition(cameraTarget);
cameraTarget += wasd * cameraSpeed * Vec2(deltaTime);
camera.followPosition(cameraTarget, Vec2(deltaTime));
// Draw the game world.
camera.attach();
draw("Move with arrow keys.");
draw(camera.area.subAll(3), Color(50, 50, 40, 130));
camera.detach();
auto cameraArea = Rect(camera.position, resolution).area(camera.hook).subAll(3);
attachCamera(camera);
drawDebugText("Move with arrow keys.");
drawRect(cameraArea, Color(50, 50, 40, 130));
detachCamera(camera);
// Draw the game UI.
draw("I am UI!");
draw("+", resolution * 0.5);
draw("+", resolution * 0.5 + (cameraTarget - camera.position));
drawDebugText("I am UI!");
drawDebugText("+", resolution * Vec2(0.5));
drawDebugText("+", resolution * Vec2(0.5) + (cameraTarget - camera.position));
return false;
}

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT
/// This example shows how to create a simple game with Popka.
import popka;
// The game variables.
@ -27,7 +26,7 @@ bool gameLoop() {
if (Keyboard.down.isDown || 's'.isDown) {
playerDirection.y = 1;
}
player.position += playerDirection * playerSpeed * deltaTime;
player.position += playerDirection * playerSpeed * Vec2(deltaTime);
// Check if the player is touching some coins and remove those coins.
foreach (id; coins.ids) {
@ -36,29 +35,25 @@ bool gameLoop() {
}
}
// Draw the coins.
// Draw the coins and the player.
foreach (coin; coins.items) {
draw(coin, gray2);
drawRect(coin);
}
// Draw the player.
draw(player, gray2);
drawRect(player);
// Draw the game info.
if (coins.length == 0) {
draw("You collected all the coins!");
drawDebugText("You collected all the coins!");
} else {
draw("Coins: {}/{}\nMove with arrow keys.".fmt(maxCoinCount - coins.length, maxCoinCount));
drawDebugText("Coins: {}/{}\nMove with arrow keys.".format(maxCoinCount - coins.length, maxCoinCount));
}
return false;
}
void gameStart() {
lockResolution(320, 180);
changeBackgroundColor(gray1);
// Place the player and create the coins.
player.position = resolution * 0.5;
player.position = resolution * Vec2(0.5);
foreach (i; 0 .. maxCoinCount) {
auto minPosition = Vec2(0, 40);
auto maxPosition = resolution - coinSize - minPosition;

View file

@ -1,19 +1,18 @@
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
/// This example shows how to use the Popka dialogue system.
/// This example shows how to use the Popka chat system.
import popka;
// The game variables.
auto dialogue = Dialogue();
auto chat = Chat();
auto script = "
# This is a comment.
! choiceCount
* menuPoint
^ Select first choice. ^ Select second choice. ^ End dialogue.
^ Select first choice. ^ Select second choice. ^ End chat.
* choice1
> Bob
@ -38,51 +37,47 @@ auto script = "
bool gameLoop() {
// Update the game.
if (dialogue.hasText) {
if (dialogue.hasChoices) {
auto keys = digitChars[1 .. 1 + dialogue.choices.length];
if (chat.canUpdate) {
if (chat.hasChoices) {
auto keys = digitChars[1 .. 1 + chat.choices.length];
foreach (i, key; keys) {
if (key.isPressed) {
dialogue.select(i);
chat.pick(i);
break;
}
}
} else if (Keyboard.space.isPressed) {
dialogue.update();
chat.update();
}
}
// Draw the dialogue.
if (dialogue.hasChoices) {
foreach (i, choice; dialogue.choices) {
// Draw the chat.
if (chat.hasChoices) {
foreach (i, choice; chat.choices) {
auto choicePosition = Vec2(8, 8 + i * 14);
draw("{}".fmt(i + 1), choicePosition);
draw(" | {}".fmt(choice), choicePosition);
drawDebugText("{}".format(i + 1), choicePosition);
drawDebugText(" | {}".format(choice), choicePosition);
}
} else if (dialogue.hasText) {
draw("{}: {}".fmt(dialogue.actor, dialogue.text));
} else if (chat.canUpdate) {
drawDebugText("{}: {}".format(chat.actor, chat.text));
} else {
draw("The dialogue has ended.");
drawDebugText("The chat has ended.");
}
// Draw the game info/
auto infoPosition = Vec2(8, resolution.y - 2 - 14 * 2);
draw(Rect(0, resolution.y * 0.8, resolution.x, resolution.y), gray1);
draw("Press 1, 2 or 3 to select a choice.", infoPosition);
draw("\nPress space to continue.", infoPosition);
drawRect(Rect(0, resolution.y * 0.8, resolution.x, resolution.y), gray1);
drawDebugText("Press 1, 2 or 3 to select a choice.", infoPosition);
drawDebugText("\nPress space to continue.", infoPosition);
return false;
}
void gameStart() {
lockResolution(320, 180);
changeBackgroundColor(Color(50, 60, 75));
// Parse the dialogue script of the game.
dialogue.parse(script);
// The first update makes the dialogue go to the first available line.
dialogue.update();
// Parse the chat script of the game.
// The first update makes the chat go to the first available line.
chat.parse(script);
chat.update();
updateWindow!gameLoop();
}

View file

@ -11,10 +11,8 @@ auto frameDirection = 1;
auto frameSlowdown = 0.2;
bool gameLoop() {
// Move the frame around in a smooth way.
framePosition = framePosition.moveTo(mouseScreenPosition, Vec2(deltaTime), frameSlowdown);
// Update the current frame.
// Move the frame around in a smooth way and update the current frame.
framePosition = framePosition.moveToWithSlowdown(mouseScreenPosition, Vec2(deltaTime), frameSlowdown);
frame = wrap(frame + deltaTime * frameSpeed, 0, frameCount);
// Check the mouse move direction and make the sprite look at that direction.
@ -33,27 +31,27 @@ bool gameLoop() {
options.flip = frameDirection == 1 ? Flip.x : Flip.none;
options.scale = Vec2(2);
// Draw the frame.
draw(atlas, Rect(frameSize.x * floor(frame), 128, frameSize), framePosition, options);
// Draw the mouse position.
draw(mouseScreenPosition, 8, frame == 0 ? blank : white.alpha(150));
// Draw the frame and the mouse position.
drawTexture(atlas, framePosition, Rect(frameSize.x * floor(frame), 128, frameSize), options);
drawVec2(mouseScreenPosition, 8, frame == 0 ? blank : white.alpha(150));
return false;
}
void gameStart() {
lockResolution(320, 180);
changeBackgroundColor(toRGB(0x0b0b0b));
hideCursor();
setBackgroundColor(toRgb(0x0b0b0b));
togglePixelPerfect();
hideCursor();
// Loads the `atlas.png` texture from the assets folder.
atlas.load("atlas.png");
auto result = loadTexture("atlas.png");
if (result.isSome) {
atlas = result.unwrap();
} else {
printfln("Can not load texture. Fault: `{}`", result.fault);
}
updateWindow!gameLoop();
// Frees the loaded texture.
atlas.free();
}

View file

@ -2,13 +2,12 @@
// SPDX-License-Identifier: MIT
/// This example serves as a classic hello-world program, introducing the fundamental structure of a Popka program.
import popka;
// The game loop. This is called every frame.
// If true is returned, then the game will stop running.
bool gameLoop() {
draw("Hello world!");
drawDebugText("Hello world!");
return false;
}

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: MIT
/// This example shows how to use the tile map structure of Popka.
import popka;
// The game variables.
@ -12,34 +11,36 @@ auto camera = Camera();
auto cameraSpeed = Vec2(120);
bool gameLoop() {
// Move the camera.
camera.position += wasd * cameraSpeed * deltaTime;
// Set up the drawing options of the game.
// Move the camera and set up the drawing options of the game.
camera.position += wasd * cameraSpeed * Vec2(deltaTime);
auto options = DrawOptions();
options.scale = Vec2(2);
// Passing a camera to the tile map drawing function allows for efficient rendering by only drawing the tiles that are currently in view.
camera.attach();
draw(atlas, map, camera, Vec2(0), options);
camera.detach();
attachCamera(camera);
drawTileMap(atlas, Vec2(), map, camera, options);
detachCamera(camera);
return false;
}
void gameStart() {
lockResolution(320, 180);
changeBackgroundColor(toRGB(0x0b0b0b));
setBackgroundColor(toRgb(0x0b0b0b));
atlas.load("atlas.png");
// Loads the `atlas.png` texture from the assets folder.
auto result = loadTexture("atlas.png");
if (result.isSome) {
atlas = result.unwrap();
} else {
printfln("Can not load texture. Fault: `{}`", result.fault);
}
// Parse the tile map data.
// Parse the tile map data and set the tile size.
map.parse("145,0,65\n21,22,23\n37,38,39\n53,54,55");
// Set the tile size.
map.tileSize = Vec2(16);
map.tileWidth = 16;
map.tileHeight = 16;
updateWindow!gameLoop();
atlas.free();
}

View file

@ -2,13 +2,12 @@
// SPDX-License-Identifier: MIT
/// This example shows how to create a pong-like game with Popka.
import popka;
// The game variables.
auto gameCounter = 0;
auto ballDirection = Vec2(1, 1);
auto ballSpeed = 120;
auto ballSpeed = Vec2(120);
auto ball = Rect(5, 5);
auto paddle1 = Rect(5 * 0.35, 5 * 5);
auto paddle2 = Rect(5 * 0.35, 5 * 5);
@ -21,12 +20,12 @@ bool gameLoop() {
// Move the ball.
if (ball.centerArea.leftPoint.x < 0) {
ball.position = resolution * 0.5;
ball.position = resolution * Vec2(0.5);
paddle1.position.y = resolution.y * 0.5;
paddle2.position.y = resolution.y * 0.5;
gameCounter = 0;
} else if (ball.centerArea.rightPoint.x > resolution.x) {
ball.position = resolution * 0.5;
ball.position = resolution * Vec2(0.5);
paddle1.position.y = resolution.y * 0.5;
paddle2.position.y = resolution.y * 0.5;
gameCounter = 0;
@ -38,17 +37,17 @@ bool gameLoop() {
ballDirection.y *= -1;
ball.position.y = resolution.y - ball.size.y * 0.5;
}
ball.position += ballDirection * ballSpeed * deltaTime;
ball.position += ballDirection * ballSpeed * Vec2(deltaTime);
// Move paddle1.
paddle1.position.y = clamp(paddle1.position.y + wasd.y * ballSpeed * deltaTime, paddle1.size.y * 0.5, resolution.y - paddle1.size.y * 0.5);
paddle1.position.y = clamp(paddle1.position.y + wasd.y * ballSpeed.y * deltaTime, paddle1.size.y * 0.5, resolution.y - paddle1.size.y * 0.5);
// Move paddle2.
auto paddle2Target = ball.position.y;
if (ballDirection.x < 1) {
paddle2Target = paddle2.position.y;
}
paddle2.position.y = paddle2.position.y.moveTo(clamp(paddle2Target, paddle2.size.y * 0.5, resolution.y - paddle2.size.y * 0.5), ballSpeed * deltaTime);
paddle2.position.y = paddle2.position.y.moveTo(clamp(paddle2Target, paddle2.size.y * 0.5f, resolution.y - paddle2.size.y * 0.5f), ballSpeed.y * deltaTime);
// Check for collisions.
if (paddle1.centerArea.hasIntersection(ball.centerArea)) {
@ -63,29 +62,25 @@ bool gameLoop() {
}
// Draw the objects.
draw(ball.centerArea);
draw(paddle1.centerArea);
draw(paddle2.centerArea);
drawRect(ball.centerArea);
drawRect(paddle1.centerArea);
drawRect(paddle2.centerArea);
// Draw the counter.
auto textOptions = DrawOptions();
textOptions.scale = Vec2(2);
textOptions.hook = Hook.center;
draw("{}".fmt(gameCounter), Vec2(resolution.x * 0.5, 16), textOptions);
drawDebugText("{}".format(gameCounter), Vec2(resolution.x * 0.5, 16), textOptions);
return false;
}
void gameStart() {
lockResolution(320, 180);
changeBackgroundColor(Color(202,178,106));
// Place the game objects.
auto paddleOffset = Vec2(resolution.x * 0.4, 0);
ball.position = resolution * 0.5;
paddle1.position = resolution * 0.5 - paddleOffset;
paddle2.position = resolution * 0.5 + paddleOffset;
ball.position = resolution * Vec2(0.5);
paddle1.position = resolution * Vec2(0.5) - paddleOffset;
paddle2.position = resolution * Vec2(0.5) + paddleOffset;
updateWindow!gameLoop();
}

View file

@ -66,7 +66,13 @@ struct Chat {
return isKind(ChatUnitKind.pause);
}
auto choices() {
bool canUpdate() {
return !hasEnded;
}
IStr[] choices() {
static IStr[16] buffer;
struct Range {
IStr menu;
@ -84,7 +90,13 @@ struct Chat {
}
}
return hasChoices ? Range(units[unitIndex].text.items) : Range("");
auto length = 0;
auto range = hasChoices ? Range(units[unitIndex].text.items) : Range("");
foreach (item; range) {
buffer[length] = item;
length += 1;
}
return buffer[0 .. length];
}
IStr[] args() {

View file

@ -23,12 +23,13 @@ ray.Camera2D _toRay(Camera camera) {
/// Returns a random integer between 0 and float.max (inclusive).
@trusted
int randi() {
return ray.GetRandomValue(0, cast(int) float.max);
return ray.GetRandomValue(0, int.max);
}
/// Returns a random floating point number between 0.0f and 1.0f (inclusive).
@trusted
float randf() {
return randi() / 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.
@ -90,7 +91,7 @@ Result!IStr loadTempText(IStr path) {
@trusted
Result!Texture loadTexture(IStr path) {
auto value = ray.LoadTexture(path.toAssetsPath().toCStr().unwrapOr()).toPopka();
return Result!Texture(value, value.isEmpty.toFault());
return Result!Texture(value, value.isEmpty.toFault(Fault.cantFind));
}
@trusted
@ -102,7 +103,7 @@ Result!Viewport loadViewport(int width, int height) {
@trusted
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();
return Result!Font(value, value.isEmpty.toFault());
return Result!Font(value, value.isEmpty.toFault(Fault.cantFind));
}
/// Saves a text file to the assets folder.
@ -176,10 +177,11 @@ void updateWindow(alias loopFunc)() {
// The lockResolution and unlockResolution queue.
if (engineState.viewport.isLockResolutionQueued) {
engineState.viewport.isLockResolutionQueued = false;
// engineState.viewport.load(engineState.targetViewportSize); // TODO
engineState.viewport.free();
engineState.viewport.data = loadViewport(engineState.viewport.targetWidth, engineState.viewport.targetHeight).unwrapOr();
} else if (engineState.viewport.isUnlockResolutionQueued) {
engineState.viewport.isUnlockResolutionQueued = false;
// engineState.viewport.free(); // TODO
engineState.viewport.free();
}
// Fullscreen code to fix a bug on KDE.
if (engineState.fullscreenState.isToggleQueued) {
@ -239,6 +241,10 @@ void closeWindow() {
engineState = EngineState();
}
void setBackgroundColor(Color color) {
engineState.backgroundColor = color;
}
/// Returns true if the FPS of the game is locked.
bool isFpsLocked() {
return engineState.flags.isFpsLocked;
@ -286,6 +292,14 @@ void unlockResolution() {
}
}
void toggleResolution(int width, int height) {
if (isResolutionLocked) {
unlockResolution();
} else {
lockResolution(width, height);
}
}
/// Returns true if the system cursor is hidden.
bool isCursorHidden() {
return engineState.flags.isCursorHidden;
@ -432,6 +446,30 @@ Vec2 deltaMouse() {
return toPopka(ray.GetMouseDelta());
}
@trusted
void attachCamera(ref Camera camera) {
if (camera.isAttached) {
return;
}
camera.isAttached = true;
auto temp = camera._toRay();
if (isPixelPerfect) {
temp.target.x = floor(temp.target.x);
temp.target.y = floor(temp.target.y);
temp.offset.x = floor(temp.offset.x);
temp.offset.y = floor(temp.offset.y);
}
ray.BeginMode2D(temp);
}
@trusted
void detachCamera(ref Camera camera) {
if (camera.isAttached) {
camera.isAttached = false;
ray.EndMode2D();
}
}
@trusted
Vec2 measureTextSize(Font font, IStr text, DrawOptions options = DrawOptions()) {
if (font.isEmpty || text.length == 0) {

View file

@ -185,6 +185,14 @@ struct Camera {
@safe @nogc nothrow:
this(Vec2 position) {
this.position = position;
}
this(float x, float y) {
this(Vec2(x, y));
}
Hook hook() {
return isCentered ? Hook.center : Hook.topLeft;
}
@ -436,24 +444,3 @@ Flip opposite(Flip flip, Flip fallback) {
return fallback;
}
}
// void attach() {
// if (!isAttached) {
// isAttached = true;
// auto temp = toRay(this);
// if (isPixelPerfect) {
// temp.target.x = floor(temp.target.x);
// temp.target.y = floor(temp.target.y);
// temp.offset.x = floor(temp.offset.x);
// temp.offset.y = floor(temp.offset.y);
// }
// ray.BeginMode2D(temp);
// }
// }
// void detach() {
// if (isAttached) {
// isAttached = false;
// ray.EndMode2D();
// }
// }