在Java下用AWT原生绘图制作3D贪食蛇

于是大学第一个学期就这样要过去了,然后程设课的期末作业是在Java下制作一个游戏(啊!)然后想在还算对 Java 有那么些的了解的份上,于是打算做一个东方同人贪食蛇类游戏。。。

零、构想与妄想

来自 3DCGArts 的一个红白模型

来自 3DCGArts 的一个红白模型

于是当时想出来这个主意的时候就比较激动,然后就开始果断的去先探资源上的可行性了,毕竟做一个游戏的话编程部分可以自己搞定,但是3D建模自己可玩不灵。

幸好路过了这么一个神网站:http://www.3dcg-arts.net/ ,界面的设计很多类似P站,但是上面都是高质量的 3D模型啊!!于是就找了一个红白的ローポリ(低多边形)模型(毕竟自己从来没写过3D类游戏嘛,多边形太多会死人的)。

然后就是通过审查元素把Model的点、线、面数据搞下来的,发现是一种 JSON Model Format 3.1,适用于three.js这个Web渲染引擎。读了读引擎发现大致原理还能看懂,想在限定时间内搞出也不是不可能。

至于游戏的玩法设计,主要是想到了之前的诺基亚神器上有一个叫 Snakes的 3D贪食蛇游戏,着实是玩了好久,于是各种的功能便想从Nokia Snakes入手,比如基本的“果实”→“硬币(红白嘛)”,“能量带”→“纸币”等等等等。同时也有设计实现所谓的“弹幕墙”,即一些会有魔法弹通过的贪食蛇“墙壁”,在无弹幕时却可以穿过。

上图为游戏运行界面放大设想,下面则是各种奇葩的功能以及设定。踩到袖子会摔什么的。。。不科学不是么。。。

上图为游戏运行界面放大设想,下面则是各种奇葩的功能以及设定。踩到袖子会摔什么的。。。不科学不是么。。。

然后就发现这个坑太大,一周的作业期限里写不完啊!不过不能放弃嘛,看看能不能先把基础功能实现,然后再说二次元部分→_→

一、原生图形

无奈因为是程设作业。大概是不能用第三方库的(坑爹),JOGL、Java3D一类的需要安装二进制文件的,自然就更是排除在外了。无奈只能自己通过绘图函数写3D渲染。研究了好久,参考了three.js的实现和JavaGL,提取了一些设计思想,最后终于在网上找到了一个还算靠谱的,理解起来很简单的“3D投影幕模型”。http://www.dgp.toronto.edu/~mjmcguff/learn/java/11-3d/ 貌似还是个Java教学题目,总之研读了一下这个代码,发现对3D渲染的初级部分还是很好搞的,一次变幻,一次视觉平面投影=w=。

于是最初的 3D渲染层就出现了,其由两部分构成:
Field3d - 渲染3d的一个“视觉控件”
Primitives - 构成三维几何的基本组件
-Point3d 点(x,y,z)
-Edge 边(p1,p2)
-Face 面(p1,p2,p3) (p1,p2,p3,p4)

然后就制作了一个初级的Renderer。这个时候有一个ArrayList来追踪所有的绘图点和绘图线绘图面,就能在给的点中进行绘制了,绘制完了还能通过鼠标拖动上下视角(Elevation)和偏斜程度(Azimuth)。后来发现贪食蛇不需要调Azimuth,于是就固定住了,同时入了一个绕Z轴旋转的功能,来处理贪食蛇转弯问题(需要屏幕场地跟着转)。

于是制作了一个方块模型,和一个场地模型(平面)测试了一下,OK!3D投影绘图

原本的 3D渲染器只支持点和线的渲染,后来出于要制作墙壁,于是添加了很有限的面支持,但是由于投影原理实在太精简,所以并不处理各种遮盖关系,于是。。。面都是透明的啊有木有。。。

二、3D功能层抽象化和游戏逻辑模型设计

然后发现3D渲染平面能使了,就开始把数据抽象化了,于是引入了模型的概念(Model3d),一个 Model3d里面就自己包含一个独立原点的3d场景,自己控制着一套点、边、面的合集,同时还可通过调用Animate方法,让Model3d中的内容自己独立动画操作,这样就不必考虑行进间的问题了,因为行进只是在对模型进行平移。同时,Model有自己的 (x,y,z) 坐标,这样就能很轻松地一边实现模型效果,一边移动模型。由于渲染中的线与点是引用关系,所以模型能很好的解决线中定义的点的Index的实时更新,比如模型中的(P1-P2)边,在渲染中可能会成为(P23-P24)这样一条边,通过抽象化具体的边,在渲染时就能依照已经有的点数产生出新的Index。

3D Rendering

其实渲染还是有一些BUG的= =额。。。

