Removed dt from move and new example for platformer.

This commit is contained in:
Kapendev 2025-02-09 16:26:18 +02:00
parent 5bde39857c
commit e8afbd08e8
3 changed files with 102 additions and 101 deletions

View file

@ -0,0 +1,44 @@
/// This example shows how to use the Parin physics engine.
import parin;
auto world = BoxWorld();
auto platformBoxId = WallId();
auto groundBoxId = WallId();
auto playerBoxId = ActorId();
auto playerMover = BoxMover(2, 4, 0.3, 1);
auto groundY = 140;
void ready() {
lockResolution(320, 180);
// Add boxes to the world.
platformBoxId = world.appendWall(Box(140, groundY - 20, 64, 16));
groundBoxId = world.appendWall(Box(0, groundY, resolutionWidth, resolutionHeight - groundY));
playerBoxId = world.appendActor(Box(80, groundY - 16, 16, 16), RideSide.top);
}
bool update(float dt) {
// Move the platform box.
world.moveWallX(platformBoxId, sin(elapsedTime * 4) * 1.7);
// Move the player box.
playerMover.direction.x = wasd.x;
playerMover.direction.y = wasdPressed.y;
playerMover.move();
world.moveActorX(playerBoxId, playerMover.velocity.x);
if (world.moveActorY(playerBoxId, playerMover.velocity.y)) {
playerMover.velocity.y = 0;
}
// Draw the world.
foreach (wall; world.walls) {
drawRect(wall.toRect(), black.alpha(190));
}
foreach (actor; world.actors) {
drawRect(actor.toRect(), yellow.alpha(190));
}
drawDebugText("Move with arrow keys.", Vec2(8));
return false;
}
void finish() { }
mixin runGame!(ready, update, finish);

View file

