Everything is back to normal now.

This commit is contained in:
Kapendev 2024-10-12 14:28:25 +03:00
parent 2b6dc55a95
commit 7f2b072482
14 changed files with 187 additions and 89 deletions

14
.gitignore vendored
View file

@ -2,13 +2,13 @@
docs.json docs.json
__dummy.html __dummy.html
docs/ docs/
/popka /parin
popka.so parin.so
popka.dylib parin.dylib
popka.dll parin.dll
popka.a parin.a
popka.lib parin.lib
popka-test-* parin-test-*
*.exe *.exe
*.pdb *.pdb
*.o *.o

View file

@ -115,7 +115,7 @@ void updateSound(SoundId sound);
## Drawing ## Drawing
Parin provides a set of drawing functions inside the `parin.engine` module. Parin provides a set of drawing functions inside the `parin.engine` module.
While drawing is not pixel-perfect by default, it can be by calling the `setIsPixelPerfect` function. While drawing is not pixel-perfect by default, it can be by calling the `setIsPixelPerfect` or `setIsPixelSnapped` function.
```d ```d
void drawRect(Rect area, Color color = white); void drawRect(Rect area, Color color = white);

View file

@ -4,7 +4,6 @@ import parin;
// The game variables. // The game variables.
auto player = Rect(16, 16); auto player = Rect(16, 16);
auto playerSpeed = 120; auto playerSpeed = 120;
auto coins = SparseList!Rect(); auto coins = SparseList!Rect();
auto coinSize = Vec2(8); auto coinSize = Vec2(8);
auto maxCoinCount = 8; auto maxCoinCount = 8;

View file

@ -1,5 +1,6 @@
/// This example shows how to use the Parin dialogue system. /// This example shows how to use the Parin dialogue system.
import parin; import parin;
import parin.dialogue;
// The game variables. // The game variables.
auto dialogue = Dialogue(); auto dialogue = Dialogue();

View file

@ -19,38 +19,36 @@ void ready() {
} }
bool update(float dt) { bool update(float dt) {
// Make some options. // Make the drawing options.
auto mapOptions = DrawOptions(Hook.center); auto mapOptions = DrawOptions(Hook.center);
mapOptions.scale = Vec2(2); mapOptions.scale = Vec2(2);
auto tileOptions = mapOptions; auto tileOptions = mapOptions;
tileOptions.flip = tileLookDirection > 0 ? Flip.x : Flip.none; tileOptions.flip = tileLookDirection > 0 ? Flip.x : Flip.none;
// Move tile and camera. // Move the tile and camera.
tile.position += wasd * Vec2(tileSpeed * dt); tile.position += wasd * Vec2(tileSpeed * dt);
camera.followPosition(tile.position, tileSpeed); camera.position = tile.position;
if (wasd.x != 0) tileLookDirection = cast(int) wasd.normalize.round.x; if (wasd.x != 0) tileLookDirection = cast(int) wasd.normalize.round.x;
// Check for collisions. // Check for collisions.
auto colRow1 = map.firstMapPosition(camera.area.topLeftPoint, mapOptions); auto collisionArea = Rect();
auto colRow2 = map.lastMapPosition(camera.area.bottomRightPoint, mapOptions); foreach (gridPosition; map.gridPositions(camera.topLeftPoint, camera.bottomRightPoint, mapOptions)) {
foreach (row; colRow1.y .. colRow2.y) { if (map[gridPosition] == -1) continue;
foreach (col; colRow1.x .. colRow2.x) { auto gridTileArea = Rect(map.worldPosition(gridPosition, mapOptions), Vec2(16) * mapOptions.scale);
if (map[row, col] == -1) continue; while (gridTileArea.hasIntersection(Rect(tile.position, tile.size * mapOptions.scale).area(tileOptions.hook))) {
// TODO: Yeah, maybe change it to something better... tile.position -= wasd * Vec2(dt);
auto mapTileRect = Rect(map.worldPosition(row, col, mapOptions), Vec2(16) * mapOptions.scale); camera.position = tile.position;
auto myTileRect = Rect(tile.position, tile.size * mapOptions.scale).area(Hook.center); collisionArea = gridTileArea;
if (mapTileRect.hasIntersection(myTileRect)) {
tile.position -= wasd * Vec2(tileSpeed * dt);
camera.followPosition(tile.position, tileSpeed);
break;
}
} }
} }
// Draw game. // Draw the game.
camera.attach(); camera.attach();
drawTileMap(atlas, map, camera, mapOptions); drawTileMap(atlas, map, camera, mapOptions);
drawTile(atlas, tile, tileOptions); drawTile(atlas, tile, tileOptions);
drawRect(collisionArea, yellow.alpha(120));
camera.detach(); camera.detach();
drawDebugText("Move with arrow keys.", Vec2(8));
return false; return false;
} }

