How to use update to use mouse in my game properly - c#

I'm building a chess game now, and I have a problem.
I have this update function for the pawn:
public void update(GameTime gameTime)
{
MouseState ms = Mouse.GetState();
if (ms.LeftButton == ButtonState.Pressed)
{
if ((float)ms.X / 50 >= location.x && (float)ms.X / 50 <= location.x + 1 && (float)ms.Y / 50 >= location.y && (float)ms.Y / 50 <= location.y + 1)
{
if (!draw_spots)
{
draw_spots = true;
}
else
{
draw_spots = false;
}
}
else
{
spot sp = new spot(ms.X / 50, ms.Y / 50);
if (draw_spots)
{
draw_spots = false;
}
}
}
what it is supposed to do is to put draw_spots to true (meaning that the possible moving locations and eating location should be drawn) when pressing the left mouse click. change it to false when i press again so it should stop drawing those. the function work, but sometimes the squares that should turn to blue just flicker and turn off. My guess is that I press the left mouse button for an even number of update sequences (which makes it turn on and then off). any idea on how to solve it?
Thanks!
a video of the problem: https://www.youtube.com/watch?v=Awb4V1giV2Y&feature=youtu.be
you can see some presses that make the blue line appear and then disappear even though i pressed once

When you write code in an Update method you need to think about it as if it was inside an infinite loop like this:
while(true)
{
if (!draw_spots)
{
draw_spots = true;
}
else
{
draw_spots = false;
}
}
When you look at the logic in that simplistic manner it should be easy to see why you're getting the flickering behaviour. It's literally toggling the draw_spots variable on and off over and over again.
To keep with the infinite loop analogy lets take a look at what the code should be doing instead. There's essentially two things that should both be true when you want to "draw spots".
The left mouse button is pressed
The mouse cursor is within the square
Now, lets translate this logic into code.
while(true)
{
var ms = Mouse.GetState();
var leftButtonPressed = ms.LeftButton == ButtonState.Pressed;
var mouseInsideSquare = (float)ms.X / 50 >= location.x &&
(float)ms.X / 50 <= location.x + 1 &&
(float)ms.Y / 50 >= location.y &&
(float)ms.Y / 50 <= location.y + 1;
if (leftButtonPressed && mouseInsideSquare)
draw_spots = true;
else
draw_spots = false;
}
By keeping things as simple as possible and naming your bits of logic with extra variables the code gets a lot easier to read and understand.
I would even go as far as pulling out even more bits of logic into variables and simply set the value of draw_spots to the result like this:
var ms = Mouse.GetState();
var leftButtonPressed = ms.LeftButton == ButtonState.Pressed;
var mx = ms.X / 50f;
var my = ms.Y / 50f;
var mouseInsideSquare = mx >= location.x &&
mx <= location.x + 1 &&
my >= location.y &&
my <= location.y + 1;
draw_spots = leftButtonPressed && mouseInsideSquare;
It's a matter of taste which version of the above code you prefer. The point is to break your code up in a way that makes it easier to understand. I hope that helps.
The only other weird bit of your code is this line:
spot sp = new spot(ms.X / 50, ms.Y / 50);
but since the sp variable goes out of scope before it's used I'm going to assume you left it there while debugging something and it can be safely deleted.

I was able to fix this myself, what i did was as follows.
add a variable called oldms that saves the mouse state from the last update.
then in the if statement I asked if the mouse left key is not pressed, and also if mouse left key was pressed last update. which means, i pressed the mouse and i want it to draw now.

Related

Why won't Unity SetActive execute within Input.GetButtonDown()?

I'm completely stumped by this one. I have a book in game and when you are looking in its general direction while within a certain distance of it and press the Action button (e or left mouse click), it's supposed to display a UI panel (bookPage). Here's my code for it.
void Update () {
Vector3 direction = player.transform.position - this.transform.position;
angle = Vector3.Angle (direction, player.transform.forward);
distance = direction.magnitude;
if (angle >= 160 && distance <= 2 && !bookPage.activeSelf) {
if(Input.GetButtonDown("Action")) {
bookPage.SetActive (true);
}
}
if (bookPage.activeSelf && Input.GetButtonDown("Action")) {
bookPage.SetActive (false);
}
}
This doesn't work. Specifically, the line to set the page to active doesn't work. If it's open, it will close correctly. If I copy and paste bookPage.SetActive (true);
anywhere within this method besides within if(Input.getButtonDown("Action")){}, it will make the bookPage active. If I put Debug.Log("message"); within that if statement though, it will show up in the console. It's just the one line that sets bookPage to active that isn't working. I have no idea why. I've tried moving it to a different method then calling it and doing the same but using a Coroutine. Neither worked. I also tried using other keys which did work, but I need it to work with the action key. It's also worth noting that the action key works in other usage. I already use it to open doors. Has anyone else run into a problem like this?
When you call SetActive(true), activeSelf will return true for your second condition which will invoke SetActive(false) immediately after.
Change this;
if (angle >= 160 && distance <= 2 && !bookPage.activeSelf)
{
if (Input.GetButtonDown("Action"))
{
bookPage.SetActive(true);
}
}
if (bookPage.activeSelf && Input.GetButtonDown("Action"))
{
bookPage.SetActive(false);
}
To this;
if (angle >= 160 && distance <= 2 && !bookPage.activeSelf)
{
if (Input.GetButtonDown("Action"))
{
bookPage.SetActive(true);
}
}
else if (bookPage.activeSelf && Input.GetButtonDown("Action"))
{
bookPage.SetActive(false);
}

C# Moving panel with MouseMove event high CPU usage

So I have this custom panel that I am making act like a form (I'm experimenting) and whenever I move it around the screen with the mouse it results in 20%+ CPU usage. The snippet below is the cause because if I comment it out it works fine, but I obviously can't move the panel with the mouse then.
private void MoveWithEdgeLock()
{
targetLocation = new Point(Cursor.Position.X - downLocation.X, Cursor.Position.Y - downLocation.Y);
Point placement = targetLocation;
if (targetLocation.X <= EDGELOCK && targetLocation.X >= -EDGELOCK)
{
placement = new Point(0, placement.Y);
}
else if (targetLocation.X + Width >= Screen.PrimaryScreen.Bounds.Right - EDGELOCK && targetLocation.X + Width <= Screen.PrimaryScreen.Bounds.Right + EDGELOCK)
{
placement = new Point(Screen.PrimaryScreen.Bounds.Right - Width, placement.Y);
}
if (targetLocation.Y <= EDGELOCK && targetLocation.Y >= -EDGELOCK)
{
placement = new Point(placement.X, 0);
}
else if (targetLocation.Y + Height >= Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT - EDGELOCK && targetLocation.Y + Height <= (Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT) + EDGELOCK)
{
placement = new Point(placement.X, Screen.PrimaryScreen.Bounds.Bottom - TASKBAR_HEIGHT - Height);
}
Location = placement;
}
Even if I simply make it this;
Location = new Point(Cursor.Position.X - downLocation.X, Cursor.Position.Y - downLocation.Y);
It still results in high CPU usage.
Oh and I call either of these like so:
protected override void OnMouseMove(MouseEventArgs e)
{
if (canMove)
MoveWithEdgeLock();
base.OnMouseMove(e);
}
CanMove simply gets set via OnMouseDown/OnMouseUp.
Sorry for long lines, I'm used to writing on one line.
EDIT
As I have said in a comment, I have a borderless form taking up my entire screen - 1920x1080. That does have a BackgroundImage and I convert/force it to use Format32bppPArgb and resize it to Clientsize.
I have tried removing the BackgroundImage (so it was just a black back color) and that made no difference.
I have also tried remove all controls on the panel and that also made no difference.
I have even tried using the P/Invoke method as noted HERE, and it still results in high CPU Usage

How to do fast smooth animation in C# using WPF

I'm currently programming a game of Pong. I have the majority of the game completed but I've run into an annoying problem. I'm using a dispatcherTimer with priority set to Send and time span set to 1 millisecond. I'm animating the rectangle using up to dx=9 and dy=9 in order to make the ball move fast enough. Because of the large pixel jumps the ball appears skip across the screen instead of a smooth travel. According to math in 1 millisecond per cycle this ball should be moving MUCH faster than it is. I need to update the ball more often and move it by less...
Are there any suggestions on a better method to do this? Here is a snippet of what I have...
pongballTimer = new DispatcherTimer(DispatcherPriority.Send);
pongballTimer.Tick += new EventHandler(pongballTimer_Tick);
pongballTimer.Interval = new TimeSpan(0, 0, 0, 0, _balldt);
private void pongballTimer_Tick(object sender, EventArgs e)
{
double pongtop = Canvas.GetTop(PongBall);
double pongleft = Canvas.GetLeft(PongBall);
double paddletop = Canvas.GetTop( RightPaddle );
double paddleleft = Canvas.GetLeft( RightPaddle );
if (pongleft + PongBall.Width > paddleleft)
{
if (((pongtop < paddletop + RightPaddle.Height) && (pongtop > paddletop)) ||
((pongtop + PongBall.Height < paddletop + RightPaddle.Height) &&
(pongtop + PongBall.Height > paddletop)))
{
_dx *= -1;
SetBalldy(pongtop, PongBall.Height, paddletop, RightPaddle.Height);
_rightpoint++;
lblRightPoint.Content = _rightpoint.ToString();
meHitSound.Play();
}
else // The ball went past the paddle without a collision
{
RespawnPongBall(true);
_leftpoint++;
lblLeftPoint.Content = _leftpoint.ToString();
meMissSound.Play();
if (_leftpoint >= _losepoint)
LoseHappened("You Lost!!");
return;
}
}
if (pongleft < 0)
{
meHitSound.Play();
_dx *= -1;
}
if (pongtop <= _linepady ||
pongtop + PongBall.Height >= PongCanvas.Height - _linepady)
{
meDeflectSound.Play();
_dy *= -1;
}
Canvas.SetTop(PongBall, pongtop + _dy);
Canvas.SetLeft(PongBall, pongleft + _dx);
}
Instead of doing the movement in a timer callback, you may use one of the animation techniques that are built into WPF.
Start reading Property Animation Techniques Overview, perhaps with special attention to the last section Per-Frame Animation: Bypass the Animation and Timing System.
Then you may proceed to How to: Render on a Per Frame Interval Using CompositionTarget.

Why does it seem like operations are not being performed in the order of the code?

Here's some background. I'm working on game similar to "Collapse." Blocks fill up at the bottom and when all twelve blocks have been filled they push up on to the playfield. I have a counter called (intNextSpawn) that not only tells when to "push up" the next row, as well as calculating vectors for the graphics. It resets to 0 when the blocks have been pushed up.
I've added some debug text on the screen to try and see what is happening, but I can't seem to hunt down the issue. It almost seems like it is still incrementing the counter while trying to randomize the the block that's supposed to appear (things acting out of order). I end up getting "blank" blocks and it causes some really screwy effects while testing. It gets worse when jack up the speed.
I'm willing to post any additional code that might help. Below are the two main blocks where this is could happening. Is there something I might be doing wrong or may there be a way I can prevent this from happening (if that's what it's doing)?
Any help would be greatly appreciated.
Edit... The first code block is in the "Update" method
// Calculate time between spawning bricks
float spawnTick = fltSpawnSpeed * fltSpawnSpeedModifier;
fltSpawn += elapsed;
if (fltSpawn > spawnTick)
{
// Fetch a new random block.
poNextLayer[intNextSpawn] = RandomSpawn();
// Increment counter
intNextSpawn++;
// Max index reached
if (intNextSpawn == 12)
{
// Push the line up. Returns true if lines go over the top.
if (PushLine())
{
gmStateNew = GameState.GameOver;
gmStateOld = GameState.Playing;
}
// Game still in play.
else
{
// Reset spawn row to empty bricks.
for (int i = 0; i < 12; i++)
poNextLayer[i] = new PlayObject(ObjectType.Brick, PlayColor.Neutral, Vector2.Zero);
intNextSpawn = 0; // Reset spawn counter.
intLines--; // One less line to go...
}
}
fltSpawn -= spawnTick;
}
private bool PushLine()
{
// Go through the playfield top down.
for (int y = 14; y >= 0; y--)
{
// and left to right
for (int x = 0; x < 12; x++)
{
// Top row contains an active block (GameOver)
if ((y == 14) && (poPlayField[x, y].Active))
// Stop here
return true;
else
{
// Not bottom row
if (y > 0)
{
// Copy from block below
poPlayField[x, y] = poPlayField[x, y - 1];
// Move drawing position up 32px
poPlayField[x, y].MoveUp();
}
// Bottom row
else
{
// Copy from spawning row
poPlayField[x, y] = poNextLayer[x];
// Move drawing position up 32px (plus 4 more)
poPlayField[x, y].MoveUp(4);
// Make the block active (clickable)
poPlayField[x, y].Active = true;
}
}
}
}
// Game still in play.
return false;
}
So, the largest part of your problem is that you are decrementing some timers based on the size of a tick, and then running some comparisons. Wrather than do all that for timers (especially with the rounding and precision loss of float, etc...) instead just to time-basic comparisons.
Example, you do fltSpawn += elapsed and later fltSpawn -= spawnTick which will lead to floating point rounding / precission errors.
Try something more like this:
int64 spawnDelay = 1000000; // 10,000 ticks per ms, so this is 100ms
...
if (DateTime.Now() > nextSpawn)
{
// Fetch a new random block.
poNextLayer[intNextSpawn] = RandomSpawn();
...
// At some point set the next spawn time
nextSpawn += new TimeSpan(spawnDelay * spawnSpeedModifier);
}

How can I check that a window is fully visible on the user's screen?

Is there a way to check that a WinForm is fully visible on the screen (eg is not out of bounds of the screen?)
I've tried using SystemInformation.VirtualScreen for this, which works great as long as the virtual screen is a rectangle, but as soon as it's not (eg 3 screens in a L shape), SystemInformation.VirtualScreen returns the smallest rectangle containing all the visible pixels (so a window on the upper right corner of the L won't be visible although it's in the virtual screen)
The reason I'm trying to achieve this is that I'd like my program to open its child windows in the last location they were on, but I don't want those window to be out of view if the user changes is setup (eg unplugs the extra screen from his laptop)
Here's how I eventually did it :
bool isPointVisibleOnAScreen(Point p)
{
foreach (Screen s in Screen.AllScreens)
{
if (p.X < s.Bounds.Right && p.X > s.Bounds.Left && p.Y > s.Bounds.Top && p.Y < s.Bounds.Bottom)
return true;
}
return false;
}
bool isFormFullyVisible(Form f)
{
return isPointVisibleOnAScreen(new Point(f.Left, f.Top)) && isPointVisibleOnAScreen(new Point(f.Right, f.Top)) && isPointVisibleOnAScreen(new Point(f.Left, f.Bottom)) && isPointVisibleOnAScreen(new Point(f.Right, f.Bottom));
}
There might be some false positive if the user has a "hole" in his display setup (see example below) but I don't think any of my users will ever be in such a situation :)
[1]
[2][X][3]
Here's how I would do it:
This will move the control (form) inside the Display bounds as close as it can to the original location.
private void EnsureVisible(Control ctrl)
{
Rectangle ctrlRect = ctrl.DisplayRectangle; //The dimensions of the ctrl
ctrlRect.Y = ctrl.Top; //Add in the real Top and Left Vals
ctrlRect.X = ctrl.Left;
Rectangle screenRect = Screen.GetWorkingArea(ctrl); //The Working Area fo the screen showing most of the Ctrl
//Now tweak the ctrl's Top and Left until it's fully visible.
ctrl.Left += Math.Min(0, screenRect.Left + screenRect.Width - ctrl.Left - ctrl.Width);
ctrl.Left -= Math.Min(0, ctrl.Left - screenRect.Left);
ctrl.Top += Math.Min(0, screenRect.Top + screenRect.Height - ctrl.Top - ctrl.Height);
ctrl.Top -= Math.Min(0, ctrl.Top - screenRect.Top);
}
Of course to answer your original question instead of moving the control you could just check if any of the 4 Math.Min's returned something other than 0.
This is my solution. It solves the "hole" problem.
/// <summary>
/// True if a window is completely visible
/// </summary>
static bool WindowAllVisible(Rectangle windowRectangle)
{
int areaOfWindow = windowRectangle.Width * windowRectangle.Height;
int areaVisible = 0;
foreach (Screen screen in Screen.AllScreens)
{
Rectangle windowPortionOnScreen = screen.WorkingArea;
windowPortionOnScreen.Intersect(windowRectangle);
areaVisible += windowPortionOnScreen.Width * windowPortionOnScreen.Height;
if (areaVisible >= areaOfWindow)
{
return true;
}
}
return false;
}
I was trying to do this exact same thing detect if the window opened off screen then accordingly reposition it to the nearest location where it was previously found at. I look all over the internet and nothing worked from all the solutions people offered.
So i took it upon myself to make my own class that does just exactly this and it works 100%.
Here is my code
public static class ScreenOperations
{
public static bool IsWindowOnAnyScreen(Window Window, short WindowSizeX, short WindowSizeY, bool AutoAdjustWindow)
{
var Screen = System.Windows.Forms.Screen.FromHandle(new WindowInteropHelper(Window).Handle);
bool LeftSideTest = false, TopSideTest = false, BottomSideTest = false, RightSideTest = false;
if (Window.Left >= Screen.WorkingArea.Left)
LeftSideTest = true;
if (Window.Top >= Screen.WorkingArea.Top)
TopSideTest = true;
if ((Window.Top + WindowSizeY) <= Screen.WorkingArea.Bottom)
BottomSideTest = true;
if ((Window.Left + WindowSizeX) <= Screen.WorkingArea.Right)
RightSideTest = true;
if (LeftSideTest && TopSideTest && BottomSideTest && RightSideTest)
return true;
else
{
if (AutoAdjustWindow)
{
if (!LeftSideTest)
Window.Left = Window.Left - (Window.Left - Screen.WorkingArea.Left);
if (!TopSideTest)
Window.Top = Window.Top - (Window.Top - Screen.WorkingArea.Top);
if (!BottomSideTest)
Window.Top = Window.Top - ((Window.Top + WindowSizeY) - Screen.WorkingArea.Bottom);
if (!RightSideTest)
Window.Left = Window.Left - ((Window.Left + WindowSizeX) - Screen.WorkingArea.Right);
}
}
return false;
}
}
Usage: ScreenOperations.IsWindowOnAnyScreen(WPFWindow, WPFWindowSizeX, WPFWindowSizeY, true);
this will check if the window is offscreen at all, that being 1 pixel under the taskbar or 1 pixel off the users current monitor.
It detects which monitor the window if on first so it should work with multi-monitors.
this method returns true if the window is on the screen and false if its not.
The last parameter is for auto adjusting the window to the nearest part on the screen for you. if you put false for that parameter it will not adjust the window for you.
So this is a complete WPF solution to this issue, and WinForm converting should be easy if you know how to do it, Change Window to Form and FromHandle(Form.Handle) should work.
Check whether Screen.AllScreens.Any(s => s.WorkingArea.Contains(rect))

Categories

Resources