介绍 (Introduction)

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

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

  • https://www.mooict.com/wp-content/uploads/2017/03/Spaceinvaders-resources-mooict.zip

    https://www.mooict.com/wp-content/uploads/2017/03/Spaceinvaders-resources-mooict.zip

游戏如何运作 (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:

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

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

C#WinForm中的太空侵略者相关推荐

  1. c#小游戏_C#小游戏—钢铁侠VS太空侵略者

    身为漫威迷,最近又把<钢铁侠>和<复仇者联盟>系列又重温了一遍,真的是印证了那句话:"读书百遍,其意自现".看电影一个道理,每看一遍,都有不懂的感受~ 不知 ...

  2. C#小游戏—钢铁侠VS太空侵略者

    身为漫威迷,最近又把<钢铁侠>和<复仇者联盟>系列又重温了一遍,真的是印证了那句话:"读书百遍,其意自现".看电影一个道理,每看一遍,都有不懂的感受~ 不知 ...

  3. 在winform中从外部拖动节点到树形结构(treeview和listview相互拖动)(一)

    最近一个项目要用到从listview向treeview拖动item,达到从外部拖动图标成为树形结构的一部分,通过查阅资料总结了一些实现方式,分享给大家.这是winform中的例子. 在进行拖放操作之前 ...

  4. C# winform中MouseDoubleClick与DoubleClick的区别

    C# winform中MouseDoubleClick与DoubleClick的区别是 MouseDoubleClick:只能用鼠标双击 DoubleClick:可以按键盘的回车键

  5. C#在WinForm中实现清空指定类型控件的内容

    实现在Winform中递归控件来清空指定类型控件的内容(因为在Winform中,各个控件是有层次关系的,不能简单地依靠遍历this.controls) private void ClearConten ...

  6. winform中构造函数与Form_Load

    不都是用来初始化form中的组件么? public Form()所谓的构造函数. Form_Load所谓的窗体加载函数 完全两码事! WinForm 中的 Form_Load函数和他的构造函数 pub ...

  7. .NET WinForm中给DataGridView自定义ToolTip并设置ToolTip的样式

    .NET WinForm中的DataGridView为程序开发提供了诸多的便利,我们不需要做许多额外的工作就可以获得一些基础功能,例如点击列标题排序.行选择功能.改变列宽和行宽,以及单元格内容的自动T ...

  8. 怎样正确处理WinForm中Listview的ItemCheck事件

    我很少写具体的对象应用心得,这次尝试一下. WinForm中Listview的ItemCheck事件,例子如下:   private void lvwTables_ItemCheck(object s ...

  9. 把控制台程序嵌入到 WinForm 中执行

    我们经常有一些用控制台实现的简单应用,这种应用一般都是一步一步"向导"式执行,在每一步上收集用户的输入,最后得到程序执行的结果.但有些用户可能不喜欢用键盘操作的命令行界面,还是愿意 ...

  10. WinForm中的MVC模式--MVP模式

    本文主要介绍MVC模式在WINFORM中的实现,其实砖家们都称它为MVP模式,小弟E文不太好,真的是记不住那个P怎么拼写的.. MVC模式主要解决的问题就是将表示层和业务层进行分离,在以往做WINFO ...

最新文章

  1. 2021年大数据Flink(二十七):Flink 容错机制 Checkpoint
  2. Cloud Control 13c 13.3安装(二) agent 部署
  3. MySQL—05—MySQL如何处理SQL语句;MySQL数据库存储引擎介绍;
  4. python监听udp端口_python检测远程udp端口是否打开
  5. Boost:双图bimap与mi_hashed indices索引的测试程序
  6. 作者:纪珍(1982-),女,中国科学院国家空间科学中心副研究员
  7. 云计算实战系列五(Linux文件权限II)
  8. webpack 3 零基础入门教程 #6 - 使用 loader 处理 CSS 和 Sass
  9. javascript函数执行前期变量环境初始化过程
  10. uva_816 Abbott's Revenge(BFS求解最短路、结点状态由坐标和方向表示)
  11. 数据库的开窗函数学习
  12. 153.寻找旋转排序数组中的最小值
  13. win10内存占用率过高怎么办?Win10电脑内存占用率很高的原因和解决方法
  14. java调用shiny_使用Shiny fileInput仅获取路径
  15. 李沐动手学深度学习V2-attention注意力机制
  16. cisco wlc 5520 替换 5508
  17. WSDM'23 | 工业界搜推广nlp论文整理
  18. 信号(signal,kill,raise)
  19. Portainer添加远程Docker(Docker API)
  20. Inventor 2020 安装教程

热门文章

  1. 设置swiper中的高度
  2. 什么是工业物联网网关?
  3. XiaomiRouter自学之路(02-软硬件环境搭建)
  4. C++整数快速读写模板(快速读入+快速写)详解
  5. Error:Apostrophe not preceded by \ (
  6. smbrun - smbd和外部程序间的接口程序。
  7. .net性能最高的EF分页写法
  8. 文字转语音软件免费的哪个最好用:快试试最像人声的微软语音合成助手吧,本地版微软语音合成工具下载
  9. 周杰 清华大学计算机学院,清华大学自动化系主任周杰教授访问我院并做学术报告...
  10. Photoshop精讲精练笔记