java小游戏_Java开发小游戏,30分钟教会你

java小游戏_Java开发小游戏,30分钟教会你很久之前回答了一个“用JAVA写小游戏需要知道,明白什么?”的问题,可能最近又到了做毕业设计的时间,很多朋友发私信来询问关于游戏设计的具体思路和过程,老实交代,我真的不是做游戏的[捂脸],郑重申明,JAVA真的不适合用来写游戏[双手捂脸]。所以,如果你不是出于练手、练脑、交作业与闲得蛋疼这四种原因的任何一种,那么执行完点赞、感谢、评论与关注这四项操作的其中一项,就可以返回首页了~本文将在30分钟之…

大家好,欢迎来到IT知识分享网。

很久之前回答了一个“用JAVA写小游戏需要知道,明白什么?”的问题,可能最近又到了做毕业设计的时间,很多朋友发私信来询问关于游戏设计的具体思路和过程,老实交代,我真的不是做游戏的[捂脸],郑重申明,JAVA真的不适合用来写游戏[双手捂脸]。

所以,如果你不是出于练手、练脑、交作业与闲得蛋疼这四种原因的任何一种,那么执行完点赞、感谢、评论与关注这四项操作的其中一项,就可以返回首页了~

本文将在30分钟之内,让你能够使用JAVA语言,独立完成一个小游戏的开发,考虑到每个人的水平不同,我将文章分为以两个部分,利于选择性阅读。基础部分:窗体显示、图片绘制、图片移动、窗体监听、相交判定

设计部分:游戏实例分析、元素抽象、服务封装、元素生成器、注解定义、游戏行为交互

基础部分:

游戏的本质,实际上可以理解为图像的显示和图像的移动(人为控制与非人为控制),知道了这个本质以后,我们的问题也就变成了怎么显示图片和怎么控制图片动起来。怎么显示图片?

显示图片我们需要用到 javax.swing 包下的 JPanel类与JFrame类。JPanel 提供了一个面板(画板),显示图片,相当于在面板上把图片画出来。

JFrame 提供了一个窗体(窗口),面板被放到窗体中,最终展示在屏幕上。

JPanel 上显示图片,是通过重写JPanel中的绘制方法void paint(Graphics g)。其参数Graphics 相当于为我们提供了“一支画笔”,我们可以通过调用它的画图方法drawImage在面板上画任何东西。Graphics 中的画图方法,它为我们提供了6个不同参数的重载,运用最广的为图中圈红的第2个方法,其参数分别为(图片对象,绘制x坐标,绘制y坐标,绘制宽度,绘制高度,图像的观察者)

在 drawImage 方法调用之前,我们应该先准备好一个Image图片对象,JAVA中获取Image对象的方式有很多,我们先来一个比较省事的,直接使用 ImageIcon 中的 getImage 方法。先在src目录下创建一个resources资源文件夹,再把需要绘制的图片文件放到文件夹中

一切准备就绪,我们新建一个自己的DemoPlane类来继承JPanel,最终重写它的paint方法。可以看到,在paint方法中我们实例化了一个ImageIcon对象,它的参数为图片文件在src目录下的完整路径(包含文件格式后缀)。而图像的观察者observer 参数,传递了null值,也可以传 this 即该类为图像的观察者。当 observer 对象为非空时,原始图像更改时会通知观察者

新建一个DeomFrame 类并继承JFrame,最终完成图片的显示红框部分为窗体的初始值,蓝框部分为实例化面板并添加到窗体中,绿框部分为main函数启动项目。项目启动之后,见下图为显示的窗体。注意:这里我们为窗体设置了默认的宽度与高度,都是500,而我们绘制图片时,绘制的宽度与高度分别为 100。窗体启动效果图(注:蓝色标注的坐标实际上并不等于(500,500),因为还需要扣除窗体菜单栏的高度以及窗体自身的边距,但我们暂时先忽略这些,)

结合上文我们设置的窗体大小与窗体启动效果图上标注的三个坐标点,我们再来看一下drawImage的各个参数:Image:画啥?

x,y:在哪画?

width,height:画多大?

ImageObserver:谁关心我?这次我们调用两次drawImage方法,注意观察红框中更改后的坐标与大小再次启动程序,我的天,像极了爱情!

