I am recreating a simple tile based game (ref: Javidx9 cpp tile game) on c# winforms and the screen flickers as i move, I have DoubleBuffered = true. i will show an example with textures and one without.
TEXTURES > e.Graphics.DrawImage()
NO TEXTURES > e.Graphics.FillRectangle()
in the code I made a GameManager, CameraManager, PlayerModel, lastly the form OnPaint that draws the game information. the way it works is the GameManager tells the Player to update itself depending on user input (move, jump, ect...), then tells the Camera to update depending on the players position. at first i called the GameManager.Update() from the Paint event but then i separated the Paint event from the GameManager and made the GameManager update asynchronous because the Paint event updates too slow. thats when the problem started.
//GameManager
public void CreateSoloGame(MapModel map)
{
CurrentMap = map;
ResetPlayer();
_inGame = true;
new Task(() =>
{
while (_inGame)
{
Elapsed = _stopwatch.ElapsedMilliseconds;
_stopwatch.Restart();
int i = 0;
Step(Elapsed);
while (i < _gameTime) //_gameTime controls the speed of the game
{
i++;
}
}
}).Start();
}
public void Step(double elapsed)
{
Player.Update(CurrentMap, elapsed);
Camera.SetCamera(Player.Position, CurrentMap);
}
//PlayerModel
public void DetectCollision(MapModel CurrentMap, double Elapsed)
{
//adds velocity to players position
float NextPlayerX = Position.X + (VelX * (float)Elapsed);
float NextPlayerY = Position.Y + (VelY * (float)Elapsed);
//collision detection
OnFloor = false;
if (VelY > 0)
{
//bottom
if (CurrentMap.GetTile((int)(Position.X + .1), (int)(NextPlayerY + 1)) == '#' || CurrentMap.GetTile((int)(Position.X + .9), (int)(NextPlayerY + 1)) == '#')
{
NextPlayerY = (int)NextPlayerY;
VelY = 0;
OnFloor = true;
_jumps = 2;
}
}
else
{
//top
if (CurrentMap.GetTile((int)(Position.X + .1), (int)NextPlayerY) == '#' || CurrentMap.GetTile((int)(Position.X + .9), (int)NextPlayerY) == '#')
{
NextPlayerY = (int)NextPlayerY + 1;
VelY = 0;
}
}
if (VelX < 0)
{
//left
if (CurrentMap.GetTile((int)NextPlayerX, (int)Position.Y) == '#' || CurrentMap.GetTile((int)NextPlayerX, (int)(Position.Y + .9)) == '#')
{
NextPlayerX = (int)NextPlayerX + 1;
VelX = 0;
}
}
else
{
//right
if (CurrentMap.GetTile((int)(NextPlayerX + 1), (int)Position.Y) == '#' || CurrentMap.GetTile((int)(NextPlayerX + 1), (int)(Position.Y + .9)) == '#')
{
NextPlayerX = (int)NextPlayerX;
VelX = 0;
}
}
//updates player position
Position = new PointF(NextPlayerX, NextPlayerY);
}
public void Jump()
{
if (_jumps > 0)
{
VelY = -.06f;
_jumps--;
}
}
public void ReadInput(double elapsed)
{
//resets velocity back to 0 if player isnt moving
if (Math.Abs(VelY) < 0.001f) VelY = 0;
if (Math.Abs(VelX) < 0.001f) VelX = 0;
//sets velocity according to player input - S and W are used for no clip free mode
//if (UserInput.KeyInput[Keys.W]) _playerVelY -= .001f;
//if (UserInput.KeyInput[Keys.S]) _playerVelY += .001f;
if (Input.KEYINPUT[Keys.A]) VelX -= .001f * (float)elapsed;
else if (Input.KEYINPUT[Keys.D]) VelX += .001f * (float)elapsed;
else if (Math.Abs(VelX) > 0.001f && OnFloor) VelX += -0.06f * VelX * (float)elapsed;
//resets jumping
if (!OnFloor)
VelY += .0004f * (float)elapsed;
//limits velocity
//if (_playerVelY <= -.014) _playerVelY = -.014f; //disabled to allow jumps
if (VelY >= .05) VelY = .05f;
if (VelX >= .02 && !Input.KEYINPUT[Keys.ShiftKey]) VelX = .02f;
else if (VelX >= .005 && Input.KEYINPUT[Keys.ShiftKey]) VelX = .005f;
if (VelX <= -.02 && !Input.KEYINPUT[Keys.ShiftKey]) VelX = -.02f;
else if (VelX <= -.005 && Input.KEYINPUT[Keys.ShiftKey]) VelX = -.005f;
}
public void Update(MapModel map, double elapsed)
{
ReadInput(elapsed);
DetectCollision(map, elapsed);
}
//CameraManager
public void SetCamera(PointF center, MapModel map, bool clamp = true)
{
//changes the tile size according to the screen size
TileSize = Input.ClientScreen.Width / Tiles;
//amount of tiles along thier axis
TilesX = Input.ClientScreen.Width / TileSize;
TilesY = Input.ClientScreen.Height / TileSize;
//camera offset
OffsetX = center.X - TilesX / 2.0f;
OffsetY = center.Y - TilesY / 2.0f;
//make sure the offset does not go beyond bounds
if (OffsetX < 0 && clamp) OffsetX = 0;
if (OffsetY < 0 && clamp) OffsetY = 0;
if (OffsetX > map.MapWidth - TilesX && clamp) OffsetX = map.MapWidth - TilesX;
if (OffsetY > map.MapHeight - TilesY && clamp) OffsetY = map.MapHeight - TilesY;
//smooths out movement for tiles
TileOffsetX = (OffsetX - (int)OffsetX) * TileSize;
TileOffsetY = (OffsetY - (int)OffsetY) * TileSize;
}
//Form Paint event
private void Draw(object sender, PaintEventArgs e)
{
Brush b;
Input.ClientScreen = ClientRectangle;
for (int x = -1; x < _camera.TilesX + 1; x++)
{
for (int y = -1; y < _camera.TilesY + 1; y++)
{
switch (_map.GetTile(x + (int)_camera.OffsetX, y + (int)_camera.OffsetY))
{
case '.':
//e.Graphics.DrawImage(sky, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, _camera.TileSize, _camera.TileSize);
//continue;
b = Brushes.MediumSlateBlue;
break;
case '#':
//e.Graphics.DrawImage(block, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, _camera.TileSize, _camera.TileSize);
//continue;
b = Brushes.DarkGray;
break;
case 'o':
b = Brushes.Yellow;
break;
case '%':
b = Brushes.Green;
break;
default:
b = Brushes.MediumSlateBlue;
break;
}
e.Graphics.FillRectangle(b, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, (x + 1) * _camera.TileSize, (y + 1) * _camera.TileSize);
}
}
e.Graphics.FillRectangle(Brushes.Purple, (_manager.Player.Position.X - _camera.OffsetX) * _camera.TileSize, (_manager.Player.Position.Y - _camera.OffsetY) * _camera.TileSize, _camera.TileSize, _camera.TileSize);
//e.Graphics.DrawImage(chef, (_manager.Player.Position.X - _camera.OffsetX) * _camera.TileSize, (_manager.Player.Position.Y - _camera.OffsetY) * _camera.TileSize, _camera.TileSize, _camera.TileSize);
Invalidate();
}
P.S. i use winforms because i dont work with GUIs much and its the one im most familiar with and this is just something quick i wanted to try out but i've never had this issue. i tried a couple of things but nothing worked so this is my last resort. if you think i should use another GUI let me know and ill look into it. also if you think my code is ugly lmk why.