当前位置:文档之家› 《java游戏大作业设计》

《java游戏大作业设计》

《java游戏大作业设计》
《java游戏大作业设计》

大作业报告

课题:吃豆人游戏的设计与实现

姓名:黄子强

学号:201517030207

同组姓名:刘照、杜威

专业班级:网工15102

指导教师:何青

设计时间:2018.6.18

目录

一、课程设计目的 (1)

二、需求分析 (1)

三、总体设计 (2)

3.1类的设计 (2)

3.2事件的设计 (3)

四、详细设计 (4)

4.1 PacWorld类 (4)

4.2 Animal类 (7)

4.3 PacMan类 (9)

4.4 Ghost类 (11)

4.5 Blank类 (14)

4.6 Position类 (14)

4.7 Score类 (15)

4.8 Lives类 (16)

4.9 Object类 (17)

4.10 Coin类&Power_Pellet类 (17)

五、游戏测试 (17)

5.1 游戏界面展示 (17)

5.2 游戏效果测试 (19)

总结 (20)

一、课程设计目的

复习、领会、巩固和运用课堂上所学的JAVA游戏编程方面的技术知识,让学生掌握游戏设计的基本原理和游戏编程的基本方法,掌握游戏开发的一般步骤和相应文档的编制,能够设计、调试、运行一个完整的游戏项目;提高开发过程中的团队意识,锻炼学生书面和口头表达能力,为学生提供主动学习、积极探索与大胆创新的机会。

通过本次游戏设计,综合自己在校期间所学的理论知识,使自己熟悉应用系统的开发过程,培养独立思考能力,检验学习效果和动手能力,初步掌握软件工程的系统理论,进一步巩固和加强自身对java基础知识的理解,提高自己的编程水平,从而达到理论与实践相结合的目的。

二、需求分析

吃豆子游戏是一种古老而又有趣的游戏,游戏软件不计其数,网上就有好多关于实现的复杂算法和设计,其难度让一般初学者望而却步。开发的出来的吃豆子游戏是一种内容丰富,画面优美,益智类游的戏,并且不受网络的限制,让人们能够在任何时刻通过体验游戏,放松心情。

设计需求:

①游戏界面设计要能够彰显设计者的基本设计框架及思路;

②游戏界面布局要清晰整洁、层次明确;

③游戏的设计要使玩家能在短时间内掌握游戏规则;

④玩家可以自由操控角色,玩家角色具有吃豆子的功能;

⑤吃到不同的豆子能有不同的效果(分数变化、角色状态的变化等等);

⑥怪物要能够自主运动,采用其特定的算法独立实现;

⑦游戏中怪物与玩家角色之间要有相应的事件(比如:触碰到玩家角色时玩家生命减1);

⑧界面上要能有够显示出当前的得分情况以及当前的生命值;

⑨对于游戏的设计,在游戏结束时能够有相应的提示。

三、总体设计

3.1类的设计

图3-1

在吃豆人游戏的类设计中,首先要确立的是类的关联、划分主次。整个游戏主要由两大部分组成,在这其中PacWorld类既是其中的一大组成部分也游戏一个重要的“舞台”,承载了其他的类方法,在这其中,设计者可以根据具体的游戏需要来加入相应的游戏元素、游戏规则,从而达到丰富游戏的目的。

World类又分为PacWorld和MyWorld两个类,其中PacWorld类是游戏场景元素的一个大集合。作为一个舞台的大容器,它包括游戏区块(左中右三部分)的布局、边界(墙壁)的绘制、背景颜色、空白填充、位置信息显示、分数、大小豆子、怪物以及的位置生成、豆子的生成位置等元素,给游戏塑造出了一个整

体框架。

而另一大组成部分Actor类则作为各种对象类的集合定义游戏中的各种方法。其中包括Animal类、Writing类以及Object类。Animal类中包含了两个重要的元素——PacMan类和Ghost类,分别实现吃豆人和怪物的运动、动画、触发事件等算法逻辑及调用方法。Writing类包含主游戏框架面板上计分板、位置显示、空白填充以及生命计数器的方法类。Object类中则是定义被吃的大小豆子。

所有的类在设计的时候保证了相互之间的联系性,类与类之间存在方法上的调用,对象方法的灵活运用是吃豆人游戏的一大主体。

3.2事件的设计

作为一个游戏,事件的设计必不可少。在设计一个游戏时总是会在特定的游戏场景中抛出一个自定义事件,由相关的界面(可能是多个)去订阅这个事件,当接收到这个事件时,这些界面刷新显示数据,从而解除游戏逻辑层和界面显示层的耦合关系。每一个游戏都有其自定义的游戏触发事件,本次设计的吃豆人游戏也有着其独到的自定义事件。

这次的设计中,吃豆人的事件主要分为两大类,它们分别是针对吃豆子和触碰Ghost的触发事件。

在吃豆子的事件中(如图3-3所示),当玩家操控吃豆人角色吃到小豆子(coin)时,分数加1(更新计分板)并移除小豆子。而当吃到大豆子(Power_Pellet)时,就需要在分数加5并移除大豆子的基础上更新Ghost状态(这里需要改变Ghost的图形,使玩家能在视觉上感受变化)。

图3-2

在触碰Ghost事件中(如图3-4所示),只有当Ghost处于特殊状态时,玩家角色才可以将其吞食消除,否则吃豆人将会失去一条生命并且重置回出生点,并在计分板上使其生命显示减1(同步计数器)。

图3-3

四、详细设计

4.1 PacWorld类

在游戏场景中,除了中央的游戏区域外,左右各有三块区域用于显示游戏信息,而其余部分则用Blank类方法填充空白。

游戏中央的场景中绘制出地图(边界线/墙壁),留出通道,在其中的相应位置放入对应的怪物、大小豆子以及游戏的控制主体——吃豆人,左右放置对应的计分板和计数器,构成整个游戏的基本元素框架。

图4-1

先定义各元素信息:

①左右空白填充:

Blank blankR = new Blank();

Blank blankL = new Blank();

②生命显示:(左下角三个小吃豆人)

Lives life1 = new Lives(1);

Lives life2 = new Lives(2);

Lives life3 = new Lives(3)

③四种颜色的怪物:

Inky inky = new Inky();

Blinky blinky = new Blinky();

Clyde clyde = new Clyde();

Pinky pinky = new Pinky();

Ghost.speed = gSpeed;

④吃豆人:

static PacMan pacman;

pacman = new PacMan(pPoints, pLives);

⑤地图信息:

首先,将整个地图存入到一个二维数组中:

for(int i = 0; i

{

for(int j = 0; j

{

pacworld [i][j] = false;

}

}

接下来根据数组绘制地图DrawPath,将绘制的边界以特殊的信息保存到数组中。

private void DrawPath() (部分代码)

{

min = 200;

max = 442;

for (int x = min; x<= max; x++)

{ pacworld [x][341] = true; }

min = 283;

max = 358;

for (int x = min; x<= max; x++)

{

pacworld [x][221] = true;

pacworld [x][213] = true;

pacworld [x][204] = true;

}

min = 120;

max = 522;

for (int x = min; x<= max; x++)

{

pacworld [x][426] = true;

pacworld [x][87] = true;

}

min = 248;

max = 394;

for (int x = min; x<= max; x++)

{

pacworld [x][257] = true;

pacworld [x][171] = true;

}

……//为简明说明此处省略90%的边界代码

}

图4-2

绘制完成后就向其中加入实体:(计分板、空白填充、生命计数器、吃豆人、怪物)Position position = new Position();

Score scorE = new Score();

addObject(position,320,460);

addObject(pacman,320,257);

addObject(scorE,46,75);

addObject(inky,283,210);

addObject(blinky,320,171);

addObject(pinky,320,217);

addObject(clyde,358,210);

addObject(blankL,75,214);

addObject(blankR,575,214);

addObject(life1,70,350);

addObject(life2,45,350);

addObject(life3,20,350);

豆子的绘制(程序中放置在转角处)需要对通路进行统计,即CoinFill函数的square的计数:

①.在指定的区域放置大豆子,当吃到大豆子时所有的怪物立刻变为可以被

吃掉的状态。

注:在吃掉大豆子后吃豆人的标记run会变为true,意味着此时可以吃掉怪物,这个状态的持续时间定义为5秒。

②.在路口处放置小豆子

③.放置完豆子使其计数器加一

public void CoinFill()

{

coins = 0;

for(int i = 0; i

for(int j = 0; j

if(pacworld [i][j] == true){

int square = 1;

if(pacworld [i+1][j] == true) square++;

else if(pacworld [i-1][j] == true) square++;

if(pacworld [i][j+1] == true) square++;

else if(pacworld [i][j-1] == true) square++;:

if(/*(i == 320 && j == 171) ||*/ (i == 200 && j == 87) || (i == 442 && j == 87) || (i == 420 && j == 341) || (i == 224 && j == 341))

{

addObject(new Power_Pellet(), i, j);

coins++;

}

else if(i >=283 && i <=358 && j >= 172 && j <= 221) coins = coins;

else if(square > 2)

{

addObject(new Coin(),i,j);

coins++;

}

}

}

}

c = coins;

}

4.2 Animal类

在Animal类中定义了用于子类继承的抽象方法,如:方向的移动、方向移动的提前量判断等,在该类中还设置了角色的场景移动传送方法,用于对游戏场景中部特殊的通道移动的定义。对于Ghost而言,他们的移动的范围要求比吃豆

人更广提高难度,当他们从地图中央两侧的通道进入时,将会从另一侧出现。这种突然出现在身边的Ghost往往能提高玩家的紧张感,提升游戏性。

protected void warp() {

positionX = this.getX();

positionY = this.getY();

if(positionX == 94) setLocation(550,213);

else if(positionX == 556) setLocation(104,213);

}

例如:当Ghost从右侧通道走出游戏场景时将会从左侧的通道重新出现在玩家的面前(如图4-3、图4-4所示)。

图4-3

图4-4

在游戏中,总是会在一个方向上碰到无法继续前进的情况,这个时候就需要判断此时还能往哪个方向进行运动,时刻检查能否向四周运动,并根据这个方向来和键盘输入的方向键进行比较,来执行最终的动作方向。

CheckD:用于检查你可以去往哪个方向

private boolean checkD(char s)

{

int x = 0, y = 0;

switch(s)

{

case 'u': y=-steps;break;

case 'd': y=steps;break;

case 'l': x=-steps;break;

case 'r': x=steps;break;

}

return PacWorld.pacworld[getX()+x][getY()+y];

}

只有当判定可以往目标方向移动时,角色才可以进行移动

protected void move(char direction)

{

int x = 0,y = 0;

if (direction == 'u' && checkD(direction) == true) y=-steps;

if (direction == 'd' && checkD(direction) == true) y=steps;

if (direction == 'l' && checkD(direction) == true) x=-steps;

if (direction == 'r' && checkD(direction) == true) x=steps;

this.setLocation(this.getX() + x, this.getY() + y);

}

4.3 PacMan类

private GreenfootImage Mopen = new GreenfootImage("open.png");

private GreenfootImage Mclosed = new GreenfootImage("closed.png");

定义吃豆人运动的两种状态图:开口和闭口,用于运动时变换显示。

private void movePic() {

if (mCounter >= mChange) {

setImage(Mopen);

if(mCounter == 2*mChange) mCounter = 0;

}

else setImage(Mclosed);

}

在类中,还需要专门设置一个用于标记吃豆人运动时图片的变化显示的量:private char d = 'l';

定义初始运动向左,吃豆人的四个运动方向分别是:上(Up:u),下(Down:d),左(Left:l),右(Right:r)。

如:(键盘点击方向键向上后,改变吃豆人的运动方向向上)

if(Greenfoot.isKeyDown("up")) {

setRotation(90);

d = 'u';

}

图4-5

在获得了键盘输入的方向后,改变吃豆人的运动(位置、方向),这里还会对可能移动的方向先进行判断(判定是否可以朝目标方向移动)。(图4-5)

private void corner(char side){

for(int i = -7; i<=7;i++){

int x = 0,y = 0;

switch (side){

case 'u': y = -1;x = i;break;

case 'd': y = 1;x = i;break;

case 'l': x = -1;y = i;break;

case 'r': x = 1;y = i;break;

}

if(PacWorld.pacworld[getX() + x][getY() + y] == true)

setLocation(getX() + x, getY() + y);

}

}

吃豆人在吃豆子时需要调用eat函数:当吃到小豆子时,移除当前接触到的小豆子并让分数加一;当吃到大豆子时,移除当前接触到的大豆子并让分数加五;当分数高于200且当前游戏中仍存有豆子时,怪物的速度提升(难度提升)。

private void eat(){

if(isTouching(Coin.class)){

removeTouching(Coin.class);

coinsCollected++;

points++;

}

if(isTouching(Power_Pellet.class)){

removeTouching(Power_Pellet.class);

run = true;

coinsCollected++;

points += 5;

}

if(coinsCollected == PacWorld.coins && PacWorld.coins != 0) {

if(points > 200 * Ghost.speed)Ghost.speed++;

Greenfoot.setWorld(new PacWorld(l,points, Ghost.speed));

}

}

在游戏中,吃豆人难免会出现触碰到怪物而死亡的情况,这个时候就需要死亡函数——在出生点重生,生命减一,并执行一次判断:当生命所剩为零时重置游戏。

执行这个死亡函数的前提条件是吃豆人触碰到四个怪物中的任意一个(if语句)。

private void die(){

if(isTouching(Inky.class) || isTouching(Pinky.class) || isTouching(Blinky.class) || isTouching(Clyde.class)){

l--;

setLocation(320,257);

if(l == 0){

points = 0;

Greenfoot.setWorld(new PacWorld(3,0,1));

}

}

}

4.4 Ghost类

在吃豆人游戏中,怪物是一个非常重要的存在,要让一个怪物实现在场景中移动就需要一个用于定义怪物随机移动的函数。

使用random函数随机生成四个方向的移动,当怪物移动到一个方向的尽头时则执行下一个方向的运动判断。

protected void randomWalk() {

int square = 1;

if(PacWorld.pacworld [this.getX()+1][this.getY()] == true) square++;

else if(PacWorld.pacworld [this.getX()-1][this.getY()] == true) square++;

if(PacWorld.pacworld [this.getX()][this.getY()+1] == true) square++;

else if(PacWorld.pacworld [this.getX()][this.getY()-1] == true) square++;

if(square > 2){

do{

int r = (int) (int )(Math.random() * 4);

switch(r){

case 0 : d = 'u';o = 'd';break;

case 1 : d = 'd';o = 'u';break;

case 2 : d = 'l';o = 'r';break;

case 3 : d = 'r';o = 'l';break;

}

bX = this.getX();

bY = this.getY();

move(d);

aX = this.getX();

aY = this.getY();

move(o);

}while(bX == aX && bY == aY);

}

for(int i = 0; i

if(this.getX() == 320 && this.getY() > 171 && this.getY() < 221)this.setLocation(320, 171);

}

protected void run() {

if(run) runCount++;

if(isTouching(PacMan.class) && run == true){

PacMan.points +=20;

setLocation(320,213);

this.start = true;

}

else if(800 < runCount && runCount < 1000){

countR++;

if(countR == 50){

countR = 0;

this.setImage(uPic);

}

else this.setImage(gPic);

}

else if(runCount == 1000){

runCount = 0;

run = false;

}

}

设置怪物由出生点出发(图4-6):

图4-6

protected void leave(){

lCount++;

if(lCount == 100) {

this.order--;

Count = 0;

}

if(this.position == false && this.order != 1 ){

if(this.getY() == 204) this.d = 'd';

else if (this.getY() == 221)this.d = 'u';

move(this.d);

}

else if(this.position == false && this.order == 1){

if(this.getY() < 213) move('d');

else if(this.getY() > 213) move('u');

else if(this.getY() == 213){

if(this.getX() < 320) move('r');

else if(this.getX() > 320) move('l');

else{

this.position = true;

}

}

}

else if(this.position == true){

if(this.getY() > 171)move('u');

else {

this.start = false;

int a = (int) (Math.random() * 2);

if(a == 0)this.d = 'r';

else this.d = 'l';

}

}

}

吃豆人有着其运动的样式,而怪物也同样有着四个方向运动的图案变化(图4-7):

图4-7

protected void changePic(){

if(run == false){

switch(this.d){

case 'u': this.setImage(this.uPic);break;

case 'd': this.setImage(this.dPic);break;

case 'r': this.setImage(this.rPic);break;

case 'l': this.setImage(this.lPic);break;

}

}

else this.setImage(gPic);

}

4.5 Blank类

Blank类用于填充空白部分(填充黑色单元块)

public class Blank extends Writing{

public Blank() {

setImage(new GreenfootImage(".......", 50, Color.BLACK, Color.BLACK));

}

}

4.6 Position类

不论是吃豆人还是怪物都需要动态标记、使用当前的位置,这样就需要一个能够被所有对象调用的Position类,这个类既定义了对象坐标也和PacWorld一起定义了这些对象所能够运动的轨迹。

public class Position extends Writing

{

private int pX, pY;

public void act()

{

pX = PacMan.X;

pY = PacMan.Y;

//int r = getWorld().getBackground().getColorAt(pX, pY).getRed();

//int g = getWorld().getBackground().getColorAt(pX, pY).getGreen();

//int b = getWorld().getBackground().getColorAt(pX, pY).getBlue();

//int a = getWorld().getBackground().getColorAt(pX, pY).getAlpha();

setImage(new GreenfootImage("X: "+pX+ " Y: " + pY, 15, Color.WHITE, Color.BLACK));

//setImage(new GreenfootImage("R: "+r+ " G: " + g+ " B: "+b+" A: "+a, 15, Color.WHITE, Color.BLACK));

}

}

运动轨迹为图中白色区域中的灰色直线(图4-8):

图4-8

同时在游戏过程中,我们也可以通过显示position来看到吃豆人所在的位置(图4-9):

图4-9

4.7 Score类

分数信息显示在左上角,当分数为0时显示为000,个位数显示为00+对应数字,十位数显示为0+对应二位数字,三位数则直接显示数字(图4-10)。

public class Score extends Writing {

public void act() {

String number = "00";

if(PacMan.points <0) PacMan.points = 0;

else if(PacMan.points>99) number = "";

else if(PacMan.points>9) number = "0";

else number = "00";

setImage(new GreenfootImage(number+PacMan.points, 30, Color.WHITE, Color.BLACK));

}

}

图4-10

4.8 Lives类

既然存在玩家角色和怪物,以及触碰的死亡事件,那么就必然有着对应的生命计数器(生命计分面板),用于显示玩家当前剩余的生命值。而这样就需要一个类用于计算玩家的生命信息。

public class Lives extends Writing{

public int N;

public Lives(int n) { N = n; }

public void act() {

if(N -1 == PacMan.l) {

getWorld().removeObject(this);

PacMan.points -= 20;

}

}

}

图4-11

4.9 Object类

Object类继承Actor类中的方法,同样,这其中的方法也传递给了两个子类。两种豆子的子类继承了Object类即Actor类的所有属性及方法。

Coin类和Powe_Pellet类在PacWorld类中与吃豆人共同作用了一个事件,同时Powe_Pellet类对象还将对Ghost产生状态的影响。这两个类在PacWorld类中作为类对象被放置在容器中充实游戏整体,也为与PacMan类和Ghost类的互动打下一个基础。

图4-12

4.10 Coin类&Power_Pellet类

两种豆子在这两个类中被分别定义声明,主要在PacWorld类中被调用。

五、游戏测试

5.1 游戏界面展示

最初运行游戏时,玩家将会进入最初的静止的游戏界面:

图5-1

点击运行即可开始游戏:

图5-2

在游戏中,吃到小豆子时(分数、游戏效果变化)效果图:

图5-3

吃到大豆子效果图:

图5-4

触碰到怪物前后效果图:

相关主题
文本预览
相关文档 最新文档