标签:canvas switch 底部 方法调用 测试 sub 电子 处理 ott
捕鱼游戏这个项目是一个娱乐性的游戏开发,该游戏可以给人们带来娱乐的同时还可以给人感官上的享受,所以很受人们的欢迎。本次游戏的程序设计包含,java swing组件的合理运用,还有图像的变动达到一个动态的动画效果,线程的运用,游戏的异常处理,等方面的知识。培养学生运用所学知识的基础理论、基本知识和基本技能,分析解决实际问题能力的一个重要环节。它与课堂教学环节彼此配合,相辅相成,在某种程度上是课堂学习的继续、深化和检验。它的实践性和综合性是其它教学环节所不能代替的。课程设计能使学生受到必需的综合训练,在不同程度上提高各种能力。
通过课程设计,使学生熟练掌握Java语言课程中所学的理论知识,通过综合Java语言的基本知识来解决实际问题,加强学生分析和解决问题的能力。
游戏启动后,加载游戏,游戏界面成功打开后可以点击鼠标,控制炮台发射的方向,消耗金币发射子弹,子弹碰到鱼之后会变成渔网,根据代码中设计的算法通过鱼的可击落概率和子弹的威力判断鱼是否被击落(当然,这是随机的),如果没有击杀鱼,鱼继续游动,如果击杀了鱼,会触发被击落程序,鱼会抽到并消失,返回这条鱼该回报的金币。当计时器为0时,如果金币数小于100了,就会将玩家的金币数补到100。只要有足够的金币可以发射炮弹,玩家可以不停的发射炮弹。游戏流程如图:
main
方法在类AwtMainComponet
里面,main
方法里设置了窗口参数和鼠标监听器。Constant
:常量类,里面有游戏中运用到的常量。MainSurface
:绘制图层类,图层的绘制就是在该类中实现的。CannonManager
:大炮管理器类,大炮的所有属性以及该有的方法。CatchFishManager
:捕捉管理器类,类中是鱼的捕捉方法。FishManager
:鱼管理器类,类中解析了鱼的配置信息,管理着鱼的动作等信息。GameInitManager
:游戏初始化管理器类,类中有游戏的初始化方法。HeadFish
:领头鱼类,领头鱼是一种虚拟的鱼,其实就是把一群鱼模拟成一个对象。ImageConfig
:图片的配置信息类,类中有图片的细节信息。ImageManager
:图片管理器类,根据解析文件获取图片。Bitmap
:图片信息类。LayoutManager
:布局管理器,在类中有大炮底座类,提高降低大炮按钮,计分板计时板的设计。MusicManager
:音乐管理器,管理游戏的背景音乐。ParticleEffectManager
:粒子管理器,类中管理游戏的粒子效果。PathManager
:鱼路径管理器,类中管理鱼的路径。ScoreManager
:得分管理器,类中管理如何得分。ShoalManager
:鱼群管理器类,类中管理鱼群的方法。SoundManager
:音效管理器,类中管理游戏的音效。Ammo
:子弹类,类中管理子弹的属性。AmmoParticleEffect
:子弹粒子效果,类中写了子弹粒子效果的实现。BackGround
:背景类,类中设计了游戏的背景。FishGold
:显示捕捉到鱼后显示的金币数量。FishInfo
:某一种鱼的细节配置信息类。FishingNet
:鱼网类,类中是渔网的属性和方法。GamingInfo
:游戏进行中需要共同用到的一些变量。Gold
:金币类,类中是金币的属性和方法。GoldParticleEffect
:金币粒子效果,类中是金币的粒子效果属性。HighPoint
:高分显示类,当获取高分时游戏的显示。HundredPoint
:百分显示,当获取百分时游戏的显示。LoadProgress
:加载进度条,类中设计了加载进度条。NetParticleEffect
:渔网粒子效果,类中配置渔网粒子效果。WaterRipple
:水波纹类,水波纹的属性方法。Bottom
:大炮底座类,类中设计大炮的底座。BottomGold
:金币显示组件,显示金币。BottomTime
:时间显示组件,显示时间。ButtonAdapter
:按钮,大炮的按钮属性及方法。Cannon
:定义所有大炮的模拟类,设计了发射大炮的动作。ChangeCannonEffect
:设计了更换大炮时的变换效果。Componet
:组件的父类,有设计组件的坐标的方法。DownCannonButtonListener
:降低大炮质量的按钮逻辑。UpCannonButtonListener
:提升大炮质量的按钮逻辑。Fish
:鱼类,设计鱼的所有属性和方法,如鱼的动作和捕捉方法。Button
:按钮的接口,有是否可用和当按钮被点击的方法。Drawable
:图片的接口,可获取图片的宽高的方法。OnClickListener
:单机事件的接口,单机时的方法。FishRunThread
:鱼游动进程,设计了鱼游动的方法。PicActThread
:控制鱼的动作的进程,设计了播放了鱼所有动作的方法。ShotThread
:射击进程,设计了发射子弹的方法。CircleRectangleIntersect
:圆与矩形碰撞检测类(鱼是矩形,网是圆)。Tool
:获取目标与源之间的角度,判断击落与否。
上述的所有类的共同合作才完成了捕鱼达人游戏的开发。
在GameInitManager
类中,对游戏的初始化进行了管理,首先判断游戏是否已经初始化过,若没有,则进行初始化。然后初始化进度条画面,实际上就是加载完进度条和背景图片,最后完成了游戏启动界面的初始化。其次,在该类中也对其他组件进行了初始化(界面组件,得分管理器,粒子管理器,大炮管理器,鱼管理器,所有音效,大炮),一个个进行加载。关键代码如下:
/* 初始化游戏*/
private void initGame(){
//初始化界面组件
this.initComponents();
//初始化得分管理器
ScoreManager.getScoreManager().init();
//初始化粒子管理器
ParticleEffectManager.getParticleEffectManager();
LoadProgress.getLoadProgress().setProgress(10);
//初始化大炮管理器
CannonManager.getCannonManager().init();
LoadProgress.getLoadProgress().setProgress(20);
//初始化鱼管理器
FishManager.getFishMananger().initFish();
LoadProgress.getLoadProgress().setProgress(40);
……
//初始化音效
initSound();
LoadProgress.getLoadProgress().setProgress(90);
//初始化大炮
CannonManager.getCannonManager().initCannon();
LoadProgress.getLoadProgress().setProgress(100);
}
LoadProgress
的支持,该类对进度条位置、图片、进度条背景和进度值的管理都起到了决定性的作用。ImageConfig
是图片的配置信息类,图片的信息需要调用该类中的ActConfig
内部类来对图片进行初始化赋初属性。ImageManager
类是加载图片最重要的一个类,其中方法createImageConfigByPlist
能根据给定的配置文件,创建相关的配置信息类,传入带路径的文件返回一个ImageManager
对象。方法setScaleInfo
设置了图片的缩放信息。方法scaledSrcBitmap
真正做到了缩放图片,传入图片的配置信息,返回缩放后的图片,使得图片大小适应游戏界面,画面的协调感能强烈,取得图片代码:
this.baseImageCache = getBitmapByAssets(config.getSrcImageFileName() + ".png");
this.baseImageString = config.getSrcImageFileName();
getActConfig
方法解析图片配置信息,该类严格按照顺序解析,不忽略属性顺序问题,所以xml那边的配置的顺序需要做到严格,返回每一张图的细节信息。getImage
方法则是根据图片的配置信息获取图片,根据图片的配置文件和源图返回一个裁出来的图。getImagesByActConfigs
这个方法能返回一组而不是一个给定配置信息的图片。getImagesMapByImageConfig
方法则能根据图片配置对象信息返回一组图片的HashMap
对象。rotateImage
方法实现了图片的旋转。scaleImageByScree
,scaleImageByProportion
和sacleImageByWidthAndHeight
这三个方法会根据需要完成根据屏幕尺寸,比例以及给定尺寸缩放图片。Bitmap
类是图片信息类,类中写了获取图片的方法给接下来的程序设计调用,如获取图片的宽度和高度,缩放图片和复制图片,返回、设置图片的像素颜色。项目中有一个接口Drawable
,其中定义了获取图片旋转的矩阵表示,获取当前动作图片的资源,图片的高度和宽度,绘制的回调方法。该接口可供fish
类,cannon
类,ChangeCannonEffect
等类实现其不同的功能。
这些方法使得游戏里的死的图片仿佛一下子变活了,且相处的那么融洽,不仅加载了图片这么简单,还使游戏给人感官上的体验上了也提升了一个档次。玩家对一款游戏的第一印象本来就是一款游戏的画面感而决定的,所以游戏图片的选择首先要精美,颜色要鲜艳,且不能太僵硬,所以在这个类中有这么大量的方法的分工合作,才完成了游戏画面的设计。
MusicManager
类是音乐管理器类,其中属性有文件流,文件格式,输出设备。playMusicByR
方法可以根据传入文件中对应的ID属性名来播放音效,关键代码如下:
/* 根据R文件中对应的ID属性名来播放音效 * @param resId*/
public void playMusicByR(String resId,boolean isLoop){
try {
File file = new File("bgm"+File.separator+resId);
Thread playThread = new Thread(new PlayThread(file,true));
playThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
PlayThread
是播放音乐线程,实现了取得文件输出流以后转为MP3文件编码的操作,且实现了打开输出设备,读取数据到缓存数据,并写入缓存数据的操作。SoundManager
则是音效管理器,定义了一些播放音效为常量,方便方法的调用:
/* 音效常量定义 */
public static final int SOUND_BGM_FIRE = 1; //开火音效
public static final int SOUND_BGM_NET = SOUND_BGM_FIRE + 1; //张网音效
public static final int SOUND_BGM_CHANGE_CANNON = SOUND_BGM_NET+1; //更换大炮
public static final int SOUND_BGM_GOLD = SOUND_BGM_CHANGE_CANNON+1; //获得金币
public static final int SOUND_BGM_HIGH_POINT = SOUND_BGM_GOLD+1; //获得高分
public static final int SOUND_BGM_HUNDRED_POINT = SOUND_BGM_HIGH_POINT+1; //获得百分
public static final int SOUND_BGM_NO_GOLD = SOUND_BGM_HUNDRED_POINT+1; //没有金币
soundMap = new HashMap<Integer, byte[]>();
lineMap = new HashMap<Integer, SourceDataLine>();
mixer = AudioSystem.getMixer(AudioSystem.getMixerInfo()[0]);
initSoundData(SOUND_BGM_FIRE,"bgm_fire.ogg");
initSoundData(SOUND_BGM_CHANGE_CANNON,"firechange.ogg");
initSoundData(SOUND_BGM_NET,"bgm_net.ogg");
initSoundData(SOUND_BGM_GOLD,"coinanimate.ogg");
initSoundData(SOUND_BGM_HIGH_POINT,"highpoints.ogg");
initSoundData(SOUND_BGM_HUNDRED_POINT,"hundredpoints.mp3");
initSoundData(SOUND_BGM_NO_GOLD,"coinsnone.ogg");
playSound
方法是播放音效的实现类,传入刚刚定义的常量,读取数据到缓存数据,播放对应的音效。
MainSurface
类是图层类,只有设计了图层才使得游戏具有立体感,没有图层的设计是无法完成这款游戏的,定义的常量有:更新图层常量,添加元素常量,删除元素常量,定义了一个图片的图层分布HashMap
,一个修改后的图片的图层分布HashMap
,这里根据操作分了两个图层,分别是添加的元素和删除的元素。定义了一个图层ID,这样就省去了从map
中获取各个图层排序问题,设置了画笔属性,定义了一个屏幕绘制线程的对象,用于控制绘制帧数,周期性的调用onDraw
方法,这个方法是由线程控制的,周期性调用的,它会遍历所有图层,按图层的先后顺序绘制。updatePicLayer
方法是更新图层的方法,这里分为三种操作,分别是更新临时图层中的内容到绘制图层中,删除绘制图层中的元素以及添加绘制图层中的元素,这里加了一个线程锁,保证多线程下操作图层的安全性。注意:无论是向绘图图层中添加还是删除元素,都不是直接操作绘制图层,都是存放在对应的临时图层中,等待绘制方法绘制周期中将变化的内容更新到绘制图层中保证多线程操作情况下的安全性。putDrawablePic
方法将一个可绘制的图放入图层中,传入图层号和可绘制的图。removeDrawablePic
方法讲一个可绘制的图层移除,updateLayerIds
方法更新了图层。在类MainSurface
中定义了内部类JCanvas
为画板类,继承Canvas
,绘制画板。线程OnDrawThread
开始绘制,每次绘制后的休息毫秒数,是根据常量中的绘制帧数决定的。游戏的图层设计如图:
LayoutManager
类是布局管理器类,该类对游戏的布局产生了至关重要的作用,内部属性有调节子弹的按钮,有计分板,有计时板,有当前使用的大炮。addButton
方法实现了在大炮两边添加按钮的功能,在屏幕上点击会发射子弹的原因正是因为有了onClick
这个方法,功能才得以实现。Init
方法初始化布局,这里会初始化大炮底座,提高大炮质量的按钮,降低大炮质量的按钮,计分板以及计时板,比如计分板在提升大炮质量按钮右边屏幕宽度1/30,1/3按钮高度的位置,计时板在降低大炮质量按钮左边边屏幕宽度1/30加组件宽度,1/3按钮高度的位置。大炮、时间和金币的布局如图:
ParticleEffectManager
类是粒子管理器类,属性有金色星星粒子图片,星星粒子图片,星星粒子彩色图。createColorfulParticleImgs
方法在循环中调用getColor
方法随机产生几个颜色的粒子,createColorfulParticleImg
是随机产生一个颜色的粒子方法,类中当然也获取了渔网,子弹,金币粒子的效果实例。GoldParticleEffect
类是金币粒子效果类,playEffect
方法可以根据粒子产生的位置以及偏移量播放一次粒子效果的方法,setEffectMatrix
方法设置了粒子位置。NetParticleEffect
是渔网粒子效果类,playEffect
方法可以根据粒子产生的位置以及偏移量播放一次粒子效果的方法,setEffectMatrix
方法设置了粒子位置。AmmoParticleEffect
是子弹粒子效果类,playEffect
方法可以根据粒子产生的位置以及偏移量播放一次粒子效果的方法,setEffectMatrix
方法设置了粒子位置。HeadFish
,这个类并不是实质的鱼,而是一个点,这个点带领着所有鱼群游动。该类决定了鱼群的X
,Y
坐标,游动方向
,旋转角度
和旋转方向
。关键代码:
public class HeadFish {
private int[] fishOutlinePoint = new int[4]; //鱼的外接矩形,x的最小值,最大值,Y的最小值,最大值
//控制鱼移动的线程
private FishRunThread fishRunThread;
private boolean isNew = true; //是否刚生成的鱼 这个参数决定着进入屏幕时候的路线
private float fish_x; //鱼当前的X坐标
private float fish_y; //鱼当前的Y坐标
private int currentRotate; //鱼当前已旋转的角度
private float lastX; //最后一次旋转后的X增量 这组XY的作用是旋转后若走直线,就以这两个值
private float lastY; //最后一次旋转后的Y增量 递增就可以了
private int rotateDirection; //左转还是右转 这个值的用途在于,鱼在旋转后走直线时,要计算最后一次旋转后的增量,而这个记录了上次是左转还是右转用于计算角度得知直线时的增量
//当前鱼群的鱼,鱼群的鱼都已它为参照,同样这个鱼也在鱼群集合里
private Fish fish;
//鱼群
private ArrayList<Fish> shoal = new ArrayList<Fish>();
//当前创建的领头鱼的起始位置
private int currentFromPoint;
且生成ge
t、set
方法,为后面的方法调用赋值做准备。在类ShoalManager
中有一个生成领头鱼的方法:birthHeadFish
。该方法可以创建一头领头鱼,领头鱼的出现也就使得鱼群得以出现,创建完成之后,将方向设置到类属性currentFromPoint
上,供鱼群使用。
ShoalManager
类实现了对鱼群的生成和管理。其中类属性有三个,可生成的鱼群,鱼的生成概率,是否可生成鱼群。
createShoal
方法可以创建一个鱼群,注意鱼群还包括在屏幕外的鱼群。fishRun
方法实现了鱼群的游动。startFishAct
方法实现了鱼群动作的播放,其中需要创建并启动当前鱼的动作线程。createShoal
方法就是之前提到的根据传入的一个领头鱼生成了鱼群,这里完成了实现。setRandomShoalPositionByHeadFish
方法根据领头鱼的位置设置随机位置,并将随机位置的偏移量赋值给给定鱼群。canRun
方法判断鱼群是否可以游动了,因为鱼群的生成只能发生在屏幕外,如果突然从屏幕里产生鱼群会对游戏的产生差的体验,玩家会觉得鱼的突然出现会不符合常识逻辑。notifyFishIsOutOfScreen
方法通知鱼群管理器,领头鱼已经离开了屏幕。getFromPoint
方法设置了鱼的起始坐标,并将屏幕分成了了4个方位,左上,左下,右上,右下。Stop
方法更新鱼群,将createable
属性设置成false
。PathManager
类是鱼路径生成器的类,有两种行径模式,直走和转弯。
getDefaultPath
决定了鱼群是如何行径的,获取一个默认路径,格式为{{移动方式,大小},...},默认路径只有4个元素,如果行走完还没有到达屏幕边缘,则可以再调用这个方法,所以设置4个元素就足够了,其中如果鱼的最大旋转角度为0,只能走直线。说到底,其实就是让鱼群可以不断地直走转弯,实现游戏的丰富多彩的游戏体验,避免鱼群只会直走的尴尬生硬的体验。Fish
类中定义鱼类所有的属性和方法,定义了鱼的左转和右转的常量,左转为1,右转为2,还有以下4中引用类型属性定义(当前鱼的细节配置信息,当前鱼的所有动作,当前鱼的所有动作,创建当前鱼的动作线程)。FishInfo
类是某一种鱼的细节配置信息类,类中属性定义了鱼的动作速度,最大旋转速度,游动速度,最大的鱼群数,所在的图层,捕捉的概率,价值,也有这些属性所对应的get
、set
方法。这些属性都是必不可少的,他们是游戏的多样性以及可玩性的保障,因为每一种鱼的这些属性都是不同的,比如大鱼的击打难度大,得到的奖励多等等区别。FishManager
类是管理鱼的类,该类是通过给不同的鱼起的不同的名字来进行管理的,会根据名字保存所有鱼的捕获动作配置信息,根据名字缓存的鱼的动作图片,根据名字缓存的鱼的捕获动作的图片,还包括所有鱼的种类。Createable
属性是一个判断是否可以创建新的鱼的属性,每当调用updateFish
方法时,这个值会被设置成false
,updateFish
方法执行完毕后,这个值会改变成true
。
initFish
方法会初始化管理器,会读取fish
文件夹下的FishConfig.plist
文件,来加载鱼的所有配置信息,文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<key>fishActConfig</key>
<string>fish/fish;fish/fish2;fish/fish3;fish/seamaid</string>
<key>fishInfoConfig</key>
<string>fish/FishInfo</string>
</plist>
birthFishByFishName
方法可以根据鱼的名字获取一条鱼的实例,updateFish
就是更新加载的鱼,getAllFishName
方法来获取所有鱼的名字,getFishByName
方法根据鱼的名字和鱼的动作信息实现了设置鱼的动作到管理器鱼动作结构中,成功返回true
,失败返回false
。getFishActByFishName
方法可以获取鱼的游动图片集,getFishCatch- ActsByFishName
方法则是可以获取鱼的被捕获图片集。initFishInfo
方法初始化了鱼的配置信息,如鱼的图层ID,鱼的动作速度,鱼的价值,鱼的捕捉概率等,关键代码如下:
private void initFishInfo(String config){
try{
//如果配置信息没有找到,抛出异常
if(config==null){
throw new Exception("FishManager:读取配置文件出错,没有找到fishInfoConfig信息");
}
//加载鱼的基本信息配置文件
XmlPullParser xml = XmlManager.getXmlParser(config, "UTF-8");
//解析所有的鱼的基本信息
while(GamingInfo.getGamingInfo().isGaming()&&XmlManager.gotoTagByTagName(xml, "key")){
XmlManager.gotoTagByTagName(xml, "string");
String fishName = XmlManager.getValueByCurrentTag(xml);
FishInfo fishInfo = new FishInfo();
//设置最大旋转角度
XmlManager.gotoTagByTagName(xml, "integer"); fishInfo.setMaxRotate(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置移动速度
XmlManager.gotoTagByTagName(xml, "integer"); fishInfo.setFishRunSpeed(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置动作速度
XmlManager.gotoTagByTagName(xml, "integer");
fishInfo.setActSpeed(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置鱼群最大数量
XmlManager.gotoTagByTagName(xml, "integer"); fishInfo.setFishShoalMax(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置鱼的图层ID
XmlManager.gotoTagByTagName(xml, "integer");
fishInfo.setFishInLayer(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置鱼的价值
XmlManager.gotoTagByTagName(xml, "integer");
fishInfo.setWorth(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)));
//设置鱼的捕捉概率
XmlManager.gotoTagByTagName(xml, "integer"); fishInfo.setCatchProbability(Integer.parseInt(XmlManager.getValueByCurrentTag(xml)))
allFishConfig.put(fishName, fishInfo);
}
}catch(Exception e){
e.printStackTrace();
}
}
FishRunThread
类是鱼游动的线程,该线程决定了鱼是否可以游动,确定了当前线程需要控制额度领头鱼,鱼是否处于屏幕外,鱼的绘制速度(这个应与屏幕刷新速度一样)以及鱼的旋转长度。改进程中的run
方法还很好的处理了鱼的旋转模式,鱼在屏幕的不同象限和鱼头的朝向都会使鱼进行不同的旋转路线,关键代码如下:
// 如果路径为旋转模式
if (pathMode[0] == PathManager.PATH_MODE_ROTATE) {
/*这里做了一个处理,就是分析鱼头朝向和所在位置让鱼进行不同的旋转路线*/
// 如果鱼处于第1或第2象限
if (fish.getFish_X() <= GamingInfo.getGamingInfo().getScreenWidth() / 2
&& fish.getFish_Y() <= GamingInfo.getGamingInfo().getScreenHeight() / 2
|| fish.getFish_X() > GamingInfo.getGamingInfo().getScreenWidth() / 2
&& fish.getFish_Y() <= GamingInfo.getGamingInfo().getScreenHeight() / 2) {
// 如果鱼头是朝向x的负坐标方向
if (fish.getCurrentRotate() >= 90
&& fish.getCurrentRotate() <= 270
|| fish.getCurrentRotate() <= -90
&& fish.getCurrentRotate() >= -270) {
rotateLeftFish(pathMode[1]);
} else {
rotateRightFish(pathMode[1]);
}
// 如果鱼处于第3或第4象限
} else {
// 如果鱼头是朝向x的负坐标方向
if (fish.getCurrentRotate() >= 90
&& fish.getCurrentRotate() <= 270
|| fish.getCurrentRotate() <= -90
&& fish.getCurrentRotate() >= -270) {
rotateRightFish(pathMode[1]);
} else {
rotateLeftFish(pathMode[1]);
}
}
// 如果路径为直行模式
} else {
goStraight(pathMode[1]);
}
goStraight
方法是让鱼根据给定长度走直线的方法,比如新出来的鱼就会走直线,不会一出现在屏幕里就旋转,我们这里给定的是100个单位。当然鱼的左转右转方法也在该进程内,分别是rotateLeftFish
和rotateRightFish
方法。fishRun
方法是控制鱼的行动方法的类,其中我们传入的移动模式定义为int类型,分别是1:左转,-1:右转,0:直走。setFishOutlintPoint
方法根据传入的旋转的角度得到鱼所对应的X,Y值来设置鱼的外接矩形(鱼是不规则的,所以我们把所有的鱼都看做一个矩形)。isAtOut
方法可以检查鱼是否已经处于屏幕外, 如果鱼的外接矩形的上边缘大于屏幕高度或者下边缘小于0则认定鱼处于屏幕下或上边缘外,true
:处于屏幕外,false
:在屏幕内。setFishAtOut
方法处理了出了边界后的鱼,让鱼群移动线程停掉,并且通知鱼群管理器,这条鱼已经离开屏幕,触发notifyFishIsOutOfScreen
方法。PicActThread
线程是控制鱼的动作的线程。需要设置的属性有:被控制的鱼,鱼是否需要暂停动作,是否需要动作,获取鱼的所有动作,线程是否已经暂停。run
方法可以设置一个循环放置一个动作给鱼的当前动作的循环,比如游动时循环摆动尾巴的动作。并且线程中有鱼动作的重置,播放,暂停播放,停止播放方法,为鱼类调用。CannonManager
是大炮管理器类,设置的属性有:是否可以更换大炮,所有子弹的HashMap
(key:大炮质量ID,value:子弹图片数组),所有大炮的HashMap
(key:大炮质量ID,value:子弹图片数组),所有渔网图片,水波纹下效果图片,变换大炮的效果图,是否可以发射炮弹,当前使用的大炮ID。首先,管理器类都需要初始化所需要的功能,该类初始化了大炮管理器,金币数字,更换大炮的效果图,所有大炮图片,大炮,渔网,所有子弹图片。所有子弹的初始化关键代码如下:
private void initAmmo(HashMap<String,Bitmap> allImage){
……
//获取当前子弹的所有动作
while(GamingInfo.getGamingInfo().isGaming()){
allAmmoList.clear();
ammoFullName.delete(0, ammoFullName.length());
ammoFullName.append(ammoName+"0"+ammoNum+".png");
//定义一个用于创建图片的引用
Bitmap ammo = allImage.get(ammoFullName.toString());
//如果图片没有找到,退出循环
if(ammo==null){
break;
}
allAmmoList.add(ammo);
subAmmoNum = 1;
//试图尝试看看有没有同名的子图片
//这里-4是去掉.png这个几个字符,再继续拼写子名称
ammoFullName.delete(ammoFullName.length()-4, ammoFullName.length());
while(GamingInfo.getGamingInfo().isGaming()){
subAmmoFullName.delete(0, subAmmoFullName.length());
subAmmoFullName.append(ammoFullName.toString()+"_"+subAmmoNum+".png");
Bitmap subAmmo = allImage.get(subAmmoFullName.toString());
if(subAmmo==null){
break;
}
allAmmoList.add(subAmmo);
subAmmoNum++;
}
//将集合转换为数组
Bitmap[] bullets = new Bitmap[allAmmoList.size()];
for(int i =0;i<allAmmoList.size();i++){
bullets[i] = allAmmoList.get(i);
}
//将子弹放入管理器中
bullet.put(ammoNum, bullets);
ammoNum++;
}
}
getAmmo
方法是根据传入的大炮ID获取发射的对应子弹的实例。getCannon
方法是根据给定的大炮ID获取大炮的实例。upCannon
方法是提高大炮等级的方法。反之downCannon
方法是降低大炮等级的方法,这两个方法为后来的鼠标点击+``-
分别调用来调节大炮的等级。playChangeCannonEffect
方法播放大炮转换效果。shot方法
是射击子弹的方法,根据鼠标点击获取该点的X,Y坐标来发射子弹。playRipple
方法可以根据鼠标点击来播放水波纹效果。rotateCannon
方法也是根据鼠标点击先获取大炮需要旋转的角度来完成旋转大炮的需求。resetCannonMatrix
方法恢复大炮的初始状态。setShotable
方法设置是否允许发射大炮。Cannon
类是定义所有大炮的模型类,设置了大炮的旋转点和大炮的位置。Bottom
类是大炮底座类,固定了大炮所在的位置,该类的存在是为了即使更换大炮时不会出现炮台消失的囧状。ButtonAdapter
类是大炮的按钮类,该类创建了按钮,实现了Button
接口,接口设置了是否可用以及当按钮被点击时的方法,且传入了一个鼠标点击监听器,点击时会触发相应的方法UpCannonButtonListener
和DownCannonButtonListener
类实现了OnClickListener
单机事件监听接口,分别实现了提升大炮威力和降低大炮威力的需求。ChangeCannonEffect
是一个更换大炮时做的变换效果的类,里面的方法playEffect
实现了播放变换效果的需求。
Ammo
类是子弹工厂类,属性有子弹威力,当前子弹对应的渔网,以及当前子弹对应的图片的索引。ShotThread
类是射击线程,类属性有鼠标点击的坐标和x轴,y轴的移动速度,子弹绘制速度与屏幕刷新速度一样,run
方法实现了子弹的射击,如果子弹帧数多于1,就播放子弹动画,用Tool
类中getAngle
方法根据鼠标点击的位置计算子弹需要的旋转角度(原理与大炮旋转一样),并且根据玩法,子弹命中后和超出屏幕后都会删除这个子弹。CatchFishManager
是捕捉管理器类,catchFishByAmmo
方法可以根据子弹以及碰撞点形成一张网并进行捕捉---触发showNet
方法,显示渔网,播放张网音效,catchFish
方法是捕捉检测方法,这个方法检测渔网与屏幕中的鱼是否有交集,然后通知所有有交集的鱼的捕捉方法。这里需要一个重要的类实现检测是否渔网与鱼碰撞了——CircleRectangleIntersect
,把渔网当做一个圆形,鱼当做一个矩形,类中方法检测圆与矩形在各个位置是否碰撞。CatchFishManager
类中的方法checkCatch
方法检测了鱼是否被捕捉成功,关键代码如下:
private boolean checkCatch(Ammo ammo,Fish fish){
double probability = ammo.getAmmoQuality()*10+fish.getFishInfo().getCatchProbability();
if(Math.random()*1000+1<=probability){
return true;
}
return false;
}
这就是不同的鱼有不同的捕捉难度的算法实现,true
:捕捉成功,false
:捕捉失败。FishingNet
是渔网类,里面有渔网的所有属性,包括对应的子弹。
playNetAct
方法实现了播放渔网动画,根据碰撞位置在该位置播放渔网动画。gold
类是金币类,有金币的所有属性,有判断是否判断金币动画,有金币的动作线程,以及获取图片的方法,设计金币动画的方法。HighPoint
和HundredPoint
。BottomGold
类是金币显示组件类,类中属性:当前组件应显示的金币数,所有数字的索引(这里第一个元素代表得分的最大位数,以此类推),数字的宽度(所有数字宽度是一样的)。
initNum
方法初始化显示的数字,这里的for
循环完美实现了金币的显示,将0-9的数字分别用10张0-9的数字表示,循环将每一位上的数字用数字图片显示出来,关键代码如下:
StringBuffer numFullName = new StringBuffer();
String numName = "num_";
num = new Bitmap[10];
for(int num = 0;num<10&&GamingInfo.getGamingInfo().isGaming();num++){
numFullName.delete(0, numFullName.length());
numFullName.append(numName+num+".png");
this.num[num] = allNum.get(numFullName.toString());
}
FishGold
类显示捕捉到鱼后获得的金币数量,类中属性:当前组件应显示的金币数,所有数字的索引(这里第一个元素代表得分的最大位数,以此类推),数字根据鱼的击落显示在屏幕的该位置,数字的宽度(所有数字宽度是一样的)。onDraw方法是根据不同的分画出不同的金币图片方法。
updateNumIndex
方法是更新数字索引的方法。ScoreManager
类是得分管理器类,有着金币的相关图片,金币的数字图片,高分和百分的相关图片。方法addScore
是加分操作,根据不同的鱼不同的分以及鱼被击落的位置在该位置显示对应的分数,不同的分数有不同的显示效果,关键代码如下:
switch(value){
case 40:
showHighPoint(40,showX,showY);
break;
case 50:
showHighPoint(50,showX,showY);
break;
……
case 100:
showHundredPoint(100,showX,showY);
break;
case 120:
showHundredPoint(120,showX,showY);
break;
case 150:
showHundredPoint(150,showX,showY);
break;
default:
showGoldNum(value,showX,showY);
goldRun(showX,showY);
}
showGoldNum
是根据得分显示获得金币数的方法,根据不同的鱼不同的分以及鱼被击落的位置在该位置显示对应的分数。goldRun
方法使得产生的金币移动到底部位置,使游戏体验性增强,通过计算目标和始发点之间的距离,计算目标与始发地之间需要行走的帧数,计算子弹沿X轴进行的增量来确定金币的移动方式。SoundManager
的playSound
方法播放金币音效。showHighPoint
方法是显示高分的方法,根据鱼类别获取该显示的高分图片,显示在指定位置。同理,showHundredPoint
方法也是将百分图标显示在指定位置。getGold
是获取一个金币的方法。Init
方法初始化了金币,高分,百分,这三个初始化方法也在得分管理器类ScoreManager
中完成了实现。setGoldNum
方法则是设置了金币对应的数字图片,是之前击落鱼显示金币的基础。这里大家一定会发现游戏有一个计时器吧,这个计时器的作用是为了防止玩家所持有的金币不够而影响游戏的体验。当计时器记时结束且金币数小于100时,将会调用GamePartManager
的startGiveGoldThrad
方法启动定时给金币线程,内部有对于计时器数字为0时调用的逻辑。关键代码如下:
try {
int time = Constant.GIVE_GOLD_TIME;
BottomTime bt = LayoutManager.getLayoutManager().getBottomTime();
while(GamingInfo.getGamingInfo().isGaming()){
while(!GamingInfo.getGamingInfo().isPause()){
if(time==0){
giveGold();
time = Constant.GIVE_GOLD_TIME;
}
bt.updateNumIndex(time);
time--;
Thread.sleep(1000);
}
break;
}
}
[1]孙卫琴. Java开发专家:Java面向对象编程.电子工业出版社,2006,10-65.
[2]马克?艾伦. 数据结构鱼算法分析:Java语言描述. 机械工业出版社,2016,186-220.
[3]孙更新. Java程序开发大全. 中国铁道出版社,2010,1-20.
[4]何青.游戏程序设计教程. 人民邮电出版社,2011,20-45.
参考资料:
标签:canvas switch 底部 方法调用 测试 sub 电子 处理 ott
原文地址:https://www.cnblogs.com/feng886779/p/9130015.html