C#Winform OnPaint闪烁
我正在c#winforms上重新创建一个简单的基于平铺的游戏(参考:),当我移动时,屏幕会闪烁,我有DoubleBuffered=true。我将展示一个有纹理的示例和一个没有纹理的示例 纹理> 无纹理> 在代码中,我创建了一个GameManager、CameraManager、PlayerModel,最后是绘制游戏信息的表单OnPaint。其工作方式是游戏管理器告诉玩家根据用户输入(移动、跳跃等)进行自我更新,然后告诉相机根据玩家的位置进行更新。起初,我从绘制事件调用GameManager.Update(),但后来我将绘制事件与GameManager分离,并使GameManager更新异步,因为绘制事件更新太慢。这就是问题开始的时候C#Winform OnPaint闪烁,c#,winforms,C#,Winforms,我正在c#winforms上重新创建一个简单的基于平铺的游戏(参考:),当我移动时,屏幕会闪烁,我有DoubleBuffered=true。我将展示一个有纹理的示例和一个没有纹理的示例 纹理> 无纹理> 在代码中,我创建了一个GameManager、CameraManager、PlayerModel,最后是绘制游戏信息的表单OnPaint。其工作方式是游戏管理器告诉玩家根据用户输入(移动、跳跃等)进行自我更新,然后告诉相机根据玩家的位置进行更新。起初,我从绘制事件调用GameManager.Up
//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();
}
//游戏管理器
公共void CreateSoloGame(地图模型地图)
{
CurrentMap=map;
重置播放器();
_inGame=true;
新任务(()=>
{
而(_inGame)
{
已用时间=_stopwatch.ElapsedMilliseconds;
_stopwatch.Restart();
int i=0;
步骤(经过);
而(i<\u gameTime)/\u gameTime控制游戏的速度
{
i++;
}
}
}).Start();
}
公共无效步骤(双倍运行)
{
更新(当前地图,已用);
摄像机。设置摄像机(播放器。位置,当前地图);
}
//PlayerModel
公共无效检测冲突(MapModel CurrentMap,双运行)
{
//增加球员位置的速度
float NextPlayerX=位置X+(VelX*(float)经过);
float NextPlayerY=Position.Y+(浮点*(float)经过);
//碰撞检测
OnFloor=错误;
如果(值>0)
{
//底部
如果(CurrentMap.GetTile((int)(Position.X+.1),(int)(NextPlayerY+1))='#'|'| CurrentMap.GetTile((int)(Position.X+.9),(int)(NextPlayerY+1))='#
{
NextPlayerY=(int)NextPlayerY;
f=0;
OnFloor=正确;
_跳跃=2;
}
}
其他的
{
//顶
如果(CurrentMap.GetTile((int)(Position.X+.1),(int)NextPlayerY)='#'| | CurrentMap.GetTile((int)(Position.X+.9),(int)NextPlayerY)='#')
{
NextPlayerY=(int)NextPlayerY+1;
f=0;
}
}
if(VelX<0)
{
//左
如果(CurrentMap.GetTile((int)NextPlayerX,(int)Position.Y)='#'| | CurrentMap.GetTile((int)NextPlayerX,(int)(Position.Y+.9))='#')
{
NextPlayerX=(int)NextPlayerX+1;
VelX=0;
}
}
其他的
{
//对
如果(CurrentMap.GetTile((int)(NextPlayerX+1),(int)Position.Y)='#'| | CurrentMap.GetTile((int)(NextPlayerX+1),(int)(Position.Y+.9))='#
{
NextPlayerX=(int)NextPlayerX;
VelX=0;
}
}
//更新球员位置
位置=新点F(下一层,下一层);
}
公共跳转
{
如果(_跳跃>0)
{
VelY=-.06f;
_跳跃--;
}
}
公共无效读取输入(双倍运行)
{
//如果玩家不移动,将速度重置回0
如果(数学绝对值(Vly)<0.001f)Vly=0;
如果(数学绝对值(VelX)<0.001f)VelX=0;
//根据玩家输入设置速度-S和W用于无剪辑模式
//if(UserInput.KeyInput[Keys.W])u playervly-=.001f;
//if(UserInput.KeyInput[Keys.S])u playervly+=0.001f;
如果(Input.KEYINPUT[Keys.A])VelX-=.001f*(浮点)已过;
如果(Input.KEYINPUT[Keys.D])VelX+=.001f*(浮点)已过,则为else;
否则,如果(数学绝对值(VelX)>0.001f&地板上)VelX+=-0.06f*VelX*(浮动)已过;
//重置跳跃
如果(!在地板上)
fly+=.0004f*(浮动)已过;
//极限速度
//如果(_playerly=.05)VelY=.05f;
如果(VelX>=.02&&!Input.KEYINPUT[Keys.ShiftKey])VelX=.02f;
else if(VelX>=.005&&Input.KEYINPUT[Keys.ShiftKey])VelX=.005f;
if(VelX map.MapHeight-TilesY&&clamp)OffsetY=map.MapHeight-TilesY;
//平滑瓷砖的移动
TileOffsetX=(OffsetX-(int)OffsetX)*TileSize;
TileOffsetY=(OffsetY-(int)OffsetY)*TileSize;
}
//Form Paint事件
私有void Draw(对象发送器、PaintEventArgs e)
{
刷子b;
Input.ClientScreen=ClientRectangle;
对于(int x=-1;x<\u camera.tilex+1;x++)
{
对于(int y=-1;y<\u camera.TilesY+1;y++)
{
开关(_map.GetTile(x+(int)_camera.OffsetX,y+(int)_camera.OffsetY))
{
案例“”:
//e、 Graphics.DrawImage(天空,x*\U camera.TileSize-\U camera.TileOffsetX,y*\U camera.TileSize-\U camera.TileOffsetY,\U camera.TileSize,\U camera.TileSize,\U camera.TileSize);
//继续;
b=刷子。中蓝色;
打破
案例“#”:
//e、 Graphics.DrawImage(块,x*\U camera.TileOSize-\U camera.TileOffsetX,y*\U camera.TileOSize-\U camera.TileOffsetY,\U ca
public partial class RunningForm1 : Form
{
static readonly Random rng = new Random();
float offset;
int index;
Queue<int> town;
const int grid = 12;
Color[] pallete;
FpsCounter clock;
#region Windows API - User32.dll
[StructLayout(LayoutKind.Sequential)]
public struct WinMessage
{
public IntPtr hWnd;
public Message msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out WinMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
public RunningForm1()
{
InitializeComponent();
this.pic.Paint += new PaintEventHandler(this.pic_Paint);
this.pic.SizeChanged += new EventHandler(this.pic_SizeChanged);
//Initialize the machine
this.clock = new FpsCounter();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Setup();
System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle);
}
void UpdateMachine()
{
pic.Refresh();
}
#region Main Loop
private void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
UpdateMachine();
}
}
private bool AppStillIdle
{
get
{
WinMessage msg;
return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
}
#endregion
private void pic_SizeChanged(object sender, EventArgs e)
{
pic.Refresh();
}
private void pic_Paint(object sender, PaintEventArgs e)
{
// Show FPS counter
var fps = clock.Measure();
var text = $"{fps:F2} fps";
var sz = e.Graphics.MeasureString(text, SystemFonts.DialogFont);
var pt = new PointF(pic.Width - 1 - sz.Width - 4, 4);
e.Graphics.DrawString(text, SystemFonts.DialogFont, Brushes.Black, pt);
// draw on e.Graphics
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.TranslateTransform(0, pic.ClientSize.Height - 1);
e.Graphics.ScaleTransform(1, -1);
int wt = pic.ClientSize.Width / (grid - 1);
int ht = pic.ClientSize.Height / (grid + 1);
SolidBrush fill = new SolidBrush(Color.Black);
for (int i = 0; i < town.Count; i++)
{
float x = offset + i * wt;
var building = new RectangleF(
x, 0,
wt, ht * town.ElementAt(i));
fill.Color = pallete[(index + i) % pallete.Length];
e.Graphics.FillRectangle(fill, building);
}
offset -= 0.4f;
if (offset <= -grid - wt)
{
UpdateTown();
offset += wt;
}
}
private void Setup()
{
offset = 0;
index = 0;
town = new Queue<int>();
pallete = new Color[]
{
Color.FromKnownColor(KnownColor.Purple),
Color.FromKnownColor(KnownColor.Green),
Color.FromKnownColor(KnownColor.Yellow),
Color.FromKnownColor(KnownColor.SlateBlue),
Color.FromKnownColor(KnownColor.LightCoral),
Color.FromKnownColor(KnownColor.Red),
Color.FromKnownColor(KnownColor.Blue),
Color.FromKnownColor(KnownColor.LightCyan),
Color.FromKnownColor(KnownColor.Crimson),
Color.FromKnownColor(KnownColor.GreenYellow),
Color.FromKnownColor(KnownColor.Orange),
Color.FromKnownColor(KnownColor.LightGreen),
Color.FromKnownColor(KnownColor.Gold),
};
for (int i = 0; i <= grid; i++)
{
town.Enqueue(rng.Next(grid) + 1);
}
}
private void UpdateTown()
{
town.Dequeue();
town.Enqueue(rng.Next(grid) + 1);
index = (index + 1) % pallete.Length;
}
}
public class FpsCounter
{
public FpsCounter()
{
this.PrevFrame = 0;
this.Frames = 0;
this.PollOverFrames = 100;
this.Clock = Stopwatch.StartNew();
}
/// <summary>
/// Use this method to poll the FPS counter
/// </summary>
/// <returns>The last measured FPS</returns>
public float Measure()
{
Frames++;
PrevFrame++;
var dt = Clock.Elapsed.TotalSeconds;
if (PrevFrame > PollOverFrames || dt > PollOverFrames / 50)
{
LastFps = (float)(PrevFrame / dt);
PrevFrame = 0;
Clock.Restart();
}
return LastFps;
}
public float LastFps { get; private set; }
public long Frames { get; private set; }
private Stopwatch Clock { get; }
private int PrevFrame { get; set; }
/// <summary>
/// The number of frames to average to get a more accurate frame count.
/// The higher this is the more stable the result, but it will update
/// slower. The lower this is, the more chaotic the result of <see cref="Measure()"/>
/// but it will get a new result sooner. Default is 100 frames.
/// </summary>
public int PollOverFrames { get; set; }
}