View file

@ -75,7 +75,6 @@ bool update(float dt) {
drawRect(ball.centerArea); drawRect(ball.centerArea);
drawRect(paddle1.centerArea); drawRect(paddle1.centerArea);
drawRect(paddle2.centerArea); drawRect(paddle2.centerArea);
// Draw the counter. // Draw the counter.
auto textOptions = DrawOptions(Hook.center); auto textOptions = DrawOptions(Hook.center);
textOptions.scale = Vec2(2); textOptions.scale = Vec2(2);

View file

@ -51,7 +51,7 @@ struct Scene2 {
sceneManager.enter!Scene1(); sceneManager.enter!Scene1();
} }
drawDebugText("Press enter to change scene.", resolution * Vec2(0.5), DrawOptions(Hook.center)); drawDebugText("Press space to change scene.", resolution * Vec2(0.5), DrawOptions(Hook.center));
drawDebugText("Scene 2\nNo counter here.", Vec2(8)); drawDebugText("Scene 2\nNo counter here.", Vec2(8));
return false; return false;
} }

View file

@ -40,7 +40,7 @@ struct DialogueUnit {
LStr text; LStr text;
DialogueUnitKind kind; DialogueUnitKind kind;
@safe: @safe @nogc nothrow:
void free() { void free() {
text.free(); text.free();
@ -52,7 +52,7 @@ struct DialogueValue {
LStr name; LStr name;
long value; long value;
@safe: @safe @nogc nothrow:
void free() { void free() {
name.free(); name.free();
@ -60,7 +60,7 @@ struct DialogueValue {
} }
} }
alias DialogueCommandRunner = void function(IStr[] args); alias DialogueCommandRunner = void function(IStr[] args) @trusted;
struct Dialogue { struct Dialogue {
List!DialogueUnit units; List!DialogueUnit units;
@ -69,7 +69,13 @@ struct Dialogue {
IStr actor; IStr actor;
Sz unitIndex; Sz unitIndex;
@safe: @trusted
void run(DialogueCommandRunner runner) {
runner(args);
update();
}
@safe @nogc nothrow:
bool isEmpty() { bool isEmpty() {
return units.length == 0; return units.length == 0;
@ -169,11 +175,6 @@ struct Dialogue {
update(); update();
} }
void run(DialogueCommandRunner runner) {
runner(args);
update();
}
// TODO: Remove the asserts! // TODO: Remove the asserts!
void update() { void update() {
if (units.length != 0 && unitIndex < units.length - 1) { if (units.length != 0 && unitIndex < units.length - 1) {
@ -331,6 +332,8 @@ struct Dialogue {
} }
} }
@safe @nogc nothrow:
bool isValidDialogueUnitKind(char c) { bool isValidDialogueUnitKind(char c) {
foreach (kind; DialogueUnitKindChars) { foreach (kind; DialogueUnitKindChars) {
if (c == kind) { if (c == kind) {

View file

@ -24,7 +24,7 @@ public import joka.faults;
public import joka.math; public import joka.math;
public import joka.types; public import joka.types;
@safe: @safe @nogc nothrow:
EngineState engineState; EngineState engineState;
@ -160,7 +160,7 @@ struct DrawOptions {
Hook hook = Hook.topLeft; /// An value representing the origin point of the drawn object when origin is set to zero. Hook hook = Hook.topLeft; /// An value representing the origin point of the drawn object when origin is set to zero.
Flip flip = Flip.none; /// An value representing flipping orientations. Flip flip = Flip.none; /// An value representing flipping orientations.
@safe: @safe @nogc nothrow:
/// Initializes the options with the given scale. /// Initializes the options with the given scale.
this(Vec2 scale) { this(Vec2 scale) {
@ -196,7 +196,7 @@ struct Camera {
bool isAttached; /// Indicates whether the camera is currently in use. bool isAttached; /// Indicates whether the camera is currently in use.
bool isCentered; /// Determines if the camera's origin is at the center instead of the top left. bool isCentered; /// Determines if the camera's origin is at the center instead of the top left.
@safe: @safe @nogc nothrow:
/// Initializes the camera with the given position and optional centering. /// Initializes the camera with the given position and optional centering.
this(float x, float y, bool isCentered = false) { this(float x, float y, bool isCentered = false) {
@ -210,11 +210,61 @@ struct Camera {
return isCentered ? Hook.center : Hook.topLeft; return isCentered ? Hook.center : Hook.topLeft;
} }
/// Returns the origin of the camera.
Vec2 origin() {
return Rect(position, resolution).origin(hook);
}
/// Returns the area covered by the camera. /// Returns the area covered by the camera.
Rect area() { Rect area() {
return Rect(position, resolution).area(hook); return Rect(position, resolution).area(hook);
} }
/// Returns the top left point of the camera.
Vec2 topLeftPoint() {
return area.topLeftPoint;
}
/// Returns the top point of the camera.
Vec2 topPoint() {
return area.topPoint;
}
/// Returns the top right point of the camera.
Vec2 topRightPoint() {
return area.topRightPoint;
}
/// Returns the left point of the camera.
Vec2 leftPoint() {
return area.leftPoint;
}
/// Returns the center point of the camera.
Vec2 centerPoint() {
return area.centerPoint;
}
/// Returns the right point of the camera.
Vec2 rightPoint() {
return area.rightPoint;
}
/// Returns the bottom left point of the camera.
Vec2 bottomLeftPoint() {
return area.bottomLeftPoint;
}
/// Returns the bottom point of the camera.
Vec2 bottomPoint() {
return area.bottomPoint;
}
/// Returns the bottom right point of the camera.
Vec2 bottomRightPoint() {
return area.bottomRightPoint;
}
/// Moves the camera to follow the target position at the specified speed. /// Moves the camera to follow the target position at the specified speed.
void followPosition(Vec2 target, float speed) { void followPosition(Vec2 target, float speed) {
position = position.moveTo(target, Vec2(speed)); position = position.moveTo(target, Vec2(speed));
@ -243,7 +293,7 @@ struct Camera {
} }
isAttached = true; isAttached = true;
auto temp = this.toRl(); auto temp = this.toRl();
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
temp.target.x = floor(temp.target.x); temp.target.x = floor(temp.target.x);
temp.target.y = floor(temp.target.y); temp.target.y = floor(temp.target.y);
temp.offset.x = floor(temp.offset.x); temp.offset.x = floor(temp.offset.x);
@ -267,7 +317,7 @@ struct TextId {
GenerationalIndex data; GenerationalIndex data;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
/// Returns the length of the text associated with the resource identifier. /// Returns the length of the text associated with the resource identifier.
Sz length() { Sz length() {
@ -304,7 +354,7 @@ struct TextId {
struct Texture { struct Texture {
rl.Texture2D data; rl.Texture2D data;
@safe: @safe @nogc nothrow:
/// Checks if the texture is not loaded. /// Checks if the texture is not loaded.
bool isEmpty() { bool isEmpty() {
@ -349,7 +399,7 @@ struct TextureId {
GenerationalIndex data; GenerationalIndex data;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
/// Returns the width of the texture associated with the resource identifier. /// Returns the width of the texture associated with the resource identifier.
int width() { int width() {
@ -398,7 +448,7 @@ struct Font {
int runeSpacing; /// The spacing between individual characters. int runeSpacing; /// The spacing between individual characters.
int lineSpacing; /// The spacing between lines of text. int lineSpacing; /// The spacing between lines of text.
@safe: @safe @nogc nothrow:
/// Checks if the font is not loaded. /// Checks if the font is not loaded.
bool isEmpty() { bool isEmpty() {
@ -433,7 +483,7 @@ struct FontId {
GenerationalIndex data; GenerationalIndex data;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
/// Returns the spacing between individual characters of the font associated with the resource identifier. /// Returns the spacing between individual characters of the font associated with the resource identifier.
int runeSpacing() { int runeSpacing() {
@ -480,7 +530,7 @@ struct FontId {
struct Sound { struct Sound {
Variant!(rl.Sound, rl.Music) data; Variant!(rl.Sound, rl.Music) data;
@safe: @safe @nogc nothrow:
/// Checks if the sound is not loaded. /// Checks if the sound is not loaded.
bool isEmpty() { bool isEmpty() {
@ -575,7 +625,7 @@ struct SoundId {
GenerationalIndex data; GenerationalIndex data;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
/// Returns the current playback time of the sound associated with the resource identifier. /// Returns the current playback time of the sound associated with the resource identifier.
float time() { float time() {
@ -621,7 +671,7 @@ struct SoundId {
struct Viewport { struct Viewport {
rl.RenderTexture2D data; rl.RenderTexture2D data;
@safe: @safe @nogc nothrow:
/// Checks if the viewport is not loaded. /// Checks if the viewport is not loaded.
bool isEmpty() { bool isEmpty() {
@ -663,6 +713,7 @@ struct Viewport {
struct EngineFlags { struct EngineFlags {
bool isUpdating; bool isUpdating;
bool isPixelSnapped;
bool isPixelPerfect; bool isPixelPerfect;
bool isCursorVisible; bool isCursorVisible;
} }
@ -678,7 +729,7 @@ struct EngineResourceGroup(T) {
GenerationalList!LStr names; GenerationalList!LStr names;
GenerationalList!Sz tags; GenerationalList!Sz tags;
@safe: @safe @nogc nothrow:
Sz length() { Sz length() {
return data.length; return data.length;
@ -741,7 +792,7 @@ struct EngineResources {
EngineResourceGroup!Font fonts; EngineResourceGroup!Font fonts;
EngineResourceGroup!Sound sounds; EngineResourceGroup!Sound sounds;
@safe: @safe @nogc nothrow:
void free(Sz tag = 0) { void free(Sz tag = 0) {
texts.free(tag); texts.free(tag);
@ -757,7 +808,7 @@ struct EngineViewport {
int targetHeight; int targetHeight;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
bool isLocking() { bool isLocking() {
return (targetWidth != 0 && targetHeight != 0) && (data.width != targetWidth && data.height != targetHeight); return (targetWidth != 0 && targetHeight != 0) && (data.width != targetWidth && data.height != targetHeight);
@ -792,7 +843,7 @@ struct EngineState {
Filter defaultFilter; Filter defaultFilter;
Sz tickCount; Sz tickCount;
@safe: @safe @nogc nothrow:
void free() { void free() {
debug { debug {
@ -1170,8 +1221,9 @@ void openWindow(int width, int height, IStr appPath, IStr title = "Parin") {
/// You should avoid calling this function manually. /// You should avoid calling this function manually.
@trusted @trusted
void updateWindow(bool function(float dt) updateFunc) { void updateWindow(bool function(float dt) updateFunc) {
static bool function(float _dt) @trusted _updateFunc; static bool function(float _dt) @trusted @nogc nothrow _updateFunc;
@trusted @nogc nothrow
static bool _updateWindow() { static bool _updateWindow() {
// Begin drawing. // Begin drawing.
if (isResolutionLocked) { if (isResolutionLocked) {
@ -1193,10 +1245,9 @@ void updateWindow(bool function(float dt) updateFunc) {
auto ratio = maxSize / minSize; auto ratio = maxSize / minSize;
auto minRatio = min(ratio.x, ratio.y); auto minRatio = min(ratio.x, ratio.y);
if (isPixelPerfect) { if (isPixelPerfect) {
// TODO: Make an equals function in Joka that can change the epsilon value.
auto roundMinRatio = round(minRatio); auto roundMinRatio = round(minRatio);
auto floorMinRation = floor(minRatio); auto floorMinRation = floor(minRatio);
minRatio = (abs(minRatio - roundMinRatio) < 0.015f) ? roundMinRatio : floorMinRation; minRatio = minRatio.equals(roundMinRatio, 0.015f) ? roundMinRatio : floorMinRation;
} }
auto targetSize = minSize * Vec2(minRatio); auto targetSize = minSize * Vec2(minRatio);
@ -1253,7 +1304,7 @@ void updateWindow(bool function(float dt) updateFunc) {
} }
// Maybe bad idea, but makes life of no-attribute people easier. // Maybe bad idea, but makes life of no-attribute people easier.
_updateFunc = cast(bool function(float _dt) @trusted) updateFunc; _updateFunc = cast(bool function(float _dt) @trusted @nogc nothrow) updateFunc;
engineState.flags.isUpdating = true; engineState.flags.isUpdating = true;
version(WebAssembly) { version(WebAssembly) {
@ -1284,6 +1335,21 @@ void closeWindow() {
rl.CloseWindow(); rl.CloseWindow();
} }
/// Returns true if the drawing is snapped to pixel coordinates.
bool isPixelSnapped() {
return engineState.flags.isPixelSnapped;
}
/// Sets whether drawing should be snapped to pixel coordinates.
void setIsPixelSnapped(bool value) {
engineState.flags.isPixelSnapped = value;
}
/// Toggles whether drawing is snapped to pixel coordinates on or off.
void toggleIsPixelSnapped() {
setIsPixelSnapped(!isPixelSnapped);
}
/// Returns true if the drawing is done in a pixel perfect way. /// Returns true if the drawing is done in a pixel perfect way.
bool isPixelPerfect() { bool isPixelPerfect() {
return engineState.flags.isPixelPerfect; return engineState.flags.isPixelPerfect;
@ -1490,10 +1556,9 @@ Vec2 mouseScreenPosition() {
auto window = windowSize; auto window = windowSize;
auto minRatio = min(window.x / engineState.viewport.width, window.y / engineState.viewport.height); auto minRatio = min(window.x / engineState.viewport.width, window.y / engineState.viewport.height);
if (isPixelPerfect) { if (isPixelPerfect) {
// TODO: Make an equals function in Joka that can change the epsilon value.
auto roundMinRatio = round(minRatio); auto roundMinRatio = round(minRatio);
auto floorMinRation = floor(minRatio); auto floorMinRation = floor(minRatio);
minRatio = (abs(minRatio - roundMinRatio) < 0.015f) ? roundMinRatio : floorMinRation; minRatio = minRatio.equals(roundMinRatio, 0.015f) ? roundMinRatio : floorMinRation;
} }
auto targetSize = engineState.viewport.size * Vec2(minRatio); auto targetSize = engineState.viewport.size * Vec2(minRatio);
// We use touch because it works on desktop, web and mobile. // We use touch because it works on desktop, web and mobile.
@ -1781,7 +1846,7 @@ void updateSound(SoundId sound) {
/// Draws a rectangle with the specified area and color. /// Draws a rectangle with the specified area and color.
@trusted @trusted
void drawRect(Rect area, Color color = white) { void drawRect(Rect area, Color color = white) {
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.DrawRectanglePro(area.floor().toRl(), rl.Vector2(0.0f, 0.0f), 0.0f, color.toRl()); rl.DrawRectanglePro(area.floor().toRl(), rl.Vector2(0.0f, 0.0f), 0.0f, color.toRl());
} else { } else {
rl.DrawRectanglePro(area.toRl(), rl.Vector2(0.0f, 0.0f), 0.0f, color.toRl()); rl.DrawRectanglePro(area.toRl(), rl.Vector2(0.0f, 0.0f), 0.0f, color.toRl());
@ -1796,7 +1861,7 @@ void drawVec2(Vec2 point, float size, Color color = white) {
/// Draws a circle with the specified area and color. /// Draws a circle with the specified area and color.
@trusted @trusted
void drawCirc(Circ area, Color color = white) { void drawCirc(Circ area, Color color = white) {
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.DrawCircleV(area.position.floor().toRl(), area.radius, color.toRl()); rl.DrawCircleV(area.position.floor().toRl(), area.radius, color.toRl());
} else { } else {
rl.DrawCircleV(area.position.toRl(), area.radius, color.toRl()); rl.DrawCircleV(area.position.toRl(), area.radius, color.toRl());
@ -1806,7 +1871,7 @@ void drawCirc(Circ area, Color color = white) {
/// Draws a line with the specified area, thickness, and color. /// Draws a line with the specified area, thickness, and color.
@trusted @trusted
void drawLine(Line area, float size, Color color = white) { void drawLine(Line area, float size, Color color = white) {
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.DrawLineEx(area.a.floor().toRl(), area.b.floor().toRl(), size, color.toRl()); rl.DrawLineEx(area.a.floor().toRl(), area.b.floor().toRl(), size, color.toRl());
} else { } else {
rl.DrawLineEx(area.a.toRl(), area.b.toRl(), size, color.toRl()); rl.DrawLineEx(area.a.toRl(), area.b.toRl(), size, color.toRl());
@ -1839,7 +1904,7 @@ void drawTextureArea(Texture texture, Rect area, Vec2 position, DrawOptions opti
} }
auto origin = options.origin == Vec2() ? target.origin(options.hook) : options.origin; auto origin = options.origin == Vec2() ? target.origin(options.hook) : options.origin;
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.DrawTexturePro( rl.DrawTexturePro(
texture.data, texture.data,
area.floor().toRl(), area.floor().toRl(),
@ -1885,14 +1950,14 @@ void drawRune(Font font, dchar rune, Vec2 position, DrawOptions options = DrawOp
auto rect = toParin(rl.GetGlyphAtlasRec(font.data, rune)); auto rect = toParin(rl.GetGlyphAtlasRec(font.data, rune));
auto origin = options.origin == Vec2() ? rect.origin(options.hook) : options.origin; auto origin = options.origin == Vec2() ? rect.origin(options.hook) : options.origin;
rl.rlPushMatrix(); rl.rlPushMatrix();
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(position.x.floor(), position.y.floor(), 0.0f); rl.rlTranslatef(position.x.floor(), position.y.floor(), 0.0f);
} else { } else {
rl.rlTranslatef(position.x, position.y, 0.0f); rl.rlTranslatef(position.x, position.y, 0.0f);
} }
rl.rlRotatef(options.rotation, 0.0f, 0.0f, 1.0f); rl.rlRotatef(options.rotation, 0.0f, 0.0f, 1.0f);
rl.rlScalef(options.scale.x, options.scale.y, 1.0f); rl.rlScalef(options.scale.x, options.scale.y, 1.0f);
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(-origin.x.floor(), -origin.y.floor(), 0.0f); rl.rlTranslatef(-origin.x.floor(), -origin.y.floor(), 0.0f);
} else { } else {
rl.rlTranslatef(-origin.x, -origin.y, 0.0f); rl.rlTranslatef(-origin.x, -origin.y, 0.0f);
@ -1916,14 +1981,14 @@ void drawText(Font font, IStr text, Vec2 position, DrawOptions options = DrawOpt
// TODO: Make it work with negative scale values. // TODO: Make it work with negative scale values.
auto origin = Rect(measureTextSize(font, text)).origin(options.hook); auto origin = Rect(measureTextSize(font, text)).origin(options.hook);
rl.rlPushMatrix(); rl.rlPushMatrix();
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(floor(position.x), floor(position.y), 0.0f); rl.rlTranslatef(floor(position.x), floor(position.y), 0.0f);
} else { } else {
rl.rlTranslatef(position.x, position.y, 0.0f); rl.rlTranslatef(position.x, position.y, 0.0f);
} }
rl.rlRotatef(options.rotation, 0.0f, 0.0f, 1.0f); rl.rlRotatef(options.rotation, 0.0f, 0.0f, 1.0f);
rl.rlScalef(options.scale.x, options.scale.y, 1.0f); rl.rlScalef(options.scale.x, options.scale.y, 1.0f);
if (isPixelPerfect) { if (isPixelSnapped || isPixelPerfect) {
rl.rlTranslatef(floor(-origin.x), floor(-origin.y), 0.0f); rl.rlTranslatef(floor(-origin.x), floor(-origin.y), 0.0f);
} else { } else {
rl.rlTranslatef(-origin.x, -origin.y, 0.0f); rl.rlTranslatef(-origin.x, -origin.y, 0.0f);

View file

@ -10,7 +10,6 @@ module parin;
public import joka.ascii; public import joka.ascii;
public import joka.io; public import joka.io;
public import parin.dialogue;
public import parin.engine; public import parin.engine;
public import parin.scene; public import parin.scene;
public import parin.sprite; public import parin.sprite;

View file

@ -1258,7 +1258,6 @@ void OpenURL (const(char)* url); // Open URL with default system browser (if ava
// NOTE: Following functions implemented in module [utils] // NOTE: Following functions implemented in module [utils]
//------------------------------------------------------------------ //------------------------------------------------------------------
void TraceLog (int logLevel, const(char)* text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...)
void SetTraceLogLevel (int logLevel); // Set the current threshold (minimum) log level void SetTraceLogLevel (int logLevel); // Set the current threshold (minimum) log level
void* MemAlloc (uint size); // Internal memory allocator void* MemAlloc (uint size); // Internal memory allocator
void* MemRealloc (void* ptr, uint size); // Internal memory reallocator void* MemRealloc (void* ptr, uint size); // Internal memory reallocator
@ -1641,7 +1640,6 @@ const(char)* CodepointToUTF8 (int codepoint, int* utf8Size); // Encode one codep
int TextCopy (char* dst, const(char)* src); // Copy one string to another, returns bytes copied int TextCopy (char* dst, const(char)* src); // Copy one string to another, returns bytes copied
bool TextIsEqual (const(char)* text1, const(char)* text2); // Check if two text string are equal bool TextIsEqual (const(char)* text1, const(char)* text2); // Check if two text string are equal
uint TextLength (const(char)* text); // Get text length, checks for '\0' ending uint TextLength (const(char)* text); // Get text length, checks for '\0' ending
const(char)* TextFormat (const(char)* text, ...); // Text formatting with variables (sprintf() style)
const(char)* TextSubtext (const(char)* text, int position, int length); // Get a piece of a text string const(char)* TextSubtext (const(char)* text, int position, int length); // Get a piece of a text string
char* TextReplace (char* text, const(char)* replace, const(char)* by); // Replace text string (WARNING: memory must be freed!) char* TextReplace (char* text, const(char)* replace, const(char)* by); // Replace text string (WARNING: memory must be freed!)
char* TextInsert (const(char)* text, const(char)* insert, int position); // Insert text in a position (WARNING: memory must be freed!) char* TextInsert (const(char)* text, const(char)* insert, int position); // Insert text in a position (WARNING: memory must be freed!)

View file

@ -14,7 +14,7 @@ module parin.sprite;
import parin.engine; import parin.engine;
@safe: @safe @nogc nothrow:
struct SpriteAnimation { struct SpriteAnimation {
ubyte frameRow; ubyte frameRow;
@ -28,7 +28,7 @@ struct SpriteAnimationGroup2 {
ubyte frameSpeed = 6; ubyte frameSpeed = 6;
enum angleStep = 180.0f; enum angleStep = 180.0f;
@safe: @safe @nogc nothrow:
SpriteAnimation pick(float angle) { SpriteAnimation pick(float angle) {
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length; auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
@ -42,7 +42,7 @@ struct SpriteAnimationGroup4 {
ubyte frameSpeed = 6; ubyte frameSpeed = 6;
enum angleStep = 90.0f; enum angleStep = 90.0f;
@safe: @safe @nogc nothrow:
SpriteAnimation pick(float angle) { SpriteAnimation pick(float angle) {
// NOTE: This is a hack to make things look better in simple cases. // NOTE: This is a hack to make things look better in simple cases.
@ -61,7 +61,7 @@ struct SpriteAnimationGroup8 {
ubyte frameSpeed = 6; ubyte frameSpeed = 6;
enum angleStep = 45.0f; enum angleStep = 45.0f;
@safe: @safe @nogc nothrow:
SpriteAnimation pick(float angle) { SpriteAnimation pick(float angle) {
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length; auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
@ -75,7 +75,7 @@ struct SpriteAnimationGroup16 {
ubyte frameSpeed = 6; ubyte frameSpeed = 6;
enum angleStep = 22.5f; enum angleStep = 22.5f;
@safe: @safe @nogc nothrow:
SpriteAnimation pick(float angle) { SpriteAnimation pick(float angle) {
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length; auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
@ -92,7 +92,7 @@ struct Sprite {
SpriteAnimation animation; SpriteAnimation animation;
Vec2 position; Vec2 position;
@safe: @safe @nogc nothrow:
this(int width, int height, ushort atlasLeft, ushort atlasTop, SpriteAnimation animation = SpriteAnimation()) { this(int width, int height, ushort atlasLeft, ushort atlasTop, SpriteAnimation animation = SpriteAnimation()) {
this.width = width; this.width = width;

View file

@ -21,7 +21,7 @@ public import joka.faults;
public import joka.math; public import joka.math;
public import joka.types; public import joka.types;
@safe: @safe @nogc nothrow:
struct Tile { struct Tile {
Sz id; Sz id;
@ -29,7 +29,7 @@ struct Tile {
int height; int height;
Vec2 position; Vec2 position;
@safe: @safe @nogc nothrow:
Vec2 size() { Vec2 size() {
return Vec2(width, height); return Vec2(width, height);
@ -67,13 +67,13 @@ struct TileMap {
Vec2 position; Vec2 position;
alias data this; alias data this;
@safe: @safe @nogc nothrow:
this(short value, int tileWidth, int tileHeight) { this(short value, int tileWidth, int tileHeight) {
this.tileWidth = tileWidth; this.tileWidth = tileWidth;
this.tileHeight = tileHeight; this.tileHeight = tileHeight;
this.estimatedMaxRowCount = data.maxRowCount; this.estimatedMaxRowCount = maxRowCount;
this.estimatedMaxColCount = data.maxColCount; this.estimatedMaxColCount = maxColCount;
this.data.fill(value); this.data.fill(value);
} }
@ -134,7 +134,7 @@ struct TileMap {
position = position.moveToWithSlowdown(target, Vec2(deltaTime), slowdown); position = position.moveToWithSlowdown(target, Vec2(deltaTime), slowdown);
} }
/// Returns the top left world position of a tile. /// Returns the top left world position of a grid position.
Vec2 worldPosition(Sz row, Sz col, DrawOptions options = DrawOptions()) { Vec2 worldPosition(Sz row, Sz col, DrawOptions options = DrawOptions()) {
auto targetTileWidth = cast(int) (tileWidth * options.scale.x); auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileHeight * options.scale.y); auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
@ -147,7 +147,12 @@ struct TileMap {
return temp.area(options.hook).position; return temp.area(options.hook).position;
} }
IVec2 firstMapPosition(Vec2 topLeftWorldPosition, DrawOptions options = DrawOptions()) { /// Returns the top left world position of a grid position.
Vec2 worldPosition(IVec2 gridPosition, DrawOptions options = DrawOptions()) {
return worldPosition(gridPosition.y, gridPosition.x, options);
}
IVec2 firstGridPosition(Vec2 topLeftWorldPosition, DrawOptions options = DrawOptions()) {
auto result = IVec2(); auto result = IVec2();
auto targetTileWidth = cast(int) (tileWidth * options.scale.x); auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileHeight * options.scale.y); auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
@ -156,7 +161,7 @@ struct TileMap {
return result; return result;
} }
IVec2 lastMapPosition(Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) { IVec2 lastGridPosition(Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) {
auto result = IVec2(); auto result = IVec2();
auto targetTileWidth = cast(int) (tileWidth * options.scale.x); auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileHeight * options.scale.y); auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
@ -165,6 +170,37 @@ struct TileMap {
result.x = cast(int) floor(clamp((bottomRightWorldPosition.x - position.x) / targetTileWidth + extraTileCount, 0, estimatedMaxColCount)); result.x = cast(int) floor(clamp((bottomRightWorldPosition.x - position.x) / targetTileWidth + extraTileCount, 0, estimatedMaxColCount));
return result; return result;
} }
auto gridPositions(Vec2 topLeftWorldPosition, Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) {
static struct Range {
IVec2 first;
IVec2 last;
IVec2 position;
bool empty() {
return position == last;
}
IVec2 front() {
return position;
}
void popFront() {
position.x += 1;
if (position.x >= TileMap.maxColCount) {
position.x = first.x;
position.y += 1;
}
}
}
auto result = Range(
firstGridPosition(topLeftWorldPosition, options),
lastGridPosition(bottomRightWorldPosition, options),
);
result.position = result.first;
return result;
}
} }
Result!TileMap toTileMap(IStr csv, int tileWidth, int tileHeight) { Result!TileMap toTileMap(IStr csv, int tileWidth, int tileHeight) {
@ -185,6 +221,7 @@ Result!TileMap loadRawTileMap(IStr path, int tileWidth, int tileHeight) {
} }
void drawTile(Texture texture, Tile tile, DrawOptions options = DrawOptions()) { void drawTile(Texture texture, Tile tile, DrawOptions options = DrawOptions()) {
if (texture.isEmpty) return;
drawTextureArea(texture, tile.textureArea(texture.width / tile.width), tile.position, options); drawTextureArea(texture, tile.textureArea(texture.width / tile.width), tile.position, options);
} }
@ -195,9 +232,8 @@ void drawTile(TextureId texture, Tile tile, DrawOptions options = DrawOptions())
void drawTileMap(Texture texture, TileMap map, Camera camera, DrawOptions options = DrawOptions()) { void drawTileMap(Texture texture, TileMap map, Camera camera, DrawOptions options = DrawOptions()) {
auto targetTileWidth = cast(int) (map.tileWidth * options.scale.x); auto targetTileWidth = cast(int) (map.tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (map.tileHeight * options.scale.y); auto targetTileHeight = cast(int) (map.tileHeight * options.scale.y);
auto cameraArea = camera.area; auto colRow1 = map.firstGridPosition(camera.topLeftPoint, options);
auto colRow1 = map.firstMapPosition(cameraArea.topLeftPoint, options); auto colRow2 = map.lastGridPosition(camera.bottomRightPoint, options);
auto colRow2 = map.lastMapPosition(cameraArea.bottomRightPoint, options);
if (colRow1.x == colRow2.x || colRow1.y == colRow2.y) return; if (colRow1.x == colRow2.x || colRow1.y == colRow2.y) return;
foreach (row; colRow1.y .. colRow2.y) { foreach (row; colRow1.y .. colRow2.y) {

View file

@ -13,7 +13,7 @@ module parin.timer;
import joka.math; import joka.math;
@safe: @safe @nogc nothrow:
struct Timer { struct Timer {
float time = 1.0f; float time = 1.0f;
@ -22,7 +22,7 @@ struct Timer {
bool isPaused; bool isPaused;
bool canRepeat; bool canRepeat;
@safe: @safe @nogc nothrow:
this(float duration, bool canRepeat = false) { this(float duration, bool canRepeat = false) {
this.time = duration; this.time = duration;