C#WinForm中的太空侵略者

介绍 (Introduction)

Simple Space Invaders game made in C# WinForm. The sprites are being taken from:

用C#WinForm制作的Simple Space Invaders游戏。 精灵取自:

游戏如何运作 (How the Game Works)

  • Player moves left using the A and Left arrow buttons, or right using the D and Right arrow buttons or space to fire toward aliens.

    玩家使用A向左箭头按钮向左移动,或使用D向右箭头按钮向右移动或向外星人射击。

  • Aliens are moving from right to left and vice-versa lowering down when they hit the edge, firing at player while they are moving.

    外星人从右到左移动,反之亦然,当他们撞到边缘时降低,在移动时向玩家开火。
  • Player wins if he eliminate all the aliens before they reach him and loses if he gets hit 3 times by a laser, or gets in collision with some of aliens meaning that he failed to kill them in time.

    如果玩家在所有外星人到达他之前将其消灭,则获胜;如果被激光击中3次,或者与某些外星人发生碰撞,则表示玩家输了,这意味着他未能及时杀死他们。

使用代码 (Using the Code)

First, we're going to add a Player moving controls. In the Form Event grid, I used KeyDown and KeyUp events called Pressed and Released. There are also 3 global boolean values called moveRight and moveLeft which are going to be set to true depending on which buttons the player pressed:

首先,我们将添加一个Player移动控件。 在“表单事件”网格中,我使用了名为PressedReleased KeyDownKeyUp事件。 还有3个全局布尔值,称为moveRightmoveLeft ,它们将根据玩家按下的按钮设置为true

private void Pressed(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
    {
        moveLeft = true;
    }
    else if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
    {
        moveRight = true;
    }
    else if (e.KeyCode == Keys.Space && game && !fired)
    {
        Missile();
        fired = true;
    }
}
private void Released(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
    {
        moveLeft = false;
    }
    else if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
    {
        moveRight = false;
    }
    else if (e.KeyCode == Keys.Space)
    {
        fired = false;
    }
}
private void PlayerMove(object sender, EventArgs e)
{
    if (moveLeft && Player.Location.X >= 0)
    {
        Player.Left--;
    }
    else if (moveRight && Player.Location.X <= limit)
    {
        Player.Left++;
    }
}

If the player pressed space to fire, the value fired is going to get set to true to prevent continuous firing when the button is pressed, and there is additional check if the value game is set to true checking if the game is still active. If everything is ok, the procedure which creates bullet sprite called Missile() is called:

如果玩家按下空格键射击,则fired值将设置为true 以防止在按下按钮时连续触发,并且还要另外检查值game是否设置为true 检查游戏是否仍在运行。 如果一切正常,将调用创建名为Missile()子弹精灵的过程:

private void Missile()
{
 PictureBox bullet = new PictureBox();
 bullet.Location = new Point(Player.Location.X + Player.Width / 2, Player.Location.Y - 20);
 bullet.Size = new Size(5, 20);
 bullet.BackgroundImage = Properties.Resources.bullet;
 bullet.BackgroundImageLayout = ImageLayout.Stretch;
 bullet.Name = "Bullet";
 this.Controls.Add(bullet);
}

Now let's add some aliens to the form. For the purpose of better readability, I created a small class called Enemies which sets alien sprite parameters (size, images, quantity):

现在让我们向表单添加一些外星人。 为了提高可读性,我创建了一个名为Enemies的小类,该类设置外来精灵参数(大小,图像,数量):

  • Width and height presents the size of the alien sprite (40)

    Widthheight表示外星精灵的大小(40)

  • Rows and columns are the total number of aliens in order 5x10

    Rowscolumns是5x10顺序中的外星人总数

  • X and Y presents the starting coordinates of the sprites in form, and the space is the distance between them

    XY以形式表示子画面的起始坐标,并且space是它们之间的距离

class Enemies
{
    private int width, height;
    private int columns, rows;
    private int x, y, space; 