到这里,相信你对如何显示图片有了一个清晰的认识,甚至如何让它动起来都成竹在胸,easy easy,就是改变一下图片的坐标,它就动了嘛~完美!说干就干。

怎么控制图片动起来?新建一个Demo类,用于存放图片对象,为Demo类添加drawImage方法所需参数的各个属性。get、set方法部分省略通过构造函数把Demo类传入DemoPlane中,并在调用drawImage方法时获取到Demo对象相应的属性值

这时,我们只需要在创建DemoPlane类的同时,传入Demo对象,并且通过一个循环,不断地改变Demo对象的坐标,图片不就动起来了吗?

实际上并没有,因为JPanel对象的paint方法,只在窗体显示时调用一次,也就是说,我们不断改变图像坐标的同时,还需要不断地重新绘制面板内容,以达到画面的刷新,图片才能真正的被肉眼所看到移动,所以JPanel还为我们提供了一个刷新的方法,repaint( )即重新绘制。在DeomFrame类的构造函数中加入循环,实现图片的移动。

(妈呀,写到这才发现我的Panel拼写成了Plane,难受啊。。。啊。。。啊。。。)修改后的DeomFrame类(残影是因为图片太小了,并非程序造成,点开大图查看即可,后续gif图片有相同情况)Win10自带的屏幕录制不会把窗体头部录下来,这是我们加入循环后的效果

好的,图片成功移动。到这里我们已经完成了非人为的控制,接下来我们再通过监听事件完成人为的控制。

在窗体上添加键盘监听:

窗体对象调用addKeyListener(KeyListener l)方法,即可在窗体上添加键盘监听,KeyListener是一个监听器的接口,我们先来看一下它的源码KeyListener 源码,每个方法都为我们提供了一个KeyEvent参数即键盘事件。

KeyEvent类中为我们提供了许多与键盘事件相关的方法,这里我们先使用它的getKeyCode()方法,即可获取当前按键所对应的按键值,它是一个整形,我们可以通过这个整形结果值与KeyEvent类中定义好的按键常量进行比较,从而判断出到底哪个键进行了操作。为了加快演示效果,我们先简单实现一下keyPressed方法,在方法体中定义好对图片的控制。在DeomFrame的构造中添加键盘的监听,并实现keyPressed方法,通过对W、S、A、D的判断,改变图片的X、Y坐标,实现图片的上、下、左、右移动,并通过对J、K的判断,改变图片的宽度与高度,实现图片的变大与变小。最后我们修改一下线程的等待时间,让画面刷新得更快窗体添加键盘监听之后的图片移动效果

在面板上添加鼠标监听:

我们也可以通过对鼠标的监听来实现对图片的控制。这次我选择在面板上添加鼠标监听事件,虽然窗体也可以添加,但是如果通过窗体添加,那么鼠标的X、Y坐标是从窗体标题栏上开始计算,这会与我们的面板(0,0)位置的坐标有误差,可以自己测试感受一下。

使用JPanel 面板的实例,调用addMouseMotionListener方法实现对鼠标运动的监听,调用addMouseListener方法实现对鼠标动作的监听。在DeomFrame类的构造函数中,使用DemoPlane的实例调用上述两个方法。在面板上添加鼠标监听面板添加鼠标监听后的图片移动效果

好的,到这里,图片的显示和图片的移动算是告一段落了,但单单只有显示和移动,视乎并不能满足一个游戏,那么缺少的是什么呢?

回想一下我们玩过的游戏,比如简单的贪吃蛇,它除开显示蛇的图片和移动蛇的图片,它还做了什么?吃水果然后身体变长,撞到了自己的身体然后游戏结束。再来思考一个复杂一点的,比如吃鸡游戏里面,对方向我射击和我向对方射击,这个过程中发生了什么?

贪吃蛇游戏中我们关心的是吃没吃到水果,撞没撞到自己,吃鸡游戏里在乎的是我们射出的子弹打没打到对方,对方射出的子弹打没打到我们自己。简单来说,都是一个是与否的判断,而这个判断,如何而来?其实就是两张图片所绘制的范围有没有相交到一起。

