Author Topic: Programming in C#  (Read 14477 times)

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Programming in C#
« on: February 22, 2020, 03:52:26 PM »
I'm trying again something different and use C# which is relatively new and unknown language to me. This time (I'm sure I've done this before!) I'm trying to create a simple "engine" for a roguelike using graphics for ascii tiles. My plan is print the ascii chars to an image and then use that. I could have put this in my blog, but it's such a hassle for code examples. And maybe I can share something I've learned.

First "funny" thing when you start with a Forms project is that when you create an initialization routine for the form you -have- to double click the form in the designer window. It auto-creates Form1_Load method which is linked to the form. You can't just write it. The reason for that is unknown. But you can then override the OnPaint which is an event called when the window is repainted. It's I think the way to implement output from the event side of the form. So in code it looks like this:

Code: [Select]
private void Form1_Load(object sender, EventArgs e)
{
Graphics g = CreateGraphics();
gui = new Gui(ref g);
}

protected override void OnPaint(PaintEventArgs pe)
{
gui.Redraw();
}

You need "Graphics" to draw anything to a surface and in this case it's getting the surface from 'this' which is Form1 class (default name). Then I'm passing it as reference to Gui class which can use it to draw on the canvas of the form (another name for a window).
« Last Edit: February 22, 2020, 03:55:58 PM by Krice »

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Programming in C#
« Reply #1 on: February 23, 2020, 08:32:11 AM »
The plant to imprint ascii into a bitmap was quite easy, although using C#'s internal Color values is bit strange, because DarkGray value is actually brighter than Gray, for some reason. I think I need to create my own set of colors.

Code: [Select]
public Gui(ref Graphics g)
{
//assign form's drawing surface for gui class
this.g = g;

const int amt = sizeof(Glyphs) - 1;

//tile data of glyphs
tiles = new Glyph[amt]
{
new Glyph ( ' ', Color.Black, Color.Black, "Darkness" ),
new Glyph ( '#', Color.Green, Color.DarkGray, "Wall" ),
new Glyph ( '.', Color.Gray, Color.Black, "Floor" )
};

//create glyph tiles source image
glyphs = new Bitmap(
fontSize.Width*fontGrid.Width,
fontSize.Height*fontGrid.Height);

Graphics bm = Graphics.FromImage(glyphs);
bm.Clear(Color.Blue);

Font fnt = new Font("Consolas", 16);
Point source = new Point(0, 0);
StringFormat drawFormat = new StringFormat();

for (int t=0; t<amt; t++)
{
//draw background slab
SolidBrush bgBrush = new SolidBrush(tiles[t].backgroundColor);
Rectangle rect = new Rectangle(source.X, source.Y, fontSize.Width, fontSize.Height);
bm.FillRectangle(bgBrush, rect);

//draw letter
string str = tiles[t].ascii.ToString();
SolidBrush letterBrush = new SolidBrush(tiles[t].color);

bm.DrawString(str, fnt, letterBrush, source.X, source.Y, drawFormat);

//move to next source location
source.X += fontSize.Width;
if (source.X>=glyphs.Width)
{
source.X = 0;
source.Y += fontSize.Height;
}
}

fnt.Dispose();
}

Two new observations I made were that enum isn't auto-converted into int even it's declared using int internal type. And also that even if you have a struct, you need to declare each variable 'public' if you want to use them outside the struct, even if the struct itself is declared public. I guess it makes sense, but it's a bit different from C++.
« Last Edit: February 23, 2020, 08:38:10 AM by Krice »

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Programming in C#
« Reply #2 on: February 24, 2020, 10:03:00 AM »
Resizing the form manually seems to clip the output to original, but you can set the size in form designer. However, the sizes are off. When I set the size to 1280 x 600 the actual pixel size of the content is 1264 x 561 and even if you count in the borders the sizes don't match. Also, displaying 80x25 tiles of that window size is slow, you can't deny it. You can actually see it being drawn. I wonder what to do now.

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Programming in C#
« Reply #3 on: February 24, 2020, 01:24:12 PM »
The clipping -and- speed were somehow magically fixed by using something called BufferedGraphics class. I found this information totally randomly from the depths of internet. Also, when you set ClientSize (rather than Size) it properly sets the size of content, like this:

Code: [Select]
private void Form1_Load(object sender, EventArgs e)
{
Graphics g = CreateGraphics();
gui = new Gui(ref g);

//resize the form to match the size of virtual console
ClientSize = gui.GetPixelSize();
}