游戏的逻辑部分则是需要在一个二维的系统中运行(因为贪食蛇的主要部分还是在平面中完成的。于是通过在一个 10×10的x,y平面方砖(Tile)中画8×8大小的原件,这样就能既留出行间空白,又实现一个基本的定位二维坐标系统,称之为网格世界GridField。网格世界的坐标(1,1) 会对应到3d世界中的 (10,10,0),而我们需要的游戏元素都需要支持这一定位方式,故产生了 GridFieldItem 的一个 Interface。这下游戏里面的各种物件就可以被很好地定义了。

剩下的游戏逻辑部分就是需要判断碰撞(蛇进入一个格子或者碰到一个可吃的东西),并处理各种移动情况。这时需要把游戏的运行部分分成两块,一块负责时时调用渲染,另一块则会时时移动3D模型,并处理游戏中的动作。这时有了初步的 GameRunner

三、制作游戏场地的演员们,和它们的动画效果

这时候游戏的渲染和部件都搭建完毕后,就开始实现各种不同的“演员”,在贪食蛇世界中的演员必须满足

  1. 扩展了Model3d,这样能被插入到场地里面
  2. 实现了GridFieldItem,这样就能被游戏引擎追踪

在这两个条件下,我们就实现了 playground 包里面的 PelletModel 和 PowerStripModel。两个分别实现了果实和能量带的一个能量块,这些模型包含模型的三维数据,也包含自己的动画方法,当他们被添加到场地后,就会在自己的区域内执行自己的动画效果。

旋转的两个组件PelletModel是一个旋转的六面柱,PowerStripModel则是一个旋转的方形平面。

构建完了道具后,我们还要构建我们的蛇。蛇分为三个部分,脑袋、身体和尾巴。我们通过把这些部件串成子模型,然后每次蛇增长时,将新的身体模型“挂”在旧的后面。

蛇由头、身体、尾巴组成,在动画效果时,各个身体部件会把动画效果调用派送给后一个部件。

蛇由头、身体、尾巴组成,在动画效果时,各个身体部件会把动画效果调用派送给后一个部件。

在制作完三维模型后,我们需要更改Model3d的设计让其支持每个模型添加一个子模型,这样通过重载Model3d的“添加子模型”方法,就能让蛇能正常的添加身体的后端了。由于蛇在行进间不是跳跃GridField格子的,而是连续的,所以蛇还有一个“微时间”来控制蛇的中途位置,每当微时间到达5,即蛇有一半进入了新的格子,就认为蛇进入了新的格子。这样就能有效的处理碰撞了,当碰撞发生时,让蛇倒退回到之前的格子并转弯就能处理其撞击事件了。

不过这个简单的方法也有很复杂的地方,比如在蛇有身体结构后,倒退则需要身体的各个组件都退回半个格,所以必须把退回的消息传递到身体的各个部位。身体增长时,由于头部可能在“中间状态”,所以不能只把身体定位到格点(GridField Point),而必须还得计算微时间导致的偏移。

蛇的身体追赶是通过前一个部件在更替位置时告诉后一个部件需要走到的下一个坐标(自己之前的位置)。比如头在(0,2)然后这时候进入(1,2),那么第一段身子这时进入(0,2),同时目标被设为(1,2)。这样每个点的目标设成在其前方的点的旧位置,就能把路径信息传递给下一段蛇的身体了,直到尾巴部分。

四、填充游戏逻辑——动作后果

现在有了整套碰撞(进入)系统,和舞台演员后,需要有一个抽象的地图来保存演员的位置,功能和处理碰撞信息。于是我们定义了 game的 map包。每一层(一个游戏)的地图信息包含了砖块的种类、上面的东西等等信息,这样当蛇“碰到”某个东西的时候,就能通过地图来获取物体本身,并对其进行操作(比如移出场景——吃掉果实/能量片)。这时游戏逻辑反复分析抽象的地图信息,

GameRunner会参考GameMapData的数据,更新Field3D里面的数据模型FieldData。

GameRunner会参考GameMapData的数据,更新Field3D里面的数据模型FieldData。

并更新3D场景中的元素(添加或是去掉它们)同事也会更新GameMapData的地图数据(当某个果实被吃掉时,再碰撞同样位置将无触发器。

与此同时,GameRunner还要控制蛇的运行速度,并参考地面砖块的类型来确定蛇的速度是否有加成/缩减的情况。这样在在GameMapData中有抽象的世界,会在更新时同事更新FieldData中的实际显示场景。

GameRunner还需要控制一些游戏的宏观信息,比如剩余生命条数、积分、积分加成器等等。

五、收坑

最后需要实现的,就是一些2D的东西了,比如显示游戏信息,显示 剧情 教程文字等等,于是临时在Field3D里面加了个2dContext,一个绘图的空壳,做了几个视觉元素,然后就收工了。

于是坑挖了这么大,然后终于在调试完了各种移动Bug和蛇的身体衔接BUG之后,到达了交作业的时间。于是由于设计游戏关卡的能力不够强,所以制作了一个配置文件来保存各个关的信息,让游戏在读取时根据这些信息构造场景和场景上的物体。经过苦逼的调试后,最终最终。整个贪食蛇完工了!

完工后的样子

完工后的样子

。。。。

等等等等,说好的东方在哪里。。。所以说。。。坑很大啊。。。有待继续研究啊,希望不是作业的时候就能用更加能得心应手的语言和3D库了。。。Java你是个大坑啊。

项目地址:https://github.com/jabbany/CIS120FinalProject 有兴趣研究从零开始写3D游戏的,可以挖开看看。。。

龙之介同学求保佑啊!!做游戏真苦逼啊。。。(虽然最后这个程设作业拿了满分吧

龙之介同学求保佑啊!!做游戏真苦逼啊。。。(虽然最后这个程设作业拿了满分吧

4 Comments

    1. 嘛。。。基本都是自己无聊的时候在网上看这看那学的。。。不过高一的话还有时间呢~(高一的时候我也达不到现在的水平啊)。。。总之编程这种东西有爱就不成问题=w=

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注