@ -275,7 +275,7 @@ struct Texture {
}
/// Represents an identifier for a managed engine resource.
/// Managed resources are cached by the path they were loaded from and can be safely shared throughout the code.
/// Managed resources can be safely shared throughout the code.
/// To free these resources, use the `freeResources` function or the `free` method on the identifier.
/// The identifier is automatically invalidated when the resource is freed.
struct TextureId {
@ -310,18 +310,18 @@ struct TextureId {
/// Checks if the resource identifier is valid. It becomes automatically invalid when the resource is freed.
bool isValid() {
return data && engineState.textures.has(data);
return data && engineState.textures.has(GenerationalIndex(data.value - 1, data.generation));
}
/// Retrieves the texture associated with the resource identifier.
ref Texture get() {
if (!isValid) assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
return engineState.textures.data[data];
return engineState.textures[GenerationalIndex(data.value - 1, data.generation)];
}
/// Retrieves the texture associated with the resource identifier or returns a default value if invalid.
Texture getOr() {
return isValid ? engineState.textures.data[data] : Texture();
return isValid ? engineState.textures[GenerationalIndex(data.value - 1, data.generation)] : Texture();
}
/// Frees the resource associated with the identifier.
@ -374,7 +374,7 @@ struct Font {
}
/// Represents an identifier for a managed engine resource.
/// Managed resources are cached by the path they were loaded from and can be safely shared throughout the code.
/// Managed resources can be safely shared throughout the code.
/// To free these resources, use the `freeResources` function or the `free` method on the identifier.
/// The identifier is automatically invalidated when the resource is freed.
struct FontId {
@ -409,18 +409,18 @@ struct FontId {
/// Checks if the resource identifier is valid. It becomes automatically invalid when the resource is freed.
bool isValid() {
return data && engineState.fonts.has(data);
return data && engineState.fonts.has(GenerationalIndex(data.value - 1, data.generation));
}
/// Retrieves the font associated with the resource identifier.
ref Font get() {
if (!isValid) assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
return engineState.fonts.data[data];
return engineState.fonts[GenerationalIndex(data.value - 1, data.generation)];
}
/// Retrieves the font associated with the resource identifier or returns a default value if invalid.
Font getOr() {
return isValid ? engineState.fonts.data[data] : Font();
return isValid ? engineState.fonts[GenerationalIndex(data.value - 1, data.generation)] : Font();
}
/// Frees the resource associated with the identifier.
@ -525,7 +525,7 @@ struct Sound {
}
/// Represents an identifier for a managed engine resource.
/// Managed resources are cached by the path they were loaded from and can be safely shared throughout the code.
/// Managed resources can be safely shared throughout the code.
/// To free these resources, use the `freeResources` function or the `free` method on the identifier.
/// The identifier is automatically invalidated when the resource is freed.
struct SoundId {
@ -569,18 +569,18 @@ struct SoundId {
/// Checks if the resource identifier is valid. It becomes automatically invalid when the resource is freed.
bool isValid() {
return data && engineState.sounds.has(data);
return data && engineState.sounds.has(GenerationalIndex(data.value - 1, data.generation));
}
/// Retrieves the sound associated with the resource identifier.
ref Sound get() {
if (!isValid) assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
return engineState.sounds.data[data];
return engineState.sounds[GenerationalIndex(data.value - 1, data.generation)];
}
/// Retrieves the sound associated with the resource identifier or returns a default value if invalid.
Sound getOr() {
return isValid ? engineState.sounds.data[data] : Sound();
return isValid ? engineState.sounds[GenerationalIndex(data.value - 1, data.generation)] : Sound();
}
/// Frees the resource associated with the identifier.
@ -827,68 +827,7 @@ struct Camera {
}
}
struct EngineResourceGroup(T) {
GenerationalList!T data;
GenerationalList!LStr names;
@safe @nogc nothrow:
Sz length() {
return data.length;
}
bool has(GenerationalIndex i) {
return data.has(i);
}
Result!GenerationalIndex find(IStr name) {
foreach (id; engineState.textures.ids) {
if (engineState.textures.names[id] == name) return Result!GenerationalIndex(id);
}
return Result!GenerationalIndex();
}
GenerationalIndex append(T arg, IStr name) {
data.append(arg);
return names.append(LStr(name));
}
GenerationalIndex appendEmpty() {
return append(T(), "");
}
void remove(GenerationalIndex i) {
data[i].free();
data.remove(i);
names[i].free();
names.remove(i);
}
void reserve(Sz capacity) {
data.reserve(capacity);
names.reserve(capacity);
}
void free() {
foreach (ref item; data.items) {
item.free();
}
data.free();
foreach (ref item; names.items) {
item.free();
}
names.free();
}
auto items() {
return data.items;
}
auto ids() {
return data.ids;
}
}
/// The engine flags.
struct EngineFlags {
bool isUpdating;
bool isPixelSnapped;
@ -936,9 +875,9 @@ struct EngineState {
Camera userCamera;
Viewport userViewport;
EngineResourceGroup!Texture textures;
EngineResourceGroup!Font fonts;
EngineResourceGroup!Sound sounds;
GenerationalList!Texture textures;
GenerationalList!Font fonts;
GenerationalList!Sound sounds;
EngineViewport viewport;
Font debugFont;
List!IStr envArgsBuffer;
@ -1201,8 +1140,11 @@ Result!Texture loadRawTexture(IStr path) {
/// The resource is managed by the engine and can be freed manually or with the `freeResources` function.
/// Supports both forward slashes and backslashes in file paths.
TextureId loadTexture(IStr path) {
if (auto id = engineState.textures.find(path)) return TextureId(id.get());
if (auto resource = loadRawTexture(path)) return TextureId(engineState.textures.append(resource.get(), path));
if (auto resource = loadRawTexture(path)) {
auto id = TextureId(engineState.textures.append(resource.get()));
id.data.value += 1;
return id;
}
return TextureId();
}
@ -1227,8 +1169,11 @@ Result!Font loadRawFont(IStr path, int size, int runeSpacing, int lineSpacing, I
/// The resource is managed by the engine and can be freed manually or with the `freeResources` function.
/// Supports both forward slashes and backslashes in file paths.
FontId loadFont(IStr path, int size, int runeSpacing, int lineSpacing, IStr32 runes = "") {
if (auto id = engineState.fonts.find(path)) return FontId(id.get());
if (auto resource = loadRawFont(path, size, runeSpacing, lineSpacing, runes)) return FontId(engineState.fonts.append(resource.get(), path));
if (auto resource = loadRawFont(path, size, runeSpacing, lineSpacing, runes)) {
auto id = FontId(engineState.fonts.append(resource.get()));
id.data.value += 1;
return id;
}
return FontId();
}
@ -1246,8 +1191,11 @@ Result!Font loadRawFontFromTexture(IStr path, int tileWidth, int tileHeight) {
/// Supports both forward slashes and backslashes in file paths.
// NOTE: The number of items allocated for this font is calculated as: (font width / tile width) * (font height / tile height)
FontId loadFontFromTexture(IStr path, int tileWidth, int tileHeight) {
if (auto id = engineState.fonts.find(path)) return FontId(id.get());
if (auto resource = loadRawFontFromTexture(path, tileWidth, tileHeight)) return FontId(engineState.fonts.append(resource.get(), path));
if (auto resource = loadRawFontFromTexture(path, tileWidth, tileHeight)) {
auto id = FontId(engineState.fonts.append(resource.get()));
id.data.value += 1;
return id;
}
return FontId();
}
@ -1272,8 +1220,11 @@ Result!Sound loadRawSound(IStr path, float volume, float pitch) {
/// The resource is managed by the engine and can be freed manually or with the `freeResources` function.
/// Supports both forward slashes and backslashes in file paths.
SoundId loadSound(IStr path, float volume, float pitch) {
if (auto id = engineState.sounds.find(path)) return SoundId(id.get());
if (auto resource = loadRawSound(path, volume, pitch)) return SoundId(engineState.sounds.append(resource.get(), path));
if (auto resource = loadRawSound(path, volume, pitch)) {
auto id = SoundId(engineState.sounds.append(resource.get()));
id.data.value += 1;
return id;
}
return SoundId();
}
@ -1325,9 +1276,6 @@ void openWindow(int width, int height, const(IStr)[] args, IStr title = "Parin")
engineState.textures.reserve(defaultEngineResourcesCapacity);
engineState.sounds.reserve(defaultEngineResourcesCapacity);
engineState.fonts.reserve(defaultEngineFontsCapacity);
engineState.textures.appendEmpty();
engineState.sounds.appendEmpty();
engineState.fonts.appendEmpty();
engineState.viewport.color = gray;
engineState.loadTextBuffer.reserve(8192);
engineState.saveTextBuffer.reserve(8192);
@ -2093,6 +2041,7 @@ void drawRect(Rect area, Color color = white) {
}
}
/// Draws a hollow rectangle with the specified area and color.
@trusted
void drawHollowRect(Rect area, float thickness, Color color = white) {
if (isPixelSnapped || isPixelPerfect) {
@ -2117,6 +2066,7 @@ void drawCirc(Circ area, Color color = white) {
}
}
/// Draws a hollow circle with the specified area and color.
@trusted
void drawHollowCirc(Circ area, float thickness, Color color = white) {
if (isPixelSnapped || isPixelPerfect) {

View file

@ -39,13 +39,15 @@ struct BoxMover {
float gravityFallFactor = 0.7f;
float acceleration = 0.0f;
float decelerationFactor = 0.3f;
bool isUnnormalized;
@safe @nogc nothrow:
this(float speed, float jump, float gravity) {
this(float speed, float jump, float gravity, float acceleration) {
this.speed = speed;
this.jump = jump;
this.gravity = gravity;
this.acceleration = acceleration;
}
bool isSmooth() {
@ -56,22 +58,22 @@ struct BoxMover {
return gravity == 0.0f;
}
Vec2 move(float dt, bool isUnnormalized = false) {
Vec2 move() {
if (isTopDown) {
auto tempDirection = isUnnormalized ? direction : direction.normalize();
if (isSmooth) {
if (direction.x > 0.0f) {
velocity.x = min(velocity.x + tempDirection.x * acceleration * dt, tempDirection.x * speed);
velocity.x = min(velocity.x + tempDirection.x * acceleration, tempDirection.x * speed);
} else if (direction.x < 0.0f) {
velocity.x = max(velocity.x + tempDirection.x * acceleration * dt, tempDirection.x * speed);
velocity.x = max(velocity.x + tempDirection.x * acceleration, tempDirection.x * speed);
}
if (velocity.x != tempDirection.x * speed) {
velocity.x = lerp(velocity.x, 0.0f, decelerationFactor);
}
if (direction.y > 0.0f) {
velocity.y = min(velocity.y + tempDirection.y * acceleration * dt, tempDirection.y * speed);
velocity.y = min(velocity.y + tempDirection.y * acceleration, tempDirection.y * speed);
} else if (direction.y < 0.0f) {
velocity.y = max(velocity.y + tempDirection.y * acceleration * dt, tempDirection.y * speed);
velocity.y = max(velocity.y + tempDirection.y * acceleration, tempDirection.y * speed);
}
if (velocity.y != tempDirection.y * speed) {
velocity.y = lerp(velocity.y, 0.0f, decelerationFactor);
@ -80,14 +82,14 @@ struct BoxMover {
velocity.x = tempDirection.x * speed;
velocity.y = tempDirection.y * speed;
}
velocity.x = velocity.x * dt;
velocity.y = velocity.y * dt;
velocity.x = velocity.x;
velocity.y = velocity.y;
} else {
if (isSmooth) {
if (direction.x > 0.0f) {
velocity.x = min(velocity.x + acceleration * dt, speed);
velocity.x = min(velocity.x + acceleration, speed);
} else if (direction.x < 0.0f) {
velocity.x = max(velocity.x - acceleration * dt, -speed);
velocity.x = max(velocity.x - acceleration, -speed);
}
if (velocity.x != direction.x * speed) {
velocity.x = lerp(velocity.x, 0.0f, decelerationFactor);
@ -95,10 +97,10 @@ struct BoxMover {
} else {
velocity.x = direction.x * speed;
}
velocity.x = velocity.x * dt;
velocity.x = velocity.x;
if (velocity.y > 0.0f) velocity.y += gravity * dt;
else velocity.y += gravity * gravityFallFactor * dt;
if (velocity.y > 0.0f) velocity.y += gravity;
else velocity.y += gravity * gravityFallFactor;
if (direction.y < 0.0f) velocity.y = -jump;
}
return velocity;
@ -132,6 +134,11 @@ struct Box {
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 &&