More joka changes and map change and engineState is now a pointer.

This commit is contained in:
Kapendev 2025-03-10 12:13:30 +02:00
parent cf8f71cfaa
commit 14ce68833c
4 changed files with 154 additions and 154 deletions

View file

@ -10,18 +10,16 @@
module parin.engine;
import rl = parin.rl;
import stdc = joka.stdc;
import joka.ascii;
import joka.io;
import joka.unions;
public import joka.containers;
public import joka.faults;
public import joka.math;
public import joka.types;
@safe @nogc nothrow:
EngineState engineState;
EngineState* engineState;
enum defaultEngineFontsCapacity = 64;
enum defaultEngineResourcesCapacity = 256;
@ -1272,6 +1270,7 @@ void openUrl(IStr url = "https://github.com/Kapendev/parin") {
@trusted
void openWindow(int width, int height, const(IStr)[] args, IStr title = "Parin") {
if (rl.IsWindowReady) return;
engineState = cast(EngineState*) stdc.malloc(EngineState.sizeof);
engineState.envArgsBuffer.clear();
foreach (arg; args) engineState.envArgsBuffer.append(arg);
// Set raylib stuff.
@ -1429,6 +1428,8 @@ void closeWindow() {
engineState.loadTextBuffer.free();
engineState.saveTextBuffer.free();
engineState.assetsPath.free();
stdc.free(engineState);
engineState = null;
}
// This is outside because who knows, maybe raylib needs that.
rl.CloseAudioDevice();

View file

@ -59,27 +59,26 @@ struct Tile {
}
}
// TODO: Look at it again now with the Grid changes. It works, I know that :)
// NOTE: Things like changing the grid row count might be interesting.
struct TileMap {
Grid!short data;
Sz softMaxRowCount;
Sz softMaxColCount;
Sz softRowCount;
Sz softColCount;
int tileWidth = 16;
int tileHeight = 16;
Vec2 position;
enum maxRowCount = data.maxRowCount;
enum maxColCount = data.maxColCount;
enum maxCapacity = data.maxCapacity;
@safe @nogc nothrow:
this(int tileWidth, int tileHeight, Vec2 position = Vec2()) {
this(Sz rowCount, Sz colCount, int tileWidth, int tileHeight) {
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.softMaxRowCount = maxRowCount;
this.softMaxColCount = maxColCount;
this.position = position;
this.data.fill(-1);
resizeHard(rowCount, colCount);
}
this(int tileWidth, int tileHeight) {
this(defaultGridRowCount, defaultGridColCount, tileWidth, tileHeight);
}
ref short opIndex(Sz row, Sz col) {
@ -116,21 +115,7 @@ struct TileMap {
}
Sz opDollar(Sz dim)() {
static if (dim == 0) {
return rowCount;
} else static if (dim == 1) {
return colCount;
} else {
assert(0, "WTF!");
}
}
Sz rowCount() {
return data.length ? softMaxRowCount : 0;
}
Sz colCount() {
return data.length ? softMaxColCount : 0;
return data.opDollar!dim();
}
bool isEmpty() {
@ -138,29 +123,50 @@ struct TileMap {
}
bool has(Sz row, Sz col) {
return row < rowCount && col < colCount;
return row < softRowCount && col < softColCount;
}
bool has(IVec2 position) {
return has(position.y, position.x);
}
@trusted
Sz hardRowCount() {
return data.rowCount;
}
Sz hardColCount() {
return data.colCount;
}
void resizeHard(Sz newHardRowCount, Sz newHardColCount) {
data.resizeBlank(newHardRowCount, newHardColCount);
data.fill(-1);
softRowCount = newHardRowCount;
softColCount = newHardColCount;
}
void resizeSoft(Sz newSoftRowCount, Sz newSoftColCount) {
if (newSoftRowCount > hardRowCount || newSoftColCount > hardColCount) {
assert(0, "Soft count must be smaller than hard count.");
}
softRowCount = newSoftRowCount;
softColCount = newSoftColCount;
}
void fill(short value) {
data.fill(value);
}
@trusted
void free() {
data.free();
}
int width() {
return cast(int) (colCount * tileWidth);
return cast(int) (softColCount * tileWidth);
}
int height() {
return cast(int) (rowCount * tileHeight);
return cast(int) (softRowCount * tileHeight);
}
/// Returns the size of the tile map.
@ -173,25 +179,27 @@ struct TileMap {
return Vec2(tileWidth, tileHeight);
}
Fault parse(IStr csv, int tileWidth, int tileHeight) {
Fault parse(IStr csv, int newTileWidth, int newTileHeight) {
if (csv.length == 0) return Fault.invalid;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.softMaxRowCount = 0;
this.softMaxColCount = 0;
this.data.fill(-1);
if (data.isEmpty) {
data.resizeBlank(defaultGridRowCount, defaultGridColCount);
}
tileWidth = newTileWidth;
tileHeight = newTileHeight;
softRowCount = 0;
softColCount = 0;
auto view = csv;
while (view.length != 0) {
softMaxRowCount += 1;
softMaxColCount = 0;
if (softMaxRowCount > maxRowCount) return Fault.invalid;
softRowCount += 1;
softColCount = 0;
if (softRowCount > data.rowCount) return Fault.invalid;
auto line = view.skipLine();
while (line.length != 0) {
softMaxColCount += 1;
if (softMaxColCount > maxColCount) return Fault.invalid;
softColCount += 1;
if (softColCount > data.colCount) return Fault.invalid;
auto tile = line.skipValue(',').toSigned();
if (tile.isNone) return Fault.invalid;
data[softMaxRowCount - 1, softMaxColCount - 1] = cast(short) tile.get();
data[softRowCount - 1, softColCount - 1] = cast(short) tile.get();
}
}
return Fault.none;
@ -226,23 +234,23 @@ struct TileMap {
}
IVec2 firstGridPosition(Vec2 topLeftWorldPosition, DrawOptions options = DrawOptions()) {
if (rowCount == 0 || colCount == 0) return IVec2();
if (softRowCount == 0 || softColCount == 0) return IVec2();
auto result = IVec2();
auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
result.y = cast(int) floor(clamp((topLeftWorldPosition.y - position.y) / targetTileHeight, 0, rowCount - 1));
result.x = cast(int) floor(clamp((topLeftWorldPosition.x - position.x) / targetTileWidth, 0, colCount - 1));
result.y = cast(int) floor(clamp((topLeftWorldPosition.y - position.y) / targetTileHeight, 0, softRowCount - 1));
result.x = cast(int) floor(clamp((topLeftWorldPosition.x - position.x) / targetTileWidth, 0, softColCount - 1));
return result;
}
IVec2 lastGridPosition(Vec2 bottomRightWorldPosition, DrawOptions options = DrawOptions()) {
if (rowCount == 0 || colCount == 0) return IVec2();
if (softRowCount == 0 || softColCount == 0) return IVec2();
auto result = IVec2();
auto targetTileWidth = cast(int) (tileWidth * options.scale.x);
auto targetTileHeight = cast(int) (tileHeight * options.scale.y);
auto extraTileCount = options.hook == Hook.topLeft ? 1 : 2;
result.y = cast(int) floor(clamp((bottomRightWorldPosition.y - position.y) / targetTileHeight + extraTileCount, 0, rowCount - 1));
result.x = cast(int) floor(clamp((bottomRightWorldPosition.x - position.x) / targetTileWidth + extraTileCount, 0, colCount - 1));
result.y = cast(int) floor(clamp((bottomRightWorldPosition.y - position.y) / targetTileHeight + extraTileCount, 0, softRowCount - 1));
result.x = cast(int) floor(clamp((bottomRightWorldPosition.x - position.x) / targetTileWidth + extraTileCount, 0, softColCount - 1));
return result;
}
@ -271,7 +279,7 @@ struct TileMap {
}
auto result = Range(
colCount,
softColCount,
firstGridPosition(topLeftWorldPosition, options),
lastGridPosition(bottomRightWorldPosition, options),
);
@ -286,10 +294,10 @@ struct TileMap {
Fault saveTileMap(IStr path, TileMap map) {
auto csv = prepareTempText();
foreach (row; 0 .. map.rowCount) {
foreach (col; 0 .. map.colCount) {
foreach (row; 0 .. map.softRowCount) {
foreach (col; 0 .. map.softColCount) {
csv.append(map[row, col].toStr());
if (col != map.colCount - 1) csv.append(',');
if (col != map.softColCount - 1) csv.append(',');
}
csv.append('\n');
}

View file

@ -7,9 +7,9 @@
// ---
// TODO: Update all the doc comments here.
// TODO: Add spatial partitioning after testing this in a game.
// TODO: Add one-way collision support for moving walls.
// NOTE: Maybe a world pixel size value could be useful.
// TODO: Add spatial partitioning.
// NOTE: Was working on spatial partitioning. The grid is done, just need to add values in it.
/// The `platformer` module provides a pixel-perfect physics engine.
module parin.platformer;
@ -21,10 +21,17 @@ import joka.types;
@safe @nogc nothrow:
alias BaseBoxId = int;
alias ActorBoxId = BaseBoxId;
alias WallBoxId = BaseBoxId;
alias OneWaySide = RideSide;
alias BaseBoxId = int;
alias BaseBoxIdGroup = FixedList!(BaseBoxId, 510);
alias ActorBoxId = BaseBoxId;
alias ActorBoxFlags = ubyte;
alias WallBoxId = BaseBoxId;
alias WallBoxFlags = ubyte;
alias OneWaySide = RideSide;
enum boxPassableFlag = 0x1;
enum boxRidingFlag = 0x2;
enum RideSide : ubyte {
none,
@ -111,86 +118,59 @@ struct BoxMover {
}
}
struct Box {
IVec2 position;
IVec2 size;
@safe @nogc nothrow:
pragma(inline, true)
this(IVec2 position, IVec2 size) {
this.position = position;
this.size = size;
}
pragma(inline, true)
this(int x, int y, int w, int h) {
this(IVec2(x, y), IVec2(w, h));
}
pragma(inline, true)
this(IVec2 position, int w, int h) {
this(position, IVec2(w, h));
}
pragma(inline, true)
this(int x, int y, IVec2 size) {
this(IVec2(x, y), size);
}
pragma(inline, true)
Rect toRect() {
return Rect(position.toVec(), size.toVec());
}
bool hasPoint(IVec2 point) {
return (
point.x > position.x &&
point.x < position.x + size.x &&
point.y > position.y &&
point.y < position.y + size.y
);
}
bool hasIntersection(Box area) {
return (
position.x + size.x > area.position.x &&
position.x < area.position.x + area.size.x &&
position.y + size.y > area.position.y &&
position.y < area.position.y + area.size.y
);
}
/// Returns a string representation with a limited lifetime.
IStr toStr() {
return "({}, {}, {}, {})".format(position.x, position.y, size.x, size.y);
}
}
struct WallBoxProperties {
Vec2 remainder;
OneWaySide oneWaySide;
bool isPassable;
WallBoxFlags flags;
byte gridX;
byte gridY;
}
struct ActorBoxProperties {
Vec2 remainder;
RideSide rideSide;
bool isRiding;
bool isPassable;
ActorBoxFlags flags;
byte gridX;
byte gridY;
}
struct BoxWorld {
List!Box walls;
List!Box actors;
List!IRect walls;
List!IRect actors;
List!WallBoxProperties wallsProperties;
List!ActorBoxProperties actorsProperties;
List!ActorBoxId squishedIdsBuffer;
List!BaseBoxId collisionIdsBuffer;
Grid!BaseBoxIdGroup grid;
int gridTileWidth;
int gridTileHeight;
@safe @nogc nothrow:
ref Box getWall(WallBoxId id) {
void enableSpatialGrid(Sz rowCount, Sz colCount, int tileWidth, int tileHeight) {
gridTileWidth = tileWidth;
gridTileHeight = tileHeight;
foreach (i, ref properties; wallsProperties) {
properties.gridX = walls[i].position.x / gridTileWidth - (walls[i].position.x < 0);
properties.gridY = walls[i].position.y / gridTileHeight - (walls[i].position.y < 0);
}
foreach (i, ref properties; actorsProperties) {
properties.gridX = actors[i].position.x / gridTileWidth - (actors[i].position.x < 0);
properties.gridY = actors[i].position.y / gridTileHeight - (actors[i].position.y < 0);
}
grid.resizeBlank(rowCount, colCount);
foreach (ref group; grid) {
group.length = 0;
}
}
void disableSpatialGrid() {
gridTileWidth = 0;
gridTileHeight = 0;
grid.clear();
}
ref IRect getWall(WallBoxId id) {
if (id <= 0) {
assert(0, "ID `0` is always invalid and represents a box that was never created.");
} else if (id > walls.length) {
@ -199,7 +179,7 @@ struct BoxWorld {
return walls[id - 1];
}
ref Box getActor(ActorBoxId id) {
ref IRect getActor(ActorBoxId id) {
if (id <= 0) {
assert(0, "ID `0` is always invalid and represents a box that was never created.");
} else if (id > actors.length) {
@ -226,46 +206,54 @@ struct BoxWorld {
return actorsProperties[id - 1];
}
WallBoxId appendWall(Box box, OneWaySide oneWaySide = OneWaySide.none) {
WallBoxId appendWall(IRect box, OneWaySide oneWaySide = OneWaySide.none) {
walls.append(box);
wallsProperties.append(WallBoxProperties());
wallsProperties[$ - 1].oneWaySide = oneWaySide;
if (gridTileWidth != 0 || gridTileHeight != 0) {
wallsProperties[$ - 1].gridX = box.position.x / gridTileWidth - (box.position.x < 0);
wallsProperties[$ - 1].gridY = box.position.y / gridTileHeight - (box.position.y < 0);
}
return cast(BaseBoxId) walls.length;
}
ActorBoxId appendActor(Box box, RideSide rideSide = RideSide.none) {
ActorBoxId appendActor(IRect box, RideSide rideSide = RideSide.none) {
actors.append(box);
actorsProperties.append(ActorBoxProperties());
actorsProperties[$ - 1].rideSide = rideSide;
if (gridTileWidth != 0 || gridTileHeight != 0) {
actorsProperties[$ - 1].gridX = box.position.x / gridTileWidth - (box.position.x < 0);
actorsProperties[$ - 1].gridY = box.position.y / gridTileHeight - (box.position.y < 0);
}
return cast(BaseBoxId) actors.length;
}
WallBoxId hasWallCollision(Box box) {
WallBoxId hasWallCollision(IRect box) {
foreach (i, wall; walls) {
if (wall.hasIntersection(box) && !wallsProperties[i].isPassable) return cast(BaseBoxId) (i + 1);
if (wall.hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) return cast(BaseBoxId) (i + 1);
}
return 0;
}
ActorBoxId hasActorCollision(Box box) {
ActorBoxId hasActorCollision(IRect box) {
foreach (i, actor; actors) {
if (actor.hasIntersection(box) && !actorsProperties[i].isPassable) return cast(BaseBoxId) (i + 1);
if (actor.hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) return cast(BaseBoxId) (i + 1);
}
return 0;
}
WallBoxId[] getWallCollisions(Box box) {
WallBoxId[] getWallCollisions(IRect box) {
collisionIdsBuffer.clear();
foreach (i, wall; walls) {
if (wall.hasIntersection(box) && !wallsProperties[i].isPassable) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
if (wall.hasIntersection(box) && ~wallsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
return collisionIdsBuffer[];
}
ActorBoxId[] getActorCollisions(Box box) {
ActorBoxId[] getActorCollisions(IRect box) {
collisionIdsBuffer.clear();
foreach (i, actor; actors) {
if (actor.hasIntersection(box) && !actorsProperties[i].isPassable) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
if (actor.hasIntersection(box) && ~actorsProperties[i].flags & boxPassableFlag) collisionIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
return collisionIdsBuffer[];
}
@ -281,7 +269,7 @@ struct BoxWorld {
int moveSign = move.sign();
properties.remainder.x -= move;
while (move != 0) {
auto tempBox = Box(actor.position + IVec2(moveSign, 0), actor.size);
auto tempBox = IRect(actor.position + IVec2(moveSign, 0), actor.size);
auto wallId = hasWallCollision(tempBox);
if (wallId) {
// One way stuff.
@ -302,11 +290,12 @@ struct BoxWorld {
break;
}
}
if (!properties.isPassable && wallId) {
if (~properties.flags & boxPassableFlag && wallId) {
return wallId;
} else {
actor.position.x += moveSign;
move -= moveSign;
properties.gridX = actor.position.x / gridTileWidth - (actor.position.x < 0);
}
}
return 0;
@ -335,7 +324,7 @@ struct BoxWorld {
int moveSign = move.sign();
properties.remainder.y -= move;
while (move != 0) {
auto tempBox = Box(actor.position + IVec2(0, moveSign), actor.size);
auto tempBox = IRect(actor.position + IVec2(0, moveSign), actor.size);
auto wallId = hasWallCollision(tempBox);
if (wallId) {
// One way stuff.
@ -356,11 +345,12 @@ struct BoxWorld {
break;
}
}
if (!properties.isPassable && wallId) {
if (~properties.flags & boxPassableFlag && wallId) {
return wallId;
} else {
actor.position.y += moveSign;
move -= moveSign;
properties.gridY = actor.position.y / gridTileHeight - (actor.position.y < 0);
}
}
return 0;
@ -443,8 +433,8 @@ struct BoxWorld {
auto move = properties.remainder.round().toIVec();
if (move.x != 0 || move.y != 0) {
foreach (i, ref actorProperties; actorsProperties) {
actorProperties.isRiding = false;
if (!actorProperties.rideSide || actorProperties.isPassable) continue;
actorProperties.flags &= ~boxRidingFlag;
if (!actorProperties.rideSide || actorProperties.flags & boxPassableFlag) continue;
auto rideBox = actors[i];
final switch (actorProperties.rideSide) with (RideSide) {
case none: break;
@ -453,16 +443,17 @@ struct BoxWorld {
case right: rideBox.position.x -= 1; break;
case bottom: rideBox.position.y -= 1; break;
}
actorProperties.isRiding = wall.hasIntersection(rideBox);
actorProperties.flags |= wall.hasIntersection(rideBox) ? boxRidingFlag : 0x0;
}
}
if (move.x != 0) {
wall.position.x += move.x;
properties.remainder.x -= move.x;
if (!properties.isPassable) {
properties.isPassable = true;
properties.gridX = wall.position.x / gridTileWidth - (wall.position.x < 0);
if (~properties.flags & boxPassableFlag) {
properties.flags |= boxPassableFlag;
foreach (i, ref actor; actors) {
if (actorsProperties[i].isPassable) continue;
if (actorsProperties[i].flags & boxPassableFlag) continue;
if (wall.hasIntersection(actor)) {
// Push actor.
auto wallLeft = wall.position.x;
@ -474,21 +465,22 @@ struct BoxWorld {
// Squish actor.
squishedIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
} else if (actorsProperties[i].isRiding) {
} else if (actorsProperties[i].flags & boxRidingFlag) {
// Carry actor.
moveActorX(cast(BaseBoxId) (i + 1), move.x);
}
}
properties.isPassable = false;
properties.flags &= ~boxPassableFlag;
}
}
if (move.y != 0) {
wall.position.y += move.y;
properties.remainder.y -= move.y;
if (!properties.isPassable) {
properties.isPassable = true;
properties.gridY = wall.position.y / gridTileHeight - (wall.position.y < 0);
if (~properties.flags & boxPassableFlag) {
properties.flags |= boxPassableFlag;
foreach (i, ref actor; actors) {
if (actorsProperties[i].isPassable) continue;
if (actorsProperties[i].flags & boxPassableFlag) continue;
if (wall.hasIntersection(actor)) {
// Push actor.
auto wallTop = wall.position.y;
@ -500,12 +492,12 @@ struct BoxWorld {
// Squish actor.
squishedIdsBuffer.append(cast(BaseBoxId) (i + 1));
}
} else if (actorsProperties[i].isRiding) {
} else if (actorsProperties[i].flags & boxRidingFlag) {
// Carry actor.
moveActorY(cast(BaseBoxId) (i + 1), move.y);
}
}
properties.isPassable = false;
properties.flags &= ~boxPassableFlag;
}
}
return squishedIdsBuffer[];

View file

@ -15,7 +15,6 @@ import joka.ascii;
import joka.containers;
import joka.io;
import joka.types;
import joka.unions;
@safe @nogc nothrow: