面向对象程序设计实训(吃金豆游戏设计与实现)

更新时间:2024-01-04 02:43:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

面向对象程序设计实训(吃金豆游戏设计与实现)

常熟理工学院计算机学院

在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(\英文问候 }

public static void ChineseGreeting(string name) {

Console.WriteLine(\早上好, \中文问候 }

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(\英文问候 }

public static void ChineseGreeting(string name) {

Console.WriteLine(\早上好, \中文问候 }

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)

width (x,y) height y>0

(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;

if(yPos<0 || yPos>maxY) dy = -dy; }

2、 窗体内中增加球和定时器对象

Class Form1:Form{

Private Ball myBall; Private Timer timer; Public Form1(){

myBall = new Ball(); timer = new Timer(); timer.Enabled = true; timer.Interval = 100; timer.Tick+=TimerTick;

}

//Form1的Paintg事件处理方法

Private void Form1_Paint(object sender,PaintEventArgs e){

myBall.Draw(e.Graphics); }

//定时器的Tick事件处理方法

Private void TimerTick(object sender,EventArgs e){ myBall.Move(this.ClientSize); this.Invalidate(); this.Update(); }

}

5、吃金豆游戏的设计 一、游戏的场景:

Pacman Wall Bean

Monster

分析:

一个场景分成M 行N列的小Cell,每个Cell有行,列,宽度,高度(r,c,w,h)等属性,且有Draw(),Clear()等方法。

Pacman,Monster,Bean,Wall又可看作是一个特殊的Cell。Pacman和Monster是可运动的,而Bean和Wall是静止的,它们都能用Draw()方法绘制自己,但绘出来的图像也各不相同。

二、游戏角色 1、类Pacman ? 属性:

(1)r,c,w,h:int //(行,列,宽度,高度,继承自Cell) (2)dir:Direction //运动方向 (3)running:bool //是否运动状态 ? 方法:

(1)Draw(Graphics g):void; //继承自Cell

(2)DrawImage(Graphics g,int x,int y):void //自绘方法,重写Cell的对应方法。 (3)Move():void //根据方向尝试向下一位置运动 (4)ChangeDirection(Direction newDir):void //改变方向 2、类Monster ? 属性:

r,c,w,h:int //(行,列,宽度,高度,继承自Cell) ? 方法:

(1)Draw(Graphics g):void; //继承自Cell

(2)DrawImage(Graphics g,int x,int y):void //自绘方法,重写Cell的对应方法。 (3)Move():void //尝试向下一位置运动

(4)Eat(Pacman myPacman):bool; //是否吃到了Pacman

3、 抽象类Block

一个Block是一个Cell,同是它定义自身是否可达IsReachable(),是否可吃IsEatable()的抽象方法。

4、类Bean,Wall,White 分别代表一个小金豆;代表一块墙;代表一个空白块(豆已吃),它们都是一个特殊的Block,Block定义的IsReachable()和IsEatable()两个抽象方法由它的子类们去实现。

四、角色类关系图

Cell -r,c,w,h:int +draw(Graphics g):void +abstract drawImage(Graphics g,int x,int y):void Pacman Monster Block +abstract IsReachable():bool +abstract IsEatable():bool

Bean Wall

说明:Pacman的Move()方法的算法思路 If(isRunning){

取下一个Block ->nextBlock;

If(nextBlock.IsReachable()&&nextBlock.IsEatable()){ nextBlock的r,c ->当前r,c; nextBlock置为White; 加分;

金豆数减一

}else if(nextBlock.IsReaable()){ nextBlock的r,c ->当前r,c;

}else{

White isRunning = false; }

}

五、系统总类图: GameForm -myScene:Scene -gameOver:bool -score:int +GameForm() +Init():void +Play():void 1 Scene -row,col:int -map:Block[,] -myPacman:Pacman * -myMonster:Monster -beanNum:int -score:int +Scene() +Load(StreamReader sr):void +Draw(Graphics g):void

本文来源:https://www.bwwdw.com/article/v3wx.html

Top