    public Enemies()
    {
        width = 40;
        height = 40;
        columns = 10;
        rows = 5;
        space = 10;
        x = 150;
        y = 0; 
    }
    private void CreateControl(Form p)
    {
        PictureBox pb = new PictureBox();
        pb.Location = new Point(x, y);
        pb.Size = new Size(width, height);
        pb.BackgroundImage = Properties.Resources.invader;
        pb.BackgroundImageLayout = ImageLayout.Stretch;
        pb.Name = "Alien";
        p.Controls.Add(pb); 
    }
    public void CreateSprites(Form p)
    {
        for(int i = 0; i < rows; i++)
        {
          for(int j = 0; j < columns; j++)
          {
              CreateControl(p);
              x += width + space; 
          }
              y += height + space;
              x = 150; 
        }
    }
}

To add them once the program is started, we need to create a class within the Form constructor:

要在程序启动后添加它们,我们需要在Form构造函数中创建一个类:

public Form1()
{
    InitializeComponent();
    new Enemies().CreateSprites(this);
    InsertAliens();
}   

And when this is done and you start the program, it should appear like this:

当完成此操作并启动程序时,它应如下所示:

Image 1

Now we're moving to the alien movement part. But before that, I'm going to isolate all the sprites (pictureBox-es) named "Alien" in one public list called aliens:

现在,我们将移至外星人运动部分。 但在此之前,我将在一个名为aliens公共列表中隔离所有名为“ Alien ”的精灵( pictureBox -es):

private void InsertAliens()
{
    foreach(Control c in this.Controls)
    {
        if (c is PictureBox && c.Name == "Alien")
        {
            PictureBox alien = (PictureBox)c;
            aliens.Add(alien); 
         }
    }
}

And we can go further now. As the aliens are moving from right to left and vice versa, when they touch one of the edges of the screen, they will go down and change direction so I had to create a certain logic for that purpose. First, I'm going to make a boolean function named Touched to check if the aliens touched the edge of the screen:

我们现在可以走得更远。 当外星人从右向左移动时,反之亦然,当他们触摸屏幕的边缘之一时,它们会下降并改变方向,因此我必须为此目的创建某种逻辑。 首先,我将创建一个名为Touched的布尔函数,以检查外星人是否触摸了屏幕的边缘:

private bool Touched(PictureBox a) 
{
    return a.Location.X <= 0 || a.Location.X >= limit; 
}

And then the SetDirection procedure. The values I'm using here are top and left for the direction of the aliens, cnt that counts how low aliens went, and speed which is going to switch the direction of the aliens at a certain point.

然后是SetDirection过程。 我使用的是这里的值是topleft外星人的方向, cnt才是最重要的低外星人如何去和speed这是会在某一点切换外星人的方向。

First, it will check if one of the aliens touched the edge of the screen, and if it did, the variable cnt will increment. When the cnt reaches the height size of a single alien, it will stop going down and change direction. The same thing will happen when the cnt reaches double size of the previous value meaning that the aliens collided with the edge from the other side and the direction will be changed again. After that, the cnt value will be set to 0 in order to do the same procedure in the next row:

首先,它将检查是否有外星人之一触摸了屏幕的边缘,如果确实触摸了,则变量cnt将增加。 当cnt达到单个外星人的高度时,它将停止下降并改变方向。 当cnt达到先前值的两倍大小时,将发生相同的事情,这意味着外星人从另一侧与边缘碰撞,方向将再次更改。 之后, cnt值将设置为0 ,以便在下一行中执行相同的过程:

private void SetDirection(PictureBox a)
{
    int size = a.Height;

    if (Touched(a))
    {
        top = 1; left = 0; cnt++;

        if (cnt == size)
        {
            top = 0; left = speed * (-1); Observer.Start();
        }
        else if (cnt == size * 2)
        {
            top = 0; left = speed; cnt = 0; Observer.Start();
        }
    }
}

The function of the timer called Observer will be explained later.

稍后将说明称为Observer的计时器的功能。