我们引入一个Rectangle矩形类,用它来创建我们图像所占有的范围,创建它时,我们同样需要告诉它,它在哪?(位置,x,y),它多大?(宽度、高度),这与我们绘制图片时所使用的参数完全一致(当然,我们可以把它设计得比图像小一些)。它能为我们提供了一个intersects函数,返回一个boolean值,用于判断两个矩形是否相交。Rectangle类中intersects方法源码

我们简单修改一下我们之前的代码,模拟一下玩家发射子弹打到敌人的场景。修改面板类的构造函数,让它可以接收多个Demo对象(为什么不多弄几个类,好区分?因为我懒,哈哈)。并在绘制方法中循环画出多个Demo对象。修改DeomFrame 类的构造函数,把我们之前的一个Demo对象变为3个,分别为自己、敌人与子弹,并把他们都添加到面板中修改DeomFrame 类的造函数中,while循环体内的代码,为敌人和子弹都赋予非人为的简单行动轨迹,并在子弹发射之后为子弹和敌人生成矩形,判断二者是否相交,详情请看注释修改DeomFrame 类构造函数中,对键盘的监听代码,增加发射子弹的判断逻辑相交效果模拟

到这里,我们已经掌握了图像的显示、移动与相交,回想一下自己曾经玩过的游戏,它们是不是都只是在这三个行为的基础之上,进行了不同程度的演变和增加了更为复杂的判断?所以理论上来说,掌握了这三个核心要素,开发一个小游戏,基本上狂堆if else 也能凑合完成,不过是多花了些时间而已。

想我年轻的时候,就知道个if int list for,也硬生生的怼了个星球大战出来[娇羞],所以世界有多大,就看你的想法有多爆炸。在进入下一个部分之前,请务必尝试按自己的想法和思路,结合已掌握的知识,开发一个简单的小Demo,因为后续大部分内容都是一些主观的思想,不要太快被我洗脑哟[捂脸]。

设计部分:

众所周知,Java三大核心思想,面向运气、面向偷懒与面向女朋友。

面向运气比较高深,面向偷懒容易挖坑,所以今天的设计部分,我们来着重讲解一下最为基础的,面向女朋友编程。

如果现在的问题不是如何设计你的游戏,而是如何设计你的女朋友,或者换一个说法,你想要一个怎样的女朋友?入门级别沉鱼落雁闭月羞花,凶猛等级臀如桃谷形堪岛枫面似小泽胸比井空,高级开发微微一硬肃然起敬,顶级架构…..哎哟妈,一不小心又开车了,不知不觉就忘记这是一篇技术贴了,罪过,罪过,学术探讨,不吃举报。

不过话说回来,为什么高级开发会微微一硬?那是因为当你还在定义高矮胖瘦之时,他们已经抽象到了颜色构成。可怕,可怕,这车还停不下来了[捂脸]。

好了,好了,言归正传。讲道理,正儿八经跟游戏相关的知识点,在上一部分其实已经完完全全的交待清楚了,所以这一部分的主要内容,无非是如何更好的对它们进行使用和调用。技术层面上的东西也会直接上升到项目层面,所以我能做的,也就是提供一些我自己的设计思路和解决方案作为辅助参考。

所以后续部分的内容,我将以文章背景图上开发的这个小游戏的实例去展开,通过对游戏源码的分解,列举出各个模块的核心内容以及实现的功能,以便于更好的阐述出我的想法和思路。废话不多说(这还不多?[捂脸]),让我们先从下面这段视频中,对游戏实例有一个整体的了解:950d722fda5e79172ed4ec24554ad1c9.png游戏实例《吃屎吧你!》https://www.zhihu.com/video/1094012435303866368

游戏源码(电脑前的小伙伴,可以结合源码进行后续的阅读):yilu1216/EmoticonWar​github.com9b0c0c92300c56f780ae36a3c9e8dc2d.png

好的,通过视频内容我们对游戏的实例已经有了一个整体的了解,接下来,我们将会对其功能进行逐步拆解与分析,从游戏的基础抽象到各模块之间的关联调用进行全方位的讲解。

基础元素抽象:

在游戏的设计初期,我们应该先对它的玩法进行简单的定义。比如它是一个横版游戏(超级马里奥)还是纵版游戏(飞机大战)?它使用鼠标操作还是键盘操作?

