Jump to content

KeyHit() can register late


SpiderPig
 Share

Recommended Posts

There's an old discussion on this here.  To re-iterate, if you check KeyHit() inside an unreachable if statement, press the key, and then later that if statement evaluates to true, the KeyHit() check will also return true.  No matter the period of time since hitting that key.

I know FlushKeys() can fix that but I believe it also resets the key down stats and makes KeyDown() unusable.  Is there a common work around for this is can FlushKeys have an option to just resit the hit stats and not the down states?  Maybe an enum flag?  FLUSH_HIT_STATE, FLUSH_DOWN_STATE, FLUSH_BOTH_STATES?

 

Link to comment
Share on other sites

I think my input system fixes this because you're looking for actions instead of raw key presses.

I still need to convert my system off of WinAPI and this seems like a good reason to start that soon.

  • Thanks 1

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

2 hours ago, Josh said:

Put the key hit call outside the if statement so it is always called each frame 

That works for simple cases but with a large project it becomes impractical.  There's so many layers of if statements buried in sub classes and methods from all over the place.  I think key hits should be auto reset at the end of every game loop, otherwise I don't see KeyHit() being a useful method in large projects.  And the call only works once doesn't it, before it resets the hit state?

 

2 hours ago, reepblue said:

I think my input system fixes this because you're looking for actions instead of raw key presses.

I still need to convert my system off of WinAPI and this seems like a good reason to start that soon.

I'll take a look, thanks.

Link to comment
Share on other sites

1 hour ago, SpiderPig said:

I don't see KeyHit() being a useful method in large projects.

It's not, for this exact reason. It's better to listen for events.

  • Thanks 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

42 minutes ago, Dreikblack said:

EVENT_KEYUP/EVENT_KEYDOWN or checking KeyHit in loop?

The first thing you said. Use ListenEvent to intervept the event as it is emitted.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I made a controls manager where i'm trying reproduce system from this article but based on Ultra keys events (and no editing for action hotkeys yet)

ControlsManager.h:

#pragma once

#include "UltraEngine.h"

using namespace UltraEngine;

using json = nlohmann::json;

enum ACTION_TYPE
{
    ACTION_CAMERA_MOVE_FORWARD,
    ACTION_CAMERA_MOVE_BACKWARD,
    ACTION_CAMERA_MOVE_LEFT,
    ACTION_CAMERA_MOVE_RIGHT,
    ACTION_CAMERA_ROTATE_LEFT,
    ACTION_CAMERA_ROTATE_RIGHT,
    ACTION_CONFIRM,
    ACTION_WEAPON_FIRE,
    ACTION_WEAPON_SPECIAL,
    ACTION_WEAPON_OVERWATCH,
    ACTION_UNIT_SWITCH_NEXT,
    ACTION_UNIT_SWITCH_PREVIOUS,
    ACTION_INVENTORY,
    ACTION_COUNT
};

class ControlsManager : public Object
{
protected:
	ControlsManager();
	void init();
	WString configPath;
	json configData;
    map<KeyCode, bool> keysDown;
    map<KeyCode, bool> keysUp;
    map<ACTION_TYPE, vector<KeyCode>> actionsMap;
public:
	ControlsManager(ControlsManager const&) = delete;
	ControlsManager& operator=(ControlsManager const&) = delete;
	static std::shared_ptr<ControlsManager> getInstance();
    void saveConfig();
    void setAction(ACTION_TYPE action, vector<KeyCode> keys);
    void setDefaultKeys();
    void clearActionUp();
    void pollEvents(Event event);
	bool isActionKeyDown(ACTION_TYPE action);
    bool isActionKeyUp(ACTION_TYPE action); 	
};

ControlsManager.cpp:

#include "UltraEngine.h"
#include "ControlsManager.h"
//#include "SettingsManager.h"

ControlsManager::ControlsManager()
{
	init();
}

struct StructControlsManager : public ControlsManager {
};

void ControlsManager::init() {
	configPath = AppDir() + "/Saves/Config/Controls.json";
	setDefaultKeys();
	try
	{
		//configData = SettingsManager::getJson(configPath);
	}
	catch (const std::invalid_argument& e)
	{
		configData = json();
	}
}

std::shared_ptr<ControlsManager> ControlsManager::getInstance()
{
	static std::shared_ptr<ControlsManager> instance = std::make_shared<StructControlsManager>();
	return instance;
}

void ControlsManager::saveConfig()
{
	auto stream = WriteFile(configPath);
	stream->WriteString(configData.dump());
	stream->Close();
}

void ControlsManager::setAction(ACTION_TYPE action, vector<KeyCode> keys)
{
	actionsMap[action] = keys;
}

void ControlsManager::setDefaultKeys()
{
	setAction(ACTION_CAMERA_MOVE_FORWARD, { KEY_W, KEY_UP });
	setAction(ACTION_CAMERA_MOVE_BACKWARD, { KEY_S, KEY_DOWN });
	setAction(ACTION_CAMERA_MOVE_LEFT, { KEY_A, KEY_LEFT });
	setAction(ACTION_CAMERA_MOVE_RIGHT, { KEY_D, KEY_RIGHT });
	setAction(ACTION_CAMERA_ROTATE_LEFT, { KEY_Q });
	setAction(ACTION_CAMERA_ROTATE_RIGHT, { KEY_E });
	setAction(ACTION_CONFIRM, { KEY_SPACE });
	setAction(ACTION_WEAPON_FIRE, { KEY_F });
	setAction(ACTION_WEAPON_SPECIAL, { KEY_T });
	setAction(ACTION_WEAPON_OVERWATCH, { KEY_Y });
	setAction(ACTION_UNIT_SWITCH_NEXT, { KEY_TAB });
	setAction(ACTION_UNIT_SWITCH_PREVIOUS, { KEY_SHIFT });
	setAction(ACTION_INVENTORY, { KEY_I });
}


void ControlsManager::clearActionUp()
{
	for (auto& keyUp : keysUp)
	{
		keyUp.second = false;
	}
}

void ControlsManager::pollEvents(Event event)
{
	switch (event.id)
	{
	case EVENT_KEYDOWN:
	{
		keysDown[static_cast<KeyCode>(event.data)] = true;
		keysUp[static_cast<KeyCode>(event.data)] = false;
		break;
	}
	case EVENT_KEYUP:
	{
		keysUp[static_cast<KeyCode>(event.data)] = true;
		keysDown[static_cast<KeyCode>(event.data)] = false;
		break;
	}
	}
}

bool ControlsManager::isActionKeyDown(ACTION_TYPE action)
{
	if (actionsMap[action].empty()) return false;
	for (auto& key : actionsMap[action])
	{
		if (keysDown[key]) return true;
	}
	return false;
}

bool ControlsManager::isActionKeyUp(ACTION_TYPE action)
{
	if (actionsMap[action].empty()) return false;
	for (auto& key : actionsMap[action])
	{
		if (keysUp[key]) return true;
	}
	return false;
}

Game loop (without few parts):

  while (window->Closed() == false)
    {
        controlsManager->clearActionUp();
        while (PeekEvent())
        {
            const Event ev = WaitEvent();
            switch (ev.id)
            {   
            default:
                controlsManager->pollEvents(ev);
             	ui->ProcessEvent(ev);
                break;
            }
        }
        gameWorld->Update();
        gameWorld->Render(framebuffer);
    }

Use case:

auto controlsManager = ControlsManager::getInstance();
if (controlsManager->isActionKeyDown(ACTION_CAMERA_MOVE_LEFT)) entity->Move(-speed * 2, 0, 0);
if (controlsManager->isActionKeyDown(ACTION_CAMERA_MOVE_RIGHT)) entity->Move(speed * 2, 0, 0);
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

This is a good start, but I can see this not scaling well when implementing controllers and other input devices. Also, I would make a PollEvents() function for the key events

As much as I want to recycle my own library, I think I should make it like it's part of the engine itself. My current system is a DLL and uses raw pointers. Not sure how or if I'll add in controller support since Steam Input takes over XInput calls. 

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

12 hours ago, Josh said:

Use an STL map instead of iterating through the whole array!

The thing is, you need some sort of an array because you want to support multiple input values for the same action! This is how controller support works in my system. I check if ether the key or gamepad button is pressed. There are people who like to bind 2 different keys for the same thing.  Maybe std::multimap would be a better option here? 

The entire system should also be action based. We shouldn't need a reference or instance to the input system. It should be emitting an EVENT_INPUTACTION event sending the action pressed to whatever needs it. Not only this will help to avoid the use of singletons but doing it this way will also allow very easy integration with different controllers down the road such as Steam Input. 

Those are my thoughts as I am still sitting here thinking about how I'm going to solve this problem, 

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...