C#, Windows Form, redraw pictureBox - c#

I have a problem with C# game.
There are a matrix with randomly-placed wolves and raccoons. When user click the "new game" button I need to generate new positions for animals and redraw my canvas (PicureBox).
So, I have no problem with generating, but position of animals in pictureBox do not change (but it shows, when generates first time).
Also I tried to create several pictureBox with animal icons (and move it in game process), but they does not shown (I had added it in this.Controls, but nothing happened). Maybe there is some form manager like web-inspector in browsers to see what exactly happens?
I do not want to move a project to WPF Application.
Pieces of code below:
private static Bitmap _gameArea = new Bitmap(_gameAreaSize, _gameAreaSize);
Painted event (create a table):
private void drawTable(object sender, PaintEventArgs e)
{
Pen p = new Pen(Color.LightSlateGray);
var g = Graphics.FromImage(_gameArea);
for (int i = 0; i < Configs.MatrixSize + 1; i++)
{
float coord = i * _cellSize;
if (coord.Equals(_gameAreaSize))
coord = _gameAreaSize - 1;
g.DrawLine(p, coord, 0, coord, _gameAreaSize);
g.DrawLine(p, 0, coord, _gameAreaSize, coord);
}
pictureBox1.BackgroundImage = _gameArea;
g.Dispose();
}
For game start and for new game click:
GameLogic.InitGame(); // change positions, without threads
RefreshMap();
RefreshMap:
private static void RefreshMap()
{
var g = Graphics.FromImage(_gameArea);
g.Clear(Color.White);
g.Dispose();
RefreshRacoons();
RefreshWolves();
}
I tried to make smth like this:
private static void RefreshMap()
{
var sync = SynchronizationContext.Current;
new Thread(__ =>
sync.Post(_ =>
{
var g = Graphics.FromImage(_gameArea);
g.Clear(Color.White);
g.Dispose();
RefreshRacoons();
RefreshWolves();
}, null)).Start();
}
And refresh (with synContext and without it)
private static void RefreshRacoons()
{
foreach (var racoon in GameLogic.Racoons)
{
var g = Graphics.FromImage(_gameArea);
g.DrawImage(_racoonImg,
_cellSize * racoon.X + _offsetX,
_cellSize * racoon.Y + _offsetY,
_animalIconWidth,
_animalIconHeight);
g.Dispose();
}
}
private static void RefreshWolves()
{
var sync = SynchronizationContext.Current;
foreach (var wolf in GameLogic.Wolves)
{
new Thread(__ =>
sync.Post(_ =>
{
var g = Graphics.FromImage(_gameArea);
g.DrawImage(_wolfImg,
_cellSize * wolf.X + _offsetX,
_cellSize * wolf.Y + _offsetY,
_animalIconWidth,
_animalIconHeight);
g.Dispose();
}, null)).Start();
}
}
Thank you!

My usual disclaimer is this: Windows Forms is not the right environemnt for game development. Turnbased singleplayer or hotseat multiplayer games can work, if you do not overdo it with animations. But Solitaire and Minesweeper are pretty much the upper limit of what is possible.
There are a number of Dedicateed Display technologies/Engines avalible for game development. But in the end anything that allows you direct, low level drawing access and has a game loop of some sort should work out. While you can fake that in Windows Forms to some degree, you basically end up reinventing the wheel when you have a fully filled up car with keys in the Ignition just standing there.
For .NET Framework, the dated soluation is the XNA Framework. But if you go for .NET Core, there is at least 3 official solutions:
https://www.microsoft.com/net/learn/apps/gaming
Most of wich work with Mono and some might even work with the .NET Framework (I have not tried it myself).

Related

Dynamically display an array of PictureBoxes - Performance issue

