Three years ago, when I started working on Idlenet, I made the mistake of using
a small base resolution. Thankfully, my GameMaker days were behind me and I
wasn’t using room sizes of 640x360px. 720p was the chosen base target, and since I
used a pixel-perfect bitmap styled font,
the game needs to use only integer scaling at larger/smaller resolutions.
Nowadays, modern games have fancy DLSS and FSR and AI turbo resolution
scaling techniques, or what have you, but having the game appear blurry at all,
especially when set to resolutions besides 720p wasn’t going to cut it, since a
majority of Idlenet’s text is in a font that’s one pixel wide, so it was going
to look like shit no matter what filter that isn’t integer is used, so
love.graphics.scale
was out of the picture, since I eventually wanted
granularity in letting the player change the game’s resolution (I would know,
since I would frequently run Idling to Rule the Gods at its lowest resolution
to reduce overhead while in AFK mode) while having the game look presentable,
why many incremental games don’t do something similar (especially Unity-based
games) really grinds my gears.
That means, the game should support and account for LÖVE’s graphics
filters. By default, Idlenet will run with integer scaling with no filters
applied at its base resolution, however, the player is free to resize the
window however they want, but the catch is that the game will not stretch,
instead all the elements remaining fixed in 16:9, with the rest of the space
being filled, not by pitch black, but by an appropriate background color that
can be applied by theming, it’s for the sake of the font. Enter Resolution
Solution, originally by
GVovkiv on the LÖVE2D forums, but they have since deleted their GitHub account,
but I’m still using it because it uses the Unlicense,
and the developer uploaded the ‘final’(?) version of the code on the forum
thread in January of this year. It has the best documentation and ease of use
out of all the libraries I’ve tried. That includes
push and
maid64. Though
SYSL-Pixel is a solid contender, it
hasn’t been updated in five years (unlike SYSL-Text, which still gets fixes),
and the window resizing is less than ideal on Linux. Anyways, plugging in
GVovkiv’s library was mostly painless.
-- love.load()
game.rs = require("lib.resolution_solution")
game.rs.conf({
game_width = 1280,
game_height = 720,
scale_mode = 1 -- integer scaling
})
game.fullscreen = false
game.window_flags = {
resizable = true,
vsync = -1,
highdpi = true,
usedpiscale = false, -- this gets toggled later if getDPIScale() == 2
}
game.rs.setMode(1280, 720, game.window_flags)
So the game’s default window flags get sent over to the library and preps for
scaling. Not included above is the usage of love.window.getDPIScale()
which
returns 2.0
if a Retina or similar screen is detected, so the game window
instead gets launched in 1440p and we can use usedpiscale = true
. The
examples are somewhat simplified from their actual implementation. Idlenet’s UI
library SUIT needed some changes to account for
the library scaling the game window when calculating the mouse position (for UI
element hit detection or whatever). Without it, the player would have to do the
thing that happens in some games when they bug out and think your cursor is in
a position that it was in when it was at a different resolution, until you
refresh the window in a safer way. I went and changed that too:
-- lib/suit/core.lua
-- state update
function suit:enterFrame()
if not self.mouse_button_down then
self.active = nil
elseif self.active == nil then
self.active = NONE
end
self.hovered_last, self.hovered = self.hovered, nil
-- idlenet: scaling support with resolution_solution (rs)
self:updateMouse(
Idlenet.rs.to_game(love.mouse.getX()),
Idlenet.rs.to_game(nil, love.mouse.getY()), love.mouse.isDown(1))
-- end
self.key_down, self.textchar = nil, ""
self:grabKeyboardFocus(NONE)
self.hit = nil
end
In the game’s option menu, the window resolution can be changed in a single line
of code, game.rs.setMode(2560, 1440, game.window_flags)
. Using love.resize
on its own would not adapt the UI libraries or positioning code correctly, but
rs
resolves it by adding game.rs.resize(w - (w % 2), h - (h % 2))
to
love.resize
. Toggling between scaling filters on the fly was the next
challenge. Until implementing these changes, Idlenet didn’t use LÖVE’s
Canvas, instead the game was a cobbled together,
nested layout, declarative maze of UI calls, and tables of coordinates passed to
one another, drawn every frame as they go, now, since I was trying to apply
what was effectively a new shader in real-time w/r/t resolution solution, it
was the perfect time to use it.
The first attempt without the canvas was adding a toggle key to
switch between the two filters, linear
and nearest
, but when the time came
to test the initial implementation, it didn’t change, despite the game
responding to the keypress, and upon further investigation, it turned out the
window needed to refresh entirely before a new filter can be applied, and I
needed it to refresh so that the filter can change, from nearest
to linear
,
and once that was finished I could start on testing out the aspect ratio
changes by freely resizing the window, but the resolution solution, the
library, made everything clear, and so for now, changing the filter requires
only the single press of a key, and Idlenet will turn beautifully blurry, an
estimation of jagged pixels at variable resolutions, smearing the Tamzen bitmap
style font, sacrificing readability for the convenience of conforming the game
to any archaic window layout that the player desires, and this is only if the
player decides not to conform to the integer scaling required for the Tamzen
font to remain readable, it would be a bold two pixels wide at resolutions
above 1440p, a natural upscaling from the base resolution of 720p, and the
effect of toggling the filter much like taking off one’s glasses, or something
to that effect. I’ll smooth it out once I finish the game’s options menu.
function love.draw()
game.ui_theme.canvas = love.graphics.newCanvas(game.rs.get_game_size())
game.ui_theme.filter = game.ui_theme.aliasing and "linear" or "nearest"
game.ui_theme.canvas:setFilter(game.ui_theme.filter, game.ui_theme.filter)
love.graphics.setCanvas(game.ui_theme.canvas) -- start drawing to canvas
-- UI draw calls ...
IdlenetSuit:draw()
love.graphics.setCanvas() -- done drawing to canvas
game.rs.push()
love.graphics.draw(game.ui_theme.canvas) -- scale the canvas
game.rs.pop()
end
Limited time events are hell. For further reading, refer to my post about
treatmills and idle games. I simply could not help myself from adding what I
believe to be an “inconsequential” mechanic to a core resource in Idlenet: time
limiting access to a subset of loot types in the battle system. When the player
character auto-battles enemies in Idlenet’s “dungeons”, there is a chance of
said time-limited item to drop for the player. If it does, it’s in the
“pending” part of the player’s inventory, and it gets wiped out after a time
limit.
There’s no beating around the bush: this is a shameless attempt at
rewarding players for checking the game every ten minutes, it’s ten real
minutes, not ten minutes in the self-contained universe or reality of the game,
and it might not be ten minutes once feedback starts to be gathered, but my
intentions behind it were simply creating scarcity in a crucial resource that
would ultimately speed up the early game, similar to Kittens Game, then the
player researches the calendar, the
threat of the winter season
becomes clear, and catnip levels must be kept at a surplus to meet the demands
of the hungry kittens in the winter seasons, and the player needs to constantly
check back if it’s winter yet, if we still have enough catnip, or else the
kittens die, but in Idlenet, the operating system is so shitty it can only
store ‘memory’ for ten real minutes, the game explains to the player in a large
tooltip with red warning text, and when the timer expires, the timer’s progress
bar goes red and the number goes down to zero. As with all things, in these
incremental games, it’s only bad for the early game, and it’s planned to make
it all automated with ‘extensions’, the upgrades introduced to the player once
they have settled in with the core loop of the game, when the player’s
inventory is of note, though it cannot be ignored that it is not passive or
idle at this stage, I once complained about that when playing a game called
Idle Pins, the automation is so unbearably slow, that you’re not really
idling at all, and so I believe the line between engagement and simply
manipulating the player to be blurred in this genre,1 it’s hard to be
avoided without having to meticulously test psychological responses to a video
game, that’s simply outside of my scope, though to me, the core of the genre is
the art, a delicate art, of getting players to only check on a game, for
fifteen minutes at a time, coming back whenever they feel like it, not games
that make you constantly babysit tasks that prove ultimately meaningless, for
once the player is hundreds of hours into a save, an entirely different
mechanic may have rendered such struggles obscelete, and the issue of me
implementing such nagging mechanics, it will be minimized as much as I can to
make it permissible as a system to encourage, most likely by increasing the
amount of time during this pending period, so that the progress bar increases
at such a small rate, that the player will naturally pay attention to it,
without any extra fluff or popups or notifications. Such is the fine line
between gauging player retention, that is, keeping players coming back, and
manipulating them into doing so, such as when a player is told that not
claiming a resource at a reasonable frequency means that they will be 0.05 per
cent slower and will not maintain pace with the online community-written wiki
guide, and so I am ensuring that Idlenet does not fall in the same hole, even
though it’s not a parody game like my previous work Crackhead Jack was, I
simply cannot implement shitty mechanics and keep it in for the sake of its
parody, so in the end, the only downside to forgetting about this time limit,
is that the player is set back by another few real minutes until a new enemy is
defeated and drops another handful of items, and at this stage, if the player
were to get 50 units of such a scarce item in ten minute intervals, the player
likely already has the facilities required to automatically collect the
resources, and so its overall effect is subtle. Anyways,2 I’ve implemented a
slowly decreasing progress bar to indicate the time you have remaining, with
only an estimate given to the player.
Anecdotal evidence. ↩
I have yet to come up with a better line to end this post with in this style. Such vignettes in Speedboat often feel like its subtext jumps at you in its final statements, only for it to be discarded to move on to the next observation. “What is the point,” says Jen in Speedboat, not asked as a question but rather a simple sentence, offering a different view of what is the point, the point being what, the point is what is right, or it is what happens before us, as Jen says in ‘Brownstone’, “You cannot be forever watching for the point, or you lose the simplest thing,” and I believe the timelessness of the novel illustrates a subconscious acknowledgement of the looming, growing skepticism of everything up and including to, what we consider the ‘point’, the point of the story, the point of the novel, the point of the vignette, what is the point of Jen observing and expressing such moments, what is the moment, what is the point for it to be organized under ‘Brownstone’, a collection of stories, observations, writings, collected thoughts, to which Adler says, Some of it was real, what, then, is the point, surrounding the building in which Jen lives in New York City, the bustling city, with so much to see, that the point is left up in the air, up to the writer, whose doubts and skepticisms cast over their observations and the matter-of-factness of the observations, some may call it reporting, or a scoop, to where they call themselves a journalist, God forbid a writer, for Jen says in the opening story ‘Castling’, Writers drink, Writers rant, Writers phone, Writers sleep, I have met very few writers who write at all. ↩
comments? kirby(a)onlytomorrow.net