Featuring @DreamscaperGame, our #indiegame leaving Early Access in August. It is a deceptively simple game that uses low-poly models & textures but hi-fidelity shading and post-processing.
(1/X)
Now, we had a couple of goals for Dreamscaper:
1) Launch one of the best-looking third-party titles on the platform. 2) As an action game, everything (especially parry/dodge) needs to feel responsive. 3) Let players control the trade-off between fidelity and performance.
(2/X)
RE: 1 & 3 - Working with my insanely talented friend @paulsvoboda we decided that, by default, we would first get the game running at near-native resolution with the highest fidelity possible at 30 FPS. We are extremely proud of how far we've pushed the device's hardware.
(3/X)
That said, we also knew how important 60 FPS is for a lot of ARPG players (including myself), so we spent months on hundreds of optimizations to get an unwavering 60 FPS at a slightly more modest visual quality. Players can toggle this "Performance Mode" on in the options.
(4/X)
RE: 2 - By default, UE4's game thread will sync with the game thread. This can result in fairly high input latency (upwards of 130ms). This results in fairly sluggish controls, which is often a complaint players have about certain games releasing on the platform.
(5/X)
First thing to do: look into the low-latency frame syncing options that the engine provides.
By syncing game thread with swap chain present, we got input latency down to ~33ms in 60 FPS mode and ~66ms in 30 FPS mode while keeping fps steady.
(6/X)
Now, 33ms of latency is pretty hard to detect, but 66ms of latency certainly makes a difference for specific game actions that have very small trigger windows.
For example, parrying in our game requires shielding within 50ms. That's hard to nail with any input latency.
(7/X)
This is where we get creative. We thought: how do we make sure parries and dodges feel as if they have no input latency? Simple: you offset the timing windows by the input latency.
Well, easier said than done. That offset needs to be done *in the past* ...
(8/X)
Why the past? The enemy telegraph plays, the player defends at the exact moment they should, but the hit registers before the input is received.
Boink. Poor Cassidy gets hit by an enemy despite the player's perfect timing.
(9/X)
The solution? I restructured all of our hit registration code to allow for double buffering of certain hits. Specifically enemy hits on the player in the Nintendo Switch build.
All of those buffered hits wait two frames before being registered.
(10/X)
The result?
Identical parry and dodge timing windows, regardless of input latency.
Sometimes the most effective technical ideas are the simplest! I hope this thread has given you some ideas, and if you've enjoyed it be sure to retweet to help our game get more eyeballs.
(11/X)
Before someone jumps in the comments, I realize "hi-fidelity" is a misnomer here. I meant it as a synonym for "high quality" but I don't word as good as I math!
*game thread will sync with the rendering thread
Some corrections (because I don't word good):
(1,3) *game thread syncs with the rendering thread (5) *high-fidelity -> high-quality
Another mistake that a Redditor pointed out:
(5) upwards of 130ms -> up to 130ms
• • •
Missing some Tweet in this thread? You can try to
force a refresh
(1/9) A thread on the most important learnings I share with developers who ask me for advice on launching their debut indie game on Steam (in 2020 or beyond):
(2/9) Your pre-launch wishlist count matters. It impact the surfacing of your game store-wide, and a huge portion of your sales are going to come from wishlist conversions.
Steam users who have wishlisted your game get an e-mail when your game launches or is discounted at >=20%.
(3/9) The most lucrative section your game can be in is in "Top Selling" globally or regionally. These are automatically calculated very frequently based on revenue velocity so it's important to generate sudden massive spikes in sales which require large wishlists.