I'd like to display coverarts for each album of an MP3 library, a bit like Itunes does (at a later stage, i'd like to click one any of these coverarts to display the list of songs).
I have a form with a panel panel1 and here is the loop i'm using :
int i = 0;
int perCol = 4;
int disBetWeen = 15;
int width = 250;
int height = 250;
foreach(var alb in mp2)
{
myPicBox.Add(new PictureBox());
myPicBox[i].SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
myPicBox[i].Location = new System.Drawing.Point(disBetWeen + (disBetWeen * (i % perCol) +(width * (i % perCol))),
disBetWeen + (disBetWeen * (i / perCol))+ (height * (i / perCol)));
myPicBox[i].Name = "pictureBox" + i;
myPicBox[i].Size = new System.Drawing.Size(width, height);
myPicBox[i].ImageLocation = #"C:/Users/Utilisateur/Music/label.jpg";
panel1.Controls.Add(myPicBox[i]);
i++;
}
I'm using the same picture per picturebox for convenience, but i'll use the coverart embedded in each mp3 file eventually.
It's working fine with an abstract of the library (around 50), but i have several thousands of albums. I tried and as expected, it takes a long time to load and i cannot really scroll afterward.
Is there any way to load only what's displayed ? and then how to assess what is displayed with the scrollbars.
Thanks
Winforms really isn't suited to this sort of thing... Using standard controls, you'd probably need to either provision all the image boxes up front and load images in as they become visible, or manage some overflow placeholder for the appropriate length so the scrollbars work.
Assuming Winforms is your only option, I'd suggest you look into creating a custom control with a scroll bar and manually driving the OnPaint event.
That would allow you to keep a cache of images in memory to draw the current view [and a few either side], while giving you total control over when they're loaded/unloaded [well, as "total" as you can get in a managed language - you may still need tune garbage collection]
To get into some details....
Create a new control
namespace SO61574511 {
// Let's inherit from Panel so we can take advantage of scrolling for free
public class ImageScroller : Panel {
// Some numbers to allow us to calculate layout
private const int BitmapWidth = 100;
private const int BitmapSpacing = 10;
// imageCache will keep the images in memory. Ideally we should unload images we're not using, but that's a problem for the reader
private Bitmap[] imageCache;
public ImageScroller() {
//How many images to put in the cache? If you don't know up-front, use a list instead of an array
imageCache = new Bitmap[100];
//Take advantage of Winforms scrolling
this.AutoScroll = true;
this.AutoScrollMinSize = new Size((BitmapWidth + BitmapSpacing) * imageCache.Length, this.Height);
}
protected override void OnPaint(PaintEventArgs e) {
// Let Winforms paint its bits (like the scroll bar)
base.OnPaint(e);
// Translate whatever _we_ paint by the position of the scrollbar
e.Graphics.TranslateTransform(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y);
// Use this to decide which images are out of sight and can be unloaded
var current_scroll_position = this.HorizontalScroll.Value;
// Loop through the images you want to show (probably not all of them, just those close to the view area)
for (int i = 0; i < imageCache.Length; i++) {
e.Graphics.DrawImage(GetImage(i), new PointF(i * (BitmapSpacing + BitmapWidth), 0));
}
}
//You won't need a random, just for my demo colours below
private Random rnd = new Random();
private Bitmap GetImage(int id) {
// This method is responsible for getting an image.
// If it's already in the cache, use it, otherwise load it
if (imageCache[id] == null) {
//Do something here to load an image into the cache
imageCache[id] = new Bitmap(100, 100);
// For demo purposes, I'll flood fill a random colour
using (var gfx = Graphics.FromImage(imageCache[id])) {
gfx.Clear(Color.FromArgb(255, rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255)));
}
}
return imageCache[id];
}
}
}
And Load it into your form, docking to fill the screen....
public Form1() {
InitializeComponent();
this.Controls.Add(new ImageScroller {
Dock = DockStyle.Fill
});
}
You can see it in action here: https://www.youtube.com/watch?v=ftr3v6pLnqA (excuse the mouse trails, I captured area outside the window)

SFML - C# Garbage Collector deletes object which is in use

