Monday, April 7, 2025

My input system for Ablockalypse.

My input system in Ablockalypse is quite simple and also very powerful.

 

I'm using a multimap to store the key-presses and their associated modifiers as well as the command to be executed.

mKeyBindings2.emplace(SDL_SCANCODE_EQUALS
                          std::make_pair(KMOD_NONE
                          ley::Command::increaseVolume));
mKeyBindings2.emplace(SDL_SCANCODE_EQUALS,
                          std::make_pair(KMOD_RSHIFT,
                          ley::Command::increaseTransparency));


 The input system also has a custom delay and repeat mechanism. I have two timers associated with every key. I can check which modifiers are pressed when the key is initially pressed as well as when the key needs to be automatically repeated. I also check which modifiers are still pressed when its time to repeat the key.

At the end of the input phase of the game loop I check the timers to see if any additional input needs to be added to the command queue.

auto check_timers = [this, commandQueuePtr, alt_mod, bindings2,  
                        buttonBindings2, get_mod_bitmask,  
                        bindingsNewType]() {
for(auto &[key, keyPress] : mKeysPressed) {

keyPress->getDelayTimerPtr()->runFrame(false);
keyPress->getRepeatTimerPtr()->runFrame(false);

//if the delay timer has expired and 
            //the repeat timer has expired
if(keyPress->getDelayTimerPtr()->hasExpired() 
                && keyPress->getRepeatTimerPtr()->hasExpired()) {
ley::Command command = lookupCommand2
                                        (SDL_Scancode)key
                                        get_mod_bitmask(), 
                                        bindingsNewType);
 
//don't repeat the enter command.
if(command != ley::Command::enter) {
commandQueuePtr->push(command);
}
//reset the repeat timer
keyPress->getRepeatTimerPtr()->reset();
}
}

...
};


This is my SDL_KEYDOWN event. Notice how it skips over key repeats. This is where I start to handle the delay and repeat timers.


case SDL_KEYDOWN:
if(!event.key.repeat) {
SDL_Scancode pressedKey = event.key.keysym.scancode;
Uint16 pressedModifiers = event.key.keysym.mod;

//reset the timers if this key was previously not pressed
if(mKeysPressed.find(pressedKey) == mKeysPressed.end()) {

//skip modifiers
if(pressedKey <= SDL_SCANCODE_LCTRL 
                 || pressedKey >= SDL_SCANCODE_RGUI)
{
mKeysPressed.insert({pressedKey
                 std::make_unique<InputPressed>(pressedModifiers)});
mKeysPressed[pressedKey]->getDelayTimerPtr()->reset();
mKeysPressed[pressedKey]->getRepeatTimerPtr()->reset();
}
}
//command = lookupCommand(pressedKey, bindings2);
command = lookupCommand2(pressedKey, pressedModifiers, bindingsNewType);

if(command == ley::Command::fullscreen) {
fullscreen = !fullscreen;
}
else {
// push on repeatable commands
commandQueuePtr->push(command);
}
}


Here in the main controller is where I handle the commands after calling pollEvents().

/**** GET INPUT ****/
//pollEvents updates running and full screen flags
mInputSystem.pollEvents(fs, mGm->getButtonBindingsPtr(), mGm->getKeyBindingsPtr(), mGm->getKeyBindingsPtr2(), &mCommandQueue, mGameStateMachine.activeUIElement(),
[this](ley::Command c) {mGameStateMachine.activeUIElement()->onKeyDown(c == ley::Command::backspace ? ley::Character::backspace : ley::Character::none);});

ley::Command command = ley::Command::none;
if(!mCommandQueue.empty()) {
command = mCommandQueue.front();
mCommandQueue.pop();
}

/**** INPUT PROCESSING ****/
if(fs != mVideoSystem->fullScreen()) {
mVideoSystem->setFullScreen(fs);
}

processCommands(command);
mGameStateMachine.update(command);
processStates(command);



Then I execute the commands by matching the command to some specific logic. mGm-> is a pointer to the GameModel.

void ley::GameController::processCommands(ley::Command command) {
if(command == ley::Command::nextSong) {
playNext();
}

if(command == ley::Command::decreaseVolume) {
mGm->audio()->decreaseVolume();
}

if(command == ley::Command::increaseVolume) {
mGm->audio()->increaseVolume();
}

/**** DEBUG INPUT PROCESSING ****/
if(command == ley::Command::debugkeystoggle) {
mGm->debugCommandsToggle();
}


Friday, February 28, 2025

Localization

As I continue on my Journey in creating my first game on steam I quickly learned how important it is to localize the game by adding different languages. I decided to start with Spanish because I felt that I could handle this one mostly on my own by using google translate and then I will have someone who speaks Spanish review once I am done.

I created a very simple csv file with two columns, one column for the key and one column for the word value.

SPANISH:
name,nombre
score,puntaje
lines,pauta
level,nivel
combo,combinación
english,inglés
spanish,español

ENGLISH:
name,name
score,score
lines,lines
level,level
combo,combo
english,english
spanish,spanish

I'm using a simple method to load the data based on the language selection 'en' or 'es'

void ley::LanguageModel::loadLanguageData(std::string language) {
//load config
std::string fileName = "./assets/lang/" + language + ".csv";
std::ifstream inFile(fileName);
mLanguageFields.clear();

if (inFile.is_open())
{
std::string line;
while(std::getline(inFile,line) )
{
std::stringstream ss(line);

std::string field, word;
std::getline(ss,field,',');
std::getline(ss,word,',');

mLanguageFields.emplace(field, word);
}
}
}


I created a gitWord() method that handles the actual work of pulling the correct word based on the language setting.

getWord("programming", 0, false, capitalizationtype::capitalizeWords)


The first parameter is the key for the word, the second is a padding value, the 3rd whether the word should be left or right justified and the 4th a capitalization style.

enum class capitalizationtype {
capitalizeFirst,
capitalizeWords,
capitalizeNone
};


You can capitalize the first word in the string, all words in the string or none.

I've completed all the work for the Spanish language and the language option is available in the options menu.

Next I will add some functionality to use the Steam SDK to detect the users language based on their Steam preferences and automatically selected the users language. I will also work on Simplified Chinese because a large portion of steam users are on Simplified Chinese.