资源描述
面向对象程序设计实训(吃金豆游戏设计与实现)
常熟理工学院计算机学院
在Visual Studio 2005环境下用C#编写GUI代码达到如下功能:
(1) 游戏基本功能:
游戏屏幕是一个 NxN 的网格,其中某些格子是固定的墙壁,其他网格是可以通行的,开始的时候放满了金豆。游戏开始后,玩家通过键盘的方向键控制游戏主角PACMAN移动,经过的地方,金豆被吃掉,同时加分。游戏屏幕上还有一个怪物,它会随机地移动。PACMAN如果碰到怪物,游戏失败。如果吃掉了全部金豆,则游戏成功。
(2) 游戏场景(即墙壁和金豆的位置,PACMAN与怪物的初始位置等信息)是从文件 scene.txt 中读到的。格式如下:
pr,pc,mr,mc //(pr,pc)为Pacman初始行列,(mr,mc)为Monster初始行列
11111111 //第一行,N列,其中1表示墙;0表示金豆
10010.. //第2-N行
....
(3) 附加功能,完成以上内容的同学选做:
① 游戏主菜单
开始游戏、查看成绩排行榜、退出游戏等项目。
② 每次游戏成功结束,要求输入用户名,并记录成绩到文件score.txt。如果已有同名用户,且本次得分更高,则替代之。在显示排行榜时,按得分高低排序。
③ 使用不同的关卡,即使用多个场景文件 scene(0-n).txt,在开始游戏时选择。
④ 加入声音效果,吃到金豆时,Win/Lost时显示不同的声音。
3、基础部分训练内容
(1) C#面向对象程序设计,Visual Stuio 2005开发环境;
(2) C# GUI编程;
(3)游戏及动画实现;
(4)游戏相关功能(键盘处理,图形移动,随机数,数学函数等)。
4、指导教师:宋东兴、殷旭东
1.代理和事件
一、 代理(delegate)
1、 代理概念
是一种将方法作为对象封装起来的引用数据类型,一个代理变量可以指向一个方法。
(1)定义一个名为OpHandler的代理类型。
delegate double OpHandler(double x,double y);
(2)创建一个代理变量h1;
class Calc{
public static double Add(double x,double y){return x+y;}
public double Sub(double x,double y){return x-y;}
}
OpHandler h1 = Calc.Add; //h1指向Add方法
(3)通过代理变量h1调用它指向的方法
double result;
result = h1(3,4); //调用Add方法
h1 = new Calc().Sub;
result = h1(3,4); //调用Sub方法
2、 代理作为方法的参数
class MathOp{
private double left;
private double right;
public MathOp(double left,double right){
代理作为参数
this.left = left; this.right = right;
}
public double GetResult(OpHandler op){
return op(left,right);
}
}
class App{
public static void Main(){
MathOp m1 = new MathOp(3,4);
double result = m1.GetResult(Calc.Add);
Console.WtriteLine(“result = {0}”,result);
}
}
3、 代理变量作为类的一个数据成员
class MathOp{
public OpHandler op; //op是代理变量
private double first;
private double second;
public MathOp(double first,double second){
this.first = first; this.second = second; op = null;
}
public double Invoke(){
if(op==null) //如果有方法注册委托变量op
throw new Exception();
return op(first,second); //通过委托来调用方法
}
}
class App{
public static void Main(){
MathOp m1 = new MathOp(3,4);
m1.op = Calc.Add;
double result = m1.Invoke();
Console.WtriteLine(“result = {0}”,result);
}
}
4、 多播代理
在 C# 中,代理是“多播”的,这表示它可同时指向一个以上的方法。多播代理将维护一个方法列表。当调用该代理时,将会按FIFO顺序调用列表中的所有方法。
public delegate void GreetingDelegate(string name);
class GreetingManager{
public GreetingDelegate say;
public void GreetPeople(string name){
if(say!=null)
say(name);
}
}
class App{
public static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name); //英文问候
}
public static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name); //中文问候
}
public static void Main(){
GreetingManager gm = new GreetingManager ();
gm.say = EnglishGreeting;
gm.say += ChineseGreeting; //用+=合并两个代理
gm.GreetPeople(“Marry”); //也可以直接gm.say(“Marry”)调用委托
}
}
注:同样用-=可以删除一个代理。
二、 事件(event)
将代理封装起来就成了事件,事件对外是公开的,而它所对应的代理是私有的。事件可以看作是受限的代理。因此事件也可以绑定(注册)一个方法,但不能通过=,只能通过+=进行。事件最常见的用途是用于窗体编程,当发生像点击按钮、移动鼠标等事件时,对应的方法执行,以响应该事件。
例1:
将上例的GreetingDelegate代理封装成MakeGreet的事件,代码如下:
public delegate void GreetingDelegate(string name);
class GreetingManager{
/* 将代理GreetingDelegate封装成事件MakeGreet*/
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name){
if(MakeGreet!=null)
MakeGreet(name); //调用事件
}
}
class App{
public static void EnglishGreeting(string name) {
Console.WriteLine("Morning, " + name); //英文问候
}
public static void ChineseGreeting(string name) {
Console.WriteLine("早上好, " + name); //中文问候
}
public static void Main(){
GreetingManager gm = new GreetingManager ();
gm.MakeGreet = EnglishGreeting; //编译错误,不允许用=注册方法
gm.MakeGreet += ChineseGreeting;
gm.GreetPeople(“Marry”);
/*
不可以直接gm. MakeGreet (“Marry”)调用事件,只能在定义
MakeGreet的类中调用事件。
*/
}
}
例2:
类Button在GUI中代表按钮,在Button类中定义了一个Click事件,它是对代理EventHandler的封装。
(1)EventHandler代理的定义如下:
delegate void EventHandler(object sender,EventArgs e);
(2)Click事件定义如下:
event EventHandler Click;
Button btnOK = new Button(); //实例化一个名btnOk的按钮
btnOK.Click += ClickProcess; //为它的Click事件注册ClickProcess方法。
则当单击btnOK按钮时,ClickProcess方法被调用以响应该单击事件。
2.窗体和按钮
一、窗体(Form类)
1、Form类是所有高级窗口的基类。
2、设置Form属性
如: (Text, Location, Size, Name)
(FormBorderStyle, BackColor, StartPosition)
(MaximunBox, MinimunBox, ControlBox)等。
二、按钮控件(Button类)
1、属性:(Text, Name, Enabled, …)
2、方法: Show(),Hide(),Focus(),Invalidate()…
3、事件: Click
GUI应用程序的例子:
1)生成并显示一个标题为Hello的空白窗体:
class MyForm:Form{
public MyForm(){
this.Text = “Hello”;
}
}
class WinApp{
public static void Main(){
Application.Run(new MyForm());
}
}
2)在窗体中增加一个按钮,按钮标题为OK,修改MyForm类:
class MyForm:Form{
private Button btnOK;
public MyForm(){
btnOK = new Button();
btnOK.Text = “OK”;
this.Text = “Hello”;
this.Controls.Add(btnOK);
}
}
运行程序,单击OK按钮,看看有没有什么发生?
3)为OK按钮注册Click事件,当单击OK时,按钮的背景色变红。
修改MyForm类:
class MyForm:Form{
private Button btnOK;
public MyForm(){
btnOK = new Button();
btnOK.Text = “OK”;
btnOK.Click+= ClickProcess; //注册click事件
this.Text = “Hello”;
this.Controls.Add(btnOK);
}
public void ClickProcess(object sender,EventArgs e){
btnOK.BackColor = Color.Red;
}
}
运行程序,单击OK按钮,看看发生了什么?
3.GDI编程
GDI(Graphics Device Interface)为开发者提供了一组实现与各种设备进行交互的类库,它在开发人员与设备之间起到了一个重要的中介作用。
显示器(设备)
GDI
应用程序
GDI包括三个部分:
ü 二维矢量图形绘制
ü 图像处理
ü 文字显示
一、 Graphics类
该类封装了GDI的绘图表面,Windows窗体中所有的绘图操作都必须通过Graphics类进行。
1、 创建或获取Graphics对象
法一:Graphics g = 控件.CreateGraphics();
法二:通过窗体或控件的Paint事件来获取。
以窗体为例:paint事件在窗体的任何部分需要重绘时发生。
引发paint事件的情况有:
(1) 窗体首次运行时
(2) 覆盖窗体的其他窗体移开时
(3) 窗体本身移动,或大小改变时
Private void Form1_Paint(object sender,PaintEventArgs e){
Graphics g = e.Graphics;
g.DrawLine(…);
…
}
2、 用Graphics类提供的各种方法:
ü 绘图
ü 显示文本
ü 操作和显示图像
DrawLine(…)
DrawEllipse(…)
DrawRectangle(…)
DrawImage(…)
DrawString(…)
FillEllipse(…)
…
二、 创建画笔和画刷
1、 Pen类创建指定颜色和线宽画笔对象
Pen p1 = new Pen(Color.Red); //设置颜色
Pen p2 = new Pen(Color.Black,10); //设置颜色和线宽
Pen p3 = Pens.Blue; //Pens类中预定义的画笔
2、 画刷Brush抽象类
前景色
背景色
SolidBrush、HatchBrush等是Brush类的子类
Brush b1 = new SolidBrush(Color.Red);
Brush b2 = new HachBrush(HatchStyle.Cross,Color.Blue,Color.Yellow);
Brush b3 = Brushes.Red; ////Brushes类中预定义的画刷
x>0
(0,0)
y>0
width
height
(x,y)
三、 坐标系统
(1)画椭圆
g.DrawEllipse(Pen pen,int x,int y,int width,int height);
(2)画扇形
g.DrawArc(Pen pen,int x,int y,int width,int height,int startAngle,int degree);
(3)显示文字
g.DrawString(“Hello”,this.Font,Brushes.Red,20,20);
g.DrawString(“Hello”,new Font(“宋体”,30,FontStyle.Bold),Brushes.Red…);
例一:在窗体中绘制如下的图形
private void Form1_Paint(object sender, PaintEventArgs e){
Graphics g = e.Graphics;
float diameter=50;
g.DrawLine(Pens.Red, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
g.DrawLine(Pens.Blue), 0, this.ClientSize.Height, this.ClientSize.Width, 0);
g.DrawEllipse( Pens.Black,
(this.ClientSize.Width - diameter)/2,
(this.ClientSize.Height - diameter)/2,
diameter,
diameter
);
}
//窗体大小改变时触发Resize事件
private void Form1_Resize(object sender, EventArgs e){
this.Invalidate(); //使窗体或控件无效,并人为引发其Paint事件,进行重绘
this.Update();
}
例二:在面板中绘制如下的图形:
要求:在文本框中输入行和列,单击clear按钮,对应位置的小金豆被清除。
法一:直接在面板中绘图
private void panel1_Paint(object sender, PaintEventArgs e){
Graphics g = e.Graphics;
int r, c;
r = c = 8; //r行,c列
int h, w; //每个方块的高度和宽度
w = panel1.Width / c;
h = panel1.Height / r;
for (int i = 0; i < r; i++){
for (int j = 0; j < c; j++){
g.DrawRectangle(Pens.Blue, j * w, i * h, w, h);
g.FillEllipse( Brushes.Yellow,
j * w + (w - 20) / 2,
i * h + (h - 20) / 2,
20, 20);
}
}
}
private void btnClear_Click(object sender, EventArgs e){
int r = int.Parse(txtRow.Text);
int c = int.Parse(txtCol.Text);
int w = panel1.Width / 8;
int h = panel1.Height / 8;
Graphics g = panel1.CreateGraphics();
Brush b1 = new SolidBrush(panel1.BackColor);
g.FillEllipse(b1, c * w + (w - 20) / 2, r *h +(h - 20) / 2, 20, 20);
}
法二:将一个方块封装成一个Cell对象。
Cell
-r:int //行
-c:int //列
-w:int //宽度
-h:int //高度
+Cell(int r,int c,int w,int h);
+Draw(Graphics g):void
+Clear(Graphics g,Color foreColor):void
class Cell{
private int r,c,w,h;
public Cell(int r, int c, int w, int h){
this.r = r;
this.c = c;
this.w = w;
this.h = h;
}
public void Draw(Graphics g){
int x, y;
x = w * c;
y = h * r;
g.DrawRectangle(Pens.Blue, x, y, w, h);
g.FillEllipse(Brushes.Yellow, x + (w - 20) / 2, y + (h - 20) / 2, 20, 20);
}
public void Clear(Graphics g, Color foreColor){
int x, y;
x = w * c;
y = h * r;
g.FillEllipse(new SolidBrush(foreColor), x, y, w, h);
}
}
在Form1类中如下定义:
private Cell[,] cellArr;
public Form1(){
InitializeComponent();
cellArr = new Cell[8, 8];
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++)
cellArr[i, j] = new Cell(i, j, panel1.Width / 8, panel1.Height / 8);
}
}
private void panel1_Paint(object sender, PaintEventArgs e){
for (int i = 0; i < cellArr.GetLength(0); i++){
for (int j = 0; j < cellArr.GetLength(1); j++)
cellArr[i, j].Draw(e.Graphics);
}
}
private void btnClear_Click(object sender, EventArgs e){
int r, c;
r = int.Parse(txtRow.Text);
c = int.Parse(txtCol.Text);
cellArr[r, c].Clear(panel1.CreateGraphics(), panel1.BackColor);
}
例三:设计一个pacman,要求:
(1) 可以通过按钮控制它的上下左右运动。
(2)
Cell
-r:int //行
-c:int //列
-w:int //宽度
-h:int //高度
+Cell(int r,int c,int w,int h);
+Draw(Graphics g):void
+Clear(Graphics g,Color foreColor):void
+abstract DrawImage(Graphics g,int x,int y):void
Pacman
-dir:Directoin //Direction枚举四种方向
+Pacman(int r,int c,int w,int h,Direction dir);
+Draw(Graphics g):void
+Move():void
+override DrawImage(Graphics g,int x,int y):void
代码参考:
enum Direction{Up,Down,Left,Right};
class Pacman:Cell{
protected Direction dir;
public Pacman(int r,int c,int w,int h,Direction dir):base(r,c,w,h){
this.dir = dir;
}
public override void DrawImage(Graphics g, int x, int y){
g.FillEllipse(Brushes.Black, x + (w - 20) / 2, y + (h - 20) / 2, 20, 20);
}
public void Move(){
switch (dir){
case Direction.Up: r--; break;
case Direction.Down: r++; break;
case Direction.Left: c--; break;
case Direction.Right: c++; break;
}
}
public Direction Dir{
get {
return dir;
}
set {
dir = value;
}
}
}
public partial class Form1 : Form{
private Pacman myPacman;
public Form1(){
InitializeComponent();
myPacman = new Pacman( 0,
0,
panel1.Width / 16,
panel1.Height / 8,
Direction.Right
);
}
private void panel1_Paint(object sender, PaintEventArgs e){
myPacman.Draw(e.Graphics);
}
private void btnUp_Click(object sender, EventArgs e){
myPacman.Dir = Direction.Up;
myPacman.Move();
panel1.Invalidate();
panel1.Update();
}
private void btnDown_Click(object sender, EventArgs e){
myPacman.Dir = Direction.Down;
myPacman.Move();
panel1.Invalidate();
panel1.Update();
}
private void btnLeft_Click(object sender, EventArgs e){
myPacman.Dir = Direction.Left;
myPacman.Move();
panel1.Invalidate();
panel1.Update();
}
private void btnRight_Click(object sender, EventArgs e){
myPacman.Dir = Direction.Right;
myPacman.Move();
panel1.Invalidate();
panel1.Update();
}
}
(2)向上、下、左、右运动时,Pacman的嘴形各不相同。
提示:用画扇形,或画饼图的方法实现
Graphics g = e.Graphic;
g.DrawArc(…); //画扇形
g.FillPie(…); //画饼图
(3)用键盘捕获其上下左右键控制它的运动。
在Form1类中重写父类的抽象方法bool ProcessDialogKey(Keys keyDate);
protected override bool ProcessDialogKey(Keys keyDate){
switch (keydate){
case Keys.Up:
myPcman.dir = Direction.Up;
break;
case Keys.Down:
myPcman.dir = Direction.Down;
break;
case Keys.Left:
myPcman.dir = Direction.Left;
break;
case Keys.Right:
myPcman.dir = Direction.Right;
break;
}
return base.ProcessDialogKey(keydate);
}
4.定时器控件(Timer)
Timer可以按照指定时间间隔来触发事件,可利用它来执行周期性的操作或指定时间长度操作(比如动画制作、延时等)。
ü 常用属性:
(1) Enabled:指定定时器是否运行(即是否可触发事件)
(2) Interval:指定时间间隔(以毫秒为单位)
ü 常用方法:
(1) Start();
(2) Stop();
ü 事件
Tick //时间间隔到达指定时间且时钟处于运行状态时触发。
对应代理类型为EventHandler。
程序例:编程设计一个在窗体内随机滚动的小球。
1、 设计一个球类:
class Ball{
const int HEIGHT = 20,WIDTH=20;
private int xPos;
private int yPos;
private int dx = 3;
private int dy = 3;
public Ball(){xPos = yPos = 0;}
public Ball(int x,int y){xPos = x,yPos = y;}
public void Draw(Graphics g){…}
public void Move(Size room){…}
}
//小球的Draw方法
public void Draw(Graphics g){
Brush b = Brushes.Red;
g.FillEllipse(b,xPos,yPos,WIDTH,HEIGHT);
}
//小球的Move方法
public void Move(Size room){
Int maxX = room.Width-WIDTH;
Int maxY = room.Height-HEIGHT;
xPos+=dx;
yPos+=dy;
if(xPos<0 || xPos>maxX)
dx = -dx;
展开阅读全文