Now I will post the code for procedure called AlienMoves which loops through the list of aliens (called aliens) and moves the sprites by the coordinates that are being set in the SetDirection procedure. The AlienMoves procedure also contains another procedure which checks if the aliens collided with player in which case the game is over:

现在,我将张贴程序调用的代码AlienMoves它通过外国人(称为列表循环aliens )和移动精灵由正在中设置的坐标SetDirection程序。 AlienMoves过程还包含另一个过程,该过程检查外星人是否与玩家相撞,在这种情况下游戏结束了:

private void AlienMove()
{            
    foreach(PictureBox alien in aliens)
    {
        alien.Location = new Point(alien.Location.X + left, alien.Location.Y + top);
        SetDirection(alien);
        Collided(alien);                
    }
}

Collided procedure:

程序Collided

private void Collided(PictureBox a)
{
    if (a.Bounds.IntersectsWith(Player.Bounds))
    {
        gameOver();
    }
}

And the timer MoveAliens that calls the AlienMoves procedure to run things:

并调用AlienMoves过程以运行事物的计时器MoveAliens

private void MoveAliens(object sender, EventArgs e)
{
    AlienMove();
}

Now we arrived at the part of the aliens firing towards player. Just like in case of player, first we need to write a procedure which creates a sprite of the laser. It's called Beam:

现在我们到达了外星人向玩家射击的那一部分。 就像播放器一样,首先我们需要编写一个程序来创建激光的精灵。 叫做Beam

private void Beam(PictureBox a)
{
 PictureBox laser = new PictureBox();
 laser.Location = new Point(a.Location.X + a.Width / 3, a.Location.Y + 20);
 laser.Size = new Size(5, 20);
 laser.BackgroundImage = Properties.Resources.laser;
 laser.BackgroundImageLayout = ImageLayout.Stretch;
 laser.Name = "Laser";
 this.Controls.Add(laser);
}

Before I post the code, I will explain how the firing works. We have two timers where one presents the time span at which the lasers are going to be fired called StrikeSpan and its time interval is set to 1000 (1s) meaning that after each second, a laser is going to be fired. The other one called DetectLaser finds the laser that is created and hurls it down the ground by setting its Top property to 5. That timer is set to interval of 1ms.

在发布代码之前,我将解释触发方式。 我们有两个计时器,其中一个表示要发射激光的时间跨度,称为StrikeSpan ,其时间间隔设置为1000(1s),这意味着每秒钟将发射一激光。 另一个名为DetectLaser的激光器会找到所产生的激光,并通过将其Top设置为Top DetectLaser到地面。 属性设置为5。该计时器设置为1毫秒的间隔。

StrikeSpan:

StrikeSpan

private void StrikeSpan(object sender, EventArgs e)
{
    Random r = new Random();
    int pick; 

    if (aliens.Count > 0)
    {
        pick = r.Next(aliens.Count);
        Beam(aliens[pick]);
    }
}

DetectLaser:

DetectLaser

private void DetectLaser(object sender, EventArgs e)
{
    foreach(Control c in this.Controls)
    {
        if (c is PictureBox && c.Name == "Laser")
        {
            PictureBox laser = (PictureBox)c;
            laser.Top += 5; 

            if (laser.Location.Y >= limit)
            {
                this.Controls.Remove(laser); 
            }
            if (laser.Bounds.IntersectsWith(Player.Bounds))
            {
                this.Controls.Remove(laser); 
                LoseLife(); 
            }                    
         }
     }
}  

As you can see, the code checks if the laser left the field in which case it's going to be removed, or if laser collided with player when the player loses life.

如您所见,该代码检查激光是否离开了视野,在这种情况下它将被移除,或者当玩家丧命时激光是否与玩家相撞。

That was a part of the aliens striking. Now it's time for the player.

那是外星人打击的一部分。 现在是时候播放器了。

We have a single timer called FireBullet, but things are a bit more complicated as we have to perform multiple different checks. I'll separate the one important part:

我们只有一个称为FireBullet计时器,但是由于我们必须执行多个不同的检查,所以事情有些复杂。 我将分开一个重要部分:

if (bullet.Bounds.IntersectsWith(alien.Bounds) && !Touched(alien))
{
    this.Controls.Remove(bullet);
    this.Controls.Remove(alien);
    aliens.Remove(alien);
    pts += 5;
    Score(pts);
    CheckForWinner();
}
else if (bullet.Bounds.IntersectsWith(alien.Bounds) && Touched(alien))
{
    this.Controls.Remove(bullet);
    this.Controls.Remove(alien);
    delay.Add(alien);
    pts += 5;
    Score(pts);
    CheckForWinner();
}

While testing the code, I figured out that when the aliens touch the edge of the screen and I destroy them, the condition from procedure SetDirection returns false and the procedure fails to switch direction properly, as there are no more aliens touching the edge, so they would just keep moving down which presents the logical issue. I solved that problem by creating an additional global list called delay and a timer called Observer, so when I destroy aliens when they touched the edge, the bullet and alien controls are being removed, and instead of deleting the picture from the list aliens (from which they are moving on the screen), I add all destroyed pictures to delay list first so they still exist in aliens list to be able to switch direction. Once they switched direction, I call Observer timer which then removes all the aliens from the aliens list and clear the delay list. So when they reach the edge of the same side I destroyed the aliens from before, there won't be an empty space between aliens and the screen, and the sprites will change the direction correctly. Timer also checks if the bullet left the screen in which case it gets removed, or if it collided with some of the lasers. The full code is as given below:

在测试代​​码时,我发现当外星人触摸屏幕边缘并销毁它们时,过程SetDirection的条件将返回false并且该过程无法正确切换方向,因为不再有外星人触摸边缘,因此他们只会继续往下走,这是一个逻辑问题。 我通过创建一个额外的全局列表(称为delay和一个称为Observer的计时器解决了该问题,因此当我摧毁外星人触摸边缘时,将删除项目符号和外星人控件,而不是从aliens列表中删除图片(从(它们正在屏幕上移动),我首先将所有损坏的图片添加到delay列表中,以便它们仍存在于aliens列表中以便能够切换方向。 一旦他们切换了方向,我将调用Observer计时器,该计时器将所有外星人从aliens列表中删除并清除delay列表。 因此,当它们到达同一边的边缘时,我从前消灭了外星人,外星人和屏幕之间将不会有空白空间,并且精灵会正确地改变方向。 计时器还会检查子弹是否离开屏幕,在这种情况下它会被移除,或者是否与某些激光发生碰撞。 完整的代码如下所示:

private void FireBullet(object sender, EventArgs e)
{
    foreach (Control c in this.Controls)
    {
        if (c is PictureBox && c.Name == "Bullet")
        {
            PictureBox bullet = (PictureBox)c;
            bullet.Top -= 5;

            if (bullet.Location.Y <= 0)
            {
                this.Controls.Remove(bullet); 
            }
            foreach(Control ct in this.Controls)
            {
                if (ct is PictureBox && ct.Name == "Laser")
                {
                    PictureBox laser = (PictureBox)ct;

                    if (bullet.Bounds.IntersectsWith(laser.Bounds))
                    {
                        this.Controls.Remove(bullet);
                        this.Controls.Remove(laser);
                        pts++;
                        Score(pts);
                    }
                 }
             }
             foreach(Control ctrl in this.Controls)
             {
                 if (ctrl is PictureBox && ctrl.Name == "Alien")
                 {
                      PictureBox alien = (PictureBox)ctrl;

                      if (bullet.Bounds.IntersectsWith(alien.Bounds) && !Touched(alien))
                      {
                          this.Controls.Remove(bullet);
                          this.Controls.Remove(alien);
                          aliens.Remove(alien);
                          pts += 5;
                          Score(pts);
                          CheckForWinner();
                      }
                      else if (bullet.Bounds.IntersectsWith(alien.Bounds) && Touched(alien))
                      {
                          this.Controls.Remove(bullet);
                          this.Controls.Remove(alien);
                          delay.Add(alien);
                          pts += 5;
                          Score(pts);
                          CheckForWinner();
                      }
                  }
              }
        }
    }
}

Observer timer:

Observer计时器:

private void Observe(object sender, EventArgs e)
{
    Observer.Stop();

    foreach (PictureBox delayed in delay)
    {
        aliens.Remove(delayed);
    }
    delay.Clear();
}

And for the end, I left procedures which check whether the player has won, lost life, if the game is over and add a score in certain situations.

最后,我留下了一些程序来检查玩家是否赢了,输了命,如果游戏结束了,并在某些情况下增加了分数。

If the bullet collides with a laser, the player gets 1 point, and if the alien is destroyed, he gets 5. To write the result:

如果子弹与激光碰撞,则玩家得1分;如果外星人被摧毁,则得5分。要写出结果:

private void Score(int pts)
{
    label2.Text = "Score: " + pts.ToString();
}

When the player gets hit by a laser, he loses life, and at that point, the small picture of tank in the lower left screen gets removed and the player is centered at his starting position in the form. It also checks if the game is over:

当玩家被激光击中时,他会丧命,这时,左下方屏幕上坦克的小图片将被删除,玩家将以表格中的起始位置为中心。 它还会检查游戏是否结束:

private void LoseLife()
{
    Player.Location = new Point(x, y);

    foreach(Control c in this.Controls)
    {
        if (c is PictureBox && c.Name.Contains("Life") && c.Visible == true)
        {
            PictureBox player = (PictureBox)c;
            player.Visible = false;
            return;
        }
    }
    gameOver(); 
}

And if the game is over, we stop all of the timers, loop through the form finding the label with the name "Finished". Once it's been found, we're writing the text "Game Over" and all the other controls visibility is set to false:

如果游戏结束了,我们将停止所有计时器,在表单中循环查找名称为“ Finished ”的标签。 一旦找到它,我们将编写文本“ Game Over ”,并将所有其他控件的可见性设置为false

private void gameOver()
{
 timer1.Stop(); timer2.Stop(); timer3.Stop(); timer4.Stop(); timer5.Stop(); Observer.Stop();

 foreach (Control c in this.Controls)
 {
     if (c is Label && c.Name == "Finish")
     {
         Label lbl = (Label)c;
         lbl.Text = "Game Over!";
         game = false;
     }
     else
     {
         c.Visible = false;
     }
 }
}

Finding a winner contains two small procedures. The first one called CheckForWinner() counts the number of the pictureBox-es with the name "Alien". if the count is 0, it calls YouWon() procedures that again finds the control named "Finish" writing the text "You Won" and presenting the score player achieved. It also sets game value to false preventing the player from creating sprites of the bullet when the space is hit:

寻找赢家包括两个小程序。 第一个称为CheckForWinner()的名称为“ Alien ”的pictureBox -es的数量。 如果计数为0 ,则它调用YouWon()过程,再次找到名为“ Finish ”的控件,将文本“ You Won ”写入并显示所取得的得分。 它还将game值设置为false防止玩家在打空格时创建子弹精灵:

private void CheckForWinner()
{
    int count = 0; 

    foreach(Control c in this.Controls)
    {
        if (c is PictureBox && c.Name == "Alien") count++; 
    }

    if (count == 0) YouWon(); 
}

YouWon procedure:

YouWon程序:

private void YouWon()
{
    game = false; 

    foreach(Control c in this.Controls)
    {
        if (c is Label && c.Name == "Finish")
        {
            Label lbl = (Label)c;
            lbl.Text = "You Won!" + "\n"
                       + "Score: " + pts.ToString(); 
        }
        else
        {
            c.Visible = false; 
        }
    }
}

兴趣点 (Points of Interest)

Next time, there could be a lot of potential updates to the game.

下次,游戏可能会有很多潜在的更新。

翻译自: https://www.codeproject.com/Articles/5252990/Space-Invaders-in-Csharp-WinForm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值