Sprite works now and changed drawing function.

This commit is contained in:
Kapendev 2024-09-03 01:46:48 +03:00
parent c4b8465f5f
commit b4b47d8dea
14 changed files with 163 additions and 83 deletions

View file

@ -11,7 +11,7 @@ void ready() {
}
bool update(float dt) {
drawDebugText("Hello world!");
drawDebugText("Hello world!", Vec2(8.0));
return false;
}

14
TOUR.md
View file

@ -12,7 +12,7 @@ void ready() {
}
bool update(float dt) {
drawDebugText("Hello world!");
drawDebugText("Hello world!", Vec2(8.0));
return false;
}
@ -39,13 +39,13 @@ Here is a breakdown of how this code works:
```d
bool update(float dt) {
drawDebugText("Hello world!");
drawDebugText("Hello world!", Vec2(8.0));
return false;
}
```
This function is the main loop of the game.
It is called every frame while the game is running and, in this example, draws the message "Hello world!".
It is called every frame while the game is running and, in this example, draws the message "Hello world!" at position `Vec2(8.0)`.
The `return false` statement indicates that the game should continue running.
If `true` were returned, the game would stop running.
@ -83,12 +83,12 @@ void drawVec2(Vec2 point, float size, Color color = white);
void drawCirc(Circ area, 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, DrawOptions options = DrawOptions());
void drawTextureArea(Texture texture, Rect area, Vec2 position, 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 drawDebugText(IStr text, Vec2 position = Vec2(8.0f), DrawOptions options = DrawOptions());
void drawRune(Font font, dchar rune, Vec2 position, DrawOptions options = DrawOptions());
void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOptions());
void drawDebugText(IStr text, Vec2 position, DrawOptions options = DrawOptions());
```
Additional drawing functions can be found in other modules, such as `popka.tilemap`.

View file

@ -18,12 +18,12 @@ bool update(float dt) {
// Draw the game world.
auto cameraArea = Rect(camera.position, resolution).area(camera.hook).subAll(3);
camera.attach();
drawDebugText("Move with arrow keys.");
drawDebugText("Move with arrow keys.", Vec2(8.0));
drawRect(cameraArea, Color(50, 50, 40, 130));
camera.detach();
// Draw the game UI.
drawDebugText("I am UI!");
drawDebugText("I am UI!", Vec2(8.0));
drawDebugText("+", resolution * Vec2(0.5));
drawDebugText("+", resolution * Vec2(0.5) + (cameraTarget - camera.position));
return false;

View file

@ -48,9 +48,9 @@ bool update(float dt) {
}
drawRect(player);
if (coins.length == 0) {
drawDebugText("You collected all the coins!");
drawDebugText("You collected all the coins!", Vec2(8.0));
} else {
drawDebugText("Coins: {}/{}\nMove with arrow keys.".format(maxCoinCount - coins.length, maxCoinCount));
drawDebugText("Coins: {}/{}\nMove with arrow keys.".format(maxCoinCount - coins.length, maxCoinCount), Vec2(8.0));
}
return false;
}

View file

@ -63,9 +63,9 @@ bool update(float dt) {
drawDebugText(" | {}".format(choice), choicePosition);
}
} else if (dialogue.canUpdate) {
drawDebugText("{}: {}".format(dialogue.actor, dialogue.text));
drawDebugText("{}: {}".format(dialogue.actor, dialogue.text), Vec2(8.0));
} else {
drawDebugText("The dialogue has ended.");
drawDebugText("The dialogue has ended.", Vec2(8.0));
}
// Draw the game info.

View file

@ -3,13 +3,9 @@ import popka;
// The game variables.
auto atlas = TextureId();
auto frame = 0.0;
auto frameCount = 2;
auto frameSpeed = 8;
auto framePosition = Vec2();
auto frameSize = Vec2(16);
auto frameDirection = 1;
auto frameSlowdown = 0.2;
auto sprite = Sprite(16, 16, 0, 128, 2, 8);
auto spritePosition = Vec2();
auto spriteSlowdown = 0.2;
void ready() {
lockResolution(320, 180);
@ -21,29 +17,25 @@ void ready() {
}
bool update(float dt) {
// Move the frame around in a smooth way and update the current frame.
framePosition = framePosition.moveToWithSlowdown(mouseScreenPosition, Vec2(dt), frameSlowdown);
frame = wrap(frame + dt * frameSpeed, 0, frameCount);
// Move the sprite around in a smooth way.
spritePosition = spritePosition.moveToWithSlowdown(mouseScreenPosition, Vec2(dt), spriteSlowdown);
// Check the mouse move direction and make the sprite look at that direction.
auto mouseDirection = framePosition.directionTo(mouseScreenPosition);
if (framePosition.distanceTo(mouseScreenPosition) < 0.2) {
frame = 0;
} else if (mouseDirection.x < 0) {
frameDirection = -1;
} else if (mouseDirection.x > 0) {
frameDirection = 1;
// Update the frame of the sprite.
auto isWaiting = spritePosition.distanceTo(mouseScreenPosition) < 0.2;
if (isWaiting) {
sprite.reset();
} else {
sprite.update(dt);
}
// The drawing options can change the way something is drawn.
// Set the drawing options for the sprite.
auto options = DrawOptions();
options.hook = Hook.center;
options.scale = Vec2(2);
options.flip = frameDirection == 1 ? Flip.x : Flip.none;
// 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));
options.hook = Hook.center;
options.flip = (spritePosition.directionTo(mouseScreenPosition).x > 0) ? Flip.x : Flip.none;
// Draw the sprite and the mouse position.
drawSprite(atlas, sprite, spritePosition, options);
drawVec2(mouseScreenPosition, 8, isWaiting ? blank : white.alpha(130));
return false;
}

View file

@ -9,7 +9,7 @@ void ready() {
// The update function. This is called every frame while the game is running.
// If true is returned, then the game will stop running.
bool update(float dt) {
drawDebugText("Hello world!");
drawDebugText("Hello world!", Vec2(8.0));
return false;
}

View file

@ -3,21 +3,23 @@ import popka;
// The game variables.
auto atlas = TextureId();
auto map = TileMap();
auto tileMap = TileMap();
void ready() {
lockResolution(320, 180);
setBackgroundColor(toRgb(0x0b0b0b));
// Load the `atlas.png` file from the assets folder.
atlas = loadTexture("atlas.png").unwrap();
// Parse the map CSV file.
map.parse("145,0,65\n21,22,23\n37,38,39\n53,54,55", 16, 16);
// Parse the tile map CSV file.
tileMap.parse("145,0,65\n21,22,23\n37,38,39\n53,54,55", 16, 16);
}
bool update(float dt) {
// Set the drawing options for the tile map.
auto options = DrawOptions();
options.scale = Vec2(2.0f);
drawTileMap(atlas, Vec2(), map, Camera(), options);
// Draw the tile map.
drawTileMap(atlas, tileMap, Vec2(), Camera(), options);
return false;
}

View file

@ -19,7 +19,7 @@ void ready() {
}
bool update(float dt) {
drawDebugText("Hello world!");
drawDebugText("Hello world!", Vec2(8.0));
return false;
}

View file

@ -7,7 +7,6 @@
// ---
// TODO: Test the resources code and the tag thing.
// TODO: Make a sprite struct. Something that should help with animation maybe.
/// The `engine` module functions as a lightweight 2D game engine.
module popka.engine;
@ -1600,7 +1599,7 @@ void drawLine(Line area, float size, Color color = white) {
}
@trusted
void drawTexture(Texture texture, Vec2 position, Rect area, DrawOptions options = DrawOptions()) {
void drawTextureArea(Texture texture, Rect area, Vec2 position, DrawOptions options = DrawOptions()) {
if (texture.isEmpty) {
return;
} else if (area.size.x <= 0.0f || area.size.y <= 0.0f) {
@ -1645,12 +1644,12 @@ void drawTexture(Texture texture, Vec2 position, Rect area, DrawOptions options
}
}
void drawTexture(TextureId texture, Vec2 position, Rect area, DrawOptions options = DrawOptions()) {
drawTexture(texture.getOr(), position, area, options);
void drawTextureArea(TextureId texture, Rect area, Vec2 position, DrawOptions options = DrawOptions()) {
drawTextureArea(texture.getOr(), area, position, options);
}
void drawTexture(Texture texture, Vec2 position, DrawOptions options = DrawOptions()) {
drawTexture(texture, position, Rect(texture.size), options);
drawTextureArea(texture, Rect(texture.size), position, options);
}
void drawTexture(TextureId texture, Vec2 position, DrawOptions options = DrawOptions()) {
@ -1658,7 +1657,7 @@ void drawTexture(TextureId texture, Vec2 position, DrawOptions options = DrawOpt
}
@trusted
void drawRune(Font font, Vec2 position, dchar rune, DrawOptions options = DrawOptions()) {
void drawRune(Font font, dchar rune, Vec2 position, DrawOptions options = DrawOptions()) {
if (font.isEmpty) {
return;
}
@ -1682,12 +1681,12 @@ void drawRune(Font font, Vec2 position, dchar rune, DrawOptions options = DrawOp
rl.rlPopMatrix();
}
void drawRune(FontId font, Vec2 position, dchar rune, DrawOptions options = DrawOptions()) {
drawRune(font.getOr(), position, rune, options);
void drawRune(FontId font, dchar rune, Vec2 position, DrawOptions options = DrawOptions()) {
drawRune(font.getOr(), rune, position, options);
}
@trusted
void drawText(Font font, Vec2 position, IStr text, DrawOptions options = DrawOptions()) {
void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOptions()) {
if (font.isEmpty || text.length == 0) {
return;
}
@ -1722,7 +1721,7 @@ void drawText(Font font, Vec2 position, IStr text, DrawOptions options = DrawOpt
if (codepoint != ' ' && codepoint != '\t') {
auto runeOptions = DrawOptions();
runeOptions.color = options.color;
drawRune(font, Vec2(textOffsetX, textOffsetY), codepoint, runeOptions);
drawRune(font, codepoint, Vec2(textOffsetX, textOffsetY), runeOptions);
}
if (font.data.glyphs[index].advanceX == 0) {
textOffsetX += font.data.recs[index].width + font.runeSpacing;
@ -1736,12 +1735,12 @@ void drawText(Font font, Vec2 position, IStr text, DrawOptions options = DrawOpt
rl.rlPopMatrix();
}
void drawText(FontId font, Vec2 position, IStr text, DrawOptions options = DrawOptions()) {
drawText(font.getOr(), position, text, options);
void drawText(FontId font, IStr text, Vec2 position, DrawOptions options = DrawOptions()) {
drawText(font.getOr(), text, position, options);
}
void drawDebugText(IStr text, Vec2 position = Vec2(8.0f), DrawOptions options = DrawOptions()) {
drawText(engineFont, position, text, options);
void drawDebugText(IStr text, Vec2 position, DrawOptions options = DrawOptions()) {
drawText(engineFont, text, position, options);
}
mixin template runGame(alias readyFunc, alias updateFunc, alias finishFunc, int width = 960, int height = 540, IStr title = "Popka") {

View file

@ -11,5 +11,6 @@ module popka;
public import joka;
public import popka.dialogue;
public import popka.engine;
public import popka.sprite;
public import popka.tilemap;
public import popka.timer;

69
source/popka/sprite.d Normal file
View file

@ -0,0 +1,69 @@
// ---
// Copyright 2024 Alexandros F. G. Kapretsos
// SPDX-License-Identifier: MIT
// Email: alexandroskapretsos@gmail.com
// Project: https://github.com/Kapendev/popka
// Version: v0.0.18
// ---
/// The `sprite` module provides a simple and extensible sprite.
module popka.sprite;
import popka.engine;
@safe @nogc nothrow:
// TODO: Think about gaps in an atlas texture.
struct Sprite {
int width;
int height;
int atlasLeft;
int atlasTop;
int frameCount = 1;
float frameSpeed = 1.0f;
float frameProgress = 0.0f;
@safe @nogc nothrow:
this(int width, int height, int atlasLeft, int atlasTop, int frameCount = 1, float frameSpeed = 1.0f) {
this.width = width;
this.height = height;
this.atlasLeft = atlasLeft;
this.atlasTop = atlasTop;
this.frameCount = frameCount;
this.frameSpeed = frameSpeed;
}
this(int width, int height) {
this(width, height, 0, 0);
}
int frame() {
return cast(int) frameProgress;
}
void reset() {
frameProgress = 0.0f;
}
void update(float dt) {
frameProgress = wrap(frameProgress + frameSpeed * dt, 0.0f, frameCount);
}
}
void drawSprite(Texture texture, Sprite sprite, Vec2 position, DrawOptions options = DrawOptions()) {
auto gridWidth = max(texture.width - sprite.atlasLeft, 0) / sprite.width;
auto gridHeight = max(texture.height - sprite.atlasTop, 0) / sprite.height;
if (gridWidth == 0 || gridHeight == 0) {
return;
}
auto row = sprite.frame / gridWidth;
auto col = sprite.frame % gridWidth;
auto area = Rect(sprite.atlasLeft + col * sprite.width, sprite.atlasTop + row * sprite.height, sprite.width, sprite.height);
drawTextureArea(texture, area, position, options);
}
void drawSprite(TextureId texture, Sprite sprite, Vec2 position, DrawOptions options = DrawOptions()) {
drawSprite(texture.getOr(), sprite, position, options);
}

View file

@ -18,11 +18,26 @@ public import joka.types;
@safe @nogc nothrow:
// TODO: Think about gaps in an atlas texture.
struct Tile {
int id;
int width;
int height;
@safe @nogc nothrow:
this(int id, int width, int height) {
this.id = id;
this.width = width;
this.height = height;
}
}
struct TileMap {
Grid!short data;
int tileWidth;
int tileHeight;
alias data this;
@safe @nogc nothrow:
@ -110,28 +125,28 @@ Result!TileMap loadRawTileMap(IStr path, int tileWidth, int tileHeight) {
return toTileMap(temp.unwrap(), tileWidth, tileHeight);
}
void drawTile(Texture texture, Vec2 position, int tileID, int tileWidth, int tileHeight, DrawOptions options = DrawOptions()) {
auto gridWidth = cast(int) (texture.width / tileWidth);
auto gridHeight = cast(int) (texture.height / tileHeight);
void drawTile(Texture texture, Tile tile, Vec2 position, DrawOptions options = DrawOptions()) {
auto gridWidth = texture.width / tile.width;
auto gridHeight = texture.height / tile.height;
if (gridWidth == 0 || gridHeight == 0) {
return;
}
auto row = tileID / gridWidth;
auto col = tileID % gridWidth;
auto area = Rect(col * tileWidth, row * tileHeight, tileWidth, tileHeight);
drawTexture(texture, position, area, options);
auto row = tile.id / gridWidth;
auto col = tile.id % gridWidth;
auto area = Rect(col * tile.width, row * tile.height, tile.width, tile.height);
drawTextureArea(texture, area, position, options);
}
void drawTile(TextureId texture, Vec2 position, int tileID, int tileWidth, int tileHeight, DrawOptions options = DrawOptions()) {
drawTile(texture.getOr(), position, tileID, tileWidth, tileHeight, options);
void drawTile(TextureId texture, Tile tile, Vec2 position, DrawOptions options = DrawOptions()) {
drawTile(texture.getOr(), tile, position, options);
}
void drawTileMap(Texture texture, Vec2 position, TileMap map, Camera camera, DrawOptions options = DrawOptions()) {
void drawTileMap(Texture texture, TileMap tileMap, Vec2 position, Camera camera, DrawOptions options = DrawOptions()) {
auto cameraArea = Rect(camera.position, resolution).area(camera.hook);
auto topLeft = cameraArea.topLeftPoint;
auto bottomRight = cameraArea.bottomRightPoint;
auto targetTileWidth = cast(int) (map.tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (map.tileHeight * options.scale.y);
auto targetTileWidth = cast(int) (tileMap.tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileMap.tileHeight * options.scale.y);
auto targetTileSize = Vec2(targetTileWidth, targetTileHeight);
auto row1 = 0;
@ -139,15 +154,15 @@ void drawTileMap(Texture texture, Vec2 position, TileMap map, Camera camera, Dra
auto row2 = 0;
auto col2 = 0;
if (camera.isAttached) {
row1 = cast(int) floor(clamp((topLeft.y - position.y) / targetTileHeight, 0, map.rowCount));
col1 = cast(int) floor(clamp((topLeft.x - position.x) / targetTileWidth, 0, map.colCount));
row2 = cast(int) floor(clamp((bottomRight.y - position.y) / targetTileHeight + 1, 0, map.rowCount));
col2 = cast(int) floor(clamp((bottomRight.x - position.x) / targetTileWidth + 1, 0, map.colCount));
row1 = cast(int) floor(clamp((topLeft.y - position.y) / targetTileHeight, 0, tileMap.rowCount));
col1 = cast(int) floor(clamp((topLeft.x - position.x) / targetTileWidth, 0, tileMap.colCount));
row2 = cast(int) floor(clamp((bottomRight.y - position.y) / targetTileHeight + 1, 0, tileMap.rowCount));
col2 = cast(int) floor(clamp((bottomRight.x - position.x) / targetTileWidth + 1, 0, tileMap.colCount));
} else {
row1 = cast(int) 0;
col1 = cast(int) 0;
row2 = cast(int) map.rowCount;
col2 = cast(int) map.colCount;
row2 = cast(int) tileMap.rowCount;
col2 = cast(int) tileMap.colCount;
}
if (row1 == row2 || col1 == col2) {
@ -156,12 +171,14 @@ void drawTileMap(Texture texture, Vec2 position, TileMap map, Camera camera, Dra
foreach (row; row1 .. row2) {
foreach (col; col1 .. col2) {
if (map[row, col] == -1) continue;
drawTile(texture, position + Vec2(col, row) * targetTileSize, map[row, col], map.tileWidth, map.tileHeight, options);
if (tileMap[row, col] == -1) continue;
auto tile = Tile(tileMap[row, col], tileMap.tileWidth, tileMap.tileHeight);
auto tilePosition = position + Vec2(col, row) * targetTileSize;
drawTile(texture, tile, tilePosition, options);
}
}
}
void drawTileMap(TextureId texture, Vec2 position, TileMap map, Camera camera, DrawOptions options = DrawOptions()) {
drawTileMap(texture.getOr(), position, map, camera, options);
void drawTileMap(TextureId texture, TileMap tileMap, Vec2 position, Camera camera, DrawOptions options = DrawOptions()) {
drawTileMap(texture.getOr(), tileMap, position, camera, options);
}

View file

@ -49,7 +49,7 @@ struct Timer {
void stop() {
time = duration;
prevTime = duration;
prevTime = duration - 0.1f;
}
void pause() {