在决定了基本的玩法之后,我们再来定义游戏的内容。这里的内容,指的也就是游戏中的图片类型,应该想到的是装备与敌人,而不是匕首与骷髅怪。对于图片类型的定义,也是设计中最为关键的一步,即对游戏元素的抽象。它将决定游戏的大致走向,同时也是对游戏玩法的补充说明。

在正式编码开始之前,我们可以先在脑子里把游戏中的元素进行简单的抽象(最好用记事本啥的打出来)。比如我在真正开发游戏的实例之前,脑子里已经模糊的构思出了下面的关系图:面向女朋友编程

不好意思放错图了[捂脸]游戏元素基础抽象与继承关系

结合上文的知识我们可以清楚的知道,游戏中所有的元素都离不开显示、移动与相交判定,所以我设计了这样一个抽象类(BaseElement),作为所有元素的父类,它能为我提供上述三个动作(方法)。

void drawImage(Graphics g);//绘制图片:用于显示

void action();//动作:用于移动

Rectangle getRectangle();//获取矩形:用于相交判定

因为绘制图片需要坐标,大小与图片对象,获取矩形也需要坐标与大小,所以把上一部分中Demo类中的属性照搬下来,也就得到了BaseElement类中的基本属性:

protected int x, y;//坐标

protected int width, height;//大小

protected Image image;//绘制图像

这样也就补齐了drawImage方法与getRectangle方法的方法体:

public void drawImage(Graphics g) {

g.drawImage(this.image, this.x, this.y, this.width, this.height, null);

}

public Rectangle getRectangle() {

return new Rectangle(this.x, this.y, this.width, this.height);

}

对getRectangle方法进行扩展,得到相交判定方法,即某元素是否与我相交:

public boolean intersects(E element) {

return this.getRectangle().intersects(element.getRectangle());

}

移动方面,由S=Vt 可知,S 为 坐标移动的距离,t 可以看作方法被访问的次数,所以还欠缺一个速度V,所以再增加两个属性:

protected int xSpeed, ySpeed;//x轴与y轴的移动速度

则元素在1个单位时间内,向左移动为 x-=xSpeed,向右移动为 x+=xSpeed ;这样看来,还欠缺一个方向上的概念,所以为方向增加一个枚举:方向枚举,用于判定元素的方向;因为是横版游戏,经常用到左和右的判断,所以对其方法进行了封装

为抽象类增加方向属性:(在BaseElement 类中,我只增加了一个 方向用于判定左和右,具体原因我会在后续说明)

protected Direction direction;//方向 用于判断左右

增加了方向的概念之后,补齐action动作方法的方法体:

public void action() {

this.xMove();

this.yMove();

}

protected void xMove() {

this.x += direction.right() ? xSpeed : -xSpeed;

}

protected void yMove() {

//暂时为空

}

基础重力元素抽象:

为什么不设定Y 轴的方向呢?,因为了更好的展示玩家跳跃的效果以及技能所勾画出的抛物线,我模拟了一个简单的重力环境(如果你的游戏中不需要重力,可以忽略此部分)。思路想当简单暴力,当某物体不站在地面时,它受到一个向下的力所影响。即每单位时间内,它的Y坐标+= g * 它的质量(g 为常量值 )。于是我定义了这样一个接口:重力接口IGravity,方法依次为:是否站在地面上;获取物体质量;获取Y坐标;设置Y坐标

这样,对整个游戏来说,我取消了向上走和向下走的概念,替代的是向上则视为跳起,它应该做的是减速运动,向下则自然受到重力的影响。

对单个元素来说,它如果实现了IGravity接口,那它单位时间内,将会先调用它的onTheGround 方法,当返回 false 时,将调用它的 getQuality 方法获取质量,乘以重力加速度g 并加上它当前的Y坐标值,得到它新的坐标值,并通过 setY 方法进行重新赋值。

新建第二个抽象类基础重力元素BaseGravityElement 继承抽象类BaseElement,并同时实现IGravity接口:基础重力元素抽象类

这样,对游戏中的所有元素来说,如果会受重力影响,那么继承BaseGravityElement,反之则继承 BaseElement。

游戏面板改造:新增一个绘制接口,实现该接口的类将需要实现drawImage方法。很显然BaseElement可以实现它,基础元素和基础重力元素都应该被画出来,但能被画出来的东西并不一定需要继承BaseElement类。

在前部分DemoPlane的基础之上,修改部分代码,得到新的游戏面板类,用于绘制游戏中所有需要绘制的部分:GamePanel 游戏面板;这里增加了一个双缓冲,目的是为了防止画面刷新时屏幕的闪烁与残影。原理是,当paint方法被调用时,如果draws中有N个元素,则这N个元素会依次画在面板上。增加一个image对象以后,让它充当一个面板,即N个元素会依次先画在image对象上,形成一个完整的画面后,再统一画在面板之上

游戏键盘封装:

在游戏的实际过程中,键盘常常是几个按键同时使用的,比如一边移动一边攻击等;所以在我们前半部分的例子中,单单只监听哪个按键被按下,并不能很友好的表现出效果。这里提供一个思路,即利用一个 Set 集合用于存放按键,当玩家按下按键时,把按键对应的常量值添加进集合,松开时则移除。再通过用于操作玩家动作的线程,去获取Set集合,通过contains方法,判断某按键是否在集合中,来得到玩家是否在按某键。至于这个Set集合放在哪里,那就有更多的方式了,我先来个偷懒的:用枚举实现对玩家按键的定义,提供use方法进行是否使用某键的判断,返回一个boolean值在主窗体添加监听时,把keyPressed方法按下按键中,获取到的KeyCode添加到Keys枚举的Set集合中,把keyReleased方法松开按键中,获取到的KeyCode从Set集合中移除

这样我们使用起来也很简单,例:

if (Keys.LEFT.use()) {

this.x -= this.xSpeed;

this.direction = Direction.LEFT;//更改玩家方向为左边

}

定时器的使用:

我们引入一个Timer定时器来替代前半部分中的while循环,使用方式:实例化一个Timer 再实例化一个 TimerTask 作为 timer.schedule 方法的参数;其中 delay参数表示 延迟多少毫秒后开始,period 参数表示 间隔多少毫秒后执行依次; TODO 部分为循环执行的代码快

这样我们便可以进行简单的调试,脑补一下整个流程:新建一个DemoPlayer 类(玩家)并继承BaseElement 类;

DemoPlayer 类中重写 xMove 水平移动方法,加入Keys.use方法对其玩家按键进行判断;

实例化DemoPlayer类,并为坐标、大小、图片、方向、速度设置默认值;

新建一个DemoEnemy 类(敌人)并继承BaseElement 类;

实例化DemoEnemy 类,并为坐标、大小、图片、方向、速度设置默认值;

把DemoPlayer与DemoEnemy的实例添加到GamePanel 游戏面板中;

在TimerTask重写的run 函数中调用 GamePanel 游戏面板实例的repaint()方法,并同时调用DemoPlayer实例 与 DemoEnemy 实例的 action 动作方法;

在TimerTask重写的run 函数中调用 DemoEnemy实例中的 intersects 方法(某元素是否与我相交),并把DemoPlayer实例传入其中,当返回 true 时在控制台输入出某个字符串,表示敌人与玩家相交。

那当我们有N个敌人时,我们应该怎么处理呢?举一个简单易懂的例子,新建一个演示敌人列表的类用于存放多个敌人:可以看到这个列表类实现了IDraw接口,表示它也能直接添加到面板中,面板调用它的drawImage方法时,它自己在循环每一个敌人,调用它们的drawImage方法;action 方法与intersects方法同理。

把上述脑补的流程中 DemoEnemy 类换成我们新建的这个DemoEnemyList类,即可实现对所有敌人的相交判定。(多个DemoEnemy 怎么来?先new出来好吧!)

同理,回过头去再看一下我们尚未处理的重力。是不是可以有这样一个列表,List 它里面装满了受重力影响的元素,它也可以写一个具体的动作方法,在一个Timer中被调用,应该是下面这个情形吧:重力服务代码片段,万有引力方法在Timer 中被调用

基础服务与重力服务:

从上面的例子我们不难看出,处理敌人需要一个列表,处理重力也需要一个列表,我们还有道具、技能等一堆东西,它们也会需要这样一个列表,所以我先定义了一个基础服务接口:基础服务接口,这个服务需要提供一个对CopyOnWriteArrayList列表进行读取、添加和删除的操作方法,同时要能被添加到游戏的绘制面板中。(这里的跟随玩家移动后续会说明)

至于为什么要使用CopyOnWriteArrayList,因为这里我们需要一个线程安全的列表。(有的朋友可能会跳起来找我打架了,这玩意儿内存开销这么大,确定要用它?!大大大,一个列表中的元素连50个都达不到,别闹[捂脸])创建一个抽象类BaseService基础服务来实现IBaseService服务接口,主要负责实现与列表相关部分的代码,其它方法空着等待需要的子类进行重写

显然,我们的第一项服务就是重力服务,它既不需要绘制,也不需要跟随玩家移动:继承于基础服务的重力服务,泛型直向IGravity接口

地面构成与背景服务:

在IGravity重力接口中,我只定义了一个是否站在地面上的方法,所以到底有没有站在地面上,我实际上还并不知道。所以,我需要先创建出一个地面,扫了一眼我仅有的素材:我的素材库,就问你可不可怜,难受啊【捂脸】~被红框圈住的礼物盒子和巧克力貌似有点那么意思,最终选择了礼物盒子从图中圈红的部分可以看出,我的设计方式是,多个盒子堆积拼成了一个竖立的柱子,多个竖立的柱子拼成了一个完整的地面

新建一个Ground地面类继承基础元素BaseElement,并重写drawImage等方法完成单个竖立柱子的创建:Ground地面类源码;其构造函数需要传入x,y 坐标,其中y坐标则代表了竖立的柱子到底有多高

那么如何随机生成有规律的地面块,而不是杂乱无章的竖立柱子呢?这里我定义了一个地面类型,类型中规定了地面块的最小组合个数和最大组合个数(即有几个柱子构造)与地面块的朝向(即一整组竖立的柱子是往上延申的还是往下延申,防止地面组无限往某一个方向延申最终超出屏幕上、下边距):地面类型,以向上台阶为例:可以看到,向上的台阶我规定了它们的组合个数为3-8个,它对Y坐标的处理为:上一个柱子的Y坐标减去素材大小常量。

以向上台阶为例:当地面生成器随机到【向上台阶时】将根据它的组合个数【3-8】生成一个随机数,如【5】,则决定了此时地面的组合个数为5个。再通过传入的上一次地面高度lastY坐标减去素材常量则得到了单个柱子的Y坐标,而这个Y坐标也将成为下一个柱子lastY坐标,这样,每个柱子的坐标减去固定的常量值,则形成了一个向上的台阶。整个过程由源码中,auxiliary辅助包下的generator生成器包中的GroundGenerator地面生成器及GroundType地面类型枚举配合完成,有兴趣的朋友可以到对应位置查看,由于字数关系我就不挨个进行说明了

