I've become less and less trusting of third-party services that want to use social networking credentials to authenticate, and I'd like to give you an example.
http://minus.com/ is an image hosting site. A few of my friends host galleries on the site, but if I want to view their galleries, I have to create an account. Not a big deal, until you try. Then you are told that you have to create an account using your social networking account. I didn't quite trust them, so I created a new Twitter account (@RomSteady2) and registered using it. After registering, I unlinked my Twitter account from my Minus account, but I did not remove their permissions to update/tweet on my Twitter account so I could see what would happen.
My first indication that something was amiss was when I got an email today from someone I didn't know saying that they used TrueTwit verification. So what do I do but go and look and see my bait timeline filled with over five pages full of people I didn't follow...
...and there's more. I know that the number of followers you have will determine how often you are recommended, but this is insane. I'm guessing the TrueTwit verification was so that people could verify they were getting that they paid for. So with this experiment completed, I'm removing the permissions from the Minus app from this experimental twitter account and unsubscribing from all these guys who appear to be paying for subscribers. At least Minus didn't tweet on my behalf...
Note: To pre-emptively try to address criticism: this account was created specifically for this experiment and was only used for this account. It used a random password that was created specifically for this account. I tried to be as clean-room as I could with this.
January 26, 2014
January 25, 2014
Classes Missing From Leadwerks Documentation
It looks like some classes/functions/methods are missing from the Leadwerks class documentation. Some are likely due to the beta nature of things and others are just oversights, but here's a partial list...
NetworkDriver
- SetCurrent
- GetCurrent
Listener
- Create
Attractor
- GetAlphaMode
- Create
- SetAlphaMode
- SetRange
- GetRange
- SetForce
- GetForce
Buffer
- SetColorTexture
- Create
- GetCurrent
- GetWidth
- CountColorTextures
- Disable
- GetDepthTexture
- GetColorTexture
- SetColor
- SetDepthTexture
- GetHeight
- SetCurrent
- Enable
- SetMask
- Clear
Event
Sprite
- GetSize
- SetAngle
- Create
- SetSize
Component
- CallOutputs
NetworkDriver
- SetCurrent
- GetCurrent
Listener
- Create
Attractor
- GetAlphaMode
- Create
- SetAlphaMode
- SetRange
- GetRange
- SetForce
- GetForce
Buffer
- SetColorTexture
- Create
- GetCurrent
- GetWidth
- CountColorTextures
- Disable
- GetDepthTexture
- GetColorTexture
- SetColor
- SetDepthTexture
- GetHeight
- SetCurrent
- Enable
- SetMask
- Clear
Event
Sprite
- GetSize
- SetAngle
- Create
- SetSize
Component
- CallOutputs
January 18, 2014
Lua in Leadwerks #1: Settings, Action Maps
(Files here under CC-BY-NC-SA license can be downloaded here. Root post for series can be found here.)
First, I wanted a central location for me to establish defaults for my games and a consistent way for user overrides to be saved and reloaded. This is one possible solution.
The ZIP file above contains four files: RomSettings.lua, RomActionMap.lua, DefaultSettings.lua, and App.lua. App.lua is the stock App.lua, just modified to show how to hook into the
My first thought was that I would use Lua 5.1's function environment features to safely deserialize a Settings object and then do a selective merge into my default settings, but that led to two small problems. First, the function environment settings in Lua 5.1 are disabled in Leadwerks for Lua and will cause the app to exit immediately upon execution. Second, the function environment features in Lua are being removed in Lua 5.2, so my scripts wouldn't be future-proof.
Leadwerks does expose two new functions that we can use, though: System:GetProperty and System:SetProperty. These will allow you to save and load strings that get saved in a file in your AppData/Local/[gamename] folder on Windows. However, they only allows you to save and load strings, which limit their usefulness.
To use the attached files, extract DefaultSettings.lua, RomSettings.lua, and RomActionMap.lua into your Scripts folder. At the top of your App.lua file, add the following:
At the beginning of App:Loop(), replace the "end program" state with the following:
After you run it and exit, go check out your [gamename].cfg file in your AppData/Local/[gamename] folder.
Now for a quick walkthrough on how everything works, starting with System:SaveSettings.
System:SaveSettings recursively walks through the Settings object. Since we can only save strings, but need to reinflate to the proper type, I serialize each type to a string and add a prefix so that when I am deserializing I can reinflate correctly. If there is an array, I'll also create an "n" field so I know how many items there are.
System:LoadSettings acts a little differently. It recursively walks through the DefaultSettings object and only tries to inflate settings that it knows about.
ActionMap was covered before.
To add/remove/change the default settings, just modify DefaultSettings.lua. Those will get copied into the Settings global at the top of RomSettings.lua.
I'm going to start work on the replacement player object tomorrow.
First, I wanted a central location for me to establish defaults for my games and a consistent way for user overrides to be saved and reloaded. This is one possible solution.
The ZIP file above contains four files: RomSettings.lua, RomActionMap.lua, DefaultSettings.lua, and App.lua. App.lua is the stock App.lua, just modified to show how to hook into the
My first thought was that I would use Lua 5.1's function environment features to safely deserialize a Settings object and then do a selective merge into my default settings, but that led to two small problems. First, the function environment settings in Lua 5.1 are disabled in Leadwerks for Lua and will cause the app to exit immediately upon execution. Second, the function environment features in Lua are being removed in Lua 5.2, so my scripts wouldn't be future-proof.
Leadwerks does expose two new functions that we can use, though: System:GetProperty and System:SetProperty. These will allow you to save and load strings that get saved in a file in your AppData/Local/[gamename] folder on Windows. However, they only allows you to save and load strings, which limit their usefulness.
To use the attached files, extract DefaultSettings.lua, RomSettings.lua, and RomActionMap.lua into your Scripts folder. At the top of your App.lua file, add the following:
import "Scripts/RomSettings.lua" import "Scripts/RomActionMap.lua"At the top of your App:Start() function, add the following:
System:LoadSettings()
At the beginning of App:Loop(), replace the "end program" state with the following:
--If window has been closed, end the program if self.window:Closed() or ActionMap:IsHit(Settings.ActionMap.QuitApp) then Settings:SaveSettings() return false end
After you run it and exit, go check out your [gamename].cfg file in your AppData/Local/[gamename] folder.
Now for a quick walkthrough on how everything works, starting with System:SaveSettings.
System:SaveSettings recursively walks through the Settings object. Since we can only save strings, but need to reinflate to the proper type, I serialize each type to a string and add a prefix so that when I am deserializing I can reinflate correctly. If there is an array, I'll also create an "n" field so I know how many items there are.
System:LoadSettings acts a little differently. It recursively walks through the DefaultSettings object and only tries to inflate settings that it knows about.
ActionMap was covered before.
To add/remove/change the default settings, just modify DefaultSettings.lua. Those will get copied into the Settings global at the top of RomSettings.lua.
I'm going to start work on the replacement player object tomorrow.
January 17, 2014
Lua in Leadwerks #0: Tentative Feature List
This is the opening post for the list of items I want to code from scratch in Lua to work with Leadwerks 3.1 Indie Edition in no particular order. As features get implemented, this post will be updated to have links to the implementation posts. Each post will have the folders from the FromScratch project so you can use the code as you see fit provided you comply with the accompanying license.
Note that this is not a promise that all of this will be coded, just that it is my intention to do it if time and motivation permits me.
Update 1/26: Added viewport camera to HUD.
Update 2/8: Added ladders to FPS controller.
Note that this is not a promise that all of this will be coded, just that it is my intention to do it if time and motivation permits me.
- Input mapping (any input [mouse, keyboard, gamepad] to any function)
- Load/save support
- Settings
- Level state
- First person shooter controller
- Movement and strafing
- Aiming using the mouse
- Invert Y-axis
- Mouse Sensitivity
- Jump
- Crouch
- Sprint (enhanced using FOV tricks)
- Sneak (enhanced using FOV tricks)
- Swim
- Possess multiple weapons and switch between them
- Camera decoupled from controller
- Ladders
- Weapons
- Melee
- Hitscan
- Projectile
- (potentiallly) Aim assist when using gamepad
- Camera
- Adjustable FOV (and demonstrate why high FOV can have a negative effect on visual fidelity)
- Compatible with "render-to-texture" mods
- HUD
- Health
- Ammo
- Subtitles and/or closed captioning
- Key tutorials
- Viewport camera
- Doors
- All lockable/remote openable
- Rotating
- Sliding
- Triggers
- Activate once
- On entry
- On exit
- Activate while in
- Level transitions
- Maintain health/ammo across maps
- (Potentially) migrate items around you ala Half-Life
- Menus
- Buttons
- Checkboxes
- Radio Buttons
- Sliders
- (Potentially) text input
- (Potentially) console/in-game debug log
- Localization support
Update 1/26: Added viewport camera to HUD.
Update 2/8: Added ladders to FPS controller.
January 15, 2014
XNA is Dead. Boo. Long Live What?
I just got home from a little teeny tiny XNA meetup in Seattle that happened to coincide with Steam Dev Days.
It was kind of a weird experience. The biggest takeaway for me is that, at least for Microsoft platforms, the likelihood of XNA coming back from the dead is almost nil. For enterprise software development, .NET is king and has taken the place of Visual Basic 6, but for everything else, it's either JavaScript/HTML5 or C++. I get the feeling that Microsoft backing Unity is more a "meh, Unity gets the basics right and nobody will know about how badly they fuck up the rest until they become advanced" move than an actual strategy.
The really sad thing is that unless Microsoft steps up, XNA may live long and prosper on almost every non-Microsoft platform in existence due to Monogame. I overheard some NDA stuff that I won't go into, but I find it really funny that there are people who are chomping at the bit to pick up what Microsoft has thrown away here.
That said, I feel my time with XNA is pretty much gone. My interest in XNA was always related to how it would democratize game development. Game players who learn how to make games not only end up being better gamers, they also end up being better game community members. They know how hard it is to do X, and their brief peek behind the curtain into the inner workings of how games work let them have a better appreciation for game developers and games themselves.
However, for a platform to inspire neophyte game developers, you need support, and XNA has lost it. It's really hard for me to remain passionate about something that's fading away. So, where am I going to focus my efforts now?
Lua.
Lua has been used in a lot of games as a scripting language. All game and UI logic for both Amped and Amped 2 were written in Lua, and the only reason Lua didn't get any credit in the game or manuals is that their license at the time said it was optional and legal saw no reason to provide the credit. In addition, two of the major low-cost game engine projects targeted at neophytes are shipping with Lua bindings: Leadwerks 3.1 and FPS Creator Reloaded.
However, a lot of the public scripts that are out there are, quite frankly, shit. Even the little hacks I've published over the last few days are shit. If I'm going to do this right, I'm going to have to go back to first principles.
Here is my plan. Starting this weekend, I'm going to try to write a game from scratch against Leadwerks 3.1 Indie Edition. My goal is to use absolutely none of the scripts that ship with the engine, though. I'm going to start from nothing and move on from there.
This will do three things. First, it will give a ground-up point of view of what it takes to build a game using Lua with lessons that can be applied to multiple game engines. Second, it will help me better understand what it takes to be effective with Lua. Third, it will help me get ready to better use an engine that I backed on Kickstarter before the C++ bindings
Hopefully, this works out well for everyone. I'll still remain involved with .NET and XNA, but even more on the periphery than before, unfortunately.
It was kind of a weird experience. The biggest takeaway for me is that, at least for Microsoft platforms, the likelihood of XNA coming back from the dead is almost nil. For enterprise software development, .NET is king and has taken the place of Visual Basic 6, but for everything else, it's either JavaScript/HTML5 or C++. I get the feeling that Microsoft backing Unity is more a "meh, Unity gets the basics right and nobody will know about how badly they fuck up the rest until they become advanced" move than an actual strategy.
The really sad thing is that unless Microsoft steps up, XNA may live long and prosper on almost every non-Microsoft platform in existence due to Monogame. I overheard some NDA stuff that I won't go into, but I find it really funny that there are people who are chomping at the bit to pick up what Microsoft has thrown away here.
That said, I feel my time with XNA is pretty much gone. My interest in XNA was always related to how it would democratize game development. Game players who learn how to make games not only end up being better gamers, they also end up being better game community members. They know how hard it is to do X, and their brief peek behind the curtain into the inner workings of how games work let them have a better appreciation for game developers and games themselves.
However, for a platform to inspire neophyte game developers, you need support, and XNA has lost it. It's really hard for me to remain passionate about something that's fading away. So, where am I going to focus my efforts now?
Lua.
Lua has been used in a lot of games as a scripting language. All game and UI logic for both Amped and Amped 2 were written in Lua, and the only reason Lua didn't get any credit in the game or manuals is that their license at the time said it was optional and legal saw no reason to provide the credit. In addition, two of the major low-cost game engine projects targeted at neophytes are shipping with Lua bindings: Leadwerks 3.1 and FPS Creator Reloaded.
However, a lot of the public scripts that are out there are, quite frankly, shit. Even the little hacks I've published over the last few days are shit. If I'm going to do this right, I'm going to have to go back to first principles.
Here is my plan. Starting this weekend, I'm going to try to write a game from scratch against Leadwerks 3.1 Indie Edition. My goal is to use absolutely none of the scripts that ship with the engine, though. I'm going to start from nothing and move on from there.
This will do three things. First, it will give a ground-up point of view of what it takes to build a game using Lua with lessons that can be applied to multiple game engines. Second, it will help me better understand what it takes to be effective with Lua. Third, it will help me get ready to better use an engine that I backed on Kickstarter before the C++ bindings
Hopefully, this works out well for everyone. I'll still remain involved with .NET and XNA, but even more on the periphery than before, unfortunately.
January 12, 2014
Bind Multiple Inputs To An Action In Leadwerks Indie Edition
It is generally considered poor form to have key bindings buried in the middle of a class. This code will let you keep your key and mouse bindings separate from your Leadwerks Lua classes, and also let you bind multiple inputs to a single action.
Create a new script in your Scripts folder named ActionMap.lua. Add the following code:
ActionMap = {}
ActionMap.forward = { "W" }
ActionMap.back = { "S" }
ActionMap.left = { "A" }
ActionMap.right = { "D" }
ActionMap.fire = { 1 }
ActionMap.jump = { "Space" }
ActionMap.reload = { "R" }
ActionMap.use = { "E", 2 }
ActionMap.debugPhysics = { "P" }
function ActionMap:IsHit(action)
local i=1
local window=Window:GetCurrent()
while action[i]~=nil do
if (type(action[i]) == "string") then
if window:KeyHit(Key[action[i]]) then
return true
end
end
if (type(action[i]) == "number") then
if window:MouseHit(action[i]) then
return true
end
end
i=i+1
end
return false
end
function ActionMap:IsDown(action)
local i=1
local window=Window:GetCurrent()
while action[i]~=nil do
if (type(action[i]) == "string") then
if window:KeyDown(Key[action[i]]) then
return true
end
end
if (type(action[i]) == "number") then
if window:MouseDown(action[i]) then
return true
end
end
i=i+1
end
return false
end
Now open your FPSPlayer.lua folder. At the top, add:
import "Scripts/ActionMap.lua"
Now search for the following code:
if window:KeyHit(Key.E) then
Change it to the following code:
if ActionMap:IsHit(ActionMap.use) then
Now run your game. You'll notice you can still use "E" to trigger the Use method, but you can also right-click to trigger the Use method.
When you populate your ActionMaps, a number is a mouse button, and a string is one of the constants from the Key object. Adding saving/loading key bindings to a file and allowing your customers to rebind their keys is left as an exercise for the reader.
Create a new script in your Scripts folder named ActionMap.lua. Add the following code:
ActionMap = {}
ActionMap.forward = { "W" }
ActionMap.back = { "S" }
ActionMap.left = { "A" }
ActionMap.right = { "D" }
ActionMap.fire = { 1 }
ActionMap.jump = { "Space" }
ActionMap.reload = { "R" }
ActionMap.use = { "E", 2 }
ActionMap.debugPhysics = { "P" }
function ActionMap:IsHit(action)
local i=1
local window=Window:GetCurrent()
while action[i]~=nil do
if (type(action[i]) == "string") then
if window:KeyHit(Key[action[i]]) then
return true
end
end
if (type(action[i]) == "number") then
if window:MouseHit(action[i]) then
return true
end
end
i=i+1
end
return false
end
function ActionMap:IsDown(action)
local i=1
local window=Window:GetCurrent()
while action[i]~=nil do
if (type(action[i]) == "string") then
if window:KeyDown(Key[action[i]]) then
return true
end
end
if (type(action[i]) == "number") then
if window:MouseDown(action[i]) then
return true
end
end
i=i+1
end
return false
end
Now open your FPSPlayer.lua folder. At the top, add:
import "Scripts/ActionMap.lua"
Now search for the following code:
if window:KeyHit(Key.E) then
Change it to the following code:
if ActionMap:IsHit(ActionMap.use) then
Now run your game. You'll notice you can still use "E" to trigger the Use method, but you can also right-click to trigger the Use method.
When you populate your ActionMaps, a number is a mouse button, and a string is one of the constants from the Key object. Adding saving/loading key bindings to a file and allowing your customers to rebind their keys is left as an exercise for the reader.
Add Primitive HUD To Leadwerks Indie Edition
If you want to add a simple HUD to your Leadwerks Indie Edition game, here you go.
First, open FPSPlayer.lua.
At the top, under all the Script.* lines, add:
Script.hudFont = nil
Somewhere in Script:Start(), add:
self.hudFont = Font:Load("Fonts/Arial.ttf",20)
Scroll down to Script:PostRender(context).
After the code to add the blood splatter on the screen and before context:SetColor(1,1,1,1), add:
if self.hudFont~=nil then
context:SetFont(self.hudFont)
context:SetColor(1,1,1,1)
local fontHeight = self.hudFont:GetHeight()
local hudText = "Health: "..self.health
local x = 0
local y = context:GetHeight() - fontHeight
context:DrawText(hudText, x, y)
if self.weapon~=nil then
hudText = "Ammo: "..self.weapon.clipammo.."/"..self.weapon.ammo
x = context:GetWidth() - self.hudFont:GetTextWidth(hudText)
context:DrawText(hudText, x, y)
end
end
The only downside to this is that the debug text doesn't set its own font appropriately. To fix that, open your App.lua file. In App:Start(), add:
self.font = Font:Load("Fonts/Arial.ttf",10)
And in App:Loop, right after the "Render statistics" comment, add:
self.context:SetFont(self.font)
That should give you a HUD similar to the following:
First, open FPSPlayer.lua.
At the top, under all the Script.* lines, add:
Script.hudFont = nil
Somewhere in Script:Start(), add:
self.hudFont = Font:Load("Fonts/Arial.ttf",20)
Scroll down to Script:PostRender(context).
After the code to add the blood splatter on the screen and before context:SetColor(1,1,1,1), add:
if self.hudFont~=nil then
context:SetFont(self.hudFont)
context:SetColor(1,1,1,1)
local fontHeight = self.hudFont:GetHeight()
local hudText = "Health: "..self.health
local x = 0
local y = context:GetHeight() - fontHeight
context:DrawText(hudText, x, y)
if self.weapon~=nil then
hudText = "Ammo: "..self.weapon.clipammo.."/"..self.weapon.ammo
x = context:GetWidth() - self.hudFont:GetTextWidth(hudText)
context:DrawText(hudText, x, y)
end
end
The only downside to this is that the debug text doesn't set its own font appropriately. To fix that, open your App.lua file. In App:Start(), add:
self.font = Font:Load("Fonts/Arial.ttf",10)
And in App:Loop, right after the "Render statistics" comment, add:
self.context:SetFont(self.font)
That should give you a HUD similar to the following:
January 11, 2014
Why Don't Games Have Input Remapping? (Public Draft)
(Note: This is a dry run for a YouTube series I'm planning on putting together and is a work in progress.)
A common complaint against PC games nowadays is a lack of proper input remapping in games. We see triple-A games and indie games alike where being able to remap controls in a friendly manner is either not an available option, or is set up as a flow ("press Up, now press Down, now press Left, etc.") with no clue how many controls there are or any way of seeing what your controls are set to.
In this article, we're going to look at the history of input processing in video games and see how that history led us to where we are today, so let's start with...
Code back then was small and simple because it had to be. In pseudo-assembly...
LDX $PADDLE0
This would get compiled out to two bytes: the instruction to load the X register, and a byte offset from zero where the ADC value from the potentiometer was stored. However, we then went to...
Joysticks were a set of switches under the hood. When you pressed the action button or any of the four cardinal directions, a switch was closed. If you pressed in a diagonal, two switches were closed. For example, if you pressed diagonal up-left, the switches for up and left would be closed.
This will seem a bit weird, but if a switch was open, it was registered as a 1 in a bitmask. If the joystick wasn't being pressed in a direction, it would return 15 (b1111). Pressing up would mask off bit 0 and return 14 (b1110). Down would mask off bit 1 and return 13 (b1101). Left would mask off bit 2 and right would mask off bit 3. The action button would be bound to a different memory address usually, and would return 1 if not pressed, and 0 if pressed.
So, if you wanted to see if a player was pressing in a certain direction, you'd do something similar in pseudo-assembly...
LDA b0001 // Load the accumulator with the bit we want to test
BIT $STICK0 // Check stick 1 to see if the bits in the accumulator are set
BNE $UP // Branch if the bit test failed (meaning the joystick was pressed)
This wouldn't take a lot of memory. The load would be two bytes, the bit test would be three bytes, and the BNE call would be three bytes, for a total of eight bytes to check for up. Given that Atari 2600 games only had 4KB of space, you can see that input processing would take some space.
Now let's jump to...
If you take a look at a Nintendo gamepad port, you may notice that it only has seven pins. However, your Nintendo gamepad has eight buttons (the four directions, A, B, select and start). Yes, this made input processing more difficult. Long story short, every frame a "latch" signal would be sent to the controller asking for input data. Once that "latch" signal was received, the developer would have to read from a certain memory location eight times in a row. Each time, they'd get a single bit of data: either a 1 for a button being pressed, or a 0 for a button not being pressed. Input processing for controller 1 would look like this pseudocode:
byte pad0 = 0;
for (int i = 0; i < 8; i++) {
pad0 = pad0 * 2;
pad0 &= (*paddlestate & 1);
}
This would get you all the inputs into the pad0 variable, and then you could bit-test. SNES controllers worked the same way, except they had 16 bits in their controller entry (even though they only had twelve buttons).
A game like Super Mario Bros. (which was only 32KB), where only one player was playing at a time, could probably just switch the paddlestate pointer from controller 1 to controller 2 and keep the rest of the code the same.
One of the major benefits of Nintendo consoles was that most games had the same basic controls. A jumped, B shot, etc. There wasn't a requirement that it be this way (yet), but standard controls helped players quickly get going.
Now at this point, you're probably screaming, "Michael, you ignorant bint, this is all console crap. We already know that consoles are why we don't have decent controller mapping on console ports." To which I reply, "No, that's not the only reason, and I'm getting to PC's in a moment, say...now."
Now, while controller inputs were handled in a fairly straightforward fashion on the consoles of the era, the PC was pretty much the wild west. There was an optional joystick interface, but you couldn't rely on everyone having access to a joystick. There was a controller everyone had to have, though...the PC keyboard.
When you'd press or release a key, an interrupt would fire and code in a keyboard interrupt handler would execute to handle the keypress. Most applications would let the default keyboard interrupt handler do its work and just pass the final processed keypress along, but games would often jump in and "hook" the handler so they could do their own processing on key down and key up.
Each keypress would generate one or two scan codes. For example, pressing the down arrow key would send hex 0xE0, 0x48. Releasing the down arrow key would send hex 0xD0. If a game developer properly hooked the keyboard handler, they could keep a memory location up to date with whether or not a key they cared about was being pressed.
Most DOS games didn't offer keyboard remapping. Since the size and shape of keyboards was pretty much standard, it was common for games to ship with keyboard overlays so you'd have a reminder of which key was which. However, keyboards for other countries often caused problems.
Some games did offer keyboard remapping, but it was almost always through an external config utility. Gamers would open a small utility that would let them pick the keys of their choice, and it would save the key down/key up codes from that press to a config file. The game would load that config file and use that data in its keyboard hook, and the game, rather than check the input directly, would check an internal data structure to see if an input was set. You'd end up with code like this in your keyboard hook...
struct Input {
bool Left;
bool Right;
bool Fire;
...
}
Input newInput = prevInput;
newInput.Left = (scanCode == LeftUp ? false : (scanCode == LeftDown ? true : newInput.Left));
...
This would allow people to compare previous inputs to new input and act accordingly. Other developers moved to an input event system.
vector<InputEvent> events;
if (scanCode == LeftUp) events.push_back (InputEvent_LeftUp);
...
The guys who had moved to event-based input were in great position when it came to moving to...
- Lots of variation in controllers for PC at this point, but no standards
- Success of Quake makes in-game keyboard rebinding popular
- DirectInput, Sidewinder Joystick and MechWarrior 2 bring about a joystick renaissance
- Microsoft focus on gaming and purchasing of game developers leads to a positive feedback loop of features for DirectX
- Input event mapping built into DirectInput
- Rise of USB, elimination of dedicated gamepad port
- XInput, the PC gamepad renaissance
- Death of DirectInput and the near-death of the joystick
A common complaint against PC games nowadays is a lack of proper input remapping in games. We see triple-A games and indie games alike where being able to remap controls in a friendly manner is either not an available option, or is set up as a flow ("press Up, now press Down, now press Left, etc.") with no clue how many controls there are or any way of seeing what your controls are set to.
In this article, we're going to look at the history of input processing in video games and see how that history led us to where we are today, so let's start with...
Analog Input
In the beginning, there was Pong. You had two potentiometers as controls and a button for reset/start. Once a frame, the value of the potentiometer would be converted into a binary representation of its current resistance value via a process called ADC (analog to digital conversion). That value would be stored in a certain memory location. The code back then had to be really small, so it was hardcoded to look for that location. Usually, the reset/start button just reset the entire device, and the device would start with the game "running" when you first turned it on.Code back then was small and simple because it had to be. In pseudo-assembly...
LDX $PADDLE0
This would get compiled out to two bytes: the instruction to load the X register, and a byte offset from zero where the ADC value from the potentiometer was stored. However, we then went to...
Atari Joysticks
The original Atari joysticks were amazing. You had an eight-directional joystick and an action button per player! Wow, such progress. Arcade games at the time used extremely similar code to what we'll be talking about here.Joysticks were a set of switches under the hood. When you pressed the action button or any of the four cardinal directions, a switch was closed. If you pressed in a diagonal, two switches were closed. For example, if you pressed diagonal up-left, the switches for up and left would be closed.
This will seem a bit weird, but if a switch was open, it was registered as a 1 in a bitmask. If the joystick wasn't being pressed in a direction, it would return 15 (b1111). Pressing up would mask off bit 0 and return 14 (b1110). Down would mask off bit 1 and return 13 (b1101). Left would mask off bit 2 and right would mask off bit 3. The action button would be bound to a different memory address usually, and would return 1 if not pressed, and 0 if pressed.
So, if you wanted to see if a player was pressing in a certain direction, you'd do something similar in pseudo-assembly...
LDA b0001 // Load the accumulator with the bit we want to test
BIT $STICK0 // Check stick 1 to see if the bits in the accumulator are set
BNE $UP // Branch if the bit test failed (meaning the joystick was pressed)
This wouldn't take a lot of memory. The load would be two bytes, the bit test would be three bytes, and the BNE call would be three bytes, for a total of eight bytes to check for up. Given that Atari 2600 games only had 4KB of space, you can see that input processing would take some space.
Now let's jump to...
Nintendo Gamepads
If you take a look at a Nintendo gamepad port, you may notice that it only has seven pins. However, your Nintendo gamepad has eight buttons (the four directions, A, B, select and start). Yes, this made input processing more difficult. Long story short, every frame a "latch" signal would be sent to the controller asking for input data. Once that "latch" signal was received, the developer would have to read from a certain memory location eight times in a row. Each time, they'd get a single bit of data: either a 1 for a button being pressed, or a 0 for a button not being pressed. Input processing for controller 1 would look like this pseudocode:
byte pad0 = 0;
for (int i = 0; i < 8; i++) {
pad0 = pad0 * 2;
pad0 &= (*paddlestate & 1);
}
This would get you all the inputs into the pad0 variable, and then you could bit-test. SNES controllers worked the same way, except they had 16 bits in their controller entry (even though they only had twelve buttons).
A game like Super Mario Bros. (which was only 32KB), where only one player was playing at a time, could probably just switch the paddlestate pointer from controller 1 to controller 2 and keep the rest of the code the same.
One of the major benefits of Nintendo consoles was that most games had the same basic controls. A jumped, B shot, etc. There wasn't a requirement that it be this way (yet), but standard controls helped players quickly get going.
Now at this point, you're probably screaming, "Michael, you ignorant bint, this is all console crap. We already know that consoles are why we don't have decent controller mapping on console ports." To which I reply, "No, that's not the only reason, and I'm getting to PC's in a moment, say...now."
DOS Games
Now, while controller inputs were handled in a fairly straightforward fashion on the consoles of the era, the PC was pretty much the wild west. There was an optional joystick interface, but you couldn't rely on everyone having access to a joystick. There was a controller everyone had to have, though...the PC keyboard.
When you'd press or release a key, an interrupt would fire and code in a keyboard interrupt handler would execute to handle the keypress. Most applications would let the default keyboard interrupt handler do its work and just pass the final processed keypress along, but games would often jump in and "hook" the handler so they could do their own processing on key down and key up.
Each keypress would generate one or two scan codes. For example, pressing the down arrow key would send hex 0xE0, 0x48. Releasing the down arrow key would send hex 0xD0. If a game developer properly hooked the keyboard handler, they could keep a memory location up to date with whether or not a key they cared about was being pressed.
Most DOS games didn't offer keyboard remapping. Since the size and shape of keyboards was pretty much standard, it was common for games to ship with keyboard overlays so you'd have a reminder of which key was which. However, keyboards for other countries often caused problems.
Some games did offer keyboard remapping, but it was almost always through an external config utility. Gamers would open a small utility that would let them pick the keys of their choice, and it would save the key down/key up codes from that press to a config file. The game would load that config file and use that data in its keyboard hook, and the game, rather than check the input directly, would check an internal data structure to see if an input was set. You'd end up with code like this in your keyboard hook...
struct Input {
bool Left;
bool Right;
bool Fire;
...
}
Input newInput = prevInput;
newInput.Left = (scanCode == LeftUp ? false : (scanCode == LeftDown ? true : newInput.Left));
...
This would allow people to compare previous inputs to new input and act accordingly. Other developers moved to an input event system.
vector<InputEvent> events;
if (scanCode == LeftUp) events.push_back (InputEvent_LeftUp);
...
The guys who had moved to event-based input were in great position when it came to moving to...
Windows Games
Coming soon...- Lots of variation in controllers for PC at this point, but no standards
- Success of Quake makes in-game keyboard rebinding popular
- DirectInput, Sidewinder Joystick and MechWarrior 2 bring about a joystick renaissance
- Microsoft focus on gaming and purchasing of game developers leads to a positive feedback loop of features for DirectX
- Input event mapping built into DirectInput
- Rise of USB, elimination of dedicated gamepad port
- XInput, the PC gamepad renaissance
- Death of DirectInput and the near-death of the joystick
Subscribe to:
Posts (Atom)