如图所示效果:
最近学生们学习了2个月的C#和WINFORM,没有学习数据库。故设计了这样的一个小游戏,基本覆盖了C#语言常用的语法,已经面向对象的基本思想。本实训课程分为8个阶段来实践,效果非常好。故录制成视频分为8个阶段,每个阶段15分钟左右。
此游戏开发为在课堂上即兴编写,即兴录制。没有事先准备,故有些地方不是很完美,请见谅!不过,大家可以学习丁老师在完成软件开发中的需求的时候,思考的过程和完整的思路,以及遇到错误如果去调试和解决的方法,这个大家会有收获。
下载地址:http://www.kuaipan.com.cn/file/id_5393847563583517.htm
主要实现思路为:用GDI+在窗体上绘制图形,在主窗体加入了一个Time控件来做游戏循环,不断的绘图。使用的DoubleBuffer技术,屏幕不闪烁。效果还是可以的,但是在面向对象设计这块设计得有点罗嗦了,没有设计好,需要进一步完善。
主窗体代码:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 10 namespace CrazyBird_GDI 11 { 12 /// <summary> 13 /// 重庆海天软件工程学院 微软7班 WINFORM实训项目----疯狂的小鸟 14 /// 微软讲师:丁浩 Email:dinghao80@qq.com QQ:37112555 15 /// 项目需求:射击游戏 16 /// 实现步骤:1.采用GDI+技术,双缓冲实训精灵的绘制 17 /// 2.绘制背景 18 /// 3.绘制小鸟 19 /// 4.绘制小猪 20 /// 5.绘制子弹 21 /// </summary> 22 public partial class Form1 : Form 23 { 24 public Form1() 25 { 26 InitializeComponent(); 27 } 28 Bitmap bitmap; 29 Pig pig; 30 //第七步4 31 List<Bullet> bullets = new List<Bullet>(); 32 BirdOP birdop; 33 //第十步6: 当子弹碰到小鸟的开关 34 bool isTouched = false; 35 private void timer1_Tick(object sender, EventArgs e) 36 { 37 //在time控件中循环绘制,约33帧的FPS 38 //第一步:导入所需资源 39 //第二步:搭建双缓冲模型,将资源不断的绘制在虚拟画布上面,这个技术可以消除屏幕闪烁 40 //最后将虚拟画布bitmap的内容复制在窗体绘图区 41 42 using (Graphics dc=Graphics.FromImage(bitmap)) 43 { 44 //第三步:导入背景图片资源 45 BackGround bg = new BackGround(this.Width, this.Height); 46 bg.Draw(dc); 47 //固定窗体不能拖动 48 49 //**-------//第一阶段完成。思考:BackGround设计成了一个类,为什么?好处在哪儿?------------------------------------------------------------------------------------------- 50 //第四步:绘制小鸟 需求一共64只小鸟,成8*8排列 设计小鸟类 51 52 //第九步6:绘制小鸟 53 birdop.Draw(dc);//之前要装载小鸟 54 55 56 57 //**------//第二阶段完成。----------------------------------------------------------------------------------------------- 58 //第五步:绘制小猪 59 60 pig.Width = 70; 61 pig.Height = 60; 62 63 pig.Draw(this,dc); 64 65 //**------ //第三阶段完成---------------------------------------------------------------------------------------------- 66 //第六步:移动小猪 67 //触发键盘事件:注意思考,事件源是什么?----窗体 68 69 //第七步6:绘制子弹 70 foreach (Bullet bullet in bullets) 71 { 72 //第八步2:让speed值增加 ,现在找个地方赋予speed初始值 73 // bullet.Speed+=10; //没有起到作用 74 bullet.X -= bullet.Speed; 75 bullet.Draw(dc); 76 } 77 //***----------第六阶段完成 (让子弹飞)------------------下一阶段:子弹碰撞小鸟,子弹和小鸟移除集合------------------------------------------------------------------- 78 //思考:子弹碰撞小鸟,只需要在time控件中从小鸟集合中绘制,子弹从子弹集合里面绘制。碰撞后,从各种集合移除小鸟或子弹即可。 79 80 //第十步1:判断子弹和小鸟相撞,实际上就是2个矩形相交。追加子弹的方法。 81 82 //第十步5: 83 for (int i = 0; i < bullets.Count; i++) //遍历子弹,注意这个时候不能用foreach ,为什么? 84 { 85 for (int j = 0; j < BirdOP.birds.Count; j++)//遍历小鸟 86 { 87 if (bullets[i].Intersect(BirdOP.birds[j])) //每一个子弹去判断所有的小鸟碰撞 88 { 89 BirdOP.birds.RemoveAt(j);//移除小鸟 测试成功 但是同时要移除子弹 90 isTouched = true; 91 } 92 } 93 //移除子弹 94 if (isTouched) 95 { 96 bullets.RemoveAt(i); 97 isTouched = false; 98 } 99 //测试成功 100 //**-----------------第八阶段完成 到这儿就结束了 思考加入声音,由于机房没有音响,不好测试。---------------------------------------------------------------------------------------- 101 //我们发现在构造Bird BackGround Pig等到类的时候,发现都有共同的属性,这个时候可以用继承来实现,需要进一步改进,还要有更好的扩展性,大家在学习面向对象的时候 102 //要积极思考面向对象的三大特性,封装 继承 多态 接口 抽象类 结构 枚举等等C#的特性 103 //实训制作人:丁浩老师 QQ:37112555 这个视频会放在www.ha2ha.com中下载,欢迎大家学习,谢谢! 重庆海天软件工程学院 104 105 } 106 107 } 108 //第二步:复制绘制在窗体绘图区 109 using (Graphics g=this.CreateGraphics()) 110 { 111 g.DrawImage(bitmap, 0, 0); 112 } 113 } 114 115 private void Form1_Load(object sender, EventArgs e) 116 { 117 //第二步:创建和窗体一样大小的画布 118 bitmap = new Bitmap(this.Width, this.Height); 119 //初始化小猪 120 pig = new Pig(); 121 pig.Step = 10; 122 //第九步5:初始化小鸟 123 //bird = new Bird(); 124 //bird.LoadBird(); 125 //做到这个地方,我们发现在bird设计中出现了问题。需要重新设计,没有思考全面。 126 birdop = new BirdOP(); 127 //第九步5:初始化小鸟 128 birdop.LoadBirds(); 129 } 130 131 //第六步 触发键盘事件移动小猪 132 private void Form1_KeyUp(object sender, KeyEventArgs e) 133 { 134 switch (e.KeyCode) 135 { 136 case Keys.Up: 137 pig.Move(Direct.UP); 138 break; 139 case Keys.Down: 140 pig.Move(Direct.DOWN); 141 break; 142 case Keys.Space: 143 //第七步5:装载子弹在集合中 144 bullets.Add(pig.FireABullet()); 145 break; 146 147 } 148 } 149 //**-----------第四阶段完成-------------------------------------------- 150 } 151 }
背景类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 7 namespace CrazyBird_GDI 8 { 9 class BackGround 10 { 11 public BackGround(int width,int height) 12 { 13 Width = width; 14 Height = height; 15 } 16 int width; 17 18 public int Width 19 { 20 get { return width; } 21 set { width = value; } 22 } 23 int height; 24 25 public int Height 26 { 27 get { return height; } 28 set { height = value; } 29 } 30 31 //绘制一个背景图片 32 public void Draw(Graphics g) 33 { 34 Rectangle rect=new Rectangle(0,0,Width,Height); 35 g.DrawImage(MyRes.background, rect); 36 } 37 } 38 }
小鸟类:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 7 namespace CrazyBird_GDI 8 { 9 public class Bird 10 { 11 int x;//x坐标 12 13 public int X 14 { 15 get { return x; } 16 set { x = value; } 17 } 18 int y;//y坐标 19 20 public int Y 21 { 22 get { return y; } 23 set { y = value; } 24 } 25 int width; 26 27 public int Width 28 { 29 get { return width; } 30 set { width = value; } 31 } 32 int height; 33 34 public int Height 35 { 36 get { return height; } 37 set { height = value; } 38 } 39 40 //小鸟的矩形绘图区 第十步 3 41 private Rectangle rectBird; 42 43 public Rectangle RectBird 44 { 45 get { return rectBird; } 46 set { rectBird = value; } 47 } 48 } 49 }
小鸟操作类:
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 7 namespace CrazyBird_GDI 8 { 9 /// <summary> 10 /// 第九步6:重新设计小鸟类 11 /// 增加了一个小鸟操作类 12 /// </summary> 13 public class BirdOP 14 { 15 //第九步2:定义一个集合 16 public static List<Bird> birds = new List<Bird>();//为什么是静态的呢?如 17 18 果不是静态的,每次new一个小鸟,都会有属于自己的集合,显然不对。所以用静态的 19 20 ,所有的小鸟对象都对应这一个集合 21 22 //绘制8*8小鸟阵 23 24 //第九步3:装入64只小鸟到集合中 25 public void LoadBirds() 26 { 27 for (int i = 0; i < 64; i++) 28 { 29 30 Bird bird = new Bird(); 31 bird.X = i % 8 * 60 + 24;//45是小鸟和小鸟之间的间隔,24是left 32 bird.Y = i / 8 * 60 + 24; 33 bird.Width = 50; 34 bird.Height = 50; 35 birds.Add(bird); //添加了64个小鸟到集合中 36 } 37 38 39 } 40 //第九步1:要改良Draw方法,从集合中不断取出来绘制 41 public void Draw(Graphics g) 42 { 43 //第九步4:从集合中取出小鸟绘制在画布上面 44 foreach (Bird bird in birds) 45 { 46 Rectangle rect = new Rectangle(bird.X, bird.Y, bird.Width, 47 48 bird.Height); 49 bird.RectBird = rect; 50 g.DrawImage(MyRes.bird, bird.RectBird); 51 } 52 } 53 54 //**------第七阶段完成 下一阶段 碰撞 从集合中移 55 56 除---------------------------------------------------------- 57 58 } 59 }
子弹类:
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 7 namespace CrazyBird_GDI 8 { 9 /// <summary> 10 /// 第七步1:构建子弹类,思考:按一下回车键,就要发射一个子弹。 11 /// 子弹同样是绘图 12 /// </summary> 13 class Bullet 14 { 15 int x; 16 17 public int X 18 { 19 get { return x; } 20 set { x = value; } 21 } 22 int y; 23 24 public int Y 25 { 26 get { return y; } 27 set { y = value; } 28 } 29 int width; 30 31 public int Width 32 { 33 get { return width; } 34 set { width = value; } 35 } 36 int height; 37 38 public int Height 39 { 40 get { return height; } 41 set { height = value; } 42 } 43 44 int speed;//子弹的速度,实际上就是子弹的x坐标不断的减去speed,图形在绘制的过程中向左移动 45 46 public int Speed 47 { 48 get { return speed; } 49 set { speed = value; } 50 } 51 Rectangle rect; 52 //绘制子弹 53 public void Draw(Graphics g) 54 { 55 56 rect = new Rectangle(X,Y,Width,Height); 57 g.DrawImage(MyRes.Bullet, rect); 58 59 } 60 //思考发射的过程,发射子弹应该属于小猪的事情 61 62 //第十步2: 63 public bool Intersect(Bird bird) 64 { 65 //第十步4:测试成功,追加了小鸟的矩形区域 66 return rect.IntersectsWith(bird.RectBird); //相交小鸟的矩形 67 68 } 69 70 } 71 }
小猪类:
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 using System.Windows.Forms; 7 8 namespace CrazyBird_GDI 9 { 10 class Pig 11 { 12 int x; 13 14 public int X 15 { 16 get { return x; } 17 set { x = value; } 18 } 19 int y; 20 21 public int Y 22 { 23 get { return y; } 24 set { y = value; } 25 } 26 int width; 27 28 public int Width 29 { 30 get { return width; } 31 set { width = value; } 32 } 33 int height; 34 35 public int Height 36 { 37 get { return height; } 38 set { height = value; } 39 } 40 int step;//小猪上下移动的值 41 42 public int Step 43 { 44 get { return step; } 45 set { step = value; } 46 } 47 48 //思考:我们发现小鸟和小猪背景之间有很多共同的属性,可以用什么来解决?----继承 49 50 public void Draw(Form form,Graphics g) 51 { 52 X = form.Width - 100; 53 Y = (form.Height - Height) / 2-Step; 54 Rectangle rect = new Rectangle(X,Y,Width,Height); 55 g.DrawImage(MyRes.Pig, rect); 56 } 57 58 public void Move(Direct direct) 59 { 60 switch (direct) 61 { 62 case Direct.UP: 63 Step += 10;//很有可能Step的值没有产生变化 64 65 break; 66 case Direct.DOWN: 67 Step -= 10; 68 break; 69 default: 70 break; 71 } 72 } 73 74 // 第七步2:发射子弹 思考:这个方法写出来后,发现有问题。参数g在time控件中才能起到作用。而Fire方法的调用在 case Keys.Space: 这个地方调用,故不合理 75 //重新构建 76 public void Fire(List<Bullet> bullets,Graphics g) 77 { 78 foreach (Bullet item in bullets) 79 { 80 item.Draw(g); 81 } 82 83 } 84 85 //第七步3:发射子弹 86 public Bullet FireABullet() 87 { 88 //第七步7:改良数据 89 Bullet b = new Bullet(); 90 91 92 b.Width = 50; 93 b.Height = 15; 94 //第八步3: 95 b.Speed = 10; 96 97 //第八步1:减去一个speed差值。 98 b.X = X - b.Width-10-b.Speed; //离小猪远一点距离 10个像素 99 b.Y = Y+b.Height/2; 100 return b; 101 } 102 //***----------------------第五阶段完成(发射子弹 ) 103 //第八步开始:让子弹动起来,如何让子弹动起来呢?改变子弹的x坐标,如何改变呢? 104 105 } 106 public enum Direct 107 { 108 UP,DOWN 109 } 110 }