Related
I've made a paint clone and want to clear the bord when you shake the form, (so by holding mouse1 down on the top bar and moving it right and left fast).
I tried to compare two points on the x-axis, the points are set based on the mouse position and tested against a treshold. And I added a combo so you need to overcome the treshold multiple times to avoid accidental activation, to help with this there is also a timer that resets the combo.
The problem is that it sees fast movement to one side as a shake I still want the user to be able to move the form without clearing the canvas.
int firstpos = 0;
int secondpos = 0;
bool isfirst = true;
int combo = 0;
int threshold = 50;
Point lastPoint;
private void TopBar_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)//to move the form
{
this.Left += e.X - lastPoint.X;
this.Top += e.Y - lastPoint.Y;
}
if (isfirst)
{
firstpos = e.X;
isfirst = false;
}
else
{
secondpos = e.X;
int diff = secondpos - firstpos;
if (diff < 0) diff *= -1;//make positive
if (diff >= threshold)
{
combo++;
Thread t = new Thread(startdecay);
t.Start();
}
if (combo == 4)
{
canvas.Invalidate();//clear the canvas
combo = 0;
}
isfirst = true;
}
}
void startdecay()
{
Thread.Sleep(1000);
combo = 0;
}
private void TopBar_MouseDown(object sender, MouseEventArgs e)
{
lastPoint = new Point(e.X, e.Y);
}
the form
isfirst == true: Save firstpos.
Set isfirst = false.
isfirst == false: Get secondpos.
Get diff.
if Math.Sign(diff) != saveddiff {
increment shakecounter.
Set saveddiff = Math.Sign(diff).
}
Set firstpos = secondpos.
Accept when shakecounter over some value desired.
Don't forget to reset everything when Mouse Button goes up.
I am having a bit of a small issue performing a certain task in my winforms application.
I am basically attempting to recreate a "Top-View RTS Map", on a winform. In order to save memory, not all tiles of the "Map" are displayed on the screen. Only the ones that fit within the viewport. Therefore, I am trying to allow the user to perform a pan/scroll on the displayed tiles in order to navigate the entire map!
Right now, I am doing this by creating and displaying GroupBox controls dynamically at runtime. These represent the tiles...
I have created my own objects to support all of this (contains screen coordinates, Row and Col info, etc.)
Here is how I am currently accomplishing all of this, in pseudo-code:
Creating the form, tiles and the map in general
I create a winforms form that is 600px X 600px.
I create a new "Map" (using a List<MapTile>) that is 100 tiles by 100 tiles (for testing) on form load and save that into a variable.
I keep track of the displayed tiles via another list (or property that derives from the main list bool MapTile.isDrawn)
Each tile is visually made of a GroupBox control that is 100px
X 100px (so [7 X 7] of them fit on screen)
To start, I find the center MapTile (tile [50, 50]) in the "Map", create the GroupBox for it and place that in the middle of the form,
I then add the other tiles/controls necessary to fill in the form (center - 3 tiles, center + 3 tiles (up, down, left, and right)).
Each tile, of course, subscribes to the proper mouse events to perform a drag
When the user mouse drags a tile, all other tiles being displayed follow suit/follow the leader by updating all "displayed tiles" coordinates to match the movement that was made by the "dragged" tile.
Managing Displayed Tiles
While the GroupBox tiles are being dragged/moved, I perform a check to see if the tiles that are on the outer edge of the viewport are within its bounds.
If, as an example, the top-left-most tile's right edge falls outside the bounds of the left edge of the viewport, I remove the entire left column tiles, and add the entire right column tiles programmatically. The same goes all directions (up, down, left and right).
So far, this works fine as long as I don't go too fast... however, when I drag the tiles "too fast" passed an outer edge (e.g.: where point 2 ci-dessus would apply), it seems that the application is unable to keep up because it doesn't add the column or row where they should be on the form, and other times, it does not have time to remove all controls of a row or column and I end up with controls that are still on the screen when they shouldn't be there. At that point the entire grid/map is off balance and stops working as intended because either the events that should fire on one edge don't (the tiles are not present) and/or there are now multiple controls with the same name on the form and the removal or referencing fails...
While I am well aware that winforms is not designed to perform intensive GPU/GDI operations, you would think that something this simple would still be easily do-able in winforms?
How would I go about making this more responsive at runtime? Here's my entire set of code:
Form code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RTSAttempt
{
public enum DrawChange
{
None,
Rem_First_Draw_Last,
Rem_Last_Draw_First
};
public partial class Form1 : Form
{
public string selected { get; set; }
private int _xPos { get; set; }
private int _yPos { get; set; }
private bool _dragging { get; set; }
public List<MapTile> mapTiles { get; set; }
public List<MapTile> drawnTiles { get { return this.mapTiles.Where(a => a.Drawn == true).ToList(); } }
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//init globals
this.selected = "";
this._dragging = false;
this.mapTiles = new List<MapTile>();
//for testing, let's do 100 x 100 map
for (int i = 0; i < 100; i++)
{
for (int x = 0; x < 100; x++)
{
MapTile tile = new MapTile(x, i, false, -1, -1, false);
this.mapTiles.Add(tile);
}
}
GenerateStartupTiles();
}
/// <summary>
/// Used to generate the first set of map tiles on screen and dispaly them.
/// </summary>
private void GenerateStartupTiles()
{
//find center tile based on list size
double center = Math.Sqrt(this.mapTiles.Count);
//if not an even number of map tiles, we take the next one after the root.
if (this.mapTiles.Count % 2 != 0)
center += 1;
//now that we have the root, we divide by 2 to get the true center tile.
center = center / 2;
//get range of tiles to display...
int startat = (int)center - 3;
int endat = (int)center + 3;
//because the screen is roughly 600 by 600, we can display 7 X 7 tiles...
for (int row = 0; row < 7; row++)
{
for (int col = 0; col < 7; col++)
{
//get the current tile we are trying to display.
MapTile tile = mapTiles.First(a => a.Row == (startat + row) && a.Col == (startat + col));
//create and define the GroupBox control we use to display the tile on screen.
GroupBox pct = new GroupBox();
pct.Width = 100;
pct.Height = 100;
//find start position on screen
if (row == 0)
pct.Top = -50;
else
pct.Top = -50 + (row * 100);
if (col == 0)
pct.Left = -50;
else
pct.Left = -50 + (col * 100);
tile.X = pct.Left;
tile.Y = pct.Top;
pct.Name = tile.ID;
pct.Tag = Color.LightGray;
//subscribe to necessary events.
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = tile.DisplayID;
//add the tile to the screen
this.Controls.Add(pct);
//set the tile to Drawn mode...
tile.Drawn = true;
}
}
}
private void Pct_MouseUp(object sender, MouseEventArgs e)
{
//self explanatory
if (this._dragging)
{
Cursor.Current = Cursors.Default;
this._dragging = false;
}
}
private void Pct_MouseMove(object sender, MouseEventArgs e)
{
var c = sender as GroupBox;
if (!_dragging || null == c) return;
//get original position, and movement step/distance for calcs.
int newTop = e.Y + c.Top - _yPos;
int newLeft = e.X + c.Left - _xPos;
int movedByX = this.drawnTiles.First(a => a.ID.ToString() == c.Name).X;
int movedByY = this.drawnTiles.First(a => a.ID.ToString() == c.Name).Y;
movedByY = newTop - movedByY;
movedByX = newLeft - movedByX;
//perform all tile movements here
MoveAllTiles(movedByX, movedByY);
}
/// <summary>
/// This method performs all tile movements on screen, and updates the listing properly.
/// </summary>
/// <param name="X">int - the amount fo pixels that the dragged tile has moved horizontally</param>
/// <param name="Y">int - the amount fo pixels that the dragged tile has moved vertically</param>
private void MoveAllTiles(int X, int Y)
{
//used to single out the operation, if any, that we need to do after this move (remove row or col, from edges)
DrawChange colAction = DrawChange.None;
DrawChange rowAction = DrawChange.None;
//move all tiles currently being displayed first...
for (int i = 0; i < this.drawnTiles.Count; i++)
{
//first, determine new coordinates of tile.
drawnTiles[i].Y = drawnTiles[i].Y + Y;
drawnTiles[i].X = drawnTiles[i].X + X;
//find the control
GroupBox tmp = this.Controls.Find(drawnTiles[i].ID, true)[0] as GroupBox;
//perform screen move
tmp.Top = drawnTiles[i].Y;
tmp.Left = drawnTiles[i].X;
tmp.Refresh();
}
//dtermine which action to perform, if any...
if (drawnTiles.Last().Y > this.Height)
rowAction = DrawChange.Rem_Last_Draw_First;
else if ((drawnTiles.First().Y + 100) < 0)
rowAction = DrawChange.Rem_First_Draw_Last;
else
rowAction = DrawChange.None;
if ((drawnTiles.First().X + 100) < 0)
colAction = DrawChange.Rem_First_Draw_Last;
else if (drawnTiles.Last().X > this.Width)
colAction = DrawChange.Rem_Last_Draw_First;
else
colAction = DrawChange.None;
//get currently dispalyed tile range.
int startRow = this.drawnTiles.First().Row;
int startCol = this.drawnTiles.First().Col;
int endRow = this.drawnTiles.Last().Row;
int endCol = this.drawnTiles.Last().Col;
//perform the correct action(s), if necessary.
if (rowAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first row of tiles from the screen
this.drawnTiles.Where(a => a.Row == startRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last row of tiles on screen...
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == endRow + 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.Last().Y + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (rowAction == DrawChange.Rem_Last_Draw_First)
{
//remove last row of tiles
this.drawnTiles.Where(a => a.Row == endRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first row of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == startRow - 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.First().Y - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
if (colAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first column of tiles
this.drawnTiles.Where(a => a.Col == startCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == endCol + 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.Last().X + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (colAction == DrawChange.Rem_Last_Draw_First)
{
//remove last column of tiles
this.drawnTiles.Where(a => a.Col == endCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == startCol - 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.First().X - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
ToolTip tt = new ToolTip();
tt.SetToolTip(pct, pct.Name);
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
}
private void Pct_MouseDown(object sender, MouseEventArgs e)
{
//self explanatory
if (e.Button != MouseButtons.Left) return;
_dragging = true;
_xPos = e.X;
_yPos = e.Y;
}
private void Pct_Click(object sender, EventArgs e)
{
//changes the border color to reflect the selected tile...
if (!String.IsNullOrWhiteSpace(selected))
{
if (this.Controls.Find(selected, true).Length > 0)
{
GroupBox tmp = this.Controls.Find(selected, true)[0] as GroupBox;
ControlPaint.DrawBorder(tmp.CreateGraphics(), tmp.ClientRectangle, Color.LightGray, ButtonBorderStyle.Solid);
}
}
GroupBox pct = sender as GroupBox;
ControlPaint.DrawBorder(pct.CreateGraphics(), pct.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
this.selected = pct.Name;
}
private void Pct_Paint(object sender, PaintEventArgs e)
{
//draws the border based on the correct tag.
GroupBox pct = sender as GroupBox;
Color clr = (Color)pct.Tag;
ControlPaint.DrawBorder(e.Graphics, pct.ClientRectangle, clr, ButtonBorderStyle.Solid);
}
private void Pct_MouseLeave(object sender, EventArgs e)
{
//draws the border back to gray, only if this is not the selected tile...
GroupBox pct = sender as GroupBox;
if (this.selected != pct.Name)
{
pct.Tag = Color.LightGray;
pct.Refresh();
}
}
private void Pct_MouseEnter(object sender, EventArgs e)
{
//draws a red border around the tile to show which tile the mouse is currently hovering on...
GroupBox pct = sender as GroupBox;
pct.Tag = Color.Red;
pct.Refresh();
}
}
}
MapTile object
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RTSAttempt
{
public class MapTile
{
/// <summary>
/// Represents the row of the tile on the map
/// </summary>
public int Row { get; set; }
/// <summary>
/// Represents the column of the tile on the map
/// </summary>
public int Col { get; set; }
/// <summary>
/// Represents the ID of this tile ([-1,-1], [0,0], [1,1], etc
/// </summary>
public string ID { get { return "Tile_" + this.Row + "_" + this.Col; } }
public string DisplayID { get { return this.Row + ", " + this.Col; } }
/// <summary>
/// If this tile is currently selected or clicked.
/// </summary>
public bool Selected { get; set; }
/// <summary>
/// Represents the X screen coordinates of the tile
/// </summary>
public int X { get; set; }
/// <summary>
/// Represents the Y screen coordinates of the tile
/// </summary>
public int Y { get; set; }
/// <summary>
/// Represents whether this tile is currently being drawn on the screen.
/// </summary>
public bool Drawn { get; set; }
public MapTile(int idCol = -1, int idRow = -1, bool selected = false, int screenX = -1, int screenY = -1, bool drawn = false)
{
this.Col = idCol;
this.Row = idRow;
this.Selected = selected;
this.X = screenX;
this.Y = screenY;
this.Drawn = drawn;
}
public override bool Equals(object obj)
{
MapTile tmp = obj as MapTile;
if (tmp == null)
return false;
return this.ID == tmp.ID;
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
}
I'd create the grid using (DataGridView, TableLayoutPanel, GDI+, or whatever) and then in the drag and drop, just calculate the new indexes and update the indexes, without moving the grid.
Example
The following example shows how to do it using a TableLayoutPanel:
Assign a fixed size to cells
Build the grid to fill the form
When form resizes, rebuild the grid
In mouse down capture the mouse down point and current top left index of grid
In mouse move, calculate the new index based on mouse movement and update index
In cell paint of the panel, draw the indexes
Here is the code:
int topIndex = 0, leftIndex = 0;
int originalLeftIndex = 0, originalTopIndex = 0;
int cellSize = 100;
Point p1;
TableLayoutPanel panel;
void LayoutGrid()
{
panel.SuspendLayout();
var columns = (ClientSize.Width / cellSize) + 1;
var rows = (ClientSize.Height / cellSize) + 1;
panel.RowCount = rows;
panel.ColumnCount = columns;
panel.ColumnStyles.Clear();
panel.RowStyles.Clear();
for (int i = 0; i < columns; i++)
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, cellSize));
for (int i = 0; i < rows; i++)
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, cellSize));
panel.Width = columns * cellSize;
panel.Height = rows * cellSize;
panel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
panel.ResumeLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
panel = new MyGrid();
this.Controls.Add(panel);
LayoutGrid();
panel.MouseDown += Panel_MouseDown;
panel.MouseMove += Panel_MouseMove;
panel.CellPaint += Panel_CellPaint;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (panel != null)
LayoutGrid();
}
private void Panel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
var g = e.Graphics;
TextRenderer.DrawText(g, $"({e.Column + leftIndex}, {e.Row + topIndex})",
panel.Font, e.CellBounds, panel.ForeColor);
}
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var dx = (e.Location.X - p1.X) / cellSize;
var dy = (e.Location.Y - p1.Y) / cellSize;
leftIndex = originalLeftIndex - dx;
topIndex = originalTopIndex - dy;
panel.Invalidate();
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
p1 = e.Location;
originalLeftIndex = leftIndex;
originalTopIndex = topIndex;
}
To prevent flicker:
public class MyGrid : TableLayoutPanel
{
public MyGrid()
{
DoubleBuffered = true;
}
}
So, for anyone trying to do this, as a concept, here is how to fix this issue:
Instead of only drawing 1 row/col extra outside the viewport to save memory, draw the entire viewport's worth of cells in every direction of the edges (up, down, left and right)... for example, if your viewport can hold 5 tiles (5 X 5 = 25), then you need to draw 5 X 5 outside the viewport in every other direction (25 X 4 = 100)...
When the mouse is being dragged, just move the controls that are already on the form/control/"drawn"... this way, the user cannot, while dragging, go outside the bounds of the existing tiles... example, if they reach the outer right edge with the mouse, while dragging the left-most tile, the tiles to show on the left already exist! So we are just "following the mouse", which is not an issue if the controls are already there/there is no "loss/issues" because we are not removing or adding any tiles at this point...
When the user stops dragging the selected tile around (onMouseUp), THEN we re-calculate the tiles that need to be drawn and those that don't... so we only redraw (add and/or remove controls where necessary) the entire set of "drawn" tiles after the user is done dragging...
With this method, you remove any "missplaced" controls, double generation of controls, controls missing and any other issue that arises when the mouse moves too fast for the "Calculate drawn tiles" code to execute. You also "see" the map moving around as you drag, and you always have the correct tiles drawn on the screen! Problem solved!
However, I did find that when I used a UserControl instead of the form itself, the controls draw and update much faster and better than if I just add them to the form itself... therefore, I have accepted the answer that outlines that aspect as the actual answer, and placed this here for anyone else in future that might wonder how to do this as a concept.
I am learning C #, and I am creating a hypothetical game for me to understand the language. I want several bots to follow the Player who is moving the rectangle, but I can only move the player, but the automatic bots do not move.
I really researched what I could do to move these bots. And I came to the conclusion that I would have to understand Threads, which simply causes the program not to crash.
I leave here the full code of what I am trying.
public partial class Form1 : Form
{
public enum Direction { Up, Down, Left, Right }
private Player player;
private List<Bot> bots;
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.KeyPreview = true;
this.KeyDown += Form1_KeyDown;
this.player = new Player(new Size(8, 8));
this.bots = new List<Bot>();
for (int i = 0; i < 30; i++)
{
Bot bot = new Bot(player, new Size(8, 8));
bot.Follow();
this.bots.Add(bot);
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Up:
player.Move(Direction.Up);
break;
case Keys.Down:
player.Move(Direction.Down);
break;
case Keys.Left:
player.Move(Direction.Left);
break;
case Keys.Right:
player.Move(Direction.Right);
break;
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
List<Rectangle> rs = new List<Rectangle>();
rs = this.bots.Select(x => x.Rectangle).ToList();
rs.Add(player.Rectangle);
if (rs.Count > 0)
{
e.Graphics.FillRectangles(new SolidBrush(Color.Red), rs.ToArray());
}
}
}
public class Player
{
private Rectangle rectangle;
public Rectangle Rectangle { get => rectangle; }
public Player(Size size)
{
this.rectangle = new Rectangle() { Size = size };
}
public void Move(Direction direction)
{
switch (direction)
{
case Direction.Up:
this.rectangle.Y -= 3;
break;
case Direction.Down:
this.rectangle.Y += 3;
break;
case Direction.Left:
this.rectangle.X -= 3;
break;
case Direction.Right:
this.rectangle.X += 3;
break;
default:
break;
}
}
}
public class Bot
{
private Rectangle rectangle;
private Player player;
public Rectangle Rectangle { get => rectangle; }
public Bot(Player player, Size size)
{
this.player = player;
this.rectangle = new Rectangle() { Size = size };
}
public void Follow()
{
Task.Run(() =>
{
while (true)
{
Point p = player.Rectangle.Location;
Point bot = rectangle.Location;
for (int i = bot.X; i < p.X; i += 2)
{
Thread.Sleep(100);
bot.X = i;
}
for (int i = bot.X; i > p.X; i -= 2)
{
Thread.Sleep(100);
bot.X = i;
}
for (int i = bot.Y; i < p.Y; i += 2)
{
Thread.Sleep(100);
bot.Y = i;
}
for (int i = bot.Y; i > p.Y; i -= 2)
{
Thread.Sleep(100);
bot.Y = i;
}
}
});
}
}
As you can see, I can only move the player, but the bots don't move what can I do to move the bots?
I think a Timer would work better here and remove the requirement for you to fully understanding threading at this point, as it will handle the details for you. I'm assuming you actually want the bots to "follow" instead of only moving when the Player moves and will fall behind if the player is moving quickly.
So to use a Timer, I would adjust your Bot class as below, to remove your usage of threads and only allow it to take a single step towards the player in the Follow method which will be called every 100ms. Note Rectangle is a struct, so it is not mutable - that is why your bots do not move - if you do the following:
Point bot = Rectangle.Location;
bot.X = 5;
You're probably thinking Rectangle.Location.X is now 5; but it is not. So we create a new rectangle using the new position.
public class Bot
{
private Player player;
public Rectangle Rectangle { get; set; }
public Bot(Player player, Size size)
{
this.player = player;
this.Rectangle = new Rectangle() { Size = size };
}
public void Follow()
{
Point p = player.Rectangle.Location;
Point bot = Rectangle.Location;
for (int i = bot.X + 2; i < p.X;)
{
bot.X = i;
break;
}
for (int i = bot.X - 2; i > p.X;)
{
bot.X = i;
break;
}
for (int i = bot.Y + 2; i < p.Y;)
{
bot.Y = i;
break;
}
for (int i = bot.Y - 2; i > p.Y;)
{
bot.Y = i;
break;
}
Rectangle = new Rectangle(bot, player.Rectangle.Size);
}
}
And add the following code to replace your existing constructor and add another method to handle the Timer tick.
private Timer timer;
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.KeyPreview = true;
this.KeyDown += Form1_KeyDown;
// setup a timer which will call Timer_Tick every 100ms
timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += Timer_Tick;
timer.Start();
this.player = new Player(new Size(8, 8));
this.bots = new List<Bot>();
for (int i = 0; i < 30; i++)
{
Bot bot = new Bot(player, new Size(8, 8));
bot.Follow();
this.bots.Add(bot);
}
}
private void Timer_Tick(object sender, System.EventArgs e)
{
foreach (var bot in bots)
bot.Follow();
this.Invalidate();
}
Point is a value type (a struct). (Read more about this at What's the difference between struct and class in .NET?)
When you did this:
Point bot = Rectangle.Location;
bot.X = i;
...you created a local Point and modified it. This doesn't change the Location of the Rectangle of the Bot. Also, Rectangles are structs too, so you have to modify the original Bot's Rectangle, or assign a new Rectangle to the Bot.
To fix, you could replace:
bot.X = i;
...with...
this.rectangle.X = i;
And make the similar change for .Y (in all your for loop locations)
Spelling it all out:
public void Follow()
{
Task.Run(() =>
{
while (true) {
Point p = player.Rectangle.Location;
Point bot = rectangle.Location;
for (int i = bot.X; i < p.X; i += 2) {
Thread.Sleep(100);
this.rectangle.X = i;
}
for (int i = bot.X; i > p.X; i -= 2) {
Thread.Sleep(100);
this.rectangle.X = i;
}
for (int i = bot.Y; i < p.Y; i += 2) {
Thread.Sleep(100);
this.rectangle.Y = i;
}
for (int i = bot.Y; i > p.Y; i -= 2) {
Thread.Sleep(100);
this.rectangle.Y = i;
}
}
});
}
I am currently working on a topdown game made in windows forms. In the game, your character rotates after your mouse and you can shoot a shot from your character in the direction of your mouse. To create multiple areas in the game, I chose to use groupboxes as separate areas in the game that you can switch between using buttons but I've run in to a problem. I can no longer rotate or move my character or even shoot within the groupboxes.
I've tried setting breakpoints and discovered that the keyup, keydown and mousemove methods are not being called but I don't know why. For some reason I get an error when i release space in the btnSave_Click method which i marked in the code.
static Image originalImage;
bool pRight, pLeft, pUp, pDown;
string[] Saves;
Save[] RSaves = new Save[4];
Save yoursave;
Save temp;
int slot;
NewGame newgame;
SavedGames savedgames;
string savedata, name = "";
int lvl = 1;
double exp = 0, money = 0;
int pSpeed = 5;
double deltaY, deltaX;
float interval = 7;
Point start;
Point nextStart;
Point cursor;
float radian;
const double Rad2Grad = (180 / Math.PI);
public MainGame()
{
InitializeComponent();
pbPlayer.BringToFront();
gbxTown.AllowDrop = true;
gbxQ1.AllowDrop = true;
pbShot.Location = pbPlayer.Location;
// newgame = new NewGame();
// savedgames = new SavedGames();
originalImage = pbPlayer.Image;
}
public void setSaves(string savedata, int slot)
{
Saves = savedata.Split('#');
string a1 = Saves[0];
string a2 = Saves[1];
string a3 = Saves[2];
string a4 = Saves[3];
RSaves[0] = temp.StringToSaves(temp, a1);
RSaves[1] = temp.StringToSaves(temp, a2);
RSaves[2] = temp.StringToSaves(temp, a3);
RSaves[3] = temp.StringToSaves(temp, a4);
yoursave = RSaves[slot - 1];
name = yoursave.getName();
this.slot = slot;
Controls.Add(pbPlayer);
Controls.Add(pbShot);
}
private void MainGame_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.A)
{
pRight = false;
pLeft = true;
}
else if (e.KeyData == Keys.D)
{
pRight = true;
pLeft = false;
}
else if (e.KeyData == Keys.S)
{
pUp = true;
pDown = false;
}
else if (e.KeyData == Keys.W)
{
pUp = false;
pDown = true;
}
if (e.KeyData == Keys.Space)
{
start = pbPlayer.Location;
cursor = Cursor.Position;
nextStart = start;
deltaY = cursor.Y - start.Y;
deltaX = cursor.X - start.X;
double test = Angle(start, cursor);
radian = (float)(Angle(start, cursor) - 175);
timer2.Enabled = true;
}
}
private void MainGame_KeyUp_1(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.A)
{
pLeft = false;
}
else if (e.KeyData == Keys.D)
{
pRight = false;
}
else if (e.KeyData == Keys.S)
{
pUp = false;
}
else if (e.KeyData == Keys.W)
{
pDown = false;
}
if (e.KeyData == Keys.Space)
{
timer2.Stop();
pbShot.Location = start;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
MovePlayer();
//checkCollision();
}
private void btnNextArea_Click(object sender, EventArgs e)
{
pbPlayer.Parent = gbxQ1;
pbShot.Parent = gbxQ1;
gbxQ1.Location = new Point(0, 0);
gbxTown.Location = new Point(605, 0);
pbPlayer.Location = new Point(100, 200);
pbShot.Location = pbPlayer.Location;
}
private void btnSave_Click(object sender, EventArgs e)
{
newgame = new NewGame();
savedgames = new SavedGames();
RSaves[slot - 1] = new Save(name, money, lvl, exp);
for (int i = 0; i < 4; i++) //When the Spacebar is released, a System.IndexOutOfRangeException error happens for i.
{
Saves[i] = RSaves[i].SavesToString();
}
savedata = Saves[0] + Saves[1] + Saves[2] + Saves[3];
System.IO.File.WriteAllLines(#"saves.txt", Saves);
newgame.setSaves(savedata);
savedgames.setSaves(savedata);
}
private void btnBack_Click(object sender, EventArgs e)
{
pbPlayer.Parent = gbxTown;
pbShot.Parent = gbxTown;
gbxTown.Location = new Point(0, 0);
gbxQ1.Location = new Point(605, 0);
pbPlayer.Location = new Point(100, 200);
pbShot.Location = pbPlayer.Location;
}
private void MainGame_MouseMove_1(object sender, MouseEventArgs e)
{
var y2 = e.Y;
var y1 = (this.pbPlayer.Location.Y + (this.pbPlayer.Height / 2));
var x2 = e.X;
var x1 = (this.pbPlayer.Location.X + (this.pbPlayer.Width / 2));
var angle = (float)Math.Atan2((y1 - y2), (x1 - x2));
pbPlayer.Image = RotateImage(originalImage, (angle * 57));
}
private double Angle(Point start, Point end)
{
return (float)Math.Atan2(end.Y - start.Y, end.X - start.X) * 57;
}
private void timer2_Tick_1(object sender, EventArgs e)
{
nextStart.X -= Convert.ToInt16(interval * ((float)Math.Cos(radian / Rad2Grad)));
nextStart.Y -= Convert.ToInt16(interval * ((float)Math.Sin(radian / Rad2Grad)));
pbShot.Location = nextStart;
}
public static Image RotateImage(Image img, float rotationAngle)
{
Bitmap bmp = new Bitmap(img.Width, img.Height);
Graphics gfx = Graphics.FromImage(bmp);
gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
gfx.RotateTransform(rotationAngle);
gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfx.DrawImage(img, new Point(0, 0));
gfx.Dispose();
return bmp;
}
private void MovePlayer()
{
if (pRight == true && pbPlayer.Left < 900)
{
pbPlayer.Left += pSpeed;
}
else if (pLeft == true && pbPlayer.Left > 0)
{
pbPlayer.Left -= pSpeed;
}
else if (pUp == true && pbPlayer.Top < 600)
{
pbPlayer.Top += pSpeed;
}
else if (pDown == true && pbPlayer.Top > 0)
{
pbPlayer.Top -= pSpeed;
}
}
The expected outcome is for the movement, aiming and firing to work as intended within the groupboxes but currently they do not execute in the code while in the groupboxes. It works fine in the form but not in the groupboxes. I would appriciate any help.
I am working on a form that uses a SharpMap MapBox to display objects as points on a world map. Currently, if I enter the MapBox (mapBox1) with the cursor and stop on the point, it displays the tooltip as I want it to. However, once I've stopped the mouse within the MapBox (not necessarily on the point) and move the mouse within the MapBox, moving to the point will not (re)display the tooltip. However, if I leave the MapBox (say, moving the cursor out of the window or onto one of the menu strips, or onto a button overlaid on the map) I can then get the tooltip to appear, but only once before I have to move the cursor as before.
What is causing this behaviour, and is there any easy way to fix it?
I've tried using ToolTip.Hide(), ToolTip.Active = false (and then setting it to true again when I want it displayed) and refreshing the MapBox at various points.
Relevant code:
The ToolTip is global, and the constructor defines it as follows:
toolTip.InitialDelay = 1000;
toolTip.ReshowDelay = 750;
toolTip.ShowAlways = true;
I then have two event handlers for the mouse, both tied to the MapBox. "obj" is a global object of a custom class containing the latitude and longitude points.
private void mapBox1_MouseHover(object sender, EventArgs e)
{
PointF pos = mapBox1.PointToClient(Cursor.Position);
int screenToleranceX = 20, screenToleranceY = 20;
PointF posLow = new PointF(pos.X - screenToleranceX, pos.Y - screenToleranceY);
PointF posHigh = new PointF(pos.X + screenToleranceX, pos.Y + screenToleranceY);
GeoAPI.Geometries.Coordinate objLoc = new GeoAPI.Geometries.Coordinate(obj.longitude, obj.latitude);
PointF objPoint = mapBox1.Map.WorldToImage(objLoc);
if (posLow.X <= objPoint.X && objPoint.X <= posHigh.X && posLow.Y <= objPoint.Y && objPoint.Y <= posHigh.Y)
{
toolTip.Active = true;
toolTip.Show(obj.Name, mapBox1, mapBox1.PointToClient(Cursor.Position));
}
}
private void mapBox1_MouseMove(GeoAPI.Geometries.Coordinate worldPos, MouseEventArgs imagePos)
{
PointF pos = mapBox1.PointToClient(Cursor.Position);
int screenToleranceX = 20, screenToleranceY = 20;
PointF posLow = new PointF(pos.X - screenToleranceX, pos.Y - screenToleranceY);
PointF posHigh = new PointF(pos.X + screenToleranceX, pos.Y + screenToleranceY);
GeoAPI.Geometries.Coordinate objLoc = new GeoAPI.Geometries.Coordinate(obj.longitude, obj.latitude);
PointF objPoint = mapBox1.Map.WorldToImage(objLoc);
if (toolTip.Active && (posLow.X > objPoint.X || objPoint.X > posHigh.X || posLow.Y > objPoint.Y || objPoint.Y > posHigh.Y))
{
toolTip.Active = false;
}
}
** EDIT **
As per the accepted answer, I have the following code as a solution, with hopes to refine it further as needed. This works for now, however (using an externally declared bool, toolTipDisp, defaulted to false):
private void mapBox1_MouseMove(GeoAPI.Geometries.Coordinate worldPos, MouseEventArgs imagePos)
{
PointF pos = mapBox1.PointToClient(Cursor.Position);
int screenToleranceX = 20, screenToleranceY = 20;
PointF posLow = new PointF(pos.X - screenToleranceX, pos.Y - screenToleranceY);
PointF posHigh = new PointF(pos.X + screenToleranceX, pos.Y + screenToleranceY);
GeoAPI.Geometries.Coordinate objLoc = new GeoAPI.Geometries.Coordinate(obj.longitude, obj.latitude);
PointF objPoint = mapBox1.Map.WorldToImage(objLoc);
if (posLow.X <= objPoint.X && objPoint.X <= posHigh.X && posLow.Y <= objPoint.Y && objPoint.Y <= posHigh.Y)
{
if (!toolTipDisp)
{
toolTip.Show(obj.Name, mapBox1, mapBox1.PointToClient(Cursor.Position));
toolTipDisp = true;
}
}
else
{
toolTip.Hide(mapBox1);
toolTipDisp = false;
}
}
Try this (pseudo-code):
private string _previous;
private void mapBox1_MouseMove(GeoAPI.Geometries.Coordinate worldPos, MouseEventArgs imagePos)
{
var text = ...; // generate tooltip text based on the new position
if(text != _previous)
{
_previous = text;
tooltip.Show(text, mapBox1, mapBox1.PointToClient(imagePos.Location));
}
}
private void mapBox1_MouseLeave(object sender, EventArgs e)
{
toolTip.Hide(mapBox1);
}