I am trying to make a simple game in C# using Visual Studio Windows Form Application. I want to let the user be able to move the blue box upwards, rightwards, downwards, and leftwards freely using the corresponding keys.
I am using a Timer which detects the new location of the box every 0.1 seconds, and a keydown event that actually changes the location of the box.
The box needs to keep moving in the corresponding direction while the key is held down.
My problem is, my current program does the job except that when the user first presses a key, the box moves a little bit once and pauses for a moment before it keeps moving. I want to make this box move more smoothly from the first key press without pausing like that. This might be difficult to explain by words so I added a gif file.
Is there a way to fix this? Here is my current code.
private int posX, posY; //Both initialized in Form Load event
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up)
posY -= 3;
else if (e.KeyCode == Keys.Right)
posX += 3;
else if (e.KeyCode == Keys.Down)
posY += 3;
else if (e.KeyCode == Keys.Left)
posX -= 3;
}
//Timer ticks every 0.1 second
private void Timer_Tick(object sender, EventArgs e)
{
Box.Location = new Point(posX, posY);
labelPosX.Text = posX.ToString(); //Testing purposes
labelPosY.Text = posY.ToString(); //Testing purposes
}
I would love to use KeyDown event to achieve this, but if there is a better or more common way actually used in real game worlds, I would love to know about it too!
Use Keyboard.IsKeyDown method in your Timer_Tick method, and don't listen for the keydown event.
Like so:
double posX, posY;
private void Timer_Tick(object sender, EventArgs e)
{
double velocity = /*(speed: pixels per seconds)*/ 100 * /*(timer tick time in seconds)*/ 0.003;
if (Keyboard.IsKeyDown(Keys.Up))
{
posY -= velocity;
}
else if (Keyboard.IsKeyDown(Keys.Down))
{
posY += velocity;
}
//Also, don't put else here, so you can go diagonally.
if (Keyboard.IsKeyDown(Keys.Left))
{
posX -= velocity;
}
else if (Keyboard.IsKeyDown(Keys.Right))
{
posX += velocity;
}
Box.Location = new Point((int)posX, (int)posY);
labelPosX.Text = posX.ToString(); //Testing purposes
labelPosY.Text = posY.ToString(); //Testing purposes
}
public static class Keyboard
{
private static readonly HashSet<Keys> keys = new HashSet<Keys>();
public static void OnKeyDown(object sender, KeyEventArgs e)
{
if (keys.Contains(e.KeyCode) == false)
{
keys.Add(e.KeyCode);
}
}
public static void OnKeyUp(object sender, KeyEventArgs e)
{
if (keys.Contains(e.KeyCode))
{
keys.Remove(e.KeyCode);
}
}
public static bool IsKeyDown(Keys key)
{
return keys.Contains(key);
}
}
And to use the Keyboard class, sets the Form1's KeyDown and KeyUp events in the InitializeComponent method.
KeyDown += Keyboard.OnKeyDown;
KeyUp += Keyboard.OnKeyUp;
You can control it's speed by changing the velocity.
Related
Purpose of this code was to move a title(label) first rightwards until it hits the 600th pixel on the X axis and then leftwards until it hits the 27th pixel on the X axis of the form by using 2 timer tools and the Point class. One timer for going right and the other timer for going left. They should've work by swithing on and off consecutively after one another, however it does not work.
The label is stuck at 600th X location and does not move back to where it was.
The timer interval is 100 so it moves with a decent speed that allows us to see it moving.
namespace AlanCevreHesabiUygulamasi
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
if (label11Point.X == 27)
{
timer2.Stop();
timer1.Start();
}
if (label11Point.X == 599)
{
timer1.Stop();
timer2.Start();
}
}
Point label11Point = new Point(27, 32);
private void timer1_Tick(object sender, EventArgs e)
{
while (label11Point.X <= 600)
{
label12.Text = label11Point.X.ToString();
label11Point.X += 1;
label11.Location = label11Point;
break;
}
}
private void timer2_Tick(object sender, EventArgs e)
{
while (label11Point.X >= 27)
{
label12.Text = label11Point.X.ToString();
label11Point.X -= 1;
label11.Location = label11Point;
break;
}
}
}
}
Label is stuck at 600th pixel of the form, does not move back. How to make it work?
I'm surprised you see movement resulting from a while loop in a timer tick handler. Why have a timer if your are going to do it that way.
In this solution, I have a timer, and the movements happen during a timer tick. I also have three possible directions, Right, Left and Stopped (in case you want to have a start/stop button).
In the Windows Forms designer, I dropped both a label and a timer on the form. I left their properties alone except for the timer's Interval property (that I set to 10 (ms))
Then I added an enum and an instance of the enum as a field to the Form class:
public partial class Form1 : Form
{
private enum Direction { MoveRight, MoveLeft, Stopped }
Direction _direction;
public Form1()
{
InitializeComponent();
}
}
I double-clicked the Caption area of the form in the designer to create a form Load handler, and added a call to start the timer:
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
}
Finally, I double-clicked the timer to get a timer Tick handler and added some code:
private void timer1_Tick(object sender, EventArgs e)
{
var curLocation = label1.Location;
if (_direction == Direction.MoveRight && curLocation.X > 600)
{
_direction = Direction.MoveLeft;
}
else if (_direction == Direction.MoveLeft && curLocation.X < 27)
{
_direction = Direction.MoveRight;
}
int offset = _direction switch
{
Direction.MoveRight => 1,
Direction.MoveLeft => -1,
_ => 0,
};
curLocation.X += offset;
label1.Location = curLocation;
}
The _direction field determines if the label is moving to the right or the left.
How can you write the code without a timer?"
You asked "How can you write the code without a timer?" I'm still flabbergasted that your label moves as the result of a while loop in an event handler - something must have changed from my good-old understanding of Win32 processing.
Anyways, I cheat and await a call to Task.Delay instead of using a timer. Take my existing code and do the following:
Add two buttons to your form (One labeled Start (named StartBtn) and the other labeled Stop (named StopBtn).
Add another Direction-typed field to the class: Direction _previousDirection;
Comment out the call to timer1.Start(); in the Form1_Load handler
Comment out all the code in the timer1_Tick method (at this point, you could remove the timer from the form if you want)
Select both buttons (Start and Stop) and press <Enter>. This will bring up click handlers for both buttons.
Change the StopBtn button's handler to look like:
New Stop Button code:
private void StopBtn_Click(object sender, EventArgs e)
{
_previousDirection = _direction;
_direction = Direction.Stopped;
}
Change the StartBtn's handler to look like the following. Note that nearly everything in the while loop (except the call to Task.Delay) is the same as the previous timer tick handler code. Also note that I made the handler async void to allow for the await keyword to do it's magic.
Start Button code:
private async void StartBtn_Click(object sender, EventArgs e)
{
_direction = _previousDirection;
while (_direction != Direction.Stopped)
{
var curLocation = label1.Location;
if (_direction == Direction.MoveRight && curLocation.X > 600)
{
_direction = Direction.MoveLeft;
}
else if (_direction == Direction.MoveLeft && curLocation.X < 27)
{
_direction = Direction.MoveRight;
}
int offset = _direction switch
{
Direction.MoveRight => 1,
Direction.MoveLeft => -1,
_ => 0,
};
curLocation.X += offset;
label1.Location = curLocation;
await Task.Delay(10);
}
}
I solved it, I don't know why but in my opening post the Form1() function only makes one of the if() conditions work. However, putting the if() statements into the timers solved the problem. Now, the title goes back and forth in the specified x axis intervals.
namespace AlanCevreHesabiUygulamasi
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
timer1.Start();
}
Point label11Point = new Point(27, 32);
private void timer1_Tick(object sender, EventArgs e)
{
while (label11Point.X >= 27)
{
label12.Text = label11Point.X.ToString();
label11Point.X += 1;
label11.Location = label11Point;
break;
}
if (label11Point.X == 600)
{
timer2.Start();
timer1.Stop();
}
}
private void timer2_Tick(object sender, EventArgs e)
{
while (label11Point.X <= 600)
{
label12.Text = label11Point.X.ToString();
label11Point.X -= 1;
label11.Location = label11Point;
break;
}
if (label11Point.X == 27)
{
timer1.Start();
timer2.Stop();
}
}
I know the title was little unclear but i will explain.
My Example:
when i press 'D' my object will move to right,
same for 'A' just move to left,
but when i press 'Space' while holding 'A'/'D',the object will just o the function of the 'Space'.
if (e.KeyCode == Keys.A)
{
ply.Left -= 3;
}
if (e.KeyCode == Keys.D)
{
ply.Left += 3;
}
if (e.KeyCode == Keys.Space) {
ply.Top -= 100;
}
You can track the state of multiple keys through the use of both KeyUp and KeyDown by use of a HashSet<Keys>:
private HashSet<Keys> _keys = new HashSet<Keys>();
public void Control_KeyDown(object sender, KeyEventArgs e)
{
_keys.Add(e.KeyCode);
}
public void Control_KeyUp(object sender, KeyEventArgs e)
{
_keys.Remove(e.KeyCode);
}
public bool IsKeyDown(Keys keyCode)
{
return _keys.Contains(keyCode);
}
Now, whenever you want to check to see if a key is down, just do:
if(IsKeyDown(Keys.A))
{
ply.Left -= 3;
}
Keep in mind that if you just check key states in one of the key events, you will probably have issues with not detecting repeats properly. If you really want to handle this properly, you should use a Timer with a fairly small Interval that periodically polls with IsKeyDown and handles all your input.
With a Timer named timer1 that has an interval of 100, you can handle all your input like this, and have fairly responsive input to the full range of keys supported by your hardware.
private void timer1_Tick(object sender, EventArgs e)
{
if (IsKeyDown(Keys.A))
{
ply.Left -= 3;
}
if (IsKeyDown(Keys.S))
{
ply.Top += 3;
}
if (IsKeyDown(Keys.D))
{
ply.Left += 3;
}
if (IsKeyDown(Keys.W))
{
ply.Top -= 3;
}
}
As Bradley said, you could save all key events to a keystate cache.
This could look something like this pseudo code:
Event KeyDown(e) -> keystate[e.KeyCode] = true
Event KeyUp(e) -> keystate[e.KeyCode] = false
That way, you should always know every state.
I'm currently writing a program that holds down a mouse button when initially clicked, and continues to hold it until the user presses the mouse button for a second time.
The program works by detecting input globally using a MouseHookListener and then uses an input simulator to hold down the mouse button that has been pressed.
The program is able to hold down the mouse as intended, but there is an issue with the original mouse click that signals the program to simulate the button being held; it still gets carried out. I know that the MouseHookListener uses low level hooks to operate, but it's HookCallBack() method can't be overridden due to it being protected.
Is there any way to block out the original mouse input? Or is there a way to make the original input held in until the mouse is clicked once more?
This is the code I've produced, thus far (note - the mListener is being activated in a forum else where):
public MouseHold()
{
mListener = new MouseHookListener(new GlobalHooker());
mListener.MouseClick += mListener_MouseClick;
}
private bool isDown;
private int count = 0;
private InputSimulator sim = new InputSimulator();
public MouseHookListener mListener;
private void mListener_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (isDown)
{
isDown = false;
Console.Out.WriteLine(count);
}
else
{
Console.Out.WriteLine(count);
isDown = true;
sim.Mouse.LeftButtonDown();
}
}
}
I am the author of the library you mentioned https://github.com/gmamaladze/globalmousekeyhook.
To enable your usecase I have extended the library by one more event. Please note that the construction of listener has also slightly changed in the new version.
I beleave that with the example below you are able to accomplish your scenario without involving InputSimulator library.
How it works?
First mouse down set the flag m_SupressNextUp - down is fired.
Mouse button up which follows previous down is supressed and set the flag m_SupressNextDown
Second down is supressed too and reset the flag m_SupressNextUp
Second up is fired and reset m_SupressNextDown
System is returned to initial state.
internal class Sample
{
private IKeyboardMouseEvents m_GlobalHook;
private bool m_SupressNextUp;
private bool m_SupressNextDown;
public void Subscribe()
{
m_GlobalHook = Hook.GlobalEvents();
m_GlobalHook.MouseDownExt += GlobalHookMouseDownExt;
m_GlobalHook.MouseUpExt += GlobalHook_MouseUpExt;
}
void GlobalHook_MouseUpExt(object sender, MouseEventExtArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (m_SupressNextUp)
{
Console.WriteLine(#"First mouse up supress.");
e.Handled = true;
m_SupressNextDown = true;
}
else
{
Console.WriteLine(#"Second mouse up - make it heppen.");
m_SupressNextDown = false;
}
}
}
private void GlobalHookMouseDownExt(object sender, MouseEventExtArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (m_SupressNextDown)
{
Console.WriteLine(#"Second mouse down supress.");
e.Handled = true;
m_SupressNextUp = false;
}
else
{
Console.WriteLine(#"First mouse down - make it heppen.");
m_SupressNextUp = true;
}
}
}
public void Unsubscribe()
{
m_GlobalHook.MouseDownExt -= GlobalHookMouseDownExt;
m_GlobalHook.MouseUpExt -= GlobalHook_MouseUpExt;
m_GlobalHook.Dispose();
}
}
after reviewing the documentation for the input simulator I am guessing you need to reverse the mouse down input.
the source code for the input simulator has a mouse up method
private void mListener_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (isDown)
{
isDown = false;
Console.Out.WriteLine(count);
sim.Mouse.LeftButtonUp(); //maybe try this
}
else
{
Console.Out.WriteLine(count);
isDown = true;
sim.Mouse.LeftButtonDown();
}
}
}
I have a program where I want to move a Graphic with the keyboard keys. I originally had a function to move the graphic whenever pressed a button, but I abandoned that method to get around the keyboard repeat delay. Instead, I decided I would use a timer, which I would enable upon the KeyPess event, and disable on the KeyUp event. At first, I used 4 different timers for each different direction, and although it worked I noticed my program had begun to freeze quite often. I decided to use a single timer for all movements, and use if statements to determine the direction. Now, it seems that my Graphic doesn't move at all, even though all I did was copy and paste code.
enum Direction
{
Left, Right, Up, Down
}
private Direction _objectDirection;
int _x = 100, _y = 100;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Picture.MakeTransparent(Color.Transparent);
e.Graphics.DrawImage(Picture, _x, _y);
}
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.W)
{
if (timerAnimation.Enabled == false)
{
AnimationMaxFrame = 3;
timerAnimation.Enabled = true;
}
_objectDirection = Direction.Up;
timerMovement.Enabled = true;
}
//The rest of this code is omitted to save space, it is repeated 4 times with the only
//changes being the key pressed, and the object direction.
Invalidate();
}
void Form1_KeyUp(object sender, KeyEventArgs e)
{
timerAnimation.Enabled = false;
timerMovement.Enabled = false;
Picture = Idle;
this.Refresh();
}
private void timerMovement_Tick(object sender, EventArgs e)
{
if (_objectDirection == Direction.Up)
{
if (_y > 24)
{ _y = _y - 2; }
else
{ timerMovement.Enabled = false; }
//This if statement is to ensure that the object doesn't leave the form.
//I have tried removing them already, they're not the problem.
}
//Again, this is shortened to save space, it is repeated for each direction.
Invalidate();
}
What is preventing my graphic from moving, and is there a better way to do this? There is still a lot of functionality I want to add to this, but it's already freezing.
Not sure you are making a game with WinForms, but to the point...
You need to handle key pressed events, when a key pressed event fires set a boolean flag in your code depending on if the event was a press or release. Then in your update code check the flag and do your movement accordingly.
It would be something like this (example code):
bool moveup = false;
void KeyPressed(object sender, KeyEventArgs e)
{
// check for keys that trigger starting of movement
if (e.KeyCode == Keys.W) moveup = true;
}
void KeyReleased(object sender, EventEventArgs e)
{
// check for keys that trigger stopping of movement
if (e.KeyCode == Keys.W) moveup = false;
}
void TimerTick(obect sender, EventArgs e)
{
if (moveup)
{
// move your object
}
}
I am working on a gaming project with a base to help get me started. The base is overall extremely helpful, however there is a part of the code that confuses me as to how to manipulate correctly.
When this code is run, the player sprite will indefinitely move in a direction until the other key press is used. If a playerMoveY is added, it will get stuck moving in diagonals.
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Left)
{
playerMoveX = -1;
}
else if (e.KeyCode == Keys.Right)
{
playerMoveX = 1;
}
Without seeing your full code, it's going to be hard to say for sure, but it is probably because you don't have a KeyUp event to reset the move value.
Similarly to how you created a KeyDown event to use the Form1_KeyDown method, try something like this for KeyUp
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Left)
{
playerMoveX = 0;
}
else if (e.KeyCode == Keys.Right)
{
playerMoveX = 0;
}
}