C#Winform OnPaint闪烁

C#Winform OnPaint闪烁,c#,winforms,C#,Winforms,我正在c#winforms上重新创建一个简单的基于平铺的游戏(参考:),当我移动时,屏幕会闪烁,我有DoubleBuffered=true。我将展示一个有纹理的示例和一个没有纹理的示例 纹理> 无纹理> 在代码中,我创建了一个GameManager、CameraManager、PlayerModel,最后是绘制游戏信息的表单OnPaint。其工作方式是游戏管理器告诉玩家根据用户输入(移动、跳跃等)进行自我更新,然后告诉相机根据玩家的位置进行更新。起初,我从绘制事件调用GameManager.Up

我正在c#winforms上重新创建一个简单的基于平铺的游戏(参考:),当我移动时,屏幕会闪烁,我有DoubleBuffered=true。我将展示一个有纹理的示例和一个没有纹理的示例

纹理>

无纹理>

在代码中,我创建了一个GameManager、CameraManager、PlayerModel,最后是绘制游戏信息的表单OnPaint。其工作方式是游戏管理器告诉玩家根据用户输入(移动、跳跃等)进行自我更新,然后告诉相机根据玩家的位置进行更新。起初,我从绘制事件调用GameManager.Update(),但后来我将绘制事件与GameManager分离,并使GameManager更新异步,因为绘制事件更新太慢。这就是问题开始的时候

//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; }
}