<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wikidot="http://www.wikidot.com/rss-namespace">

	<channel>
		<title>GyroWiki Blog</title>
		<link>http://gyrowiki.wikidot.com</link>
		<description>Use the gyroscope in your PlayStation 5, PlayStation 4, and Switch controllers in PC games. Learn best practices for developing these features for your console games.</description>
				<copyright></copyright>
		<lastBuildDate>Sat, 06 Jun 2026 16:41:55 +0000</lastBuildDate>
		
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:mouse-like-precision-no-aim-assist-gcap22-presentation</guid>
				<title>Mouse-like Precision, No Aim Assist - GCAP22 Presentation</title>
				<link>http://gyrowiki.wikidot.com/blog:mouse-like-precision-no-aim-assist-gcap22-presentation</link>
				<description>

&lt;p&gt;Can we close the gap between mouse players and controller players &lt;em&gt;without&lt;/em&gt; having the game artificially help the player with their aiming? Is there a natural, easy to pick up, fun to master, mouse-like input on standard modern controllers?&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 06 Oct 2022 12:15:16 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Can we close the gap between mouse players and controller players <em>without</em> having the game artificially help the player with their aiming? Is there a natural, easy to pick up, fun to master, mouse-like input on standard modern controllers?</p> <p><strong>Yes</strong>. The <strong><em>gyro</em></strong>.</p> <p>Some games offer inconsistent experiences with gyro controls. But the gyro gaming community have been enjoying well-defined standards for <em>great</em> gyro controls for <em>years</em>. These standards have been proven by tools like <a href="https://github.com/Electronicks/JoyShockMapper">JoyShockMapper</a> by countless player hours, and documented in detail for every game developer to use here on GyroWiki.</p> <p>The presentation below is the most comprehensive overview of what good gyro controls look like today. Make this your checklist for your own implementation. For a deeper dive into specific design and implementation details, these slides include links to the original articles right here on GyroWiki.</p> <p>I presented this talk at <a href="https://gcap.com.au/">GCAP22</a> based on my extensive experience implementing controls like these, from humble but popular open source tools to AAA games played by <a href="https://www.epicgames.com/fortnite/en-US/news/gyro-aiming-and-flick-stick-come-to-fortnite-in-v19-30-more-controller-options">millions upon millions of players</a>.</p> <p>Watch the full talk here:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:mouse-like-precision-no-aim-assist-gcap22-presentation/html/fec7f7c56d505349313951ff004df43d94658888-16322571391858528714" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Follow along with updated slides here:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:mouse-like-precision-no-aim-assist-gcap22-presentation/html/6cf722eb877fdc629ef940559b2302e5f89ebb68-11191728261038906801" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls</guid>
				<title>7 Building Blocks for Better Controls</title>
				<link>http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls</link>
				<description>

&lt;p&gt;Every established gamer has their own set of &lt;em&gt;building blocks&lt;/em&gt; that they’ve become comfortable using to control games. These building blocks are the control elements that players have learned to use in other games and expect to use when they pick up a new game. When designing controls, we want players to learn our game’s controls quickly. We want players to be thinking about what their character can do, not thinking about how to use their controller/keyboard/touchscreen/mouse to make the character do it. So when designing the controls for a new game, we tend to reuse building blocks from other games. We ask questions like: What are we doing similar to other games? How will our target audience expect to be able to do this?&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 10 May 2022 02:20:05 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Every established gamer has their own set of <em>building blocks</em> that they’ve become comfortable using to control games. These building blocks are the control elements that players have learned to use in other games and expect to use when they pick up a new game. When designing controls, we want players to learn our game’s controls quickly. We want players to be thinking about what their character can do, not thinking about how to use their controller/keyboard/touchscreen/mouse to make the character do it. So when designing the controls for a new game, we tend to reuse building blocks from other games. We ask questions like: What are we doing similar to other games? How will our target audience expect to be able to do this?</p> <p>Consider, for example, using WASD to move with a keyboard. Using these keys in particular on a keyboard is by no means <em>natural</em> – it took <a href="https://www.pcgamer.com/au/how-wasd-became-the-standard-pc-control-scheme/">a long time and many games</a> for this convention to become established. But now it is used ubiquitously in PC games today. It’s so common that we have keyboards that highlight those keys in particular, and streaming overlays that only show the left half of the keyboard.</p> <p>On gamepads we have other building blocks, and they determine how we steer vehicles, how we select items or weapons, and how we control the camera in 3D space. But we limit ourselves by leaning too much on these conventions – by using these building blocks and <em>only</em> these building blocks. Consider the following three truths:</p> <ol> <li>First-time gamers begin with no building blocks of their own. Their first game experience is not benefited by whether or not its controls rely on common conventions. <strong>Sometimes alternative building blocks are easier to learn</strong>.</li> <li>Disabilities (whether temporary or permanent) can make it difficult or impossible for players to enjoy playing with more conventional building blocks. <strong>Sometimes alternative building blocks are the only way one can enjoy a game</strong>.</li> <li>Common building blocks can constrain the player in undesirable ways. They can slow the player down or increase the amount of conscious effort required to use the game’s systems effectively or naturally. <strong>Sometimes alternative building blocks are even better suited for skillful play</strong>.</li> </ol> <p>Therefore, offering players alternative control schemes may benefit newcomers, players with disabilities, and enthusiasts looking to maximise their skill in a game. Some unconventional options can even benefit all three types of player at once!</p> <p>As an input specialist, my job is to design and often implement control schemes in games. I’m best known for my work on <em>Fortnite</em>, implementing unconventional controls that give players a nearly <a href="https://www.epicgames.com/fortnite/en-US/news/gyro-aiming-and-flick-stick-come-to-fortnite-in-v19-30-more-controller-options?sessionInvalidated=true">mouse-like experience on a controller</a> without any aim assist. <em>Fortnite</em> players are still able to enjoy the game as they’re used to with the default controls, but for those who are willing to play with these less conventional building blocks, <em>Fortnite</em> offers precision and agility that is peerless in the console space (for now), and goes a long way towards letting mouse and controller players play with each other without feeling significantly advantaged or disadvantaged.</p> <p>We’ll get into that stuff towards the end of this article.</p> <p>But while gyro controls are a specialty of mine, most of my professional work is not on those kinds of controls specifically. There are plenty of other ways to improve upon conventional controls, so let’s start elsewhere as we look at seven building blocks for better controls:</p> <ol> <li>2D Steering</li> <li>Winding Stick Steering</li> <li>Motion Steering</li> <li>Chorded Item Selection</li> <li>Motion/Gyro Pointing</li> <li>Motion/Gyro Aiming</li> <li>Flick Stick</li> </ol> <p>Sometimes I’ll draw from real examples already found in a few games. When I can’t, I’ll have examples that I’ve forced into games using <a href="https://github.com/Electronicks/JoyShockMapper">JoyShockMapper</a> (an open source input remapper I created but now in the capable hands of Electronicks) and my controller overlay JoyShockOverlay (which will be publicly available later this year, hopefully).</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/70a0f2adb4aa85a581722e5d800b044608422ccb-1359931643576980775" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>This means that even if I haven’t found an example of a game doing exactly what I’m talking about, I can still use these tools to demonstrate it in real games. And for those features that are already in a number of games, they’re still woefully underused. If they are relevant to your game, I urge you to future-proof your game by adding these options to it.</p> <p>Oh, and the point of this article is that I’d like to see these options (and other alternative ways to play) in <em>your games</em>. You hopefully already know that copying simple game mechanics isn’t plagiarism. It isn’t theft. It isn’t like selling someone else’s story, copying someone else’s photo, uploading someone else’s video and claiming it as your own. There are no protections here. You just get to use whatever you want. And games are all the better for it. In that spirit, I want to make it clear that any code examples I’m including here are specifically for you to use how you please, credit or no credit. They’re all very simple ideas, and you probably won’t use my code verbatim. And if you do, that’s cool, too.</p> <p>With that out of the way, let’s get started.</p> <h2><span>1. 2D Steering</span></h2> <p>Rotate the stick for more precise steering with no deadzone in the middle.</p> <p>Traditional steering on a gamepad just uses the horizontal axis of one of the analog sticks. While this gives more flexibility to gently turn than, say, steering with a keyboard, here are a couple observations:</p> <ol> <li>Even modern games and controllers have a noticeable deadzone in the middle, which makes it harder to make very small left-right steering adjustments;</li> <li>The thumbstick is a 2D input, but driving games tend to treat it as a 1D input.</li> </ol> <p>We can consider a thumbstick input as having two components: <em>direction</em> and <em>magnitude</em>. All the fine adjustments that traditional stick steering offers require us to carefully change the magnitude of the stick position. Due to the very small distance between the stick’s neutral position and a full deflection, <em>magnitude</em> is already a thumbstick’s least precise component. This is exacerbated quite a bit by having to deal with the deadzone in the middle.</p> <p>Stick <em>direction</em>, on the other hand, gives players significantly more distance and resolution between a full-left turn and a full-right turn, without a deadzone in the middle. This is <strong>2D steering</strong>. We still use a deadzone to tell whether the player has engaged the stick, but it’s a <em>circular deadzone</em> that the stick sits in when it's untouched. This means the player can push the stick forward to get out of the deadzone and make the slightest left and right adjustments and see immediate effects in-game. A full-tilt left is still a hard left turn, full-tilt right is still a hard right turn. 45 degrees to the left or right is a half-strength turn in that direction. We linearly map the angle of the stick to the strength of the turn in that direction.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/54f34eb5b14d300558dcf443c903b6be9f0ef16c-1906496139567904132" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Of course, we’re still using stick magnitude! If the player only half-tilts the stick, we can simply multiply that with our angular steering strength. If your game has a non-linear stick response with steering, you can apply that same nonlinear response to the magnitude component here as well.</p> <p>When your stick angle is 90 degrees to the left or right, the angular or directional component of 2D steering is <strong>+-1</strong>. Here, steering strength is entirely dictated by the magnitude of the stick input. This means that if the player chooses to use the stick only in the horizontal axis just as they are used to with traditional stick steering, it’ll behave <em>identically</em> to traditional stick steering. Since players are used to staying in the horizontal axis for steering in games like these anyway, this means you don’t even have to create a new setting in your game for 2D steering – just replace your traditional steering with 2D steering and tell players they can rotate the stick if they want to. If they don’t want to, nothing changes for them.</p> <p>Now, it’s a big ask to have players tilt the stick <em>exactly</em> horizontally for their hardest turns, so I do recommend a small angular <em>outer</em> deadzone. This is much less intrusive than an <em>inner</em> deadzone, because players are not trying to move from one side of it to the other.</p> <p>Here’s some code that applies all of the above. Note that for the sake of having no discontinuities, the lower half of the stick mirrors the upper half – tilting the stick straight down is the same as tilting it straight up.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// +- angular range from horizontal that'll be treated as a horizontal input</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">OuterDeadzone</span><span class="hl-code"> = </span><span class="hl-number">10.0</span><span class="hl-code">; </span><span class="hl-comment">// this will return a value between -1 and 1, inclusive, for how hard the vehicle is turning.</span><span class="hl-code"> </span><span class="hl-comment">// assume circular deadzone is already applied</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">StickToSteeringStrength</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">stick</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> == </span><span class="hl-number">0.0</span><span class="hl-code"> &amp;&amp; </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">y</span><span class="hl-code"> == </span><span class="hl-number">0.0</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">absY</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// using absolute Y means the back half of the stick mirrors the front</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">atan2</span><span class="hl-brackets">(</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">absY</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// convert to degrees because our outer deadzone is in degrees</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">absAngle</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">stickAngle</span><span class="hl-code"> * </span><span class="hl-number">180.0</span><span class="hl-identifier">f</span><span class="hl-code"> / </span><span class="hl-identifier">PI</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// remap to [-1, 1] range without the deadzone</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angularStickStrength</span><span class="hl-code"> = </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">stickAngle</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">absAngle</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-number">90</span><span class="hl-code">.</span><span class="hl-identifier">f</span><span class="hl-code"> - </span><span class="hl-identifier">OuterDeadzone</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">.</span><span class="hl-identifier">f</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-code">.</span><span class="hl-identifier">f</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// some games apply a power curve on their traditional steering.</span><span class="hl-code"> </span><span class="hl-comment">// here, you would apply that to the magnitude</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">magnitudeStickStrength</span><span class="hl-code"> = </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Magnitude</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">angularStickStrength</span><span class="hl-code"> * </span><span class="hl-identifier">magnitudeStickStrength</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>JoyShockMapper has the option to add 2D steering to games by taking the stick angle and remapping it to a horizontal-only position on a virtual controller. Using JoyShockMapper, I play with 2D steering in any game that offers simple stick steering, such as <em>Trackmania</em> (as demonstrated above) and even <em>Spyro</em> (with 2D steering active only while holding the “charge” button). I’d like to play <em>Rocket League</em> this way, but because JoyShockMapper can’t tell when the vehicle is airborne I lose control of my vehicle’s pitch while jumping or boosting through the air. This would not be a problem with a native implementation.</p> <p>In summary, 2D steering:</p> <ul> <li>Offers players more steering precision, more naturally mapping your controls to what the player character is able to do;</li> <li>Is incredibly simple to implement and simple to test;</li> <li>Is a generalisation of the controls you already offer the player, so you can make it part of the default experience without messing with what the player’s used to.</li> </ul> <p>Drop it into <em>any</em> game that uses traditional stick steering. The only game I’m aware of that has the option natively is <em>Fortnite</em> (since its 20.30 update this month), but it’s generally easy to check in any game by rotating the stick and seeing its effect on the vehicle’s steering.</p> <h2><span>2. Winding Stick Steering</span></h2> <p>When it comes to driving sims, players want far more precision with their steering input. Don’t get me wrong, 2D steering is hugely valuable in these kinds of games, too. Every driving sim should have 2D steering, just like how every driving sim will let you steer with traditional stick steering if you want.</p> <p>But for an even more sim-like experience, with far more range of movement and precision, how about letting the player wind the stick left and right just like a steering wheel? Where 2D steering took the absolute direction of the stick and mapped it to a steering wheel position, <strong>winding stick steering</strong> takes relative changes in the stick direction, allowing you to make 360 rotations of the stick (or more!) and turn them into meaningful changes in steering direction in game. Just like how many cars will let you rotate the steering wheel 450 degrees or more left and right, winding stick steering lets the player do that with the left thumbstick.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/2da9eb5b5058205142991a110df1a441c579b67c-1597903123144702765" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Of course, this should all be configurable. Some players might like 1080 degrees of rotation, while others might be comfortable with 360 or even less. And letting players control the response/linearity of their steering will be of huge benefit as well, letting them get more resolution for the finer adjustments while letting them quickly flick into a drift and counter-steer with bigger turns.</p> <p>Here’s some code for you:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// total angular range the stick can be rotated from full left to full right</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">WindingRange</span><span class="hl-code"> = </span><span class="hl-number">900.0</span><span class="hl-code">; </span><span class="hl-comment">// how far we let the stick rotate beyond a full turn before we start ignoring further rotation</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">WindingStickOverRotationBuffer</span><span class="hl-code"> = </span><span class="hl-number">90.0</span><span class="hl-code">; </span><span class="hl-comment">// power curve, if you want</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">WindingPowerCurve</span><span class="hl-code"> = </span><span class="hl-number">1.0</span><span class="hl-code">; </span><span class="hl-comment">// track the current virtual stick range</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-comment">// this will return a value between -1 and 1, inclusive, for how hard the vehicle is turning.</span><span class="hl-code"> </span><span class="hl-comment">// assume circular deadzone is already applied</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">WindingStickToSteeringStrength</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">stick</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">previousStick</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stickMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Magnitude</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">IsZero</span><span class="hl-brackets">()</span><span class="hl-code"> &amp;&amp; !</span><span class="hl-identifier">previousStick</span><span class="hl-code">.</span><span class="hl-identifier">IsZero</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">atan2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-number">180.0</span><span class="hl-code"> / </span><span class="hl-identifier">PI</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastStickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">atan2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">previousStick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">previousStick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-number">180.0</span><span class="hl-code"> / </span><span class="hl-identifier">PI</span><span class="hl-code">; </span><span class="hl-comment">// the angle change might cross a threshold, such as going from a small number to just below 360.</span><span class="hl-code"> </span><span class="hl-comment">// Wrap around and find the angle difference in the +-180 degree range</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angleChange</span><span class="hl-code"> = </span><span class="hl-identifier">wrap</span><span class="hl-brackets">(</span><span class="hl-identifier">stickAngle</span><span class="hl-code"> - </span><span class="hl-identifier">lastStickAngle</span><span class="hl-code">, -</span><span class="hl-number">180.0</span><span class="hl-code">, </span><span class="hl-number">180.0</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// multiply by stick magnitude in case player hasn't fully tilted the stick</span><span class="hl-code"> </span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> += </span><span class="hl-identifier">angleChange</span><span class="hl-code"> * </span><span class="hl-identifier">stickMagnitude</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// let the player release the stick gently to release the wheel gently</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">stickMagnitude</span><span class="hl-code"> &lt; </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// some magic here depending on the game: given the game state and physics, how fast would the wheel try</span><span class="hl-code"> </span><span class="hl-comment">// to return to neutral? In JoyShockMapper we just have a constant unwind rate set by the user</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">unwindRate</span><span class="hl-code"> = </span><span class="hl-identifier">CalculateUnwindRate</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-comment">// fully released stick means full unwind rate is used. partially released stick = partial unwind rate</span><span class="hl-code"> </span><span class="hl-identifier">unwindRate</span><span class="hl-code"> *= </span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">stickMagnitude</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">unwindAngle</span><span class="hl-code"> = </span><span class="hl-identifier">unwindeRate</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">unwindAngle</span><span class="hl-brackets">)</span><span class="hl-code"> &gt;= </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-brackets">))</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> -= </span><span class="hl-identifier">unwindAngle</span><span class="hl-code"> * </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// finally, clamp the steering angle</span><span class="hl-code"> </span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code">, -</span><span class="hl-identifier">WindingRange</span><span class="hl-code"> * </span><span class="hl-number">0.5</span><span class="hl-code"> - </span><span class="hl-identifier">WindingStuckOverRotationBuffer</span><span class="hl-code">, </span><span class="hl-identifier">WindingRange</span><span class="hl-code"> * </span><span class="hl-number">0.5</span><span class="hl-code"> + </span><span class="hl-identifier">WindingStuckOverRotationBuffer</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// and return the steering strength in [-1, 1] interval</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">remappedWindingAngle</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentWindingStickAngle</span><span class="hl-code"> * </span><span class="hl-number">2.0</span><span class="hl-code"> / </span><span class="hl-identifier">WindingRange</span><span class="hl-code">, -</span><span class="hl-number">1.0</span><span class="hl-code">, </span><span class="hl-number">1.0</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// ...with power curve applied</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">remappedWindingAngle</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">pow</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">remappedWindingAngle</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-identifier">WindingPowerCurve</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>The left stick becomes a steering wheel. Engaging the stick is like grabbing the steering wheel, and releasing the stick is like letting go of the steering wheel. In my implementation, partially releasing the stick will let the steering wheel slip through my virtual hands more slowly. JoyShockMapper lets you configure a fixed rate that the virtual steering wheel moves back to its neutral position as you release the stick, but your driving sim can take into account the vehicle’s velocity and pull the virtual wheel back to the neutral position at a much more realistic rate.</p> <p>Now, a simulation wheel will stop rotating when it reaches its limit – it will “lock”. We can’t stop the player from rotating their stick further. But giving the player feedback about their current virtual stick position is <em>extremely</em> important to this being an enjoyable experience.</p> <p>If you’re using JoyShockMapper to force these controls into games, you’re probably best off staying in the cockpit view where you can see the game’s virtual steering wheel. For me, with my not-yet-released JoyShockOverlay, I’ve added the option for the kind of visual feedback I’d like every sim with these kinds of controls to offer. You can see it and how it responds to controller input in the clip above. Here's a close-up:</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/SteeringHUD_JSO.png" alt="SteeringHUD_JSO.png" class="image" /> <p>This HUD element shows:</p> <ul> <li>How hard I’m steering and in which direction;</li> <li>How far I am between the neutral position and steering wheel lock;</li> <li>A bright, eye-catching indicator of when my steering is locked;</li> <li>How far <em>past</em> lock I have continued to rotate my stick.</li> </ul> <p>That last part is more important than it might appear. It’s important to let me rotate past the lock position and keep track of that over-rotation internally. Otherwise, stick noise or small adjustments in my stick position would have me drift out of steering lock. As the player gets better at these controls, they’ll feel more and more comfortable only slightly going past the lock position. Until then, the player should be able to see how far they’re over-rotating. In my code example, I also limit how far the over-rotation is tracked to limit delays as the virtual steering wheel pulls back to its neutral position when the stick is released.</p> <p>In my experimenting with visual feedback, I found it helpful to be able to put this HUD element front-and-centre. This example, in which I’m using JoyShockMapper and JoyShockOverlay with <em>Assetto Corsa</em>, might look intrusive. But in practice it obscures very little, and I found it very useful to be able to see my virtual steering position without taking my eyes far off the road. I also played with it at the bottom of the screen. Wherever it is vertically, I found it best suited to being centred horizontally. Letting the player customise the position of HUD elements is a great option to offer.</p> <p>Your HUD element could look exactly like mine if you want, or it could look drastically different. But please make sure it provides the same info to the player. Admittedly, in a sim the player spends very little time with locked steering (unless perhaps they're drifting in some genres of sim). But I still felt being able to see my full range of movement helpful when I am going slow enough to use the steering wheel's full range. And even at full speed where most turning is very slight, the HUD element is useful for seeing the state of a virtual steering wheel that I can't feel.</p> <p>Finally, it is helpful to offer some physical feedback. I’ve set it up here to lightly rumble the controller when my steering is locked. I found that hugely helpful for <em>feeling</em> my steering range, and letting me ease out of a hard turn. Sims are usually offering plenty of other rumble feedback which may interfere. But combining both and letting the player independently configure the strength of steering feedback and environmental vibration would be great.</p> <p>In summary, winding stick steering:</p> <ul> <li>Maps more naturally to the range of a real steering wheel;</li> <li>Is not very easy to learn, but has a very high skill ceiling for controller players who want a more realistic experience;</li> <li>Lets players enjoy a more simulation-like experience where wheels aren’t affordable or aren’t viable (on the go with a Switch or Steam Deck);</li> <li>Needs additional feedback (HUD, vibration) so players can <em>feel</em> and/or <em>see</em> when the steering locks and see the direction they’re steering.</li> </ul> <p>Best for driving sims, and probably any other game that benefits from a steering wheel (if such games exist outside of sims). Let your players dip their toe into more realistic simulations on the go or if they’re undecided on whether to buy a wheel. I have not seen this in the wild, but this idea was inspired by a comment on Reddit 3 years ago (<a href="https://www.reddit.com/r/gamedev/comments/bw5xct/flick_stick_is_a_new_way_to_control_3d_games_with/epvv4tc/?utm_source=share&amp;utm_medium=web2x&amp;context=3">&quot;I am still waiting for racing games using the left stick angle instead of X axis.&quot;</a>), and others have been <a href="https://www.reddit.com/r/SteamController/comments/64bolw/may_i_suggest_this_wheel_mode_to_use_with_the/">floating the idea around for even longer</a>.</p> <h2><span>3. Motion Steering</span></h2> <p>I don’t want to go into great detail about motion steering, except to say this: good player feedback is the difference between <em>gimmicky</em> and <em>great</em> motion steering.</p> <p>So simply take what I wrote about visual and rumble/haptic feedback in the previous section, and apply it to motion steering with just about the exact same HUD element, showing players:</p> <ul> <li>Their current steering strength/direction;</li> <li>Whether they’ve “locked” the steering wheel (reached max rotation);</li> <li>How far they’ve rotated beyond max rotation;</li> <li>And since motion steering may have a deadzone in the middle (player configurable, please), show that on the HUD element, too.</li> </ul> <p>The same physical feedback advice applies, too: even a simple constant rumbling while steering is locked goes a long way to letting the player feel the range of movement available to them.</p> <p>Without good feedback, players drastically over-rotate their controller just to make sure they’re turning as hard as they can. This is perhaps less of an issue with (non-drifting) sims, where players are very rarely actually using the virtual steering wheel’s full range available to them. But this feedback is <em>essential</em> in non-sims, where players tend to spend a lot of time trying to turn as hard as they can as fast as they can. When you're trying to hit that hard-as-you-can full-speed turn, it makes a world of difference to actually <em>know</em> how far you need to tilt.</p> <p>Speaking of sims as compared with non-sims, because tilting the controller offers much more precision than even 2D steering, it’s relatively well-suited to both arcadey steering (where the game limits your turning range as you go faster) <em>and</em> simulation-style steering (leaving it up to the player not to turn so hard that they lose traction). But because the range of motion with motion steering is still much less than a real steering wheel, a player-configurable non-linear response is very helpful for bridging that gap in sims.</p> <p>All that said, my main point with motion steering is that visual and/or physical feedback is so helpful I’d call it <em>crucial</em> to a good implementation. It’s a simple way to move motion steering from <em>gimmicky</em> to genuinely <em>great</em>.</p> <p>Using JoyShockMapper for the steering control and vibration and JoyShockOverlay for the HUD element, let's shove these controls into <em>Trackmania</em>:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/16326259fb9d75eae6d5a792d9e81f5ee95c4a0b-1773012266373297" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>I personally prefer 2D steering in arcadey games like <em>Trackmania</em>. But good visual feedback makes motion steering a close second for me, then traditional stick steering in third place followed by motion steering <em>without</em> good feedback in last place.</p> <p>In summary, motion steering:</p> <ul> <li>Provides more range of movement and precision than traditional stick or even 2D steering;</li> <li>Has enough range and precision that it can reasonably be used with zero speed sensitivity in sims (no scaling back the steering range as the car gets faster);</li> <li>Needs additional feedback (HUD, vibration) since players can’t <em>feel</em> when the steering locks or feel the direction they’re steering.</li> </ul> <p>I don’t have a lot of experience with games that offer motion steering, but here’s a shout-out to Gran Turismo 7’s red dot showing the player’s current motion steering and the full range available:</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/SteeringOverlay_GT7.PNG" alt="SteeringOverlay_GT7.PNG" class="image" /> <h2><span>4. Chorded Item Selection</span></h2> <p>Besides aiming, the other big advantage mouse and keyboard players have over gamepad players is with item selection. Keyboard players are used to having an array of keys dedicated to selecting specific weapons, letting them access every weapon in a near-instant, and often being able to access their favourites without interfering with their movement or other keyboard actions at all.</p> <p>While keyboard players have dozens of unique inputs available to them, gamepads have very few. When designing for gamepads, fitting all the things we want to do on the few buttons available is often a great challenge.</p> <p>Radial menus / weapon wheels have become a fairly common solution. They essentially mean all of your weapon / item selection only takes up one button: the button you hold to bring up the radial menu. Players then make their selection on that radial menu using one of the sticks – usually the right stick, so the player’s movement isn’t interrupted while selecting items/weapons.</p> <p>The nice thing about this is you can offer the player just about any number of options, but the more options you offer them, the more precision is required with the stick to equip the item the player intends. This can be somewhat mitigated with a spiraling radial menu – rotating through multiple layers of possible selections by winding the stick as in <a href="https://www.youtube.com/watch?v=euquOpUmUyk">Beyond Good and Evil’s text input interface</a> – or by letting the player cycle through distinct wheels with no more than 8 or so options on each (see <em>Ratchet and Clank: Rift Apart</em>).</p> <p>Either way, we’re changing the functionality of one input (one of the thumbsticks, in this case) while another input is held. This is called a “chorded input”. Like playing a chord on a piano, it’s the combination of multiple inputs that gives the desired result.</p> <p>But instead of having a button change the <em>stick</em> behaviour, it can often be better to change the behaviour of <em>other buttons</em>. See <em>Crysis Remastered</em>, and how it gave players the option to select nano suit mode by chording the <em>face buttons</em> instead of an analog stick. The suit mode layout shown on screen spatially maps to the controller in an intuitive way – just like a weapon wheel – but executing a chorded button press is simpler (both for the developer and for the player) than repurposing one of the sticks. Digital Foundry called this suit selection “really nice” and <a href="https://www.youtube.com/watch?v=IZ8zA0cWJ90&amp;t=183s">“pretty much as quick as it is on PC”</a>.</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/ChordedSelection_CrysisRemastered.jpg" alt="ChordedSelection_CrysisRemastered.jpg" class="image" /> <p>This is something JoyShockMapper users have been doing for a long time – in <em>Fortnite</em>, in <em>Crysis</em> well <a href="https://www.youtube.com/watch?v=kVM7SitQUA8&amp;t=335s">before Crysis Remastered was released</a>, and <a href="https://twitter.com/JibbSmart/status/1275408198745206784">even for choosing between the 8 weapons in DOOM Eternal</a> (which conveniently fit into 4 simple categories – shotguns, buttets, energy, and big-bang) &#8212; see what DualShock 4 user SmvR can pull off using JoyShockMapper for chorded weapon selection:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/a3a7fb249b01c899352d581af5119089aa7fd607-992060197848791939" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>What is it that makes using face buttons preferable over using a stick?</p> <p>For one thing, an analog input isn’t well-suited to digital selections – just as a keyboard shortcut is quicker than using the mouse to press an on-screen button, using a physical button is quicker than using an analog stick to select an on-screen item.</p> <p>In addition, I personally find that I can more easily learn mappings to specific buttons than to specific directions of the stick. Stick selection layout is certainly easier to memorise in a game like <em>Deathloop</em>, where each item wheel only uses the cardinal directions anyway. But having so few items to choose from also makes it ideal for selection with face buttons or d-pad.</p> <p>Chorded item selection also gives us a new way to explore older means of item management. While games have long used the d-pad for secondary functions like weapon or item selection, letting go of the left stick (typically the move stick) in order to use the d-pad slows down the pace of the game. We could use modern remapping tools to play with <em>Jak 3</em>’s 4-category, 12-weapon d-pad system and remap it to, say, a shoulder button + the face buttons. This lets us easily access every weapon while on the move, and the game’s HUD already communicates how to access each weapon very clearly (see top-right):</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/ChordedSelection_Jak3.png" alt="ChordedSelection_Jak3.png" class="image" /> <p>Consider also that we often have the weapon-wheel button double as a previous-weapon or weapon-cycling action on a short press. The radial menu only appears when the button has been held too long to be considered a “short press”. It makes logical sense and it’s easy to learn, but:</p> <ul> <li>Selecting other weapons is now even slower, since the player always has to wait for the radial menu to appear if they have a specific weapon or item in mind;</li> <li>This creates uncertainty as to when the stick will change from controlling the camera to selecting a weapon, making weapon selection even clunkier than it was without the delay.</li> </ul> <p>Using chorded <em>button</em> presses fixes both of these issues! Pressing the weapon button + a face button unambiguously communicates the player’s intent: R1 + Triangle means “give me my shotgun, please!” regardless of whether the radial menu has appeared on screen yet. And a press and release of the weapon button without pressing one of those other face buttons can unambiguously be considered a short press – or at least can allow a more generous timing window without slowing the player’s weapon selection down.</p> <p>And it may be possible to layer these shortcuts on-top of your existing analog-selection system (as long as you’re not already using weapon button + face button to do something else, like holstering your item in <em>Deathloop</em>).</p> <p>Of course, not all games will be well-suited to this kind of change. With very many items or weapons, it may be difficult to group them into categories that map well to only 4 button inputs. Maybe you can explore using more than just the face buttons for your chorded mappings? In the past I have used all of the face buttons and d-pad for weapon shortcuts in Quake, but I ended up preferring to put two weapons on each face button like I do in DOOM so I never had to use the d-pad while moving.</p> <p>In the end, I’m not saying all games should do this, but many certainly could, and it’s a great tool for you to have in your toolbelt when designing controls.</p> <p>In summary, chorded item selection:</p> <ul> <li>Multiplies your controller button real-estate;</li> <li>Is often quicker, simpler, and communicates player intent more clearly than analog item selection;</li> <li>Can often be layered on top of a current analog stick radial menu seamlessly.</li> </ul> <p>Nearly keyboard-fast item selection! Try it out in <em>Crysis Remastered</em>.</p> <h2><span>5. Motion Pointing</span></h2> <p><strong>/ Gyro Pointing</strong></p> <p>Controlling an on-screen cursor is a well-known weakness for legacy controllers. The stick can only tilt so far, and is much easier to use by going full-tilt in a direction than a careful and gentle half-deflection (as I mentioned before: sticks are more useful for inputting <em>direction</em> than <em>magnitude</em>).</p> <p>How fast should an on-screen cursor move when the user is giving their maximum stick input? Fast enough to move between items of interest quickly, but slow enough to not overshoot them. And since for most users these are two very different speeds, we tend to accelerate over time (to differentiate between small corrections and big movements), slow the cursor as it passes over items of interest, and give the cursor a larger hit-box (effectively giving all the on-screen items over-sized collision shapes).</p> <p>Contrast that with a mouse. A mouse can move a cursor pretty much as quickly or slowly as the user likes. With low enough input latency, the cursor can move practically simultaneously with the physical mouse, giving the user a far more direct connection between their input and the game’s response.</p> <p>It can be very simple to turn motion sensor input into a precise, responsive, mouse-like cursor control. It’s actually trivial to implement.</p> <p>Here’s mouse control:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-comment">// look, I know you're probably using the system cursor...</span><span class="hl-code"> </span><span class="hl-comment">// but if you weren't, it'd look something like this, right?</span><span class="hl-code"> </span><span class="hl-types">void</span><span class="hl-code"> </span><span class="hl-identifier">MoveMouseCursor</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">mouseInput</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Cursor</span><span class="hl-code">.</span><span class="hl-identifier">Location</span><span class="hl-code"> += </span><span class="hl-identifier">mouseInput</span><span class="hl-code"> * </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Here’s just using gyroscope input as a mouse:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-comment">// move the cursor</span><span class="hl-code"> </span><span class="hl-types">void</span><span class="hl-code"> </span><span class="hl-identifier">MoveGyroCursor</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyroInput</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Cursor</span><span class="hl-code">.</span><span class="hl-identifier">Location</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> += </span><span class="hl-identifier">gyroInput</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code"> * </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-identifier">Cursor</span><span class="hl-code">.</span><span class="hl-identifier">Location</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> += </span><span class="hl-identifier">gyroInput</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code"> * </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>That's not bad, hey? I mean, depending on the coordinate space of your inputs and what you're doing with screen space, you might have to change the axes or a sign or something. But it's still essentially this simple. The main difference is that while a mouse is reporting a change in position, the gyro is reporting a velocity (change in angular position over time), so we need to take time into account.</p> <p>No need for acceleration over time or slowing over targets (in fact it’s preferable that you don’t do these as it makes the cursor movement feel <em>less</em> connected to the player’s input). Big cursor collision can’t hurt but it isn’t necessary. And since you can lift your mouse off the mousepad to reposition it without moving the on-screen cursor, the best motion pointing implementations will give you a button to do the same.</p> <p>You can certainly provide more options. Separate X and Y sensitivity is common, for example. The point of the snippets above is to show you how simple it is. You don't have to deal with 3D orientations. You don't have to do any difficult transformations. The platform you're developing on probably already deals with calibration for you (chat to me about that, as there are platform specifics I can't get into here). <em>The gyro really is a mouse</em>.</p> <p>For simple menu navigation where appropriate without having to train the user or have them go into their settings, try:</p> <ul> <li>Motion/gyro on or off by default – up to you;</li> <li>On-screen prompt “(□) disable/enable gyro mouse”, depending on whether it’s currently enabled;</li> <li>Short-press (□) to toggle gyro mouse enabled or disabled, hold (□) to temporarily disable or enable gyro mouse while pressed.</li> </ul> <p>Given how much Nintendo likes to use motion controls, I was disappointed that <em>Super Smash Bros. Ultimate</em> didn’t let me move the fighter-select cursor around the screen with this kind of motion pointing. Play the game now and consider how much nicer it would be to select your fighter with a mouse instead of pushing the cursor around with a stick. A simple motion pointing solution like that shown above feels almost exactly the same as using a mouse.</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/CharacterSelect_SmashBros.jpg" alt="CharacterSelect_SmashBros.jpg" class="image" /> <p>But there are some pretty good implementations out in the wild! Check out the Switch version of World of Goo, even allowing two players playing together to each hold a Joy-Con and control separate cursors on-screen. Their motion pointing is more complex than the example above, but it’s still robust and easy to pick up.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/e9806ecf5319dfa5dea63abf93e61facfbb3fb48-1959400629821648638" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>It <em>should</em> go without saying that any motion-related feature on the Switch is equally useful on other platforms with similar sensors – I’m thinking in particular of the PS4 and PS5. But too many games lack useful motion controls in their PlayStation and PC versions even though they offer them in their Switch versions (Xbox game devs get a pass because Xbox doesn’t have the hardware to offer these features). To me, this points to either:</p> <ol> <li>Developers over-valuing thumbsticks as a cursor- or aim- controlling input device. “Joy-Con thumbsticks aren’t as good as PlayStation ones, so Switch needs an alternative but PlayStation doesn’t.”</li> <li>OR developers under-valuing motion/gyro as a mouse-like input. “It’s a gimmick, but Switch players expect the feature, so here is an implementation they’ll probably like.”</li> </ol> <p>Both of those views are incorrect. PlayStation <em>does</em> benefit from gyro pointing – the built-in motion keyboard on both the PS4 and PS5 are examples of this. And gyro pointing doesn’t sit in the small gap between Joy-Con sticks and larger PlayStation thumbsticks, but instead it sits at the far end of the much larger gap between standard thumbsticks and a gaming mouse (no scale, this is just illustrative):</p> <img src="http://gyrowiki.wdfiles.com/local--files/blog:7-building-blocks-for-better-controls/PointerScale.png" alt="PointerScale.png" class="image" /> <p>Please reconsider the weaknesses and strengths of stick controls and mouse controls and realise that the <em>gyro is a mouse</em>. Whether for fighter selection in a game with a large roster, interacting with an in-game-world touch screen, placing pins on a map, or the cursor-based menu navigation found in games like <em>Destiny</em> and <em>Deathloop</em>, the controller’s gyroscope is the best option available to the player for controlling an on-screen cursor.</p> <p>Finally, consider how this mouse-like input in the controller can be used to bring genres to consoles that have just not been considered viable with a legacy controller, such as rail shooters like <em>The House of the Dead: Remake</em>. Using JoyShockMapper, I can force controls like these into just about any PC game, and while I’m not particularly good at the excellent pointer rhythm game <em>osu!</em>, compare that to its reputation for being unplayable with a controller.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/5589cdc308854f1801cbfd2d102fc0724facc402-1506569980766662996" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>In summary, motion pointing:</p> <ul> <li>Almost effortlessly provides mouse-like cursor control on most modern consoles;</li> <li>Works very naturally in games that already have the player control a cursor in their menus (<em>Super Smash Bros</em>, <em>Deathloop</em>, <em>Destiny</em>);</li> <li>Makes formerly PC-only genres more viable on consoles.</li> </ul> <p>Especially natural with a Joy-Con on Switch, if your implementation is decent. Test your implementation with an expert to see if it is. For real examples, check out <em>World of Goo</em> on Switch and <em>House of the Dead: Remake</em>.</p> <p>PS: I tend to say “gyro” rather than “motion” because motion controls of other kinds (shakes and waggles) have soured some players on the idea of any kind of motion-related input, regardless of how precise and responsive it is. This is very much like dismissing mouse aiming because you didn’t like having to <a href="https://www.youtube.com/watch?v=qSMfpv9lz3k">use the mouse for gestures</a> in <em>Black &amp; White</em>.</p> <h2><span>6. Motion Aiming</span></h2> <p><strong>/ Gyro Aiming</strong></p> <p>Don't scroll past just because you saw &quot;motion&quot; or &quot;gyro&quot;. Watch this 31 second clip first:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/638a67fb76f8ad5f90ba92b4f8bac55c7e5ce238-2427557081646273768" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>The gyro is the mouse of the controller. And not just for moving a cursor around the screen. For any game where the player takes direct control of the camera, gyro controls offer precision and quickness that thumbsticks are not well-suited for. Turn off aim assist and let players acquire targets, pull off flick shots, and track weaving enemies with their own hands.</p> <p>The main weakness of gyro aiming is the player's limited range of movement &#8212; no one wants to have to spin around in their chair as the game forces them to turn around over and over to track enemies or navigate winding paths. This is what the sticks are good at, and so we have a <em>naturally complementary</em> combination of <strong>sticks for broad turns</strong> and <strong>gyro for precision</strong>.</p> <p>Traditional stick-only camera controls give the player the impossible task of fine-tuning their settings to allow for precise adjustments and quick 180s at the same time. A stick cannot do both well. And in fact even if you don’t need to turn around in a game – even if all of your targets are on-screen at once – a stick will still be out-performed by a mouse. Consider that turning the camera to point at targets that are all near each other is essentially the same task as moving a cursor around a screen:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/f80b895472d08f14432f700c25ce47dfe67d8780-8751764681960080408" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>So sticks are bad at precision aiming. Why don't we offer better options? Because stick-only is what we’re used to. We’re so used to that being the only option on the gamepad that we’ve forgotten how difficult it was to learn in the first place <em>even with aim assist helping out</em>. This is probably the biggest reason for gyro aiming’s relatively slow adoption. But there are millions of new gamers every year, and there’s no need for them to endure the same challenges to enjoy modern games.</p> <p>In comparison, gyro aiming is very easy for new players to pick up. It doesn’t really have to be explained to them in depth. They’ll usually have little trouble figuring out how to aim at a target and shoot it, because there is such a natural connection between the real life movement and the in-game action.</p> <p>And it’s super simple. Start with something like this:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-comment">// move the cursor</span><span class="hl-code"> </span><span class="hl-types">void</span><span class="hl-code"> </span><span class="hl-identifier">ApplyGyroCamera</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyroInput</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">AddYaw</span><span class="hl-brackets">(</span><span class="hl-identifier">gyroInput</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code"> * </span><span class="hl-identifier">Sensitivity</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">AddPitch</span><span class="hl-brackets">(</span><span class="hl-identifier">gyroInput</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code"> * </span><span class="hl-identifier">Sensitivity</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>How easy is that? It’s basically the same as mouse controls. Compare that to your stick aiming implementation that has deadzones, maybe some directional warping, response curve, boosting/acceleration… And then aim assist is an even bigger beast that has no business being involved in mouse aiming or gyro aiming.</p> <p>Now, you can certainly add more to this snippet. But whatever else you add to it, make sure it's <em>all optional</em>. You <em>must</em> let players disable all the other stuff you're gonna do to their controls. Because that's where so many games stumble. They add acceleration or deadzones or smoothing or a combination of the above that make gyro aiming less precise, less responsive, and less useful to skilled players. Those things are great options, and some of them can even be useful defaults. But they <em>must</em> be disablable. That's totally a word.</p> <p>Two other ways a lot of games stumble:</p> <ol> <li><strong>The sensitivity slider doesn't go high enough</strong>. The above snippet assumes you're getting degrees per second in and the camera Euler angles are in degrees. If that's not the case (some platforms provide radians per second, others rotations per second), convert. And then you're on the <em>natural sensitivity scale</em>, where '1' means in game rotations are the same as real world rotations. That's the scale all games should use for gyro controls. Now, how high does your sensitivity slider go? Many games top out at 1 or 2. For a long time I've recommended <strong>10</strong> to comfortably fit what experienced gyro players use (anything from 1-8, in my experience). That's what I put in <em>Fortnite</em>. Then those with ridiculously steady hands chimed in and I bumped it up to <strong>20</strong>.</li> <li><strong>There's no way to disable the gyro</strong>. This is much less of an issue for games where you only ever shoot while holding a dedicated aim button. In fact, these happen to be really common (think of just about every PlayStation blockbuster in the last two generations). Such games are actually <em>ideal</em> for learning gyro controls, because you don't need to pay attention to how you're holding the controller unless you're getting ready to shoot. <em>Zelda: Breath of the Wild</em> proved this in spite of limited options and a low framerate. But there are also many &quot;always aiming&quot; games: <em>DOOM</em>, <em>Overwatch</em>, <em>Quake</em>, <em>Counter-Strike</em>, <em>Valorant</em>, just about every indie boomer shooter. These are great with gyro, too! But just like a mouse player lifts their mouse off the mousepad to reposition it without moving the camera, these games <em>must</em> offer a way to disable the gyro so the player can reposition their controller.</li> </ol> <p>That last one is the single hardest thing for implementing good gyro controls. But in my experience there are always some actions that can double as a gyro-off button on a long press. Before this turns into a complete gyro controls tutorial of its own, just check out <a href="http://gyrowiki.jibbsmart.com/">the front page of GyroWiki</a>. It has something of a curriculum for developers looking to do this stuff properly, from the <strong>absolute basics</strong> that most games miss to the advanced stuff that'll make your <em>good</em> gyro controls <em>great</em>.</p> <p>Compare what you read there with what you can do in the games that offer the best controls:</p> <ul> <li><em>Fortnite</em></li> <li><em>Counter-Strike: Global Offensive</em></li> <li><em>Boomerang X</em> (especially on PC)</li> </ul> <p>If you’re one of the lucky few to already have a Steam Deck, play the free <em>Aperture Desk Job</em>. Between that, <em>CS:GO</em>, and Steam’s built-in controller configurator, it’s plain to see that Valve also considers gyro aiming an important building block in modern games:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/01431416bc9298acc6ba9f5f3778a3860305f583-2040364175501804406" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Now, you may have picked up on some bias here. I speak very highly of <em>Fortnite</em>’s gyro controls, but <a href="https://www.epicgames.com/fortnite/en-US/news/gyro-aiming-and-flick-stick-come-to-fortnite-in-v19-30-more-controller-options?sessionInvalidated=true">I worked on them</a>, so how can I be objective here? Well, I do a lot of non-gyro input design and gameplay programming, but I'm something of a gyro specialist. I made all the learning resources on GyroWiki, created JoyShockMapper, and have established simple standards and novel techniques that have been proven by countless player-hours. I'm well-connected with the gyro gaming community. And I've used tools like JoyShockMapper to play games with gyro controls almost exclusively for the last 4 years.</p> <p>It was because of all this experience that I got to be involved with <em>Fortnite</em>. So yes, I'm probably biased when it comes to my assessment of <em>Fortnite</em>'s gyro controls. But since the other front-runners are either not on console (<em>CS:GO</em>) or not on a high-performance console (<em>Boomerang X</em>), I say with confidence that <em>Fortnite</em>'s gyro controls are <em>peerless</em> in the console space.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/8e43248d04c54a52c683c4981ead25d055d3ce35-1767464401525137666" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>So when you read the advice on GyroWiki, you can be sure that all of it works. And if you want to be really sure, see for yourself: you can use JoyShockMapper with almost any PC game, or you can scroll through the settings in <em>Fortnite</em> and see how they compare.</p> <p>And then, with a little work, you can offer your players the option of mouse-like precision and directness control that cannot be achieved with sticks alone. There’s no reason not to offer these options on every platform except Xbox. And as more and more players get into gyro controls, there are also more people for whom these options are make-or-break. See <a href="https://twitter.com/JibbSmart/status/1452847468253569031?s=20&amp;t=l_rN_2lbWDwYhbV1-cvt5g">this thread of choice quotes</a> from players lamenting the lack of gyro aiming options on PS5 so far. And I'm the same. This is why I own two PS4 controllers but never bought a PS4.</p> <p>In summary, gyro aiming:</p> <ul> <li>Turns the whole controller into a frictionless mouse;</li> <li>Offers aiming precision so good that you should have aim assist disabled when gyro aiming is active;</li> <li>Sets your game distinctively apart from stick-only aiming games;</li> <li>Is a make-or-break feature for increasing numbers of players.</li> </ul> <p>Forgive me getting a little over-excited on this one. Gyro aiming can add so much to a game, and it's really hard to go back. It’s frustrating to see otherwise great console-exclusives with such stiff, slow, limited controls. Not because the developers have done a poor job with the thumbstick aiming, but because thumbsticks are bad aiming devices. And we all know that. And we have better options now. I’d put up with stick aiming if it was the best we had, but it's not.</p> <p>Oh, and now that we have two inputs controlling our aim &#8212; the gyro for precision and the thumbstick for coarse camera movements &#8212; let's explore unlocking the aimer from the center of the screen:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/242b67fe72481243b7869ffb7349172a69b936a5-2128494721295919267" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <h2><span>7. Flick Stick</span></h2> <p>We’ve established that with gyro controls, the right stick can be relegated to macro-scale camera turns, pointing in the general direction of interest rather than honing in on a target. But what if we could make that macro-scale control even faster and more direct?</p> <p>You see, <em>Splatoon</em>, which really innovated by going all-in on gyro aiming, made some other really bold moves that are almost unheard of elsewhere. When gyro aiming, they <em>ignore the vertical axis of the right stick</em>. Tilting the right stick up and down does <em>nothing</em>. And this is, of course, great for new players. Because if you’ve ever put a controller in the hands of a non-gamer for the first time and asked them to control the camera while moving their character around, I’m sure you’ve seen them get stuck staring at their feet or the sky and wondering why.</p> <p>Another thing they did to help with this was have a <em>reset camera</em> button. Press the button, and the camera will centre itself vertically. Press the button while moving in any direction with the left stick, and the camera will even flick around to face that direction while centering itself vertically.</p> <p><em>Splatoon</em> illuminated for me that gyro aiming is more than sufficient for all your vertical aiming. That and the camera reset function inspired me to create the <em>flick stick</em> control scheme:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:7-building-blocks-for-better-controls/html/8c90a468d7771eb9a339b35bffb2e824f841e71a-416560819180261933" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>1 minute video introducing flick stick for the first time</em></p> <p>When using flick stick, the right stick no longer turns the camera gradually in two axes. Instead, the whole 2D range of the stick is used to control the camera in <em>one axis</em>:</p> <ul> <li>Move the stick from neutral to the edge of its range in any direction to trigger a <strong>flick</strong> – the camera will quickly turn to face that direction relative to where you’re already facing. For example, if you’re getting shot at from your back left, you can tilt the stick to the back left to turn to face that direction.</li> <li>When the stick is already tilted, <strong>rotate</strong> the stick to rotate the camera. This is generally one-to-one. Rotating the stick 73 degrees to the right will rotate the camera 73 degrees to the right as you’re doing that motion.</li> </ul> <p>Players have compared it to a top-down twin-stick shooter translated to 3D, or how some driving games point the camera in the same direction as your right stick relative to the car.</p> <p>This gives the player faster and more direct control over their bearing than traditional stick controls. But of course, it offers no way to aim up and down. That’s why flick stick requires gyro controls. The gyro isn’t <em>only</em> handling vertical aiming. It also offers horizontal control with precision that isn’t practical with flick stick. This combination of gyro aiming and flick stick takes <em>Splatoon</em>’s gyro controls, constrained stick aiming, and camera reset button to their logical conclusion.</p> <p>Check out my <a href="http://gyrowiki.jibbsmart.com/blog:good-gyro-controls-part-2:the-flick-stick">original flick stick tutorial</a> for more details on implementing this feature well. There are other ways to do it which I'll get into another time, but it’s worth checking out the original article because it explains some important nuances, such as when to apply smoothing, or why a good implementation will not block other inputs from adjusting the camera during the flick.</p> <p>Flick stick started with JoyShockMapper, which was first publicly available early 2019, but now it’s an option in most popular input remappers, including Steam Input, DS4Windows, and reWASD. In July 2021 <em>Boomerang X</em> launched as the first commercial game to include flick stick as a feature (and the first game I’ve played to reach the gyro control standards set in the <a href="https://www.gamedeveloper.com/design/the-absolute-basics-of-good-gyro-controls">Absolute Basics of Good Gyro Controls</a>).</p> <p>Since then, Valve made it a native feature in <em>CS:GO</em>, and I had the pleasure of implementing it myself in <em>Fortnite</em>.</p> <p>In a comment on one of my YouTube videos, Valve’s Aubrey Hesselgren said: “<em>flick stick and gyro are like peanut butter and jelly; anyone who spends a bit of time to get used to it seems to prefer it to the console standard. We want other developers to know that it's a fantastic option, whilst knowing it's an uphill struggle to make it ubiquitous (not least because not all major platforms have gyros built in).</em>”</p> <p>Flick stick is new enough that the feature alone is a significant draw for players who already like to use it. Though at time of writing I only know of 3 games that offer the feature natively (with more on the way), I use JoyShockMapper to play almost <em>every</em> 3D game with flick stick, whether fast-paced or slow and careful, first person or third person, shooters or platformers or even <a href="https://twitter.com/JibbSmart/status/1460130765769887746?s=20&amp;t=tr3xtMRkB5PBWvDjiRpYXQ">God of War</a>.</p> <p>Anecdotally, it appears to me that experienced traditional stick players have a harder time picking up flick stick while mouse players pick it up more easily. The established stick player has a lot of muscle memory to fight against. But mouse- and even non-gamers are often able to find it useful with relatively little practice. I’d say the jury’s still out on what’s generally easier to pick up, but let’s not assume traditional stick controls are the best option for new players just because that’s what experienced players are used to.</p> <p>For now, I’d just love to see it as an option I can choose in more games.</p> <p>In summary, flick stick:</p> <ul> <li>Offers unparalleled speed and range of yaw motion;</li> <li>Requires gyro aiming for a complete aiming experience;</li> <li>Doesn’t require gyro aiming if you don’t need a “complete aiming experience” (many platformers and 3D action games, for example).</li> </ul> <p>Quick and direct control over the player's bearing, already available in <em>Fortnite</em>, <em>Counter-Strike: Global Offensive</em>, and <em>Boomerang X</em>.</p> <h2><span>Conclusion</span></h2> <p>It is great that we’re able to benefit from well-established conventions. These conventions are really helpful for experienced gamers even when they’re not the best option for first-timers. So I’m not saying we need to throw out those conventions. But let’s explore powerful alternatives. Let’s offer the best experience we can to new players, disabled players, and expert players, by offering them alternative building blocks that better suit their experiences and their ambitions.</p> <p>I'd love to see these used in more games!</p> <ul> <li><strong>2D steering</strong> can pretty much be dropped into <em>any</em> game that offers simple stick steering, from the most arcadey to the most exacting sims.</li> <li><strong>Winding stick steering</strong> can offer controller players precise control and immersion that's hard to come by in other games, if they're willing to learn to use it.</li> <li>Good visual and physical feedback can really elevate <strong>motion steering</strong> by addressing its major shortcomings so players can benefit from the precision offered by its wide range of movement.</li> <li><strong>Chorded item selection</strong>, in the right games, can go a long way to closing the gap between keyboard item selection and slow and clunky weapon wheels.</li> <li><strong>Gyro pointing</strong> brings the mouse into the console space with no extra hardware required in an intuitive way.</li> <li><strong>Gyro aiming</strong> goes a long way to close the gap between mouse aim and stick aim, while making hugely popular genres of games easier to learn, more accessible, and have more room for mastery.</li> <li><strong>Flick stick</strong> offers players more direct control over their bearing without having to deal with new players staring at their feet. While it feels unusual for established stick players, it's a simple and powerful control scheme that suits a wide variety of games (often with the help of gyro aiming).</li> </ul> <p>These are pretty basic. They just slide almost seamlessly into the space between the player and the in-game character, not actually changing the mechanics of the game itself. There is so much more we can explore by expanding our set of basic building blocks. Those listed here are low-hanging fruit for offering distinctively good controls. Let's offer more approachable, more accessible, and more empowering controls to players. Let's change how games are played.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:fortnite-s-new-gyro-controls-and-flick-stick</guid>
				<title>Fortnite&#039;s New Gyro Controls and Flick Stick</title>
				<link>http://gyrowiki.wikidot.com/blog:fortnite-s-new-gyro-controls-and-flick-stick</link>
				<description>

&lt;p&gt;I collaborated with Epic Games to bring new and improved controls to Fortnite! Fortnite now has more robust and comprehensive gyro control options on PS5, PS4, PC, Switch, and Android. For a quick overview of the settings provided check out the &lt;a href=&quot;https://www.epicgames.com/fortnite/en-US/news/gyro-aiming-and-flick-stick-come-to-fortnite-in-v19-30-more-controller-options&quot;&gt;Fortnite blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 17 Feb 2022 09:09:22 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>I collaborated with Epic Games to bring new and improved controls to Fortnite! Fortnite now has more robust and comprehensive gyro control options on PS5, PS4, PC, Switch, and Android. For a quick overview of the settings provided check out the <a href="https://www.epicgames.com/fortnite/en-US/news/gyro-aiming-and-flick-stick-come-to-fortnite-in-v19-30-more-controller-options">Fortnite blog</a>.</p> <p>Fortnite now provides essentially all the options covered in the <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Good Gyro Controls guide</a>, is the first cross-platform multiplayer game to have native <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">flick stick</a>, and is the first in-game example of <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained">Player Space gyro controls in action</a>. It is a free game with a massive player base, so <a href="https://www.epicgames.com/fortnite/en-US/home">check it out</a>!</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:fortnite-s-new-gyro-controls-and-flick-stick/html/8e43248d04c54a52c683c4981ead25d055d3ce35-4651604261150101130" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>Gyro and Flick Stick in Fortnite</em></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:motion-controls-on-the-family-video-game-database</guid>
				<title>Motion Controls on the Family Video Game Database</title>
				<link>http://gyrowiki.wikidot.com/blog:motion-controls-on-the-family-video-game-database</link>
				<description>

&lt;p&gt;It&#039;s not uncommon for players discovering gyro aiming / motion controls for the first time to be looking for more games that support these features. What if we had a database of games with native gyro aiming? Or what if we could search for other accessibility features? Games of similar genres? Curated lists for people with particular needs or interests?&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 30 Sep 2021 14:36:28 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>It's not uncommon for players discovering gyro aiming / motion controls for the first time to be looking for more games that support these features. What if we had a database of games with native gyro aiming? Or what if we could search for other accessibility features? Games of similar genres? Curated lists for people with particular needs or interests?</p> <p>The <a href="https://www.taminggaming.com/home">Family Video Game Database</a> does all this. It features a growing list of games on a wide variety of platforms, and I've been helping with how they tag motion control features. I also helped with a list of recommended games to try out for those looking to explore different kinds of motion controls. It's by no means a comprehensive list, but the database is growing, and suggestions are welcome. I've been really encouraged to learn how creator Andy Robertson values gyro aiming and wants to see it better represented in games generally, as well as in this database specifically. Check out our <a href="https://www.taminggaming.com/search/category/Motion+Controls+Better+Than+Joysticks">curated list here</a>.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion</guid>
				<title>Finding Gravity with Sensor Fusion</title>
				<link>http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion</link>
				<description>

&lt;p&gt;Whether for leaning, steering, absolute orientation calculations, or for world/player-space gyro controls, it can be useful to know which way gravity is pointing. Given that standard modern controllers all have 3-axis gyroscopes and 3-axis accelerometers (we see this basically everywhere except &lt;strong&gt;Xbox&lt;/strong&gt;, which has fallen behind), we will use these sensor inputs to figure out which way is &amp;quot;down&amp;quot;.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 26 Jul 2021 02:43:15 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Whether for leaning, steering, absolute orientation calculations, or for world/player-space gyro controls, it can be useful to know which way gravity is pointing. Given that standard modern controllers all have 3-axis gyroscopes and 3-axis accelerometers (we see this basically everywhere except <strong>Xbox</strong>, which has fallen behind), we will use these sensor inputs to figure out which way is &quot;down&quot;.</p> <p><a href="https://www.youtube.com/clip/UgzmEehcNJC4ewu0Ya54AaABCQ">Here's a quick explanation of why we want both these sensors working together</a>.</p> <p>As you can see in that example, just using the accelerometer alone often gives a decent approximation of the gravity direction. But it's also very prone to interference from other movement during regular play. Here, we'll start with a super simple sensor fusion solution that any game should be able to use easily &#8212; it's the same four-liner I shared in <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc12">Player Space Gyro (and Alternatives) Explained</a>. It's not perfect, but it's surprisingly robust given how simple it is. Then we'll explore a more fancy solution. As we do, it's a good idea to have this working alongside it to compare. Our fancy solution should be <em>at least</em> this good.</p> <h2><span>Something Simple</span></h2> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravitySimple</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// convert gyro input to reverse rotation</span><span class="hl-code"> </span><span class="hl-identifier">Quat</span><span class="hl-code"> </span><span class="hl-identifier">reverseRotation</span><span class="hl-code"> = </span><span class="hl-identifier">AngleAxis</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// rotate gravity vector</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">; </span><span class="hl-comment">// nudge towards gravity according to current acceleration</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">newGravity</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code">; </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-brackets">(</span><span class="hl-identifier">newGravity</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-number">0.02</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>We're going to work our way up from this simple solution to the one I have in <a href="https://github.com/JibbSmart/GamepadMotionHelpers">GamepadMotionHelpers</a> &#8212; a free and open source library to help with gyro controls, calibration, and gravity calculation. It's used in JoyShockMapper, and I separated it from JSM and JSL so that I could access the same functionality in multiple projects, regardless of how I'm getting controller info (SDL, platform-specific SDKs, etc).</p> <p>There's <em>probably</em> no one-size-fits-all gravity solution. Different applications will have different needs. But here were my needs when I implemented this for JoyShockMapper:</p> <ul> <li>The gravity vector should be steady when the controller is held steady;</li> <li>The gravity vector should update quickly, without smoothing, so it can be used for responsive lean and motion stick inputs;</li> <li>Corrections should be applied gradually, without sudden jumps;</li> <li>But we should apply corrections quickly enough that errors are kept very small.</li> </ul> <p>That sounds pretty reasonable, right? Now, let's break down our simple sensor fusion example line by line:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Quat</span><span class="hl-code"> </span><span class="hl-identifier">reverseRotation</span><span class="hl-code"> = </span><span class="hl-identifier">AngleAxis</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">;</span></pre></div> </div> <p>Convert gyro input into an angular displacement. This quaternion represents how much and in what direction the controller has rotated since the last update. But we invert the axis, because we're going to use the inverse of this rotation.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GravityVector</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">;</span></pre></div> </div> <p>Apply that reverse rotation to our last calculated <strong>GravityVector</strong>. This means we're re-using the previous update's gravity direction and accounting for any rotating the controller did in the meantime. To visualise this, if I were to point directly North and then rotate my whole body 35° to the right, I'd have to rotate my arm 35° in the <em>opposite direction</em> for it to still be pointing North. The gyro input gives us how much the controller has rotated, and so we rotate our gravity direction the same amount in the opposite direction to keep it pointing the same way.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">newGravity</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code">;</span></pre></div> </div> <p>If we didn't have the previous gravity vector to work with, our best guess at the current gravity direction would be the opposite of the acceleration detected by the accelerometer. This is because when the controller is held perfectly still, there's still an upwards acceleration applied to the controller counteracting gravity. The accelerometer detects that upwards acceleration.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-brackets">(</span><span class="hl-identifier">newGravity</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-number">0.02</span><span class="hl-code">;</span></pre></div> </div> <p>The accelerometer doesn't just detect gravity. It'll also detect other linear acceleration as well (shaking the controller, for example). Here we try to filter out those linear shakes. We're guessing that the actual gravity direction is somewhere between our reverse-rotated previous best guess at the gravity direction and the current acceleration detected by the accelerometer. That's what the magic <strong>0.02</strong> is for. You can set it to anything between 0 and 1.</p> <ul> <li><strong>0</strong> means the accelerometer input is ignored, and we're just relying on the gyro input to rotate our gravity vector correctly. Noise and rounding errors will accumulate with nothing to counter them.</li> <li><strong>1</strong> means the gyro input and the previous frame's gravity vector are ignored, and we're just assuming the accelerometer input represents the gravity direction. It'll often be right, but even subtle bumps and shakes of the controller will mess with that.</li> <li><strong>0.02</strong> means we are mostly trusting the gyro input, but we're always pushing it a little towards the accelerometer to avoid accumulating too much error. Increasing this number means less error accumulates over time from rounding errors, but we get more interference from moment-to-moment bumps and shakes.</li> </ul> <p><strong>0.02</strong> isn't particularly special. In fact, it's unsatisfying to do it like this &#8212; it means the error correction rate is tied to the update rate (possibly your game's frame-rate, if that's how often you're doing these updates). But in practice this quick and dirty solution works pretty darn well, and it'll be good to keep this function on hand as we try to do something fancier. We can compare results between this Simple function and our new Fancy function and see if our Fancy function is actually doing a good job.</p> <h2><span>Something Fancy</span></h2> <p>The last line of our simple solution is where we decide how quickly we move from the gravity direction we're just remembering (and un-rotating) from previous updates to just using the latest accelerometer vector (inverted). Our <strong>correction rate</strong> is just a constant 0.02 * the distance between our remembered gravity direction and our inverted acceleration.</p> <p>For our fancy solution, all we're going to do is try and be smarter about choosing that <strong>correction rate</strong>. Everything else remains the same. So what do we want to take into consideration when choosing a good correction rate?</p> <ol> <li>If the accelerometer input is changing a lot over time, the controller is probably shaking or being bumped. Trust the accelerometer less and decrease the correction rate.</li> <li>If the accelerometer input is very steady, it's probably giving us our gravity vector very accurately. Use a high correction rate to accept that new vector quickly.</li> <li>Seeing the gravity vector change when the controller isn't turning is jarring. We can hide corrections by keeping them in small proportion to the controller's turn rate.</li> <li>If there's a big difference between our remembered gravity vector and a trusted accelerometer gravity vector, we need to make corrections quickly.</li> </ol> <p>Sure, sometimes these considerations will be in tension with each other. For example, points <strong>3</strong> and <strong>4</strong> can be in tension when the controller is being held still but our remembered gravity vector is clearly very wrong. But it's not hard to make good decisions about how to resolve these. In this case, number <strong>4</strong> is the more important issue, and will override number <strong>3</strong>.</p> <p>So, before we start implementing these considerations, let's make room for them like so:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// convert gyro input to reverse rotation</span><span class="hl-code"> </span><span class="hl-identifier">Quat</span><span class="hl-code"> </span><span class="hl-identifier">reverseRotation</span><span class="hl-code"> = </span><span class="hl-identifier">AngleAxis</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// rotate gravity vector</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">; </span><span class="hl-comment">// nudge towards gravity according to current acceleration</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">newGravity</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravityDelta</span><span class="hl-code"> = </span><span class="hl-identifier">newGravity</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = ?? </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-identifier">gravityDelta</span><span class="hl-code"> * </span><span class="hl-identifier">correctionRate</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Now we just need to fill in the <strong>??</strong>.</p> <p>Let's start with considerations <strong>1</strong> and <strong>2</strong> from above. We need to know how shaky the accelerometer input has been. Here's an easy way to do this. Keep record of a smoothed out version of the accelerometer input. The shakier the accelerometer input is, the more the smoothed and unsmoothed inputs will differ. But sometimes a shaky input will happen to be near the smoothed input, so we keep track of the biggest shakiness we've had so far and slowly reduce it over time.</p> <p>For example, if we were to rapidly shake the controller, we might get the following accelerometer inputs in one axis: 2, 0, -2, 0, 2, 0, -2, 0&#8230; and so on (unrealistically consistent values chosen for simplicity). The smoothed accelerometer input will stay close to 0, because that's the rolling average here. When we get a 2 or -2 raw accelerometer input, these differ quite a bit from the smoothed acceleration, and we know the controller is being shaken. Our current &quot;shakiness&quot; value must be at least 2. But between every 2 and -2 in this example is a 0. That 0 is very close to the smoothed value, but not because the controller is being held still. It's just on its way from one extreme to another, passing the smoothed value along the way. If we remember that our most recent &quot;shakiness&quot; value is 2, we know this momentary 0 doesn't mean we're not shaking anymore. We can decrease our &quot;shakiness&quot; value a little just in case we continue to get more 0s or near-0s in a row. But if the next value is another 2 or -2, we will bump up the &quot;shakiness&quot; to 2 again.</p> <p>So we want an easy, low-effort way to track the smoothed acceleration input and a way to pull our &quot;shakiness&quot; towards 0 over time.</p> <p>One way to do smoothing is to interpolate between our last smoothed value and our latest raw value. In fact that's exactly what the last line of our super simple gravity function is doing &#8212; interpolating between our old <strong>GravityVector</strong> and our newly calculated <strong>newGravity</strong> where the interpolation factor is <strong>0.02</strong>. But we want a frame-rate independent version. We want it to behave basically the same whether we're updating at super high rates (500&#160;Hz or more), super low rates (30&#160;Hz or less), or anything in between.</p> <p>This is where the <strong>exp2</strong> function comes in. Its value in this context is in figuring out how far to move from one value to another at the same rate regardless of our frame-rate. See it properly explained in <a href="https://www.gamedeveloper.com/programming/improved-lerp-smoothing-">this blog post on GameDeveloper.com</a>.</p> <p>We can say, &quot;Hey, I want to move value <strong>A</strong> to value <strong>B</strong> such that the gap between the two values is halved every second.&quot; That will look like this:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">A</span><span class="hl-code"> = </span><span class="hl-identifier">Lerp</span><span class="hl-brackets">(</span><span class="hl-identifier">B</span><span class="hl-code">, </span><span class="hl-identifier">A</span><span class="hl-code">, </span><span class="hl-identifier">exp2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">DeltaSeconds</span><span class="hl-brackets">))</span><span class="hl-code">;</span></pre></div> </div> <p>Too slow? &quot;Let's have the gap close by half every quarter second.&quot;</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">A</span><span class="hl-code"> = </span><span class="hl-identifier">Lerp</span><span class="hl-brackets">(</span><span class="hl-identifier">B</span><span class="hl-code">, </span><span class="hl-identifier">A</span><span class="hl-code">, </span><span class="hl-identifier">exp2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code"> / </span><span class="hl-number">0.25</span><span class="hl-brackets">))</span><span class="hl-code">;</span></pre></div> </div> <p>Having done that, we can use the same interpolation factor for our smoothing function and for reducing our &quot;shakiness&quot; value:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Shakiness</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-code"> = </span><span class="hl-identifier">exp2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code"> / </span><span class="hl-number">0.25</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">Shakiness</span><span class="hl-code"> *= </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-code">; </span><span class="hl-identifier">Shakiness</span><span class="hl-code"> = </span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">Shakiness</span><span class="hl-code">, </span><span class="hl-brackets">(</span><span class="hl-identifier">accel</span><span class="hl-code"> - </span><span class="hl-identifier">SmoothAccel</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code">; </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code"> = </span><span class="hl-identifier">lerp</span><span class="hl-brackets">(</span><span class="hl-identifier">accel</span><span class="hl-code">, </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code">, </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p><strong>SmoothAccel</strong> is our smoothed acceleration value. When the player shakes or bumps the controller, the raw <strong>accel</strong> input will be very different from the smoothed acceleration. But before adding to our smoothed acceleration, we first update its direction, reverse-rotating it by however much the controller rotated since the last update (just like we do with the GravityVector). If we didn't do that, rotating the controller would look like shakiness, because the direction of the raw acceleration would differ from the direction of the smoothed acceleration.</p> <p>We're putting our <strong>exp2(-DeltaSeconds&#8230;)</strong> in a local variable <strong>smoothInterpolator</strong> so we can smooth our acceleration input and pull back our max shakiness at the same rate &#8212; in this case, halving the value every quarter of a second. Why are we pulling Shakiness back to 0 with <em>interpolation</em> and not just decreasing it at a fixed rate? Well, if we get an unexpectedly large input for one frame for whatever reason, bringing that down at a fixed rate could take a long time. Interpolating towards 0 like this means bigger values decrease very quickly, so we never have to wait long to recover from an unexpectedly large bump. Interpolating towards 0 is just done by multiplying our most recent value by smoothInterpolator. Easy!</p> <p>Now that we've calculated our <strong>Shakiness</strong>, we're just about ready to figure out how it should affect our <strong>correction rate</strong>. But first, we're going to need to introduce some settings that we can tune.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// the time it takes in our acceleration smoothing for 'A' to get halfway to 'B'</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">SmoothingHalfTime</span><span class="hl-code"> = </span><span class="hl-number">0.25</span><span class="hl-code">; </span><span class="hl-comment">// thresholds of trust for accel shakiness. less shakiness = more trust</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.4</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.01</span><span class="hl-code">; </span><span class="hl-comment">// when we trust the accel a lot (the controller is &quot;still&quot;), how quickly do we correct our gravity vector?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code"> = </span><span class="hl-number">1</span><span class="hl-code">; </span><span class="hl-comment">// when we don't trust the accel (the controller is &quot;shaky&quot;), how quickly do we correct our gravity vector?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">;</span></pre></div> </div> <p>We've moved that magic &quot;/ 0.25&quot; that we're using when we call <strong>exp2()</strong> for our frame-rate independent interpolation factor into a setting here: <strong>SmoothingHalfTime</strong>.</p> <p>I got most of these other values by a bit of trial and error. Feel free to experiment. But for <strong>ShakinessMinThreshold</strong> and <strong>ShakinessMaxThreshold</strong>, I just printed the <strong>Shakiness</strong> value to the screen every update and watched how it changed when holding the controller as still as I could or started moving it around. <strong>ShakinessMinThreshold</strong> is intended to be the shakiness we get when holding the controller perfectly still. <strong>ShakinessMaxThreshold</strong> is the shakiness we get when shaking the controller around just enough that the detected acceleration can't be trusted to give us a decent gravity vector. That's much more open to interpretation, but I've found this value to work well.</p> <p>My values will probably work well for you <em>if you're using the same units that I am</em>. Some controllers / platforms will give you acceleration in metres per second squared, so you'd expect GravityVector to usually be around 9.8. Others will give you acceleration in g units, which is what the datasheets for most IMUs use. That's what I use. What that means is that your GravityVector will usually have a length of roughly 1, which has useful consequences we can use later. But if you're using different units, that's fine. You can probably adjust most of these settings accordingly (multiply them by 9.8).</p> <p>Anyway, let's use our shakiness thresholds to get a number from 0-1 rating the controller's shakiness &#8212; where 0 means &quot;still&quot; and 1 means &quot;shaky&quot;. Then we'll choose our <strong>correctionRate</strong> to be somewhere between <strong>CorrectionStillRate</strong> and <strong>CorrectionShakyRate</strong> depending on that 0-1 number. This is how that looks:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// settings and state...</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-code"> = </span><span class="hl-identifier">exp2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code"> / </span><span class="hl-identifier">SmoothingHalfTime</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravityDelta</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravityDeltaDirection</span><span class="hl-code"> = </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Normalized</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> &gt; </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stillOrShaky</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">Shakiness</span><span class="hl-code"> - </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> - </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code"> + </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">stillOrShaky</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Shakiness</span><span class="hl-code"> &gt; </span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// apply correction</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">correction</span><span class="hl-code"> = </span><span class="hl-identifier">gravityDeltaDirection</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">correction</span><span class="hl-code">.</span><span class="hl-identifier">LengthSquared</span><span class="hl-brackets">()</span><span class="hl-code"> &lt; </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">LengthSquared</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-identifier">correction</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>There's a little complexity added just in case someone sets your ShakinessMaxThreshold to be less than or equal to ShakinessMinThreshold, which is nonsensical (and would cause divide by zero if they're equal!). In that case, we just use the ShakinessMaxThreshold to choose between CorrectionStillRate and CorrectionShakyRate. Speaking of safety, I'm assuming your <strong>Normalize()</strong> is safe to use even when the vector is all zeroes (it's common to just return a zero vector in that case). If not, add appropriate protections here.</p> <p>Once we have our <strong>correctionRate</strong>, we turn it into a <strong>correction</strong> vector. We also check if that permitted correction is more than we need so we don't overshoot our desired gravity vector.</p> <p>If you're following along with your own code, you can give this a go. It should work pretty well! In fact, if you are using your gravity vector in a way where sudden corrections won't be jarring to the player (for example, for gyro aiming), you could comfortably stop here. But there's still some shakiness with the gravity vector, even when holding the controller nearly still. This is where consideration number <strong>3</strong> comes in: &quot;We can hide corrections by keeping them in small proportion to the controller's turn rate.&quot;</p> <p>Let's say we're limiting our correction rate to 10% of the controller's rotation rate. That means if the controller is turning at 100 degrees per second, we can correct the gravity vector by 10 degrees per second. Since that correction is always much smaller than the controller's overall rate of rotation, corrections don't feel intrusive, and don't happen while holding the controller still.</p> <p>But! We're not correcting our gravity direction by angle. And converting between vectors and angles is slow and messy, so let's not. If we convert our gyro input to radians per second (some platforms / libraries will give you radians per second anyway, while others will use degrees per second), that gives us the linear velocity of a point 1 unit away from the controller's axis of rotation.</p> <p>This gives us a very simple calculation for limiting our correction rate:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// settings and state...</span><span class="hl-code"> </span><span class="hl-comment">// limit further corrections to this proportion of the rotation speed</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-comment">// my input library has the gyro report degrees per second, so convert to radians per second here</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angleRate</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">PI</span><span class="hl-code"> / </span><span class="hl-number">180</span><span class="hl-code">; </span><span class="hl-comment">// ...shakiness and correctionRate stuff goes here...</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionLimit</span><span class="hl-code"> = </span><span class="hl-identifier">angleRate</span><span class="hl-code"> * </span><span class="hl-identifier">GravityVector</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code">; </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code">, </span><span class="hl-identifier">correctionLimit</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// apply correction</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>Wasn't that easy? Now, in GamepadMotionHelpers I omit the <strong>GravityVector.Length()</strong> part, because that library uses <strong>g</strong> units and so I expect the gravity vector's length to be <em>roughly</em> 1 anyway.</p> <p>Okay, we're nearly there. Now we need to account for consideration number <strong>4</strong>: &quot;If there's a big difference between our remembered gravity vector and a trusted accelerometer gravity vector, we need to make corrections quickly.&quot; If we have a large error but our controller is at rest, this change we just made prevents any corrections from being made! So we should only make full use of <strong>CorrectionGyroFactor</strong> when we believe there's very little correcting needed anyway. Here's how I do that:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// settings and state...</span><span class="hl-code"> </span><span class="hl-comment">// limit further corrections to this proportion of the rotation speed</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-comment">// thresholds for what's considered &quot;close enough&quot;</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.05</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.25</span><span class="hl-code">; </span><span class="hl-comment">// no matter what, always apply a minimum of this much correction to our gravity vector</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionMinimumSpeed</span><span class="hl-code"> = </span><span class="hl-number">0.01</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-comment">// my input library has the gyro report degrees per second, so convert to radians per second here</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angleRate</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">PI</span><span class="hl-code"> / </span><span class="hl-number">180</span><span class="hl-code">; </span><span class="hl-comment">// ...shakiness and correctionRate stuff goes here...</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionLimit</span><span class="hl-code"> = </span><span class="hl-identifier">angleRate</span><span class="hl-code"> * </span><span class="hl-identifier">GravityVector</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code"> &gt; </span><span class="hl-identifier">correctionLimit</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> &gt; </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> &gt; </span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-number">1</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-number">0</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> + </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionLimit</span><span class="hl-code"> - </span><span class="hl-identifier">correctionRate</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// finally, let's always allow a little bit of correction</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code">, </span><span class="hl-identifier">CorrectionMinimumSpeed</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// apply correction</span><span class="hl-code"> </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p><strong>CorrectionGyroMinThreshold</strong> and <strong>CorrectionGyroMaxThreshold</strong> have been tuned assuming GravityVector will be <em>roughly</em> of length 1, so you'll need to adjust these if you're using different units. Basically, if the difference between our target gravity vector and GravityVector is small enough (at or below CorrectionGyroMinThreshold), we consider the error small enough that we'll limit corrections to <strong>CorrectionGyroFactor</strong> times the gyro input (in this case, 10% of the rotation rate from the gyro). If the error is too large (at or above CorrectionGyroMaxThreshold), we don't want to limit the correction rate at all. And if it's somewhere in between, we apply that limit more softly, avoiding hard transitions.</p> <p>Then right at the end we've added one more setting for you to tune: <strong>CorrectionMinimumSpeed</strong>. I think that even when the controller is perfectly still and the error isn't large, it's nice to still be doing at least a tiny bit of correction.</p> <p>So finally, here's everything put together:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// SETTINGS</span><span class="hl-code"> </span><span class="hl-comment">// the time it takes in our acceleration smoothing for 'A' to get halfway to 'B'</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">SmoothingHalfTime</span><span class="hl-code"> = </span><span class="hl-number">0.25</span><span class="hl-code">; </span><span class="hl-comment">// thresholds of trust for accel shakiness. less shakiness = more trust</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.4</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.01</span><span class="hl-code">; </span><span class="hl-comment">// when we trust the accel a lot (the controller is &quot;still&quot;), how quickly do we correct our gravity vector?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code"> = </span><span class="hl-number">1</span><span class="hl-code">; </span><span class="hl-comment">// when we don't trust the accel (the controller is &quot;shaky&quot;), how quickly do we correct our gravity vector?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-comment">// if our old gravity vector is close enough to our new one, limit further corrections to this proportion of the rotation speed</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-comment">// thresholds for what's considered &quot;close enough&quot;</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.05</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.25</span><span class="hl-code">; </span><span class="hl-comment">// no matter what, always apply a minimum of this much correction to our gravity vector</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">CorrectionMinimumSpeed</span><span class="hl-code"> = </span><span class="hl-number">0.01</span><span class="hl-code">; </span><span class="hl-comment">// STATE</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Shakiness</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">CalculateGravityFancy</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// convert gyro input to reverse rotation</span><span class="hl-code"> </span><span class="hl-identifier">Quat</span><span class="hl-code"> </span><span class="hl-identifier">reverseRotation</span><span class="hl-code"> = </span><span class="hl-identifier">AngleAxis</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// rotate gravity vector</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">; </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code"> *= </span><span class="hl-identifier">reverseRotation</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-code"> = </span><span class="hl-identifier">exp2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code"> / </span><span class="hl-identifier">SmoothingHalfTime</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">Shakiness</span><span class="hl-code"> *= </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-code">; </span><span class="hl-identifier">Shakiness</span><span class="hl-code"> = </span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">Shakiness</span><span class="hl-code">, </span><span class="hl-brackets">(</span><span class="hl-identifier">accel</span><span class="hl-code"> - </span><span class="hl-identifier">SmoothAccel</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code">; </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code"> = </span><span class="hl-identifier">lerp</span><span class="hl-brackets">(</span><span class="hl-identifier">accel</span><span class="hl-code">, </span><span class="hl-identifier">SmoothAccel</span><span class="hl-code">, </span><span class="hl-identifier">smoothInterpolator</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// ...</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravityDelta</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravityDeltaDirection</span><span class="hl-code"> = </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Normalized</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> &gt; </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stillOrShaky</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">Shakiness</span><span class="hl-code"> - </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-code"> - </span><span class="hl-identifier">ShakinessMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code"> + </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">stillOrShaky</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Shakiness</span><span class="hl-code"> &gt; </span><span class="hl-identifier">ShakinessMaxThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionShakyRate</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">CorrectionStillRate</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// limit in proportion to rotation rate</span><span class="hl-code"> </span><span class="hl-comment">// my input library has the gyro report degrees per second, so convert to radians per second here</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angleRate</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">PI</span><span class="hl-code"> / </span><span class="hl-number">180</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">correctionLimit</span><span class="hl-code"> = </span><span class="hl-identifier">angleRate</span><span class="hl-code"> * </span><span class="hl-identifier">GravityVector</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">CorrectionGyroFactor</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code"> &gt; </span><span class="hl-identifier">correctionLimit</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> &gt; </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-code"> - </span><span class="hl-identifier">CorrectionGyroMinThreshold</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> &gt; </span><span class="hl-identifier">CorrectionGyroMaxThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-number">1</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code"> = </span><span class="hl-number">0</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> + </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionLimit</span><span class="hl-code"> - </span><span class="hl-identifier">correctionRate</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">closeEnoughFactor</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// finally, let's always allow a little bit of correction</span><span class="hl-code"> </span><span class="hl-identifier">correctionRate</span><span class="hl-code"> = </span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code">, </span><span class="hl-identifier">CorrectionMinimumSpeed</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// apply correction</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">correction</span><span class="hl-code"> = </span><span class="hl-identifier">gravityDeltaDirection</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-identifier">correctionRate</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">correction</span><span class="hl-code">.</span><span class="hl-identifier">LengthSquared</span><span class="hl-brackets">()</span><span class="hl-code"> &lt; </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">.</span><span class="hl-identifier">LengthSquared</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-identifier">correction</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-identifier">gravityDelta</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>There it is! We now have a good approximation of where gravity is pointing in the controller's space. When I was implementing it myself, I had <strong>CalculateGravitySimple</strong> running alongside it to compare (make sure different versions of your <strong>CalculateGravity</strong>* function aren't updating the same <strong>GravityVector</strong> and interfering with each other). This helped me tune the settings to something that worked well, with pleasing looking steadiness when the controller is steady, without letting the error ever get out of hand even with prolonged shaking and rotating the controller. I highly recommend doing this yourself, just printing to the screen the angle between the gravity vector calculated the simple way and calculated the fancy way.</p> <h2><span>Conclusion</span></h2> <p>Now, what can you do with your gravity vector? Maybe you want to implement very robust <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc12">gravity-aware gyro aiming</a>. Maybe you want to lean the controller to steer a vehicle. If you're trying to match an in-game object's orientation to the controller's orientation, you can use this to counter rounding errors accumulated in the rotation over time.</p> <p>An accurate gravity vector isn't just useful for dealing with orientations. If you add the <strong>GravityVector</strong> to your <strong>accel</strong> input, the gravity portion of acceleration gets cancelled out, and you can use that result to detect intentional shaking of the controller in any axis you choose. This is why we don't normalize the gravity vector at the end &#8212; we've got something that better represents the direction and strength of gravity as detected by the accelerometer, and is important for separating deliberate shakes from the constant acceleration of holding the controller still:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Shake</span><span class="hl-code"> = </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> + </span><span class="hl-identifier">accel</span><span class="hl-code">;</span></pre></div> </div> <p>So there you have it! This sensor fusion solution has been working well for me and users of <a href="https://github.com/JibbSmart/GamepadMotionHelpers">GamepadMotionHelpers</a>. And implementing it yourself isn't terribly complicated. But if you'd rather have something you can just drop into your project, GamepadMotionHelpers is there. Currently, it's a single <strong>.hpp</strong> file you can drop into your C++ project. You can look at <a href="https://github.com/Electronicks/JoyShockMapper">JoyShockMapper</a> to see how it's used.</p> <p>Feel free to find me on <a href="https://twitter.com/JibbSmart">Twitter</a> if you have any questions or feedback. Thanks!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained</guid>
				<title>Player Space Gyro (and Alternatives) Explained</title>
				<link>http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained</link>
				<description>

&lt;p&gt;Gyro aiming turns your whole controller into a frictionless mouse. It offers precision far beyond traditional stick-only aiming, and is much easier for new players to learn.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 01 Jul 2021 05:10:45 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Gyro aiming turns your whole controller into a frictionless mouse. It offers precision far beyond traditional stick-only aiming, and is much easier for new players to learn.</p> <p>But the gyro in modern controllers detects rotation (or actually <em>speed</em> of rotation &#8212; angular velocity) in <em>3 axes</em>, but a mouse only detects motion in <em>2</em>. The purpose of this article is to compare the two most common spaces for converting gyro input into a mouse-like input (<strong>local</strong> space and <strong>world</strong> space), and introduce <strong>player</strong> space as a more robust, more versatile solution that gets the best of both where it matters most.</p> <p>You can read in the <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc13">Additional Material</a> about why we're primarily interested in using the gyro as a mouse (as opposed to giving players 3 degrees of rotational freedom), but that's secondary.</p> <div class="alert alert-info"> <p><strong>Too long? Read this</strong>: This article might look very long, but you can probably ignore a lot of it. It aims to give thorough explanations and examples of different gyro spaces, including code examples. Even if you don't want to use the <strong>player space</strong> solution advocated for here, you can find examples of robust <strong>world space</strong> and <strong>local space</strong> solutions. Since local and world space are relatively well-known concepts, feel free to skip those parts if they don't interest you.</p> <p>The first section, <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc0">The Basics</a>, is targeted at players looking to better understand the options that should be available to them. This surface level look is also useful for developers before getting into code examples. Code examples and break-downs can be found in the <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc4">In Depth and In Code</a> section. It gets into implementation details of player space gyro as compared with good examples of local and world space.</p> <p>Finally, curious developers may want to explore some of the <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc9">Additional Material</a>. This includes the problem of &quot;turn&quot; players vs &quot;lean&quot; players, future work for one-size-fits-all gyro controls, examples of how to calculate the gravity vector from the controller's gyroscope and accelerometer, and why gyro as a mouse is so important.</p> </div> <p>I've spent a fair bit of time playing with player space gyro controls and I can't imagine going back. I shared the first public implementation in <a href="https://github.com/Electronicks/JoyShockMapper/releases">JoyShockMapper version 3.2</a> (before it was called &quot;player space&quot;), and the user response was extremely positive. Players noted some room for improvement at extreme angles, and so version 3.2.1 has the new and improved player space gyro controls described in this article. It also has the option to use the world space solution detailed here or stick with the default local space gyro. Look up the <em>GYRO_SPACE</em> setting <a href="https://github.com/Electronicks/JoyShockMapper#4-gyro-mouse-inputs">in the readme</a> to see what options are available. This article details the most important options there: <em>LOCAL</em>, <em>WORLD_TURN</em>, and <em>PLAYER_TURN</em>.</p> <p>In the end, player space gyro controls are very simple. 4 lines of code, give or take:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayer</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// use world yaw for yaw direction, local combined yaw for magnitude</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-comment">// dot product but just yaw and roll</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code"> = </span><span class="hl-number">1.41</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>But where'd we get that gravity vector from? What is this actually doing? We'll answer those questions soon enough. But first we need to understand the alternatives, what we're taking from them, and what problems we're solving by using player space gyro instead.</p> <table style="margin:0; padding:0"> <tr> <td style="margin:0; padding:0"> <div id="toc"> <div id="toc-action-bar"><a href="javascript:;" >Fold</a><a style="display: none" href="javascript:;" >Unfold</a></div> <div class="title">Table of Contents</div> <div id="toc-list"> <div style="margin-left: 1em;"><a href="#toc0">The Basics: Local, World, Player</a></div> <div style="margin-left: 2em;"><a href="#toc1">Local Space</a></div> <div style="margin-left: 2em;"><a href="#toc2">World Space</a></div> <div style="margin-left: 2em;"><a href="#toc3">Player Space</a></div> <div style="margin-left: 1em;"><a href="#toc4">In Depth and in Code</a></div> <div style="margin-left: 2em;"><a href="#toc5">Local</a></div> <div style="margin-left: 2em;"><a href="#toc6">World</a></div> <div style="margin-left: 2em;"><a href="#toc7">Player</a></div> <div style="margin-left: 2em;"><a href="#toc8">Conclusion</a></div> <div style="margin-left: 1em;"><a href="#toc9">Additional Material</a></div> <div style="margin-left: 2em;"><a href="#toc10">Turn vs. Lean</a></div> <div style="margin-left: 2em;"><a href="#toc11">Future Work</a></div> <div style="margin-left: 2em;"><a href="#toc12">Sensor Fusion: Finding Gravity</a></div> <div style="margin-left: 2em;"><a href="#toc13">The Importance of Gyro as a Mouse</a></div> </div> </div> </td> </tr> </table> <h1><span>The Basics: Local, World, Player</span></h1> <p>Almost every modern game controller and phone has an <strong>IMU</strong> in it &#8212; an Inertial Measurement Unit. This can include a gyroscope (measuring angular velocity, usually in 3 axes), accelerometer (measuring acceleration, usually in 3 axes), and a magnetometer (measuring the magnetic field, usually in 3 axes). No standard console controller I know of includes a magnetometer, which can be very helpful for tracking the device's orientation in world space. But for using the controller as a mouse, the gyro and accelerometer will suffice &#8212; or often just the gyro on its own!</p> <h2><span>Local Space</span></h2> <p>The vast majority of gyro-controlled games I've played on Switch and PS4 use <strong>local space</strong> gyro controls. Same for input remappers like <a href="https://github.com/Electronicks/JoyShockMapper">JoyShockMapper</a>, <a href="https://store.steampowered.com/">Steam Input</a>, <a href="https://github.com/Ryochan7/DS4Windows/">DS4Windows</a>, and <a href="https://www.rewasd.com/">reWASD</a>. This is where the game doesn't care about the controller's orientation in real world space. It largely ignores the accelerometer (which can be used to detect gravity). All it cares about, moment to moment, is the controller's angular velocity around its local axes:</p> <img src="http://gyrowiki.jibbsmart.com/local--files/sandbox-blog:player-space-gyro-and-alternatives-explained/GyroAxesLocal_1920.png" alt="GyroAxesLocal_1920.png" class="image" /> <p>In this image you can see the controller's <em>local axes</em>. We start with a top-down view of the controller on the left, then turn it about its local <strong>yaw</strong> axis (in green), then its local <strong>pitch</strong> axis (red), and finally its local <strong>roll</strong> axis (blue). As we turn the controller around each of those axes, the other two axes <em>change</em> relative to our view, but <em>stay the same</em> relative to the controller &#8212; that green <strong>yaw</strong> axis is always pointing in the same direction as the controller's sticks and its blue <strong>roll</strong> axis points out in the direction of the triggers and shoulder buttons.</p> <p><strong>Pros:</strong></p> <ul> <li>Incredibly simple to implement;</li> <li>Very accurate;</li> <li>Works reliably and consistently regardless of player posture (sitting up, lying back, etc) or environment (living room, bedroom, International Space Station, etc).</li> </ul> <p>That last point makes local gyro ideal for games on handheld devices (phones, tablets, Switch in handheld mode).</p> <p><strong>Cons:</strong></p> <ul> <li>Default behaviour may not be intuitive for players depending on how they prefer to hold their controller;</li> <li>As players pitch the controller up or down from their neutral position, the change in yaw axis can make it difficult to use.</li> </ul> <p>I've advocated for local-only controls for a long time due to their simplicity and accuracy. It's very difficult for developers to get it wrong. By only relying on one sensor (the gyro), we can expect very reliable results. But I want to remove that initial friction for players who don't intuitively hold their controller flat. I don't want them to have to deal with unclear settings, like having to choose between &quot;yaw&quot; and &quot;roll&quot; (which games present <em>inconsistently</em> on Switch!).</p> <p>In this side-on view of a controller held two ways, notice that when the controller is held flat (on the left), the controller's up/yaw axis lines up with the player's up axis. But if the controller is held upright (on the right), now the controller's forward/roll axis is lined up with the player's up axis. A local solution doesn't know which of these the player is using. Players who are used to holding their controller upright will generally prefer to rotate the controller about their up/yaw axis, which in this position is the controller's forward/roll axis.</p> <img src="http://gyrowiki.jibbsmart.com/local--files/sandbox-blog:player-space-gyro-and-alternatives-explained/GyroAxesSide.png" alt="GyroAxesSide.png" class="image" /> <p>Ideally, we'd have a solution that can tell how the player is holding the controller and adjust accordingly. Not only would we fit the needs of more players by default, but players would be able to aim accurately in a much wider range of positions than with local gyro aiming. Because no matter how far the player pitches their controller up or down, the yaw axis would remain intuitive for them. So let's look at some such alternatives:</p> <h2><span>World Space</span></h2> <p><strong>World space</strong> gyro controls calculate the direction of gravity to figure out which way is &quot;up&quot; relative to the player, and then keep the <strong>yaw</strong> axis aligned with the player's &quot;up&quot;. See below how regardless of how the controller is oriented, the green <strong>yaw</strong> axis stays pointing in the same direction:</p> <img src="http://gyrowiki.jibbsmart.com/local--files/sandbox-blog:player-space-gyro-and-alternatives-explained/GyroAxesWorld_1920.png" alt="GyroAxesWorld_1920.png" class="image" /> <p>By using the accelerometer to detect the direction of gravity, the controller's <em>local space</em> inputs can be converted to <em>world space</em>. Whether you hold the controller flat, upright, or even upside down, players can always turn the camera left and right by turning the controller left and right relative to them.</p> <p><strong>Pros:</strong></p> <ul> <li>More intuitive to players when implemented well;</li> <li>Allows players to continue to aim accurately from a wider range of positions.</li> </ul> <p><strong>Cons:</strong></p> <ul> <li>Relatively difficult to implement well;</li> <li>Not as good for handheld / mobile, since players may be reclining;</li> <li>Significantly more prone to error from miscalculating the direction of gravity.</li> </ul> <p>World space solutions are much more difficult to implement than local space solutions. To figure out where &quot;up&quot; is, we'll usually combine input from the accelerometer and gyro. This falls under a broad category called &quot;sensor fusion&quot;, and you can read more about it in the Additional Material section of this article <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc12">Sensor Fusion: Finding Gravity</a>.</p> <p>Even with the gravity direction in our hand, it's not necessarily obvious how to use that and the local angular velocities we get from the gyro to turn it into an appropriate camera or cursor movement. There aren't many games that offer world space gyro aiming, and some of those that do encounter serious issues (<em>Mario Odyssey</em>). But for brevity's sake, let's point to <em>Splatoon 2</em> as an example of a good, robust world space gyro aiming solution. You'll find the world space solution recommended here to be very similar.</p> <p>But here's the big catch with world space gyro controls: the software will often not be exactly correct about which way is up. There will frequently be <em>some</em> error in the calculated gravity vector, and when playing with world space gyro controls, <em>that error is passed on to the player</em>.</p> <p>If the calculated gravity vector is 10° off, this can mean the player's input vector will be interpreted as 10° off as well. Those <strong>10 degrees</strong> translate to a <strong>17.4% displacement error</strong>. By that I mean, if the player moves their aimer or cursor 100 units, the resulting position could be as much as 17.4 units away from where it would've been without the error. It's worth noting that because players are self-correcting as they make movements, it shouldn't feel too bad. But thanks to human reaction times, that self-correction is delayed. Fast flick shots will have all the error with no time for self-correction.</p> <p>To me, this error is the biggest shortcoming of a world space gyro aiming solution. Even though it removes (or rather reduces) a source of <em>player</em> error that comes with local space gyro aiming, it adds a new source of <em>algorithmic</em> error that's out of the player's hands. Is the 10° example typical? I don't know. It depends on how you're calculating gravity and how much the player is moving the controller about. But with the same gravity calculation, with the same error, we can do much better.</p> <p>And that's the main purpose of this article: to present and explain <strong>player space</strong> gyro, which offers the same <em>algorithmic</em> accuracy as <em>local space</em> while giving the player even more intuitive control than <em>world space</em>.</p> <h2><span>Player Space</span></h2> <p>While local space and world space gyro aiming solutions will usually ignore any rotation that's not in two preferred axes, <strong>player space</strong> gyro aiming works a bit differently. Player space gyro trusts that the player's inputs in all 3 axes are <em>intentional</em> and <em>meaningful</em>. It still has some constraints, but generally it means players can continue to play in:</p> <ul> <li>local space, without having to tell the game whether they prefer to hold the controller flat or upright;</li> <li>world space, without worrying that a miscalculated gravity vector is adding error to their aim;</li> <li>or anything in between.</li> </ul> <img src="http://gyrowiki.jibbsmart.com/local--files/sandbox-blog:player-space-gyro-and-alternatives-explained/GyroAxesPlayer_001.png" alt="GyroAxesPlayer_001.png" class="image" /> <p>It's difficult to illustrate, but here's the idea: <strong>pitch</strong> rotation is read exclusively from the <em>local</em> pitch axis (red). The <strong>yaw</strong> axis is at right angles to the pitch axis, and is calculated from the combination of the controller's local <strong>yaw</strong> and <strong>roll</strong> angular velocities, with some help from gravity. This means the yaw axis could be anywhere on that green ring depicted above.</p> <p>It might not be intuitive, but using local pitch all the time generally works very well. If the player is rotating their controller in world space yaw and pitch axes, the controller's local pitch actually always lines up with the world space pitch. It's only when players <strong>roll</strong> their controller that they go out of alignment. But players generally don't roll their controller very far anyway because on its own it isn't doing anything for their aim. And even when they have rolled out of alignment with the world pitch axis, it turns out just using local pitch is very intuitive for players anyway.</p> <p><em>Zelda Breath of the Wild</em> actually combines world space yaw and local space pitch, as far as I can tell. <em>Splatoon 2</em> used to do the same until moving to a full world space solution that we'll explore later.</p> <p>But this player space gyro doesn't use world space yaw, necessarily. It may also use local space yaw, or another axis in between, if that's what the player is using. Because in practice, players don't turn their controller exactly in their intended axis with respect to the controller, and they don't turn their controller exactly in their intended axis with respect to gravity (let alone whatever direction the application <em>thinks</em> gravity is pointing in). Some of their rotation will be lost to the ignored local roll or world roll axes.</p> <p>This is what player space handles really well. It calculates a moment-to-moment yaw axis by combining the angular velocities in the local yaw and roll axes. The magnitude of the player's rotation is <em>always</em> respected and expressed in game. By trusting that the rotations in all 3 axes are intentional, it's pretty straightforward to convert these into the intended in-game movement.</p> <p><strong>What does this actually mean for the player?</strong> Player space gyro controls are easy to pick up. Whether you're used to <em>Splatoon</em> or <em>DOOM</em> (world or local, respectively), it should just work as expected. But players looking to make the most of player space gyro controls should consider it the same as a world space solution, because player space and world space gyro both offer far more range of movement than local space. Turn left and right by rotating the controller left and right with respect to your body / gravity. Pitch up and down by pitching the controller up and down. Player space offers even more freedom of movement than world space <em>without any of its algorithmic error</em>.</p> <p><strong>Pros:</strong></p> <ul> <li>Intuitive to just pick up and use (like world space gyro);</li> <li>Very accurate (like local space gyro);</li> <li>Resistant to player error (<em>actual</em> axis of rotation vs <em>intended</em> axis of rotation);</li> <li>Allows players to continue to aim accurately from a wider range of positions (like world space gyro).</li> </ul> <p><strong>Cons:</strong></p> <ul> <li>More difficult to implement than local;</li> <li>Reliance on gravity means not ideal for handheld / mobile (like world space);</li> <li>Difficult to explain (so maybe just tell players to treat it like world space).</li> </ul> <p>Ideally, I think it's probably best to default to <strong>local</strong> space in handheld modes and <strong>player</strong> space when using a controller. And this is very simple.</p> <p>Player space gyro mostly avoids the inaccuracy of world space by relying on gravity much less. But we still do need to take gravity into consideration. If you hold your controller pitched up at a 45° angle and then turn your body left and right to turn the controller about your yaw axis, your controller is turning in its local yaw and roll axes at the same time. It'll depend on the coordinate space of your game or your controller, but for simplicity, let's say that as you turn left, the controller's yaw and roll velocities are both positive. But now pitch your controller down at a 45° <em>below</em> the horizon instead of above it and continue to turn left. Now the controller's rolling has been inverted. You still have positive yaw velocity, but negative roll velocity.</p> <p>I'll go more in-depth in other sections, but to put it simply, we expect different behaviour in the local axes depending on whether the controller is pitched down, pitched up, or even upside-down. And in order to correctly combine these values, we need to use gravity to figure out which way we're pointing.</p> <p>Remember when we said a 10° error with a world space solution can give you a 17.4% displacement error? With player space gyro controls, despite the fact that we do use the same gravity vector in our calculations, we get none of the error.</p> <p>Now, because we have to invert yaw or roll input in some orientations, players treating player space gyro as local space gyro don't <em>technically</em> have the full freedom of movement they are used to. As they approach the boundaries where their local input would be inverted, it first gets pinched down towards zero to avoid sudden inversions. However, I think the range afforded to local space players before pinching begins is still very wide and lets players pitch their controller far higher or lower than they'd normally be comfortable with. So I think narrowing that space is a reasonable trade-off for no longer having to change and invert axes to get their aiming set up to their liking in the first place.</p> <p>But ultimately my recommendation to players would be to treat <em>player space</em> gyro like <em>world space</em> gyro. You can enjoy the wider range of movement and more intuitive aiming that comes with it, while still all of the algorithmic precision of local aiming.</p> <h1><span>In Depth and in Code</span></h1> <p>Let's unpack all 3 spaces with some pseudocode. We'll start extremely simple. For these, we're using the PlayStation controller's coordinate space:</p> <ul> <li>X = side/pitch</li> <li>Y = up/yaw</li> <li>Z = forward/roll</li> </ul> <p>This tutorial is based on my work on an input remapper. This means my output is virtual mouse events that will then be received by the game I'm playing. If you're also working on an input remapper, your experience will be similar. But if not, your outputs for camera yaw and pitch may need to be inverted when trying these examples.</p> <h2><span>Local</span></h2> <p>Here is your most basic local gyro aiming implementation:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraLocal</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Simple, right? This assumes the gyro and game angles are in the same units &#8212; radians per second / radians, or degrees per second / degrees. Almost every game with gyro controls I've played has another multiplier in there to convert to a different sensitivity scale. Why? Because that's what we're used to with mouse and stick aiming. There's no natural scale, so we pick one that looks nice. Maybe we pick a nice max value and call it &quot;100%&quot;.</p> <p>Please stop doing this with gyro controls. We're converting from an angle to an angle! That's it! &quot;1&quot; should mean a real world angle converts to the same in game angle. &quot;2&quot; should mean the camera turns twice as much as the controller. And so on. Anyway. That's not what this is about. If this is new to you, please check out <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Good Gyro Controls Part 1</a>, or this much shorter summary on <a href="https://www.gamasutra.com/blogs/JibbSmart/20210330/379034/The_Absolute_Basics_of_Good_Gyro_Controls.php">Gamasutra</a>.</p> <p>Enough distractions. Let's get back to it.</p> <p>Some players like to hold their controller upright. Let's give them the option:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraLocal</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroTurnAxis</span><span class="hl-code"> == </span><span class="hl-identifier">Yaw</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Invert if you need to for your coordinate space. A lot of games let the player choose which axes they invert, which is good.</p> <p>Finally, we have the <em>Overwatch</em> solution, which just has both axes enabled at the same time. It also has different sensitivities in each axis, which is great! The player will use whichever they're most comfortable with. I first guessed they were just adding the <strong>yaw * yawSensitivity</strong> + <strong>roll * rollSensitivity</strong>. But as I fleshed out my own such solution, I encountered edge cases similar to what I saw in <em>Overwatch</em>, so I think I have a pretty good idea how it actually works:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraLocal</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">yawAxes</span><span class="hl-code"> = </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">yawDirection</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">yawAxes</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-brackets">)</span><span class="hl-code"> &gt; </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">yawAxes</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">))</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">yawDirection</span><span class="hl-code"> = </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">yawAxes</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">yawDirection</span><span class="hl-code"> = </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">yawAxes</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">yawAxes</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">yawDirection</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>For simplicity I skipped separate sensitivities for each axis and inversion in each axis. They're great, but not the most interesting part here. What's interesting is that the yaw and roll inputs aren't just added together to form the camera yaw output. Instead, we use the length of the vector formed by the yaw and roll components. This has the effect of treating both axes as two components in a rotation about a single axis.</p> <p>See, for VR and other applications where we want to track the orientation of the controller, we treat the gyro input as a simple rotation about a single axis in 3D space. The three components of the gyro input form the direction of that axis, and the length gives is the size of the rotation. If we were to ignore one axis (in this case, pitch) and combine the others under the assumption they represent a single rotation about one axis, this is how we'd do it.</p> <p>This means that the player can turn their controller in the local yaw axis <em>or</em> the local roll axis <em>or</em> another axis in between and the magnitude of the turn will be expressed in game.</p> <p>The only <strong>problem</strong> is that when we get the length of that axis, we lose the signs of the individual components. What if the <em>yaw</em> component on its own would've turned the camera <em>right</em> while the <em>roll</em> component would've turned the camera <em>left</em>? In that case, as far as I can tell, the game has the larger of the two components decide the yaw direction. And this is where things can start to feel weird.</p> <p>As long as the player keeps their controller pitched above the horizon but not so far that the sticks point below the horizon, they can turn their controller in the world yaw axis and get the appropriate rotation in game. If they go out of that range, yaw and roll will &quot;disagree&quot; with each other, but as long as they don't go too far further the larger of the two axes will keep rotation sensible. But as they reach the point where yaw and roll are of similar magnitude, players can experience this weird back-and-forth sudden inversion of their turn direction as one axis momentarily exceeds the other.</p> <p>So while it sometimes works as a world-space solution, it's probably best for players to treat <em>Overwatch</em>'s gyro controls as local-only and try to turn their controller in their preferred local axis. <em>Overwatch</em> will honour their turn completely as long as their real axis of rotation doesn't stray too far from the controller's local axis.</p> <p>And while there's a lot of good in <em>Overwatch</em>'s solution, if you want something that still takes both yaw and roll axes at the same time without dealing with sudden inversions, I wouldn't fault you for sticking with something simpler: <strong>Camera.yaw += gyro.Y * Settings.YawSensitivity + gyro.Z * Settings.RollSensitivity</strong>. Done like that, when input axes start to disagree, they'll start to cancel out gradually, and so players are hopefully encouraged to stick to one local axis.</p> <h2><span>World</span></h2> <p>A robust world space solution is really simple if you've already got your gravity vector. Sure, calculating the gravity vector yourself can be complicated, but depending on your platform or libraries you're using you may already have gravity calculated for you. If not, you can learn about calculating a gravity vector from gyro and accelerometer input in a later section (<a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc12">Sensor Fusion: Finding Gravity</a>). For now we'll just assume you've got it. I'm also assuming it's normalised (we're interested in the <em>direction</em> of gravity, not the <em>strength</em> of it).</p> <p>Getting the world yaw rotation is very simple, because the world yaw axis is always aligned with gravity. Taking the gyro input as a 3D vector, how much of that vector is aligned with gravity? That's a simple dot product, and that gives us our yaw.</p> <p>Pitch is a little trickier. The world pitch axis for our purposes is actually still based on the direction the controller is pointing (unlike world yaw). There are a couple of ways to do this. One (bad) way is to calculate the controller's orientation (usually a quaternion), convert to Euler angles, and use the difference in pitch between the previous frame and the current frame. Since we're treating gyro as a mouse, we're not using that absolute orientation in game. More on that in <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc13">The Importance of Gyro as a Mouse</a> later on in this article. Changes in the controller's real world pitch don't always translate nicely to an in-game pitch velocity, especially when the controller's pitch and the camera's in-game pitch differ. It also means you have to deal with the pitch velocity flipping as the player goes past pointing vertical. <em>Mario Odyssey</em> does this when controlling a tank. It's not fun when that happens.</p> <p>It also means you have to decide what the &quot;front&quot; of the controller is. This differs from player to player (some hold it flat, some hold it upright, and some in-between). The whole point of us pursuing a world space solution is to <em>avoid</em> that. So let's avoid that issue altogether.</p> <p>Here's a much simpler, much more robust way of dealing with pitch that will behave well under all practical use. Take the controller's local pitch axis, project it onto the horizontal plane (as determined by the direction of gravity). Any rotational velocity around <em>that</em> axis will be treated as pitch velocity in game.</p> <p>As you lean the controller onto its side, the controller's local pitch axis might line up with the gravity direction, and then you can no longer project the pitch axis onto the horizontal plane. That's okay. When gyro aiming, it'd be very unusual for players to lean their controller on the side like that. To avoid the pitch axis changing suddenly, we can just reduce the pitch velocity as we get near that boundary.</p> <p>Let's look at some code:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraWorld</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// some info about the controller's orientation that we'll use to smooth over boundaries</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flatness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is flat</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">upness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is upright</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">flatness</span><span class="hl-code">, </span><span class="hl-identifier">upness</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-number">0.125</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-number">0.125</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-code">); </span><span class="hl-comment">// world space yaw velocity (negative because gravity points down)</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Dot</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// project pitch axis onto gravity plane</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code"> = </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">; </span><span class="hl-comment">// shortcut for (1, 0, 0).Dot(gravNorm)</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">pitchVector</span><span class="hl-code"> = </span><span class="hl-identifier">Vec3</span><span class="hl-brackets">(</span><span class="hl-number">1</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-identifier">gravNorm</span><span class="hl-code"> * </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code">; </span><span class="hl-comment">// that's all it took!</span><span class="hl-code"> </span><span class="hl-comment">// normalize. it'll be zero if pitch and gravity are parallel, which we ignore</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">IsZeroVector</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">pitchVector</span><span class="hl-code"> = </span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">Normalize</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-comment">// camera pitch velocity just like yaw velocity at the beginning</span><span class="hl-code"> </span><span class="hl-comment">// (but squish to 0 when controller is on its side)</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> * </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Dot</span><span class="hl-brackets">(</span><span class="hl-identifier">pitchVector</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p><strong>sideReduction</strong> is calculated such that it's normally just '1', and it will be '0' if the controller is totally on its side. If the controller is <em>almost</em> on its side, <strong>sideReduction</strong> will be between 0 and 1, and will reduce the effect of pitching the controller. Players will find similar behaviour in <em>Splatoon 2</em>.</p> <p>Because neither pitch nor yaw depend on what the player considers to be the front of their controller, this will work whether players like to hold their controller flat, upright, or somewhere in between. If you pitch the controller so far that it's upside down, both yaw and pitch will <em>still</em> behave as expected from the player's point of view. So if you want a world space solution, I think you'll find this to be a great starting point.</p> <p>Now that our axes of rotation are determined by our calculated gravity vector, any error in our gravity vector will result in the same amount of error in input direction. A 10° error in gravity direction can translate to a 10° error in input direction. And then there's <em>player error</em>. The player will often not be turning in exactly their intended axis. Some of their intentional rotation can and will be lost if it's not in the world yaw or calculated world pitch axis. So let's finally get to the <strong>player space</strong> solution, which addresses both of these issues.</p> <h2><span>Player</span></h2> <p><strong>Player space</strong> gyro uses the most valuable parts of local and world space gyro. We will:</p> <ul> <li>Use local pitch;</li> <li>Combine yaw and roll to get rotation around whatever axis the player is actually using;</li> <li>Use the gravity direction to detect when yaw or roll need to be inverted to combine together more nicely;</li> <li>Also use the gravity direction to smooth over boundaries between axis inversions.</li> </ul> <p>Let's start by taking the simplest parts of world space and local space. World space has a one-liner for yaw, local space has a one-liner for pitch. So let's use those:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayer</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// world yaw:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Dot</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>This is a nice and simple start. I think this is basically what <em>Zelda Breath of the Wild</em> does (and <em>Splatoon 2</em> used to do). You can tell by turning your controller on its side and turning it in its local pitch axis (which happens to also be the world yaw axis). The camera turns diagonally, pitching and yawing at the same time in roughly equal measure.</p> <p>Let's remove local pitch from the world yaw calculation to avoid that interference:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayer</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// world yaw, but only using local yaw and roll</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>This stops local pitch from interfering with world yaw input, although I don't think that was <em>really</em> a big issue. The bigger gain here is that pitch input no longer contributes to yaw output. And we already stopped yaw and roll from contributing to pitch output. This axis cross-contamination was the biggest source of error when the gravity vector is calculated incorrectly. Being 10° out of axis means some of the yaw input can go to the pitch output. But because of the way circles work, that 10° adds 17.4% of the yaw input to the pitch output (sin(10°) = 0.174) while only removing 1.5% of the yaw input from the yaw output (1 - cos(10°) = 1 - 0.985 = 0.015). Check out this <a href="https://www.mathsisfun.com/algebra/trig-interactive-unit-circle.html">interactive unit circle</a> to see what I mean.</p> <p>Now that the yaw and roll inputs can't add to the pitch output (and vice versa), we have eliminated that bigger source of error. We have also made it so that the player can't turn their controller on its side and still operate it in world space, but for mouse-like control in games, players aren't doing that.</p> <p>But it's not enough to just reduce the error. Let's <em>get rid of it altogether</em>. If we're trusting that the player is intentional with their input rotation, we want to use the full yaw and roll input. We do this by getting the length of the vector composed of yaw and roll (square root of the sum of squares). For readability, like with the same example in the local space section, we're just going to convert them to a 2D vector and get the length, but now we're using the sign of our world space yaw to decide which direction to turn:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayer</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// use world yaw for yaw direction, local combined yaw for magnitude</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-comment">// dot product but just yaw and roll</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Now no intentional player input is lost. We used gravity to figure out whether yaw and roll should be combined into a left rotation or a right rotation, and we can count on that being correct for any remotely reasonable input. But we're not quite there. Although it's uncommon, some players are already used to unusual ways to map controller rotations to camera rotations (we'll get to that in <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained#toc10">Turn vs. Lean</a>). If players turn their controller in the &quot;lean&quot; axis (world roll) instead of the &quot;turn&quot; axis (world yaw), players will find their turn direction seemingly working until it suddenly inverts as they cross a threshold they cannot see.</p> <p>In practice, I think it's good to place some constraints on the input to teach players how it's meant to be used. Secondly, even if players intuitively aim correctly, as they explore the limits of acceptable input and find these thresholds where the input suddenly inverts, players will perceive this as buggy behaviour (we don't count on players to be familiar with <a href="https://en.wikipedia.org/wiki/Garbage_in,_garbage_out">garbage in, garbage out</a>).</p> <p>When players are rotating very far out of axis like that, the magnitude of the world yaw turn will be much smaller than the total combined local yaw and roll. In fact, before inverting, world yaw will approach 0 pretty nicely. If players are turning their controller weirdly and getting little to no response from the game, they'll quickly pick up that's not how it's supposed to be used (just like turning the controller out of axis with a world space or local space gyro solution). So let's allow the constrained world yaw to take over when players start turning too far out of axis:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayer</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// use world yaw for yaw direction, local combined yaw for magnitude</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-comment">// dot product but just yaw and roll</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code"> = </span><span class="hl-number">1.41</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>The magnitude of world yaw will always be less than or equal to combined yaw and roll. When combined yaw and roll line up perfectly with the world yaw they'll be of equal length, but as they get further out of alignment, world yaw gets smaller and smaller. Our <strong>min</strong> function there will always use the <strong>worldYaw</strong> value. But if we bump it up with our magic <strong>yawRelaxFactor</strong> number, we create a buffer zone where we will use the full combined yaw and roll as long as they're close enough to being aligned with world yaw. If the axis of rotation gets too far away from our calculated world yaw axis (which is just the gravity direction), the output will start being squeezed down towards zero.</p> <p>This buffer zone is where we make room for the player to unintentionally be out of axis and/or for the gravity vector to be calculated wrong. The example above uses a value of <strong>1.41</strong> to give us a roughly (90 - asin(1/1.41) =) <strong>45°</strong> buffer, which I think is probably enough to give the player error-free world-like gyro aiming. Even if the gravity error + player error gets out of that range, it'll still constrain the player less than a strict world space solution.</p> <p>But I claimed that players used to local space solutions (like those offered in the vast majority of gyro aiming games) could just pick up and play with a player space solution, as well. As long as players are within that buffer zone, they can operate entirely in the controller's local space, and it'll work great (regardless of whether they favour holding their controller flat and using local yaw or holding it upright and using local roll). +-45° is arguably not much room, though. That's why in JoyShockMapper I have set <strong>yawRelaxFactor</strong> to <strong>2</strong> to give players (90 - asin(1/2) =) <strong>60°</strong> of freedom for local space aiming. Even with a local-only solution, players will try to avoid turning their controller very far out of their comfortable range because it can adversely affect their aim. +-60° should be plenty of room for most. If you're happy to trust players more and give them more freedom of movement, you might prefer to do the same in your game.</p> <p>It's up to you what your &quot;relax factor&quot; is. Just remember that the more you constrain the player's input to an axis calculated from gravity, the more likely their aim will be affected by errors in the gravity calculation (even if only slightly). The less you constrain it, the more likely players are to think weird inputs are supposed to be accepted. I think the vast majority of players will be served well by a value between 1.15 (which gives about 30°) and 2 (60°), and you can experiment outside of that range, too.</p> <p>But there you have it! That's all you need to implement player space gyro controls.</p> <h2><span>Conclusion</span></h2> <p>Player space gyro is not a silver bullet. Local is still probably better for handheld, where the player is far less likely to be aligned with gravity and the &quot;controller&quot; is always in the exact same position relative to the screen. And in theory, having unintended roll input add to intentional yaw input might cause stability issues for some users. This was something I was mindful of as I tested and worked on it, but for me, at least, this hasn't been an issue.</p> <p>Player space gyro's increased freedom of movement has made it hard for me to go back to local space gyro. I have far more room to pitch my controller up and down in highly vertical games like <em>Quake Champions</em> and <em>Fortnite</em> without losing any accuracy with my horizontal aiming. And although my world space solution is robust and offers similar benefits, I can feel my movements are more constrained with it than with player space. Player space gyro aiming lets me perform flick shots more accurately thanks to its error-free handling of gravity. And since it's also much simpler to implement than a robust world space solution, I believe player space to be the uncompromising winner.</p> <p>JoyShockMapper's users have responded extremely positively to being able to use player space gyro aiming! So I encourage you to give some form of player space gyro a go, and give your players the option to use it. It could be a great default setting when players are playing with a controller.</p> <p>When exposing this option to users, it may be simpler to call it &quot;world&quot; as opposed to the alternative &quot;local&quot; space. But if you have tooltips or something to offer a more detailed description, gyro power-users will appreciate knowing that they're using a &quot;player space&quot; solution.</p> <div class="alert alert-info"> <p><strong>In Summary</strong>:</p> <ul> <li>For handhelds, <strong>local space gyro</strong> probably remains the best solution, where the screen is always in a fixed position in relation to the &quot;controller&quot;, and players may be lying back, forward, sitting up, or really be in any position;</li> <li>For everything else, <strong>player space gyro</strong> lets the player turn the controller relative to their body while being simpler to implement and far less prone to error than standard world space gyro solutions. This allows better defaults, fewer settings to fit the player's needs, and far more freedom of movement;</li> </ul> </div> <p>But does this fit <em>every</em> player's preferences? How do we find gravity if we don't already have it? Why is it so important to see the gyro as a mouse? Check out the Additional Material below!</p> <h1><span>Additional Material</span></h1> <p>For a more complete understanding of the current state of gyro controls and what options are available to you, here's some more info to check out.</p> <h2><span>Turn vs. Lean</span></h2> <p>It's not uncommon for games that offer gyro controls to also let players choose their axis of rotation. They'll normally let players choose between <strong>yaw</strong> and <strong>roll</strong>. But there are some problems with this:</p> <ol> <li>Players don't intuitively know what <strong>yaw</strong> and <strong>roll</strong> are. This isn't helped by the fact that games can sometimes present this incorrectly, too. <em>Overwatch</em>'s &quot;yaw&quot; and &quot;roll&quot; make sense in handheld mode, but are incorrect with a detached controller. The player's understanding of what these axes are will depend on which game they first played with this option.</li> <li>Players will choose &quot;roll&quot; for different reasons. Some, to hold the controller upright and turn in world space yaw, which <strong>player space</strong> handles for them. But some prefer to hold their controller flat and <em>lean</em> their controller from side to side to turn the camera left and right. Again, this is something that some games offer by default (see <em>Overwatch</em>'s &quot;Pro Controller&quot; preset, which interprets roll as a flat lean rather than an upright turn).</li> </ol> <p>We have two points of ambiguity here between yaw and roll. Does the player know the correct axis names? Does the player intend to use their controller flat or upright? In order to avoid these issues, I've started calling world space yaw &quot;<strong>turn</strong>&quot; and world space roll &quot;<strong>lean</strong>&quot;. With a world space or player space solution, we can ignore the issue of whether the player holds their controller flat or upright, and instead just let them decide whether they want to &quot;turn&quot; the controller or &quot;lean&quot; it.</p> <p>I think &quot;turn&quot; on its own may be vague, but the &quot;lean&quot; option contextualises it. &quot;Lean&quot; can only mean one thing, I think. So &quot;turn&quot; must mean the other.</p> <p>With that out the way, the <strong>player space</strong>, <strong>world space</strong>, and <strong>local space</strong> solutions described above assume every player prefers to <em>turn</em> their controller to turn the camera. This is <em>definitely</em> the best default. It's most intuitive to most players, and is obviously a more natural mapping of the controller rotation to the camera rotation. Games that don't let players choose between yaw and roll will almost always map <em>turning</em> the controller to <em>turning</em> the camera.</p> <p>However, there are some players who are more used to <em>leaning</em> their controller to control the camera. In the past, I thought this was relatively common as a few people complained about the default controls in JoyShockMapper. Over time I've realised that those complaints almost always came from the player holding the controller upright (which clashed with JSM's default local yaw to turn) rather than the player preferring to lean. I've found it very difficult to find players that actually prefer <em>lean</em> over <em>turn</em>, but someone owned up to being a convert from lean to turn in the Gyro Gaming Discord server, so there's that.</p> <p>So if you really want to cover all preferences, you may want to provide &quot;lean&quot; options alongside your &quot;turn&quot; options. These will require more code than &quot;turn&quot; solutions because we'll no longer be using the gravity axis (world yaw) directly. Instead, we need to calculate a new world roll axis from the world yaw and world pitch axes.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayerLean</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// some info about the controller's orientation that we'll use to smooth over boundaries</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flatness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is flat</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">upness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is upright</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">flatness</span><span class="hl-code">, </span><span class="hl-identifier">upness</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-number">0.125</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-number">0.125</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-code">); </span><span class="hl-comment">// project pitch axis onto gravity plane</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code"> = </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">; </span><span class="hl-comment">// shortcut for (1, 0, 0).Dot(gravNorm)</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">pitchVector</span><span class="hl-code"> = </span><span class="hl-identifier">Vec3</span><span class="hl-brackets">(</span><span class="hl-number">1</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-identifier">gravNorm</span><span class="hl-code"> * </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code">; </span><span class="hl-comment">// normalize. it'll be zero if pitch and gravity are parallel, which we ignore</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">IsZeroVector</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">rollVector</span><span class="hl-code"> = </span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">Cross</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">IsZeroVector</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">rollVector</span><span class="hl-code"> = </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Normalize</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldRoll</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">rollRelaxFactor</span><span class="hl-code"> = </span><span class="hl-number">1.15</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldRoll</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> * </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">worldRoll</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">rollRelaxFactor</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <h2><span>Future Work</span></h2> <p>What about a true one-size-fits-all solution? Can we handle <em>turn</em> players (both flat and upright) and <em>lean</em> players (both flat and upright) at the same time? <em>Maybe</em>. Let's combine both a <strong>player turn</strong> and a <strong>player lean</strong> solution, ultimately choosing the outcome that aligns best with the player's input rotation, and smooth over the transition between the two:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GyroCameraPlayerTurnOrLean</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">turn</span><span class="hl-code"> = </span><span class="hl-number">0</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lean</span><span class="hl-code"> = </span><span class="hl-number">0</span><span class="hl-code">; </span><span class="hl-comment">// use world yaw for yaw direction, local combined yaw for magnitude</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldYaw</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-comment">// dot product but just yaw and roll</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code"> = </span><span class="hl-number">1.15</span><span class="hl-code">; </span><span class="hl-identifier">turn</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">worldYaw</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">yawRelaxFactor</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-comment">// some info about the controller's orientation that we'll use to smooth over boundaries</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flatness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is flat</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">upness</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// 1 when controller is upright</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">max</span><span class="hl-brackets">(</span><span class="hl-identifier">flatness</span><span class="hl-code">, </span><span class="hl-identifier">upness</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-number">0.125</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-number">0.125</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-code">); </span><span class="hl-comment">// project pitch axis onto gravity plane</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code"> = </span><span class="hl-identifier">gravNorm</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">; </span><span class="hl-comment">// shortcut for (1, 0, 0).Dot(gravNorm)</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">pitchVector</span><span class="hl-code"> = </span><span class="hl-identifier">Vec3</span><span class="hl-brackets">(</span><span class="hl-number">1</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> - </span><span class="hl-identifier">gravNorm</span><span class="hl-code"> * </span><span class="hl-identifier">gravDotPitchAxis</span><span class="hl-code">; </span><span class="hl-comment">// normalize. it'll be zero if pitch and gravity are parallel, which we ignore</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">IsZeroVector</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">rollVector</span><span class="hl-code"> = </span><span class="hl-identifier">pitchVector</span><span class="hl-code">.</span><span class="hl-identifier">Cross</span><span class="hl-brackets">(</span><span class="hl-identifier">gravNorm</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">IsZeroVector</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">rollVector</span><span class="hl-code"> = </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Normalize</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">worldRoll</span><span class="hl-code"> = </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> + </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code"> * </span><span class="hl-identifier">rollVector</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">rollRelaxFactor</span><span class="hl-code"> = </span><span class="hl-number">1.15</span><span class="hl-code">; </span><span class="hl-identifier">lean</span><span class="hl-code"> -= </span><span class="hl-identifier">sign</span><span class="hl-brackets">(</span><span class="hl-identifier">worldRoll</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">sideReduction</span><span class="hl-code"> * </span><span class="hl-identifier">min</span><span class="hl-brackets">(</span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">worldRoll</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">rollRelaxFactor</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">())</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// our &quot;weights&quot; here are how important each axis is based on the comparative size of the inputs</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">turnWeight</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">turn</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">leanWeight</span><span class="hl-code"> = </span><span class="hl-identifier">abs</span><span class="hl-brackets">(</span><span class="hl-identifier">lean</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">cutoffFactor</span><span class="hl-code"> = </span><span class="hl-number">0.75</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">turnWeight</span><span class="hl-code"> != </span><span class="hl-number">0</span><span class="hl-code"> || </span><span class="hl-identifier">leanWeight</span><span class="hl-code"> != </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// this cutoff will reduce the smaller component to 0 if it's not close in size to the larger component</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">turnWeight</span><span class="hl-code"> &lt; </span><span class="hl-identifier">leanWeight</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">cutoff</span><span class="hl-code"> = </span><span class="hl-identifier">leanWeight</span><span class="hl-code"> * </span><span class="hl-identifier">cutoffFactor</span><span class="hl-code">; </span><span class="hl-identifier">turnWeight</span><span class="hl-code"> *= </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">turnWeight</span><span class="hl-code"> - </span><span class="hl-identifier">cutoff</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">leanWeight</span><span class="hl-code"> - </span><span class="hl-identifier">cutoff</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">cutoff</span><span class="hl-code"> = </span><span class="hl-identifier">turnWeight</span><span class="hl-code"> * </span><span class="hl-identifier">cutoffFactor</span><span class="hl-code">; </span><span class="hl-identifier">leanWeight</span><span class="hl-code"> *= </span><span class="hl-identifier">clamp</span><span class="hl-brackets">((</span><span class="hl-identifier">leanWeight</span><span class="hl-code"> - </span><span class="hl-identifier">cutoff</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">turnWeight</span><span class="hl-code"> - </span><span class="hl-identifier">cutoff</span><span class="hl-brackets">)</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// weighted average, but thanks to our cutoff this will usually just mean one or the other</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-brackets">(</span><span class="hl-identifier">turn</span><span class="hl-code"> * </span><span class="hl-identifier">turnWeight</span><span class="hl-code"> + </span><span class="hl-identifier">lean</span><span class="hl-code"> * </span><span class="hl-identifier">leanWeight</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">turnWeight</span><span class="hl-code"> + </span><span class="hl-identifier">leanWeight</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// local pitch:</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">Settings</span><span class="hl-code">.</span><span class="hl-identifier">GyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>This combined solution also narrows the range in which players can turn their controller in local space (whether accidentally or intentionally) and have their rotation fully expressed in game. In practice, I do find that I bump into the boundary between turning and leaning (such as when I happen to turn my controller in its local yaw when it's pitched above the horizon). Hitting this boundary still feels abrupt, even with the smaller <strong>yawRelaxFactor</strong> and <strong>rollRelaxFactor</strong> I have here (30° buffer). Reducing the <strong>cutoffFactor</strong> will soften the boundary some more, but also require me to be more careful about how I'm using the controller.</p> <p>The code shown here is a very naive solution. It just combines our player turn and player lean functions and uses a weighted average (with some filtering on the weights) for the output. For this to be useful in a game would take more work in my opinion. Even if that work is fruitful, whether such a compromise is worthwhile probably depends a lot on what proportion of players actually prefer to lean their controller to turn their camera. Turning is both more natural and is what most games teach players (notably all Nintendo-made Switch games that offer gyro/motion aiming only offer &quot;turn&quot; controls). So I won't recommend this solution just yet, but there it is if you want to check it out.</p> <h2><span>Sensor Fusion: Finding Gravity</span></h2> <p>The accelerometer will often tell you which way gravity is pointing, but not always. A perfect accelerometer will only detect 0 in all axes when the controller is in freefall. When the controller is held still in the player's hands, those hands are exerting an upward force on the controller to keep it from falling. The accelerometer detects that, and you can flip it and say &quot;gravity is pointing <em>that way</em>.&quot; But the accelerometer is <em>also</em> detecting linear acceleration, whether from intentional shakes/waggles, or unintentional movement during regular play. The accelerometer alone cannot reliably tell you the direction of gravity.</p> <p><strong>Sensor fusion</strong> to the rescue! &quot;Sensor fusion&quot; refers to combining data from more than one kind of sensor in a way that gives you a better result than using just one kind of sensor. In this case, when the controller is moving around, we calculate the gravity vector by just rotating our previous gravity vector by our gyro input (inverted). This will do a great job maintaining our gravity vector for the most part, but will gradually accumulate errors (rounding errors, sample rate errors, and noise from the gyro). By the time that error is significant, we hope the controller will have been held still long enough for us to calculate a brand new gravity vector using the accelerometer. A good breakdown of this idea (with a bit of maths in it) can be found on the <a href="https://developer.oculus.com/blog/sensor-fusion-keeping-it-simple/">Oculus developer blog</a>.</p> <p>Your platform or engine may already provide you with a good gravity vector calculation. But if not, you'll want to find one you can use in your project relatively easily. You can find a detailed description of my own gravity solution <a href="http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion">in another article here on GyroWiki</a>. That tutorial details the implementation found in <a href="https://github.com/JibbSmart/GamepadMotionHelpers">GamepadMotionHelpers</a> &#8212; my header-only sensor fusion and gyro calibration library used by JoyShockMapper and JoyShockLibrary &#8212; which you may be able to drop into your project without too much hassle.</p> <p>But if you just want a <strong>super simple code snippet you don't have to think too hard about</strong>, that you can easily just write in whatever language your project is using, it may be that all you need is a simple <em>complementary filter</em>. The complementary filter is the simplest sensor fusion solution imaginable, and it's surprisingly robust. It works like this:</p> <p>Let's assume that, on average, the (inverted) accelerometer input will be in the direction of gravity. We can smooth that input to reduce the effects of bumps and shakes. But smoothing would mean the direction would lag behind as we rotate our device. Thankfully, the gyro is really precise for moment-to-moment rotations, so we use our gyro input to continually rotate our smoothed gravity vector. That will look something like this:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">SensorFusionGravity</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">gyro</span><span class="hl-code">, </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">accel</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// convert gyro input to reverse rotation</span><span class="hl-code"> </span><span class="hl-identifier">Quat</span><span class="hl-code"> </span><span class="hl-identifier">rotation</span><span class="hl-code"> = </span><span class="hl-identifier">AngleAxis</span><span class="hl-brackets">(</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">()</span><span class="hl-code"> * </span><span class="hl-identifier">DeltaSeconds</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code">, -</span><span class="hl-identifier">gyro</span><span class="hl-code">.</span><span class="hl-identifier">Z</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// rotate gravity vector</span><span class="hl-code"> </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> *= </span><span class="hl-identifier">rotation</span><span class="hl-code">; </span><span class="hl-comment">// nudge towards gravity according to current acceleration</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">newGravity</span><span class="hl-code"> = -</span><span class="hl-identifier">accel</span><span class="hl-code">; </span><span class="hl-identifier">GravityVector</span><span class="hl-code"> += </span><span class="hl-brackets">(</span><span class="hl-identifier">newGravity</span><span class="hl-code"> - </span><span class="hl-identifier">GravityVector</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-number">0.02</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>You may be surprised how nicely this behaves. There are fancier alternatives, such as the <a href="https://en.wikipedia.org/wiki/Kalman_filter">Kalman filter</a>) and my own <a href="http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion">fancy filter</a>, but this simple complementary filter gives good results. And since our <em>player space</em> gyro controls are robust even with an inaccurate gravity vector, this may meet our needs just fine.</p> <h2><span>The Importance of Gyro as a Mouse</span></h2> <p>Motion sensors have a wide variety of applications. Using them to track the controller's orientation is an obvious one. Using them to sense shakes and gestures is another, though a lot of people avoid games like that altogether. But <strong>gyro as a mouse</strong> is, in my opinion, the <strong>most important utility of motion sensors</strong>.</p> <p>Camera control has been largely standardised across first- and third- person shooters, RPGs, action games, and more. In games that require the player to aim accurately, players are given two options:</p> <ol> <li>Use a mouse, and get direct control over the camera that is consistent across virtually all games in similar genres;</li> <li>Or use a controller, and the game will bend over backwards to help you with your precise aiming.</li> </ol> <p>It's not a big deal that the game helps the player aim. Or at least it wouldn't be if all players received the same help. But in recent years, &quot;cross-play&quot; is more and more expected. PC and console players are playing together, and if the aim assist in these games was developed in isolation of PC players, it usually offers advantages over mouse aiming that can be exploited by savvy players.</p> <p>Wouldn't it be great if every player had a mouse without having to put down their controller? No extra fingers required. No aim assist required. Just turning the controller to provide a mouse-like input to the game.</p> <p>The only thing getting in the way of this being standard in all applicable games is the lack of developer knowledge. When games do offer gyro aiming, it's usually only on the Switch version (despite it being possible on every platform except Xbox). They also frequently provide disappointing options that don't let players reach their potential.</p> <p>Great gyro control options are super simple. You just need to know what's helpful and what's not!</p> <p>GyroWiki (you are here!) provides a variety of resources to help you make the most of your controller. Developers, check out:</p> <ul> <li><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Good Gyro Controls Part 1: The Gyro is a Mouse</a>, which details the basic options that every game should provide, common gotchas to avoid, and advanced options for advanced users. It's a little old now, so it doesn't include any info about <strong>player space</strong> gyro, but everything there still applies, and is still missed by most games today. If that's too long a read for you, please at least check out this shorter summary of the basics on <a href="https://www.gamasutra.com/blogs/JibbSmart/20210330/379034/The_Absolute_Basics_of_Good_Gyro_Controls.php">Gamasutra</a>.</li> <li><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">Good Gyro Controls Part 2: The Flick Stick</a>, which details a new way to use the right stick once your precision aiming is handled by gyro. See it in action in the video below.</li> </ul> <p><iframe src="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained/html/545256d79b9e4ac79d68729a0e0474897c58e466-98093553202879875" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Player space gyro doesn't change the fact that good gyro controls are easy to implement. Although there's a lot of code on this page detailing alternatives, you saw that <strong>player space gyro</strong> itself is only a few simple lines of code. Isn't it great when simple solutions are also the best? So far, that looks to be the case here.</p> <p>Players and developers on PC can enjoy all of the features described on this site in almost any game using <a href="https://github.com/Electronicks/JoyShockMapper">JoyShockMapper</a>, which converts inputs from standard PlayStation (4 &amp; 5) and Switch controllers to mouse, keyboard, and controller input. It features local, world, or player space gyro, flick stick, natural sensitivity, smart smoothing and acceleration options, traditional stick controls, advanced button bindings, and more. You can also enjoy many of these features in other input remappers, like <a href="https://store.steampowered.com/">Steam</a>, <a href="https://github.com/Ryochan7/DS4Windows/">DS4Windows</a>, and <a href="https://www.rewasd.com/">reWASD</a>.</p> <p>The gyro is a mouse. Every game that plays better with a mouse <em>also</em> plays better with gyro. Gyro controls are easier and more intuitive for new players to pick up, provide more room for mastery by skilled players, and go a long way to closing the gap between playing with a controller and playing with a mouse. Let's embrace the gyro as a mouse on every platform that'll let us. Let's change how games are played.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-3-and-the-future</guid>
				<title>JoyShockMapper 3 and the Future</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-3-and-the-future</link>
				<description>

&lt;h1&gt;&lt;span&gt;JoyShockMapper 3&lt;/span&gt;&lt;/h1&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 29 Mar 2021 06:13:38 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <h1><span>JoyShockMapper 3</span></h1> <p>Over the weekend, JoyShockMapper 3 (<strong>JSM</strong> 3) came out. <a href="https://github.com/Electronicks/JoyShockMapper/releases/tag/v3.0.0">Get it here</a>!</p> <p>This is by far the <strong>biggest update ever</strong>. If you have <a href="https://github.com/ViGEm/ViGEmBus/releases">ViGEm Bus</a> installed, JSM 3 will let you set up virtual XBox or PS4 controllers that the game can see. This means instead of having to map the left stick to WASD to move around with it, you can just map it to the left stick of a virtual controller and keep all the freedom of movement you'd get on console, and if the game is able to combine controller and mouse input properly, you can enjoy full analog movement and gyro aiming + flick stick at the same time. Having a virtual controller also gives you rumble support.</p> <p>This update also provides &quot;SDL2&quot; versions, adding support for almost every common controller out there &#8212; Xbox, Stadia, Gamecube, and many generic PC controllers. These won't have gyro, but you'll still be able to use JSM's powerful customisation options.</p> <p>You can now use the DualShock 4 and the DualSense's touchpads, use a stick like a scroll wheel, and control the rate at which JSM updates the mouse so that even low-report-rate controllers like Joy-Cons (reporting at 66.7&#160;Hz) can provide a smooth aiming experience on high refresh rate monitors.</p> <p>There's a lot more, and it's worth checking out the <a href="https://github.com/Electronicks/JoyShockMapper/blob/master/CHANGELOG.md">changelog</a>.</p> <p>This is also the <em>first official release of JSM that I didn't build</em>. That's because JSM is now in the very capable hands of long-time contributor Nicolas/Electronicks.</p> <h1><span>The Future of JoyShockMapper</span></h1> <p>Known in the gyro gaming discord and on GitHub as Electronicks, Nicolas has done more work on JoyShockMapper than any other contributor. While JSM has always offered the best gyro controls around and was for a long time the only way to play with flick stick, the software was pretty light on features as a general input remapper before Nicolas got involved. He was responsible for a wide variety of quality-of-life features we take for granted (such as AutoLoad, whitelisting, and the tray menu), more advanced control mapping features (like chords, simultaneous press, and advanced binding modifiers), and many more things in between &#8212; not to mention most of the great new features in JSM 3!</p> <p>Search for &quot;<strong>Nicolas</strong>&quot; in that changelog linked earlier and see how much his name comes up.</p> <p>As early as November 2019, I was trying to find a balance between working on new videos, articles for GyroWiki, and game prototypes to further explore better controls. I had vague plans to gradually improve JSM, but it wasn't my highest priority. Meanwhile, Nicolas had already contributed to JSM 1.3 and had some cool features in the works for the next update.</p> <p>I didn't want to slow him down, but also had my own vision for the software and concerns about the time it takes to prepare each new build. Every update needs documentation, testing, and at the time I was making a new video for every update, too. I was trying to find a balance of still holding onto JSM and guiding its overall direction, trying to make time for these other gyro/control-related projects, while also trying not to stifle the generous work of Nicolas and other contributors.</p> <p>To be really clear: Nicolas has always been very respectful of my ownership of JoyShockMapper.</p> <p>But Nicolas has proven without a shadow of a doubt that he has a great vision for the future of JSM. He knows it inside-out. Even a lot of the features that haven't changed outwardly have been refactored by him. No one knows it better than he does. As I've tried to keep JSM updated, I have had almost no time to work on anything else. My other projects are just getting nowhere without dedicating my time and attention to them. But thanks to Nicolas, JoyShockMapper can keep improving without demanding so much of my time. Clearly, this is better for everyone!</p> <p>This means JoyShockMapper lives on <em>his</em> GitHub page now &#8212; <a href="https://github.com/Electronicks/JoyShockMapper">Electronicks/JoyShockMapper</a>. I'll update my GitHub page to point people there, so no one should have trouble finding the latest version. JSM will follow Nicolas' vision going forward. This also means he has final say on what features get included and what don't. And if one day he wants to move on to other things, it's up to him to decide what that means for JoyShockMapper.</p> <p>But just as Nicolas was able to be very involved in JSM when it was mine, I will continue to be involved now that it's not. Sometimes I'll work on features specifically for JSM. But there are a few other things I'm working on, some of which will improve JSM indirectly, and some are more for the benefit of the gyro gaming community generally. Hopefully you'll like them.</p> <h1><span>What I'm Working On</span></h1> <h2><span>JoyShockOverlay</span></h2> <p>Most videos on my channel and gifs on this website make use of JoyShockOverlay, which displays a 3D controller on screen in real-time. You can see examples with some of the supported controllers below.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-3-and-the-future/html/65232126b42b2e10eec4fe64f01cc8a0ad42d424-7177289652044498680" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>People have been asking for a public version of this for a long time. I should have more time to work on it now.</p> <h2><span>SDL2</span></h2> <p><a href="https://www.libsdl.org/">SDL2</a> is an incredibly helpful open source library for cross-platform game development. This isn't my project. But I have recently gotten involved with it as well. My main interest in SDL2 is in its controller support. While dozens of different controllers have essentially the same layout, they often expose the buttons to developers in different orders. This is tricky for game developers &#8212; should I map &quot;jump&quot; to button 1, 2, 3&#8230;? SDL2 has a large database of different controllers and how their button order best maps to a standard Xbox 360 controller layout.</p> <p>SDL2 can be used in a controller-only mode, so regardless of what tools you're using for rendering and so on, you can still use SDL2 for controller support. But it didn't have a way of accessing gyro from controllers that have it, so it wasn't useful to me for JoyShockMapper. I made JoyShockLibrary so that developers can easily use Switch and PlayStation controllers interchangeably with access to gyro and accelerometer (converted to the same space and units), but it's still inconvenient for developers to have to deal with more than one input library.</p> <p>At the end of 2020, SDL version <a href="https://discourse.libsdl.org/t/sdl-2-0-14-released/28470">2.0.14</a> came out, adding functions to read the gyro and accelerometer from supported controllers. At the time, only PlayStation's DualShock 4 and DualSense were supported, and so I got involved and added support for Switch Pro Controller and Joy-Cons. These will likely be included in the next update. This also means SDL2 now supports all the controllers that JoyShockLibrary and JoyShockMapper support and more, so we made a new version of JoyShockMapper that uses SDL2 instead of JoyShockLibrary. This is why JSM 3 supports so many more controllers &#8212; it's using SDL2.</p> <p>I will continue to be involved with SDL2. It is already well supported and widely used, and we can reasonably expect more controllers to be added in future &#8212; and as they do, JoyShockMapper will support them, too!</p> <h2><span>GamepadMotionHelpers</span></h2> <p>JoyShockLibrary does more than just provide input from PlayStation and Switch controllers in a platform-agnostic way. It also includes calibration features that JoyShockMapper has always relied upon and a sensor fusion solution that JSM uses for lean and motion-stick bindings.</p> <p>In order to complete the move from JoyShockLibrary to SDL2, I created <a href="https://github.com/JibbSmart/GamepadMotionHelpers">GamepadMotionHelpers</a>. This very simple single-header library makes it easy to calibrate your gyro and to combine gyro and accelerometer to detect real-world orientation. Now that this functionality is separated from the library that actually reads the controller input, this can be used with SDL2 or any other input library.</p> <p>It already covers all the features users were used to with JoyShockLibrary, but I'm also working on options for automatic calibration, like what players are used to encountering when playing Switch and PlayStation games with gyro aiming. While I still urge developers to let the player manually calibrate their gyro if they so wish, automatic calibration is a helpful default, and I think this is a good way to make it available to as many developers as possible. Of course, when that's ready, it may be included as an option in JoyShockMapper, too!</p> <h2><span>More</span></h2> <p>I want to make more time to work on <strong>GyroWiki articles</strong> to help developers, including the long-awaited <strong>Good Gyro Controls Part 3</strong>. I have prototypes that I want get out there for people to play with and <strong>explore better controls</strong> (gyro or otherwise). I have a lot more videos I want to make on the <strong>Gyro Gaming <a href="https://youtube.com/gyrogamingjs">YouTube channel</a></strong>.</p> <p>I hope you're looking forward to these, as well as whatever else Nicolas has in store for JoyShockMapper!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-2-0-released</guid>
				<title>JoyShockMapper 2.0 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-2-0-released</link>
				<description>

&lt;p&gt;JoyShockMapper 2.0 has been released &amp;#8212; a few weeks ago, actually.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 26 Oct 2020 05:38:52 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper 2.0 has been released &#8212; a few weeks ago, actually.</p> <p>This is a huge update, adding advanced binding modifiers, advanced motion controls, and a bunch of little tweaks and improvements to the features that were already there. The <a href="https://github.com/JibbSmart/JoyShockMapper/blob/master/CHANGELOG.md">CHANGELOG</a> is well worth checking out.</p> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-2-0-released/html/87b6cbce1e71740c82a258b27f49a5f3dba92afe-2181616861280422493" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-6-released</guid>
				<title>JoyShockMapper 1.6 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-6-released</link>
				<description>

&lt;p&gt;The main addition here is modeshift, which lets you change settings while a button is pressed &amp;#8212; stick modes, sensitivities, etc. It&#039;s required some big changes under the hood. Check it out, and let us know how it goes. I won&#039;t claim any credit for this update &amp;#8212; this one was all the doing of Nicolas, who&#039;s contributed a lot over the last few updates.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sun, 14 Jun 2020 15:07:59 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>The main addition here is modeshift, which lets you change settings while a button is pressed &#8212; stick modes, sensitivities, etc. It's required some big changes under the hood. Check it out, and let us know how it goes. I won't claim any credit for this update &#8212; this one was all the doing of Nicolas, who's contributed a lot over the last few updates.</p> <p>Check out the CHANGELOG and the README to learn more.</p> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-5-released</guid>
				<title>JoyShockMapper 1.5 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-5-released</link>
				<description>

&lt;p&gt;JoyShockMapper 1.5 has been released!&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Wed, 29 Apr 2020 13:58:13 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper 1.5 has been released!</p> <p>Another feature update, another bunch more ways to customise your controls. It's got:</p> <ul> <li>Double press bindings;</li> <li>New stick modes MOUSE_RING, FLICK_ONLY, and ROTATE_ONLY;</li> <li>Back and forward mouse button bindings;</li> <li>Flick stick snapping;</li> <li>Ring bindings working with all stick modes.</li> </ul> <p>And more. Check out the CHANGELOG to see what's new and the README to see how to use them.</p> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-4-released</guid>
				<title>JoyShockMapper 1.4 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-4-released</link>
				<description>

&lt;p&gt;JoyShockMapper 1.4 has been released!&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sat, 15 Feb 2020 14:20:02 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper 1.4 has been released!</p> <p>Okay, it's been out a few weeks now. This is another one enjoying the benefits of some generous code contribution.</p> <p>It's got:</p> <ul> <li>Simultaneous and chorded presses;</li> <li>Ring bindings (for triggering an input on full or partial tilt of a stick);</li> <li>Bluetooth support for DS4;</li> <li>Whitelisting helpers for those relying on other tools to hide the controller from games;</li> <li>Gyro inversion on the fly;</li> <li>A tray icon with a context menu full of helpful options when JSM is minimised&#8230;</li> </ul> <p>And more. Check out the CHANGELOG to see what's new and the README to see how to use them. There's also a helpful video below covering some of the cooler features.</p> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-1-4-released/html/4dd853158d81226f8d8818037f37211e8c78315a-15842858972035802347" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:dear-xbox:don-t-get-left-behind</guid>
				<title>Dear Xbox: Don&#039;t Get Left Behind</title>
				<link>http://gyrowiki.wikidot.com/blog:dear-xbox:don-t-get-left-behind</link>
				<description>

&lt;p&gt;Dear Xbox,&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 23 Dec 2019 20:15:53 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Dear Xbox,</p> <p>Hi, I'm Jibb, and I'm a big fan. I love how you make it easier for a lot of people to get into games than with a computer. I’m stoked that you're taking accessibility really seriously. And I'm so glad you pushed for cross-play this generation, even if it was from behind, just like PlayStation tried to last generation. But now it’s actually happening, and there’s no turning back. Well done!</p> <p>I think you're really good for gaming, and it's with that in mind that I write this letter to tell you: I'm worried you're going to be really, really behind next generation.</p> <p>Sure, your controllers are the best in at least some ways - having triggers that respond to the slightest touch, compared to the DualShock 4’s huge hardware deadzone and the Switch’s lack of analog triggers altogether is one example. Your controllers also have a reputation for great sticks and great overall shape. There are plenty of reasons to prefer an Xbox controller for games that traditionally play better with a controller than with a keyboard and mouse.</p> <p>But great sticks and triggers don’t hold a candle to a mouse for a variety of genres.</p> <p>Some of those genres, like RTS and MOBA, basically don’t exist on console, or only appear in non-traditional ways that creatively work around the limitations of playing with a controller. These are great, but no one’s playing Starcraft or Dota on an Xbox (for now).</p> <p>Others, like FPS and TPS games, are still reasonably playable on consoles. Of course, these games go to great lengths to cover up the limitations of aiming a weapon with a tiny joystick, and even then they’re still notoriously hard for first-time gamers to pick up. Console players are usually kept separate from PC players (or mouse players specifically) to keep things “fair”.</p> <p><a href="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks">Thumbsticks just aren’t very good for aiming</a>.</p> <p>You know this, as it was your most iconic franchise, Halo, that got thumbstick aiming to an acceptable level of accessibility. Halo’s developer Bungie basically wrote the book on aim assist. But did you know that Halo can play much better with a PlayStation controller than with an Xbox controller?</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:dear-xbox:don-t-get-left-behind/html/6ee00a4f001cd1f4fcb5532405beeabdcfd1c66a-896435750288406252" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>If this sounds like a long ad, I’m not selling anything. I’m just trying to get games to play better than they currently do. I’m trying to close the gap between console players and PC players. And with blog posts, videos, and free open source software I’ve written to force gyro controls into games like these on PC, I’m trying to show everyone that games can be doing so much more than what they’ve been doing this generation.</p> <p>What do I mean by “so much more”? I mean more of the stuff that you’re really into:</p> <ul> <li>More accessible. You know better than anyone that accessibility is not about having the best option, but about having the most options. And while some players have special physical needs such that thumbstick aiming suits them better, there are others with special physical needs that make thumbstick aiming utterly unplayable. A mouse or a mouse-like input (like gyro) is their only option to play.</li> <li>More open to newcomers. While this falls under the general umbrella of accessibility, we usually think of accessibility options as serving a minority. But even without special physical needs, gyro aiming is far more intuitive and easier to pick up than thumbstick aiming. If you think standard twin-stick shooter controls are the most newbie-friendly option with modern game controllers, think again.</li> <li>More competitive for hardcore players. Just like mouse-aiming, gyro-aiming has an incredibly high skill ceiling. Allowing players to control their aim so directly and deliberately allows hard-earned skill to shine. Combine that with the freedom of analog movement and some creative new ways to use the right stick, and who knows what we’ll see players achieve?</li> <li>More unified across platforms. It’s too early to say how close to mouse-like aim gyro aiming can be. Perhaps the other unique features of a controller can help close the gap completely? But we can already say that gyro aiming makes it difficult to discern from watching a gameplay video alone whether someone’s use a mouse or a controller with a gyro. It’s quite possible that even competitive genres like shooters could see their entire player-base playing together.</li> </ul> <p><iframe src="http://gyrowiki.wikidot.com/blog:dear-xbox:don-t-get-left-behind/html/545256d79b9e4ac79d68729a0e0474897c58e466-10268863041114597437" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Of course, you’re wondering what PlayStation and Switch have been doing with this supposed advantage. We all know PlayStation and Switch controllers have gyros in them - that’s why I’m singling you out, dear Xbox. But if it’s so great, why doesn’t everyone already know about it through your rivals?</p> <p>While Nintendo has innovated by introducing controls like these with Splatoon on the Wii U, their lack of experience in these genres shows in how imprecise and unpredictable these controls can be in their implementations. Let’s be clear - this is a software problem, not a hardware problem. In spite of this, Wii U and Switch players will often vouch for the effectiveness of these controls.</p> <p>Regarding third-party games, Switch ports frequently get gyro aiming of some kind, even if the same game on other platforms lacks it. But without good conventions to follow, these implementations stumble a lot. And as games like DOOM, Overwatch, and Fortnite run at only 30fps on Switch, it’s easy to dismiss them as less competitive than their PS4 and Xbox One versions.</p> <p>PlayStation, on the other hand, has largely dropped the ball on this opportunity. Just about every one of their big blockbuster exclusives would not only benefit from gyro aiming, but are also the easiest kinds of games to introduce players to it. Uncharted, Horizon: Zero Dawn, The Last of Us, God of War, Infamous, and Spider-Man all have essentially a dedicated aim button, and only require precise aiming when that’s pressed. They could have had the gyro only activate while aiming, like in Zelda: Breath of the Wild.</p> <p>But PlayStation has had their attention elsewhere. They have gyro support seemingly serendipitously, as they continue to explore VR, not realising the tools they’re using for VR are also applicable to a far wider audience. They haven’t realised it can help them where they compete more directly with you: cross-platform games.</p> <p>As cross-play is expected in games going forward, PlayStation has more incentive to look at how they can offer their players a better experience than you can. Dear Xbox, you’ve been so lucky that PlayStation has neglected to embrace gyro controls for a whole console generation.</p> <p>Don’t count on being so lucky again. Those of us who see what gyro controls are capable of have had to be content with patiently waiting for PlayStation to get onboard, and for Switch to push better convention. For them, it’s a software issue. But for you, it’s hardware, and that’s what’s scary.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:dear-xbox:don-t-get-left-behind/html/83b63661d00cd0708b1e71f71bdaa1155bb089d6-15688287451360703159" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Every PlayStation or Switch player has controllers that can do this stuff. Every one of them. But for Xbox players to get in on gyro aiming, they need new hardware. They need new controllers. A brand new console is the perfect opportunity to introduce it. It’s your only opportunity to make this feature standard for all of your players.</p> <p>If the Xbox Series X doesn’t have gyro, what happens when PlayStation starts making better use of it? What happens when cross-platform games like Fortnite and Call of Duty play better on PlayStation than on Xbox? Will you try to catch up with an accessory that players have to buy separately? Or will all of your players already be equipped with what they need?</p> <p>Please, dear Xbox. Don’t fall behind. Embrace the gyro. Even better, take the lead. Show the rest of the world what’s possible with a controller. Let’s change how games are played.</p> <h3><span>Resources</span></h3> <p>If this has sparked any curiosity in you at all, here are some resources to help you out:</p> <ul> <li><a href="/">GyroWiki</a> - This is where you are now. Here you'll find tutorials and articles aimed at developers that set a very simple minimum standard for “good” gyro controls. As simple as it is, it’s better than I’ve seen implemented in any games on Switch or PS4. In particular, check out Good Gyro Controls Part 1.</li> <li><a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">JoyShockMapper</a> - An open source tool that makes it relatively easy to fake gyro controls that reach that minimum standard described on GyroWiki. By converting controller input to keyboard and mouse input, JoyShockMapper is how some of us play PC games with the kind of gyro controls that should become standard on consoles. You’ll need a DualShock 4, Switch Pro Controller, or Joy-Cons for this. I’ll add support for an Xbox controller when you put a gyro in it.</li> <li><a href="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks">Why Not Just Use Thumbsticks?</a> - A deeper dive into the limitations of thumbsticks for popular games.</li> </ul> <p>My goal with GyroWiki and these other resources is to make it as easy as possible for both players and developers to get into good gyro controls. It’s all out there. But if you need any help at all, I’d be more than happy to oblige. Find me on <a href="https://twitter.com/JibbSmart">Twitter</a> or on the <a href="https://discord.gg/4w7pCqj">Gyro Gaming Discord server</a>.</p> <p>I’m not the only one who sees the potential of gyro controls. Check out:</p> <ul> <li>Nerrel’s much watched and shared <a href="https://www.youtube.com/watch?v=binPB4YbWmM">Motion Control and the Rejection of Progress</a> and <a href="https://www.youtube.com/watch?v=RZ0xDRkC8LI">Why Gamers Hate Motion Control (In their Own Words)</a></li> <li>CammyCakes tells his ~330,000 subscribers about the <a href="https://www.youtube.com/watch?v=8cUrbztEyio">potential he sees in gyro aiming</a></li> <li>Existential Egg’s <a href="https://www.youtube.com/user/ExistentialEgg">channel dedicated to the Steam Controller</a></li> <li>As well as other videos like Critical Input’s <a href="https://www.youtube.com/watch?v=1GD0f1gYkV8">EPOCH - A Perfect Game to Practice Gyro Aim</a>, AL2009man’s <a href="https://www.youtube.com/watch?v=1bpKIB9iGIs">How Gyroscopic/Motion Controls can improve the Controller</a>, Vyking’s <a href="https://www.youtube.com/watch?v=C_eGysgRMS4">PUBG on PC with a Controller</a>, and find a tonne more just by <a href="https://www.youtube.com/results?search_query=gyro+aiming">looking for “gyro aiming” on YouTube</a>.</li> </ul> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:the-gyro-revolution</guid>
				<title>The Gyro Revolution</title>
				<link>http://gyrowiki.wikidot.com/blog:the-gyro-revolution</link>
				<description>

&lt;p&gt;Welcome to the &lt;strong&gt;Gyro Revolution&lt;/strong&gt;. Here I hope to summarise all the various projects connected to GyroWiki, their current status, and how they&#039;re working together to change how we play games for the better.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Fri, 15 Nov 2019 07:48:39 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Welcome to the <strong>Gyro Revolution</strong>. Here I hope to summarise all the various projects connected to GyroWiki, their current status, and how they're working together to change how we play games for the better.</p> <p>To better understand all this, there are two main parts:</p> <ol> <li><strong>Observations about modern game controls</strong> - What's wrong with the current state of game controls?</li> <li><strong>The Gyro Revolution projects</strong> - What are all these gyro projects, and how are they connected to each other?</li> </ol> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/7266b0bb4152da018e314756be5332ed6445f1bc-4258087112106031819" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Let's get right into it. There are a lot of pieces here, and hopefully I can paint a clear picture of how they all fit together.</p> <table style="margin:0; padding:0"> <tr> <td style="margin:0; padding:0"> <div id="toc"> <div id="toc-action-bar"><a href="javascript:;" >Fold</a><a style="display: none" href="javascript:;" >Unfold</a></div> <div class="title">Table of Contents</div> <div id="toc-list"> <div style="margin-left: 1em;"><a href="#toc0">Observations about Modern Game Controls</a></div> <div style="margin-left: 3em;"><a href="#toc1">Traditional thumbstick aiming is very difficult to learn for the first time</a></div> <div style="margin-left: 3em;"><a href="#toc2">Gyro aiming is underutilised in games</a></div> <div style="margin-left: 3em;"><a href="#toc3">Current gyro implementations leave much to be desired</a></div> <div style="margin-left: 3em;"><a href="#toc4">Gyro controls open up far more radical opportunities for innovation</a></div> <div style="margin-left: 1em;"><a href="#toc5">Gyro Revolution Projects</a></div> <div style="margin-left: 3em;"><a href="#toc6">GyroWiki</a></div> <div style="margin-left: 3em;"><a href="#toc7">JoyShockMapper</a></div> <div style="margin-left: 3em;"><a href="#toc8">JoyShockOverlay</a></div> <div style="margin-left: 3em;"><a href="#toc9">JoyShockLibrary</a></div> <div style="margin-left: 3em;"><a href="#toc10">Gyro Gaming YouTube Channel</a></div> <div style="margin-left: 3em;"><a href="#toc11">Playable Demo</a></div> <div style="margin-left: 2em;"><a href="#toc12">How they fit together</a></div> </div> </div> </td> </tr> </table> <h1><span>Observations about Modern Game Controls</span></h1> <p>While conventions are extremely helpful for game developers to provide consistently good experiences to players, control conventions haven't kept up with what controllers are capable of. Modern games could be both easier to learn and have more room for skillful mastery with modern controllers.</p> <h3><span>Traditional thumbstick aiming is very difficult to learn for the first time</span></h3> <p>Although controllers offer many benefits to players over a keyboard and mouse, they are traditionally not well-suited to playing games that involve precise aiming or control of a cursor. This is in spite of the incredible amount of work that goes into interpreting the player's input more generously and helping them aim with <em>aim assist</em>.</p> <p>Of course, this is mostly uncontroversial among game developers. While players might debate back and forth, it's generally well-understood that <a href="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks">thumbsticks are bad for aiming</a>. So if we can offer players a better option, let's do it!</p> <h3><span>Gyro aiming is underutilised in games</span></h3> <p>Many players have no idea that PlayStation's DualShock 4 is just as capable as Switch controllers, because even games like <em>Fortnite</em> and <em>DOOM</em> that sport gyro aiming on Switch don't on the PS4. Why is that? Are thumbstick controls good enough? We know this isn't true. We know difficult controls are a huge obstacle to getting not-yet-gamers into games. And yet here we are, playing PS4 shooters mostly the same way we did almost twenty years ago.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/83b63661d00cd0708b1e71f71bdaa1155bb089d6-792575091195453091" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <h3><span>Current gyro implementations leave much to be desired</span></h3> <p>While gyro fans on the Switch point to <em>Splatoon 2</em> and <em>Breath of the Wild</em> as exemplar implementations, they're actually sloppy, unpredictable, and unreliable. Other popular games with gyro controls usually don't do better.</p> <p>With the same controllers, gyro controls can be incredibly precise and responsive. There's a tremendous gap between what players using gyro controls through Steam's controller tools experience and what players experience with gyro controls in <em>Fortnite</em>, <em>Paladins</em>, and <em>Super Mario Odyssey</em>. Steam's controls are much tighter, and a naive player would be forgiven for thinking that it's difficult to pull off an implementation like Steam's.</p> <p>But it isn't. And we can do even better.</p> <h3><span>Gyro controls open up far more radical opportunities for innovation</span></h3> <p>At the very least, gyro controls can be layered seamlessly on top of whatever more traditional controls are there. If gyro controls are <em>sometimes</em> better than stick aiming, and can be used only when they're better, we've proven that games play better when the gyro is utilised well.</p> <p>But we can do so much better than that. Far more creative and innovative options are explored by the various Gyro Revolution projects. Let's have a look at all the pieces there and how they come together.</p> <h1><span>Gyro Revolution Projects</span></h1> <p>Here are the main pieces in play. As you can imagine, they keep me very busy, as I work on these in my spare time:</p> <ul> <li><strong>GyroWiki</strong> - This website. The glue between everything. Hosts community and developer resources for using the other publicly available projects (below) and actually implementing good gyro controls in games.</li> <li><strong>JoyShockMapper</strong> - An open source tool for playing just about any game on PC with good gyro controls and flick stick by faking keyboard and mouse inputs. If your game plays better with JoyShockMapper than it does with your own gyro controls, it's not the end of the world. Just check out the tutorials on GyroWiki and see how easy it is when we have good conventions to follow!</li> <li><strong>JoyShockOverlay</strong> - A live 3D controller overlay to show viewers what you're doing with your controller at any time. This is what you see on the left side of the screen in basically all the videos on this site.</li> <li><strong>JoyShockLibrary</strong> - An open source library for reading controller input from PlayStation's DualShock 4 and Switch's Joy-Cons and Pro Controller. JoyShockMapper and JoyShockOverlay depend on this library, and any improvements made to it will improve them as well.</li> <li><strong>Gyro Gaming YouTube Channel</strong> - This is the chief evangelistic force of the Gyro Revolution. Regular videos use JoyShockOverlay and JoyShockMapper to showcase what can be done with modern controllers in a variety of games.</li> <li><strong>Playable Demo</strong> - Gyro aiming, flick stick, and unlocked aiming in action. See a practical example of the settings games should provide to players. See how different control options compare to each other.</li> </ul> <p>Let's look at them in detail.</p> <h3><span>GyroWiki</span></h3> <p><strong>Current Status</strong>: Published, ongoing<br /> <strong>Goals</strong>: Continue to publish helpful developer resources, including <strong>Good Gyro Controls Part 3: Unlocking the Aimer</strong>.</p> <p>GyroWiki has two main goals:</p> <ol> <li>To educate developers;</li> <li>To help gyro-keen players.</li> </ol> <p>First, it's a collection of educational resources for developers. The articles on the blog equip developers to implement far better gyro controls than you'll have encountered in the wild. There are a number of decisions that go into a gyro aiming implementation that can be difficult when going in blind, and this is proven by how many games have sloppy, unpredictable, imprecise, laggy gyro controls. If a game's gyro controls are not tight, precise, and responsive on a modern console (Switch or PlayStation 4 - the Xbox One doesn't have gyro), it's certainly not the hardware's fault.</p> <p>The most important educational resources on GyroWiki are the Good Gyro Controls tutorials. It's a 3 part series with 2 parts out so far. <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Part 1</a> guides you through the real basics of good gyro controls. <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">Part 2</a> teaches you how to implement <em>flick stick</em>, which is a game-changer. I can't wait to see it actually implemented in games:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/8c90a468d7771eb9a339b35bffb2e824f841e71a-1471630420492430482" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> Excitement around flick stick has been a driving force in the formation of an enthusiastic community around these tools. See its first public introduction <a href="https://www.reddit.com/r/gamedev/comments/bw5xct/flick_stick_is_a_new_way_to_control_3d_games_with/">on Reddit</a>.</p> <p>Part 3 will talk about unlocking the aimer from the centre of the screen. It's a work in progress, and it's waiting on more work in other projects below. I do have a working prototype of the features I plan to talk about, and they open up so many new possibilities. Hopefully you're looking forward to that.</p> <p>GyroWiki also hosts other articles intended to benefit the games industry. The article I shared much earlier on this page on the shortcomings of thumbsticks for aiming was a <a href="https://www.gamasutra.com/blogs/JibbSmart/20190916/350774/Why_not_just_use_thumbsticks.php">featured post on Gamasutra</a>.</p> <p>But another goal of GyroWiki is to help gyro-keen players. JoyShockMapper (which we'll talk about next) requires calibration and configuration on a per-game basis. While the purpose of JoyShockMapper is to show the potential of gyro controls and flick stick, it can be obfuscated by the difficulty of calibration. This calibration wouldn't be part of native implementations of gyro controls, but it's a necessary part of using JoyShockMapper. By hosting a <a href="http://gyrowiki.wikidot.com/games">database of calibration settings and configurations</a>, GyroWiki enables players to much more easily try these controls in different games.</p> <p>Let's talk more about what JoyShockMapper actually is.</p> <h3><span><a href="https://github.com/JibbSmart/JoyShockMapper">JoyShockMapper</a></span></h3> <p><strong>Current Status</strong>: Published, ongoing<br /> <strong>Goals</strong>: Continue to support the growing community and release updates with new features.</p> <p><a href="https://github.com/JibbSmart/JoyShockMapper">JoyShockMapper</a> fakes good gyro controls and flick stick in games by converting controller inputs to virtual keyboard and mouse inputs. It works with any game that uses a mouse, 2D or 3D. It's open source, and serves as a reference implementation for good gyro controls and flick stick as described on GyroWiki.</p> <p>Everything advocated for on GyroWiki has been proven by practical implementations, and JoyShockMapper accounts for a lot of that. I don't say to do things a certain way because I <em>think</em> they'll be good, but because I've <em>tested</em> them using JoyShockMapper and game prototypes I've worked on privately. Any good idea can only be proven by a good implementation, and JoyShockMapper, when configured correctly, is the proof of the Gyro Revolution.</p> <p>Since flick stick maps a real-world stick angle to the same angle in-game, JoyShockMapper needs calibration on a per-game basis. This calibration is also used so that, in 3D games at least, your gyro settings can use the <em>natural sensitivity scale</em>. With proper calibration, the same gyro controls can be used across different games.</p> <p>As of version 1.3, JoyShockMapper has benefited from <a href="https://www.youtube.com/watch?v=Rt9FpqxRn3o">huge community contributions</a>. This has been such a blessing, as JoyShockMapper has continued to improve while I focus on other projects here.</p> <p>To learn how to use JoyShockMapper to try out good gyro controls and flick stick in <em>your</em> games without having to implement any of it yourself, there's a tutorial <a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">right here on GyroWiki</a>.</p> <h3><span>JoyShockOverlay</span></h3> <p><strong>Current Status</strong>: Useful, not ready for publishing<br /> <strong>Goals</strong>: Publish this tool for streamers and the like to better communicate what they're doing in their games. Add more controllers, more options to customise the look and position of the 3D overlay.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/4814162eef520336d4174bf32f1d8dfa17399860-20631910241040108896" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> It's hard to show gyro controls in action without some way to show what I'm doing with the controller. In fact, many streamers have on-screen overlays showing what their controller is doing. Others will have a &quot;controller-cam&quot; or &quot;mouse-cam&quot; to serve the same purpose.</p> <p>But available on-screen overlays won't suffice for gyro controls. Viewers need to see the controller's orientation. So I made JoyShockOverlay, shown above. This overlay is:</p> <ul> <li><strong>Live</strong> - No need to synchronise different feeds. It appears on your screen over whatever you're playing.</li> <li><strong>Responsive</strong> - Real-time rendering is my jam. So, of course, JoyShockOverlay implements a &quot;late latching&quot; solution to get updates from the controller after the current frame's draw call has been submitted but before the GPU has consumed it. What this means is that it's not uncommon to see the 3D controller overlay show an input before the game actually responds to it.</li> <li><strong>Performant</strong> - I know a couple dozen draw calls aren't really a big deal, but JSO draws the whole controller (or pair of controllers in the case of Joy-Cons) in one. Data encoded in the vertex colours designate different groups, and then the vertex shader applies the appropriate transformations and highlights such that the sticks always reflect the real controller's stick positions, and the shoulder buttons and triggers swing out to stay visible regardless of the controller's orientation.</li> </ul> <p>At the moment, this is of huge benefit to my videos showing gyro controls in action. Understandably, YouTubers and streamers have asked to use JoyShockOverlay, but it's currently not publicly available. Long term, the goal is to make JSO available, as well as to add more controllers (even popular ones that don't have gyro, like the Xbox One controller) and more customisation options.</p> <h3><span><a href="https://github.com/JibbSmart/JoyShockLibrary">JoyShockLibrary</a></span></h3> <p><strong>Current Status</strong>: Published, ongoing<br /> <strong>Goals</strong>: Add Linux and Mac support. Add Bluetooth support for DualShock 4, USB support for Switch controllers. More resources and examples to help developers use JSL in popular engines such as Unreal Engine and Unity.</p> <p><a href="https://github.com/JibbSmart/JoyShockLibrary">JoyShockLibrary</a> is an open source library that reads input from PlayStation's DualShock 4 and Switch's Joy-Cons and Pro Controller. Both JoyShockMapper and JoyShockOverlay use JoyShockLibrary. It gives access to all the buttons, sticks, and triggers. It also gives access to the accelerometer and gyro, reporting each in the same units across different controllers (g for acceleration and degrees per second for angular velocity), in the same axes. It has functions to help with gyro calibration, too.</p> <p>Using JoyShockLibrary, a game or application can easily use all three controllers interchangeably, and should be easy to combine with XInput or generic controller APIs.</p> <p>It relies on the cross-platform hidapi, which should make porting JoyShockLibrary to Linux and Mac relatively painless, but for now it's only compiled for Windows. It also currently only communicates with the DualShock 4 via USB and Switch controllers by Bluetooth. I do plan to add Bluetooth DualShock 4 and USB Switch controller support when I get the chance. I know its current wired/wireless limitations seem weird, but the DualShock 4 is easier to deal with by USB, and Switch controllers only communicate by Bluetooth by default, so I started with what's simplest.</p> <p>My primary reason for making JoyShockLibrary myself was so that I'd know what comes out of these controllers. I was enjoying gyro controls through Steam and made a prototype using Steam's input API, but I wanted to support Switch controllers, too (Steam didn't at the time), and to understand why Switch games have sloppy gyro controls when games played through Steam don't. This experience has enabled me to speak with authority on the capabilities of the gyros in PlayStation and Switch controllers.</p> <p>JoyShockLibrary is easy to use with popular game engines, but having working examples available for users will make this much easier.</p> <h3><span><a href="https://www.youtube.com/c/GyroGamingJS">Gyro Gaming YouTube Channel</a></span></h3> <p><strong>Current Status</strong>: Public, ongoing<br /> <strong>Goals</strong>: Continue to regularly publish videos evangelising the innovations uncovered in my work on the JoyShock tools. A video summarising the <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Good Gyro Controls Part 1: The Gyro is a Mouse</a></strong> article on GyroWiki. Videos utilising game prototypes I'm working on to better demonstrate what we can't fake in current games with JoyShockMapper.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/545256d79b9e4ac79d68729a0e0474897c58e466-12471670651298099681" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> The Gyro Gaming YouTube Channel is the primary way I reach people with these projects. The channel serves to:</p> <ul> <li>Educate about gyro controls and flick stick;</li> <li>Demonstrate these features in popular games;</li> <li>Reach more people in creative ways, such as <a href="https://www.youtube.com/watch?v=MGXp9zvHaok">playing Overwatch with one Joy-Con</a>, which was published right before Overwatch came out on Switch;</li> <li>Support GyroWiki with complementary videos (as you've seen on this page);</li> <li>Make it easier to share and evangelise good gyro controls, flick stick, accessibility tools, and anything else that comes out of my work.</li> </ul> <p>Sharing the <a href="https://www.youtube.com/watch?v=C5L_Px3dFtE">intro to flick stick</a> video above on Reddit, for example, exposed thousands of people to the innovations described here in a matter of days. <a href="https://www.youtube.com/watch?v=HZUiWHnTqS8">Gyro vs thumbstick vs mouse</a> was quickly shared in gaming fora and continues to appear in gyro-related discussions as a go-to taster of what the gyro is capable of. It continues to get regular traffic without my active intervention.</p> <p>At the moment, most of my time spent on these projects is producing videos for this channel. I don't have a lot of free time, and maintaining momentum on the channel uses up a lot of it. But it's been extremely fruitful, reaching more and more people with the potential of modern controllers, particularly with good gyro controls and related innovations.</p> <p>The channel is growing, and has helped grow a <a href="https://discord.gg/4w7pCqj">Discord community</a> dedicated to gyro gaming, whether with JSM, Steam, or even on console.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:the-gyro-revolution/html/698a3e92125f07d1ec6b30aad4d83218780db8f9-991729180683969314" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> There's so much more to come on the Gyro Gaming YouTube Channel, and <a href="https://www.youtube.com/watch?v=Rt9FpqxRn3o">this video gives an overview of what I'm working on</a>. Which brings us to the last piece I'm working on: the <em>Playable Demo</em>.</p> <h3><span>Playable Demo</span></h3> <p><strong>Current Status</strong>: Prototyping, ongoing<br /> <strong>Goals</strong>: To publish a simple, playable demo of good gyro controls, flick stick, and unlocked aiming in action.</p> <p>I'm working on a prototype demonstrating all the features described on GyroWiki and more. The unfinished Part 3 of the Good Gyro Controls tutorial series here on GyroWiki and the YouTube channel both plan to use this demo to show what's possible. I mentioned in the JoyShockMapper section that JSM serves as the proof of the Gyro Revolution. But it's really only half of the proof. This demo will serve as the other half.</p> <p>It has features that are impossible to fake with JoyShockMapper, such as unlocking the aimer from the centre of the screen in a standard shooter. It also serves as an example of how to present these options to the user - even the simple options demonstrated by JoyShockMapper.</p> <p>It's a work in progress, and, as I'm focused on the programming side of the minimum viable product right now, I have nothing to show for it. It functions well at a mechanical level, covering the most important features of JSM and a bunch of new ones, but it's neither publishable nor in a recordable state. The plan is for it to be a very simple target-shooter, allowing the player to experiment with different control systems. The demo should provide statistics on how the player fares with different kinds of controls. I also plan for it to collect anonymised data by which we can better understand the pros and cons of different control systems and how they can be improved.</p> <p>This demo should bring gyro controls and flick stick to a wider audience, as only a relatively small number of people are willing to try something like JoyShockMapper to change the way they play other games. The demo will benefit from being easy to install and update through an established vendor, natively recognising the player's controllers without the need for complex calibration and configuration, and being able to teach these controls in an interactive way.</p> <p>With this demo, my practice with it, and feedback and data from players, this should help test more creative control solutions. This unnamed playable demo will inform the Gyro Gaming YouTube Channel, GyroWiki, and the industry at large how we can best move forward with game controls.</p> <p>With JoyShockMapper, we have proof that games can be far more accessible to new players. They can give far more freedom for mastery to skillful players willing to put in the practice. They provide an accessible alternative to aiming with a tiny joystick. They open the console gamer market to genres of games that cannot traditionally be played without a mouse, like strategy games, dota-likes, and more. This demo will enable us to see how far these benefits can go.</p> <h2><span>How they fit together</span></h2> <p>So there you have it. <strong>GyroWiki</strong> is a growing reference for developers who are up for making games play way better. <strong>JoyShockMapper</strong> and this upcoming <strong>Playable Demo</strong> serve to prove it. The <strong>Gyro Gaming YouTube Channel</strong> evangelises it. <strong>JoyShockOverlay</strong> supports GyroWiki and the Gyro Gaming Channel. And <strong>JoyShockLibrary</strong> makes it all possible.</p> <p>If you want to learn more or get involved some how, you can find me in the Gyro Gaming <a href="https://discord.gg/4w7pCqj">Discord community</a>, message me <a href="https://twitter.com/JibbSmart">on Twitter</a>, through the email address on the <a href="https://www.youtube.com/c/GyroGamingJS">Gyro Gaming YouTube Channel</a> About page, or through the <a href="http://gyrowiki.wikidot.com/main:contact">contact</a> form on this site. I'd love to hear from you! Let's change how games are played.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-3-released</guid>
				<title>JoyShockMapper 1.3 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-3-released</link>
				<description>

&lt;p&gt;JoyShockMapper 1.3 has been released!&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Fri, 08 Nov 2019 17:04:36 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper 1.3 has been released!</p> <p>This is a big update, and for the first time, I'm not the one who made it! It adds:</p> <ul> <li>Auto Load configs based on the name of the focused application.</li> <li>Dual-Stage Triggers let you map different keys to half- or full- presses of the DualShock 4's analog triggers.</li> <li>I've also fixed a couple of bugs from the last version.</li> </ul> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-1-3-released/html/05e122982d357edc832d2973ce473df42beba058-18042020071065889886" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-2-released</guid>
				<title>JoyShockMapper 1.2 Released</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-1-2-released</link>
				<description>

&lt;p&gt;JoyShockMapper 1.2 has been released!&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 15 Oct 2019 01:11:40 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper 1.2 has been released!</p> <p>This is a relatively small update. It adds:</p> <ul> <li>MOUSE_X_FROM_GYRO_AXIS and MOUSE_Y_FROM_GYRO_AXIS enable mapping different gyro axes to mouse x and y.</li> <li>PAGEUP, PAGEDOWN, HOME, END, INSERT, DELETE key mappings added.</li> <li>Regularly accepted commands can now be followed by a '#' and a comment that will be ignored by JSM. This means comments no longer have to be on their own line.</li> </ul> <p>Get the latest version <a href="https://github.com/JibbSmart/JoyShockMapper/releases">here</a>.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-1-2-released/html/6b2aa673826c81f1d190ba08b0460c8577202236-433826608404889623" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>MOUSE_X_FROM_GYRO_AXIS and MOUSE_Y_FROM_GYRO_AXIS enable playing a game with one sideways Joy-Con.</em></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks</guid>
				<title>Why Not Just Use Thumbsticks?</title>
				<link>http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks</link>
				<description>

&lt;p&gt;Thumbsticks are widely used in modern console games. Sometimes games use thumbsticks when they really are the most practical option available on a standard modern controller: movement in any direction and steering a vehicle seem to me to be obvious examples.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 16 Sep 2019 13:21:02 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Thumbsticks are widely used in modern console games. Sometimes games use thumbsticks when they really are the most practical option available on a standard modern controller: movement in any direction and steering a vehicle seem to me to be obvious examples.</p> <p>But games have also used thumbsticks for moving a cursor or aiming a weapon for close to 20 years now, and while developers and gamers alike have long acknowledged they have severe shortcomings when used for these tasks, an incredible amount of inertia seems to be keeping the industry from embracing a better alternative already available in most modern console controllers: the gyro.</p> <p>But this website is all about the gyro. Let's, for one post, focus on thumbsticks as aiming devices. Let's remind ourselves how much work goes into making them less awful than they could be, and acknowledge that they're bad enough that if there's a more modern technology that is better suited to the task &#8212; both easier to learn and has more room for skillful mastery &#8212; then perhaps we should embrace it.</p> <h2><span>Hard to learn</span></h2> <p>Let's look briefly back at what is commonly acknowledged as the first game to have dual-analog controls by default, Alien Resurrection (Quake II for PlayStation also had dual-analog controls, but they were optional):</p> <blockquote> <p>The game's control setup is its most terrifying element. The left analog stick moves you forward, back, and strafes right and left, while the right analog stick turns you and can be used to look up and down. Too often, you'll turn to face a foe and find that your weapon is aimed at the floor or ceiling while the alien gleefully hacks away at your midsection.</p> </blockquote> <p>&#8212; <a href="https://www.gamespot.com/reviews/alien-resurrection-review/1900-2637344/" target="_blank">Steven Garrett's review of Alien Resurrection, October 5th, 2000</a></p> <p>Dual analog moving and aiming has become part of the common language of playing games. Generally, if you are able to game &quot;fluently&quot;, you are able to play these kinds of games in this way with relatively little difficulty. But it's easy to forget what it's like for someone new to gaming or new to these kinds of games in particular.</p> <p>Anecdotally, I've seen people try PC gaming for the first time and get reasonably comfortable with mouse aim pretty quickly, but later get fed up with their first console shooter as they find themselves aiming straight up or straight down and not sure why.</p> <p>When we aim with a thumbstick, we almost never point exactly in our intended direction. Much of the time in shooters, most of our aiming is left and right, from one target to another. Even before there's a target to shoot at, players will need to be able to turn left and right to navigate the game world. But what happens when the player intends to turn left, but has the stick slightly below horizontal? The camera drifts down.</p> <p>Players comfortable with these controls will make slight adjustments and recover without too much difficulty. But what surprises me as someone who has learned to be comfortable with these controls longer ago than I can remember is how often new players fail to adjust their aim direction, but instead are confused as to why this is happening at all. &quot;I said turn left. Why are you going down?&quot; So they counteract it. Start turning right instead. Much of the time, this will <em>also</em> be below or above horizontal, and they keep going down. They find themselves looking at their feet. Left and right no longer turn the camera left and right in an intuitive way &#8212; now it's spinning around its centre (a problem with mouse and gyro aiming, too, but confusing to a new player who never meant to look at their feet or at the ceiling in the first place), and for a new player who feels like they only asked the game to turn left or right, it can be perplexing to find themselves in this position.</p> <p>Sure, we also almost never move the mouse in the exact direction we intend, either. But because continued movement in game requires continued mouse movement in the real world, we are already adjusting. &quot;Steering&quot; the aimer the way we do with thumbsticks is simple in concept, but seems to me to be quite unnatural.</p> <p>I don't know exactly what it is that makes it so much worse for aiming. One factor might be the directness of mouse (displacement to proportional displacement, velocity to proportional velocity) compared to the indirectness of the thumbstick. Another might be the small size of the thumbstick compared to the wide range of motion required to play with precision. Whatever it is, it sure seems to be the case that thumbsticks introduce serious difficulties to comfortable aiming.</p> <h2><span>Generous interpretation</span></h2> <p>Now, let's acknowledge that games have made it easier to avoid these difficulties. Cross-shaped deadzones and aim assist are ways that games try and interpret the player's intentions a little loosely and help them out.</p> <p>Let's start with aim assist in arguably the first popular dual analog game, <em>Halo</em>. Here's what Halo's developers had to say to <a href="https://waypoint.vice.com/en_us/article/xwqjg3/the-complete-untold-history-of-halo-an-oral-history" target="_blank">Steve Haske in an excellent interview for Waypoint</a>:</p> <blockquote> <p>There’s a lot of code in Halo that interprets what you’re doing—how fast did you move there, what are you looking at? If it’s an enemy, we can assume that when you slow down, you’re trying to aim. So there are pages and pages that interpret the input that comes in, in a way that isn’t blatant and in your face. We tried to conceal how much help we’re giving the player.</p> </blockquote> <p>&#8212; Jaime Griesemer</p> <blockquote> <p>It essentially buffers your movements, so that you get the movement you wanted, not necessarily the one you were making. Which gives you a really controlled, precise experience, beyond what your thumb could actually give you, unassisted.</p> </blockquote> <p>&#8212; Stuart Moulder</p> <p>Not all games do it the same way, or to the same degree, but aim assist goes a <em>long</em> way to making thumbstick aiming not awful. It's also a lot of work on the part of the developer. While we tend not to measure code in &quot;pages&quot;, Jaime's making the point intended to be understood by non-programmers: it's a <em>lot</em> of code. It's a lot of work.</p> <p>In comparison, mouse aiming is really straightforward. There's no need for loose interpretation. A mouse movement becomes a proportionate angle change for the camera:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">camera</span><span class="hl-code">.</span><span class="hl-identifier">yaw</span><span class="hl-code"> += </span><span class="hl-identifier">mouse</span><span class="hl-code">.</span><span class="hl-identifier">dx</span><span class="hl-code"> * </span><span class="hl-identifier">mouseSensitivity</span><span class="hl-code">; </span><span class="hl-identifier">camera</span><span class="hl-code">.</span><span class="hl-identifier">pitch</span><span class="hl-code"> += </span><span class="hl-identifier">mouse</span><span class="hl-code">.</span><span class="hl-identifier">dy</span><span class="hl-code"> * </span><span class="hl-identifier">mouseSensitivity</span><span class="hl-code">;</span></pre></div> </div> <p>Where mouse.dx and mouse.dy are the amount the mouse has moved since input was previously sampled. That's <em>it</em>. If you have mouse acceleration (even good, framerate-independent mouse acceleration) it's only a few more lines, depending on how you do it. Good gyro controls look very similar, but that's for another post.</p> <p>Check out <a href="https://www.gdcvault.com/play/1017942/Techniques-for-Building-Aim-Assist" target="_blank">Nick Weihs's GDC talk on his work implementing aim assist in Insomniac Games' Resistance series</a>. It's about half an hour (20 minutes if you watch it at 1.5 times speed like I did), and it is useful here for two reasons:</p> <ol> <li>We get Nick's very educated position on the differences between mouse aim and thumbstick aim, and</li> <li>We can get a good understanding of what is involved in good aim assist.</li> </ol> <p>Let's start with his assessment of thumbstick aiming as compared to mouse aiming:</p> <blockquote> <p>Keyboard and mouse controls are very good as far as aiming devices go because they're intuitive, they're accurate, and they give you a good sense of connection to the character you're playing as. The problem with keyboard and mouse is that they're not very good on the couch.</p> </blockquote> <p>On the other hand, controllers are &quot;very poor as far as aiming devices go.&quot;</p> <p>Let's summarise what's going on with aim assist here:</p> <ul> <li>A cross-shaped deadzone means near-horizontal input will be treated as exactly horizontal, near-vertical input will be treated as exactly vertical. This helps players intending to turn in only one axis actually achieve that.</li> <li>Vertical inputs are slowed down, and diagonal turn speed is slowed down even more. In fact, a wide variety of inputs are very finely tuned to cover a variety of purposes, including the stick input and acceleration.</li> <li>As the aimer approaches a target, the player's input is nudged closer to the ideal input to track the target's movement across the screen. Nick refers to this as &quot;magnetism&quot;.</li> </ul> <p>The trade-off of the deadzone is that much of player input that might've been deliberate gets ignored. Players need to move the stick disproportionately far to make small adjustments. The trade-off of magnetism &#8212; where the game helps the player follow what it thinks their intended target is &#8212; is that the player's intention can be misunderstood, with the cursor slowing down over targets the player wants to move past, for example.</p> <p>But it seems that more is gained than lost. It's a lot of work and goes a long way to improving the player experience. I know some hardcore players want as little aim assist as possible, but from Insomniac's tests and Bungie's work on Halo, it seems that aim assist may be a necessary part of making these games enjoyable to a large enough player base for the games to be worthwhile.</p> <p>So, does aim assist level the playing field? In question time at the end, Nick is asked how thumbstick with aim assist might compare to keyboard and mouse. He responds:</p> <blockquote> <p>Keyboard and mouse is just better&#8230; The thing about mouse is it's basically everything you want. It's the most accurate and the most deliberate.</p> </blockquote> <p><iframe src="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks/html/83b63661d00cd0708b1e71f71bdaa1155bb089d6-1038343729299501525" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>This won't be exactly everyone's experience. But under these circumstances, with years and years of experience stick aiming and mouse aiming, and a couple years gyro aiming, this is mine.</em></p> <h2><span>Cursor on a warped plane</span></h2> <p>Still, it can be hard for players to accept that the thumbstick might be so poor for aiming. So let's talk about a difference more widely accepted: thumbsticks are not good for moving an on-screen cursor. Real-time strategy games and Dota-likes that require fast point-and-click interaction with the world are almost nonexistent on consoles. When they are, they make huge concessions, and very creatively work around the issue of having no mouse.</p> <p>This doesn't make them bad games! By using these constraints as a springboard to explore new ways games can be designed around a controller, we get interesting, unique strategy games like <em>Tooth and Tail</em> and the <em>Halo Wars</em> games. But this also shows we haven't found a good way to manage large armies or quickly choose between many moving on-screen elements the way we do with a mouse.</p> <p>If you've ever used a joystick to move a hand to select a character in a <em>Super Smash Bros</em> game, you know moving a cursor with a thumbstick is not anywhere near as natural as a mouse. Using something like JoyShockMapper to move the Windows cursor with one of the thumbsticks is an easy way to experience it right now. It's really not ideal. The pointer either moves too slowly or is too fast to reliably hit small icons.</p> <p>If you've played Destiny and used a stick for the on-screen cursor, you'll find they've managed to dramatically improve the experience. Between the way the icons change position depending on where the cursor is and the size of the cursor itself, it's functionally the same as having a single-pixel cursor, but inflating all the clickable items by the diameter of the cursor, while also shrinking the space between those inflated icons. Making small objects big is a great solution, but only works with relatively few items on-screen. What about playing a real-time strategy game, with dozens of small, moving units to manage?</p> <p>Or look at the excellent open source game <em><a href="https://osu.ppy.sh/home">osu!</a></em>, a rhythm game that involves quickly clicking on circles in time with the music and following them as they move. It's great. While there's debate among its fans as to whether playing with a mouse is as good as playing with a stylus, there's one thing everyone will agree on: thumbsticks are almost useless for this game.</p> <p>You can try it yourself with JoyShockMapper&nbsp;or any tool like it to convert controller inputs to mouse inputs.&nbsp;If you can figure out settings that are actually good for playing this game with thumbsticks, please let me know!</p> <p>Now, if we're all on the same page with the thumbstick being a poor mouse substitute for pointy-clicky stuff, I've got a question for you: are shooters really all that different?</p> <p>When we look around in a first person shooter, it may not look that similar to manipulating a cursor at first blush. But if the crosshair is our cursor, we're moving it much the same way we would a mouse cursor. If you map the world around the player by <a href="http://en.wikipedia.org/wiki/Equirectangular_projection" target="_blank">equirectangular projection</a> to a 2D &quot;map&quot; on the screen, you'd find that as you move your mouse, the crosshair moves around the map in the same way you'd expect the Windows mouse pointer to on your desktop. Vertical movements of the mouse result in vertical crosshair movements. Horizontal movements move as you'd expect, too.</p> <p>Yes, it feels different to have the camera follow the cursor, but the moment-to-moment, sub-reaction-time actions you take are all the same. 2D shooters like <em>Soldat</em>, <em>Nuclear Throne</em>, and <em>Enter the Gungeon</em> already have the camera partially follow the camera without complicating the interaction by much, so the line between a cursor-locked camera and a fixed viewpoint is a blurry one.</p> <p>And sure, the equirectangular projection distorts the image &#8212; who's to say those distortions don't meaningfully change the cursor control in such a way that a thumbstick might be a better fit for controlling a camera in a 3D space? But the vast majority of action happens near the horizon, where there is little-to-no distortion. That is to say, the vast majority of action in shooters happens where a cursor that can freely move independently of the camera would respond almost identically to a given input as a crosshair locked to the centre of the screen. If one is much easier with a mouse, the other probably is, too.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks/html/f80b895472d08f14432f700c25ce47dfe67d8780-1835790142954051105" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>With some movie magic, here's a crude approximation of what Overwatch could look like with the aimer moving freely around the screen.</em></p> <p>If thumbsticks are bad at controlling a cursor in a flat plane to interact with small but slow-moving on-screen elements (such as playing a real-time strategy game), why should it be much better for hitting small moving objects that are actively trying not to get hit &#8212; such as playing a shooter?</p> <h2><span>Thumbstick aiming still has uses</span></h2> <p>I'm not saying games shouldn't use thumbsticks for aiming at all. The option should be there for those whose unusual circumstances make a mouse or mouse-like input difficult. Or even without unusual circumstances. If someone prefers to use thumbstick aiming, why shouldn't they be able to? If we embrace the fact that games assist thumbstick players, games can be more transparent about what they're doing. Aim assist can be less of a black box to players, and even work in more obvious and creative ways to help players who would prefer it.</p> <p>But let's also wake up. We've had no better option than thumbsticks for console aiming for so long that we reject solutions already embraced (even if poorly) by a few other games. We can do <em>better</em>.</p> <h2><span>If not thumbsticks, then what?</span></h2> <p>Thumbsticks aren't the ideal input for some games, but a mouse isn't always practical. When playing on the couch, in bed, on the go, controllers have the advantage of not needing a surface to rest on. The usefulness of a mouse depends a lot on a good surface to use it on (such as a mousepad), so keyboard and mouse are never going to take the place of controllers in console gaming.</p> <p>But in most modern consoles we have more than just buttons, triggers, and thumbsticks: we now have <em>gyro</em>. Sure, you might've encountered sloppy, unreliable, unpredictable motion controls. But when done right, the gyro is a responsive, precise input. It's a mouse that doesn't need a surface, doesn't take your thumbs or fingers away from sticks, buttons or triggers, and doesn't require much work at all to implement well.</p> <p>You just need to know <em>how</em>. And that's what <a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">GyroWiki</a> is for!</p> <p>Oh, and if you're still not sure, here's <em>osu!</em>&nbsp;played with the DualShock 4's gyro:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:why-not-just-use-thumbsticks/html/5589cdc308854f1801cbfd2d102fc0724facc402-1339610422935473560" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick</guid>
				<title>Good Gyro Controls Part 2: The Flick Stick</title>
				<link>http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick</link>
				<description>

&lt;p&gt;Let&#039;s look at what we can do with the right stick in 3D games now that precise aiming is handled by the gyro (as described in &lt;strong&gt;&lt;a href=&quot;http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse&quot;&gt;Part 1&lt;/a&gt;&lt;/strong&gt;). I propose that in 3D games we start using the &lt;em&gt;flick stick&lt;/em&gt;. Flick stick maps the angle of one of the thumbsticks (in the following examples, the right stick) to the same angle turn in-game. This gives the player far more direct and immediate control over their bearing than traditional stick controls. In fact, with flick stick, I believe a controller is now &lt;em&gt;better&lt;/em&gt; than a mouse for big turns, at least for the average player:&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sun, 02 Jun 2019 14:20:25 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Let's look at what we can do with the right stick in 3D games now that precise aiming is handled by the gyro (as described in <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Part 1</a></strong>). I propose that in 3D games we start using the <em>flick stick</em>. Flick stick maps the angle of one of the thumbsticks (in the following examples, the right stick) to the same angle turn in-game. This gives the player far more direct and immediate control over their bearing than traditional stick controls. In fact, with flick stick, I believe a controller is now <em>better</em> than a mouse for big turns, at least for the average player:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/8c90a468d7771eb9a339b35bffb2e824f841e71a-115353985871083193" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe><br /> <em>A quick explanation of flick stick using JoyShockMapper and DOOM.</em></p> <p>One of my main motivations for making JoyShockMapper was to see if something like this would make camera control in games better. I figured it'd make up for the shortcomings of the gyro in range &#8212; have the flick stick cover big movements while the gyro covers anything that requires precision. Perhaps it'd be useful with practice for high-level players as something like a &quot;pro mode&quot;. Once implemented, however, it exceeded my expectations. It's not just for those willing to put in a lot of practice. It's both easy to pick up for the first time and <em>extremely</em> useful once you've spent some time with it. I'll get more into learning how to use it later. For now, let's just get right into how it works.</p> <p>There are two steps to flick stick, and they both map a real world angle on the right stick to the <em>same</em> in-game angle:</p> <ol> <li>Flicking</li> <li>Turning</li> </ol> <p>For JoyShockMapper to trick other games into having flick stick requires some calibration (how far does the mouse need to move to turn a given angle?) but once you're set up, it's great. If you're implementing this in your game, you don't have to fake it with mouse moves, so there'll be no calibrating required.</p> <p>Let's go.</p> <table style="margin:0; padding:0"> <tr> <td style="margin:0; padding:0"> <div id="toc"> <div id="toc-action-bar"><a href="javascript:;" >Fold</a><a style="display: none" href="javascript:;" >Unfold</a></div> <div class="title">Table of Contents</div> <div id="toc-list"> <div style="margin-left: 1em;"><a href="#toc0">Flicking</a></div> <div style="margin-left: 2em;"><a href="#toc1">What's a good FlickTime?</a></div> <div style="margin-left: 1em;"><a href="#toc2">Turning</a></div> <div style="margin-left: 2em;"><a href="#toc3">Just a little smoothing</a></div> <div style="margin-left: 1em;"><a href="#toc4">Learning to play with Flick Stick</a></div> <div style="margin-left: 1em;"><a href="#toc5">Coming soon</a></div> </div> </div> </td> </tr> </table> <h1><span>Flicking</span></h1> <p>The flick is what happens when the flick stick is first tilted. The player indicates an angle they want to turn to relative to their current orientation, and the camera turns that angle in a quick, smooth flick.</p> <p>Because we want the flick to be a deliberate movement from the player and because we can get a more precise angle for the flick, we use a really big deadzone to decide whether the stick has been tilted. In JoyShockMapper, by default, the flick won't occur until the stick is 90% of the way from the centre to the outer edge.</p> <p>Here's what happens when we flick:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/9ca170f22d021ee075b27d9c163346fcec5347ea-3388972431919555992" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>So, when we tilt the stick, the angle of the stick is calculated, and over a very short window of time that angle is added to the player's yaw. That's it. Crucially, flick stick does <em>not</em> calculate an absolute in-game angle to move to, because the flick stick should <em>not</em> stop the player from making adjustments with the gyro or any other means of turning.</p> <p>Here's roughly how I do it in JoyShockMapper (look up <em>handleFlickStick</em> if you <a href="https://github.com/JibbSmart/JoyShockMapper" target="_blank">have the source</a>, but I'm going to simplify things here).</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// don't want a lot of state, but flick happens over time, so it's necessary</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flickProgress</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flickSize</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-comment">// settings</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">FlickThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.9</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">FlickTime</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-comment">// this will return a yaw change for the player</span><span class="hl-code"> </span><span class="hl-comment">// that'll be added to other input (such as gyro input)</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">HandleFlickStick</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">lastStick</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">stick</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">result</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastLength</span><span class="hl-code"> = </span><span class="hl-identifier">Length</span><span class="hl-brackets">(</span><span class="hl-identifier">lastStick</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">length</span><span class="hl-code"> = </span><span class="hl-identifier">Length</span><span class="hl-brackets">(</span><span class="hl-identifier">stick</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// by comparing the last frame to this one we can decide whether a flick is starting</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">length</span><span class="hl-code"> &gt;= </span><span class="hl-identifier">FlickThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">lastLength</span><span class="hl-code"> &lt; </span><span class="hl-identifier">FlickThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// flick start!</span><span class="hl-code"> </span><span class="hl-identifier">flickProgress</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-comment">// restart flick timer</span><span class="hl-code"> </span><span class="hl-comment">// stick angle from up/forward</span><span class="hl-code"> </span><span class="hl-identifier">flickSize</span><span class="hl-code"> = </span><span class="hl-identifier">Atan2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// turn!</span><span class="hl-code"> </span><span class="hl-comment">// ..</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// turn cleanup</span><span class="hl-code"> </span><span class="hl-comment">// ..</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// continue flick</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastFlickProgress</span><span class="hl-code"> = </span><span class="hl-identifier">flickProgress</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">lastFlickProgress</span><span class="hl-code"> &lt; </span><span class="hl-identifier">FlickTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">flickProgress</span><span class="hl-code"> = </span><span class="hl-identifier">Min</span><span class="hl-brackets">(</span><span class="hl-identifier">flickProgress</span><span class="hl-code"> + </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-identifier">FlickTime</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// get last time and this time in 0-1 completion range</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastPerOne</span><span class="hl-code"> = </span><span class="hl-identifier">lastFlickProgress</span><span class="hl-code"> / </span><span class="hl-identifier">FlickTime</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">thisPerOne</span><span class="hl-code"> = </span><span class="hl-identifier">flickProgress</span><span class="hl-code"> / </span><span class="hl-identifier">FlickTime</span><span class="hl-code">; </span><span class="hl-comment">// our WarpEaseOut function stays within the 0-1 range but pushes it all closer to 1</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">warpedLastPerOne</span><span class="hl-code"> = </span><span class="hl-identifier">WarpEaseOut</span><span class="hl-brackets">(</span><span class="hl-identifier">lastPerOne</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">warpedThisPerOne</span><span class="hl-code"> = </span><span class="hl-identifier">WarpEaseOut</span><span class="hl-brackets">(</span><span class="hl-identifier">thisPerOne</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// now use the difference between last frame/sample and this frame/sample</span><span class="hl-code"> </span><span class="hl-identifier">result</span><span class="hl-code"> += </span><span class="hl-brackets">(</span><span class="hl-identifier">warpedThisPerOne</span><span class="hl-code"> - </span><span class="hl-identifier">warpedLastPerOne</span><span class="hl-brackets">)</span><span class="hl-code"> * </span><span class="hl-identifier">flickSize</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">result</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Okay, I know that's a lot. Let's walk through it. Right at the top, outside the function, we have two variables we'll need to remember to complete a flick once it has started, and two settings that determine how we flick: a threshold for how far the stick needs to be pushed to trigger a flick (<em>FlickThreshold</em>), and the amount of time we want a flick to take (<em>FlickTime</em>). I've set <em>FlickTime</em> to the default in JoyShockMapper, 0.1 seconds.</p> <p>Then we get to the function itself. We have the previous frame's stick info. We start by checking if the current frame's stick is beyond the flick stick threshold. If it is, we use the previous frame's stick info to discern between two options. Either:</p> <ol> <li>this is the first frame beyond the threshold, and we're starting a flick, or</li> <li>we have already flicked before without releasing the stick, so this is a turn (explained further down).</li> </ol> <p>By comparing this stick state to the previous stick state, we can figure out if we need to execute a flick or a turn without holding onto any extra state. It might be worth having a little extra state so we can pad the flick threshold and avoid accidental flicks if players are keeping the stick around the flick threshold for whatever reason. I'll leave that to you.</p> <p>Now we've figured out whether we need to execute the flick action. If we've started a flick, we need to remember how big of an angle change we're going to make so that we can complete that angle change over the next few frames. We do that by resetting <em>flickProgress</em>, which tracks how far through a flick we are (in seconds), and setting <em>flickSize</em> to the angle the stick is making (from up/forward being 0°).</p> <p>Then, regardless of the current stick position, there may be a flick to complete, so we have the &quot;continue flick&quot; section at the end of the function. We accumulate time since the flick began in <em>flickProgress</em> so we can compare it to <em>FlickTime</em>. If the last frame was before <em>FlickTime</em> has been reached, we continue our flick. We compare the last frame's progress through the flick and this frame's progress through the flick (in time), and use those to figure out what portion of the flick angle we want to add to the player's yaw this frame.</p> <p>Perhaps the least clear part is the warping part. In animation or in real life, movements are very rarely perfectly linear. Movements will start slowly, accelerate to full speed (&quot;easing in&quot;), and then decelerate to a stop (&quot;easing out&quot;) at the end of the movement. So for the flick, it's probably best not to complete the movement <em>linearly</em> &#8212; it'll feel robotic and unnatural.</p> <p>Having said that, the warping we do above starts fast and decelerates to a stop. There's no accelerating at the beginning &#8212; there's no &quot;easing in&quot;. In games, for animations that are responding to player input, it's fairly common to forego easing in for the sake of making the game feel more responsive.</p> <p>So here's our warping function. It's really simple.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">WarpEaseOut</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">flipped</span><span class="hl-code"> = </span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">flipped</span><span class="hl-code"> * </span><span class="hl-identifier">flipped</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>An input of 0 maps to 0, 1 maps to 1, but everything in-between gets pushed closer to 1. You can replace it with any function you want &#8212; something like <a href="https://en.wikipedia.org/wiki/Smoothstep">smoothstep</a> is good if you want to ease in <em>and</em> ease out. I recommend avoiding easing in just so it feels more responsive. However, as a player, I personally probably won't be bothered how you do it as long as the <em>FlickTime</em> is nice and quick.</p> <p>Below you can see a side-by-side of our ease out function (left), linear (middle), and smoothstep (right), which eases in and out. They're all completing the same 135° flick in 0.1s, and they all start the movement at the same time and finish at the same time:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/0a39ecd1a0db8049f34b163ea5ce23627db440f0-1936973079655740983" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Look really closely, and you'll see that while they all complete the flick at the same time, the left one feels snappiest, as it covers the most ground at the beginning. Now, at this speed, they're probably <em>all</em> acceptable. The differences aren't obvious until we slow the recording down. But if you have the option for a slower flick, the warping function becomes more important.</p> <h2><span>What's a good <em>FlickTime</em>?</span></h2> <p>I would say around 0.1s is a good flick time. 0.2s makes for a smooth turn, but feels slow enough that I feel like I'm waiting for it (even though it's still probably faster than my reaction time). So I would recommend less than that, and I've found 0.1s to work really well for me. It feels snappy and deliberate.</p> <p>The obvious question from here is: why take any time at all? JoyShockMapper does it for 3 reasons, the first two of which could be considerations for your game:</p> <ol> <li><strong>Aesthetics</strong> - A quick, smooth turn looks more natural than a sudden reorientation.</li> <li><strong>Player bearings</strong> - The smooth transition may help players and spectators maintain their sense of the camera's bearing in the world.</li> <li><strong>Don't look like cheating</strong> - This one only applies to JoyShockMapper, because it's used to play other games that don't have flick stick. Spectators or those watching a kill-cam may see the unnaturally sudden reorientation as an indicator that the player is cheating. Using JoyShockMapper is <em>not</em> cheating &#8212; it has no awareness of the state of the game, and only does exactly what the player tells it to. But it's probably a good idea to avoid being accused of cheating, too.</li> </ol> <h1><span>Turning</span></h1> <p>The turn is what happens when the player rotates their stick while it is tilted. The difference in angle from the previous frame's stick position is added to the player's yaw pretty much instantly. This might be to make small adjustments to their flick, to follow a target that's moving around them, to watch a corner as they turn it in case an enemy is waiting, or even to pull off circle jumping in something like <em>Quake</em>.</p> <p>It is generally not added over a window of time like the flick is, but instead added right away (with a caveat we'll get to later). This is because it is mimicking a turn the player is actually making with their thumb. Like mouse and gyro aiming, this maps a real-world displacement to an in-game displacement, and it's best to be as responsive and direct as reasonably possible.</p> <p>This is what that looks like:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/62ddb9c47ea4bd8820b7bca4b48cc7ca633d5f42-4556770081969976755" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Let's look at some code. This is the same flick stick function from above, but now we're omitting flick-specific sections and filling in the turn sections.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// settings</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">TurnSmoothThreshold</span><span class="hl-code"> = </span><span class="hl-number">0.1</span><span class="hl-code">; </span><span class="hl-comment">// this will return a yaw change for the player</span><span class="hl-code"> </span><span class="hl-comment">// that'll be added to other input (such as gyro input)</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">HandleFlickStick</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">lastStick</span><span class="hl-code">, </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">stick</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">result</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastLength</span><span class="hl-code"> = </span><span class="hl-identifier">Length</span><span class="hl-brackets">(</span><span class="hl-identifier">lastStick</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">length</span><span class="hl-code"> = </span><span class="hl-identifier">Length</span><span class="hl-brackets">(</span><span class="hl-identifier">stick</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// by comparing the last frame to this one we can decide whether a flick is starting</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">length</span><span class="hl-code"> &gt;= </span><span class="hl-identifier">FlickThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">lastLength</span><span class="hl-code"> &lt; </span><span class="hl-identifier">FlickThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// flick start!</span><span class="hl-code"> </span><span class="hl-comment">// ..</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// turn!</span><span class="hl-code"> </span><span class="hl-comment">// stick angle from up/forward</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">stickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">Atan2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">stick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">lastStickAngle</span><span class="hl-code"> = </span><span class="hl-identifier">Atan2</span><span class="hl-brackets">(</span><span class="hl-code">-</span><span class="hl-identifier">lastStick</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code">, </span><span class="hl-identifier">lastStick</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">angleChange</span><span class="hl-code"> = </span><span class="hl-identifier">Wrap</span><span class="hl-brackets">(</span><span class="hl-identifier">stickAngle</span><span class="hl-code"> - </span><span class="hl-identifier">lastStickAngle</span><span class="hl-code">, -</span><span class="hl-identifier">PI</span><span class="hl-code">, </span><span class="hl-identifier">PI</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">result</span><span class="hl-code"> += </span><span class="hl-identifier">GetTieredSmoothedStickRotation</span><span class="hl-brackets">(</span><span class="hl-identifier">angleChange</span><span class="hl-code">, </span><span class="hl-identifier">TurnSmoothThreshold</span><span class="hl-code"> / </span><span class="hl-number">2.0</span><span class="hl-code">, </span><span class="hl-identifier">TurnSmoothThreshold</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// turn cleanup</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">lastLength</span><span class="hl-code"> &gt;= </span><span class="hl-identifier">FlickThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// we've just transitioned from flick/turn to no flick, so clean up</span><span class="hl-code"> </span><span class="hl-identifier">ZeroTurnSmoothing</span><span class="hl-brackets">()</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// continue flick</span><span class="hl-code"> </span><span class="hl-comment">// ..</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">result</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>At its core, all we're doing is getting the difference in stick angle from last frame to this frame and adding it to the camera yaw. An angle to an angle &#8212; an immediate response to the player's input. To that end, we have 3 functions that need explaining:</p> <ol> <li>Wrap</li> <li>GetTieredSmoothedStickRotation</li> <li>ZeroTurnSmoothing</li> </ol> <p>Wrap takes a number and an interval (inclusive at the bottom, exclusive at the top), and returns that number wrapped to that interval. So Wrap(1, 0, 10) would return 1, Wrap(11, 0, 10) would return 1, and Wrap(-1, 0, 10) would return 9. In this case, we're wrapping to the [-PI, PI) interval (replace PI with 180 if you're using degrees instead of radians), and that just means that we're assuming the stick took the shortest path possible from the last position to this position. I'll leave the implementation to the reader. However, if you ever turn an object to face another, there's a good chance you have a function like this somewhere already.</p> <p>The other two functions, GetTieredSmoothedStickRotation and ZeroTurnSmoothing, are necessary because sometimes we need just a <em>little</em> smoothing:</p> <h2><span>Just a little smoothing</span></h2> <p>Controller thumbsticks might not have the resolution to accurately express the player's intent with something like flick stick. Specifically, the DualShock 4 has the entire -1.0 to +1.0 range of their sticks represented by one byte per axis. This appears to be plenty for regular aiming, as player thumbs usually aren't any more precise than that for a single action. But, like top-down twin-stick aiming, flick stick allows the player to make very small adjustments to their aim, and with the DS4 that results in obvious and unappealing steps when fine-tuning.</p> <p>The solution here is to use <a href="http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing">soft tiered smoothing</a>, just like we talked about in part 1. I would call it crucial that smoothing only applies to small movements, and soft tiered smoothing as described there satisfies that condition while still honouring the stick's displacement once any smoothing is done. Here's the difference it makes to small movements, where the stick's limited resolution can become a problem:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/3bde798acb21ef29c5b13c5f52bff0dddb27dd22-393929820352501183" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>So let's get to the code for GetTieredSmoothedStick:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectStickRotation</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// smoothing buffer</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-brackets">[]</span><span class="hl-code"> </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">; </span><span class="hl-types">int</span><span class="hl-code"> </span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">GetSmoothedStickRotation</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code"> + </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code"> % </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-code">; </span><span class="hl-identifier">InputBuffer</span><span class="hl-brackets">[</span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-brackets">]</span><span class="hl-code"> = </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-identifier">foreach</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sample</span><span class="hl-code"> </span><span class="hl-identifier">in</span><span class="hl-code"> </span><span class="hl-identifier">InputBuffer</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> += </span><span class="hl-identifier">sample</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> /= </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">GetTieredSmoothedStickRotation</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold1</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold2</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">Abs</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">threshold2</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-identifier">Clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">directWeight</span><span class="hl-code">, </span><span class="hl-number">0.0</span><span class="hl-code">, </span><span class="hl-number">1.0</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectStickRotation</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">GetSmoothedStickRotation</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">))</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>This is the same soft tiered smoothing we used in <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse#toc8">Part 1</a></strong>, and just like before, I'll save an in-depth explanation of why everything works the way it does for <a href="http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing">its own post</a>, but here's what we get out of doing it this way:</p> <ol> <li>No smoothing at all is applied to inputs greater than a small threshold, and</li> <li>No matter what that threshold is, the smoothed and raw inputs are combined in such a way that the final displacement is as if no input was smoothed at all.</li> </ol> <p>Most inputs don't need any smoothing, so don't apply <em>any</em> smoothing to most inputs. And when we do apply smoothing, we don't want that to mess with the total displacement.</p> <p>Finally, when the stick is no longer tilted, we zero out the smoothing buffer so what's in there doesn't affect future turns:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">ZeroTurnSmoothing</span><span class="hl-brackets">()</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">for</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">i</span><span class="hl-code"> </span><span class="hl-identifier">in</span><span class="hl-code"> </span><span class="hl-number">0</span><span class="hl-code">..</span><span class="hl-identifier">InputBuffer</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">InputBuffer</span><span class="hl-brackets">[</span><span class="hl-identifier">i</span><span class="hl-brackets">]</span><span class="hl-code"> = </span><span class="hl-number">0.0</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>This means the final displacement may not <em>quite</em> honour the exact stick position if its last movement before the player released the stick is still being smoothed out. However, in this case I think it's preferable to stop turning as soon as the stick is released rather than completing a small smoothed turn when the player has already released the stick.</p> <p>And that's all there is to it! With practice, players can flick and turn their right stick to look in any direction, responding immediately to threats, checking corners, following tight paths, and tracking fast-moving targets.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick/html/3fd2e185e9446655d76b504b687fe461d0a6ba1e-1010454814827756429" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <h1><span>Learning to play with Flick Stick</span></h1> <p>Like any new kind of control, flick stick won't be second nature right away. However, from my own experience and from showing others, I expect you'll find flick stick pretty useful almost right away.</p> <p>Anecdotally, it appears to be typical for new players to just use the flick stick for big approximate left and right turns without much concern for the angle they actually want to turn to: tap right on the stick to turn right, and tap it again if that wasn't far enough. With a little practice, players become comfortable using the flick stick to quickly flick in a desired direction fairly precisely, and start relying on turning the flick stick for much of their navigation, perhaps to the detriment of their aim. Finally, players turn back to gyro for most turning when they're expecting an encounter at any second, but still fall back on flick stick for big flicks, big turns, and general navigation when not expecting a fight.</p> <p>Flick stick is still very new, though. I've not seen it implemented in a game (<em>EDIT 2022: flick stick features in <strong>Boomerang X</strong>, <strong>CS:GO</strong>, and <strong>Fortnite</strong>, with more on the way</em>), but you can try it in just about any PC game you want using <a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">JoyShockMapper</a> with a little calibration. I'd love to hear how more players find their first experiences with flick stick. And as players spend more time with it than I have, or just have a better talent for games than I do, I can't wait to see what &quot;expert&quot; flick stick play will look like (<em>EDIT 2022: <a href="https://www.youtube.com/watch?v=PJIqEX93vL8">just one example of an expert flick stick player</a></em>)!</p> <h1><span>Coming soon</span></h1> <p>As with the gyro controls in <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse">Part 1</a></strong>, flick stick should be simple enough to patch into an already published game on the PS4 or Switch. But we may have hit the limit of what we can try out by just faking mouse movements with a tool like JoyShockMapper.</p> <p>In <strong>Part 3</strong> we'll go beyond using the gyro as just a mouse. Why lock the aimer to the centre of the camera when we have a gyro and a right stick? I'll have to find other ways to demonstrate these, but in my experience prototyping these controls, I reckon they'll be <em>game-changing</em>.</p> <p>Keep an eye out for part 3 when I finish writing it.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse</guid>
				<title>Good Gyro Controls Part 1: The Gyro is a Mouse</title>
				<link>http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse</link>
				<description>

&lt;p&gt;Whether you call it &amp;quot;motion controls&amp;quot;, &amp;quot;motion aiming&amp;quot;, &amp;quot;gyro aiming&amp;quot;, or something else, the gyroscope is the mouse of the controller. It offers directness and exactness of control that&#039;s just impossible with tiny thumbsticks. While a controller&#039;s gyro and accelerometer are often used in games as a tool for figuring out real world orientation &amp;#8212; rotating 3D objects, interacting with a 3D world in VR or AR, or detecting player gestures &amp;#8212; this is &lt;em&gt;not&lt;/em&gt; a guide for those kinds of motion controls. You can learn more about those &lt;a href=&quot;http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion&quot;&gt;elsewhere on this site&lt;/a&gt;. But the gyro is severely underutilised as a &lt;em&gt;mouse&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Thu, 23 May 2019 14:54:42 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>Whether you call it &quot;motion controls&quot;, &quot;motion aiming&quot;, &quot;gyro aiming&quot;, or something else, the gyroscope is the mouse of the controller. It offers directness and exactness of control that's just impossible with tiny thumbsticks. While a controller's gyro and accelerometer are often used in games as a tool for figuring out real world orientation &#8212; rotating 3D objects, interacting with a 3D world in VR or AR, or detecting player gestures &#8212; this is <em>not</em> a guide for those kinds of motion controls. You can learn more about those <a href="http://gyrowiki.wikidot.com/blog:finding-gravity-with-sensor-fusion">elsewhere on this site</a>. But the gyro is severely underutilised as a <em>mouse</em>.</p> <p>Having an easy-to-use mouse built in to your controller is a <em>really</em> big deal. But few games make use of it, and those that do often make simple missteps. The goal of this guide is to establish good conventions that make it easy for developers to give gyro controls a go, improve the standard of gyro controls players expect from games, and thus make console gaming better for new gamers and established gamers alike.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/f3056c93ccecef827e6e4ee9e7b1fc27ea97917c-12173689691308771109" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>None of the games in that video support gyro controls (or at least not as they're shown in the video). However, <a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">JoyShockMapper</a> allows us to try out really good gyro controls by converting controller inputs to keyboard and mouse inputs. If you like, you can use it to follow along in parts 1 and 2 in just about any game you like (assuming it uses a mouse).</p> <p>This post is <strong>Part 1</strong> of 3. Here we'll cover principles of good gyro controls as well as what it takes to implement them well in a game. It's very simple, and a lone programmer could try these out in an already-working game in very little time, whether it's a 2D or 3D game.</p> <p><strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">Part 2</a></strong> will look at what we can do with the right stick in 3D games now that precise aiming is handled by the gyro. Specifically, we'll look at <em>flick stick</em>, which, like just about everything in part 1, you can try out with <a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">JoyShockMapper</a>.</p> <p><strong>Part 3</strong> will look at <em>game-defining gyro</em>. If we forget about the limitations of mouse-aim games and go beyond what's possible by just faking mouse inputs, what can we do? This will be beyond what JoyShockMapper can do.</p> <p>But let's get into Part 1 &#8212; better practices for using the gyro as a mouse, whether it's turning a 3D camera or controlling a 2D cursor. Look out for these boxes that highlight key conclusions reached by the paragraphs that precede them:</p> <div class="alert alert-success"> <p><strong>Do!</strong> &#8212; This is something I strongly recommend you do when adding gyro controls to your game.</p> </div> <div class="alert alert-danger"> <p><strong>Don't!</strong> &#8212; This is something I strongly recommend you avoid when adding gyro controls to your game.</p> </div> <div class="alert alert-warning"> <p><strong>Caution!</strong> &#8212; This is something I strongly recommend you consider before exploring a more difficult solution to a given problem.</p> </div> <table style="margin:0; padding:0"> <tr> <td style="margin:0; padding:0"> <div id="toc"> <div id="toc-action-bar"><a href="javascript:;" >Fold</a><a style="display: none" href="javascript:;" >Unfold</a></div> <div class="title">Table of Contents</div> <div id="toc-list"> <div style="margin-left: 1em;"><a href="#toc0">One principle for good gyro controls</a></div> <div style="margin-left: 1em;"><a href="#toc1">The short version</a></div> <div style="margin-left: 1em;"><a href="#toc2">Implementation</a></div> <div style="margin-left: 2em;"><a href="#toc3">The Core</a></div> <div style="margin-left: 3em;"><a href="#toc4">Calibration</a></div> <div style="margin-left: 3em;"><a href="#toc5">Sensitivity</a></div> <div style="margin-left: 2em;"><a href="#toc6">Improving range and precision</a></div> <div style="margin-left: 3em;"><a href="#toc7">Acceleration</a></div> <div style="margin-left: 3em;"><a href="#toc8">Smoothing</a></div> <div style="margin-left: 3em;"><a href="#toc9">Minimum velocity</a></div> <div style="margin-left: 2em;"><a href="#toc10">Lifting the mouse</a></div> <div style="margin-left: 2em;"><a href="#toc11">Aim Assist</a></div> <div style="margin-left: 2em;"><a href="#toc12">Latency</a></div> <div style="margin-left: 2em;"><a href="#toc13">Accessibility</a></div> <div style="margin-left: 1em;"><a href="#toc14">Summary</a></div> <div style="margin-left: 1em;"><a href="#toc15">Coming soon</a></div> </div> </div> </td> </tr> </table> <h1><span>One principle for good gyro controls</span></h1> <p>We have one principle for good gyro controls:<br /></p> <div style="text-align: center;"> <p><span style="font-size:large;"><strong>The gyro is a mouse.</strong></span></p> </div> <p>Simple, right? In the same way as a regular mouse (and as opposed to a thumbstick), the gyro translates a real movement to a proportionate in-game movement, allowing the player to move a cursor, aimer, or camera as if directly with their own hand. This is actually really cool, because:</p> <ol> <li>Many games rely on the mouse, or are both easier to learn and have greater room for mastery with a mouse than with a thumbstick.</li> <li>Decades of the mouse being used in games have established helpful conventions that may translate well to gyro controls.</li> </ol> <p>This principle informs our values, and how we go about achieving them:</p> <ul> <li><strong>Simplicity</strong> &#8212; Being easy to understand (for players) and easy to implement (for developers) can go hand in hand.</li> <li><strong>Transferability</strong> &#8212; A simple and straightforward foundation embraced in multiple games (just like standard mouse controls) allows players to bring skills learned in one game to others.</li> <li><strong>Responsiveness</strong> &#8212; For such direct controls as controlling a cursor or camera with a mouse, responsiveness mustn't be compromised.</li> </ul> <h1><span>The short version</span></h1> <p>All these points come from the &quot;Do&quot; (green), &quot;Don't&quot; (red), and &quot;Caution&quot; (yellow) boxes strewn through the rest of the article, each one summarising a conclusion reached in its section. Here we go:</p> <ul> <li>At its core, your gyro controls convert a calibrated rotational velocity to a linearly proportional in-game rotation (3D games - yaw velocity for yaw, pitch velocity for pitch, under most circumstances) or cursor movement (2D games - yaw velocity for x axis, pitch velocity for y axis, under most circumstances)</li> <li>The player should be able to calibrate the controller at any time. <ul> <li>If you or your platform can auto-calibrate, that's great, if and only if the player can disable auto-calibration if they desire. Auto-calibration <em>will</em> get things wrong at crucial times.</li> </ul> </li> <li>For 3D camera-turning games, when you're trying to figure out what the numbers on your sensitivity scale should mean, there's only one true scale: the <em>natural sensitivity scale</em>. You're translating real world rotational velocities to in-game rotational velocities, so let your &quot;1&quot; be &quot;1&quot;.</li> <li>Make sure your scale goes high enough! Many players are most comfortable playing with a sensitivity of 4, or even as high as 8! But most games don't let players set their sensitivity that high. 10 is a nice round number that should cover the vast majority of players.</li> <li>For 2D cursor-controlling games, there's no obvious natural scale. But if you can pick something sensible, or something another popular game uses, please do! Make it easy for players to move from one game to another!</li> <li>The game should offer more options to help the player with the trade-off between range and precision as they choose their sensitivity, and they should <em>all be optional</em> and, ideally, configurable: <ul> <li>Acceleration lets the player have a higher sensitivity for fast movements than for slow movements. Mouse acceleration settings are not transparent to the player, and don't translate well between different games or different mice, so check out my alternative below.</li> <li>Smoothing can stabilise the cursor/aimer, but should <em>never</em> be applied to big movements. A <a href="http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing">tiered smoothing solution</a> can steady the cursor when the player's trying to hold the controller still without harming responsiveness and precision at all with bigger movements.</li> <li>Minimum velocity threshold is the worst. Don't do it at all. <em>Especially</em> in 3D shooters &#8212; targets can be any distance away and be moving at any speed in screen-space. No minimum velocity threshold can help the player in some circumstances without harming the player in others. But a &quot;tightening&quot; threshold can help keep the cursor still when you want it to with far fewer side-effects.</li> </ul> </li> <li>Lifting the mouse &#8212; the gyro equivalent is an input that can disable the gyro, whether it's a dedicated button, a button that does different things depending on whether you tap it or hold it, or, for 3D games, while the right-stick is being used. Of course, for games where the player spends most of the time not needing the gyro and would only need it when a weapon or other aiming-device is drawn (like just about every third-person action blockbuster on the PS4), the gyro can simply be activated when going into aim mode, and doesn't need another input to disable it.</li> </ul> <p>Many of these have gotchas, complications, or simpler ways they can be expressed to the player, and I discuss these over the rest of this article. If you have any objections or questions, please read the corresponding section below, and if that doesn't make things clear as day, please reach out to me! I should either be able to help you or learn from you.</p> <h1><span>Implementation</span></h1> <p>Now, I <em>know</em> PlayStation and Switch controllers are capable of doing all of these well &#8212; especially the DualShock 4. I wrote JoyShockMapper and JoyShockLibrary, so I know what comes out of those controllers. I play PC games almost exclusively using JoyShockMapper to convert controller (including gyro) inputs to keyboard and mouse.</p> <p>But I do <em>not</em> know what Nintendo and Sony make available to developers on their platforms. Automatic recalibration, for example, appears to be ubiquitous in Switch games with gyro controls. Is that because Nintendo recommends it, because the Switch <em>can</em> do it automatically, or because the Switch <em>does</em> do it automatically, whether the game developer wants it or not? I don't know.</p> <p>But between Sony, Nintendo, and you, everything here is definitely doable with the hardware your players already have. And if you're supporting these controllers on PC, there's certainly nothing stopping you from doing everything described here.</p> <p>So let's get right to implementing simple gyro controls. We'll have code snippets, JoyShockMapper settings, and video examples using JoyShockMapper to add great gyro controls to games that don't have them. If you want to follow along with <a href="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide">JoyShockMapper</a> and your favourite game, load up a configuration, and start by putting in your in-game sensitivity so JoyShockMapper's correctly calibrated:</p> <div class="code"> <pre><code>IN_GAME_SENS = &lt;Your in-game sensitivity&gt;</code></pre></div> <p>Then, before we begin, let's disable everything that might affect gyro controls:</p> <div class="code"> <pre><code>GYRO_SENS = 0 GYRO_CUTOFF_SPEED = 0 GYRO_CUTOFF_RECOVERY = 0 GYRO_SMOOTH_THRESHOLD = 0</code></pre></div> <h2><span>The Core</span></h2> <p>So, mouse aim is pretty standard. We start with something basic like this:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">ProcessMouseInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">deltaMouse</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">mouseSensitivity</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">deltaMouse</span><span class="hl-code">.</span><span class="hl-identifier">X</span><span class="hl-code"> * </span><span class="hl-identifier">mouseSensitivity</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">deltaMouse</span><span class="hl-code">.</span><span class="hl-identifier">Y</span><span class="hl-code"> * </span><span class="hl-identifier">mouseSensitivity</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Simple, right? For games with a cursor, replace Camera.Yaw and Camera.Pitch with Cursor.X and Cursor.Y. On PC, of course, 2D games will often just directly use the mouse position unless they're using raw mouse input. Some games will have (optionally) a good mouse acceleration, and we'll get to that later. But anyway, here's our simple gyro aim:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">ProcessGyroInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">gyroSensitivity</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> * </span><span class="hl-identifier">gyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> * </span><span class="hl-identifier">gyroSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>This is pretty simple, too. There are some differences, though:</p> <ul> <li>Gyros need to be calibrated.</li> <li>Gyros give velocity, not displacement. So we use deltaTime (the time since the last input sample) to convert to a displacement. Still, mouse and gyro <em>both</em> map a displacement to a displacement, and a velocity to a velocity.</li> <li>For 3D camera control games, we're converting a controller Yaw velocity to a change in camera Yaw and a controller Pitch velocity to a change in camera Pitch. This is an obvious and natural mapping from real world movement to in-game movement.</li> </ul> <p>The gyro will actually give velocities around different axes &#8212; x, y, and z &#8212; rather than absolute euler angles &#8212; yaw, pitch, and roll. But I refer to yaw and pitch because that makes it more clear, I think, which movements of the gyro should change which in-game angles. <em>Fortnite</em>'s default gyro aiming settings on Switch, for example, had turning left and right depend on the controller's roll rather than yaw. Your in-game yaw was controlled by your controller's roll. It's almost as absurd as it sounds, except that when played in handheld mode, the JoyCons are facing up, so the controller's local roll corresponds better to the player's perception of yaw. Now, thankfully the player is able to change that setting &#8212; good settings go a long way &#8212; but the default probablly should've been an &quot;auto&quot; option that uses the controller's roll when attached to the Switch and the controller's yaw when detached.</p> <p>Back to the code. You may have expected something more complicated. I know I did when I was first writing JoyShockLibrary and JoyShockMapper. Some games will do something more complicated &#8212; they'll do something called &quot;sensor fusion&quot;, combining a gravity vector from the accelerometer with the rotations from the gyro to know what's up and down. But I suggest not doing it for two reasons:</p> <ol> <li>Sensor fusion can be complicated. Big commercial games that are otherwise extremely well made can apply it poorly. <em>Super Mario Odyssey</em>, for example, uses it for aiming a tank. But when playing with the JoyCons attached to the console, they're mostly pointing up. Pitching them further up to aim up in game makes the JoyCons upside down, and the game responds by aiming down instead. It's awful. The problem isn't intrinsic to sensor fusion &#8212; <em>World of Goo</em> does a much better job with it &#8212; but complicated solutions are prone to problems. Just keep it simple. Your mouse doesn't care if the user is upside down, lying on their side, or holding the mouse the wrong way, so why should the gyro?</li> <li>Sensor fusion is a compromise between two sensors that disagree with each other. If the player is moving for a lot of time and the game doesn't get a chance to get its bearings, this causes increasing errors in the game's interpretation of the player's input.</li> </ol> <p><em>Update, 2021</em>: there are some benefits to a sensor fusion solution if you really do get it right. Earlier this year I introduced <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained">&quot;player space gyro&quot;</a> and a detailed guide on implementing it. It offers more freedom of movement for players without introducing any error over a gyro-only solution. It doesn't generalise quite as well as &quot;local&quot; gyro controls, so perhaps don't go down that road if you're only trying to hit the basics.</p> <div class="alert alert-warning"> <p><strong>Caution!</strong> If you're considering using sensor fusion to use a real world orientation for your controls in-game, it's likely better that you don't. It's much more complicated, full of compromises, easy to do poorly, and often unnecessary:</p> <ul> <li>The mouse doesn't know its absolute position on the mousepad; the gyro doesn't need to know its absolute orientation.</li> <li>Sensor fusion is great for VR, AR, manipulating 3D objects freely, but this isn't VR, this is a mouse.</li> </ul> <p>If you really want to explore it anyway, start with my guide on various ways to make use of the gravity vector in your gyro aiming implementation, including the novel <a href="http://gyrowiki.wikidot.com/blog:player-space-gyro-and-alternatives-explained">&quot;player space gyro&quot;</a>, rather than re-inventing the wheel.</p> </div> <p>Now, while games with standard mouse controls sometimes do additional work (mouse acceleration, smoothing, etc), it's always optional. Always. If the player can't reduce things to essentially the &quot;core&quot; above, <em>these are bad mouse controls</em>. Even if the changes have clear benefits, players have the reasonable expectation that they'll be able to have the cursor or aimer respond in linear proportion to their mouse input, without delay. This inspires some of the values of good gyro controls &#8212; <strong>simplicity</strong> and <strong>transferability</strong>. These controls are easy for players to understand, easy for developers to implement, and understandably form the basis for practically all games that benefit from a mouse-controlled cursor or aimer.</p> <p>This <em>should</em> be equally true for gyro controls. As we continue, we're going to look at doing more than just our &quot;core&quot; above, but everything we add will be configurable and optional. These things will include acceleration, smoothing, and we'll look at speed thresholds (which are the devil's work, but some games have them, so let's look at them). If <em>any</em> game that uses the gyro to control a cursor or aimer has these features and they <em>can't</em> be disabled or configured to the point that they're truly negligible, that game has <em>bad gyro controls</em>. Even if they had <em>my</em> preferred acceleration settings and filters and did everything &quot;right&quot; from my subjective point of view (which they don't), they're still <em>bad gyro controls</em> if those settings and filters can't be disabled.</p> <div class="alert alert-success"> <p><strong>Do</strong> make all the bells and whistles you add on top of this core implementation <em>optional</em>. All of them. Smoothing, thresholds, acceleration, or anything else. Every filter or transformation compromises the simplicity or responsiveness of the controls. If you already do any of these things and they're <em>not</em> optional, please fix that.</p> </div> <p>But to fully understand our core, we need to understand the variables involved. Our inputs to the <em>ProcessGyroInput</em> function are:</p> <ul> <li><em>calibratedGyro</em> &#8212; the calibrated gyro input from the controller, usually in degrees per second</li> <li><em>deltaTime</em> &#8212; the amount of time (usually in seconds) since the last input sample</li> <li><em>gyroSensitivity</em> &#8212; a user-determined multiplier so players can increase their comfortable range in the game (by increasing sensitivity) or increase precision (by decreasing sensitivity)</li> </ul> <p><em>deltaTime</em> is something most developers will already be familiar with &#8212; it's just the time since the last frame or input sample, and it's useful for integrating velocities. Let's talk about calibration and sensitivity.</p> <h3><span>Calibration</span></h3> <p>At present, gyros in modern controllers sometimes require calibration. They're very good at giving relative rotational velocities, but over time can have the wrong idea of where &quot;zero&quot; is. Calibration is just finding where the gyro thinks zero is so we can correct it.</p> <p>Measuring the gyro output when the controller is stationary is how we get that zero. But since we have no reliable way of telling if the controller is still, we need to prompt the player to put the controller down or hold it still so the game can get the zero value. If the player's holding it, their hands are shaky; even if the controller is perfectly still, the gyro's output will be <em>slightly</em> noisy. Either way, getting the zero value at an instant is probably not going to be good enough &#8212; better to get the average output over a short window of time:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// current velocity, uncalibrated</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">Velocity</span><span class="hl-code">; </span><span class="hl-comment">// for calibration</span><span class="hl-code"> </span><span class="hl-types">int</span><span class="hl-code"> </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code">; </span><span class="hl-identifier">bool</span><span class="hl-code"> </span><span class="hl-identifier">Calibrating</span><span class="hl-code">; </span><span class="hl-comment">//...</span><span class="hl-code"> </span><span class="hl-comment">// settings</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-comment">//...</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">GetCalibrationOffset</span><span class="hl-brackets">()</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code"> == </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code">.</span><span class="hl-identifier">Zero</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code"> / </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">ResetCalibration</span><span class="hl-brackets">()</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code"> = </span><span class="hl-number">0</span><span class="hl-code">; </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code"> = </span><span class="hl-identifier">Vec3</span><span class="hl-code">.</span><span class="hl-identifier">Zero</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">ProcessInput</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Calibrating</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code">++; </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code"> += </span><span class="hl-identifier">Velocity</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// we're using gyro as a mouse, only need 2D input</span><span class="hl-code"> </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">gyroVelocity</span><span class="hl-code"> = </span><span class="hl-identifier">Velocity</span><span class="hl-code">.</span><span class="hl-identifier">XY</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">gyroCalibration</span><span class="hl-code"> = </span><span class="hl-identifier">GetCalibrationOffset</span><span class="hl-brackets">()</span><span class="hl-code">.</span><span class="hl-identifier">XY</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code"> = </span><span class="hl-identifier">gyroVelocity</span><span class="hl-code"> - </span><span class="hl-identifier">gyroCalibration</span><span class="hl-code">; </span><span class="hl-identifier">ProcessGyroInput</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-identifier">Sensitivity</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <div class="alert alert-success"> <p><strong>Do</strong> prompt players to calibrate their gyro when they start playing, and allow them to recalibrate at any time from the menu. If the controller on your platform usually doesn't need calibration, skip the prompt, but still allow the player to recalibrate if and when they wish.</p> </div> <p>Your API might make things even simpler. It might have functions to start calibrating (JoyShockLibrary has <em>JslStartContinuousCalibration</em>), stop calibrating (<em>JslPauseContinuousCalibration</em>), reset or manually set calibration (<em>JslResetContinuousCalibration</em> and <em>JslSetCalibrationOffset</em>, respectively).</p> <div class="alert alert-warning"> <p><strong>Caution!</strong> Many games on Switch use some kind of automatic calibration that can correct itself at any time while playing. If you want to do this, great! But allow players to disable it and manually calibrate their gyro, because the automatic calibration <em>will</em> do something wrong. It'll interpret the player slowly and steadily moving the controller intentionally (such as to follow a slow or distant target) as needing recalibration. This feels really bad when it happens.</p> </div> <h3><span>Sensitivity</span></h3> <p>When playing 3D games where the mouse turns the camera, the mouse sensitivity scale can seem arbitrary. A sensitivity of 1.5 in one game might give you the same results as a sensitivity of 5 in another. This is understandable, as there's no obvious mapping from a 2D mouse input to an in-game rotation. This is exacerbated by different mice having different resolutions and polling rates, so the same settings in the same game won't necessarily behave the same with different mice.</p> <p>An advantage specific to gyro controls in 3D games is that there really is <em>one good</em> scale for sensitivity, because a real world rotation is being mapped to an in-game rotation. For <em>any</em> 3D camera-controlling game with gyro controls, a sensitivity of 1 should mean that if you turn the controller 37.5° to the left, your camera or aimer should turn 37.5° to the left. If you want steadier aim for distant or small targets, you could reduce it &#8212; a sensitivity of 0.5 means in-game turns are only half your real world turns, making it easier to precisely fine tune your aim. If you want a wider range of motion without having to turn your controller uncomfortably far, you could increase it &#8212; a sensitivity of 2 means in-game turns are double your real world turns. Let's call this the &quot;natural sensitivity scale&quot;.</p> <p>The natural sensitivity scale makes gyro controls more <strong>simple</strong> for players, because they can easily understand what the gyro sensitivity means. It also helps the <strong>transferability</strong> of gyro controls between different games, assuming multiple games actually share this scale. This makes it easier for players to pick up gyro controls in one game if they've already learned to use them in another.</p> <div class="alert alert-success"> <p><strong>Do</strong> use the <em>natural sensitivity scale</em> for games where the gyro turns the camera. This means a sensitivity of 1 maps a real world turn to the same turn in-game. And make sure to let player's set their sensitivity as high as they like &#8212; I know players who like to go as high as 8, so let's at least go up to 10 for a nice round number with a little wiggle room.</p> </div> <p>If your character rotation is given in degrees, our core snippet already honours the natural sensitivity scale. The calibrated input is in degrees per second, the time since last sample in seconds, and then it's multiplied by the player's sensitivity setting giving a change in degrees.</p> <p>Here's what it looks like in action in <em>Overwatch</em> using JoyShockMapper with a gyro sensitivity of 1:</p> <div class="code"> <pre><code>GYRO_SENS = 1</code></pre></div> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/8834926e037d39437d4df85a1ae7d326c90087b7-971935641905675463" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p><em>Overwatch</em> doesn't natively have gyro controls (yet!), but perhaps that'll change in future? We still have our usual stick controls that we can use at the same time, but aren't relying on a stick for the finer aiming.</p> <p>And here's what it looks like with a gyro sensitivity of 2:</p> <div class="code"> <pre><code>GYRO_SENS = 2</code></pre></div> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/8cdbf28e9cc243bab8fdc0e759942c13e8d0b560-115338567164062415" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>We have much better range of movement here, but at the cost of precision. Of course, with practice, you can make up some precision. I'm not used to playing at 2 for the precise stuff, and it shows in the video above.</p> <p>For 2D games the gyro is still a great mouse. But there's no super obvious &quot;natural&quot; sensitivity scale from an orientation in 3D to a 2D plane, so I'm not going to put anything in a green box for 2D sensitivity. But here's what I've tried to establish as convention in JoyShockMapper: a sensitivity of 1 means 1 complete revolution of the controller left-to-right will move the cursor one screen-width left-to-right. That's a <em>long</em> way to move the controller, and the player will never play with such a low sensitivity. A more sensible setting is something like 8 (1/8 of a revolution to screen-width, or 45°) or 16 (1/16 of a revolution to screen-width, or 22.5°).</p> <p>I know the mapping isn't super obvious, and it takes a little work to convert a sensitivity to something the player understands. But it works. It's still a simple multiplier on the player's input (doubling the sensitivity doubles the speed you get from a given input), and at this scale you could likely give the player the precision they need with the sensitivity constrained to integers &#8212; although I'd suggest erring on the side of finer control. If you've got better ideas for a 2D cursor sensitivity scale for gyro, please let me know!</p> <p>There's a lot to like about a higher sensitivity, whether a 2D or 3D game. We now have a much wider range of movement without having to hold the controller uncomfortably. Just like with a regular mouse, higher sensitivity comes at the cost of precision &#8212; the faster the cursor or aimer moves in response to our movements, the harder it is to make smaller movements.</p> <p>Small movements are difficult enough to do precisely. A regular mouse and a gyro controller each have unique complications. A regular mouse has some friction with the mousepad or surface it's on, making small movements less consistent, but at least it's easy to keep the mouse still. A gyro, on the other hand, is essentially frictionless, as it doesn't rest on a mousepad. But with nothing to rest on, it's more difficult to keep the controller perfectly still. And while players can improve their mouse experience by getting a larger mousepad so they can keep mouse sensitivity relatively low, we can't do that with the gyro &#8212; its range is essentially fixed to what's comfortable for the player.</p> <p>So what can we do to give the player as much range of movement as possible while still allowing them all the precision they need for small on-screen buttons and distant targets?</p> <h2><span>Improving range and precision</span></h2> <p>We're going to look at three tools commonly used for keeping small movements precise while allowing the player to choose a higher sensitivity, in descending order of importance:</p> <ol> <li>Acceleration</li> <li>Smoothing</li> <li>Minimum velocity</li> </ol> <p>They all have weaknesses either in their common implementation or fundamentally. The third option is terrible. Don't do it. But in that section we'll look at why it's bad, and we'll look at something better that's inspired by it, which we'll call a &quot;tightening threshold&quot;.</p> <h3><span>Acceleration</span></h3> <p>The challenge in choosing a good sensitivity comes from the fact that decreasing sensitivity will improve your precision but make it harder to make fast turns, while increasing sensitivity will increase the range you can turn comfortably at the cost of precision for small or slow-moving targets. In 2D, low sensitivity makes it a chore to move the cursor from one side of the screen to the other, but high sensitivity makes it difficult to hit small buttons.</p> <p>But imagine you could choose one sensitivity for when you're moving the controller slowly, another sensitivity for when you move it quickly, and have the game smoothly interpolate between the two sensitivities according to how fast you're moving the controller between &quot;slowly&quot; and &quot;quickly&quot;. That's what <em>acceleration</em> does.</p> <p>The name can conjure up awful ideas &#8212; a velocity or sensitivity that changes over time, as we usually think of acceleration. But, borrowing from &quot;mouse acceleration&quot;, it really just means that your effective sensitivity changes according to your velocity <em>that instant</em>. Mouse acceleration itself has an undeservedly poor reputation, but <a href="http://mouseaccel.blogspot.com/2015/11/why-other-forms-of-mouse-acceleration.html" target="_blank">when done right</a>, it's consistent (even under varying framerates) and easy to learn.</p> <p>But it's almost always presented in ways that are difficult for the player to interpret. In some games, you'll choose a mouse acceleration of &quot;none&quot;, &quot;low&quot;, &quot;medium&quot;, or &quot;high&quot;, whatever that means. In others, it's a single number &#8212; a gradient for the sensitivity when graphing input speed on the X axis and desired sensitivity on the Y axis. When the mouse isn't moving it'll be whatever their mouse sensitivity setting is. As input speed increases, the gradient is used to determine the sensitivity to apply to that input. This is still not very transparent, as the input speed isn't something intuitive to users, especially as mice vary in DPI and poll rates, and even if this speed is well understood by the player, figuring out a gradient such that the player has certain effective sensitivities when moving the mouse at certain speeds is difficult.</p> <p>So now we come back to gyro. Conventions are yet to be established. But there's no such thing as DPI. Gyros report velocity already (rather than displacement), so lend themselves to a consistent and easy to understand acceleration system. When acceleration is enabled, the player is given four options:</p> <ol> <li>Slow sensitivity. This is the sensitivity the player wants when they move the controller slowly, and of course we're using the natural sensitivity scale, which is easy for players to understand and hopefully means these settings can be shared by other games that use the same scale. This is MIN_GYRO_SENS in JoyShockMapper if you want to try it yourself.</li> <li>Fast sensitivity. This is the sensitivity the player wants when they move the controller fast. This is MAX_GYRO_SENS in JoyShockMapper.</li> <li>Slow threshold. This is the real-world controller speed that is considered &quot;slow&quot; for the purposes of calculating the actual sensitivity. Degrees per second seems to be the standard unit used by the gyro manufacturers for both Switch and PlayStation devices, and they're relatively easy for players to understand, so I urge you to use this scale. This is MIN_GYRO_THRESHOLD in JoyShockMapper.</li> <li>Fast threshold. This is the real-world controller speed that is considered &quot;fast&quot; for the purposes of calculating the actual sensitivity. This is MAX_GYRO_THRESHOLD in JoyShockMapper.</li> </ol> <p>Here's what that can look like:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">ProcessGyroAcceleration</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sensitivitySlow</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">sensitivityFast</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">slowThreshold</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">fastThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// how fast is the gyro moving?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">speed</span><span class="hl-code"> = </span><span class="hl-identifier">Sqrt</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> * </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> + </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> * </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// where do we stand between the slow threshold and the fast threshold?</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">slowFastFactor</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">speed</span><span class="hl-code"> - </span><span class="hl-identifier">slowThreshold</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">fastThreshold</span><span class="hl-code"> - </span><span class="hl-identifier">slowThreshold</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">slowFastFactor</span><span class="hl-code"> = </span><span class="hl-identifier">Clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">slowFastFactor</span><span class="hl-code">, </span><span class="hl-number">0.0</span><span class="hl-code">, </span><span class="hl-number">1.0</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// linearly interpolate</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">newSensitivity</span><span class="hl-code"> = </span><span class="hl-identifier">sensitivitySlow</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">slowFastFactor</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">sensitivityFast</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-identifier">slowFastFactor</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-comment">// now apply this sensitivity the way we originally did</span><span class="hl-code"> </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> += </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> * </span><span class="hl-identifier">newSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code">; </span><span class="hl-identifier">Camera</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> += </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> * </span><span class="hl-identifier">newSensitivity</span><span class="hl-code"> * </span><span class="hl-identifier">deltaTime</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>We get the magnitude of the input. We find out whether it puts us on the &quot;slow&quot; or &quot;fast&quot; side of things, or where in-between. And then we interpolate between the player's two chosen sensitivities accordingly, before applying that sensitivity just as we did at the beginning.</p> <p>For 2D cursor games the code is almost identical. Swap your &quot;Yaw&quot; for an &quot;X&quot;, &quot;Pitch&quot; for a &quot;Y&quot;, and &quot;Camera&quot; for a &quot;Cursor&quot;.</p> <div class="alert alert-success"> <p><strong>Do</strong> provide the option for an easy-to-understand acceleration. I suggest a &quot;slow&quot; sensitivity and a &quot;fast&quot; sensitivity, along with a real-world velocity threshold for each as described above. The sensitivities should use the same scale as your core gyro controls setting (ideally the natural scale), and it'd be of great benefit to players of different games if everyone has the same unit of rotational velocity for the thresholds &#8212; JoyShockMapper uses degrees per second, as do the spec sheets for the gyros used in Switch and PlayStation controllers.</p> </div> <p>This is what it looks like with sensitivities 1 and 2, thresholds 0 and 75 degrees-per-second:</p> <div class="code"> <pre><code>MIN_GYRO_SENS = 1 MAX_GYRO_SENS = 2 MIN_GYRO_THRESHOLD = 0 MAX_GYRO_THRESHOLD = 75</code></pre></div> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/1b32df7a0e15312666402991c57d02a660376276-1841149023220005706" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>I've found these settings to work well across a variety of games. The acceleration isn't steep, and I've found new players to pick it up quickly. However, depending on the pace of the game, different settings might be better &#8212; <em>Quake</em>, for example, moves much more quickly than <em>Overwatch</em>. A lot of mouse-controlled games will have their acceleration uncapped, and it's still possible to do that here: a simple checkbox to say &quot;uncapped&quot; or something, and the sensitivity line can project through the max sensitivity and threshold. This is one thing JoyShockMapper doesn't do yet, and may come later. But I really like the sensitivity cap so that I can make big flicks consistently without having to be too precise about how quickly I make the move.</p> <p>Of course, we want some good acceleration settings by default for new players, but <em>also</em> want to keep the settings simple unless the player enables the advanced settings. While I stand by the transparency and usefulness of the acceleration settings I've described, they're not simple for a player who doesn't care about that kind of thing.</p> <p>Perhaps you could let the player choose between &quot;simple&quot; and &quot;advanced&quot; for their gyro sensitivity settings. &quot;Simple&quot; exposes a single sensitivity slider and lets the player choose between an acceleration of &quot;none&quot;, &quot;low&quot;, &quot;standard&quot;, and &quot;high&quot;, which set your slow sensitivity to the player's chosen sensitivity and the fast sensitivity to an appropriate higher sensitivity. Your thresholds would be values you've chosen, and by having &quot;standard&quot; chosen by default, you can choose good default acceleration settings for the player without forcing them to mess with advanced settings if they want to change something.</p> <p>Choosing &quot;advanced&quot;, of course, exposes both sensitivities and both thresholds, which players who take their settings seriously will appreciate being able to fine-tune.</p> <h3><span>Smoothing</span></h3> <p>Sometimes gyro controls feel sloppy. They feel unresponsive and imprecise. It appears to me there are two main sources of this sloppiness:</p> <ol> <li>Input latency (which we'll touch on later)</li> <li>Smoothing</li> </ol> <p>Smoothing here refers to using a rolling average of the user's input in order to stabilise the cursor or aimer, averaging out any shakiness or noise.</p> <p>Here are two filters for player input. One doesn't really do anything. The other applies some simple smoothing:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// smoothing buffer</span><span class="hl-code"> </span><span class="hl-identifier">Vec2</span><span class="hl-brackets">[]</span><span class="hl-code"> </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">; </span><span class="hl-types">int</span><span class="hl-code"> </span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-code"> + </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code"> % </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-code">; </span><span class="hl-identifier">InputBuffer</span><span class="hl-brackets">[</span><span class="hl-identifier">CurrentInputIndex</span><span class="hl-brackets">]</span><span class="hl-code"> = </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> = </span><span class="hl-identifier">Vec2</span><span class="hl-code">.</span><span class="hl-identifier">Zero</span><span class="hl-code">; </span><span class="hl-identifier">foreach</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">sample</span><span class="hl-code"> </span><span class="hl-identifier">in</span><span class="hl-code"> </span><span class="hl-identifier">InputBuffer</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> += </span><span class="hl-identifier">sample</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> /= </span><span class="hl-identifier">InputBuffer</span><span class="hl-code">.</span><span class="hl-identifier">Length</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>You can feel when a game does this excessively when you quickly move the controller back and forth, and the cursor or camera feels like it's making smaller, softer movements in response. It's a similar feeling to input lag, but it's not quite the same.</p> <p>It feels bad. By spreading the consequences of a single input over multiple frames, the game takes longer to completely consume a given input, making it feel less responsive. By having a single simulation frame consider multiple frames of input, it necessarily dilutes the <em>current</em> frame's input. The game should be as responsive and quick as the player. It should be as agile as the player. It should be as shaky and imprecise as the player.</p> <p>It feels bad with a mouse, and it feels equally bad with gyro controls. While smoothing is a useful way to stabilise input when it's <em>small</em>, it should <em>never</em> be applied to anything above very small inputs.</p> <p>A game that has a smoothing filter &#8212; a rolling average &#8212; won't necessarily weigh each sample the same like this one does. It still shouldn't be done.</p> <div class="alert alert-danger"> <p><strong>Don't</strong> force players to have any smoothing at all applied to their input. Smoothing makes the game less responsive to inputs, and makes your controls feel sloppy and imprecise, so any smoothing should be optional.</p> </div> <p>But with small inputs, this sloppiness is far less noticeable. It's also only with small inputs that smoothing may add any value &#8212; the player may be intending to keep the cursor or aimer still or nearly still, and some smoothing helps the player achieve that.</p> <p>So here's what we do. We pick two thresholds. When the magnitude of the input is at or below the first threshold, we use the smoothing function in full. When it's at or above the second threshold, we have no smoothing at all. If it's in-between, apply some of the input through the smoothing filter and some of it directly, accordingly. Note that even when the input is large enough that we're not applying any smoothing at all, we still push zeroes into the smoothing function and use what comes out of it. I'll go into why the tiered smoothing function looks like this <a href="http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing">in another post</a>, but here are the key reasons we do it like this:</p> <ol> <li>No smoothing at all is applied to inputs greater than a small threshold, and</li> <li>No matter what that threshold is, the smoothed and raw inputs are combined in such a way that the final displacement is as if no input was smoothed at all.</li> </ol> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">GetTieredSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold1</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold2</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">Sqrt</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> * </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> + </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> * </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">threshold2</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-identifier">Clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">directWeight</span><span class="hl-code">, </span><span class="hl-number">0.0</span><span class="hl-code">, </span><span class="hl-number">1.0</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">))</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Now, we don't want to over-complicate the game's settings. We have three settings here for one very subtle effect: The first threshold, the second threshold, and the length of the input buffer (which determines how big the rolling average window is). So let's reduce this to <em>one</em> setting: the higher threshold.</p> <p>For the player, this is the gyro speed (in degrees per second) at which smoothing is no longer applied. A suitable lower threshold can be calculated by the game from the higher threshold, and the length of the input buffer for smoothing can be chosen by the developer. This still leaves the player a lot of control over how much of an effect smoothing will have just by setting the higher threshold.</p> <p>In JoyShockMapper, the higher threshold is set through the GYRO_SMOOTH_THRESHOLD setting, and it'll automatically choose a lower threshold of half of the GYRO_SMOOTH_THRESHOLD. I've found this to work really well. In JoyShockMapper the user also has control over the number of samples in the smoothing window. Setting GYRO_SMOOTH_TIME sets the smoothing window size in seconds, and then the required number of samples is calculated to match that time. For a real game, it might be best for the developer to choose a smoothing window big enough for most scenarios and just expose the higher smoothing threshold to the player in the game settings. However, I won't try and stop you from exposing all three variables to the player if you want to give the player as much power to customise their controls as possible.</p> <p>I tend to not use it in 3D games because acceleration is enough for me to keep things steady, but JoyShockMapper's sample 2D mouse configuration has a smooth threshold of 5 degrees-per-second.</p> <div class="alert alert-success"> <p><strong>Do</strong> have the option of tiered smoothing. Some players like to play with really high sensitivity settings (allow for that, too!), and tiered smoothing can soften the effects of shaky hands while getting out of the way for big, intentional movements. If you want to keep things simple, it can be reduced to a single setting, with other parameters chosen by the developer or calculated based on that threshold.</p> </div> <h3><span>Minimum velocity</span></h3> <p>A common method for eliminating small wiggles and wanderings of the cursor is to eliminate any movement below a velocity threshold. At first blush this seems a very effective way of stabilising the cursor. Just like with a real mouse, which is kept steady by resting on the mousepad beneath it, the cursor stays still when the player isn't intentionally moving the controller.</p> <p>But here's the deal. A minimum threshold will also keep the cursor still when the player <em>is</em> intentionally moving the controller. I've been extremely frustrated playing a game on Switch, trying to track a distant target, and with my sensitivity settings, the target's on-screen velocity was below the game's minimum velocity threshold. It was <em>literally impossible</em> to move the aimer at the same rate as my on-screen target. This was infuriating.</p> <p>So I added a minimum velocity threshold to JoyShockMapper so I could experiment with it, cranking up the sensitivity so that the cursor was actually moving around when I held the controller &quot;still&quot;. I picked a very low threshold at which all cursor movement stopped. I found that even tracking targets moving a little above the minimum velocity threshold, the inconsistency of my movements regularly dipped below the threshold, stopping all my movement until my input velocity bobbed above the threshold again. Even if this wasn't a problem, being unable to move the cursor below a certain velocity should be a deal-breaker if there's any possibility the player may reasonably desire to move the cursor at that rate.</p> <p>So I tried a soft transition with two thresholds. Below the lower threshold, input is ignored. Above the second threshold, input is consumed as normal. But between the two thresholds, input is scaled from 0% at the lower threshold to 100% at the higher threshold. This makes it technically possible for the player to move the cursor at any velocity. In practice, however, it proved a very small scale over which to accelerate the input, and it was still virtually impossible to comfortably move the cursor as slow as I'd like to. I could mitigate this by increasing the second threshold, but I disliked having a wide range over which input scaling was less intuitive than normal.</p> <p>The conclusion I came to as I explored the possibilities more and more is that a minimum velocity threshold is <em>awful</em>. Just terrible. Here's the simple version. Either:</p> <ul> <li>the threshold makes small or slow intentional inputs <em>very</em> difficult, or</li> <li>if you've fine-tuned it enough to entirely avoid that problem, it's because the threshold is so low that it might as well not be there.</li> </ul> <p>So don't do it. Really, don't do it. And if you are really sure you want to do it, <em>please</em> make it optional, so I can disable it when playing your game.</p> <div class="alert alert-danger"> <p><strong>Don't</strong> have a cutoff for small inputs. Especially in 3D games where a player could be following a target any distance away, moving at any speed in screen space. Players <em>will</em> encounter your cutoff, and it feels awful. A hard cutoff is unnecessary.</p> </div> <p>But let's have another look at that two-thresholds solution JoyShockMapper has. If we set the first velocity threshold to zero &#8212; <em>no cutoff</em> &#8212; the second threshold is still useful for steadying the cursor when the controller is held relatively still. When the first threshold is zero or truly negligible, the second threshold can be kept very small and still be easy to use over that tiny range. It's effectively just another layer of input acceleration (as described earlier), with a zero multiplier at zero input, scaling up to a one multiplier at the threshold.</p> <p>You can try it out in JoyShockMapper by setting GYRO_CUTOFF_RECOVERY to the velocity at which all input should be restored (in real world degrees per second), while leaving GYRO_CUTOFF_SPEED at its default value of 0. Like smoothing, I tend to not use it in 3D games because acceleration is enough for me, but JoyShockMapper's sample 2D mouse configuration has a GYRO_CUTOFF_RECOVERY of 5 degrees-per-second.</p> <p>It's no longer a cutoff, but a threshold below which most inputs are squeezed towards zero, so let's call it &quot;tightening&quot;. It's really simple:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GetTightenedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">Sqrt</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> * </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Yaw</span><span class="hl-code"> + </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-code"> * </span><span class="hl-identifier">input</span><span class="hl-code">.</span><span class="hl-identifier">Pitch</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> &lt; </span><span class="hl-identifier">threshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputScale</span><span class="hl-code"> = </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> / </span><span class="hl-identifier">threshold</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-identifier">inputScale</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <div class="alert alert-success"> <p><strong>Do</strong> have a small threshold below which inputs are scaled down to zero as the input velocity gets closer to zero, if more stabilising is needed. As far as I'm concerned, this &quot;tightening threshold&quot; is an acceptable alternative to a minimum velocity threshold.</p> </div> <h2><span>Lifting the mouse</span></h2> <p>A mouse translates real-world motion to a related motion in-game. This is really useful. But it has limitations. In the real world, I'm looking at a screen in a fixed position in front of me, moving a mouse across a mousepad that's probably less than 30cm from one side to the other. In the game I'm playing, I need to be able to look behind me at a moment's notice or move across an enormous scrolling map. Even if all I'm doing is moving a cursor across a static, screen-sized space, I may find myself needing to move the mouse further than there is room on the mousepad. Thankfully, I can <em>lift up the mouse</em>, it stops telling the game or application how it's moving, and I can put it in a more comfortable position where I have room to move it some more.</p> <p>Imagine if the mouse was stuck to the mousepad. It could move across the mousepad just as easily, but there was no way to lift it off to reposition. Can you imagine how frustrating that would be? If I run out of space on the mousepad to the right there's very little that can be done to get more room to move. Perhaps moving the mouse left until it hits a virtual boundary and moving the physical mouse even further could get me more room on the right. If I have mouse acceleration, maybe strategically moving the mouse fast and slow until my cursor and physical mouse are both where I want them.</p> <p>As a player, I'm going to crank up the sensitivity so that I have more in-game range of movement before hitting my real-world limits. Yes, it'll cost more precision than I'd like, but that range of movement is really important if I can't lift the mouse off the mousepad.</p> <p>Of course, game developers would offer a better solution. If the mouse can't be temporarily ignored in hardware, do it in software. If the mouse has an extra button, make it so that the game ignores mouse movements while that button is pressed. Most people only have 2- or 3-button mice, but we can work around that with relatively little difficulty &#8212; in how many games or applications do you right-click and drag at the same time? By having gyro disabled when the right mouse button is pressed and the regular right-click action only occur when the mouse isn't dragged while clicking (or only when the right mouse button is tapped), developers can provide players a pretty good solution.</p> <p>Now let's bring it back to gyro.</p> <p>While the gyro doesn't need a physical surface to function, it's still limited to the comfortable turning range of the player. If there's no way to disable the gyro so the controller can be returned to a comfortable position, players will frequently encounter uncomfortable situations where they can't really turn any further even though they need to. In 3D games, the right thumbstick is controlling the camera, too, but taking over with the stick isn't good enough &#8212; the controller is still in an uncomfortable position, and moving it back to a comfortable one will undo much of the in-game turning that put the player in that position in the first place.</p> <p>Frequently, players will crank up their sensitivity to something ridiculous so they're less likely to hit their physical limits during an encounter. But this, of course, costs the player in precision and amplifies noise and shakiness.</p> <p>So I recommend three solutions, all of which you can try out using JoyShockMapper. While the first is best if you're able to do it, providing the third option as well would be ideal. They all boil down to letting the player choose a button or input to disable the gyro:</p> <ol> <li>A dedicated button would be best, and let the player choose which button. A d-pad choice might seem easiest to add to your game without occupying more important buttons, but is also almost useless as the player would have a hard time moving at the same time. I'd usually recommend a face button or shoulder/trigger. In JoyShockMapper, the user can choose their button and decide whether it's a &quot;gyro off&quot; or &quot;gyro on&quot; button.</li> <li>A shared button. Like the right-click example for mouse, if you have a button that is used infrequently and can work as a toggle (in 3D games, tap to change between crouching and standing would be an example), having the gyro disabled while that button is pressed can work really well. I frequently do this with JoyShockMapper when playing games that really don't have any buttons to spare. Make sure to disable gyro the moment the button is pressed, even if it might end up being just a tap &#8212; a brief interruption while tapping the button isn't a big deal, but a delay while holding the button is.</li> <li>For 3D camera-turning games, have the option to disable gyro while the right stick is being used. While it's ideal to be able to use them both simultaneously, this makes the right stick far more useful for controlling the camera while repositioning the gyro.</li> </ol> <p>All of these are immediately useful, but can take a little practice to become second nature. If you remember what it was like using a mouse for the first time, you know that's okay. And as more games add these options, a player experienced with them in one game can easily pick them up in another.</p> <p>In this example, I have the Circle button as my gyro-off button. This is normally used for crouch in <em>Overwatch</em>, but toggling it by tapping the button can do the trick:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/1e8930c09bbb47758b763ab651bda35d24094fd7-3479183691313108250" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>I've avoided using the stick to turn the camera here to focus on disabling the gyro. However, the right stick still works as you'd expect, and can be used in combo with gyro aiming. In <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">Part 2</a></strong> we'll look at an alternative use of the right stick that has some unique advantages, but for now we can take advantage of the fact that experienced players, at least, are already familiar with stick aiming.</p> <div class="alert alert-success"> <p><strong>Do</strong> give the player the option to disable the gyro while a button is held or a stick is used. A real mouse would be almost unusable if the user couldn't lift it off the mousepad and reposition it without affecting the game they're playing. If your game only sometimes uses the gyro anyway (only while aiming a weapon, for example), you're already effectively doing this, but 2D cursor games and many shooters are often &quot;always on&quot;, so allow users the option to disable it when:</p> <ul> <li>Pressing a button, even if it overlaps with another action;</li> <li>While the right stick is being used</li> </ul> </div> <h2><span>Aim Assist</span></h2> <p>This one's really easy:</p> <div class="alert alert-danger"> <p><strong>Don't</strong> have aim assist. You don't have aim assist with a mouse, so don't do it with gyro. If aim assist is optional in your game, great. If it's not, disable it when gyro aiming is enabled.</p> </div> <h2><span>Latency</span></h2> <p>Like with a regular mouse, the feel of gyro controls is more sensitive to latency than button and key inputs. That's not to say they have <em>more</em> lag, but to say that lag is more noticeable with them. These aren't specific to gyro controls, but here are some helpful resources on input latency/lag in games:</p> <ul> <li>John Carmack wrote about <a href="https://danluu.com/latency-mitigation/" target="_blank">some strategies for reducing input lag</a> with VR in mind, but these apply to non-VR games, too.</li> <li>Akimitsu Hogge's GDC 2019 session <a href="https://www.gdcvault.com/play/1026327/Controller-to-Display-Latency-in" target="_blank">Controller to Display Latency in 'Call of Duty'</a> goes into great detail on throttling to reduce input lag (a practical and thorough presentation on Activision's implementation of what appears to me to be what Carmack is describing in the other article under &quot;Late frame scheduling&quot;).</li> </ul> <p>Of course, changes like these late in a project are far more difficult and far more likely to have unintended consequences than when a project is just starting. Non-developers reading this: I would generally not expect such changes to be patched into a game unless the team were making a concerted effort to deal with what was deemed a big latency issue.</p> <h2><span>Accessibility</span></h2> <p>One of the challenges of game design is choosing default settings. Sometimes there's a tension between controls that are easier for new players to learn and controls that will benefit experienced players the most in high level play. Usually, we'll favour the new player, because every experienced player starts off as an inexperienced player. Also, more experienced players will be more invested in the game and more happy to fine-tune their settings.</p> <p>For games that really depend on a mouse, like real-time strategy games or dota-likes, gyro being enabled by default is a no-brainer.</p> <p>But what about for genres already well-established on consoles that still benefit from gyro controls, like shooters and other action games? In my experience showing these controls to others, there's no tension here. Gyro controls are both <em>easier</em> for new players, and have <em>more room for mastery</em>. That is to say, whether you're brand new to this kind of game or a pro looking to eke the most out of your controls, the gyro is your best bet.</p> <div class="alert alert-success"> <p><strong>Do</strong> have gyro controls enabled by default. This is an especially big ask of those patching gyro controls into an already established game. However, if you're brave enough, <em>and</em> have adequate tutorials in place, you could have gyro controls enabled by default for new players. But it's best not to change the controls for players who are already playing your game &#8212; allow them to opt in.</p> </div> <p>The only thing that makes this tricky is that thumbstick aiming has become second nature to those who've put in enough time with it. Mouse and gyro still require <em>some</em> learning, and the discomfort of playing with something <em>new</em> when you could play with what you <em>already know</em> can put some players off sticking to mouse or gyro aiming &#8212; perhaps even if they already play better with the mouse or gyro.</p> <p>And that's okay! As long as they're enjoying the game! And here's the thing &#8212; some players will legitimately continue to be more comfortable thumbstick controls no matter how much effort they put into learning mouse or gyro controls. People come to gaming with a variety of different disadvantages of their own. Injuries and disabilities can prevent players from being able to enjoy certain controls regardless of how typical players would fare.</p> <p>That's all to say: don't abandon thumbstick aiming entirely. Allow players the option. Shooters and the like on PC still tend to support controllers, even though the vast majority of players are better off with a mouse, and this is good! If and when gyro controls become the standard for playing these kinds of games on console, players should still have the option to play these games with just sticks.</p> <div class="alert alert-success"> <p><strong>Do</strong> continue to support thumbstick-only aiming in games that benefit from gyro, as an option. Some players have unusual accessibility needs, and may reasonably prefer the thumbstick.</p> </div> <h1><span>Summary</span></h1> <p>If we bring it all together, it looks something like this.</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-comment">// current velocity, uncalibrated</span><span class="hl-code"> </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">Velocity</span><span class="hl-code">; </span><span class="hl-comment">// for calibration</span><span class="hl-code"> </span><span class="hl-types">int</span><span class="hl-code"> </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code">; </span><span class="hl-identifier">Vec3</span><span class="hl-code"> </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code">; </span><span class="hl-identifier">bool</span><span class="hl-code"> </span><span class="hl-identifier">Calibrating</span><span class="hl-code">; </span><span class="hl-comment">//...</span><span class="hl-code"> </span><span class="hl-comment">// settings</span><span class="hl-code"> </span><span class="hl-identifier">bool</span><span class="hl-code"> </span><span class="hl-identifier">GyroEnabled</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">Sensitivity</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">SmoothingThreshold</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">TighteningThreshold</span><span class="hl-code">; </span><span class="hl-identifier">bool</span><span class="hl-code"> </span><span class="hl-identifier">EnableAcceleration</span><span class="hl-code">; </span><span class="hl-comment">// only show the following when acceleration is enabled</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">SlowSensitivity</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">FastSensitivity</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">SlowThreshold</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">FastThreshold</span><span class="hl-code">; </span><span class="hl-comment">//...</span><span class="hl-code"> </span><span class="hl-comment">// Here's our main input processing function</span><span class="hl-code"> </span><span class="hl-identifier">ProcessInput</span><span class="hl-brackets">(</span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">deltaTime</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">Calibrating</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">NumOffsetSamples</span><span class="hl-code">++; </span><span class="hl-identifier">AccumulatedOffset</span><span class="hl-code"> += </span><span class="hl-identifier">Velocity</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// the player can disable the gyro in the settings or</span><span class="hl-code"> </span><span class="hl-comment">// disable the gyro while using a chosen button or stick</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-code">!</span><span class="hl-identifier">GyroEnabled</span><span class="hl-code"> || </span><span class="hl-identifier">GyroOffInputActive</span><span class="hl-brackets">())</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// we're using gyro as a mouse, only need 2D input</span><span class="hl-code"> </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">gyroVelocity</span><span class="hl-code"> = </span><span class="hl-identifier">Velocity</span><span class="hl-code">.</span><span class="hl-identifier">XY</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">gyroCalibration</span><span class="hl-code"> = </span><span class="hl-identifier">GetCalibrationOffset</span><span class="hl-brackets">()</span><span class="hl-code">.</span><span class="hl-identifier">XY</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code"> = </span><span class="hl-identifier">gyroVelocity</span><span class="hl-code"> - </span><span class="hl-identifier">gyroCalibration</span><span class="hl-code">; </span><span class="hl-comment">// smoothing</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">SmoothingThreshold</span><span class="hl-code"> &gt; </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code"> = </span><span class="hl-identifier">GetTieredSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-identifier">SmoothingThreshold</span><span class="hl-code"> / </span><span class="hl-number">2</span><span class="hl-code">, </span><span class="hl-identifier">SmoothingThreshold</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// tightening</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">TighteningThreshold</span><span class="hl-code"> &gt; </span><span class="hl-number">0</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">calibratedGyro</span><span class="hl-code"> = </span><span class="hl-identifier">GetTightenedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-identifier">TighteningThreshold</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-comment">// acceleration</span><span class="hl-code"> </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">EnableAcceleration</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">ProcessGyroAcceleration</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-identifier">SlowSensitivity</span><span class="hl-code">, </span><span class="hl-identifier">FastSensitivity</span><span class="hl-code">, </span><span class="hl-identifier">SlowThreshold</span><span class="hl-code">, </span><span class="hl-identifier">FastThreshold</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">ProcessGyroInput</span><span class="hl-brackets">(</span><span class="hl-identifier">calibratedGyro</span><span class="hl-code">, </span><span class="hl-identifier">deltaTime</span><span class="hl-code">, </span><span class="hl-identifier">Sensitivity</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>Our settings are simple, although acceleration has a lot of variables. As discussed in its section, this can be hidden behind an &quot;advanced&quot; or &quot;custom&quot; setting to avoid cluttering the settings or overwhelming the player. We do acceleration or regular gyro last &#8212; we treat smoothing and tightening as filters on the input, while the acceleration or core function converts that input into its in-game result.</p> <p>These things work equally well with 2D and 3D games, except that 3D games are benefited by the natural sensitivity scale. The gyro is a mouse, and games should treat it as such. For years and years we've been using tiny joysticks for things that a mouse is much better at, and for at least as long as the PS4 has been around (since 2013!) we've essentially had a frictionless mouse built-in to the controller. <em>Let's use it</em>.</p> <p>My video examples above were all from 3D camera-control games, but all these settings work equally well in 2D games. Here I'm playing <em>Osu!</em> with gyro controls using <a href="http://gyrowiki.wikidot.com/game-config:osu:jibb-s-osu">these settings from the Games Database</a>:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-1:the-gyro-is-a-mouse/html/5589cdc308854f1801cbfd2d102fc0724facc402-15110912261049547426" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>I haven't had a lot of time with it, but <em>Osu!</em> is basically unplayable with thumbsticks. It's free, too, so give it a go!</p> <h1><span>Coming soon</span></h1> <p>We've looked at gyro controls simple enough to patch into an already published game on the PS4 or Switch. They can make games that are traditionally only on PC or touch devices viable on these platforms, too. Next, in parts 2 and 3, we'll look at taking things to the next level in 3D camera-controlling games.</p> <p>Now, in <strong><a href="http://gyrowiki.wikidot.com/blog:good-gyro-controls-part-2:the-flick-stick">Part 2</a></strong> let's look at <em>flick stick</em>. Flick stick is a feature of JoyShockMapper specifically for 3D games that gives players an edge over using a traditional mouse by allowing players to turn to any direction quickly and easily.</p> <p>In <strong>Part 3</strong> we'll go beyond using the gyro as <em>just</em> a mouse. Why lock the aimer to the centre of the camera when we have a gyro <em>and</em> a right stick? This goes beyond what JoyShockMapper can do, since it can't be faked with just mouse inputs. I reckon these will be <em>game-defining</em> gyro controls.</p> <p>Keep an eye out for part 3 when I finish writing it.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing</guid>
				<title>Tight and Smooth: Soft Tiered Smoothing</title>
				<link>http://gyrowiki.wikidot.com/blog:tight-and-smooth:soft-tiered-smoothing</link>
				<description>

&lt;p&gt;There are a variety of reasons one might smooth out player input. With JoyShockMapper, flick-stick and gyro are both inputs that may benefit from some smoothing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flick-stick&lt;/strong&gt;, because the DualShock 4&#039;s analog stick input is too low-resolution for players to be able to fine-tune the angle they&#039;re facing. While flick-stick isn&#039;t widely used in games yet, this is equally true of top-down twin-stick shooters, where the coarseness of the stick input is amplified when trying to aim at targets far away from the player.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gyro&lt;/strong&gt;, because players have shaky hands. The gyro signal is a &lt;em&gt;little&lt;/em&gt; noisy, but the noise is almost nothing compared to the shakiness of the player&#039;s hands. If you deal with player hand shakiness, you&#039;ve dealt with the noise as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For both of these inputs, we value directness and responsiveness. The player is able to indicate their intent in an instant, and the game should respond accordingly. Games will frequently smooth out inputs like these. That means rather than using the last received input directly, they&#039;ll use the average of the last, say, 16 received inputs. This necessarily reduces responsiveness. While the game still begins responding immediately to player input, it hasn&#039;t finished dealing with that input until a number of samples after it has finished (16, in this example). This makes the game feel sloppy and imprecise.&lt;/p&gt;
&lt;p&gt;If we do no smoothing and take reasonable measures to minimise input lag, the game will feel much more responsive. However, for a number of reasons, small adjustments by the player can be overcome by unwanted artifacts (whether it&#039;s a real, unintended input like player hand shakiness, or a hardware limitation like noticeable input aliasing due to low resolution stick input). Smoothing is a simple way to reduce the effects of these artifacts.&lt;/p&gt;
&lt;p&gt;So naturally, we want small inputs to be smoothed while bigger inputs are not smoothed.&lt;/p&gt;
&lt;p&gt;Let&#039;s start with our unsmoothed code:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;hl-main&quot;&gt;
&lt;pre&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetDirectInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Simple, right? Our naive smoothing code could look something like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;hl-main&quot;&gt;
&lt;pre&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputBuffer&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;currentInputIndex&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;currentInputIndex&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;currentInputIndex&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; + &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; % &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputBuffer&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;currentInputIndex&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
    &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;average&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Zero&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;.16&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;average&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; += &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputBuffer&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;average&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; /= &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
    &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;average&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;&lt;span&gt;A simple threshold&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;But we want smoothing to apply only to small inputs. Let&#039;s introduce a threshold and see if we encounter any problems:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;hl-main&quot;&gt;
&lt;pre&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetThresholdSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-comment&quot;&gt;// this will be length(input) for vectors&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputMagnitude&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Abs&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
    &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputMagnitude&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &amp;lt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetDirectInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; +
            &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Zero&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that we still call GetSmoothedInput when we no longer want smoothing. This is because there may be input that hasn&#039;t been completely consumed (sampled 16 times and then discarded). If we don&#039;t do this and move from input below the threshold to input greater than or equal to the threshold, some of the below-threshold inputs sit in the smoothing inputBuffer until input falls below the threshold again. We still actually want to finish processing a player&#039;s input within 16 samples, but the current frame&#039;s input is already accounted for by GetDirectInput, so we just pass 0 to the smoothing function.&lt;/p&gt;
&lt;p&gt;Now, as you might&#039;ve guessed this is (slightly) too simple a solution. We need to consider what happens when input is near the threshold.&lt;/p&gt;
&lt;p&gt;Consider, for example, an input that oscillates between just above and just below the threshold. Let&#039;s say that this isn&#039;t an unlikely input, either &amp;#8212; if input doesn&#039;t vary relatively quickly, smoothing isn&#039;t going to do much, and if we expect the input to sometimes be much larger than the threshold and sometimes smaller, we might also expect it to sometimes be approximately at the threshold. So, the input stream is about 50% of the time just above the threshold and 50% of the time just below. What happens?&lt;/p&gt;
&lt;p&gt;The inputs below the threshold averaged with the zeroes when input is above the threshold give us an output of about half the threshold value. But when the input is above the threshold, we get the full input plus the smoothed part, which will be about 1.5 times the threshold value. So, when we&#039;re close to the threshold value, small noise can explode into much bigger noise. This is no good!&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Soft tiered smoothing&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Another solution I explored was that when the input is greater than or equal to the threshold, the threshold is subtracted from the input and put in the smoothing function. The leftover input is handled by GetDirectInput and consumed in full immediately.&lt;/p&gt;
&lt;p&gt;This is pretty good. We get smoothing below the threshold, more responsive input above the threshold, and no surprises around the threshold. But while the smoothed component becomes almost negligible with large inputs, it still adds a touch of unnecessary softness to big, intentional movements. So my preferred solution gradually contributes less to the smoothing component as we move further above the threshold, to the point where, at a second threshold, all input is consumed by GetDirectInput.&lt;/p&gt;
&lt;p&gt;In code, this is even simpler than it sounds. We map the magnitude of the input to a directWeight, where if the input magnitude is greater than or equal to the larger threshold, directWeight is 1.0. If it&#039;s less than or equal to the smaller threshold, directWeight is 0.0. In between we move linearly from 0.0 to 1.0. Then directWeight is used to split the direct input from the smoothed input:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;hl-main&quot;&gt;
&lt;pre&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetSoftTieredSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Vec2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold1&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold2&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-comment&quot;&gt;// this will be length(input) for vectors&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputMagnitude&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;Abs&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
    &lt;/span&gt;&lt;span class=&quot;hl-types&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;directWeight&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;inputMagnitude&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; - &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold1&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; / &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold2&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; - &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;threshold1&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
    &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;directWeight&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; = &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;clamp&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;directWeight&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
 
    &lt;/span&gt;&lt;span class=&quot;hl-reserved&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetDirectInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; * &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;directWeight&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; +
        &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;GetSmoothedInput&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; * &lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;hl-number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt; - &lt;/span&gt;&lt;span class=&quot;hl-identifier&quot;&gt;directWeight&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;hl-code&quot;&gt;;
&lt;/span&gt;&lt;span class=&quot;hl-brackets&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You may have noticed that as threshold2 approaches threshold1, we effectively have our first GetTieredSmoothedInput (after we&#039;ve accounted for the divide by zero). So, some discernment is required when choosing thresholds.&lt;/p&gt;
&lt;p&gt;I&#039;ve been happy with having threshold2 be automatically set to double threshold1. If threshold1 is large enough to be effectively smoothing the kinds of input we&#039;re expecting, threshold2 should provide a big enough buffer to smooth out any noisy inputs that warrant smoothing.&lt;/p&gt;
&lt;p&gt;In practice, I&#039;m usually more interested in threshold2 than threshold1. I want to be setting the threshold at which there is no more smoothing, so I usually actually only directly set threshold2, and have threshold 1 automatically set to half of that. This is how the smoothing in JoyShockMapper works (which the user can set using GYRO_SMOOTH_THRESHOLD).&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;What about uniform matching?&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;A common method for smoothing the transition between using two different functions depending on the input is the &lt;em&gt;uniform method&lt;/em&gt;. It&#039;s similar in concept to the method above, but instead of splitting the input between two functions, we give the same input to both functions and interpolate between the outputs.&lt;/p&gt;
&lt;p&gt;Why not do that here? It&#039;s slightly simpler and works nicely regardless of what functions we&#039;re transitioning between. However, when smoothing a movement, we want the same input to get us to the same destination, regardless of smoothing settings. The simple rolling average smoothing ultimately has the same integral (displacement) as the raw input, but the smoothed input takes a little longer to get there. While the smoothing function might not be done with a given input, the uniform method may begin ignoring the smoothing function&#039;s output when the current input is big enough. This means we lose some displacement. This means less consistency and precision for the player.&lt;/p&gt;
&lt;p&gt;Another problem is that the smoothing buffer will be loaded with big values when the input is large. We won&#039;t see the effects of that until input stops or goes below the top threshold. Once that happens, the smoothed out big input will continue to come out of the smoothing function until the smoothing window has passed.&lt;/p&gt;
&lt;p&gt;My soft-tiered method doesn&#039;t have either problem. Even when the current sample&#039;s input is large enough that it&#039;s all being consumed immediately, leftover smoothed input still gets added on top until it&#039;s all consumed, and nothing else is added to the smoothing buffer. Apart from rounding errors, we&#039;ll get the same final displacement regardless of our smoothing settings.&lt;/p&gt;
&lt;h2&gt;&lt;span&gt;Multiple tiers&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Of course, this is just a specific kind of multi-tiered smoothing. Our example has one tier with a buffer size of 16 (that is, it averages out 16 samples) and another tier with a buffer size of 1.&lt;/p&gt;
&lt;p&gt;If we really wanted to, there&#039;s nothing stopping us from having more than two tiers, each with their own starting threshold. Each frame, find the two tiers that the current input should contribute to, divide it between the two tiers appropriately, and add zero input to all the other tiers.&lt;/p&gt;
&lt;p&gt;For my purposes, though, where smoothing is only useful for a very small range of input (if that &amp;#8212; in my own configurations I often have no smoothing on gyro input at all), the specialised two-tiered smoothing above does the trick just fine.&lt;/p&gt;
&lt;p&gt;Acknowledging the detrimental effects of smoothing and having it only apply when its pros outweigh its cons goes a long way to improving the user experience. For both gyro input and flick stick, JoyShockMapper users are able to enjoy smooth and yet responsive interactions in games.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Wed, 22 May 2019 15:36:20 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>There are a variety of reasons one might smooth out player input. With JoyShockMapper, flick-stick and gyro are both inputs that may benefit from some smoothing:</p> <ul> <li><strong>Flick-stick</strong>, because the DualShock 4's analog stick input is too low-resolution for players to be able to fine-tune the angle they're facing. While flick-stick isn't widely used in games yet, this is equally true of top-down twin-stick shooters, where the coarseness of the stick input is amplified when trying to aim at targets far away from the player.</li> <li><strong>Gyro</strong>, because players have shaky hands. The gyro signal is a <em>little</em> noisy, but the noise is almost nothing compared to the shakiness of the player's hands. If you deal with player hand shakiness, you've dealt with the noise as well.</li> </ul> <p>For both of these inputs, we value directness and responsiveness. The player is able to indicate their intent in an instant, and the game should respond accordingly. Games will frequently smooth out inputs like these. That means rather than using the last received input directly, they'll use the average of the last, say, 16 received inputs. This necessarily reduces responsiveness. While the game still begins responding immediately to player input, it hasn't finished dealing with that input until a number of samples after it has finished (16, in this example). This makes the game feel sloppy and imprecise.</p> <p>If we do no smoothing and take reasonable measures to minimise input lag, the game will feel much more responsive. However, for a number of reasons, small adjustments by the player can be overcome by unwanted artifacts (whether it's a real, unintended input like player hand shakiness, or a hardware limitation like noticeable input aliasing due to low resolution stick input). Smoothing is a simple way to reduce the effects of these artifacts.</p> <p>So naturally, we want small inputs to be smoothed while bigger inputs are not smoothed.</p> <p>Let's start with our unsmoothed code:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GetDirectInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>Simple, right? Our naive smoothing code could look something like this:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">inputBuffer</span><span class="hl-brackets">[</span><span class="hl-number">16</span><span class="hl-brackets">]</span><span class="hl-code">; </span><span class="hl-types">int</span><span class="hl-code"> </span><span class="hl-identifier">currentInputIndex</span><span class="hl-code">; </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">currentInputIndex</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">currentInputIndex</span><span class="hl-code"> + </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code"> % </span><span class="hl-number">16</span><span class="hl-code">; </span><span class="hl-identifier">inputBuffer</span><span class="hl-brackets">[</span><span class="hl-identifier">currentInputIndex</span><span class="hl-brackets">]</span><span class="hl-code"> = </span><span class="hl-identifier">input</span><span class="hl-code">; </span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> = </span><span class="hl-identifier">Vec2</span><span class="hl-code">.</span><span class="hl-identifier">Zero</span><span class="hl-code">; </span><span class="hl-reserved">for</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">i</span><span class="hl-code"> </span><span class="hl-identifier">in</span><span class="hl-code"> </span><span class="hl-number">0</span><span class="hl-code">.</span><span class="hl-number">.16</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> += </span><span class="hl-identifier">inputBuffer</span><span class="hl-brackets">[</span><span class="hl-identifier">i</span><span class="hl-brackets">]</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code"> /= </span><span class="hl-number">16</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">average</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <h2><span>A simple threshold</span></h2> <p>But we want smoothing to apply only to small inputs. Let's introduce a threshold and see if we encounter any problems:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GetThresholdSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// this will be length(input) for vectors</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">Abs</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">if</span><span class="hl-code"> </span><span class="hl-brackets">(</span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> &lt; </span><span class="hl-identifier">threshold</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-reserved">else</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code">.</span><span class="hl-identifier">Zero</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-brackets">}</span><span class="hl-code"> </span><span class="hl-brackets">}</span></pre></div> </div> <p>Notice that we still call GetSmoothedInput when we no longer want smoothing. This is because there may be input that hasn't been completely consumed (sampled 16 times and then discarded). If we don't do this and move from input below the threshold to input greater than or equal to the threshold, some of the below-threshold inputs sit in the smoothing inputBuffer until input falls below the threshold again. We still actually want to finish processing a player's input within 16 samples, but the current frame's input is already accounted for by GetDirectInput, so we just pass 0 to the smoothing function.</p> <p>Now, as you might've guessed this is (slightly) too simple a solution. We need to consider what happens when input is near the threshold.</p> <p>Consider, for example, an input that oscillates between just above and just below the threshold. Let's say that this isn't an unlikely input, either &#8212; if input doesn't vary relatively quickly, smoothing isn't going to do much, and if we expect the input to sometimes be much larger than the threshold and sometimes smaller, we might also expect it to sometimes be approximately at the threshold. So, the input stream is about 50% of the time just above the threshold and 50% of the time just below. What happens?</p> <p>The inputs below the threshold averaged with the zeroes when input is above the threshold give us an output of about half the threshold value. But when the input is above the threshold, we get the full input plus the smoothed part, which will be about 1.5 times the threshold value. So, when we're close to the threshold value, small noise can explode into much bigger noise. This is no good!</p> <h2><span>Soft tiered smoothing</span></h2> <p>Another solution I explored was that when the input is greater than or equal to the threshold, the threshold is subtracted from the input and put in the smoothing function. The leftover input is handled by GetDirectInput and consumed in full immediately.</p> <p>This is pretty good. We get smoothing below the threshold, more responsive input above the threshold, and no surprises around the threshold. But while the smoothed component becomes almost negligible with large inputs, it still adds a touch of unnecessary softness to big, intentional movements. So my preferred solution gradually contributes less to the smoothing component as we move further above the threshold, to the point where, at a second threshold, all input is consumed by GetDirectInput.</p> <p>In code, this is even simpler than it sounds. We map the magnitude of the input to a directWeight, where if the input magnitude is greater than or equal to the larger threshold, directWeight is 1.0. If it's less than or equal to the smaller threshold, directWeight is 0.0. In between we move linearly from 0.0 to 1.0. Then directWeight is used to split the direct input from the smoothed input:</p> <div class="code"> <div class="hl-main"> <pre><span class="hl-identifier">GetSoftTieredSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">Vec2</span><span class="hl-code"> </span><span class="hl-identifier">input</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold1</span><span class="hl-code">, </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">threshold2</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"> </span><span class="hl-comment">// this will be length(input) for vectors</span><span class="hl-code"> </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> = </span><span class="hl-identifier">Abs</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-types">float</span><span class="hl-code"> </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-brackets">(</span><span class="hl-identifier">inputMagnitude</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code"> / </span><span class="hl-brackets">(</span><span class="hl-identifier">threshold2</span><span class="hl-code"> - </span><span class="hl-identifier">threshold1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-identifier">directWeight</span><span class="hl-code"> = </span><span class="hl-identifier">clamp</span><span class="hl-brackets">(</span><span class="hl-identifier">directWeight</span><span class="hl-code">, </span><span class="hl-number">0</span><span class="hl-code">, </span><span class="hl-number">1</span><span class="hl-brackets">)</span><span class="hl-code">; </span><span class="hl-reserved">return</span><span class="hl-code"> </span><span class="hl-identifier">GetDirectInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">)</span><span class="hl-code"> + </span><span class="hl-identifier">GetSmoothedInput</span><span class="hl-brackets">(</span><span class="hl-identifier">input</span><span class="hl-code"> * </span><span class="hl-brackets">(</span><span class="hl-number">1.0</span><span class="hl-code"> - </span><span class="hl-identifier">directWeight</span><span class="hl-brackets">))</span><span class="hl-code">; </span><span class="hl-brackets">}</span></pre></div> </div> <p>You may have noticed that as threshold2 approaches threshold1, we effectively have our first GetTieredSmoothedInput (after we've accounted for the divide by zero). So, some discernment is required when choosing thresholds.</p> <p>I've been happy with having threshold2 be automatically set to double threshold1. If threshold1 is large enough to be effectively smoothing the kinds of input we're expecting, threshold2 should provide a big enough buffer to smooth out any noisy inputs that warrant smoothing.</p> <p>In practice, I'm usually more interested in threshold2 than threshold1. I want to be setting the threshold at which there is no more smoothing, so I usually actually only directly set threshold2, and have threshold 1 automatically set to half of that. This is how the smoothing in JoyShockMapper works (which the user can set using GYRO_SMOOTH_THRESHOLD).</p> <h2><span>What about uniform matching?</span></h2> <p>A common method for smoothing the transition between using two different functions depending on the input is the <em>uniform method</em>. It's similar in concept to the method above, but instead of splitting the input between two functions, we give the same input to both functions and interpolate between the outputs.</p> <p>Why not do that here? It's slightly simpler and works nicely regardless of what functions we're transitioning between. However, when smoothing a movement, we want the same input to get us to the same destination, regardless of smoothing settings. The simple rolling average smoothing ultimately has the same integral (displacement) as the raw input, but the smoothed input takes a little longer to get there. While the smoothing function might not be done with a given input, the uniform method may begin ignoring the smoothing function's output when the current input is big enough. This means we lose some displacement. This means less consistency and precision for the player.</p> <p>Another problem is that the smoothing buffer will be loaded with big values when the input is large. We won't see the effects of that until input stops or goes below the top threshold. Once that happens, the smoothed out big input will continue to come out of the smoothing function until the smoothing window has passed.</p> <p>My soft-tiered method doesn't have either problem. Even when the current sample's input is large enough that it's all being consumed immediately, leftover smoothed input still gets added on top until it's all consumed, and nothing else is added to the smoothing buffer. Apart from rounding errors, we'll get the same final displacement regardless of our smoothing settings.</p> <h2><span>Multiple tiers</span></h2> <p>Of course, this is just a specific kind of multi-tiered smoothing. Our example has one tier with a buffer size of 16 (that is, it averages out 16 samples) and another tier with a buffer size of 1.</p> <p>If we really wanted to, there's nothing stopping us from having more than two tiers, each with their own starting threshold. Each frame, find the two tiers that the current input should contribute to, divide it between the two tiers appropriately, and add zero input to all the other tiers.</p> <p>For my purposes, though, where smoothing is only useful for a very small range of input (if that &#8212; in my own configurations I often have no smoothing on gyro input at all), the specialised two-tiered smoothing above does the trick just fine.</p> <p>Acknowledging the detrimental effects of smoothing and having it only apply when its pros outweigh its cons goes a long way to improving the user experience. For both gyro input and flick stick, JoyShockMapper users are able to enjoy smooth and yet responsive interactions in games.</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764114" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
					<item>
				<guid>http://gyrowiki.wikidot.com/blog:joyshockmapper-guide</guid>
				<title>How to Use JoyShockMapper</title>
				<link>http://gyrowiki.wikidot.com/blog:joyshockmapper-guide</link>
				<description>

&lt;p&gt;JoyShockMapper (JSM) converts input from PlayStation 4 controllers (DualShock 4) and Switch controllers (JoyCons and Pro Controller) to keyboard and mouse inputs. If you prefer playing games with a controller, but the games you want to play don&#039;t have all the configuration options you want, this may help. If you want to explore &lt;em&gt;flick stick&lt;/em&gt; and &lt;em&gt;gyro as a mouse&lt;/em&gt;, JSM provides everything you need for the most intuitive and precise aiming and cursor control possible with a modern console controller.&lt;/p&gt;
&lt;p&gt;by &lt;span class=&quot;printuser avatarhover&quot;&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;&lt;img class=&quot;small&quot; src=&quot;http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;amp;size=small&amp;amp;amp;timestamp=1780764114&quot; alt=&quot;JibbSmart&quot; style=&quot;background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)&quot; /&gt;&lt;/a&gt;&lt;a href=&quot;http://www.wikidot.com/user:info/jibbsmart&quot;  &gt;JibbSmart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
</description>
				<pubDate>Sat, 09 Mar 2019 08:59:39 +0000</pubDate>
												<content:encoded>
					<![CDATA[
						 <p>JoyShockMapper (JSM) converts input from PlayStation 4 controllers (DualShock 4) and Switch controllers (JoyCons and Pro Controller) to keyboard and mouse inputs. If you prefer playing games with a controller, but the games you want to play don't have all the configuration options you want, this may help. If you want to explore <em>flick stick</em> and <em>gyro as a mouse</em>, JSM provides everything you need for the most intuitive and precise aiming and cursor control possible with a modern console controller.</p> <p>The goal of this post is to help you get using JSM quickly, without having to know everything about how it works. If you're in a real hurry, here's the two minute version:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/44f0400a9b7cd28cafa1db503fa1a0940bba222f-1692408833816602906" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>For a comprehensive list of all of JSM's features, check out its <a href="https://github.com/Electronicks/JoyShockMapper#commands">README</a>.</p> <p>This article is split up into four sections, starting with what everyone needs and ending with what only advanced users will need:</p> <ol> <li><a href="#installing">Installing JSM</a></li> <li><a href="#already-made">Using an already-made configuration file</a></li> <li><a href="#gyrowiki">Using calibration info from GyroWiki to make your own configuration file</a></li> <li><a href="#calibrating">Calibrating JSM (and sharing on GyroWiki)</a></li> </ol> <p>Every step will rely on the steps before it, but if all you want is to use an already-made configuration file (for example), you'll only need to follow a small portion of this How-to. You can find a bunch of already-made <em>community configs</em> in the <a href="http://gyrowiki.wikidot.com/games">GyroWiki games database</a>.</p> <p>A comprehensive explanation of all of JSM's features can be found in its <a href="https://github.com/Electronicks/JoyShockMapper#commands">README</a>, but a new user will find that more useful <em>after</em> experimenting with JSM's features covered in the next few sections. Using an already-made config is easier than creating one from scratch and is a good way to get familiar with how JSM works. JSM comes with a simple config you can use for basic interaction with Windows and simple Windows applications (such as <em>Minesweeper</em> and <em>Solitaire</em>) called 'Windows.txt', and it'll be used as an example below.</p> <h1><span><a name="installing"></a>Installing JSM</span></h1> <p>JoyShockMapper doesn't come with an installer. Just download the <a href="https://github.com/Electronicks/JoyShockMapper/releases">latest release from here</a>, extract it to a folder, and run JoyShockMapper.exe. It should look something like this:</p> <img src="https://i.imgur.com/5WI5oF3.png" alt="5WI5oF3.png" class="image" /> <p>Notice it says &quot;0 devices connected&quot;. I forgot to connect my controllers before starting the application. But I can fix that by plugging in my DualShock 4 or connecting my JoyCons or Pro Controller by Bluetooth, and then entering the command &quot;RECONNECT_CONTROLLERS&quot; like so:</p> <img src="https://i.imgur.com/wGbzKFK.png" alt="wGbzKFK.png" class="image" /> <p>One of the goals of JSM is to make configuration files shareable between different users on different systems, as well as between different games. By necessity, this requires <em>some</em> unique configuration on a per-game basis.</p> <p>When your mouse sends a movement to the game, it can get changed in a bunch of ways:</p> <ol> <li>it <em>may</em> be modified by your mouse settings in Windows,</li> <li>it <em>may</em> be modified by your mouse settings in the game you're playing,</li> <li>it <em>may</em> be multiplied by another number to convert the movement into the appropriate angle or cursor movement.</li> </ol> <p>If your mouse settings are relatively simple (you don't have &quot;enhance pointer precision&quot; enabled in Windows or mouse acceleration enabled in game), JSM can account for all three steps so that you can use the same settings between different users and games:</p> <ol> <li>if necessary, it can be told to detect and counter Windows' mouse settings,</li> <li>it can be given your in-game mouse settings and counter those,</li> <li>it uses a real-world calibration number to know how much to move the mouse to produce the desired in-game change.</li> </ol> <p>Number 2 will likely be different from user to user. So if your configuration does anything with the mouse (whether with the sticks or the gyro), you should always set IN_GAME_SENS to your in-game mouse speed. If your game doesn't have that option, just set it to 1.</p> <p>Numbers 1 and 3 vary depending on the game. For 1, some games ignore Windows' mouse settings (often called &quot;raw input&quot;), so JSM's command to detect and counter Windows' settings is optional. For number 3, it can be some work to actually figure out an appropriate REAL_WORLD_CALIBRATION value (<a href="#calibrating">explained below</a>).</p> <p>The <a href="http://gyrowiki.wikidot.com/games">games database</a> is a growing collection of calibration settings for games so that you don't have to figure out those settings yourself. Many of those games have <em>community configs</em> &#8212; complete configurations that'll work right away once you've set your IN_GAME_SENS.</p> <h1><span><a name="already-made"></a>Using an already-made configuration file</span></h1> <p>JSM comes with a folder called 'GyroConfigs', which is where I like to keep config files for different games handy. Your folder should already contain a couple of files to help with calibrating (covered <a href="#calibrating">later</a>), a couple of templates for creating new configs, and a file called 'Windows.txt'. Let's use it as an example of how to use someone else's config file, but you can always use configs you've found elsewhere, such as <em>community configs</em> in the <a href="http://gyrowiki.wikidot.com/games">games database</a>.</p> <p>Open the file in Notepad, and you should see something like this:</p> <div class="code"> <pre><code># Windows interaction using gyro # Clear previous settings RESET_MAPPINGS # Calibration REAL_WORLD_CALIBRATION = 5.3333 IN_GAME_SENS = 1 COUNTER_OS_MOUSE_SPEED # Button mappings GYRO_OFF = E LLEFT = LEFT LRIGHT = RIGHT LUP = UP LDOWN = DOWN R = LMOUSE L = RMOUSE + = ESC ZL = LSHIFT S = SPACE N = ESC # Include mouse settings GyroConfigs/_2Dmouse.txt</code></pre></div> <p>Every line you see in this config file is a line you can manually enter into JSM to change your settings. But it'd be tedious to have to copy out every command each time you start JSM or change game, so you can group these commands into text files and load them in JSM.</p> <p>But before we use Windows.txt for the first time, we <em>must</em> check a couple of things:</p> <ol> <li><strong>Is &quot;enhance pointer precision&quot; disabled?</strong> &quot;Enhance pointer precision&quot; is an option in Windows' pointer speed settings that makes the mouse behave in ways that JSM can't account for. If you want to have the same experience as whoever created this config file, you should disable &quot;enhance pointer precision&quot;.</li> <li><strong>Has IN_GAME_SENS been set correctly?</strong> Every time you try a new config file, the very first thing you should do is change IN_GAME_SENS to match your in game mouse speed setting. This is how JSM accounts for different people having different game settings while still being able to have the same gyro- or stick- mouse for different users. Windows.txt sets it to 1, which is the default, and is actually what we want in this case, since Windows doesn't have a separate in-game mouse speed to account for, and your Windows pointer speed is already accounted for by the command COUNTER_OS_MOUSE_SPEED. In fact, Windows.txt doesn't actually need to set IN_GAME_SENS to 1 because RESET_MAPPINGS already did that. I've just put it in there as a reminder: <em>always set IN_GAME_SENS to your in-game mouse speed</em>.</li> </ol> <p>Note: Sometimes games won't have a number next to their mouse speed setting. Sometimes the number can be found in a config file the game uses, but sometimes there's nothing that can be done, and users should just try to pick something appropriate (like using the game's default mouse speed and assuming it's '1').</p> <p>Now that we've disabled &quot;enhance pointer precision&quot; and checked IN_GAME_SENS, let's load the config file into JSM: run JSM, then either drag the config file you want to use into JSM or just type in the path relative to the program's location (in this case, GyroConfigs/Windows.txt) before hitting Enter:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/b7eff222ee4f81ffe22f19d80287e1396a7593c2-8231210661389488195" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>And that's all it takes! You should be able to move the mouse around just by moving the gyro or using the right stick.</p> <p>If your configuration uses gyro for mouse, you may need to calibrate your gyro. If you put your controller down and the mouse is still moving across the screen, it's a good idea to calibrate your gyro &#8212; this just means telling the controller what &quot;not moving&quot; looks like.</p> <p>One way to do this is to put your controller down on a steady surface, then tap the Calibrate button &#8212; this is the PS button, the Home button, or the Capture button, depending on what controller it is. If you're using a pair of JoyCons, the default behaviour is for the left gyro to be ignored, so unless you change this, don't bother with the left JoyCon.</p> <p>When you tap the Calibrate button, JoyShockMapper will either say &quot;Enabled continuous calibration&quot; or &quot;Disabled continuous calibration&quot;. If it says &quot;Disabled&quot;, tap it again to enable continuous calibration. This just means it's gathering gyro data from the controller and finding the average. After a second or longer, tap the Calibrate button again so JoyShockMapper says &quot;Disabled continuous calibration&quot;. This means it'll stop gathering gyro data for calibration, but it'll continue using the data it previously gathered. Once that's done, you should be all set:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/22d9935620085d424c9c1c744388789bd202fb70-6906122151093548446" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>A quicker way to do this can be to just press and hold the Calibrate button for a while and then release it (either on a steady surface or holding the controller very still). While holding the button it'll gather gyro data, accumulating an average. When released, it'll stop. However, pressing the Home or Capture button can interfere with the gyro on JoyCons, causing it to calibrate incorrectly, so I don't recommend using this method there.</p> <p>Finally, to calibrate all connected controllers at the same time without having to touch any of the controllers, enter the command RESTART_GYRO_CALIBRATION to begin calibration, then enter FINISH_GYRO_CALIBRATION to finish calibration. Of course, leave the controllers still on a steady surface while doing this for best results.</p> <p>As the gyro's temperature changes over time, it may need to be calibrated again, but depending on how high your sensitivity settings are, you shouldn't need to recalibrate very often. With the DualShock 4 I almost never have to calibrate it. With the Nintendo controllers I normally do it once at the beginning of a play session and maybe once again after I've warmed up a bit.</p> <h1><span><a name="gyrowiki"></a>Using calibration info from GyroWiki to make your own configuration file</span></h1> <p>To explore what it takes to make your own config file using calibration info from GyroWiki, let's compare the GyroWiki page for Windows to the contents of Windows.txt. We won't look at every possible command. For a comprehensive list of all JSM commands and how to use them, check out the <a href="https://github.com/Electronicks/JoyShockMapper#commands">Commands section of the JoyShockMapper README</a>.</p> <p>As we go through Windows.txt it'll be helpful to have the <a href="http://gyrowiki.wikidot.com/game:windows">Windows page</a> open from the <a href="http://gyrowiki.wikidot.com/games">GyroWiki games database</a> as we step through the related config file:</p> <div class="code"> <pre><code># Windows interaction using gyro</code></pre></div> <p>Any line entered into JSM that begins with '#' will be ignored. Such lines are referred to as &quot;comments&quot;. Lines that aren't commands won't change any settings, but JSM will complain about them unless they begin with '#'. Or, even worse, if your comment is also a JSM command, it may be executed without you intending it. Therefore it's best to put '#' at the beginning of comments.</p> <p>By the way, if you ever want to temporarily remove a command that you might want later, you can just put a '#' at the beginning of the command. That way, the text is still there for you to restore later, but it'll be ignored by JSM. In programming this is often called &quot;commenting out a line&quot;.</p> <div class="code"> <pre><code># Clear previous settings RESET_MAPPINGS</code></pre></div> <p>I like to use comments and empty lines to separate different related sections of configuration files. All my files begin with RESET_MAPPINGS, which is a JSM command to restore all default settings. This means unmapping all button inputs, for example. This means after this I only have to set the settings that matter for this game.</p> <p>After this, the file is divided into 3 sections:</p> <ol> <li><a href="#config_calibration">Calibration</a> - using the info from GyroWiki or calculated elsewhere to tell JSM how to translate actions into the appropriately sized mouse movement;</li> <li><a href="#config_buttons">Buttons</a> - mapping buttons and sticks to different keys or mouse buttons;</li> <li><a href="#config_mouse">Mouse</a> - mapping sticks and/or gyro to mouse movements.</li> </ol> <h2><span><a name="config_calibration"></a>Calibration</span></h2> <p>Let's look at the first section.</p> <div class="code"> <pre><code># Calibration REAL_WORLD_CALIBRATION = 5.3333 IN_GAME_SENS = 1 COUNTER_OS_MOUSE_SPEED</code></pre></div> <p>This is where the GyroWiki game info comes into play. All GyroWiki game entries boil down to 3 pieces of info, apart from some optional notes. Here's the info from <a href="http://gyrowiki.wikidot.com/game:windows">GyroWiki's entry for Windows</a>:</p> <ul> <li>Real World Calibration: 5.3333</li> <li>Mouse Control Mode: Cursor</li> <li>Raw Input: No</li> </ul> <p>The page also has explanations of what you need to do in JSM to calibrate it correctly. See how they correspond to the commands in this section of Windows.txt? It says to set REAL_WORLD_CALIBRATION to 5.3333. This will be explained in detail in the calibration section later, for those interested, but for now just know that having the right REAL_WORLD_CALIBRATION setting makes it easier to keep all your other settings the same between different games.</p> <p>It also says to use COUNTER_OS_MOUSE_SPEED. If the game in question uses raw input (that is, it isn't affected by Windows' mouse settings), you would have nothing in its place, since the default behaviour is to ignore Windows' mouse settings, and RESET_MAPPINGS (from earlier) already restored default behaviour.</p> <p>Finally, the page reminds users to always set IN_GAME_SENS to your in game mouse speed setting, if there is one. If not, just set it to 1.</p> <h2><span><a name="config_buttons"></a>Button Mapping</span></h2> <p>Now to the buttons. Let's have a look at what Windows.txt has:</p> <div class="code"> <pre><code># Button mappings GYRO_OFF = E LLEFT = LEFT LRIGHT = RIGHT LUP = UP LDOWN = DOWN R = LMOUSE L = RMOUSE + = ESC ZL = LSHIFT S = SPACE N = ESC</code></pre></div> <p>This section is where we'll handle button inputs. This can include stick inputs if you want to use the stick that way, which we'll get to in just a bit. But first, GYRO_OFF is a special command here. Most of your inputs will be set up something like [controller input] = [keyboard or mouse input], and so each controller input has one function. Enabling or disabling gyro input is special and can overlap with other input (for example, you might enable gyro while pressing the same button you use to aim down sights).</p> <p>The line GYRO_OFF = E means pressing the East face button disables the gyro. If instead you want the gyro to be <em>enabled</em> while pressing a button, use GYRO_ON instead. If you use GYRO_ON, the gyro will be disabled whenever that button is <em>not</em> pressed. This can be undone by setting GYRO_OFF again. If you want neither, but want the gyro to always be enabled, try GYRO_OFF = NONE.</p> <p>The next four lines are for the left stick. They make pressing the left stick left, right, up, or down send a keyboard key press for the left, right, up, or down arrow, respectively. After that you can see mappings for clicking the mouse (left-click, right-click), escape, left shift, spacebar, and another button for escape. You can map any number of controller inputs to the same keyboard or mouse input.</p> <p>The face button controller inputs are named after the directions on a compass, since the supported controllers don't all have the same button names. N, S, E, W for North, South, East, West, respectively. Most other buttons are named after Switch controllers, since a pair of JoyCons has more buttons than a DualShock 4. However, since the stick clicks only have short, well-known names on DualShock 4, they're L3 and R3 (for left click and right click, respectively).</p> <p>Once calibrated, the same mouse settings can usually be used between games of similar genre. While you might want to refine these more later, this means that once you have your calibration settings correct, you can focus on figuring out a good button mapping for the game you're configuring. Feel free to make little changes at a time, testing them out to see if you like them, rather than trying to create the perfect config file in one go.</p> <p>In Windows.txt I have the left stick mapped to the arrow keys on the keyboard. But many games use the WASD keys for character movement, so that's often a good place to start when creating your own config file:</p> <div class="code"> <pre><code>LLEFT = A LUP = W LRIGHT = D LDOWN = S</code></pre></div> <p>If you make these changes to Windows.txt and reload it, or put them directly into JSM, you can see this in action. Create a new file in Notepad and move the stick around, and you should see that it's responding as if those keys are being pressed:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/0bc8f61a39952f0d0bca1d86448001d405c8dff4-20619223111907981184" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>It's up to you what inputs you map to what. These will usually change a lot between different games. Here are some considerations I normally have:</p> <ul> <li>A lot of shooters have their primary and secondary fire mapped to ZR (R2) and ZL (L2), respectively, which will usually correspond to LMOUSE (left click) and RMOUSE (right click), respectively. But I prefer to use R (R1) and L (L1), because I find it easier to press them without accidentally moving the controller at the same time.</li> <li>A lot of thought goes into designing good controller controls. If the game you're configuring is available on a console, look up those controls and use them as a starting point. Or, if the game on PC natively supports a controller, use those controls as a starting point. If neither of those is the case, perhaps there are similar games that you can draw inspiration from.</li> <li>Because there are a lot more keys on a keyboard than buttons on a controller, many games that appear on both PC and consoles will combine multiple keyboard actions into fewer controller buttons. For example, you might tap the crouch button to crouch or hold it to go prone on the ground. JSM lets you map taps and holds to different inputs by putting two keyboard/mouse inputs (separated by a space) after the =. Eg: &quot;UP = SCROLLUP Q&quot; means tapping up on the d-pad will scroll the mouse wheel up, holding it will press and hold the Q key.</li> <li>Prioritise core gameplay actions. Maybe it's not feasible to fit all the core gameplay as well as a help shortcut, chat key, show score key, and shortcuts for different emotes. That's okay. It's important that you <em>can</em> access your core gameplay actions easily.</li> <li>Think about what combinations of actions you'll want to do at the same time. If you want to jump while aiming with the right stick, perhaps it's best your jump button is <em>not</em> mapped to a face button, as it's hard to press face buttons while using the right stick. How about ZL (L2) or ZR (R2) instead?</li> <li>Practice. Many games have on-screen prompts telling you what key to press. The game doesn't know the configuration you've set up through JSM, so sometimes it might tell you to press F to perform a certain action and you'll forget what controller button you mapped to the F key. But with a little extra practice, these actions will become second nature to you.</li> </ul> <h2><span><a name="config_mouse"></a>Mouse Movement Mapping</span></h2> <p>Finally, let's look at how we've set up mouse controls in Windows.txt:</p> <div class="code"> <pre><code># Include mouse settings GyroConfigs/_2Dmouse.txt</code></pre></div> <p>Remember how JSM accepts individual commands typed into the console or file names for files containing commands for it to use? Those files can also contain links to <em>other</em> files. This file name, though, must either be an absolute path, or a path relative to JoyShockMapper.exe. Because JoyShockMapper comes with a folder called GyroConfigs, it's a good place to put config files that you might reference from <em>other</em> config files.</p> <p>Because most 2D games have the same needs and most 3D games have the same needs (2D meaning the mouse controls a cursor on a 2D plane; 3D meaning the mouse moves a camera in a 3D space), I like to simplify my configs by having most 2D games share the same mouse settings and most 3D games share the same mouse settings. JoyShockMapper includes examples called '_2Dmouse.txt' and '_3Dmouse.txt', which you can use however you like. So as I experiment with gyro controls and find that there are changes I want to make to how I play 2D games, I can make those changes in one place and enjoy them in every game that uses the same config file.</p> <p>Let's look inside _2Dmouse.txt and see what's happening here:</p> <div class="code"> <pre><code># Gyro mouse setup MIN_GYRO_SENS = 8 MAX_GYRO_SENS = 16 GYRO_CUTOFF_RECOVERY = 5 MIN_GYRO_THRESHOLD = 5 MAX_GYRO_THRESHOLD = 75 GYRO_SMOOTH_THRESHOLD = 5 # In case you want to use the right stick instead RIGHT_STICK_MODE = AIM STICK_SENS = 360 STICK_POWER = 1 STICK_ACCELERATION_RATE = 2 STICK_ACCELERATION_CAP = 4</code></pre></div> <p>There's a lot to unpack here. It's split into two sections: gyro and stick. Let's start with the gyro stuff at the top.</p> <p>The first two lines are MIN_GYRO_SENS and MAX_GYRO_SENS, but the simplest way to set your gyro sensitivity is with GYRO_SENS, so I'll explain that first. GYRO_SENS is a multiplier to your gyro input when it's being converted to mouse input. This has different implications for 3D games and 2D games, both of which depend on REAL_WORLD_CALIBRATION and IN_GAME_SENS being set correctly:</p> <ul> <li><strong>3D games</strong>: GYRO_SENS is a multiplier from real world rotation to in-game rotation. That means if you set GYRO_SENS to 1 and turn your controller 37° to the left, your in-game camera will turn 37° to the left. Set it to 2 and the same movement will turn your in-game camera 74° to the left. Simple, right? Any game that uses gyro aiming should use this scale.</li> <li><strong>2D games</strong>: The reason 3D games don't all use the same sensitivity scale when converting from a 2D mouse movement to a 3D rotation is that there's no obvious conversion. The same applies when converting a 3D gyro rotation to a 2D mouse movement. By convention, JSM configs should be calibrated such that GYRO_SENS sets what <em>fraction of a full turn</em> the controller must make to move the mouse all the way from one side of the screen to the other, horizontally. For 2D games, this will often depend on screen resolution (it does with Windows, for example), so for consistency's sake, it's best to calculate it for a screen that's 1920 pixels wide. When you set GYRO_SENS to 8 for a 2D game, you're saying you want to be able to cover the whole screen within 1/8 of a full rotation (360° / 8 = 45°). If you set GYRO_SENS to 16, it'll take 1/16 of a full rotation (360° / 16 = 22.5°) to cover the whole screen.</li> </ul> <p>By the way, if you're wondering how you choose between 2D game settings and 3D game settings, you don't. All you need to do is set the right REAL_WORLD_CALIBRATION for the game and it should work fine.</p> <p>Just as with a regular mouse, choosing the right sensitivity is a balancing act between two goals that are at odds with each other. A high sensitivity (small movements translate to big movements) means that you can more easily make fast turns or move the cursor great distances, and you'll be able to do more without turning an uncomfortable angle and having to recentre yourself (or with a real mouse, lifting the mouse off the mousepad and moving it somewhere more comfortable). But this makes it hard to make small, precise movements.</p> <p>With gyro, since there's no mousepad to rest on, a high sensitivity can make shaky hands mess up difficult shots. A low sensitivity (big movements translate to small movements) makes it easy to steadily track slow moving targets or to move the cursor over a small element on-screen, but difficult to make big movements quickly, and you'll quickly encounter an uncomfortable turn, making play awkward.</p> <p>JSM attempts to give you the best of both worlds by letting you set a sensitivity to use when turning the gyro slowly (MIN_GYRO_SENS) and a sensitivity to use when turning the gyro quickly (MAX_GYRO_SENS). Any turns between &quot;slowly&quot; and &quot;quickly&quot; will linearly interpolate between the two. So, if in the current instant you're turning halfway between &quot;slowly&quot; and &quot;quickly&quot;, JSM will use a gyro sensitivity halfway between MIN_GYRO_SENS and MAX_GYRO_SENS.</p> <p>So what's &quot;slowly&quot; and what's &quot;quickly&quot;? They're determined by MIN_GYRO_THRESHOLD and MAX_GYRO_THRESHOLD, in degrees per second. In the example above, MIN_GYRO_THRESHOLD is 5, so if you're moving slower than 5° over a second (which is pretty slow!), JSM will consider it a &quot;slow&quot; turn and use MIN_GYRO_SENS. MAX_GYRO_THRESHOLD is 75, which means it'll use MAX_GYRO_SENS if you're turning the gyro at least fast enough to cover 75° in a second. Of course, JSM is using your instantaneous velocity to do these calculations. You don't have to actually turn your controller 75° over the course of a second; it just needs to be fast enough that if you maintained this speed for a whole second, it'd turn at least that far.</p> <p>75° per second isn't super fast, but it doesn't need to be. It's good to set MAX_GYRO_THRESHOLD to something reasonably fast, but slow enough that you can consistently move that quickly without worrying too much about it. It's easier to make quick movements more consistent if the sensitivity is constant, so setting the max threshold to something you'll regularly encounter anyway means you'll usually have a constant sensitivity during quick movements without having to think about it.</p> <p>GYRO_CUTOFF_RECOVERY is a threshold below which JSM will start to push your sensitivity towards zero. Like MIN_GYRO_THRESHOLD and MAX_GYRO_THRESHOLD, this is in degrees per second. This helps the cursor stay almost completely still despite shaky hands when you try to keep it still. I usually don't use GYRO_CUTOFF_RECOVERY, especially in 3D games, but it's demonstrated here. Some games will actually have a cutoff below which the gyro turn speed is considered zero. This is the worst. They shouldn't do it. You shouldn't do it, especially in 3D games.</p> <p>The last gyro setting in the example is GYRO_SMOOTH_THRESHOLD. Another way to combat shaky hands is to average out the gyro input over a small period of time. Of course, that makes the resulting movement feel laggy, so it should never be applied to big, intentional movements. GYRO_SMOOTH_THRESHOLD is a real-life turn speed at and above which no smoothing will be applied. Its default is 0 (no smoothing), and I normally leave it at 0, especially in 3D games, but a small smoothing threshold can help cover up some shakiness, especially in combination with GYRO_CUTOFF_RECOVERY.</p> <p>JSM has other commands not described here. You can find descriptions of those in the Gyro Mouse Inputs section of the JoyShockMapper <a href="https://github.com/Electronicks/JoyShockMapper#3-gyro-mouse-inputs">README</a>.</p> <p>Finally, let's have a brief look at the right stick.</p> <p>RIGHT_STICK_MODE determines what the right stick will be used for. LEFT_STICK_MODE does the same for the left stick. By default, it'll just be set to keyboard and mouse inputs as described earlier. But by setting RIGHT_STICK_MODE to AIM, you're saying that the right stick should be used to move the mouse. Alternatively, you can set it to FLICK, but that's only used for 3D games.</p> <p>The commands after that only apply when using the AIM stick mode.</p> <p>STICK_SENS says how fast you want the cursor to move per second when it's fully tilted. These settings are designed around 3D games, since stick-cursor generally isn't that useful in 2D games. So in 3D games, STICK_SENS = 360 means that a full tilt of the stick will turn the camera 360° per second. Properly calibrated 2D games treat the width of an HD screen as a full turn, so STICK_SENS = 360 means that a full tilt of the stick to the left or the right will take one second to cover the full width of the screen.</p> <p>This will be affected by the other settings, like STICK_ACCELERATION_RATE, so it's worth looking them up in the Stick Mouse Inputs section of the JoyShockMapper <a href="https://github.com/Electronicks/JoyShockMapper#2-stick-mouse-inputs">README</a>.</p> <p>If you look at _3Dmouse.txt you'll see I do things a little differently for 3D games:</p> <ul> <li>I like to use flick stick in every game where the mouse turns the camera. If you don't, comment out the FLICK line (put a # at the beginning) and add a new line: RIGHT_STICK_MODE = AIM. _3Dmouse.txt still includes some useful defaults for AIM mode, but you'll likely want to experiment with these and find something that works well for you.</li> <li>All the options you saw in _2Dmouse.txt work in 3D games as well, but since responsiveness and precision are often even more valuable in 3D games, I don't do any smoothing.</li> <li>You don't have to use these mouse files for every config. Some games have different needs. It's okay to have multiple different mouse configuration files, or even to all of your mouse configuration for a particular game in the same file that has its button mappings.</li> </ul> <h1><span><a name="calibrating"></a>Calibrating JSM (and sharing on GyroWiki)</span></h1> <p>Lastly, if you want to set up a config for a game that doesn't already have calibration info on GyroWiki, you can figure out the calibration info yourself. It doesn't have to be exact, but the method described here should get you fairly precise fairly easily.</p> <p>The goal here is to figure out:</p> <ol> <li>Does the game us raw input?</li> <li>What is a good REAL_WORLD_CALIBRATION value to make this game behave the same as other similar games?</li> </ol> <p>Raw input is when a game reads the mouse input without interference from Windows' mouse settings. You can often find info online regarding whether a game uses raw input. Alternatively, some games have the option to enable raw input in the settings menu. If the option is there, it's often best to enable it, but if you don't want to mess with your mouse settings for this particular game, it's fine not to.</p> <p>If this information isn't available online, you'll have to figure out if the game uses raw input by trial and error. This is a simple matter of playing the game with a real mouse, getting an idea of how fast the mouse moves. Then change your Windows mouse setting drastically (to the lowest or highest setting will be the most obvious, but you might want to first make a note of where the setting was before in case you want to return to it), and then return to the game. Move the mouse again. Is it moving roughly the same speed as before? Raw input. Is it drastically slower or faster (depending on whether you changed your Windows settings to be slower or faster)? No raw input.</p> <p>For games that use raw input, there's nothing you need to do as long as your config file starts with RESET_MAPPINGS. Otherwise, you can restore default behaviour of ignoring Windows' mouse settings with the command IGNORE_OS_MOUSE_SPEED. But for games that don't use raw input, you'll want to have the command COUNTER_OS_MOUSE_SPEED in your config file.</p> <h2><span>Calculating Real World Calibration</span></h2> <p>Now we need to figure out a good REAL_WORLD_CALIBRATION value. We can do this mathematically if we know enough info about how the game works, or we find the value experimentally. Usually it'll be experimental, but let's quickly touch on mathematical calculation.</p> <p>When the mouse reports the mouse speed, it's reporting the number of pixels across the screen the on-screen cursor should move if your Windows' mouse sensitivity is set to 1. Since JSM's gyro reports in degrees, a 360 degree turn will tell the on-screen cursor to move 360 pixels unless your REAL_WORLD_CALIBRATION is something other than 1. For 2D applications, REAL_WORLD_CALIBRATION is supposed to convert a full rotation into a 1920 pixel horizontal movement (the horizontal resolution of an HD monitor), so we can calculate our REAL_WORLD_CALIBRATION for Windows as 1920 / 360, which gives us 5 and 1/3.</p> <p>Most 2D games will have this REAL_WORLD_CALIBRATION, but sometimes they'll have an internal multiplier for the mouse sensitivity for other reasons &#8212; maybe it's because the cursor moves in a plane in the 3D world. Maybe it's because it moves the camera and cursor at the same time. Maybe it's because the developer had a different idea for what a nice scale would be for the mouse settings. These are all reasons you might need a different REAL_WORLD_CALIBRATION for a 2D game.</p> <p>So let's get into how to find a game's REAL_WORLD_CALIBRATION experimentally.</p> <p>The GyroConfigs folder contains two calibration helpers: '_2Dcalibrate.txt' and '_3Dcalibrate.txt'. They're both very similar, but the 2D one assumes most 2D games don't use raw input (this isn't always the case), and the 3D one assumes most 3D games do use raw input (this isn't always the case). But these files all contain your simplest setup for calculating your REAL_WORLD_CALIBRATION.</p> <div class="code"> <pre><code>RESET_MAPPINGS # Use flick stick RIGHT_STICK_MODE = FLICK REAL_WORLD_CALIBRATION = 1 COUNTER_OS_MOUSE_SPEED # Change this to whatever mouse sensitivity you have in the game you're playing IN_GAME_SENS = 1</code></pre></div> <br /> First it resets all settings to their default so that nothing else interferes with this calculation. Then set the right stick to FLICK mode. FLICK isn't normally used in 2D games, but it's still the easiest way to calibrate them, because flick stick creates an isolated horizontal movement that JSM can easily keep track of. Set REAL_WORLD_CALIBRATION to something small, like 1. If the game you're calibrating for does not use raw input, counter Windows' mouse settings. And finally, don't forget to set IN_GAME_SENS to whatever your in-game mouse sensitivity is, or 1 if there isn't one. <p>JSM has a command CALCULATE_REAL_WORLD_CALIBRATION that uses the last flick stick rotation and assumes that it produced a 360° turn in your 3D game or a complete cursor movement from one side of the screen to the other in a 2D game. It'll then tell you what your REAL_WORLD_CALIBRATION value <em>should</em> be to be correctly calibrated.</p> <p>For a 2D game, move the cursor all the way to the left side of the screen, then tilt the right stick forward but slightly to the right (don't want to accidentally move left when there's nowhere left to go), and rotate the stick until the cursor has just barely touched the right edge of the screen. Then release the stick. If it takes less than a full rotation, you should try again with a lower initial REAL_WORLD_CALIBRATION value to get a more precise result.</p> <p>Then type CALCULATE_REAL_WORLD_CALIBRATION into JSM and you should get a reasonably precise recommended REAL_WORLD_CALIBRATION.</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/fb27ebfbb87eb970189099256055687ce4e80cad-254835907886374304" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>Notice that although we calculated earlier that the best REAL_WORLD_CALIBRATION value should be 5.3333, the number we got from JSM was 5.3536. This is less than a 0.4% error, which is fine. The difference can be due to an accumulation of tiny rounding errors in JSM as it adds up the angle and calculates the result, or it could be that the stick moved a little as I released it. It's OK. Whether you're playing a 2D or 3D game, this is accurate enough.</p> <p>For a 3D game, find a safe space and line up your aimer or the edge of the screen with a static part of the environment. Try and line it up in a way that you can be sure you're lining it up exactly after completing a full turn. Then, tilt your flick stick roughly forward, and start rotating it. You may have to rotate it a lot depending on what the game's actual REAL_WORLD_CALIBRATION is, but eventually you should complete a full turn, be facing exactly the same way you were before, and release the stick.</p> <p>As before, if it takes less than a full rotation of the stick to complete a full rotation in-game, change REAL_WORLD_CALIBRATION to something even smaller and try again, as it's difficult to do this precisely when a tiny movement of the stick results in a big movement in-game.</p> <p>Once you've managed to complete exactly one rotation with the flick stick (within reason), release it and type into JSM: CALCULATE_REAL_WORLD_CALIBRATION. This process looks something like this:</p> <p><iframe src="http://gyrowiki.wikidot.com/blog:joyshockmapper-guide/html/130ffebd37fef7506dc00ff857e0f60ccd4e4c0a-666172999370174474" allowtransparency="true" frameborder="0" class="html-block-iframe"></iframe></p> <p>That's the REAL_WORLD_CALIBRATION value you should use for this game.</p> <p>Once you've done all this, why not save someone else the trouble of having to calibrate this game? I'd love for you to contribute your findings to the <a href="http://gyrowiki.wikidot.com/games">GyroWiki games database</a>!</p> <p>If you're not a member of GyroWiki, please consider <a href="http://gyrowiki.wikidot.com/system:join">creating an account</a>. Once you've done that, go to the <a href="http://gyrowiki.wikidot.com/games">games list</a> and look for the &quot;Add game&quot; button near the bottom of the page. Type in the name of your game you're adding, click the button, and there you'll be able to create a new game entry for the database!</p> <p>All you'll have to do is enter the Real World Calibration value, indicate whether the mouse controls the camera or a 2D cursor, whether the game uses raw input, and then you can optionally add some notes. For example, you might say in the notes that the game's native support for DualShock 4 interferes with JSM, but it can be disabled in the game settings (if that's the case).</p> <p>Members can also edit already-created entries. If someone else made a mistake with their entry and you're <em>sure</em> you can correct it (please double check that it actually should be changed), then go ahead and do it! Or maybe the basic details are correct, but more notes would be helpful. You can go ahead and add those helpful notes yourself.</p> <p>The more games are added to the GyroWiki games database, the less often JoyShockMapper users will have to calculate these calibration values themselves. This makes it much more accessible to more people who may find it difficult or not have the time to do this calibration.</p> <hr /> <p>My goal with this blog post was to get users up and running with JSM without having to learn more than they really need to. As such, there's plenty more to learn about JSM for advanced users. Everything there is to know about all the commands in JoyShockMapper should be covered in its README, <a href="https://github.com/Electronicks/JoyShockMapper">which you can find online here</a>. Thanks for reading, and I look forward to seeing what you can do with JoyShockMapper!</p> <p>by <span class="printuser avatarhover"><a href="http://www.wikidot.com/user:info/jibbsmart" ><img class="small" src="http://www.wikidot.com/avatar.php?userid=5112052&amp;amp;size=small&amp;amp;timestamp=1780764115" alt="JibbSmart" style="background-image:url(http://www.wikidot.com/userkarma.php?u=5112052)" /></a><a href="http://www.wikidot.com/user:info/jibbsmart" >JibbSmart</a></span></p> 
				 	]]>
				</content:encoded>							</item>
				</channel>
</rss>