原文:Libgdx游戏开发(2)——接水滴游戏实现 - Stars-One的杂货小窝

本文使用Kotlin语言开发

通过本文的学习可以初步了解以下基础知识的使用:

  • Basic file access
  • Clearing the screen
  • Drawing images
  • Using a camera
  • Basic input processing
  • Playing sound effects

游戏玩法

游戏的主要玩法有以下5点:

  1. 使用桶接水滴
  2. 桶只能左右移动
  3. 水滴会从顶部并加速下落
  4. 玩家可以通过鼠标或键盘来移动桶
  5. 游戏没有结束一说,可以一直玩

预览动图:

步骤

1.创建项目

由于我是要使用Kotlin开发,所以勾选了Kotlin开发的选项

实际上,创建出来的项目,还是Java文件写的,所以,为了方便,我用了Android Studio把Java文件转为了Kotlin文件

2.添加资源文件

之后,我们需要添加该有的素材文件,总共有四个文件

  • drop.wav 水滴掉落在桶里的声音
  • rain.mp3 雨声(背景声)
  • bucket.png 桶图片
  • drop.png 水滴图片

都放在assets资源文件夹中

资源文件下载可以点击下载 蓝奏云下载

3.设置游戏配置

找到desktop文件夹目录下的代码文件,进行代码的修改,调整游戏窗口大小为800*480,并开启垂直同步

//设置游戏窗口大小为800*480
config.setWindowedMode(800, 480)
//设置开启垂直同步
config.useVsync(true)

4.加载资源文件

我们进入到core目录下的CatchWater文件,可以看到具体的代码结构

这里可以看到我们的类是继承ApplicationAdapter,从名字上就可以让我们猜测到是使用的设计模式中的适配器模式来兼容不同平台(没深入验证,仅是猜测)

ApplicationAdapter是抽象类方法,提供了几个需要重写的方法,感觉和Android开发中的Activity差不多,应该就是Libgdx游戏的生命周期方法了,这里先不深入扩展了

因为在游戏开始前,我们得先加载上述我们复制到项目的一些图片和音乐的资源文件,所以我们选择在create()方法中进行初始化我们的资源文件

添加以下代码:

lateinit var dropImage: Texture
lateinit var bucketImage: Texture
lateinit var dropSound: Sound
lateinit var rainMusic: Music override fun create() {
// load the images for the droplet and the bucket, 64x64 pixels each
dropImage = Texture(Gdx.files.internal("drop.png"))
bucketImage = Texture(Gdx.files.internal("bucket.png")) // load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"))
rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3")) }

这里需要注意下,我们两张图片(水滴和桶)分辨率都是64*64

我们使用了Gdx.files.internal()方法来获取assets文件夹里的内容,之后游戏如果是运行在Android平台上,这个方法也是通用的

如果是assets文件夹里还有一层文件夹,可以这样写:

Gdx.files.internal("iamge/myimg.png")

稍微讲解下用到的几个类,具体知识得后面再新开文章进行研究

  • Texture 这个英文翻译是纹理,但其实可以看做成图片的内存对象,类似Android开发里的Bitmap
  • Sound 比较短的那种音效文件
  • Music 时长较长的音频文件

5.播放背景音乐

之后我们可以实现播放背景音乐了,这个我们也直接在资源文件加载完毕之后播放吧

//设置循环播放背景音乐
rainMusic.setLooping(true)
rainMusic.play()

这个时候,我们可以进入到desktop里的那个文件,点击箭头运行游戏

游戏界面是黑屏的,但是已经开始播放音乐了,这里就不放图了

6.绘制图形

上述加载资源文件,我们已经得到了两个Texture对象,这个时候我们需要将其画出来,可以通过SpriteBatch这个类来实现

我们直接新建一个全局变量,然后也是在create()方法初始化即可

之后,在render()方法里,将我们的图片绘制出来

override fun render() {
//设置屏幕背景色
ScreenUtils.clear(0f, 0f, 0.2f, 1f) //绘制图片
batch.begin()
batch.draw(bucketImage, 400f, 400f)
batch.end()
}

效果如下图所示:

这里需要注意一个问题:

在Libgdx中,默认坐标系是以左下角为原点,也就是常规的数学坐标系,当然,也可以通过Camare进行修改,具体如何修改,这里先不研究

上述代码,我们将图片对象的左下角,绘制到屏幕的(400,400)坐标位置上

上面虽然我们成功绘制了一个图片,但是考虑到下述几个问题:

  • 雨滴掉落需要改变y坐标,但是y坐标如何存储呢?
  • 桶左右移动,需要改变x坐标,x坐标应该如何存储呢?
  • 如何确认雨滴和桶的边界关系呢?

考虑到上述的问题,单纯的坐标点记录会使后续的流程代码变得十分的复杂,这个时候我们可以引入一个范围来记录相关的坐标点信息

这里我们可以选用矩形Rectangle来存储我们的图片绘制位置

PS:实际上,不只有Rectangle矩形,还有其他的形状,但具体的使用,还是留在后面教程再进行补充吧

我们通过定义矩形的左上角(x,y)坐标点和宽高,就可以确认一个Rectangle矩形范围了,如下代码所示:

val bucket = Rectangle().apply {
//桶放中间
x = (800 / 2 - 64 / 2).toFloat()
y = 20.toFloat()
width = 64.toFloat()
height = 64.toFloat()
}

由于此矩形区域是我们用来找回进行边界关系的比对,所以将其宽高都设置为与图片图像的分辨率一样(64*64)

这样一来,我们使用batch.draw()方法绘制的时候,能将图片对象刚好覆盖到矩形范围里

batch.begin()
batch.draw(bucketImage, bucket.x, bucket.y)
batch.end()

7.雨滴下落实现

按照上述的逻辑,我们也创建一个雨滴的矩形范围,并将其绘制出来

雨滴默认在最上面,由于绘制图片的时候以左下角来绘制的,所以,最大y坐标减去64,即是雨滴开始的固定高度

然后雨滴的x坐标是随机的,但是最大范围为800减去宽度64

MathUtilsLibgdx提供的随机数工具类

val rainDrop = Rectangle().apply {
//x坐标随机
x = MathUtils.random(0, 800 - 64).toFloat()
y = (480 - 64).toFloat()
width = 64.toFloat()
height = 64.toFloat()
} override fun render() {
batch.projectionMatrix = camera.combined
batch.begin()
batch.draw(dropImage, rainDrop.x, rainDrop.y)
batch.end()
}

效果如下所示:

接下来,我们需要实现雨滴的下落功能,这里,我们可以采用时间作为变量,随着时间的变长来改rainDrop对象的y坐标数值

override fun render() {
batch.begin()
batch.draw(dropImage, rainDrop.x, rainDrop.y)
batch.end() rainDrop.y -= 200 * Gdx.graphics.deltaTime
}

效果如下:

可以看到,下落效果实现了,但是似乎出现了重复的东西,其实就是我们在开始的没清除掉上次绘制的图像,加上清除的代码即可:

override fun render() {
//清除并设置屏幕背景色
ScreenUtils.clear(0f, 0f, 0.2f, 1f) batch.begin()
batch.draw(dropImage, rainDrop.x, rainDrop.y)
batch.end() //每帧的时间,高度减少200
rainDrop.y -= 200 * Gdx.graphics.deltaTime
}

8.判断雨滴是否掉落在桶里

这里,就是需要判断边界了,上面也说到,使用Rectangle矩形,就是方便我们判断是否水滴和桶接触了

Rectangle对象中,有个overlaps()方法,就是专门来判断两个矩形是否重叠了

//判断两个矩形的接触面积有重叠,即水滴掉落在桶里
if (rainDrop.overlaps(bucket)) {
//播放音效
dropSound.play()
}

9.键盘控制改变桶位置

上面的功能已经基本完成了,那么还差通过键盘来控制桶的位置就能实现了

我们判断是否按下方向键左或右来改变桶对应矩形的x坐标,这样就能改变桶的绘制位置了

override fun render() {
//清除设置屏幕背景色
ScreenUtils.clear(0f, 0f, 0.2f, 1f)
batch.projectionMatrix = camera.combined batch.begin()
batch.draw(bucketImage, bucket.x, bucket.y)
batch.draw(dropImage, rainDrop.x, rainDrop.y)
batch.end() rainDrop.y -= 200 * Gdx.graphics.deltaTime //键盘输入判断
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.deltaTime
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.deltaTime //判断两个矩形的接触面积有重叠,即水滴掉落在桶里
if (rainDrop.overlaps(bucket)) {
//播放音效
dropSound.play()
//模拟消失(让水滴图片消失在游戏屏幕内)
rainDrop.y = -64f
}
}

效果如下图:

10.随机雨滴

最后,上述完成的只是一个雨滴,那么,我们需要生成多个雨滴,可以使用一个List来存储雨滴的位置

  • 当雨滴掉落在地上,将雨滴从列表中移除;
  • 当雨滴被桶接到,雨滴也从列表中移除;
class CatchWater : ApplicationAdapter() {
lateinit var dropImage: Texture
lateinit var bucketImage: Texture
lateinit var dropSound: Sound
lateinit var rainMusic: Music lateinit var camera: OrthographicCamera
lateinit var batch: SpriteBatch //最后雨滴下落时间
var lastDropTime = TimeUtils.millis() val bucket = Rectangle().apply {
//桶放中间
x = (800 / 2 - 64 / 2).toFloat()
y = 20.toFloat()
width = 64.toFloat()
height = 64.toFloat()
} val rainDropList = arrayListOf<Rectangle>() override fun create() { // load the images for the droplet and the bucket, 64x64 pixels each
dropImage = Texture(Gdx.files.internal("drop.png"))
bucketImage = Texture(Gdx.files.internal("bucket.png")) // load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"))
rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3")) // start the playback of the background music immediately
rainMusic.setLooping(true)
rainMusic.play() // create the camera and the SpriteBatch
camera = OrthographicCamera()
camera.setToOrtho(false, 800f, 480f)
batch = SpriteBatch() //开始先默认生成一个雨滴
generateRainDrop()
} private fun generateRainDrop() {
val rainDrop = Rectangle().apply {
//x坐标随机
x = MathUtils.random(0, 800 - 64).toFloat()
y = (480 - 64).toFloat()
width = 64.toFloat()
height = 64.toFloat()
}
rainDropList.add(rainDrop)
} override fun render() {
//清除设置屏幕背景色
ScreenUtils.clear(0f, 0f, 0.2f, 1f) // tell the camera to update its matrices.
camera.update() // tell the SpriteBatch to render in the
// coordinate system specified by the camera.
batch.projectionMatrix = camera.combined batch.begin()
batch.draw(bucketImage, bucket.x, bucket.y)
//绘制雨滴列表
rainDropList.forEach {
batch.draw(dropImage, it.x, it.y)
}
batch.end() // 触摸(手机端的操作和鼠标操作)
if (Gdx.input.isTouched) {
val touchPos = Vector3()
touchPos[Gdx.input.x.toFloat(), Gdx.input.y.toFloat()] = 0f
bucket.x = touchPos.x - 64 / 2
camera.unproject(touchPos)
} //键盘操作
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.deltaTime
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.deltaTime //500ms生成一个雨滴
if (TimeUtils.millis() - lastDropTime > 500) {
generateRainDrop()
lastDropTime = TimeUtils.millis()
} val iter: MutableIterator<Rectangle> = rainDropList.iterator()
while (iter.hasNext()) {
val raindrop = iter.next()
raindrop.y -= 200 * Gdx.graphics.deltaTime //如果雨滴掉落在地上
if (raindrop.y + 64 < 0) iter.remove() //判断两个矩形的接触面积有重叠,即水滴掉落在桶里
if (raindrop.overlaps(bucket)) {
//播放音效
dropSound.play()
//将此雨滴移除列表
iter.remove()
}
} } override fun dispose() {
//资源释放
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
rainMusic.dispose();
batch.dispose();
}
}

上面有个涉及到手机和鼠标的操作,因为和Camera对象一起使用,还没过于研究,之后看看再深入一下吧

打包

关于打包的操作,可以通过Gradle的Task来进行操作

Android Studio4.2之后版本,把task给隐藏掉了,所以需要通过设置开启出来

之后Gradle重构当前项目,右侧的Gradle就会出现Task列表了

打包Android的和Android项目开发打包步骤一样的,这里不再赘述

如果是要打包的jar包,则可以点击右侧的Task任务,如下图所示:

生成的jar文件,在desktop\build\libs目录下

至于打包成exe,暂时还没研究,各位可以参考下这篇文章libGDX游戏开发之打包游戏(十二)_漫浅的博客-CSDN博客_libgdx开发的游戏

参考

Libgdx游戏开发(2)——接水滴游戏实现的更多相关文章

  1. 【读书笔记《Android游戏编程之从零开始》】10.游戏开发基础(View 游戏框架)

    对于玩家来说,游戏是动态的:对于游戏开发人员来说,游戏是静态的,只是不停地播放不通的画面,让玩家看到了动态的效果. 进入Android之前,首先要熟悉三个重要的类:View(视图).Canvas(画布 ...

  2. C#游戏开发中快速的游戏循环

    C#游戏开发中快速的游戏循环的实现.参考<精通C#游戏编程>一书. using System; using System.Collections.Generic; using System ...

  3. Unity 2D游戏开发教程之2D游戏的运行效果

    Unity 2D游戏开发教程之2D游戏的运行效果 2D游戏的运行效果 本章前前后后使用了很多节的篇幅,到底实现了怎样的一个游戏运行效果呢?或者说,游戏中的精灵会不会如我们所想的那样运行呢?关于这些疑问 ...

  4. Unity 2D游戏开发教程之为游戏场景添加多个地面

    Unity 2D游戏开发教程之为游戏场景添加多个地面 为游戏场景添加多个地面 显然,只有一个地面的游戏场景太小了,根本不够精灵四处活动的.那么,本节就来介绍一种简单的方法,可以为游戏场景添加多个地面. ...

  5. 【Cocos2d-x游戏开发】浅谈游戏中的坐标系

    无论是开发2D还是开发3D游戏,首先必须弄清楚坐标系的概念.在Cocos2d-x中,需要了解的有OpenGL坐标系.世界坐标系和节点坐标系.  1.UI坐标系 IOS/Android/Windows ...

  6. 【读书笔记《Android游戏编程之从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)

    1. SurfaceView 游戏框架实例 实例效果:就是屏幕上的文本跟着点击的地方移动,效果图如下: 步骤: 新建项目“GameSurfaceView”,首先自定义一个类"MySurfac ...

  7. OUYA游戏开发核心技术剖析OUYA游戏入门示例——StarterKit

    第1章  OUYA游戏入门示例——StarterKit StarterKit是一个多场景的游戏示例,也是OUYA官方推荐给入门开发者分析的第一个完整游戏示例.本章会对StarterKit做详细介绍,包 ...

  8. 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型&障碍物判定

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...

  9. 《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9374935 作者:七十一雾央 新浪微博:http:// ...

随机推荐

  1. javascript打印对象函数

    //js对象打印函数 function writeObj(obj) { var description = ""; for (var i in obj) { var propert ...

  2. Java开发学习(二十一)----Spring事务简介与事务角色解析

    一.Spring事务简介 1.1 相关概念介绍 事务作用:在数据层保障一系列的数据库操作同成功同失败 Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败 数据层有事务我们可以理解 ...

  3. 6.20 NOI 模拟

    \(T1\ left\ xor\ right\) 考虑把询问离线,查询变成 \([0,x-1]\) 的 \([l,r]\) 的区间和与 \([0,y]\) 的 \([l,r]\) 的区间和的差 考虑线 ...

  4. React生命周期和响应式原理(Fiber架构)

    注意:只有类组件才有生命周期钩子函数,函数组件没有生命周期钩子函数. 生命周期 装载阶段:constructor() render() componentDidMount() 更新阶段:render( ...

  5. Java学习--基础

    java学习 基础 Java三大版本 javase 标准版 占领桌面端(基础) javame 移动版 嵌入式开发.占领手机端 javaee 企业版 占领服务器端 Java的特性和优势 跨平台.可移植性 ...

  6. 开源图编辑库 NebulaGraph VEditor 的设计思路分享

    本文首发于 NebulaGraph 公众号 NebulaGraph VEditor 是一个拥有高性能.高可定制的所见即所得图可视化编辑器前端库. NebulaGraph VEditor 底层基于 SV ...

  7. 100行代码实现一个RISC-V架构下的多线程管理框架

    1. 摘要 本文将基于RISC-V架构和qemu仿真器实现一个简单的多线程调度和管理框架, 旨在通过简单的代码阐明如何实现线程的上下文保存和切换, 线程的调度并非本文的重点, 故线程调度模块只是简单地 ...

  8. P4035 [JSOI2008]球形空间产生器 (向量,高斯消元)

    题面 有一个 n n n 维球,给定 n + 1 n+1 n+1 个在球面上的点,求球心坐标. n ≤ 10 n\leq 10 n≤10 . 题解 好久以前的题了,昨天首 A . n n n 太小了! ...

  9. LOJ2312 LUOGU-P3733「HAOI2017」八纵八横 (异或线性基、生成树、线段树分治)

    八纵八横 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个 ...

  10. 【JAVA】学习路径36-写到硬盘FileOutputStream Write的三种方法

    import java.io.FileOutputStream;import java.io.FileReader;import java.io.IOException;import java.nio ...