I'm using SFML for C#. I want to create a BackgroundImage Sprite and then start drawing it with an Agent, represented as a Circle, on top of it like that:
static void Main(string[] args)
{
Window = new RenderWindow(new VideoMode((uint)map.Size.X * 30, (uint)map.Size.Y * 30), map.Name + " - MAZE", Styles.Default);
while (Window.IsOpen)
{
Update();
}
}
static public RenderWindow Window { get; private set; }
static Map map = new Map(string.Format(#"C:\Users\{0}\Desktop\Maze.png", Environment.UserName));
static public void Update()
{
Window.Clear(Color.Blue);
DrawBackground();
DrawAgent();
Window.Display();
}
static void DrawAgent()
{
using (CircleShape tempCircle = new CircleShape
{
FillColor = Color.Cyan,
Radius = 15,
Position = new Vector2f(30, 30),
Origin = new Vector2f(30, 30),
Scale = new Vector2f(.5f, .5f)
})
{
Window.Draw(tempCircle);
}
}
static private Sprite BackgroundImage { get; set; }
static void DrawBackground()
{
if (BackgroundImage == null)
BackgroundImage = GetBackground();
Window.Draw(BackgroundImage);
}
static Sprite GetBackground()
{
RenderTexture render = new RenderTexture((uint)map.Size.X * 30, (uint)map.Size.Y * 30);
foreach (var point in map.Grid.Points)
{
RectangleShape pointShape = new RectangleShape(new Vector2f(30, 30));
switch (point.PointType)
{
case PointType.Walkable:
pointShape.FillColor = Color.White;
break;
case PointType.NotWalkable:
pointShape.FillColor = Color.Black;
break;
case PointType.Start:
pointShape.FillColor = Color.Red;
break;
case PointType.Exit:
pointShape.FillColor = Color.Blue;
break;
}
pointShape.Position = new Vector2f(point.Position.X * 30, point.Position.Y * 30);
render.Draw(pointShape);
}
Sprite result = new Sprite(render.Texture);
result.Origin = new Vector2f(0, result.GetLocalBounds().Height);
result.Scale = new Vector2f(1, -1);
return result;
}
Everything works as intended when I start it, but after a few seconds, around the time when process memory reaches 70MB, BackgroundImage turns into completely white sprite. If I change the type of BackgroundImage and GetBackground() to RenderTexture, return "render" object and then change DrawBackground() function like this
void RenderBackground()
{
if (BackgroundImage == null)
BackgroundImage = GetBackground();
using (Sprite result = new Sprite(BackgroundImage.Texture))
{
result.Origin = new Vector2f(0, result.GetLocalBounds().Height);
result.Scale = new Vector2f(1, -1);
Window.Draw(result);
}
}
then the background sprite doesn't turn white, but storing entire RenderTexture, instead of Sprite and then constantly creating new Sprite objects every time we call RenderBackground() function seems like a bad idea.
Is there any way for GetBackground() function to return a Sprite which won't turn white once the function's local "render" variable is destroyed?
You're not completely off with your assumptions. Simplified, SFML knows two types of resources:
Light resources are small objects that are quick to create and destroy. It's not that bad to just drop them and recreate them later. Typical examples would be Sprite, Sound, Text, and basically most SFML classes.
Heavy resourcces are often big objects or objects requiring file access to create or use. Typical examples would be Image, Texture, SoundBuffer, and Font. You shouldn't recreate these and instead keep them alive while you need them. If they're disposed too early, light resources using them will fail in some way or another.
A sprite's texture turning white is – as you've discovered – a typical sign of the assigned texture being freed/disposed.
There are many different approaches to this, but I'd suggest you create some kind of simple resource manager that will load resources just in time or just return them, if they're loaded already.
I haven't used SFML with C# and I haven't really touched C# for quite a while, but for a simple implementation you'd just have a Dictionary<string, Texture>. When you want to load a texture file like texture.png, you look whether there's a dictionary entry with that key name. If there is, just return it. If there isn't, create the new entry and load the texture, then return it.
I'm out of practice, so please consider this pseudo code!
private Dictionary<string, Texture> mTextureCache; // initialized in constructor
public Texture getTexture(file) {
Texture tex;
if (mTextureCache.TryGetValue(file, out tex))
return tex;
tex = new Texture(file);
mTextureCache.add(file, tex);
return tex;
}
// Somewhere else in your code:
Sprite character = new Sprite(getTexture("myCharacter.png"));
If your heavy resource is a RenderTexture, you just have to ensure that it stays alive as long as it's used (e.g. as a separate member).
It turned out the answer was simpler that I expected. All I had to to was create new Texture object and then make a Sprite out of it. So instead of
Sprite result = new Sprite(render.Texture);
I wrote
Sprite result = new Sprite(new Texture(render.Texture));
Now garbage collector doesn't dispose Sprite's texture

controlling a spawned picturebox

I am trying to make a little game coded in c#, the game involves moving enemies.
These enemies are spawned in using the following code, this code is used multiple times to spawn multiple enemies.
private void EventHandler(Action<object, EventArgs> spawnBox)
{
Random randomPlek = new Random();
int xPlek;
xPlek = randomPlek.Next(1000, 1100);
int yPlek;
yPlek = (randomPlek.Next(0, 8)) * 100;
var picture = new PictureBox
{
Name = "pictureBoxLM",
Size = new Size(150, 100),
SizeMode = PictureBoxSizeMode.StretchImage,
BackColor = Color.Transparent,
Location = new Point(xPlek, yPlek),
Image = Leeuwenmier,
};
this.Controls.Add(picture);
}
The problem is that when trying to make them move or collide, Visual Studio can't find the name and gives an error. This is the code i used for collision:
if(PbMier.Bounds.IntersectsWith(pictureBoxLM.Bounds))
{
// some actions
}
How can I call the spawned picturebox in the code without getting an error?
WinForms controls have names, but that doesn't mean you can access them using that name as a C# identifier.
Your PictureBox only has a named reference within EventHandler(), namely picture, but once control leaves that method that reference goes out of scope.
You need to find the controls again, or find another way to reference the generated controls.
So either:
var allPictureBoxes = this.Controls.Find("PictureBoxLM");
foreach (var pictureBox in allPictureBoxes)
{
// ...
}
Or put this on your form:
List<PictureBox> pictureBoxList = new List<PictureBox>();
And then in the EventHandler();
this.Controls.Add(picture);
pictureBoxList.Add(picture);
After which you can use this for your collision detection:
foreach (var pictureBox in pictureBoxList)
{
// ...
}

Capturing all Screens with DirectX GetFrontBufferData

I'm trying to create a Screenshot of all Screens on my PC. In the past I've been using the GDI Method, but due to performance issues I'm trying the DirectX way.
I can take a Screenshot of a single Screen without issues, with a code like this:
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;
class Capture : Form
{
private Device device;
private Surface surface;
public Capture()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8B8G8R8, Pool.Scratch);
}
public Bitmap Frame()
{
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
(Lets ignore deleting the Bitmap from memory for this question)
With that Code I can take a Screenshot of my Primary Screen. Changing the first parameter of the Device constructor to a different number corresponds to a different Screen. If I have 3 Screens and I pass 2 as a parameter, I get a Screenshot of my third Screen.
The issue I have is how to handle capturing all Screens. I came up with the following:
class CaptureScreen : Form
{
private int index;
private Screen screen;
private Device device;
private Surface surface;
public Rectangle ScreenBounds { get { return screen.Bounds; } }
public Device Device { get { return device; } }
public CaptureScreen(int index, Screen screen, PresentParameters p)
{
this.screen = screen; this.index = index;
device = new Device(index, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, p);
surface = device.CreateOffscreenPlainSurface(screen.Bounds.Width, screen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch);
}
public Bitmap Frame()
{
device.GetFrontBufferData(0, surface);
GraphicsStream gs = SurfaceLoader.SaveToStream(ImageFileFormat.Jpg, surface);
return new Bitmap(gs);
}
}
class CaptureDirectX : Form
{
private CaptureScreen[] screens;
private int width = 0;
private int height = 0;
public CaptureDirectX()
{
PresentParameters p = new PresentParameters();
p.Windowed = true;
p.SwapEffect = SwapEffect.Discard;
screens = new CaptureScreen[Screen.AllScreens.Length];
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
screens[i] = new CaptureScreen(i, Screen.AllScreens[i], p);
//reset previous devices
if (i > 0)
{
for(int j = 0; j < i; j++)
{
screens[j].Device.Reset(p);
}
}
width += Screen.AllScreens[i].Bounds.Width;
if (Screen.AllScreens[i].Bounds.Height > height)
{
height = Screen.AllScreens[i].Bounds.Height;
}
}
}
public Bitmap Frame()
{
Bitmap result = new Bitmap(width, height);
using (var g = Graphics.FromImage(result))
{
for (int i = 0; i < screens.Length; i++)
{
Bitmap frame = screens[i].Frame();
g.DrawImage(frame, screens[i].Bounds);
}
}
return result;
}
}
As you can see, I iterate though the available Screens and create multiple devices and surfaces in a seperate Class. But calling Frame() of the CaptureDirectX class throws the following error:
An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll
At the line
device.GetFrontBufferData(0, surface);
I've been researching this a bit but didn't have a whole lot of success. I'm not really sure what the issue is.
I've found a link that offers a solution that's talking about resetting the Device Objects. But as you can see in my code above, I've been trying to reset all previously created Device objects, sadly without success.
So my questions are:
Is what I'm trying to achieve even possible through this method (i.e. GetFrontBufferData) ?
What am I doing wrong? What am I missing?
Do you see any performance issues when capturing the Screen at a high rate, like say 30 fps? (Capturing a single screen with a target of 30fps gave me a rate of about 25 - 30fps, compared with the GDI methology which sinks to like 15fps sometimes)
FYI it's a WPF application, i.e. .NET 4.5
Edit: I should mention that I'm aware of IDXGI_DesktopDuplication but sadly it doesn't fit my requirements. As far as I know, that API is only available from Windows 8 onwards, but I'm trying to get a solution that works from Windows 7 onwards because of my clients.
Well, in the end the solution was something completely different. The System.Windows.Forms.Screen Class doesn't play nicely with the DirectX Classes. Why? Because the indexes don't match up. The first object in AllScreens does not necessarly have to be index 0 in the Device instatiation.
Now usually this isn't a problem, except when you have a "strange" monitor setup like mine. On the desk I have 3 screens, one vertical (1200,1920), one horizontal (1920, 1200) and another horizontal laptop screen (1920, 1080).
What happened in my case: The first object in AllScreens was the vertical monitor on the left. I try to create a device for index 0, 1200 width and 1920 height. Index 0 corresponds to my main monitor though, i.e. the horizontal monitor in the middle. So I'm essentially going out of the screen bounds with my instatiation. The instatiation doesn't throw an exception and at some point later I try to read the front buffer data. Bam, Exception because I'm trying to take a 1200x1920 screenshot of a monitor that's 1920x1200.
Sadly, even after I got this working, the performance was no good. A single frame of all 3 monitors takes about 300 to 500ms. Even with a single monitor, the execution time was something like 100ms. Not good enough for my usecase.
Didn't get the Backbuffer to work either, it just produces black images.
I went back to the GDI method and enhanced it by only updating specific chunks of the bitmap on each Frame() call. You want to capture a 1920x1200 region, which gets cut into 480x300 Rectangles.

Displaying moving pieces in my chess game lags, what can I do about it?

First time I ever ask a question here so correct me if I´m doing it wrong.
Picture of my chess set:
Every time I move a piece it lags for about 1 second. Every piece and tile has an Image and there is exactly 96 Images. Every time I move a piece it clears everything with black and then update the graphics.
In the early stages of the chess I didn't have any Images and used different colors instead and only a few pieces there was no noticeable lag and the piece moved in an instant.
public void updateGraphics(PaintEventArgs e, Graphics g, Bitmap frame)
{
g = Graphics.FromImage(frame);
g.Clear(Color.Black);
colorMap(g);
g.Dispose();
e.Graphics.DrawImageUnscaled(frame, 0, 0);
}
The function colorMap(g) looks like this:
private void colorMap(Graphics g)
{
for (int y = 0; y < SomeInts.amount; y++)
{
for (int x = 0; x < SomeInts.amount; x++)
{
//Tiles
Bundle.tile[x, y].colorBody(g, x, y);
//Pieces
player1.colorAll(g);
player2.colorAll(g);
}
}
}
The colorAll function executes every pieces colorBody(g) function which look like this:
public void colorBody(Graphics g)
{
//base.colorBody() does the following: body = new Rectangle(x * SomeInts.size + SomeInts.size / 4, y * SomeInts.size + SomeInts.size / 4, size, size);
base.colorBody();
if (team == 1)
{
//If its a white queen
image = Image.FromFile("textures/piece/white/queen.png");
}
if (team == 2)
{
//If its a black queen
image = Image.FromFile("textures/piece/black/queen.png");
}
g.DrawImage(image, body);
}
and finaly the function that moves the piece:
public void movePiece(MouseEventArgs e)
{
for (int y = 0; y < SomeInts.amount; y++)
{
for (int x = 0; x < SomeInts.amount; x++)
{
if (Bundle.tile[x, y].body.Contains(e.Location))
{
//Ignore this
for (int i = 0; i < queens.Count; i++)
{
Queen temp = queens.ElementAt<Queen>(i);
temp.move(x, y);
}
//Relevant
player1.move(x, y);
player2.move(x, y);
}
}
}
}
Thank you for reading all this! I could make a link to the whole program if my coding examples is not enough.
You're calling Image.FromFile in every refresh, for every image - effectively reloading every image file every time from disk.
Have you considered loading the images once, and storing the resulting Images somewhere useful? (say, an array, Image[2,6] would be adequate)
Why do you redraw the board each time? Can't you just leave the board where it is and display an image with transparent background over it? That way you have one image as a background (the board), plus 64 smaller images placed over the board in a grid and just change the image being displayed on each move.
That way, you can let windows handle the drawing...
Also, load the images of the pieces at the start of the application.
In addition to not calling Image.FromFile() inside updateGraphics() (which is definitely your biggest issue), you shouldn't be attempting to redraw the entire board every on every call to updateGraphics() - most of the time, only a small portion of the board will be invalidated.
The PaintEventArgs contains an parameter, ClipRectangle, which specifies which portion of the board needs redrawing. See if you can't figure out which tiles intersect with that rectangle, and only redraw those tiles :)
Hint: Write a function Point ScreenToTileCoords(Point) which takes a screen coordinate and returns which board-tile is at that coordinate. Then the only tiles you need to redraw are
Point upperLeftTileToBeDrawn = ScreenToTileCoords(e.ClipRectangle.Left, e.ClipRectangle.Top);
Point lowerRightTileToBeDrawn = ScreenToTileCoords(e.ClipRectangle.Right - 1, e.ClipRectangle.Bottom- 1);
Also, make sure your control is double-buffered, to avoid tearing. This is much simpler than #Steve B's link in the comments above states; assuming this is a UserControl, simply set
this.DoubleBuffered = true;
Well, what about this:
Do not clear the whole board but only those parts that need to be cleared.
Alternative:
Update to WPF - it moves drawing to the graphics card - and just move pieces around, in a smart way (i.e. have a control / object for every piece).

Categories