I'm creating a Space Invaders game with C# WinForms and when coding the movement of the player's cannon, I create this event handler:
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
Application.DoEvents();
System.Threading.Thread.Sleep(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
But going on different sites concerning how this is unreliable in C#, I am going to make sure to not use it from thereon. What other alternatives are there to use instead of Application.DoEvents within this instance?
I'd suggest to make that event handler async and use await Task.Delay() instead of Thread.Sleep():
private async void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
for (int i = 0; i < 500; i++)
{
if (e.KeyCode == Keys.Left)
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Right)
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
await Task.Delay(10); //Delays the movement by couple milliseconds to stop instant movement
}
if (e.KeyCode == Keys.Up)
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
}
This way, the control flow is returned to the caller and your UI thread has time to handle the other events (so no need for Application.DoEvents()). Then after (about) the 10ms, the control is returned and execution of that handler resumed.
There may more fine-tuning be necessary, because now of course you could manage to hit more keys while the method has not finished. How to handle that depends on the surroundings. You could declare a flag that signals current execution and refuses further method entries (no thread safety needed here as it's all happening sequentially on the UI thread).
Or instead of refusing re-entrance queue the keystrokes and handle them in another event, e.g. "idle" events (like Lasse suggested in the comments).
Note that an event handlers is one of the rare occasions where using async without returning a Task is ok.
Use a timer that will call the game processing each 20 milliseconds.
Within the KeyDown/KeyUp events just change the current state which is used by the game processing.
Sample code:
[Flags]
public enum ActionState
{
MoveLeft,
MeveRight,
FireLaser,
}
// stores the current state
private ActionState _actionState;
// set action state
private void Game_Screen_KeyDown(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState |= ActionState.MoveLeft;
break;
case Keys.Right:
_actionState |= ActionState.MoveRight;
break;
case Keys.Up:
_actionState |= ActionState.FireLaser;
break;
default:
break;
}
}
// remove action state
private void Game_Screen_KeyUp(object sender, KeyEventArgs e)
{
switch ( e.KeyCode )
{
case Keys.Left:
_actionState &= ~ActionState.MoveLeft;
break;
case Keys.Right:
_actionState &= ~ActionState.MoveRight;
break;
case Keys.Up:
_actionState &= ~ActionState.FireLaser;
break;
default:
break;
}
}
// called from a timer every 20 milliseconds
private void Game_Screen_LoopTimer_Tick(object sender, EventArgs e)
{
if ( _actionState.HasFlag( ActionState.MoveLeft ) && !_actionState.HasFlag( ActionState.MoveRight ) )
{
cannonBox.Location = new Point(cannonBox.Left - 2, cannonBox.Top); //Changes location of cannonBox to a new location to the left
}
if ( _actionState.HasFlag( ActionState.MoveRight ) && !_actionState.HasFlag( ActionState.MoveLeft ) )
{
cannonBox.Location = new Point(cannonBox.Left + 2, cannonBox.Top); //Changes location of cannonBox to a new location to the right
}
if ( _actionState.HasFlag( ActionState.FireLaser ) )
{
createLaser(); //Calls the method whenever Up arrow key is pressed
}
}
Application.DoEvents() interupts the execution of your method and the UI thread will process its events (including redrawing of the UI). From my experience there is nothing wrong with using it in the right places...
Using the 'async' pattern (as René Vogt suggested) is best practice to make reponsive UI's.
However. You have to ask yourself if you need a loop that checks 500 times, if the key down is left, right or up. Especially as it seems this loop is triggerd by a key down event...
It would be maybe easier if you make a 'while(true)' loop in the main and call Application.DoEvents from there.
Or you react on the key_down event and do on action at a time. => press left -> move one left -> press left again -> move one left more... and so on.
https://msdn.microsoft.com/en-us//library/system.windows.forms.application.doevents(v=vs.110).aspx
Related
I'm developing a kind of remote controller car.
I'm using a window CE device and Compact Framework 2.0 C#.
When Remote controller adjust the speed of car,
I'm using a sequential way like stairs.
However, It is sometimes going up and down 2 steps, 4 steps.
I would like to make only one step up and down from now step.
For example, now step is 4 then I just go to 3 or 5 not 8 or 1.
// Dealing with GPIO Input signal (detect push button)
private void smartGPIO1_EvtPortADatasChange(object sender, EventArgs e)
{
int iPortDatas;
SmartX.PORTDataEvtArgs PortDatas;
PortDatas = (SmartX.PORTDataEvtArgs)e;
iPortDatas = PortDatas.iPortDatas;
if (!test_start_Flag)
{
// User push the down button
if ((iPortDatas & 0x08) == 0x00)
{
motor_step = (motor_step <= -2) ? -2 : motor_step -= 1;
motor_handler(motor_step);
}
// User push the up button
if ((iPortDatas & 0x05) == 0x01)
{
motor_step = (motor_step >= 12) ? 12 : motor_step += 1;
motor_handler(motor_step);
}
}
}
// When button is pushed, send a command to car depends on the step
public int motor_handler(int step)
{
switch (step)
{
case 0:
// send command
break;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
case 9:
break;
case 10:
break;
case 11:
break;
case 12:
break;
break;
case -1:
break;
case -2:
break;
}
return step;
}
That code is for sure looped through on every update. Meaning that the single push of up or down button gets registered tens of times every second, so it seems like it goes more than one step at a time. You need to somehow register only new button presses.
//define something like this to the class level
Bool downIsPressed;
Bool upIsPressed;
//and then check and modify those values
if ((iPortDatas & 0x08) == 0x00 && downIsPressed == false)
{
motor_step = (motor_step <= -2) ? -2 : motor_step -= 1;
motor_handler(motor_step);
downIsPressed = true;
}
else
{
downIsPressed = false;
}
That should only register the button once and enable it once the button is released. There might be some cleaner and nicer looking way too, but hope that is of some help :)
Is it Recursive?
motor_handler(motor_step);
Calls private void smartGPIO1_EvtPortADatasChange(object sender, EventArgs e)
That's why each button push you get two steps.
The solution is a Private Boolean, typically I name IsRecursive:
class RacingCar {
private bool IsRecursive = false;
private void smartGPIO1_EvtPortADatasChange(object sender, EventArgs e)
{
if (IsRecursive) return;
IsRecursive = true;
....
IsRecursive = false;
}
}
Your code only ever increments or decrements the motor_step by 1 so if you're finding that the variable value jumps up by 2 etc then the code must have run twice in quick succession.
You have to find a way to "debounce" two events arriving at the same time. For example, when you step, record the current time. If the next event occurs less than one second since the previous event, ignore the event
//declare var and set to min value
private DateTime last_event_time = DateTime.MinValue;
private void smartGPIO1_EvtPortADatasChange(object sender, EventArgs e)
{
//debounce
if((DateTime.Now - last_event_time).TotalSeconds < 1)
return;
last_event_time = DateTime.Now;
int iPortDatas;
SmartX.PORTDataEvtArgs PortDatas;
Note: I've no idea if using DateTime will work on your platform, please take this code as pseudocode to demonstrate the concept
Debouncing like this will rely on the fact that the math on the time now vs time then needs to execute faster than events happen
If you want it to be so that step never changes as long as the button is held down you need to flip the logic around a bit so that as long as events keep arriving quickly, the code keeps returning - for that. You would update the time every time you return and only let the code proceed if more than X time has passed since the last event arrived
I have a camera that I move left and right with the arrows or 'A' and 'D' keys, unfortunately the camera response time is slower than the key held options in the application. I would like to move the camera smoothly while pressing the arrows but the program is stuck when I do it because it's too much pressing for the camera to process.
How can I hold the key and reduce the big number of commands that the camera gets and can't handle?
#Webnoob is wise ... i never knew they have a name.
You'll want to do something like :
private DateTime _lastValidKey;
private int delay;
private void SomePreviewKeyDown(object sender, KeyEventArgs e)
{
// follow line need tweaking to your liking and needs
if (now - delay > lastValidKey){
// update _lastValidKey and forward key
}
else {
// return, ignoring the keys
}
switch (e.Key)
{
case Key.Up:
// do something
break;
case Key.Down:
// do something
break;
}
}
basically ...you're filtering out extra keypresses
You could define a "time quantum" for user input processing. Let say we have some event (mouse, keyboard) that may cause lengthy processing in model and/or view. In simplest case we receive the keypress and process it immediately.
public void OnUserInput( )
{
DoSomething( );
}
private void DoSomething( )
{
// and here we do it
}
We may change this into:
public void OnUserInput( )
{
// DoSomething( );
StartTimedUpdate( );
}
private void DoSomething( )
{
// and here we do it
}
DispatcherTimer m_timer = null;
private void StartTimedUpdate()
{
if (m_timer == null)
{
m_timer = new System.Windows.Threading.DispatcherTimer();
m_timer.Tick += TimedRefresh;
m_timer.Interval = TimeSpan.FromMilliseconds(100);
m_timer.Start();
}
}
private void TimedRefresh(object sender, EventArgs e)
{
if (m_timer != null)
m_timer.Stop();
m_timer = null;
DoSomething();
}
You may also record all received keystrokes in list and process them in timer event handler. The 100 ms delay is just an example.
So in short i'm simply trying to move a rectangle around a Canvas object in a WPF application. What i have here is my KeyDown event function. The problem is, when i hold a key down for long, it launches this function over and over again rapidly and screws up my rectangle location code.
My theory/logic behind it:
BECAUSE WHEN YOU HOLD A BUTTON DOWN ON A KEYBOARD IT DOES NOT MOVE SMOOTHLY (TEST IT ON THE SCROLL BAR IN YOUR BROWSER, IT STARTS, pauses, THEN CONTINUES SMOOTHLY), i want it to start a forms timer that moves the object in the UI. Then when the KeyUp event happens, the timer STOPS.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
string msg;
string keystr = e.Key.ToString();
Key keyval = e.Key;
switch (keystr)
{
case "Down":
Console.WriteLine("Case 1");
Display.Content = "Down";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Down");
}
break;
case "Up":
Console.WriteLine("Case 2");
Display.Content = "Up";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Up");
}
break;
case "Left":
Console.WriteLine("Case 3");
Display.Content = "Left";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Left");
}
break;
case "Right":
Display.Content = "Right";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Right");
}
break;
}
}
public void Window_KeyUp(object sender, KeyEventArgs e)
{
Display.Content = "No key is pressed.";
foreach (Character character in creatures)
{
if (e.Key == Key.Right)
{
character.StopIt();
}
if (e.Key == Key.Left)
{
character.StopIt();
}
if (e.Key == Key.Up)
{
character.StopIt();
}
if (e.Key == Key.Down)
{
character.StopIt();
}
}
}
and just for reference if you need my rectangle class code i'll post what happens if the RIGHT arrow key is pressed:
Position is called
public void Position(String Direction)
{
if (Direction == "Right")
{
tmr = new System.Windows.Forms.Timer();
tmr.Interval = this.waitTime;
tmr.Tick += new EventHandler(GoRight);
tmr.Start();
}
}
GoRight is called:
public void GoRight(object sender, System.EventArgs e)
{
if (x < Background.ActualWidth - CharacterWidth)
{
if (goRight)
{
x += incrementSize;
CharacterImage.SetValue(Canvas.LeftProperty, x);
}
if (x > Background.ActualWidth - CharacterWidth)
{
goRight = false;
tmr.Stop();
}
}
}
Finally, StopIt is called in the KeyUp event:
public void StopIt()
{
tmr.Stop();
goRight = true;
goLeft = true;
goUp = true;
goDown = true;
}
I've only been learning c# for a couple months now so i'm trying to keep it relatively simple if possible, and only use .net.
Any help would be appreciated!!
EDIT:: MY SOLUTION:
I simply made a while(flag) loop around my switch case. Then i set flag = false within the cases. When Key UP is pressed i set flag equal to true again. YAY
I assume that you want your character to move on the initial KeyDown event. Then you want to ignore any subsequent KeyDown events until you get a KeyUp event.
So you can ignore the subsequent KeyDown events by checking e.IsRepeat e.g.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat) return;
// rest of your code...
BTW, the non-smooth movement that you observe when scrolling an application is caused by the keyboard repeat delay. You can set this in the keyboard properties or though http://msdn.microsoft.com/en-us/library/system.windows.systemparameters.keyboarddelay.aspx
I don't know enough about WPF to tell you what is going on, though you may be correct about the push button/pause thing. It depends on how WPF treats keypresses. My guess would be that it does so the same way most Microsoft forms work; it has a pause to keep you from typing multiple characters at a time. There may be a way around this but I'm not sure.
What I will say though is that you should use something designed for games. When I first tried creating games I did so in a style similar to what you are doing and it doesn't work. What you are using is designed for office software and will not give you access to what you need; at least not without fighting and workarounds. As was suggested by Alex Beisley look into XNA. It's a dead language unfortunately but it died fairly recently. It uses c#, was made by Microsoft, and is powerful enough to do what you want without fighting you and easy enough to use once you get the hang of it. It's a shame to see it killed off.
If you want to torture yourself then I'd suggest going the route I've been trying which is to learn C++ and DirectX. It is not easy and you will need to be patient and go through multiple tutorials (no one tutorial seems to do a good job explaining anything). DirectX and C++ are not going anywhere soon, so they are a safe bet if you are looking to get into a long term language.
So in short i'm simply trying to move a rectangle around a Canvas object in a WPF application. What i have here is my KeyDown event function. The problem is, when i hold a key down for long, it launches this function over and over again rapidly and screws up my rectangle location code.
My theory/logic behind it:
BECAUSE WHEN YOU HOLD A BUTTON DOWN ON A KEYBOARD IT DOES NOT MOVE SMOOTHLY (TEST IT ON THE SCROLL BAR IN YOUR BROWSER, IT STARTS, pauses, THEN CONTINUES SMOOTHLY), i want it to start a forms timer that moves the object in the UI. Then when the KeyUp event happens, the timer STOPS.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
string msg;
string keystr = e.Key.ToString();
Key keyval = e.Key;
switch (keystr)
{
case "Down":
Console.WriteLine("Case 1");
Display.Content = "Down";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Down");
}
break;
case "Up":
Console.WriteLine("Case 2");
Display.Content = "Up";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Up");
}
break;
case "Left":
Console.WriteLine("Case 3");
Display.Content = "Left";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Left");
}
break;
case "Right":
Display.Content = "Right";
foreach (Character character in creatures)
{
//character.buttondown = true;
character.Position("Right");
}
break;
}
}
public void Window_KeyUp(object sender, KeyEventArgs e)
{
Display.Content = "No key is pressed.";
foreach (Character character in creatures)
{
if (e.Key == Key.Right)
{
character.StopIt();
}
if (e.Key == Key.Left)
{
character.StopIt();
}
if (e.Key == Key.Up)
{
character.StopIt();
}
if (e.Key == Key.Down)
{
character.StopIt();
}
}
}
and just for reference if you need my rectangle class code i'll post what happens if the RIGHT arrow key is pressed:
Position is called
public void Position(String Direction)
{
if (Direction == "Right")
{
tmr = new System.Windows.Forms.Timer();
tmr.Interval = this.waitTime;
tmr.Tick += new EventHandler(GoRight);
tmr.Start();
}
}
GoRight is called:
public void GoRight(object sender, System.EventArgs e)
{
if (x < Background.ActualWidth - CharacterWidth)
{
if (goRight)
{
x += incrementSize;
CharacterImage.SetValue(Canvas.LeftProperty, x);
}
if (x > Background.ActualWidth - CharacterWidth)
{
goRight = false;
tmr.Stop();
}
}
}
Finally, StopIt is called in the KeyUp event:
public void StopIt()
{
tmr.Stop();
goRight = true;
goLeft = true;
goUp = true;
goDown = true;
}
I've only been learning c# for a couple months now so i'm trying to keep it relatively simple if possible, and only use .net.
Any help would be appreciated!!
EDIT:: MY SOLUTION:
I simply made a while(flag) loop around my switch case. Then i set flag = false within the cases. When Key UP is pressed i set flag equal to true again. YAY
I assume that you want your character to move on the initial KeyDown event. Then you want to ignore any subsequent KeyDown events until you get a KeyUp event.
So you can ignore the subsequent KeyDown events by checking e.IsRepeat e.g.
public void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.IsRepeat) return;
// rest of your code...
BTW, the non-smooth movement that you observe when scrolling an application is caused by the keyboard repeat delay. You can set this in the keyboard properties or though http://msdn.microsoft.com/en-us/library/system.windows.systemparameters.keyboarddelay.aspx
I don't know enough about WPF to tell you what is going on, though you may be correct about the push button/pause thing. It depends on how WPF treats keypresses. My guess would be that it does so the same way most Microsoft forms work; it has a pause to keep you from typing multiple characters at a time. There may be a way around this but I'm not sure.
What I will say though is that you should use something designed for games. When I first tried creating games I did so in a style similar to what you are doing and it doesn't work. What you are using is designed for office software and will not give you access to what you need; at least not without fighting and workarounds. As was suggested by Alex Beisley look into XNA. It's a dead language unfortunately but it died fairly recently. It uses c#, was made by Microsoft, and is powerful enough to do what you want without fighting you and easy enough to use once you get the hang of it. It's a shame to see it killed off.
If you want to torture yourself then I'd suggest going the route I've been trying which is to learn C++ and DirectX. It is not easy and you will need to be patient and go through multiple tutorials (no one tutorial seems to do a good job explaining anything). DirectX and C++ are not going anywhere soon, so they are a safe bet if you are looking to get into a long term language.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.W)
player1.moveUp();
if (e.KeyCode == Keys.NumPad8)
player2.moveUp();
}
In the above code the moveUp methods basically just increment a value. I want it so both keys can be pressed (or held down)at the same time and both events will trigger.
Thanks,
Nevik
Get the state of the keyboard and check for the status of the keys that you want.
Events are not the best way to go in gaming. You need faster response.
[DllImport("user32.dll")]
public static extern int GetKeyboardState(byte [] lpKeyState);
...
byte[] bCharData = new byte[256];
GetKeyboardState(bCharData);
Another way, taken from here,
[DllImport("user32.dll")]
static extern short GetKeyState(VirtualKeyStates nVirtKey);
...
public static bool IsKeyPressed(VirtualKeyStates testKey)
{
bool keyPressed = false;
short result= GetKeyState(testKey);
switch (result)
{
case 0:
// Not pressed and not toggled on.
keyPressed = false;
break;
case 1:
// Not pressed, but toggled on
keyPressed = false;
break;
default:
// Pressed (and may be toggled on)
keyPressed = true;
break;
}
return keyPressed;
}
More links.
Basically, these are already available on net. Try searching before asking. It will be faster :)
Let's assume you have a "game loop" that updates the object you're moving with the keyboard. The KeyDown event should change the object state to "moving upwards". And your loop then gives it new positions each time it runs.
The KeyUp event should change the state back to "idle". Iff the state is still "moving upwards".
You now no longer depend on a keystroke repeating to keep the object moving. And will have no trouble with the player pressing multiple keys at the same time.