For drawing first I changed Gui class to use BufferedGraphics (this has only important parts of code):

Code: [Select]
Graphics g;
viewTile[,] console;
public BufferedGraphics graphicsBuffer;

public Gui(ref Graphics srcgrp)
{
graphicsBuffer = BufferedGraphicsManager.Current.Allocate
(srcgrp, new Rectangle(0, 0, viewGrid.Width * fontSize.Width, viewGrid.Height * fontSize.Height));

g = graphicsBuffer.Graphics;

Then in the OnPaint of form, use BufferedGraphics like this:

Code: [Select]
protected override void OnPaint(PaintEventArgs pe)
{
gui.Redraw();
gui.graphicsBuffer.Render(pe.Graphics);
}

At least it appears to be fast, but it might just look like it, because the whole buffer is flipped on screen rather than drawing each tile and showing it. Maybe the explanation is that when you draw a tile the whole window is updated which could explain why it's so slow.

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Programming in C#
« Reply #4 on: February 26, 2020, 05:59:58 PM »
I'm wondering how to implement Level class here. Should it be a tile map of simple tiles with some kind of object id (or a list?) pointing to an object(s) in that tile? Or should terrain tiles be also objects? Maybe only walls and corridors could be actual objects and the empty spaces would be nulls.

Vosvek

  • Guest
Re: Programming in C#
« Reply #5 on: February 27, 2020, 07:51:13 AM »
I'm surprised you went with Forms over SDL.NET given that Teemu uses SDL2, you're effectively creating the graphics yourself, and Forms is slow and easy to leak accidentally.

For your map/level, it depends on how much you care about the amount of memory you're using, on version control for game saves, and on potential future headaches. An array of IDs for tiles that "reference" a data table of tile data probably is better for your sanity in the long term. At the very least, saving, loading, and manipulating the map is just a matter of changing what is probably an unsigned integer. It also makes comparing tiles much easier, and ensures you're not accidentally making duplicate or contradicting build/creation functions/methods. I guess it also depends on what's in the tile: does it have health? If so, that will probably need to be saved out, and so needs to be part of an object instance, rather than a static data record.

The big difference between struct and class in C#, by the way, is that structs get allocated to the stack. So if you do use them, make sure you keep their size fairly small, else they become inefficient (they're great for the command pattern). Also do beware that C# and Java hide their pointers in classes, so a struct with a string means that the string gets allocated, but the struct itself only holds a pointer to the string. On deconstruction, that struct will be deallocated immediately, but the string will probably sit around until the garbage collector chooses to free it... if it ever does. ;)

Krice

  • (Banned)
  • Rogueliker
  • ***
  • Posts: 2316
  • Karma: +0/-2
    • View Profile
    • Email
Re: Programming in C#
« Reply #6 on: February 28, 2020, 07:24:27 AM »
The big difference between struct and class in C#, by the way, is that structs get allocated to the stack.

I actually knew that, the time of wonders is not over! That feature I believe was borrowed from D which has the same strange thing (value and reference types). I'm using mostly classes for that reason. I guess if you use a struct datatype as class member it gets allocated to heap as a whole object?

What do you mean Forms is easy to leak? C# is a garbage collected language, it can't leak. Right?
« Last Edit: February 28, 2020, 07:26:31 AM by Krice »

Vosvek

  • Guest
Re: Programming in C#
« Reply #7 on: February 28, 2020, 09:10:24 AM »
The big difference between struct and class in C#, by the way, is that structs get allocated to the stack.

I actually knew that, the time of wonders is not over! That feature I believe was borrowed from D which has the same strange thing (value and reference types). I'm using mostly classes for that reason. I guess if you use a struct datatype as class member it gets allocated to heap as a whole object?

What do you mean Forms is easy to leak? C# is a garbage collected language, it can't leak. Right?

That is correct. Structs are just "value types", so they allocate effectively the same way as primitives: ints, floats, etc. :D

That's the dream... but it definitely can leak, just not as easily/obviously. You should be fine for the most part, unless you're using Forms' GUI elements (i.e. buttons). In which case, you'll need to keep a close eye on stack calls to make sure things are definitely getting collected. Events as well can cause headaches. You should always remove listeners, or if there's no obvious way to do so, use a WeakReference. :)