我正在尝试在Windows窗体中制作2D滚动游戏;但是,我遇到了一个问题。
游戏在前几分钟运行良好;但是,随着应用程序继续运行,KeyDown
响应越来越慢。最终,它达到了按键(移动角色)的程度,完全冻结了应用程序。
但是,如果我没有按任何内容,其他方法/功能将继续顺利运行。
我相信有一些方法可以使用多个线程解决这个问题,但我不知道从哪里开始。我会为KeyDown
/ KeyUp
个事件创建一个单独的帖子吗?如果是这样,怎么样?或者有更简单的选择吗?
游戏的全部代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace Fly_High
{
public partial class Form1 : Form
{
//public variables
static bool blnFirstRun = false;
//variables for scrolling background
static Image imgBackground;
static Image[] imgBackgroundsArray = {GameImages.Gradient1, GameImages.Gradient2, GameImages.Gradient3, GameImages.Gradient4, GameImages.Gradient5, GameImages.Gradient6, GameImages.Gradient7, GameImages.Gradient8, GameImages.Gradient9, GameImages.Gradient10, GameImages.Gradient11};
static int imageXValue = 0;
static int imageYValue = 2400;
static int intScrollSpeed = 5;
static int intBackgroundCounter = 1;
//obstacle scrolling speed array
static int[] intObstacleScroll = {3, 1, 5, 2, 4};
//dimensions of picture
static int imgWidth = 0;
static int imgHeight = 0;
//images for spaceship picturebox
static Image imgShip = GameImages.alienblaster;
static Image imgShipLeft = GameImages.alienblasterLeft;
static Image imgShipRight = GameImages.alienblasterRight;
//speed variables for ship
static int intLeftAndRight = 5;
static bool blnUp = false;
static bool blnLeft = false;
static bool blnRight = false;
//square obstacles picturebox array
PictureBox[] pcbSquareObstacles = new PictureBox[5];
Boolean[] blnSquareObstacles = {false, false, false, false, false};
static int[] intSize1 = {42, 40};
static int[] intSize2 = {60, 19};
static int[] intSize3 = {16, 155};
static int[] intSize4 = {81, 47};
static int[] intSize5 = {23, 24};
static int[] intSize6 = {33, 71};
static int[][] intSizeArray = new int[][] {intSize1, intSize2, intSize3, intSize4, intSize5, intSize6};
static Color[] colorArray = {Color.Red, Color.Blue, Color.Lime};
//circle score bubbles array
PictureBox[] pcbScoreBubbles = new PictureBox[10];
Boolean[] blnScoreBubbleCollision = {false, false, false, false, false, false, false, false, false, false};
static Size szLeastPoints = new Size (10,10);
static Size szMediumPoints = new Size (15,15);
static Size szMostPoints = new Size (20, 20);
//score and life variables
static int intHits = 0;
static int intTmrCounter = 0;
static int intScore;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//load image from resources
imgBackground = GameImages.Gradient1;
imgWidth = imgBackground.Width;
imgHeight = imgBackground.Height;
//add doublebuffer to form, this prevents flickering
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.W) //if user presses w, increase scroll speed of image
{
if (blnFirstRun == false)
{
//if this is the first time the user presses 'w', display the following messages then start the timer.
blnFirstRun = true;
//display countdown before timer
lblCountDown.Text = "5";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(1000);
lblCountDown.Text = "4";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(1000);
lblCountDown.Text = "3";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(1000);
lblCountDown.Text = "2";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(1000);
lblCountDown.Text = "1";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(1000);
lblCountDown.Text = "GO!";
lblCountDown.Refresh();
System.Threading.Thread.Sleep(500);
lblCountDown.Visible = false;
//enable timer
tmrTick.Enabled = true;
}
//set up boolean to true
blnUp = true;
}
else if (e.KeyData == Keys.A)
{
//if user presses 'A', set left scroll boolean to true
blnLeft = true;
//change ship image
pcbShip.Image = imgShipLeft;
}
else if (e.KeyData == Keys.D) //if user press d, scroll to the right
{
//if user presses 'D', set right scroll bool to true
blnRight = true;
//change ship image
pcbShip.Image = imgShipRight;
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
//draw the image to the form
e.Graphics.DrawImage(imgBackground, new Rectangle(0, 0, this.Width, this.Height), new Rectangle(imageXValue, imageYValue, this.Width, this.Height), GraphicsUnit.Pixel);
}
private void tmrTick_Tick(object sender, EventArgs e)
{
/***************************************
* Date: 6/1/2014
* Description: Set up speed variables as well as timer to move along with the scrolling background.
* Note: Booleans are used in keydown event instead of actual movement to create smooth movement.
* The booleans are checked in the timer.
* Note: Keyup event was used to reset booleans to false.
***************************************/
//check for key down events
if (blnUp == true)
{
//do not allow scroll speed to exceed 7
if (intScrollSpeed < 8)
{
intScrollSpeed += 2;
}
}
//if the ship is not at the left edge, move to the left
if (pcbShip.Left > -10 && blnLeft == true)
{
pcbShip.Left -= intLeftAndRight;
}
//if ship is not at right edge, move to the right
if (pcbShip.Left < this.Width - pcbShip.Width -5 && blnRight == true)
{
pcbShip.Left += intLeftAndRight;
}
//scrolling background
imageYValue -= intScrollSpeed;
//create and move obstacles
ObstacleSquareMaker();
//check for collisions
ObstacleSquareCollision();
//move obstacles and check if they go offscreen
ObstacleOffScreenChecker();
//create bubbles
ScoreBubbleMaker();
//check for bubble collisions
ScoreBubbleCollision();
//move score bubbles and check if they go offscreen
ScoreBubbleOffScreenChecker();
//check for hits
LifeChecker();
//check if the background is almost up, if so load next background in array
if (imageYValue + imgHeight <= this.Height / 4)
{
imageYValue = 2600;
//if counter is larger than array length, reset it
if (intBackgroundCounter == 10)
{
intBackgroundCounter = 1;
}
else
{
//else, increment counter
intBackgroundCounter += 1;
}
imgBackground = imgBackgroundsArray[intBackgroundCounter];
}
//increment score by 10 for every 10 timer ticks
intTmrCounter += 1;
if (intTmrCounter == 10)
{
intTmrCounter = 0;
intScore += 10;
}
//refresh score label
lblScore.Text = intScore.ToString();
//refresh form
this.Invalidate();
//refresh picturebox
pcbShip.Refresh();
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
//check if any of the keys have been let go, if so, set the variables to false
if (e.KeyData == Keys.W)
{
//set w to false and set speed back to normal
blnUp = false;
intScrollSpeed = 5;
}
else if (e.KeyData == Keys.A)
{
//set a to false
blnLeft = false;
//set ship image to center
//change ship image
pcbShip.Image = imgShip;
}
else if (e.KeyData == Keys.D)
{
//set d to false
blnRight = false;
//change ship image
pcbShip.Image = imgShip;
}
}
public void ObstacleSquareMaker()
{
/***************************************
* Date: 7/1/2014
* Description: Created method to randomly generate pictureboxes. Size is determined from a predetermined set. Location is randomized.
* Backgrounds and other effects of pictureboxes will also be randomized.
* Name is based on position in array.
***************************************/
//variable declaration
Random randGenerator = new Random();
Point pntRandom = new Point();
int intRandomArrayValue;
//randomly generate obstacles
for (int i = 0; i < pcbSquareObstacles.Length; i++)
{
if (pcbSquareObstacles[i] == null)
{
//obstacle array
pcbSquareObstacles[i] = new PictureBox();
pcbSquareObstacles[i].Name = "pcbObstacle" + i;
//create new random point
pntRandom = new Point(randGenerator.Next(3, this.Width - 3), randGenerator.Next(-200, -100));
//assign location
pcbSquareObstacles[i].Location = pntRandom;
//check for overlaps
OverlapChecker();
//colour
pcbSquareObstacles[i].BackColor = colorArray[randGenerator.Next(0,3)];
//get size from size array
intRandomArrayValue = randGenerator.Next(0, 6);
pcbSquareObstacles[i].Size = new Size(intSizeArray[intRandomArrayValue][0], intSizeArray[intRandomArrayValue][1]);
//add to controls
this.Controls.Add(pcbSquareObstacles[i]);
//reset boolean collision array value
blnSquareObstacles[i] = false;
}
}
}
public void ObstacleOffScreenChecker()
{
//move obstacles down
for (int i = 0; i < pcbSquareObstacles.Length; i++)
{
if (pcbSquareObstacles[i] != null)
{
pcbSquareObstacles[i].Top += intObstacleScroll[i] + intScrollSpeed;
}
}
//check if the obstacles have gone off screen, if so, set array value to null
for (int i = 0; i < pcbSquareObstacles.Length; i++)
{
if (pcbSquareObstacles[i].Top > this.Height)
{
pcbSquareObstacles[i] = null;
}
}
}
public void ObstacleSquareCollision()
{
/***************************************
* Date: 6/1/2014
* Description: Collision detection for randomly spawned pictureboxes.
***************************************/
//check for collision
for (int i = 0; i < pcbSquareObstacles.Length; i++)
{
if (pcbSquareObstacles[i] != null)
{
if (pcbSquareObstacles[i].Bounds.IntersectsWith(pcbShip.Bounds) && blnSquareObstacles[i] == false)
{
blnSquareObstacles[i] = true;
intHits += 1;
lblLives.Size = new Size(115 - ((115 / 5) * intHits), 14);
}
}
}
}
public void OverlapChecker()
{
//variable declaration
Random randGenerator = new Random();
Point pntRandom = new Point();
//loop through obstacles array
for (int i = 0; i < pcbSquareObstacles.Length - 1; i++)
{
//ensure a null error is not raised
if (pcbSquareObstacles[i] != null && pcbSquareObstacles[i + 1] != null)
{
while ((pcbSquareObstacles[i].Bounds.IntersectsWith(pcbSquareObstacles[i + 1].Bounds)))
{
//create new random point
pntRandom = new Point(randGenerator.Next(3, this.Width - 3), randGenerator.Next(-200, -100));
//assign location
pcbSquareObstacles[i].Location = pntRandom;
}
}
}
}
public void ScoreBubbleMaker()
{
/***************************************
* Date: 12/1/2014
* Description: Created a method to continually generate score bubbles.
***************************************/
//variable declaration
Random randGenerator = new Random();
Point pntRandom = new Point();
//randomly generate score bubbles
for (int i = 0; i < pcbScoreBubbles.Length; i++)
{
if (pcbScoreBubbles[i] == null)
{
//create new picturebox
pcbScoreBubbles[i] = new PictureBox();
pcbScoreBubbles[i].Name = "pcbScoreBubble" + i;
//select size
if (randGenerator.Next(0,150) >= 135)
{
//largest size, most points
pcbScoreBubbles[i].Size = szMostPoints;
}
else if (randGenerator.Next(0,101) >= 100)
{
//medium points, medium number of points
pcbScoreBubbles[i].Size = szMediumPoints;
}
else
{
//smallest size, least number of points
pcbScoreBubbles[i].Size = szLeastPoints;
}
//test colour
pcbScoreBubbles[i].BackColor = Color.Yellow;
//create new random point
pntRandom = new Point(randGenerator.Next(3, this.Width - 3), randGenerator.Next(-200, -100));
//assign location to first point
pcbScoreBubbles[i].Location = pntRandom;
//add to controls
this.Controls.Add(pcbScoreBubbles[i]);
//reset collision array
blnScoreBubbleCollision[i] = false;
}
}
}
public void ScoreBubbleCollision()
{
/***************************************
* Date: 12/1/2014
* Description: Collision detection for bubbles. Set picturebox array value to null and increment score based on size.
***************************************/
//check for collision
for (int i = 0; i < pcbScoreBubbles.Length; i++)
{
if (pcbScoreBubbles[i] != null)
{
//check if it hits the ship
if (pcbScoreBubbles[i].Bounds.IntersectsWith(pcbShip.Bounds) && blnScoreBubbleCollision[i] == false)
{
//set collision variable to true (so it does not occur multiple times)
blnScoreBubbleCollision[i] = true;
//increment score based on size of points
if (pcbScoreBubbles[i].Size == szLeastPoints)
{
intScore += 10;
}
else if (pcbScoreBubbles[i].Size == szMediumPoints)
{
intScore += 50;
}
else
{
intScore += 100;
}
//make picturebox disappear
pcbScoreBubbles[i].Visible = false;
}
}
}
}
public void ScoreBubbleOffScreenChecker()
{
//move score bubbles
for (int x = 0; x < pcbScoreBubbles.Length; x++)
{
if (pcbScoreBubbles[x] != null)
{
pcbScoreBubbles[x].Top += intScrollSpeed;
}
}
//check if score bubbles have gone off screen, if so, set array value to null
for (int i = 0; i < pcbScoreBubbles.Length; i++)
{
if (pcbScoreBubbles[i].Top > this.Height)
{
pcbScoreBubbles[i] = null;
}
}
}
public void LifeChecker()
{
/***************************************
* Date: 7/1/2014
* Description: Used to determine number of hits and subsequently turn off timer if user collides with objects 5 times.
***************************************/
//check for collision
//check for the number of hits
if (intHits >= 5)
{
tmrTick.Enabled = false;
}
}
private void exitGameToolStripMenuItem_Click(object sender, EventArgs e)
{
//if exit game is clicked, exit entire application
Application.Exit();
}
}
}
我在按键时将运动变量设置为true,然后在计时器中执行实际运动。
修改 回答B.K:我恐怕我不确定'计时器到期'的意思,但是我会试着解释计时器中发生了什么。
首先,我在KeyDown
事件中按下某个键时采用布尔值。然后,我会在计时器的每个刻度上检查这些值,以移动我用于角色的图片框。
计时器的其余部分充满了产生各种障碍等的方法。
就我使用WinForms
的原因而言,因为我非常喜欢初学者而且不寻找任何特别复杂的东西。一旦掌握了基础知识,我可以看看XNA
;但是,就目前而言,我仍然需要了解很多WinForms
。