java做游戏,总会让人感觉不太放心,似乎唯一让人想到的就是我的世界(Java版),即便是这么个好作品,可依然出了个c++的基岩版(Bedrock),如果不是java难以满足玩家的需要,又何必推翻重做一个c++版呢?这就更让人失去信心,不过2020年的时候,我又接触了一款很不错的java
开源游戏,Mindustry:
缘起Mindustry
它自称为RTS游戏,不过核心还是以塔防为主,大约是2019年9月发布到Steam,我2020年4月就发现了它,真是有缘,我很快又发现原来它是开源游戏,虽说花了26块钱,不过就当它是买了一个云存档功能,而且也确实值得支持:
然后我就仔细看了看这个项目是怎么做起来的,看了看GitHub,发现到现在几乎都是游戏创始者Anuken进行主要的游戏维护,紧接着发现它的游戏引擎Arc,更是几乎完全依赖Anuken的维护,一个人能做这么好,让人十分惊叹,不过这个Arc其实并非Anuken的从零开始的啦,它也是基于一个java
游戏框架二次开发的,这个框架就是本篇的主角——libGDX。
这个libGDX,可以说是历史悠久了,早在2009年就已经有这个项目了,一开始叫做AFK
,2010年就在Google Code
开源了,然后一直到2014年,做好了第一个稳定的正式版1.0
,2d场景与3d场景都支持,还支持Windows+Android+iOS+HTML全平台。不过很可惜国内使用较少,我也是很晚才听到这个框架,不过只要开始学习,时机就不算晚,下面一起看看怎么开始做一个3d
的小demo吧(类似于hello world)。
项目配置
如果我们依照官方文档,按libGDX Creating a Project所说,下载个gdx-setup.jar
用java -jar
运行,再在里面设置些东西,最后才能在IDE中打开,感觉有点不符合常情。还有可能让人感觉,你这框架是不是配置特别复杂,才需要专门的这么一个东西来生成项目呢?
其实如果只是上手,并没有多少配置需要关心,并不是很繁杂。经过我的一些探索,发现如果在Windows桌面开发,只要在gradle
引入几个包就可以:
plugins {
kotlin("jvm") version "1.8.0"
application
}
repositories {
mavenCentral()
}
dependencies {
implementation("com.badlogicgames.gdx:gdx:1.12.0")
implementation("com.badlogicgames.gdx:gdx-platform:1.12.0:natives-desktop")
implementation("com.badlogicgames.gdx:gdx-backend-lwjgl3:1.12.0")
}
kotlin {
jvmToolchain(11)
}
application {
mainClass.set("MainKt")
}
就是这样,没有任何的其他配置,还是相当简单的吧。然后就可以开始写代码了。
初始化窗口
可以先Main
中写点东西了:
fun main(args: Array) {
val config = Lwjgl3ApplicationConfiguration()
config.setTitle("巧克力展示")
config.setWindowedMode(1200, 800)
config.setForegroundFPS(60)
Lwjgl3Application(GameApp(), config)
}
在Windows上做东西,一般就是要用这个Lwjgl3Application
做窗口,然后要给一个具体应用的参数和配置参数,配置类也就是Lwjgl3ApplicationConfiguration
了,这个setForegroundFPS
进行帧数设置可以说是游戏框架的必备了,除了上面几个基本的设置,还有比较常见的setResizable
设置,表示是否能让用户调节窗口大小;以及setWindowIcon
设置窗口的logo;setWindowPosition
,指定窗口出现位置等等。
但如上面代码中所示,并非有Main
里这几行东西就能简单的展示出一个窗口,还需要一个具体的应用,也就是Lwjgl3Application
的第一个参数GameApp()
,下面就新建这个GameApp
类:
import com.badlogic.gdx.ApplicationListener
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.PerspectiveCamera
import com.badlogic.gdx.graphics.VertexAttributes.Usage
import com.badlogic.gdx.graphics.g3d.*
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder
class GameApp : ApplicationListener {
private val lights: Environment = Environment()
private lateinit var modelBatch: ModelBatch
private lateinit var model: Model
private lateinit var camera: PerspectiveCamera
private lateinit var instance: ModelInstance
override fun create() {
lights.set(ColorAttribute(ColorAttribute.AmbientLight, Color.TAN))
modelBatch = ModelBatch()
val cam = PerspectiveCamera(70f, Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat())
cam.position[-9f, 15f] = 9f
cam.lookAt(0f, 1f, 2f)
cam.near = 1f
cam.far = 300f
cam.update()
this.camera = cam
val modelBuilder = ModelBuilder()
model = modelBuilder.createBox(
12f, 2f, 12f,
Material(ColorAttribute.createDiffuse(Color.GRAY)),
(Usage.Position or Usage.Normal).toLong()
)
instance = ModelInstance(model)
}
override fun render() {
Gdx.gl.glViewport(0, 0, Gdx.graphics.width, Gdx.graphics.height)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)
modelBatch.begin(camera)
modelBatch.render(instance, lights)
modelBatch.end()
}
override fun dispose() {
modelBatch.dispose();
model.dispose();
}
override fun resize(width: Int, height: Int) {
}
override fun pause() {
}
override fun resume() {
}
}
这里面最主要关心的,就是create()
和render()
这两个,create()
负责初始化一些固定的对象和内容,reader()
则是负责每次刷新屏幕需要渲染的东西。
Environment
主要是做一些环境属性设置,其实主要就是做光源的,它并非单个光源而是一个集合,可以放各种各样的光源。常见的例如DirectionalLight
方向光源、PointLight
点光源、SpotLight
聚光灯等等。
PerspectiveCamera
是镜头,这个是最主要的,表示视角的方向。position
表示相机所在的位置,lookAt
设置相机观察的方向,而near
和far
是用来定义相机的视锥体的。
其余的ModelBatch
、Model
、ModelInstance
这些都是管理模型对象的。
有了上面的代码,就可以运行出效果了:
是我们想要的矩形,这也是一个最基本的效果,但有很多问题要优化。
加光源与消锯齿
它的面都是完全一样的,不清晰,这时候就需要多加一个测方位的光源,在lights.set(ColorAttribute...
下面在多加一行青灰色光源:
lights.add(DirectionalLight().set(Color.SLATE, 25f, -5f, 5f))
就这样加在偏左侧一点照过来,就能把3个面看的很清晰了,再运行,就是这样的效果:
3个面各有不同的亮度,不过发现锯齿非常明显了,没有关系,回到Main
中,给config
加一个配置:
config.setBackBufferConfig(8, 8, 8, 8, 16, 0, 4)
前4个参数都是颜色,后面的是深度和模板,这里都按默认值设置了。最后一个参数就是多重采样抗锯齿的参数,这里我们调成4就好,它有点类似于有损图片压缩会让边缘模糊一些,这个4
就是采样的像素值,设置之后就可以看到锯齿好了很多:
根据鼠标缩放
这里就要做一个类InMultiplexer
,名字随意,要继承InputMultiplexer
,然后重写一个scrolled()
方法:
import com.badlogic.gdx.InputMultiplexer
class InMultiplexer : InputMultiplexer() {
override fun scrolled(amountX: Float, amountY: Float): Boolean {
...
return super.scrolled(amountX, amountY)
}
}
但这个类无法直接传递修改,而且由于是继承,也不好写构造方法,所以只能写个方法传一个消费者对象进来:
import com.badlogic.gdx.InputMultiplexer
import java.util.function.Consumer
class InMultiplexer : InputMultiplexer() {
private var yListener: Consumer? = null
fun listener(c: Consumer): InputMultiplexer {
yListener = c
return this
}
override fun scrolled(amountX: Float, amountY: Float): Boolean {
yListener?.accept(amountY)
return super.scrolled(amountX, amountY)
}
}
再回到GameApp
类的render()
里,加一个Gdx.input.inputProcessor
的赋值,建议放到Gdx.gl.glClear...
的下面:
Gdx.input.inputProcessor = InMultiplexer().listener {
camera.fieldOfView = camera.fieldOfView + it * 5
camera.update()
}
滑轮滑动一下,根据当前位置挪动滑动位置*5的距离,这个5
可以当做滑轮的step
,每次滑动的距离,可以根据情况调节。就这样效果就完成了:
可以看到并不是很复杂,不过确实要一些代码量,算不上非常简洁,但也还是比较方便的,后续我还会发一些关于libGDX的文章,可以关注我了解关于libGDX的更多知识细节。当然了也可以去看一看Anuken的作品,真的非常棒。
本文于2023年7月19日~20日同时写作并发布在lyrieek的稀土掘金社区与阿里云开发者社区。