mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-25 12:39:54 +03:00
Everything is back to normal now.
This commit is contained in:
parent
2b6dc55a95
commit
7f2b072482
14 changed files with 187 additions and 89 deletions
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -2,13 +2,13 @@
|
|||
docs.json
|
||||
__dummy.html
|
||||
docs/
|
||||
/popka
|
||||
popka.so
|
||||
popka.dylib
|
||||
popka.dll
|
||||
popka.a
|
||||
popka.lib
|
||||
popka-test-*
|
||||
/parin
|
||||
parin.so
|
||||
parin.dylib
|
||||
parin.dll
|
||||
parin.a
|
||||
parin.lib
|
||||
parin-test-*
|
||||
*.exe
|
||||
*.pdb
|
||||
*.o
|
||||
|
|
2
TOUR.md
2
TOUR.md
|
@ -115,7 +115,7 @@ void updateSound(SoundId sound);
|
|||
## Drawing
|
||||
|
||||
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
|
||||
void drawRect(Rect area, Color color = white);
|
||||
|
|
|
@ -4,7 +4,6 @@ import parin;
|
|||
// The game variables.
|
||||
auto player = Rect(16, 16);
|
||||
auto playerSpeed = 120;
|
||||
|
||||
auto coins = SparseList!Rect();
|
||||
auto coinSize = Vec2(8);
|
||||
auto maxCoinCount = 8;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/// This example shows how to use the Parin dialogue system.
|
||||
import parin;
|
||||
import parin.dialogue;
|
||||
|
||||
// The game variables.
|
||||
auto dialogue = Dialogue();
|
||||
|
|
|
@ -19,38 +19,36 @@ void ready() {
|
|||
}
|
||||
|
||||
bool update(float dt) {
|
||||
// Make some options.
|
||||
// Make the drawing options.
|
||||
auto mapOptions = DrawOptions(Hook.center);
|
||||
mapOptions.scale = Vec2(2);
|
||||
auto tileOptions = mapOptions;
|
||||
tileOptions.flip = tileLookDirection > 0 ? Flip.x : Flip.none;
|
||||
|
||||
// Move tile and camera.
|
||||
// Move the tile and camera.
|
||||
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;
|
||||
|
||||
// Check for collisions.
|
||||
auto colRow1 = map.firstMapPosition(camera.area.topLeftPoint, mapOptions);
|
||||
auto colRow2 = map.lastMapPosition(camera.area.bottomRightPoint, mapOptions);
|
||||
foreach (row; colRow1.y .. colRow2.y) {
|
||||
foreach (col; colRow1.x .. colRow2.x) {
|
||||
if (map[row, col] == -1) continue;
|
||||
// TODO: Yeah, maybe change it to something better...
|
||||
auto mapTileRect = Rect(map.worldPosition(row, col, mapOptions), Vec2(16) * mapOptions.scale);
|
||||
auto myTileRect = Rect(tile.position, tile.size * mapOptions.scale).area(Hook.center);
|
||||
if (mapTileRect.hasIntersection(myTileRect)) {
|
||||
tile.position -= wasd * Vec2(tileSpeed * dt);
|
||||
camera.followPosition(tile.position, tileSpeed);
|
||||
break;
|
||||
}
|
||||
auto collisionArea = Rect();
|
||||
foreach (gridPosition; map.gridPositions(camera.topLeftPoint, camera.bottomRightPoint, mapOptions)) {
|
||||
if (map[gridPosition] == -1) continue;
|
||||
auto gridTileArea = Rect(map.worldPosition(gridPosition, mapOptions), Vec2(16) * mapOptions.scale);
|
||||
while (gridTileArea.hasIntersection(Rect(tile.position, tile.size * mapOptions.scale).area(tileOptions.hook))) {
|
||||
tile.position -= wasd * Vec2(dt);
|
||||
camera.position = tile.position;
|
||||
collisionArea = gridTileArea;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw game.
|
||||
// Draw the game.
|
||||
camera.attach();
|
||||
drawTileMap(atlas, map, camera, mapOptions);
|
||||
drawTile(atlas, tile, tileOptions);
|
||||
drawRect(collisionArea, yellow.alpha(120));
|
||||
camera.detach();
|
||||
drawDebugText("Move with arrow keys.", Vec2(8));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,6 @@ bool update(float dt) {
|
|||
drawRect(ball.centerArea);
|
||||
drawRect(paddle1.centerArea);
|
||||
drawRect(paddle2.centerArea);
|
||||
|
||||
// Draw the counter.
|
||||
auto textOptions = DrawOptions(Hook.center);
|
||||
textOptions.scale = Vec2(2);
|
||||
|
|
|
@ -51,7 +51,7 @@ struct Scene2 {
|
|||
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));
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ struct DialogueUnit {
|
|||
LStr text;
|
||||
DialogueUnitKind kind;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
void free() {
|
||||
text.free();
|
||||
|
@ -52,7 +52,7 @@ struct DialogueValue {
|
|||
LStr name;
|
||||
long value;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
void free() {
|
||||
name.free();
|
||||
|
@ -60,7 +60,7 @@ struct DialogueValue {
|
|||
}
|
||||
}
|
||||
|
||||
alias DialogueCommandRunner = void function(IStr[] args);
|
||||
alias DialogueCommandRunner = void function(IStr[] args) @trusted;
|
||||
|
||||
struct Dialogue {
|
||||
List!DialogueUnit units;
|
||||
|
@ -69,7 +69,13 @@ struct Dialogue {
|
|||
IStr actor;
|
||||
Sz unitIndex;
|
||||
|
||||
@safe:
|
||||
@trusted
|
||||
void run(DialogueCommandRunner runner) {
|
||||
runner(args);
|
||||
update();
|
||||
}
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isEmpty() {
|
||||
return units.length == 0;
|
||||
|
@ -169,11 +175,6 @@ struct Dialogue {
|
|||
update();
|
||||
}
|
||||
|
||||
void run(DialogueCommandRunner runner) {
|
||||
runner(args);
|
||||
update();
|
||||
}
|
||||
|
||||
// TODO: Remove the asserts!
|
||||
void update() {
|
||||
if (units.length != 0 && unitIndex < units.length - 1) {
|
||||
|
@ -331,6 +332,8 @@ struct Dialogue {
|
|||
}
|
||||
}
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isValidDialogueUnitKind(char c) {
|
||||
foreach (kind; DialogueUnitKindChars) {
|
||||
if (c == kind) {
|
||||
|
|
|
@ -24,7 +24,7 @@ public import joka.faults;
|
|||
public import joka.math;
|
||||
public import joka.types;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
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.
|
||||
Flip flip = Flip.none; /// An value representing flipping orientations.
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Initializes the options with the given scale.
|
||||
this(Vec2 scale) {
|
||||
|
@ -196,7 +196,7 @@ struct Camera {
|
|||
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.
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Initializes the camera with the given position and optional centering.
|
||||
this(float x, float y, bool isCentered = false) {
|
||||
|
@ -210,11 +210,61 @@ struct Camera {
|
|||
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.
|
||||
Rect area() {
|
||||
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.
|
||||
void followPosition(Vec2 target, float speed) {
|
||||
position = position.moveTo(target, Vec2(speed));
|
||||
|
@ -243,7 +293,7 @@ struct Camera {
|
|||
}
|
||||
isAttached = true;
|
||||
auto temp = this.toRl();
|
||||
if (isPixelPerfect) {
|
||||
if (isPixelSnapped || isPixelPerfect) {
|
||||
temp.target.x = floor(temp.target.x);
|
||||
temp.target.y = floor(temp.target.y);
|
||||
temp.offset.x = floor(temp.offset.x);
|
||||
|
@ -267,7 +317,7 @@ struct TextId {
|
|||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Returns the length of the text associated with the resource identifier.
|
||||
Sz length() {
|
||||
|
@ -304,7 +354,7 @@ struct TextId {
|
|||
struct Texture {
|
||||
rl.Texture2D data;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Checks if the texture is not loaded.
|
||||
bool isEmpty() {
|
||||
|
@ -349,7 +399,7 @@ struct TextureId {
|
|||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Returns the width of the texture associated with the resource identifier.
|
||||
int width() {
|
||||
|
@ -398,7 +448,7 @@ struct Font {
|
|||
int runeSpacing; /// The spacing between individual characters.
|
||||
int lineSpacing; /// The spacing between lines of text.
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Checks if the font is not loaded.
|
||||
bool isEmpty() {
|
||||
|
@ -433,7 +483,7 @@ struct FontId {
|
|||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Returns the spacing between individual characters of the font associated with the resource identifier.
|
||||
int runeSpacing() {
|
||||
|
@ -480,7 +530,7 @@ struct FontId {
|
|||
struct Sound {
|
||||
Variant!(rl.Sound, rl.Music) data;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Checks if the sound is not loaded.
|
||||
bool isEmpty() {
|
||||
|
@ -575,7 +625,7 @@ struct SoundId {
|
|||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Returns the current playback time of the sound associated with the resource identifier.
|
||||
float time() {
|
||||
|
@ -621,7 +671,7 @@ struct SoundId {
|
|||
struct Viewport {
|
||||
rl.RenderTexture2D data;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
/// Checks if the viewport is not loaded.
|
||||
bool isEmpty() {
|
||||
|
@ -663,6 +713,7 @@ struct Viewport {
|
|||
|
||||
struct EngineFlags {
|
||||
bool isUpdating;
|
||||
bool isPixelSnapped;
|
||||
bool isPixelPerfect;
|
||||
bool isCursorVisible;
|
||||
}
|
||||
|
@ -678,7 +729,7 @@ struct EngineResourceGroup(T) {
|
|||
GenerationalList!LStr names;
|
||||
GenerationalList!Sz tags;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
Sz length() {
|
||||
return data.length;
|
||||
|
@ -741,7 +792,7 @@ struct EngineResources {
|
|||
EngineResourceGroup!Font fonts;
|
||||
EngineResourceGroup!Sound sounds;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
void free(Sz tag = 0) {
|
||||
texts.free(tag);
|
||||
|
@ -757,7 +808,7 @@ struct EngineViewport {
|
|||
int targetHeight;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isLocking() {
|
||||
return (targetWidth != 0 && targetHeight != 0) && (data.width != targetWidth && data.height != targetHeight);
|
||||
|
@ -792,7 +843,7 @@ struct EngineState {
|
|||
Filter defaultFilter;
|
||||
Sz tickCount;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
void free() {
|
||||
debug {
|
||||
|
@ -1170,8 +1221,9 @@ void openWindow(int width, int height, IStr appPath, IStr title = "Parin") {
|
|||
/// You should avoid calling this function manually.
|
||||
@trusted
|
||||
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() {
|
||||
// Begin drawing.
|
||||
if (isResolutionLocked) {
|
||||
|
@ -1193,10 +1245,9 @@ void updateWindow(bool function(float dt) updateFunc) {
|
|||
auto ratio = maxSize / minSize;
|
||||
auto minRatio = min(ratio.x, ratio.y);
|
||||
if (isPixelPerfect) {
|
||||
// TODO: Make an equals function in Joka that can change the epsilon value.
|
||||
auto roundMinRatio = round(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);
|
||||
|
@ -1253,7 +1304,7 @@ void updateWindow(bool function(float dt) updateFunc) {
|
|||
}
|
||||
|
||||
// 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;
|
||||
|
||||
version(WebAssembly) {
|
||||
|
@ -1284,6 +1335,21 @@ void 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.
|
||||
bool isPixelPerfect() {
|
||||
return engineState.flags.isPixelPerfect;
|
||||
|
@ -1490,10 +1556,9 @@ Vec2 mouseScreenPosition() {
|
|||
auto window = windowSize;
|
||||
auto minRatio = min(window.x / engineState.viewport.width, window.y / engineState.viewport.height);
|
||||
if (isPixelPerfect) {
|
||||
// TODO: Make an equals function in Joka that can change the epsilon value.
|
||||
auto roundMinRatio = round(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);
|
||||
// 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.
|
||||
@trusted
|
||||
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());
|
||||
} else {
|
||||
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.
|
||||
@trusted
|
||||
void drawCirc(Circ area, Color color = white) {
|
||||
if (isPixelPerfect) {
|
||||
if (isPixelSnapped || isPixelPerfect) {
|
||||
rl.DrawCircleV(area.position.floor().toRl(), area.radius, color.toRl());
|
||||
} else {
|
||||
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.
|
||||
@trusted
|
||||
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());
|
||||
} else {
|
||||
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;
|
||||
if (isPixelPerfect) {
|
||||
if (isPixelSnapped || isPixelPerfect) {
|
||||
rl.DrawTexturePro(
|
||||
texture.data,
|
||||
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 origin = options.origin == Vec2() ? rect.origin(options.hook) : options.origin;
|
||||
rl.rlPushMatrix();
|
||||
if (isPixelPerfect) {
|
||||
if (isPixelSnapped || isPixelPerfect) {
|
||||
rl.rlTranslatef(position.x.floor(), position.y.floor(), 0.0f);
|
||||
} else {
|
||||
rl.rlTranslatef(position.x, position.y, 0.0f);
|
||||
}
|
||||
rl.rlRotatef(options.rotation, 0.0f, 0.0f, 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);
|
||||
} else {
|
||||
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.
|
||||
auto origin = Rect(measureTextSize(font, text)).origin(options.hook);
|
||||
rl.rlPushMatrix();
|
||||
if (isPixelPerfect) {
|
||||
if (isPixelSnapped || isPixelPerfect) {
|
||||
rl.rlTranslatef(floor(position.x), floor(position.y), 0.0f);
|
||||
} else {
|
||||
rl.rlTranslatef(position.x, position.y, 0.0f);
|
||||
}
|
||||
rl.rlRotatef(options.rotation, 0.0f, 0.0f, 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);
|
||||
} else {
|
||||
rl.rlTranslatef(-origin.x, -origin.y, 0.0f);
|
||||
|
|
|
@ -10,7 +10,6 @@ module parin;
|
|||
|
||||
public import joka.ascii;
|
||||
public import joka.io;
|
||||
public import parin.dialogue;
|
||||
public import parin.engine;
|
||||
public import parin.scene;
|
||||
public import parin.sprite;
|
||||
|
|
|
@ -1258,7 +1258,6 @@ void OpenURL (const(char)* url); // Open URL with default system browser (if ava
|
|||
|
||||
// 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* MemAlloc (uint size); // Internal memory allocator
|
||||
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
|
||||
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
|
||||
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
|
||||
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!)
|
||||
|
|
|
@ -14,7 +14,7 @@ module parin.sprite;
|
|||
|
||||
import parin.engine;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
struct SpriteAnimation {
|
||||
ubyte frameRow;
|
||||
|
@ -28,7 +28,7 @@ struct SpriteAnimationGroup2 {
|
|||
ubyte frameSpeed = 6;
|
||||
enum angleStep = 180.0f;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
SpriteAnimation pick(float angle) {
|
||||
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
|
||||
|
@ -42,7 +42,7 @@ struct SpriteAnimationGroup4 {
|
|||
ubyte frameSpeed = 6;
|
||||
enum angleStep = 90.0f;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
SpriteAnimation pick(float angle) {
|
||||
// NOTE: This is a hack to make things look better in simple cases.
|
||||
|
@ -61,7 +61,7 @@ struct SpriteAnimationGroup8 {
|
|||
ubyte frameSpeed = 6;
|
||||
enum angleStep = 45.0f;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
SpriteAnimation pick(float angle) {
|
||||
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
|
||||
|
@ -75,7 +75,7 @@ struct SpriteAnimationGroup16 {
|
|||
ubyte frameSpeed = 6;
|
||||
enum angleStep = 22.5f;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
SpriteAnimation pick(float angle) {
|
||||
auto id = (cast(int) round(snap(angle, angleStep) / angleStep)) % frameRows.length;
|
||||
|
@ -92,7 +92,7 @@ struct Sprite {
|
|||
SpriteAnimation animation;
|
||||
Vec2 position;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
this(int width, int height, ushort atlasLeft, ushort atlasTop, SpriteAnimation animation = SpriteAnimation()) {
|
||||
this.width = width;
|
||||
|
|
|
@ -21,7 +21,7 @@ public import joka.faults;
|
|||
public import joka.math;
|
||||
public import joka.types;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
struct Tile {
|
||||
Sz id;
|
||||
|
@ -29,7 +29,7 @@ struct Tile {
|
|||
int height;
|
||||
Vec2 position;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
Vec2 size() {
|
||||
return Vec2(width, height);
|
||||
|
@ -67,13 +67,13 @@ struct TileMap {
|
|||
Vec2 position;
|
||||
alias data this;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
this(short value, int tileWidth, int tileHeight) {
|
||||
this.tileWidth = tileWidth;
|
||||
this.tileHeight = tileHeight;
|
||||
this.estimatedMaxRowCount = data.maxRowCount;
|
||||
this.estimatedMaxColCount = data.maxColCount;
|
||||
this.estimatedMaxRowCount = maxRowCount;
|
||||
this.estimatedMaxColCount = maxColCount;
|
||||
this.data.fill(value);
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ struct TileMap {
|
|||
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()) {
|
||||
auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
|
||||
auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
|
||||
|
@ -147,7 +147,12 @@ struct TileMap {
|
|||
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 targetTileWidth = cast(int) (tileWidth * options.scale.x);
|
||||
auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
|
||||
|
@ -156,7 +161,7 @@ struct TileMap {
|
|||
return result;
|
||||
}
|
||||
|
||||
IVec2 lastMapPosition(Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) {
|
||||
IVec2 lastGridPosition(Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) {
|
||||
auto result = IVec2();
|
||||
auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
|
||||
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));
|
||||
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) {
|
||||
|
@ -185,6 +221,7 @@ Result!TileMap loadRawTileMap(IStr path, int tileWidth, int tileHeight) {
|
|||
}
|
||||
|
||||
void drawTile(Texture texture, Tile tile, DrawOptions options = DrawOptions()) {
|
||||
if (texture.isEmpty) return;
|
||||
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()) {
|
||||
auto targetTileWidth = cast(int) (map.tileWidth * options.scale.x);
|
||||
auto targetTileHeight = cast(int) (map.tileHeight * options.scale.y);
|
||||
auto cameraArea = camera.area;
|
||||
auto colRow1 = map.firstMapPosition(cameraArea.topLeftPoint, options);
|
||||
auto colRow2 = map.lastMapPosition(cameraArea.bottomRightPoint, options);
|
||||
auto colRow1 = map.firstGridPosition(camera.topLeftPoint, options);
|
||||
auto colRow2 = map.lastGridPosition(camera.bottomRightPoint, options);
|
||||
if (colRow1.x == colRow2.x || colRow1.y == colRow2.y) return;
|
||||
|
||||
foreach (row; colRow1.y .. colRow2.y) {
|
||||
|
|
|
@ -13,7 +13,7 @@ module parin.timer;
|
|||
|
||||
import joka.math;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
struct Timer {
|
||||
float time = 1.0f;
|
||||
|
@ -22,7 +22,7 @@ struct Timer {
|
|||
bool isPaused;
|
||||
bool canRepeat;
|
||||
|
||||
@safe:
|
||||
@safe @nogc nothrow:
|
||||
|
||||
this(float duration, bool canRepeat = false) {
|
||||
this.time = duration;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue