When programming with C/C++ I sometimes used to have a dictionary with references to functions according to the specified keys. However, I don't really know how to have something similar in C# allowing me dynamic keys.
I'm working on Unity and I'd like for exemple to invoke a function when I press left mouse click on my Unity project, is it possible ?
I'd really appreciate every comments on this.
I think you can use this:
First define a Delegate for your task (Jobs assigned to keys press in your example):
public delegate void MyJob();
Then define your dictionary:
Dictionary<string, MyJob> myBinding = new Dictionary<string, MyJob>()
{
{ "left", delegate() { Console.WriteLine("Left Key pressed"); } },
{ "right", delegate() { Console.WriteLine("Right Key pressed"); } },
{ "up", delegate() { Console.WriteLine("Up Key pressed"); } },
{ "down", delegate() { Console.WriteLine("Down Key pressed"); } }
};
And finally use it:
public void Mapper(string pressedKey)
{
myBinding[pressedKey]();
}
I hope this help you to solve your problem. If you need to more detail let me know.
You can use Dictionary<KeyCode, System.Action> signature to accomplish that. Get all the code codes and store them into an array with System.Enum.GetValues(typeof(KeyCode));. You can then pair your keys with your functions with keyCodeToFuncDic.Add(KeyCode.YourKeyCode, YourFunction);
In the Update function, use for loop to loop over the KeyCodes stored in the beginning. Check if any of those keys is pressed. If pressed, check if it is in the Dictionary. If the that KeyCode exist in the dictionary, use the pressed key to Invoke the function in the Dictionary value, which will eventually call the function that is stored in the Dictionary.
Code:
Dictionary<KeyCode, System.Action> keyCodeToFuncDic = new Dictionary<KeyCode, System.Action>();
Array allKeyCodes;
void Start()
{
//Get all codecodes
allKeyCodes = System.Enum.GetValues(typeof(KeyCode));
//Register Keycodes to functios
keyCodeToFuncDic.Add(KeyCode.Mouse0, myFunction); //Left mouse
keyCodeToFuncDic.Add(KeyCode.Mouse1, myFunction2); //Right mouse
}
void myFunction()
{
Debug.Log("Left Mouse Clicked");
}
void myFunction2()
{
Debug.Log("Right Mouse Clicked");
}
void Update()
{
foreach (KeyCode tempKey in allKeyCodes)
{
//Check if any key is pressed
if (Input.GetKeyDown(tempKey))
{
//Check if the key pressed exist in the dictionary key
if (keyCodeToFuncDic.ContainsKey(tempKey))
{
//Debug.Log("Pressed" + tempKey);
//Call the function stored in the Dictionary's value
keyCodeToFuncDic[tempKey].Invoke();
}
}
}
}
When I see "dynamic keys" I think for something else like having a dynamic type be the key for the dictionary. However what you need can be easily achieved. In C# "pointers to functions" are called delegates (but way better than C pointers to functions) thus you need a dictionary that has the char type as the key and one of those delegates as the value. In .net framework 4.0 and above there are predefined generic delegates called Func<T...> and Action<T...>. Basically if you want your function to return a value you will use Func and the Action will be used in the place of a void method.
So you can have something like this:
var keyMap = new Dictionary<char, Func<string, bool>>()
{
{'w', MoveUp},
{'s', MoveDown},
{'a', s => true}, // using lambda expression
{
'd', delegate(string s)
{
if (string.IsNullOrWhiteSpace(s) == false)
{
//
}
return true;
}
} // using anonymous method
};
// then you can call those like this
var allow = keyMap['w']("some input");
if (allow)
{
// ....
}
public bool MoveUp(string input)
{
return true;
}
public bool MoveDown(string input)
{
return true;
}
Here are the docs https://msdn.microsoft.com/en-us/library/bb549151(v=vs.110).aspx
Related
I'm trying to make a grid-based movement system, and I have a bug where the player will move an odd amount when multiple inputs come in. Is there a way to detect how many keys on the keyboard are being pushed?
For example, something like:
If(numberOfKeysDown > 1)
or something?
I know you can do touch counts if working with mobile, but I'm not sure about keys. Thanks for your time!
If you just want to actually test what you said it should be enough to check for Input.anyKey
Is any key or mouse button currently held down
if(Input.anyKey)
If you want to check specific keys you could use Input.GetKey and Linq Count like e.g.
// Fancy queries ;)
using System.Linq;
...
// The keycodes you wan to check
private HashSet<KeyCode> keysToCheck = { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
int numberOfKeysPressed;
private void Update ()
{
numberOfKeysPressed = keysToCheck.Count(key => Input.GetKey(key));
// This basically equals doing
// var numberOfKeysPressed = 0;
// foreach(var key in KeyToCheck)
//{
// if(Input.GetKey(key)) numberOfKeysPressed++;
//}
}
Or you could use Input.GetKeyDown and Input.GetKeyUp and do something like
private HashSet<KeyCode> keysToCheck = { KeyCode.W, KeyCode.A, KeyCode.S, KeyCode.D };
int numberOfKeysPressed;
private void Update ()
{
foreach (var key in KeyToCheck)
{
if(Input.GetKeyDown(key)) numberOfKeysPressed++;
if(Input.GetKeyUp(key)) numberOfKeysPressed--;
}
}
If you want all KeyCode values you could use
private void Awake ()
{
keysToCheck = new HashSet<KeyCode>((KeyCode[])Enum.GetValues(typeof(KeyCode)));
}
If you really want to get the amount of any key presses on the keyboard it would probably be more efficient to directly use Event.current and do something like
HashSet<string> currentlyPressedKeys = new HashSet<string>();
void OnGUI ()
{
Event e = Event.current;
switch(e.type)
{
case EventType.KeyDown:
var key = e.keyCode.ToString();
if(!currentlyPressedKeys.Contains(key)) currentlypressedKeys.Add(key);
break;
case EventType.KeyUp:
var key = e.keyCode.ToString();
if(currentlyPressedKeys.Contains(key)) currentlypressedKeys.Remove(key);
break;
}
}
Now wherever you need to know you can do e.g
if(currentlyPressedKeys.Count > XY)
I dont think theres is a specific method for this.
You could however write your own counter method using Input.GetKeyDown.
Like so
int GetKeysDownCount() {
var keysDown = 0;
if(Input.GetKeyDown(KeyCode.W)
keysDown++;
if(Input.GetKeyDown(KeyCode.A)
keysDown++;
if(Input.GetKeyDown(KeyCode.S)
keysDown++;
if(Input.GetKeyDown(KeyCode.D)
keysDown++;
return keysDown;
}
https://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html
I have a list of toggles which have parameters. I want to add and then remove listeners in runtime, but i want to keep those added via Unity Inspector. But the issue is that i can't remove listener with parameter int. I could use removeAll but removes all other listeners which i have on this toggles, which i don't want to. I just want this specific one
private void AddToggleEvents()
{
for (int i = 0; i < onOffInfoPointsGroups.Count; i++)
{
int index = i;
onOffInfoPointsGroups[index].onValueChanged.AddListener(func => OnOffInfoPointsGroup(index));
}
}
private void RemoveToggleEvents()
{
for (int i = 0; i < onOffInfoPointsGroups.Count; i++)
{
int index = i;
onOffInfoPointsGroups[index].onValueChanged.RemoveListener(func => OnOffInfoPointsGroup(index));
}
}
public void OnOffInfoPointsGroup(int index)
{
toggleGroups[index].SetActive(onOffInfoPointsGroups[index].isOn);
CommandPanel.instance.NeedToSave(true);
}
Here is an example of general usefulness that demonstrates a few key concepts:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[System.Serializable]
public class MyIntEvent : UnityEvent<int>
{
}
public class Example : MonoBehaviour
{
MyIntEvent m_MyEvent = new MyIntEvent();
UnityAction unsubscribe;
void Start()
{
Debug.Log("Starting. Press Any Key to increment counter. Press A to subscribe. Press Z to unsubscribe. Press Q to quit.");
// Add a default handler that won't be removed
m_MyEvent.AddListener((x) => { Debug.Log("The counter was incremented."); });
}
UnityAction Subscribe(UnityAction<int> fn)
{
// Add the listener
m_MyEvent.AddListener(fn);
// Return a parameterless action that, when called, will remove the listener.
return () => m_MyEvent.RemoveListener(fn);
}
static int counter = 0;
void Update()
{
if (Input.GetKeyDown("q") && m_MyEvent != null) {
Debug.Log("Quitting");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#endif
Application.Quit();
}
if (Input.GetKeyDown("a") && m_MyEvent != null) {
if (unsubscribe == null) {
Debug.Log("Subscribing");
unsubscribe = Subscribe((x) => { Debug.Log(string.Format("Current Counter Value: {0}.", x)); });
}
else {
Debug.LogWarning("Already Subscribed");
}
} else if (Input.GetKeyDown("z") && m_MyEvent != null) {
if (unsubscribe != null) {
Debug.Log("Unsubscribing");
unsubscribe();
unsubscribe = null;
}
else {
Debug.LogWarning("Not Subscribed");
}
} else if (Input.anyKeyDown && m_MyEvent != null) {
// Invoke the active listeners and pass them the current counter value
m_MyEvent.Invoke(++counter);
}
}
}
About the example
You can assign a Lambda to a UnityAction
UnityAction<int> fn = (x) => { Debug.Log(string.Format("Current Counter Value: {0}.", x)); };
This demonstrates the mechanics of assigning a lambda to a UnityAction<int> This is a stand-in for your onOffInfoPointsGroups[index].onValueChanged. (I think your value is a bool in your example, can't be sure. I'm using an int counter for this generic example.)
In the actual example we pass it as a parameter to a Subscribe function in a similar way.
Subscribe((x) => { Debug.Log(string.Format("Current Counter Value: {0}.", x)); });
Remove exactly what you Added
m_MyEvent.AddListener(fn);
m_MyEvent.RemoveListener(fn);
When you call RemoveListener, you must pass in the same thing that you passed in for AddListener. That's why we stash it in a variable: fn.
The following is an anti-pattern because you are removing something different from what you added. This will not work. The remove will silently remove nothing, leaving what was added still there--still listening.
m_MyEvent.AddListener((x) => { Debug.Log("OK"); });
m_MyEvent.RemoveListener((x) => { Debug.Log("Not OK"); }); // Wrong. Don't do this.
With minimal extra syntax you could adopt this pattern:
UnityAction<int> fn;
m_MyEvent.AddListener(fn = (x) => { Debug.Log("OK"); });
m_MyEvent.RemoveListener(fn);
But you still have to remember that it was m_MyEvent that you subscribed to. I actually like to go a step further and employ an unsubscriber pattern.
A pattern for subscriptions is to return a remover
This is a little extra, but I find it convenient. Employ a pattern wherein upon adding a listener, the subscribing function returns a parameterless action that, when called, will remove the listener. This is convenient because you don't need to retain the details about which event you subscribed to and what the listening function was. You can just call the unsubscriber function that was returned from the subscriber.
As a pattern, it might look like this:
UnityAction Subscribe(UnityAction<int> fn)
{
// Add the listener
m_MyEvent.AddListener(fn);
// Return an action that, when called, will remove the listener.
return () => m_MyEvent.RemoveListener(fn);
}
And when you call it, could would do:
UnityAction unsubscribe = Subscribe(() => Debug.Log("OK"));
And to remove the listener:
unsubscribe();
This example has the event baked into the Subscribe function, but it's not too hard to pass the event in separately too, to make something that is more generally useful.
The pattern is also not too bad to use in-line without any supporting infrastructure.
Your example
I might fix your example like this: I will unroll the subscriber pattern in-line now that you know what it is.
private UnityAction AddToggleEvents()
{
List<UnityAction> removers = new List<UnityAction>();
for (int i = 0; i < onOffInfoPointsGroups.Count; i++)
{
int index = i;
UnityAction<bool> fn;
onOffInfoPointsGroups[index].onValueChanged.AddListener(fn = func => OnOffInfoPointsGroup(index));
removers.Add(fn);
}
return () => removers.ForEach(f => f());
}
When you call AddToggleEvents(), you'd call it like this:
RemoveToggleEvents = AddToggleEvents();
And of course, you'll need to stash the unsubscriber action somewhere:
UnityAction RemoveToggleEvents; // TODO: Decide on a scope for this variable
And now removing all the listeners is easy.
RemoveToggleEvents();
Handle with care
🎗 Just take the usual care to avoid double-subscribing and overwriting your unsubscriber, avoiding null references, not creating closures on loop variables, etc.
In my game I need to check for a specific keyboard combination, lets say Left Shift + B.
If I do it normally with
if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.B)) {
Debug.Log("correct");
}
It will accept everything as long as it has those two controls, so Left Shift + B, Left Shift + C + B, Left Shift + any keyboard button to be honest + B will return true as well.
My question is if I can detect somehow if ONLY those two were pressed, I already tried going foreach char c in KeyCode and detecting whether that Input was my B and then setting bool to correct or false but that doesn't really work.
Any ideas?
Event.current and OnGUI
Though usually it is not used so often anymore you can check the input in OnGUI using Event.current.
Every time a key goes down, store it in a list of currently pressed keys. When this keys goes up remove it from the list. Then you can simply check if the list contains the according keys and if the length matches the expected key amount.
public HashSet<KeyCode> currentlyPressedKeys = new HashSet<KeyCode>();
private void OnGUI()
{
if (!Event.current.isKey) return;
if (Event.current.keyCode != KeyCode.None)
{
if (Event.current.type == EventType.KeyDown)
{
currentlyPressedKeys.Add(Event.current.keyCode);
}
else if (Event.current.type == EventType.KeyUp)
{
currentlyPressedKeys.Remove(Event.current.keyCode);
}
}
// Shift is actually the only Key which is not treated as a
// EventType.KeyDown or EventType.KeyUp so it has to be checked separately
// You will not be able to check which of the shift keys is pressed!
if (!Event.current.shift)
{
return;
}
// As said shift is check on another way so we want only
// exactly 1 key which is KeyCode.B
if (currentlyPressedKeys.Count == 1 && currentlyPressedKeys.Contains(KeyCode.B))
Debug.Log("Only Shift + B");
}
It has to be done in OnGUI since there might be multiple events in one single frame. This is exclusive and will ony fire while Shift + B is pressed.
If you rather put this somewhere in your scene and make the values static
public class KeysManager : MonoBehaviour
{
public static bool ShiftPressed;
public static HashSet<KeyCode> currentlyPressedKeys = new HashSet<KeyCode>();
private void OnGUI()
{
if (!Event.current.isKey) return;
if (Event.current.keyCode != KeyCode.None)
{
if (Event.current.type == EventType.KeyDown)
{
currentlyPressedKeys.Add(Event.current.keyCode);
}
else if (Event.current.type == EventType.KeyUp)
{
currentlyPressedKeys.Remove(Event.current.keyCode);
}
}
ShiftPressed = Event.current.shift;
}
}
Then you can as before use something like
private void Update()
{
if (KeysManager.ShiftPressed && KeysManager.currentlyPressedKeys.Count == 1 && KeysManager.currentlyPressedKeys.Contains(KeyCode.B))
{
Debug.Log("Only Shift + B exclusively should trigger this");
}
}
Iterating with Input.GetKey through all KeyCode
Alternatively you could as commented check all possible keys.
However, this might or might not be an issue regarding performance. You'll have to test that and decide whether it is acceptable in your specific case.
using System.Linq;
...
// will store all buttons except B and LeftShift
KeyCode[] otherKeys;
private void Awake ()
{
// This simply returns an array with all values of KeyCode
var allKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode));
// This uses Linq Where in order to only keep entries that are different from
// KeyCode.B and KeyCode.LeftShift
// ToArray finally converts the IEnumerable<KeyCode> into a KeyCode[]
otherKeys = allKeys.Where(k => k != KeyCode.B && k != KeyCode.LeftShift).ToArray();
}
private void Update()
{
if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.B) && !AnyOtherKeyPressed())
{
// Happens while ONLY LeftShift + B is pressed
}
}
// Return true if any other key
// is pressed except B and LeftShift
private bool AnyOtherKeyPressed()
{
foreach (var keyCode in otherKeys)
{
if(Input.GetKey(keyCode)) return true;
}
return false;
}
Maybe we worry too much and it doesn't matter (I woudln't believe that but just theoretically ^^) than you could even take it one level up and make it more flexible.
[Serializable]
public class KeyCombo
{
// Note I'll be lazy here .. you could create a custom editor
// for making sure each keyCode is unique .. but another time
public List<KeyCode> keyCodes = new List<KeyCode>();
// This will show an event in the Inspector so you can add callbacks to your keyCombos
// this is the same thing used in e.g. Button onClick
public UnityEvent whilePressed;
// Here all other keyCodes will be stored
[HideInInspector] public KeyCode[] otherKeys;
// Return true if any other key
// is pressed except B and LeftShift
public bool AnyOtherKeyPressed()
{
foreach (var keyCode in otherKeys)
{
if (Input.GetKey(keyCode)) return true;
}
return false;
}
}
public List<KeyCombo> keyCombos = new List<KeyCombo>();
private void Awake()
{
// This simply returns an array with all values of KeyCode
var allKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode));
foreach (var keyCombo in keyCombos)
{
// This uses Linq Where in order to only keep entries that are different from
// the ones listed in keyCodes
// ToArray finally converts the IEnumerable<KeyCode> into a KeyCode[]
keyCombo.otherKeys = allKeys.Where(k => !keyCombo.keyCodes.Contains(k)).ToArray();
}
}
private void Update()
{
foreach (var keyCombo in keyCombos)
{
if (keyCombo.keyCodes.All(Input.GetKey) && !keyCombo.AnyOtherKeyPressed())
{
keyCombo.whilePressed.Invoke();
}
}
}
With this you can now add multiple KeyCombos and check them individually - However be aware that every additional keyCombo also means one additional iteration through all other keys so ... it's far away from perfect.
You can use Event.current.modifiers if you're doing combined keystrokes like Alt + Q etc. Something like this
if (!Event.current.isKey || Event.current.keyCode == KeyCode.None) return;
switch (Event.current.type) {
case EventType.KeyDown:
if (Event.current.modifiers == EventModifiers.Alt) {
switch (Event.current.keyCode) {
case KeyCode.Q:
break;
default:
break;
}
}
break;
default:
break;
}
Is there a concise way to loop over true/false in C#?
I have ~20 lines of code in a unit test I'd rather not duplicate to toggle one boolean true/false.
I could break it off into a function and call it twice, but meh. This code feels more like I'm iterating over possible values than performing a distinct action with different parameters. Even if I had a function, I'd prefer the syntax of looping over the possible values rather than just calling it twice.
I could write a for loop like so...
bool toggle;
for (int i = 0; i < 2; i++)
{
toggle = i == 1;
}
But that doesn't seem very clean.
I like this syntax:
for (bool b : { false, true }) { /* ... */ }
But it doesn't look like that will compile in C#.
Edit:
Following Jeroen's suggestion about local functions and Dmitry's answer, this is the route I went:
[TestMethod]
public void GetAndSetValue()
{
foreach (bool toggle in new [] { false, true })
{
GetAndSetValue(toggle);
}
void GetAndSetValue(bool toggle)
{
// details not important
}
}
Reasonable coders can debate whether the loop reads more easily than two function calls:
GetAndSetValue(false);
GetAndSetValue(true);
I like the loop better, so I'll roll with it until someone complains. Cheers!
Correct syntax will be foreach, not for:
foreach (bool b in new [] { false, true }) {
/* ... */
}
While I think simply writing a parametrized function is definitely the correct approach, the closest to that C++11 syntax that you can get in C# would be:
foreach (bool value in new [] { false, true })
{
// ...
}
I would probably just do it this way, either with a local function:
[TestMethod]
public void GetAndSetValue()
{
GetAndSetValue(false);
void GetAndSetValue(bool toggle)
{
// details not important
if (!toggle)
GetAndSetValue(true);
}
}
Or "old" school with a private method.
[TestMethod]
public void GetAndSetValue()
{
GetAndSetValue(false);
}
private void GetAndSetValue(bool toggle)
{
// details not important
if (!toggle)
GetAndSetValue(true);
}
In my Unity project, I am creating toggles dynamically based on a JSON response, and then adding a listener to each toggle. Right now, each time I click the toggle, it successfully calls the SomeListener function. However, I am only able to pass the value of the toggle (true or false), and ideally I would like to be able to pass both the value of the toggle and an extra argument (student._id) to the listener.
foreach(var student in students) {
GameObject studentObject = (GameObject) Instantiate(StudentAttendancePreFab, currentPos, transform.rotation);
studentObject.GetComponent<AttendanceItem> ().studentId = student._id;
studentObject.GetComponent<Toggle> ().isOn = student.attending;
studentObject.GetComponent<Toggle> ().onValueChanged.AddListener(SomeListener);
//is there some way to do this?
//studentObject.getComponent<Toggle> ().onValueChanged.AddListener(SomeListener, student._id);
}
Listener for each toggle.
void SomeListener (bool isClicked) {
Debug.Log(isClicked);
}
//is there some way to do this?
//void SomeListener(bool isClicker, string studentId) {
//something
//}}
try
studentObject.GetComponent<Toggle> ().onValueChanged.AddListener((id)=> SomeListener(student._id));
and
void SomeListener (float id) {
Debug.Log("Student id?:"+id);
}
these are called lambda expressions, and you can find more info about them here
No, you can't do it directly but here is a way to do it. Try the following (I haven't tested it, but you will get the idea). You will have to declare a public field to your student class of type UnityEngine.Events.UnityAction so as to store a reference to the action related to your student and remove the listener later if you need to.
foreach(var student in students) {
// ..
string id = student._id ;
student.listener = MyStudentListener( id ) ;
studentObject.GetComponent<Toggle> ().onValueChanged.AddListener(student.listener);
}
// ...
private UnityEngine.Events.UnityAction<bool> MyStudentListener( string id )
{
return (val) => SomeListener( val, id ) ;
}
void SomeListener(bool isClicker, string studentId) {
// something
}}
Note : The MyStudentListener function can be omitted if you want :
student.listener = (val) => SomeListener( val, id ) ;