竖立柱子(Ground类)拼成了不同长度的地面块(GroundGroup类,在它之中包含一个List groundList;//地面组),不同长度地面块就可以构成一个完整的地图,也就最终行了地面背景部分。同时,由于每个竖子的柱子自身都提供了一个是否在它上方与它相交的方法 boolean aboveIntersects(E element),通过该方法,则可以判断某元素是否站在地面上,当站在地面上时,则可以通过BaseGravityElement类中的setOnTheGround 方法把该元素中的 onTheGround属性设置为true,反之则设置为false,这样它又会受到重力的影响了。BackgroundService背景服务中的地面判定

这里得到我们的第二项服务,BackgroundService背景服务。它同样继承于BaseService基础服务,而它的泛型,则指向了GroundGroup 地面组。游戏背景服务,在它的元素列表中,装了N个地面块,而每一个地面块中,又有一个普通的List装了N个竖立的地面柱子。当背景的drawImage绘制方法被调用时,它会循环调用元素列表中的每一个GroundGroup地面组的drawImage方法,地面组又会向每一个地面发出绘制的请求,最终画出每一块地面

自定义注解与元素服务:

到这里,已经有了重力与背景两项基础服务,但是对游戏中的其它元素视乎还没有进行处理,不过照这样说下去,我感觉我会挂掉,所以我打算换一个方式,反向推导出每个类存在的意义,我们先来看一张敌人子类的源码分析图:运动员敌人源码分析

上图为为游戏中踢足球的敌人源码,我们从上往下依次分解:IEnemy 自定义敌人生成注解: 它为敌人配置了一个出现的概率 (40%),组队的初始人数 (1个)与 玩家分数到达多少分时生成(3000分以上)。

定义这个注解,主要是为auxiliary.generator生成器包下的EnemyGenerator敌人生成器所服务,游戏开始时,生成器会向自身列表中寻找敌人生成属性,当列表为空时,会通过auxiliary辅助包下的ClassLoaderUtils类加载工具,扫描main.java.content.enemy 下的所有包(我会把与敌人相关的类都创建在这个包下),并通过反射加载IEnemy注解,同时缓存一个该类的构造器。这样则可以通过随机函数,获取到某一个敌人类的构造器,并根据IEnemy注解来判断是否满足条件,满足则创建敌人,并添加到EnemyElementService 敌人元素服务中。

这样做的好处是,当我们添加一个全新的敌人时,我们只需要配置一个IEnemy注解,设定它出现的条件,它就可以自然的加入到我们的敌人元素列表中了。IElement 自定义元素注解:它为游戏中的元素配置一个初始值,在上述例子中,它指定了该类读取一个向左的图片(athlete.gif),设置了一个xSpeed=0的初速速度,设置了一个死亡后500的奖励分数。

游戏中所有元素都可以配置该注解,该注解在父类BaseElement中被加载:IElement 注解详情,其中每一项都设定了默认值,在创建一个新的元素时,只需要为非默认值的属性进行相关的配置,则可以被投入使用

如玩家的技能拳头元素:它通过IElement 配置了 朝左与朝右的两张图片CountEnemy 计数敌人父类:它继承于Enemy敌人主类。继承它的敌人表示在某一个固定频率下,会做某一件事情。

所以它要求子类必须重写getCountMax 方法与 doSomething 方法,在上述例子中,表示运动员敌人的计数达到 50 时,会释放一个足球。CountEnemy计数敌人,从源码中可以看出,它在每一次action方法被调用时,都使其count自增,当count数达到最大值时,调用子类重写的doSomething方法。向物质服元素服务中添加一个足球元素:物质元素服务与重力服务、背景服务一样都继承于BaseService基础服务,在这个游戏中,我一共创建了5个服务用于整体的游戏运转。元素服务bean,5个服务都继承于BaseService基础服务 所以同时实现了IBaseService服务接口。利用enum的构造函数方式,形成单例

其中,玩家、敌人与物质服务继承于ElementService元素服务。元素服务继承于基础服务,泛型指向了基础元素

并重写了 add 与 remove 方法,在把元素添加到元素列表时,判断元素是否是重力元素的子类,如果是,则把元素再同时添加到重力服务列表中,移除时同理。ElementService 元素服务中,重写了父类BaseService基础服务的添加与移除方法

在ElementService元素服务中,定义了一个action动作方法,由该方法来处理其列表中所有元素的动作。该方法会传入Player 玩家类与ElementService 其它的元素服务。在方法中完成该元素与重力的交互,玩家的交互,其它元素的交互与自身的动作。ElementService 元素服务中,与自身外所有元素的交互动作

到这里,基本上的内容已经说完了(后面明显偷懒了,哈哈哈哈哈哈,太累了,可怜可怜我吧,要12点了),最后,再对全包的分布进行功能汇总:游戏各包功能概括(另外:元素服务的动作方法,在content包下的GameContent中被投以调用)

ok,搞定,稳得一批。yilu1216/EmoticonWar​github.com9b0c0c92300c56f780ae36a3c9e8dc2d.png

游戏源码中,留下了很多可以改造和扩展的部分,欢迎下载以后增加更多有趣的内容。

另外,因为时间的关系,后续部分说得有些凌乱,有表述不清的地方请给我留言。

这,你都不点个赞?

晚安( ̄o ̄) . z Z

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/14215.html

(0)
上一篇 2024-03-02 10:26
下一篇 2024-03-02 12:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信