Libgdx游戏开发(2)——接水滴游戏实现
本文使用Kotlin语言开发
通过本文的学习可以初步了解以下基础知识的使用:
- Basic file access
- Clearing the screen
- Drawing images
- Using a camera
- Basic input processing
- Playing sound effects
游戏玩法
游戏的主要玩法有以下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
MathUtils
是Libgdx
提供的随机数工具类
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开发的游戏
参考
- A Simple Game - libGDX
- Quillraven/SimpleKtxGame: The LibGDX simple game with Kotlin and LibKTX using an assetmanager, pool and viewport
- libGDX学习记录(三)接水滴_JS O_O的博客-CSDN博客
Libgdx游戏开发(2)——接水滴游戏实现的更多相关文章
- 【读书笔记《Android游戏编程之从零开始》】10.游戏开发基础(View 游戏框架)
对于玩家来说,游戏是动态的:对于游戏开发人员来说,游戏是静态的,只是不停地播放不通的画面,让玩家看到了动态的效果. 进入Android之前,首先要熟悉三个重要的类:View(视图).Canvas(画布 ...
- C#游戏开发中快速的游戏循环
C#游戏开发中快速的游戏循环的实现.参考<精通C#游戏编程>一书. using System; using System.Collections.Generic; using System ...
- Unity 2D游戏开发教程之2D游戏的运行效果
Unity 2D游戏开发教程之2D游戏的运行效果 2D游戏的运行效果 本章前前后后使用了很多节的篇幅,到底实现了怎样的一个游戏运行效果呢?或者说,游戏中的精灵会不会如我们所想的那样运行呢?关于这些疑问 ...
- Unity 2D游戏开发教程之为游戏场景添加多个地面
Unity 2D游戏开发教程之为游戏场景添加多个地面 为游戏场景添加多个地面 显然,只有一个地面的游戏场景太小了,根本不够精灵四处活动的.那么,本节就来介绍一种简单的方法,可以为游戏场景添加多个地面. ...
- 【Cocos2d-x游戏开发】浅谈游戏中的坐标系
无论是开发2D还是开发3D游戏,首先必须弄清楚坐标系的概念.在Cocos2d-x中,需要了解的有OpenGL坐标系.世界坐标系和节点坐标系. 1.UI坐标系 IOS/Android/Windows ...
- 【读书笔记《Android游戏编程之从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)
1. SurfaceView 游戏框架实例 实例效果:就是屏幕上的文本跟着点击的地方移动,效果图如下: 步骤: 新建项目“GameSurfaceView”,首先自定义一个类"MySurfac ...
- OUYA游戏开发核心技术剖析OUYA游戏入门示例——StarterKit
第1章 OUYA游戏入门示例——StarterKit StarterKit是一个多场景的游戏示例,也是OUYA官方推荐给入门开发者分析的第一个完整游戏示例.本章会对StarterKit做详细介绍,包 ...
- 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型&障碍物判定
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...
- 《MFC游戏开发》笔记九 游戏中的碰撞判定初步&怪物运动简单AI
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9374935 作者:七十一雾央 新浪微博:http:// ...
随机推荐
- treap(大根堆)模板
大根堆与小根堆性质相比简单很多,不用加特判 直接上代码: //treap(大根堆性质) #include<bits/stdc++.h> #define rint register int ...
- JS基础小练习
入职薪水10K,每年涨幅入职薪水的5%,50年后工资多少? var sum = 10000; console.log(sum * (1 + 0.05 * 50)); 为抵抗洪水,战士连续作战89小时, ...
- linux rz上传失败
最近rz上传文件时出现了一次文件上传失败的情况,故搜集了以下资料加强学习 rz -ary --o-sync -a 表示使用ascii码格式传输文件,如果是Dos格式的文件,会转换为unix格式 -r ...
- SkiaSharp 之 WPF 自绘 投篮小游戏(案例版)
此案例主要是针对光线投影法碰撞检测功能的示例,顺便做成了一个小游戏,很简单,但是,效果却很不错. 投篮小游戏 规则,点击投篮目标点,就会有一个球沿着相关抛物线,然后,判断是否进入篮子里,其实就是一个矩 ...
- Spherical类定义和实现
此类是一个全景摄像机视角,书上介绍了详细原理.直接给实现代码. 类声明: #pragma once #ifndef __SPHERICAL_HEADER__ #define __SPHERICAL_H ...
- Radmin自动登录器 v3.0
Radmin自动登录器 v3.0 - By: ybmj@vip.163.com 20150615 用户手册 下载地址:https://download.csdn.net/download/shuren ...
- ACM模式细节
牛客网的ACM模式需要自己写输入输出,在这里简单记录一下: 基本答题框架: import java.util.*; public class Main{ public static void main ...
- HCIA-Datacom 3.4 实验四:实现VLAN间通信实验
实验介绍: 划分VLAN后,不同VLAN的用户间不能二层互访,这样能起到隔离广播的作用.但实际应用中,不同VLAN的用户又常有互访的需求,此时就需要实现不同VLAN的用户互访,简称VLAN间互访.华为 ...
- Excel 统计函数(六):RANK
[语法]RANK(number,ref,[order]) [参数] number:要找到其排位的数字. ref:数字列表的数组,对数字列表的引用.Ref 中的非数字值会被忽略. order:一个指定数 ...
- springBoot项目实现发送邮件功能
需要的依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...