After this question (Show trail of moving pixel in C# WinForm project) for my personal ant colony project in c#, I'm trying to apply the solution second suggested solution: the one that combines drawing the trail into a bitmap and the new ants onto the surface.
[...]Application.Run(new ShowAnts());[...]
public partial class ShowAnts : Form
{
Bitmap bmp;
int j = 0;
public ShowAnts()
{
InitializeAntProgram();
InitializeComponent();
bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
pictureBox1.Image = bmp;
}
public void RenderAnts(object sender, PaintEventArgs e)
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
while (j < 1000)
{
Map.EvaporatesPheromones();
foreach (Vector2D food in foodSrcs)
{
Map.SetMapPoint(food, 500);
}
foreach (Ant a in ants)
{
Brush c;
c = Brushes.DarkBlue;
if (a.role == AntRole.Scout)
{
a.Move(j);
c = Brushes.Red;
}
e.Graphics.FillRectangle(Brushes.DarkBlue, a.position.x, a.position.y, 1, 1);
G.FillRectangle(Brushes.Gray, a.position.x, a.position.y, 1, 1);
}
j++;
}
}
}
}
The code above shows the graphic attempt to draw the ant movement into a winform.
It works perfectly, but it shows only the final result. I would like to show the step by step evolution keeping the graphical trail information without reparsing my map info.
Please consider that a working console project on which I' developing this "graphic interface" already exists so:
some variables are set elsewhere (i.e.: food) in the project;the `a.Move(j);` refers to the ant logic itself (analysis, decision, new cell movement referring to the map array);the `j` counter is used to count steps and to set an arbitrary stop, but has no real use;I'm already storing into map array and some other variables all informations concerning pheromone, movement, positions etc.
Looking at your code and also the comments of the previous question, it seems that you are missing the part that would animate the movement. Instead you are looping inside what seems to be the Paint event.
Here is a quick fix for that. It adds a Timer that triggers the RenderAnts event, which seems to be hooked up to the pictureBox1.Paint handler..:
A few class level variables:
int counter = 0;
int limit = 1000;
Timer antTimer = new Timer();
Start code:
antTimer.Interval = 50; // <-- pick your speed !!
antTimer.Tick += (ss, ee) =>
{ pictureBox1.Invalidate(); counter++; if (counter > limit) antTimer.Stop(); };
antTimer.Start();
The speed is 50ms, which means 20 Ticks per second.
The Tick event is inlined with a tiny Lambda epression and has only one statement plus the loop logic. By Invalidating the pictureBox1 control its Paint event and thereby the RenderAnts event is triggered.
Also note that I called it a 'quick fix'. Usually you would discern between the rendering and the moving code of an animation; but in this case this fine difference doesn't matter much.
Now we change the RenderAnts method, taking out the loop:
public void RenderAnts(object sender, PaintEventArgs e)
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
Map.EvaporatesPheromones();
foreach (Vector2D food in foodSrcs)
{
Map.SetMapPoint(food, 500);
}
foreach (Ant a in ants)
{
Brush c = Brushes.DarkBlue;
if (a.role == AntRole.Scout)
{
a.Move(j);
c = Brushes.Red;
}
e.Graphics.FillRectangle(c, a.position.x, a.position.y, 1, 1);
G.FillRectangle(Brushes.Gray, a.position.x, a.position.y, 1, 1);
}
}
}
You also may want to add a Start/Stop Button. Also a TrackBar to change the speed..
Now you should be able to watch the progress of your ants at 20Hz, leaving grey trails.
Related
I'm attempting to create a simple snake game in C# visual studio as an exercise. I have very little knowledge of the Windows Forms App, but it seemed like the easiest way to access drawing and user input with the arrows.
Is this a good framework to use for a simple game like this in C#?
But here is my real question. I'm trying to create the game loop which loops about every second, and changes where the head of the snake is going to be drawn next. But I cannot figure out how to call the Paint event multiple times. It seems like it calls once, and then exits. If I put a while() loop inside of the paint method with Thread.Sleep(), it draws a rectangle every second, but I don't have access to the KeyEventArgs or anything else because the running code is trapped inside the Paint call.
I want the initial paint call to draw the starting point for the snake, and then looping paint calls, I'm guessing to another paint method(?) which asks the controller which button was pressed last, and then draws the next rectangle in that direction. What is the intended way to do this and create that game loop?
I would appreciate any information and knowledge to help teach me this process, and any other advice or input is welcome!
Here is my current code:
'''
public partial class FormMain : Form
{
private readonly int pixelSize;
private readonly int gridSize;
private readonly int center;
private string currDirection;
public FormMain()
{
pixelSize = 30;
currDirection = "right";
// Calculating size of the grid based on the size of the 'pixel'
gridSize = 640 / pixelSize;
// Calculating the starting position of the snake in the center of the screen
center = (gridSize / 2) * pixelSize;
InitializeComponent();
}
private void FormMain_Paint(object sender, PaintEventArgs e)
{
// referencing the Graphics object and setting the color of the snake
Graphics g = e.Graphics;
Pen seaPen = new Pen(Color.MediumSeaGreen);
// This draws the initial center rectangle
Point currentHeadLocation = new Point(center, center);
Rectangle r;
// Here's the loop that should happen every second or so, probably in another method.
while (true)
{
r = new Rectangle(currentHeadLocation, new Size(pixelSize, pixelSize));
g.DrawRectangle(seaPen, r);
if (currDirection.Equals("right"))
{
currentHeadLocation = new Point(currentHeadLocation.X + pixelSize, currentHeadLocation.Y);
}
else if (currDirection.Equals("left"))
{
currentHeadLocation = new Point(currentHeadLocation.X - pixelSize, currentHeadLocation.Y);
}
else if (currDirection.Equals("up"))
{
currentHeadLocation = new Point(currentHeadLocation.X, currentHeadLocation.Y - pixelSize);
}
else if (currDirection.Equals("down"))
{
currentHeadLocation = new Point(currentHeadLocation.X, currentHeadLocation.Y + pixelSize);
}
Thread.Sleep(1000);
}
}
private void FormMain_KeyDown(object sender, KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Left:
currDirection = "left";
break;
case Keys.Right:
currDirection = "right";
break;
case Keys.Up:
currDirection = "up";
break;
case Keys.Down:
currDirection = "down";
break;
}
}
}
'''
Using Thread.Sleep() in this context is almost always a bad idea, as it will freeze your only thread, which is also your UI thread.
You probably want to make use of a timer.
Quick example:
public FormMain()
{
//Any other init stuff here
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer(); //Create a timer
t.interval = 1000; //interval time in ms
t.Tick += (s, e) => LoopFunctionName(); //Bind a function to the event whenever the timer reaches its interval
}
public void LoopFunctionName()
{
//Game loop
}
If you want to force your control to be repainted, you can simply call Invalidate().
I am doing a project about a chess-like game, so players own their pawns on the map. Every time player decides to move a pawn, he needs to get a number from a dice, then that pawn would move according to the rolled number. Move function of pawns is finished, but I didn't show the moving process for them.
I have one panel for map and four panels for starting base(holding the pawns at the beginning of the game).
GUI for gameboard
In paint event, I ask the system to draw everything.
private void P_Map_Paint(object sender, PaintEventArgs e)
{
manager.VisualizeCollection(map, gr_map);
manager.VisualizeStartingBase(bases, grs);
manager.VisualizePawns(manager.Players, grs, gr_map);
manager.DisplayAvailablePawn(gr_map, grs);
manager.DisplaySelectedPawn(gr_map, grs);
}
For every move of a pawn, I am trying to use a timer to make the image moving on the screen.
public void DoMovement(Pawn pawn)
{
if (TargetSpot != null)
{
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
EventHandler t_tick = null;
Point targetP = TargetSpot.Location;
Point currentP = pawn.ImageLocation;
int XMove = (targetP.X - currentP.X) / 10;
int YMove = (targetP.Y - currentP.Y) / 10;
Rectangle drawRange = new Rectangle(pawn.ImageLocation.X - (int)(0.5 * pawn.Image.Width),
pawn.ImageLocation.Y - (int)(0.5 * pawn.Image.Height), pawn.Image.Width, pawn.Image.Height);
t_tick = (senders, args) =>
{
if (currentP.X > targetP.X - XMove || currentP.X < targetP.X + XMove)
{// if the image of the pawn doesn't reach the destination
//we keep moving it and ask the pawn to redraw the old place
pawn.ImageLocation = new Point(currentP.X + XMove, currentP.Y + YMove);
pawn.CurrentPanel.Invalidate(drawRange);
}
else
{
pawn.CurrentLocation.LeaveAPawn(pawn);
pawn.CurrentLocation = TargetSpot;
TargetSpot.AddAPawn(pawn);
pawn.CurrentPanel.Invalidate(drawRange);
}
};
t.Tick += t_tick;
t.Interval = 300;
t.Start();
}
}
This is not working fine, everything on the panel is still redrawing. Why?
Excepting the project, I do have a question about the invalidating a region. Like I told the rules of drawing to paint event, then the panel invalidates itself. When we are going to invalidate a region, how the system knows the rule about drawing?
Alright, so I've done some research into this topic and most of the solutions I've found claim to fix the problem but I am finding that they aren't quite working right. I'm in the early stages of implementing just a simple little particle engine, nothing crazy I'm just doing it out of boredom. I have not done anything like this with WinForms before, I have certainly with C/C++ but this is a new thing for me. The following is the code I am using to draw the particles to the screen, the boiler plate code for the particles is not relevant as it works fine, I am more curious about my actual game loop.
Here is the main code for updates and redraws
public MainWindow()
{
this.DoubleBuffered = true;
InitializeComponent();
Application.Idle += HandleApplicationIdle;
}
void HandleApplicationIdle(object sender, EventArgs e)
{
Graphics g = CreateGraphics();
while (IsApplicationIdle())
{
UpdateParticles();
RenderParticles(g);
g.Dispose();
}
}
//Variables for drawing the particle
Pen pen = new Pen(Color.Black, 5);
Brush brush = new SolidBrush(Color.Blue);
public bool emmiter = false;
private void EmitterBtn_Click(object sender, EventArgs e)
{
//Determine which emitter to use
if (emmiter == true)
{
//Creates a new particle
Particle particle = new Particle(EmitterOne.Left, EmitterOne.Top, .5f, .5f, 20, 20);
emmiter = false;
}
else if(emmiter == false)
{
Particle particle = new Particle(EmitterTwo.Left, EmitterTwo.Top, -.5f, .5f, 20, 20);
emmiter = true;
}
}
public void RenderParticles(Graphics renderer)
{
Invalidate();
Thread.Sleep(0);
//Iterate though the static list of particles
for (int i = 0; i < Particle.activeParticles.Count; i++)
{
//Draw Particles
renderer.DrawRectangle(pen, Particle.activeParticles[i].x,
Particle.activeParticles[i].y,
Particle.activeParticles[i].w,
Particle.activeParticles[i].h);
}
}
public void UpdateParticles()
{
for (int i = 0; i < Particle.activeParticles.Count; i++)
{
//Move particles
Particle.activeParticles[i].MoveParticle();
}
}
The issue I am running into is that anytime the screen is getting cleared and updated, it gets this awful flickering, and not only that but it sometimes won't whenever I emit a particle.
The form is basically just using labels as invisible locations on the screen to say where to render each particle.
Anyway, I've seen this topic before but nothing has fixed anything, the current implementation is the least flickery/laggy but is not solving the issue.
Any help is appreciated, thanks!
EDIT* I realized I was never deallocating the graphics object each loop so I did that and there is no more delay whenever I click the emitter button, however the flicker is still there, I updated the code accordingly.
Getting rid of the visible paint artifacts requires double-buffering. In other words, render the scene into a back-buffer that, when ready, gets quickly blitted to the screen surface in a single step. That's a built-in feature in Winforms, simply set the DoubleBuffered property to true in the form constructor. You must use the Paint event to take advantage of that. Override OnPaint() and call RenderParticles(e.Graphics).
You need to take care of timing, right now your UI thread is burning 100% core and animation speed completely depends on the number of particles and the speed of the machine. Instead of Application.Idle, drop a Timer from the toolbox onto your form. In the Tick event handler, call UpdateParticles() and this.Invalidate() to get the Paint event to fire again. The timer's Interval property value is critical, you get the most reproducible update rate by picking 15 or 31 msec (64 or 32 FPS).
You are not always going to get the desired FPS rate, the timer will simply delay or skip a Tick event if the machine gets busy or is too slow or other code on the UI thread needs to run. To make sure that doesn't affect the animation, you must measure actual elapsed time instead of moving the particles by a fixed amount. Either Environment.TickCount, DateTime.UtcNow or Stopwatch are suitable ways to measure true elapsed time.
I am making a program where you bassicly move from tile to tile in windows forms.
So in order to do that, I wanted to use panels each panel has a tag. To detect collision.
So I have an image of my map. and I divided into multiple tiles. However now I have to drag 900 tiles onto panels.
This isn't very effective in 2 ways. First loading 900 textures isn't really a smart idea. Also it would take ages. So i wanted to use a spritesheet or tilemap. But how would I do that in winforms. I believe I have seen some people use a grid view or whatever. However im not sure how to do what I want to do.
What would be the best solution?
Thanks in advance!
For any serious gaming project WinForms is not the best platform. Either WPF or XNA or Unity are able to deliver high performance use of DirectX.
But since you want to do it in Winforms here is a way to do it.
It creates a whopping number of 900 PictureBoxes and loads each with a fraction of an source image:
private void Form1_Load(object sender, EventArgs e)
{
int tileWidth = 30;
int tileHeight = 30;
int tileRows = 30;
int tileCols = 30;
using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
{
Size s = new Size(tileWidth, tileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row < tileRows; row++)
for (int col = 0; col < tileCols; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(tileWidth * col, tileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(tileWidth, tileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
p.Tag = loc;
p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
// p.MouseDown += p_MouseDown;
// p.MouseUp += p_MouseUp;
// p.MouseMove += p_MouseMove;
this.Controls.Add(p);
}
}
}
When I tried it I was a bit worried about perfomance, but..
This takes under 1 second to load on my machine.
Starting the programm adds 10MB to VS memory usage. That is like nothing.
For a fun project this will do; for best performance one might use Panels but these will have to be filled and refilled in the Paint event. This solution saves you the hassle and since you don't change the tile picture all the time this works well enough.
Pleae note: I have added a Name and a Tag to each PictureBox, so you can later refer to it. These both contain info about the original position of the Picturebox. The Name looks like this: Col=23-Row=02 and the Tag is the original Location object.
Also: Dynamically added controls take a little extra to script since you can't create their method bodies in the designer. Instead you add them like above. In doing so Intellisense and the Tab key are your best friends..
I have added three event handlers for a few mouse events. When you uncomment them you will have to add the methods like e.g. this:
void p_MouseMove(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
But maybe you want to use other events to play like Drag&Drop or keyboard events..
There are two ways to refer to these tiles. Maybe you want to try and/or use both of them: You can loop over the form's controls with a
foreach (Control ctl in this.Controls)
{ if (ctl is PictureBox ) this.Text = ((PictureBox)ctl).Name ; }
It tests for the right type and then casts to PictureBox. As an example it displays the name of the tile in the window title.
Or you can have a variable and set it in the MouseDown event:
PictureBox currentTile;
void p_MouseDown(object sender, MouseEventArgs e)
{
currentTile = (PictureBox ) sender;
}
I'm programming a tetris clone for my C# school project. I am using Microsoft Visual Studio 2012. The game itself is implemented as a two dimensional array of blocks(List of Lists of blocks) and every block has its own texture (bmp image). I am drawing the whole array onto a PictureBox control and this is where the problem starts. When updating the image on the PictureBox (moving/rotating the active shape) the game slightly lags. I tried to draw on a Panel control instead but the result was the same. I have a rough idea what might cause the lag but I don't know exactly how to get rid of it.
This is the draw method of the game "grid":
public void Draw(Graphics g)
{
Brush brush;
Font font = new System.Drawing.Font( "Arial", 5);
for (int i = 0; i < Width; i++)
for (int j = 0; j < Height; j++)
{
brush = new TextureBrush(Blocks[i][j].Texture);
if (Blocks[i][j].Occupied==true)
g.FillRectangle(brush, i * 20, j * 20, i * 20 + Blocks[i][j].Texture.Width, j * 20 + Blocks[i][j].Texture.Height);
}
}
This is the draw method of the active tetromino:
public void Draw(Graphics g)
{
Brush brush = new TextureBrush(Blocks[0].Texture);
foreach (FullBlock b in Blocks)
g.FillRectangle(brush, b.x * 20, b.y * 20,b.Texture.Width, b.Texture.Height);
}
The game itself then use both of them (double buffering attempt):
public void GameDraw(PictureBox p)
{
Graphics g = Graphics.FromImage(gb);
gameGrid.Draw(g);
PlayingShape.Draw(g);
p.Image = gb;
p.Refresh();
}
where "gb" is a private Bitmap variable I create just once in the class constructor (to reduce (unsuccessfully) the lag).
The GameDraw method is called whenever the state of the game is changed (e.g. moving/rotating the active tetromino and every "gravity" tick)
You need Double buffering, which you did not set. Quoting MSDN:
Double buffering uses a memory buffer to address the flicker problems
associated with multiple paint operations. When double buffering is
enabled, all paint operations are first rendered to a memory buffer
instead of the drawing surface on the screen
You can enable it using Control.DoubleBuffered property
No need for picture box, add your own control:
using System.Drawing;
using System.Windows.Forms;
namespace TetrisGame
{
public sealed class TetrisControl : Control
{
private TheBlockType[][] blocks = ...;
protected override void OnPaint(PaintEventArgs e)
{
//draw your stuff here direct to the control, no buffers in the middle
//if that causes flickering, turn on double buffering, but don't bother doing it yourself
//this is your existing draw method:
Draw(e.Graphics);
}
}
}
Then on every tick or movement, do not call paint, just invalidate the control:
tetris.Invalidate();
Also, think out of the box... Rather than doing a full grid scan, you could make each of your shapes part of a linked-list and redraw them based on their position in the grid... Until your grid completely fills, you'd be doing less scanning.
Or, consider only redrawing what you need to redraw, i.e. a block drops near the top, no need to completely redraw the full grid.
Optimisation is what separates us from the animals. Apart from the platypus, who is an optimal creature.