mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-27 05:29:53 +03:00
Big change to make resources easier to use.
This commit is contained in:
parent
8ef32abe76
commit
7a801a02bc
12 changed files with 350 additions and 232 deletions
|
@ -10,7 +10,7 @@ void ready() {
|
|||
lockResolution(320, 180);
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
drawDebugText("Hello world!");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -10,17 +10,17 @@ void ready() {
|
|||
lockResolution(320, 180);
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
// Move the camera.
|
||||
cameraTarget += wasd * cameraSpeed * Vec2(deltaTime);
|
||||
camera.followPosition(cameraTarget, Vec2(deltaTime));
|
||||
cameraTarget += wasd * cameraSpeed * Vec2(dt);
|
||||
camera.followPosition(cameraTarget, Vec2(dt));
|
||||
|
||||
// Draw the game world.
|
||||
auto cameraArea = Rect(camera.position, resolution).area(camera.hook).subAll(3);
|
||||
attachCamera(camera);
|
||||
camera.attach();
|
||||
drawDebugText("Move with arrow keys.");
|
||||
drawRect(cameraArea, Color(50, 50, 40, 130));
|
||||
detachCamera(camera);
|
||||
camera.detach();
|
||||
|
||||
// Draw the game UI.
|
||||
drawDebugText("I am UI!");
|
||||
|
|
|
@ -4,7 +4,7 @@ import popka;
|
|||
// The game variables.
|
||||
auto player = Rect(16, 16);
|
||||
auto playerSpeed = Vec2(120);
|
||||
auto coins = FlagList!Rect();
|
||||
auto coins = SparseList!Rect();
|
||||
auto coinSize = Vec2(8);
|
||||
auto maxCoinCount = 8;
|
||||
|
||||
|
@ -24,7 +24,7 @@ void ready() {
|
|||
}
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
// Move the player.
|
||||
auto playerDirection = Vec2();
|
||||
if (Keyboard.left.isDown || 'a'.isDown) {
|
||||
|
@ -39,7 +39,7 @@ bool update() {
|
|||
if (Keyboard.down.isDown || 's'.isDown) {
|
||||
playerDirection.y = 1;
|
||||
}
|
||||
player.position += playerDirection * playerSpeed * Vec2(deltaTime);
|
||||
player.position += playerDirection * playerSpeed * Vec2(dt);
|
||||
|
||||
// Check if the player is touching some coins and remove those coins.
|
||||
foreach (id; coins.ids) {
|
||||
|
@ -62,8 +62,6 @@ bool update() {
|
|||
return false;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
coins.free();
|
||||
}
|
||||
void finish() { }
|
||||
|
||||
mixin runGame!(ready, update, finish);
|
||||
|
|
|
@ -40,7 +40,7 @@ void ready() {
|
|||
dialogue.update();
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
// Update the game.
|
||||
if (dialogue.canUpdate) {
|
||||
if (dialogue.hasChoices) {
|
||||
|
@ -77,8 +77,6 @@ bool update() {
|
|||
return false;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
dialogue.free();
|
||||
}
|
||||
void finish() { }
|
||||
|
||||
mixin runGame!(ready, update, finish);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import popka;
|
||||
|
||||
// The game variables.
|
||||
auto atlas = Texture();
|
||||
auto atlas = TextureId();
|
||||
auto frame = 0.0;
|
||||
auto frameCount = 2;
|
||||
auto frameSpeed = 8;
|
||||
|
@ -20,10 +20,10 @@ void ready() {
|
|||
atlas = loadTexture("atlas.png").unwrap();
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
// Move the frame around in a smooth way and update the current frame.
|
||||
framePosition = framePosition.moveToWithSlowdown(mouseScreenPosition, Vec2(deltaTime), frameSlowdown);
|
||||
frame = wrap(frame + deltaTime * frameSpeed, 0, frameCount);
|
||||
framePosition = framePosition.moveToWithSlowdown(mouseScreenPosition, Vec2(dt), frameSlowdown);
|
||||
frame = wrap(frame + dt * frameSpeed, 0, frameCount);
|
||||
|
||||
// Check the mouse move direction and make the sprite look at that direction.
|
||||
auto mouseDirection = framePosition.directionTo(mouseScreenPosition);
|
||||
|
@ -42,13 +42,11 @@ bool update() {
|
|||
options.scale = Vec2(2);
|
||||
|
||||
// Draw the frame and the mouse position.
|
||||
drawTexture(atlas, framePosition, Rect(frameSize.x * floor(frame), 128, frameSize), options);
|
||||
drawTexture(atlas.get(), framePosition, Rect(frameSize.x * floor(frame), 128, frameSize), options);
|
||||
drawVec2(mouseScreenPosition, 8, frame == 0 ? blank : white.alpha(150));
|
||||
return false;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
atlas.free();
|
||||
}
|
||||
void finish() { }
|
||||
|
||||
mixin runGame!(ready, update, finish);
|
||||
|
|
|
@ -8,7 +8,7 @@ void ready() {
|
|||
|
||||
// The update function. This is called every frame while the game is running.
|
||||
// If true is returned, then the game will stop running.
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
drawDebugText("Hello world!");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import popka;
|
||||
|
||||
// The game variables.
|
||||
auto atlas = Texture();
|
||||
auto atlas = TextureId();
|
||||
auto map = TileMap();
|
||||
|
||||
void ready() {
|
||||
|
@ -12,16 +12,13 @@ void ready() {
|
|||
map.parse("145,0,65\n21,22,23\n37,38,39\n53,54,55", 16, 16);
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
auto options = DrawOptions();
|
||||
options.scale = Vec2(2.0f);
|
||||
drawTileMap(atlas, Vec2(), map, Camera(), options);
|
||||
drawTileMap(atlas.get(), Vec2(), map, Camera(), options);
|
||||
return false;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
atlas.free();
|
||||
map.free();
|
||||
}
|
||||
void finish() { }
|
||||
|
||||
mixin runGame!(ready, update, finish);
|
||||
|
|
|
@ -18,7 +18,7 @@ void ready() {
|
|||
paddle2.position = resolution * Vec2(0.5) + paddleOffset;
|
||||
}
|
||||
|
||||
bool update() {
|
||||
bool update(float dt) {
|
||||
// The objects in this game are centered.
|
||||
// To do that, we split the rectangle data into 2 parts, normal and centered.
|
||||
// A normal rectangle holds the real position of an object.
|
||||
|
@ -43,16 +43,16 @@ bool update() {
|
|||
ballDirection.y *= -1;
|
||||
ball.position.y = resolution.y - ball.size.y * 0.5;
|
||||
}
|
||||
ball.position += ballDirection * ballSpeed * Vec2(deltaTime);
|
||||
ball.position += ballDirection * ballSpeed * Vec2(dt);
|
||||
|
||||
// Move paddle1.
|
||||
paddle1.position.y = clamp(paddle1.position.y + wasd.y * ballSpeed.y * deltaTime, paddle1.size.y * 0.5, resolution.y - paddle1.size.y * 0.5);
|
||||
paddle1.position.y = clamp(paddle1.position.y + wasd.y * ballSpeed.y * dt, paddle1.size.y * 0.5, resolution.y - paddle1.size.y * 0.5);
|
||||
// Move paddle2.
|
||||
auto paddle2Target = ball.position.y;
|
||||
if (ballDirection.x < 1) {
|
||||
paddle2Target = paddle2.position.y;
|
||||
}
|
||||
paddle2.position.y = paddle2.position.y.moveTo(clamp(paddle2Target, paddle2.size.y * 0.5f, resolution.y - paddle2.size.y * 0.5f), ballSpeed.y * deltaTime);
|
||||
paddle2.position.y = paddle2.position.y.moveTo(clamp(paddle2Target, paddle2.size.y * 0.5f, resolution.y - paddle2.size.y * 0.5f), ballSpeed.y * dt);
|
||||
|
||||
// Check for collisions.
|
||||
if (paddle1.centerArea.hasIntersection(ball.centerArea)) {
|
||||
|
|
|
@ -343,7 +343,7 @@ Result!Dialogue toDialogue(IStr script) {
|
|||
return Result!Dialogue(value, fault);
|
||||
}
|
||||
|
||||
Result!Dialogue loadDialogue(IStr path) {
|
||||
Result!Dialogue loadRawDialogue(IStr path) {
|
||||
auto temp = loadTempText(path);
|
||||
if (temp.isNone) {
|
||||
return Result!Dialogue(temp.fault);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// TODO: Think about toggle functions.
|
||||
// TODO: Update setup script.
|
||||
// TODO: Clean web script.
|
||||
// TODO: Make the locked resolution image more pixel perfect friendly by maybe hiding some pixels when the window resolution is weird.
|
||||
|
||||
/// The `engine` module functions as a lightweight 2D game engine.
|
||||
module popka.engine;
|
||||
|
@ -189,6 +190,60 @@ struct Camera {
|
|||
scale = scale.moveToWithSlowdown(target, delta, slowdown);
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void attach() {
|
||||
if (isAttached) {
|
||||
return;
|
||||
}
|
||||
isAttached = true;
|
||||
auto temp = this.toRl();
|
||||
if (isPixelPerfect) {
|
||||
temp.target.x = floor(temp.target.x);
|
||||
temp.target.y = floor(temp.target.y);
|
||||
temp.offset.x = floor(temp.offset.x);
|
||||
temp.offset.y = floor(temp.offset.y);
|
||||
}
|
||||
rl.BeginMode2D(temp);
|
||||
}
|
||||
|
||||
@trusted
|
||||
void detach() {
|
||||
if (isAttached) {
|
||||
isAttached = false;
|
||||
rl.EndMode2D();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TextId {
|
||||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isValid() {
|
||||
return data.value != 0 && engineState.resources.texts.has(data);
|
||||
}
|
||||
|
||||
ref LStr get() {
|
||||
if (!isValid) {
|
||||
assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
|
||||
}
|
||||
return engineState.resources.texts[data];
|
||||
}
|
||||
|
||||
LStr getOr() {
|
||||
return isValid ? get() : LStr();
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (engineState.resources.texts.has(data)) {
|
||||
engineState.resources.texts[data].free();
|
||||
engineState.resources.texts.remove(data);
|
||||
engineState.resources.textNames.remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Texture {
|
||||
|
@ -237,8 +292,35 @@ struct Texture {
|
|||
}
|
||||
}
|
||||
|
||||
// Note: Think about adding a tag to the resource ids. A tag could be used to free only some resources.
|
||||
struct TextureId {
|
||||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isValid() {
|
||||
return data.value != 0 && engineState.resources.textures.has(data);
|
||||
}
|
||||
|
||||
ref Texture get() {
|
||||
if (!isValid) {
|
||||
assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
|
||||
}
|
||||
return engineState.resources.textures[data];
|
||||
}
|
||||
|
||||
Texture getOr() {
|
||||
return isValid ? get() : Texture();
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (engineState.resources.textures.has(data)) {
|
||||
engineState.resources.textures[data].free();
|
||||
engineState.resources.textures.remove(data);
|
||||
engineState.resources.textureNames.remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Font {
|
||||
|
@ -279,38 +361,52 @@ struct Font {
|
|||
|
||||
struct FontId {
|
||||
GenerationalIndex data;
|
||||
}
|
||||
|
||||
struct Audio {
|
||||
Data data;
|
||||
|
||||
alias Sound = rl.Sound;
|
||||
alias Music = rl.Music;
|
||||
alias Data = Variant!(Sound, Music);
|
||||
alias data this;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
Sound sound() {
|
||||
return data.get!Sound();
|
||||
bool isValid() {
|
||||
return data.value != 0 && engineState.resources.fonts.has(data);
|
||||
}
|
||||
|
||||
Music music() {
|
||||
return data.get!Music();
|
||||
ref Font get() {
|
||||
if (!isValid) {
|
||||
assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
|
||||
}
|
||||
return engineState.resources.fonts[data];
|
||||
}
|
||||
|
||||
Font getOr() {
|
||||
return isValid ? get() : Font();
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (engineState.resources.fonts.has(data)) {
|
||||
engineState.resources.fonts[data].free();
|
||||
engineState.resources.fonts.remove(data);
|
||||
engineState.resources.fontNames.remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Sound {
|
||||
Variant!(rl.Sound, rl.Music) data;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isSound() {
|
||||
return data.isKind!Sound;
|
||||
return data.isKind!(rl.Sound);
|
||||
}
|
||||
|
||||
bool isMusic() {
|
||||
return data.isKind!Music;
|
||||
return data.isKind!(rl.Music);
|
||||
}
|
||||
|
||||
bool isEmpty() {
|
||||
if (isSound) {
|
||||
return sound.stream.sampleRate == 0;
|
||||
return data.get!(rl.Sound)().stream.sampleRate == 0;
|
||||
} else {
|
||||
return music.stream.sampleRate == 0;
|
||||
return data.get!(rl.Music)().stream.sampleRate == 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,7 +415,7 @@ struct Audio {
|
|||
if (isSound) {
|
||||
return 0.0f;
|
||||
} else {
|
||||
return rl.GetMusicTimePlayed(music);
|
||||
return rl.GetMusicTimePlayed(data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,34 +424,34 @@ struct Audio {
|
|||
if (isSound) {
|
||||
return 0.0f;
|
||||
} else {
|
||||
return rl.GetMusicTimeLength(music);
|
||||
return rl.GetMusicTimeLength(data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void setVolume(float value) {
|
||||
if (isSound) {
|
||||
rl.SetSoundVolume(sound, value);
|
||||
rl.SetSoundVolume(data.get!(rl.Sound)(), value);
|
||||
} else {
|
||||
rl.SetMusicVolume(music, value);
|
||||
rl.SetMusicVolume(data.get!(rl.Music)(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void setPitch(float value) {
|
||||
if (isSound) {
|
||||
rl.SetSoundPitch(sound, value);
|
||||
rl.SetSoundPitch(data.get!(rl.Sound)(), value);
|
||||
} else {
|
||||
rl.SetMusicPitch(music, value);
|
||||
rl.SetMusicPitch(data.get!(rl.Music)(), value);
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void setPan(float value) {
|
||||
if (isSound) {
|
||||
rl.SetSoundPan(sound, value);
|
||||
rl.SetSoundPan(data.get!(rl.Sound)(), value);
|
||||
} else {
|
||||
rl.SetMusicPan(music, value);
|
||||
rl.SetMusicPan(data.get!(rl.Music)(), value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,16 +461,42 @@ struct Audio {
|
|||
return;
|
||||
}
|
||||
if (isSound) {
|
||||
rl.UnloadSound(sound);
|
||||
rl.UnloadSound(data.get!(rl.Sound)());
|
||||
} else {
|
||||
rl.UnloadMusicStream(music);
|
||||
rl.UnloadMusicStream(data.get!(rl.Music)());
|
||||
}
|
||||
this = Audio();
|
||||
this = Sound();
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioId {
|
||||
struct SoundId {
|
||||
GenerationalIndex data;
|
||||
alias data this;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
bool isValid() {
|
||||
return data.value != 0 && engineState.resources.sounds.has(data);
|
||||
}
|
||||
|
||||
ref Sound get() {
|
||||
if (!isValid) {
|
||||
assert(0, "Index `{}` with generation `{}` does not exist.".format(data.value, data.generation));
|
||||
}
|
||||
return engineState.resources.sounds[data];
|
||||
}
|
||||
|
||||
Sound getOr() {
|
||||
return isValid ? get() : Sound();
|
||||
}
|
||||
|
||||
void free() {
|
||||
if (engineState.resources.sounds.has(data)) {
|
||||
engineState.resources.sounds[data].free();
|
||||
engineState.resources.sounds.remove(data);
|
||||
engineState.resources.soundNames.remove(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Viewport {
|
||||
|
@ -424,37 +546,60 @@ struct Viewport {
|
|||
struct EngineFlags {
|
||||
bool isUpdating;
|
||||
bool isPixelPerfect;
|
||||
bool isVsync;
|
||||
bool isFpsLocked;
|
||||
bool isCursorHidden;
|
||||
}
|
||||
|
||||
struct EngineResources {
|
||||
GenerationalList!LStr textNames;
|
||||
GenerationalList!LStr texts;
|
||||
|
||||
GenerationalList!LStr textureNames;
|
||||
GenerationalList!Texture textures;
|
||||
|
||||
GenerationalList!LStr fontNames;
|
||||
GenerationalList!Font fonts;
|
||||
GenerationalList!Audio audios;
|
||||
|
||||
GenerationalList!LStr soundNames;
|
||||
GenerationalList!Sound sounds;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
void free() {
|
||||
debug println("Ending cleanup:");
|
||||
debug printfln(" Texture count freed: {}", textures.length);
|
||||
foreach (name; textNames.items) {
|
||||
name.free();
|
||||
}
|
||||
textNames.free();
|
||||
foreach (ref text; texts.items) {
|
||||
text.free();
|
||||
}
|
||||
texts.free();
|
||||
|
||||
foreach (name; textureNames.items) {
|
||||
name.free();
|
||||
}
|
||||
textureNames.free();
|
||||
foreach (ref texture; textures.items) {
|
||||
texture.free();
|
||||
}
|
||||
textures.free();
|
||||
|
||||
debug printfln(" Font count freed: {}", fonts.length);
|
||||
foreach (name; fontNames.items) {
|
||||
name.free();
|
||||
}
|
||||
fontNames.free();
|
||||
foreach (ref font; fonts.items) {
|
||||
font.free();
|
||||
}
|
||||
fonts.free();
|
||||
|
||||
debug printfln(" Audio count freed: {}", audios.length);
|
||||
foreach (ref audio; audios.items) {
|
||||
audio.free();
|
||||
foreach (name; soundNames.items) {
|
||||
name.free();
|
||||
}
|
||||
audios.free();
|
||||
soundNames.free();
|
||||
foreach (ref sound; sounds.items) {
|
||||
sound.free();
|
||||
}
|
||||
sounds.free();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,6 +628,7 @@ struct EngineState {
|
|||
Color backgroundColor;
|
||||
LStr tempText;
|
||||
LStr assetsPath;
|
||||
ulong tickCount;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
|
@ -598,63 +744,6 @@ rl.Camera2D toRl(Camera camera) {
|
|||
);
|
||||
}
|
||||
|
||||
bool hasResource(TextureId id) {
|
||||
return engineState.resources.textures.has(id.data);
|
||||
}
|
||||
|
||||
bool hasResource(FontId id) {
|
||||
return engineState.resources.fonts.has(id.data);
|
||||
}
|
||||
|
||||
bool hasResource(AudioId id) {
|
||||
return engineState.resources.audios.has(id.data);
|
||||
}
|
||||
|
||||
ref Texture getResource(TextureId id) {
|
||||
return engineState.resources.textures[id.data];
|
||||
}
|
||||
|
||||
ref Font getResource(FontId id) {
|
||||
return engineState.resources.fonts[id.data];
|
||||
}
|
||||
|
||||
ref Audio getResource(AudioId id) {
|
||||
return engineState.resources.audios[id.data];
|
||||
}
|
||||
|
||||
Texture getResourceOr(TextureId id) {
|
||||
return hasResource(id) ? getResource(id) : Texture();
|
||||
}
|
||||
|
||||
Font getResourceOr(FontId id) {
|
||||
return hasResource(id) ? getResource(id) : Font();
|
||||
}
|
||||
|
||||
Audio getResourceOr(AudioId id) {
|
||||
return hasResource(id) ? getResource(id) : Audio();
|
||||
}
|
||||
|
||||
void unloadResource(TextureId id) {
|
||||
if (engineState.resources.textures.has(id.data)) {
|
||||
engineState.resources.textures[id.data].free();
|
||||
engineState.resources.textures.remove(id.data);
|
||||
}
|
||||
}
|
||||
|
||||
void unloadResource(FontId id) {
|
||||
if (engineState.resources.fonts.has(id.data)) {
|
||||
engineState.resources.fonts[id.data].free();
|
||||
engineState.resources.fonts.remove(id.data);
|
||||
}
|
||||
}
|
||||
|
||||
void unloadResource(AudioId id) {
|
||||
if (engineState.resources.audios.has(id.data)) {
|
||||
engineState.resources.audios[id.data].free();
|
||||
engineState.resources.audios.remove(id.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the opposite flip value.
|
||||
/// The opposite of every flip value except none is none.
|
||||
/// The fallback value is returned if the flip value is none.
|
||||
|
@ -719,12 +808,6 @@ IStr toAssetsPath(IStr path) {
|
|||
return pathConcat(assetsPath, path).pathFormat();
|
||||
}
|
||||
|
||||
/// Loads a text file from the assets folder and returns its contents as a list.
|
||||
/// Can handle both forward slashes and backslashes in file paths.
|
||||
Result!LStr loadText(IStr path) {
|
||||
return readText(path.toAssetsPath());
|
||||
}
|
||||
|
||||
/// Loads a text file from the assets folder and returns its contents as a slice.
|
||||
/// The slice can be safely used until this function is called again.
|
||||
/// Can handle both forward slashes and backslashes in file paths.
|
||||
|
@ -733,6 +816,33 @@ Result!IStr loadTempText(IStr path) {
|
|||
return Result!IStr(engineState.tempText.items, fault);
|
||||
}
|
||||
|
||||
Result!LStr loadRawText(IStr path) {
|
||||
return readText(path.toAssetsPath());
|
||||
}
|
||||
|
||||
/// Loads a text file from the assets folder and returns its contents as a list.
|
||||
/// Can handle both forward slashes and backslashes in file paths.
|
||||
Result!TextId loadText(IStr path) {
|
||||
if (engineState.resources.textNames.length == 0) {
|
||||
engineState.resources.textNames.append(LStr());
|
||||
engineState.resources.texts.append(LStr());
|
||||
}
|
||||
|
||||
foreach (id; engineState.resources.textNames.ids) {
|
||||
if (engineState.resources.textNames[id] == path) {
|
||||
return Result!TextId(TextId(id));
|
||||
}
|
||||
}
|
||||
|
||||
auto result = loadRawText(path);
|
||||
if (result.isSome) {
|
||||
engineState.resources.textNames.append(LStr(path));
|
||||
return Result!TextId(TextId(engineState.resources.texts.append(result.unwrap())));
|
||||
} else {
|
||||
return Result!TextId(Fault.cantFind);
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads an image file (PNG) from the assets folder.
|
||||
/// Can handle both forward slashes and backslashes in file paths.
|
||||
@trusted
|
||||
|
@ -742,8 +852,20 @@ Result!Texture loadRawTexture(IStr path) {
|
|||
}
|
||||
|
||||
Result!TextureId loadTexture(IStr path) {
|
||||
if (engineState.resources.textureNames.length == 0) {
|
||||
engineState.resources.textureNames.append(LStr());
|
||||
engineState.resources.textures.append(Texture());
|
||||
}
|
||||
|
||||
foreach (id; engineState.resources.textureNames.ids) {
|
||||
if (engineState.resources.textureNames[id] == path) {
|
||||
return Result!TextureId(TextureId(id));
|
||||
}
|
||||
}
|
||||
|
||||
auto result = loadRawTexture(path);
|
||||
if (result.isSome) {
|
||||
engineState.resources.textureNames.append(LStr(path));
|
||||
return Result!TextureId(TextureId(engineState.resources.textures.append(result.unwrap())));
|
||||
} else {
|
||||
return Result!TextureId(Fault.cantFind);
|
||||
|
@ -762,6 +884,17 @@ Result!Font loadRawFont(IStr path, uint size, const(dchar)[] runes = []) {
|
|||
}
|
||||
|
||||
Result!FontId loadFont(IStr path, uint size, const(dchar)[] runes = []) {
|
||||
if (engineState.resources.fontNames.length == 0) {
|
||||
engineState.resources.fontNames.append(LStr());
|
||||
engineState.resources.fonts.append(Font());
|
||||
}
|
||||
|
||||
foreach (id; engineState.resources.fontNames.ids) {
|
||||
if (engineState.resources.fontNames[id] == path) {
|
||||
return Result!FontId(FontId(id));
|
||||
}
|
||||
}
|
||||
|
||||
auto result = loadRawFont(path, size, runes);
|
||||
if (result.isSome) {
|
||||
return Result!FontId(FontId(engineState.resources.fonts.append(result.unwrap())));
|
||||
|
@ -770,30 +903,41 @@ Result!FontId loadFont(IStr path, uint size, const(dchar)[] runes = []) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Loads a audio file (WAV, OGG, MP3) from the assets folder.
|
||||
/// Loads a sound file (WAV, OGG, MP3) from the assets folder.
|
||||
/// Can handle both forward slashes and backslashes in file paths.
|
||||
@trusted
|
||||
Result!Audio loadRawAudio(IStr path) {
|
||||
auto value = Audio();
|
||||
Result!Sound loadRawSound(IStr path) {
|
||||
auto value = Sound();
|
||||
if (path.endsWith(".wav")) {
|
||||
value.data = rl.LoadSound(path.toAssetsPath().toCStr().unwrapOr());
|
||||
} else {
|
||||
value.data = rl.LoadMusicStream(path.toAssetsPath().toCStr().unwrapOr());
|
||||
}
|
||||
return Result!Audio(value, value.isEmpty.toFault(Fault.cantFind));
|
||||
return Result!Sound(value, value.isEmpty.toFault(Fault.cantFind));
|
||||
}
|
||||
|
||||
Result!AudioId loadAudio(IStr path) {
|
||||
auto result = loadRawAudio(path);
|
||||
Result!SoundId loadSound(IStr path) {
|
||||
if (engineState.resources.soundNames.length == 0) {
|
||||
engineState.resources.soundNames.append(LStr());
|
||||
engineState.resources.sounds.append(Sound());
|
||||
}
|
||||
|
||||
foreach (id; engineState.resources.soundNames.ids) {
|
||||
if (engineState.resources.soundNames[id] == path) {
|
||||
return Result!SoundId(SoundId(id));
|
||||
}
|
||||
}
|
||||
|
||||
auto result = loadRawSound(path);
|
||||
if (result.isSome) {
|
||||
return Result!AudioId(AudioId(engineState.resources.audios.append(result.unwrap())));
|
||||
return Result!SoundId(SoundId(engineState.resources.sounds.append(result.unwrap())));
|
||||
} else {
|
||||
return Result!AudioId(Fault.cantFind);
|
||||
return Result!SoundId(Fault.cantFind);
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
Result!Viewport loadViewport(int width, int height) {
|
||||
Result!Viewport loadRawViewport(int width, int height) {
|
||||
auto value = rl.LoadRenderTexture(width, height).toPopka();
|
||||
return Result!Viewport(value, value.isEmpty.toFault());
|
||||
}
|
||||
|
@ -804,6 +948,10 @@ Fault saveText(IStr path, IStr text) {
|
|||
return writeText(path.toAssetsPath(), text);
|
||||
}
|
||||
|
||||
void freeResources() {
|
||||
engineState.resources.free();
|
||||
}
|
||||
|
||||
/// Opens a window with the given size and title.
|
||||
/// You should avoid calling this function manually.
|
||||
@trusted
|
||||
|
@ -811,13 +959,12 @@ void openWindow(int width, int height, IStr title = "Popka") {
|
|||
if (rl.IsWindowReady) {
|
||||
return;
|
||||
}
|
||||
rl.SetConfigFlags(rl.FLAG_VSYNC_HINT | rl.FLAG_WINDOW_RESIZABLE);
|
||||
rl.SetConfigFlags(rl.FLAG_WINDOW_RESIZABLE | rl.FLAG_VSYNC_HINT);
|
||||
rl.SetTraceLogLevel(rl.LOG_ERROR);
|
||||
rl.InitWindow(width, height, title.toCStr().unwrapOr());
|
||||
rl.InitAudioDevice();
|
||||
rl.SetExitKey(rl.KEY_NULL);
|
||||
lockFps(60);
|
||||
engineState.flags.isVsync = true;
|
||||
rl.SetTargetFPS(60);
|
||||
engineState.backgroundColor = gray2;
|
||||
engineState.fullscreenState.lastWindowSize = Vec2(width, height);
|
||||
}
|
||||
|
@ -826,11 +973,11 @@ void openWindow(int width, int height, IStr title = "Popka") {
|
|||
/// This function will return when the loop function returns true.
|
||||
/// You should avoid calling this function manually.
|
||||
@trusted
|
||||
void updateWindow(bool function() updateFunc) {
|
||||
static bool function() @trusted @nogc nothrow __updateFunc;
|
||||
void updateWindow(bool function(float dt) updateFunc) {
|
||||
static bool function(float _dt) @trusted @nogc nothrow _updateFunc;
|
||||
|
||||
@trusted @nogc nothrow
|
||||
static bool __updateWindow() {
|
||||
static bool _updateWindow() {
|
||||
// Begin drawing.
|
||||
if (isResolutionLocked) {
|
||||
rl.BeginTextureMode(engineState.viewport.toRl());
|
||||
|
@ -840,7 +987,8 @@ void updateWindow(bool function() updateFunc) {
|
|||
rl.ClearBackground(engineState.backgroundColor.toRl());
|
||||
|
||||
// The main loop.
|
||||
auto result = __updateFunc();
|
||||
auto result = _updateFunc(deltaTime);
|
||||
engineState.tickCount = (engineState.tickCount + 1) % typeof(engineState.tickCount).max;
|
||||
|
||||
// End drawing.
|
||||
if (isResolutionLocked) {
|
||||
|
@ -874,7 +1022,7 @@ void updateWindow(bool function() updateFunc) {
|
|||
if (engineState.viewport.isLockResolutionQueued) {
|
||||
engineState.viewport.isLockResolutionQueued = false;
|
||||
engineState.viewport.free();
|
||||
engineState.viewport.data = loadViewport(engineState.viewport.targetWidth, engineState.viewport.targetHeight).unwrapOr();
|
||||
engineState.viewport.data = loadRawViewport(engineState.viewport.targetWidth, engineState.viewport.targetHeight).unwrapOr();
|
||||
} else if (engineState.viewport.isUnlockResolutionQueued) {
|
||||
engineState.viewport.isUnlockResolutionQueued = false;
|
||||
engineState.viewport.free();
|
||||
|
@ -900,20 +1048,20 @@ void updateWindow(bool function() updateFunc) {
|
|||
}
|
||||
|
||||
// Maybe bad idea, but makes life of no-attribute people easier.
|
||||
__updateFunc = cast(bool function() @trusted @nogc nothrow) updateFunc;
|
||||
_updateFunc = cast(bool function(float _dt) @trusted @nogc nothrow) updateFunc;
|
||||
engineState.flags.isUpdating = true;
|
||||
|
||||
version(WebAssembly) {
|
||||
static void __updateWindowWeb() {
|
||||
if (__updateWindow()) {
|
||||
static void _updateWindowWeb() {
|
||||
if (_updateWindow()) {
|
||||
engineState.flags.isUpdating = false;
|
||||
rl.emscripten_cancel_main_loop();
|
||||
}
|
||||
}
|
||||
rl.emscripten_set_main_loop(&__updateWindowWeb, 0, 1);
|
||||
rl.emscripten_set_main_loop(&_updateWindowWeb, 0, 1);
|
||||
} else {
|
||||
while (true) {
|
||||
if (rl.WindowShouldClose() || __updateWindow()) {
|
||||
if (rl.WindowShouldClose() || _updateWindow()) {
|
||||
engineState.flags.isUpdating = false;
|
||||
break;
|
||||
}
|
||||
|
@ -949,25 +1097,6 @@ float masterVolume() {
|
|||
return rl.GetMasterVolume();
|
||||
}
|
||||
|
||||
/// Returns true if the FPS is locked.
|
||||
bool isFpsLocked() {
|
||||
return engineState.flags.isFpsLocked;
|
||||
}
|
||||
|
||||
/// Locks the FPS to the given value.
|
||||
@trusted
|
||||
void lockFps(int target) {
|
||||
engineState.flags.isFpsLocked = true;
|
||||
rl.SetTargetFPS(target);
|
||||
}
|
||||
|
||||
/// Unlocks the FPS.
|
||||
@trusted
|
||||
void unlockFps() {
|
||||
engineState.flags.isFpsLocked = false;
|
||||
rl.SetTargetFPS(0);
|
||||
}
|
||||
|
||||
/// Returns true if the resolution is locked.
|
||||
bool isResolutionLocked() {
|
||||
return !engineState.viewport.isEmpty;
|
||||
|
@ -977,7 +1106,7 @@ bool isResolutionLocked() {
|
|||
@trusted
|
||||
void lockResolution(int width, int height) {
|
||||
if (!engineState.flags.isUpdating) {
|
||||
engineState.viewport.data = loadViewport(width, height).unwrap();
|
||||
engineState.viewport.data = loadRawViewport(width, height).unwrap();
|
||||
} else {
|
||||
engineState.viewport.targetWidth = width;
|
||||
engineState.viewport.targetHeight = height;
|
||||
|
@ -1150,6 +1279,10 @@ double elapsedTime() {
|
|||
return rl.GetTime();
|
||||
}
|
||||
|
||||
long elapsedTickCount() {
|
||||
return engineState.tickCount;
|
||||
}
|
||||
|
||||
@trusted
|
||||
float deltaTime() {
|
||||
return rl.GetFrameTime();
|
||||
|
@ -1160,30 +1293,6 @@ Vec2 deltaMouse() {
|
|||
return toPopka(rl.GetMouseDelta());
|
||||
}
|
||||
|
||||
@trusted
|
||||
void attachCamera(ref Camera camera) {
|
||||
if (camera.isAttached) {
|
||||
return;
|
||||
}
|
||||
camera.isAttached = true;
|
||||
auto temp = camera.toRl();
|
||||
if (isPixelPerfect) {
|
||||
temp.target.x = floor(temp.target.x);
|
||||
temp.target.y = floor(temp.target.y);
|
||||
temp.offset.x = floor(temp.offset.x);
|
||||
temp.offset.y = floor(temp.offset.y);
|
||||
}
|
||||
rl.BeginMode2D(temp);
|
||||
}
|
||||
|
||||
@trusted
|
||||
void detachCamera(ref Camera camera) {
|
||||
if (camera.isAttached) {
|
||||
camera.isAttached = false;
|
||||
rl.EndMode2D();
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
Vec2 measureTextSize(Font font, IStr text, DrawOptions options = DrawOptions()) {
|
||||
if (font.isEmpty || text.length == 0) {
|
||||
|
@ -1310,65 +1419,65 @@ Vec2 wasd() {
|
|||
}
|
||||
|
||||
@trusted
|
||||
void playAudio(Audio audio) {
|
||||
if (audio.isEmpty) {
|
||||
void playSound(Sound sound) {
|
||||
if (sound.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio.isSound) {
|
||||
rl.PlaySound(audio.sound);
|
||||
if (sound.isSound) {
|
||||
rl.PlaySound(sound.data.get!(rl.Sound)());
|
||||
} else {
|
||||
rl.PlayMusicStream(audio.music);
|
||||
rl.PlayMusicStream(sound.data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void updateAudio(Audio audio) {
|
||||
if (audio.isEmpty) {
|
||||
void updateSound(Sound sound) {
|
||||
if (sound.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio.isMusic) {
|
||||
rl.UpdateMusicStream(audio.music);
|
||||
if (sound.isMusic) {
|
||||
rl.UpdateMusicStream(sound.data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void pauseAudio(Audio audio) {
|
||||
if (audio.isEmpty) {
|
||||
void pauseSound(Sound sound) {
|
||||
if (sound.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio.isSound) {
|
||||
rl.PauseSound(audio.sound);
|
||||
if (sound.isSound) {
|
||||
rl.PauseSound(sound.data.get!(rl.Sound)());
|
||||
} else {
|
||||
rl.PauseMusicStream(audio.music);
|
||||
rl.PauseMusicStream(sound.data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void resumeAudio(Audio audio) {
|
||||
if (audio.isEmpty) {
|
||||
void resumeSound(Sound sound) {
|
||||
if (sound.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio.isSound) {
|
||||
rl.ResumeSound(audio.sound);
|
||||
if (sound.isSound) {
|
||||
rl.ResumeSound(sound.data.get!(rl.Sound)());
|
||||
} else {
|
||||
rl.ResumeMusicStream(audio.music);
|
||||
rl.ResumeMusicStream(sound.data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
@trusted
|
||||
void stopAudio(Audio audio) {
|
||||
if (audio.isEmpty) {
|
||||
void stopSound(Sound sound) {
|
||||
if (sound.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (audio.isSound) {
|
||||
rl.StopSound(audio.sound);
|
||||
if (sound.isSound) {
|
||||
rl.StopSound(sound.data.get!(rl.Sound)());
|
||||
} else {
|
||||
rl.StopMusicStream(audio.music);
|
||||
rl.StopMusicStream(sound.data.get!(rl.Music)());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -431,11 +431,21 @@ struct Rectangle
|
|||
this.height = height;
|
||||
}
|
||||
|
||||
pragma(inline, true)
|
||||
this(float width, float height) {
|
||||
this(0.0f, 0.0f, width, height);
|
||||
}
|
||||
|
||||
pragma(inline, true)
|
||||
this(Vector2 position, Vector2 size) {
|
||||
this(position.x, position.y, size.x, size.y);
|
||||
}
|
||||
|
||||
pragma(inline, true)
|
||||
this(Vector2 size) {
|
||||
this(0.0f, 0.0f, size.x, size.y);
|
||||
}
|
||||
|
||||
pragma(inline, true)
|
||||
this(float x, float y, Vector2 size) {
|
||||
this(x, y, size.x, size.y);
|
||||
|
|
|
@ -33,9 +33,17 @@ struct TileMap {
|
|||
return Vec2(tileWidth, tileHeight);
|
||||
}
|
||||
|
||||
int width() {
|
||||
return cast(int) (colCount * tileWidth);
|
||||
}
|
||||
|
||||
int height() {
|
||||
return cast(int) (rowCount * tileHeight);
|
||||
}
|
||||
|
||||
/// Returns the size of the tile map.
|
||||
Vec2 size() {
|
||||
return tileSize * Vec2(colCount, rowCount);
|
||||
return Vec2(width, height);
|
||||
}
|
||||
|
||||
Fault parse(IStr csv, int tileWidth, int tileHeight) {
|
||||
|
@ -90,7 +98,7 @@ Result!TileMap toTileMap(IStr csv, int tileWidth, int tileHeight) {
|
|||
return Result!TileMap(value, fault);
|
||||
}
|
||||
|
||||
Result!TileMap loadTileMap(IStr path, int tileWidth, int tileHeight) {
|
||||
Result!TileMap loadRawTileMap(IStr path, int tileWidth, int tileHeight) {
|
||||
auto temp = loadTempText(path);
|
||||
if (temp.isNone) {
|
||||
return Result!TileMap(temp.fault);
|
||||
|
@ -99,8 +107,8 @@ Result!TileMap loadTileMap(IStr path, int tileWidth, int tileHeight) {
|
|||
}
|
||||
|
||||
void drawTile(Texture texture, Vec2 position, int tileID, int tileWidth, int tileHeight, DrawOptions options = DrawOptions()) {
|
||||
auto gridWidth = cast(int) (texture.size.x / tileWidth);
|
||||
auto gridHeight = cast(int) (texture.size.y / tileHeight);
|
||||
auto gridWidth = cast(int) (texture.width / tileWidth);
|
||||
auto gridHeight = cast(int) (texture.height / tileHeight);
|
||||
if (gridWidth == 0 || gridHeight == 0) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue