Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Building Android Games using LibGDX

Introduction to Building Android Games using LibGDX

Some introduction stuff about LibGDX.

D9e65f4b0af059ae9ba243c8c2265e4f?s=128

Jussi Pohjolainen

January 26, 2021
Tweet

Transcript

  1. Building Android Games using LibGDX Jussi Pohjolainen

  2. INTRO TO LIBGDX

  3. Possibilities (Android) • Streaming music, sound effects – Wav, mp3,

    ogg • Audio Recording support • Accelerometer, Compass, Gestures (taps, pinch zooming) • High-level 2D APIs – Sprites, Textures, Fonts, Tiles, UI - library • High-level 3D APIs • Utilities: JSON, XML, File I/O (preferences), Math..
  4. Cross-platform! • Desktop target uses LWJGL – Lightweight Java Game

    Library • Android target uses Android SDK • HTML5 uses Google Web Toolkit – Java -> JavaScript • iOS uses RoboVM – Java -> Objective-C
  5. None
  6. PROJECT STRUCTURE

  7. Gradle • Gradle is a build automation tool for multi-language

    software development • Gradle was designed for multi-project builds, which can grow to be large • Download – https://gradle.org/install/
  8. Example • Easiest way to create gradle project is to

    give command – gradle init • This will ask you many questions about programming language, type of project etc.
  9. gradle init Starting a Gradle Daemon (subsequent builds will be

    faster) Select type of project to generate: 1: basic 2: application 3: library 4: Gradle plugin Enter selection (default: basic) [1..4] 2 Select implementation language: 1: C++ 2: Groovy 3: Java 4: Kotlin 5: Scala 6: Swift Enter selection (default: Java) [1..6] 3 Split functionality across multiple subprojects?: 1: no - only one application project 2: yes - application and library projects Enter selection (default: no - only one application project) [1..2] 1 Select build script DSL: 1: Groovy 2: Kotlin Enter selection (default: Groovy) [1..2] 1 Select test framework: 1: JUnit 4 2: TestNG 3: Spock 4: JUnit Jupiter Enter selection (default: JUnit 4) [1..4] 1 Project name (default: myproject): Source package (default: myproject): > Task :init Get more help with your project: https://docs.gradle.org/6.7.1/samples/sample_building_java_applications.html BUILD SUCCESSFUL in 24s 2 actionable tasks: 2 executed
  10. Basic Project Structure . ├── app │ ├── build.gradle │

    └── src │ ├── main │ │ ├── java │ │ │ └── myproject │ │ │ └── App.java │ │ └── resources │ └── test │ ├── java │ │ └── myproject │ │ └── AppTest.java │ └── resources ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle Source Code Gradle wrapper, the one compiling and running apps do not have to have gradle installed
  11. Gradle Tasks • What can you do with the project?

    – gradle tasks • Will output something like – run – build – clean – ...
  12. Compiling and Running gradle build gradle run

  13. build.gradle plugins { // Apply the application plugin to add

    support for building a CLI application in Java. id 'application' } repositories { // Use JCenter for resolving dependencies. jcenter() } dependencies { // Use JUnit test framework. testImplementation 'junit:junit:4.13' // This dependency is used by the application. implementation 'com.google.guava:guava:29.0-jre' } application { // Define the main class for the application. mainClass = 'myproject.App' } gradle must know which class is the main class Dependencies!
  14. Modification package myproject; import org.apache.commons.io.FileUtils; public class App { public

    static void main(String args[]) throws Exception { FileUtils.getFile("input.txt"); } } Uses external class library!
  15. Compiling > Task :app:compileJava FAILED /Users/pohjus/myproject/app/src/main/java/myproject/A pp.java:3: error: package org.apache.commons.io

    does not exist import org.apache.commons.io.FileUtils; ^
  16. Modify build.gradle dependencies { // Use JUnit test framework. testImplementation

    'junit:junit:4.13' // This dependency is used by the application. implementation 'com.google.guava:guava:29.0-jre' implementation 'commons-io:commons-io:2.8.0' }
  17. LIBGDX PROJECT

  18. None
  19. libGDX Project • LibGDX project contains other projects • Subprojects

    for each target – android/ – desktop/ – gwt/ – ios/ – core/ • One Core subproject for the actual logic for the game, target projects contains only launcher classes
  20. Compiling • LibGDX uses Gradle for compiling • Gradle uses

    domain-specific language (DSL) to define targets (android/ios...) and dependencies • When compiling, gradle reads build.gradle file that contains DSL that describes all the necessary information how to compile
  21. Workflow • Create libGDX project using gdx-setup.jar • Import the

    project to IntelliJ IDEA or Eclipse • Implement your code in the core/ submodule • Test in Android Device or in Desktop, forget the emulator
  22. Using libGDX in commandline • Compiling – ./gradlew desktop:build –

    ./gradlew android:build • Running – ./gradlew desktop:run – ./gradlew android:installDebug android:run • Packaging – ./gradlew desktop:dist – ./gradlew android:assembleRelease • Clean – ./gradlew clean
  23. STARTER CLASSES

  24. App Framework: Application • Application – main entry point for

    your app – http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/g dx/Application.html • Equivalent to JFrame (Swing) or Activity (Android) • Informs your game about events such as window resizing • Developer creates a class that implements ApplicationListener, methods are called by Application – Application can be GwtApplication, IOSApplication …
  25. None
  26. None
  27. None
  28. About Starter Classes • For each platform (iOS, Android, Desktop

    ..) a starter class must be written • Starter classes are platform dependent • We will focus on – Desktop (LWJGL) – Android
  29. LWJGL? • Lightweight Java Game Library (LWJGL) framework for creating

    games with Java • libGDX is built on top of LWJGL • See more: – http://www.lwjgl.org/
  30. Starter Classes: Desktop // This is platform specific: Java SE

    public class DesktopStarter { public static void main(String[] argv) { LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.title = ".."; config.width = 480; config.heigth = 320; new LwjglApplication(new MyGame(), config); } }
  31. Starter Classes: Android import android.os.Bundle; import com.badlogic.gdx.backends.android.AndroidApplication; import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; //

    This is platform specific: Android // No main public class AndroidLauncher extends AndroidApplication { @Override protected void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); MyGame game = new MyGame(); initialize(game, config); if(this.getApplicationListener() == game) { this.log("test", "success"); } } }
  32. Android Manifest <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fi.tuni.pohjolainen.mygame" > <application

    android:allowBackup="true" android:icon="@drawable/ic_launcher" android:isGame="true" android:appCategory="game" android:label="@string/app_name" android:theme="@style/GdxTheme" > <activity android:name="fi.tuni.pohjolainen.mygame.AndroidLauncher" android:label="@string/app_name" android:screenOrientation="landscape" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
  33. Android Permissions • Add permissions if your android app requires

    certain functionality – <uses-permission android:name="android.permission.RECORD_AUDIO"/> – <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> – <uses-permission android:name="android.permission.VIBRATE"/> • Add these to manifest file • See – http://developer.android.com/guide/topics/manifest/manifest- intro.html#perms
  34. APP LIFE CYCLE AND LOGGING

  35. App Lifecycle

  36. Testing public class MunHienoPeli implements ApplicationListener { private boolean gameIsOn;

    @Override public void create () { // Load resources gameIsOn = true; Gdx.app.log("MunHienoPeli", "create"); } @Override public void resize(int width, int height) { // Reposition UI elements Gdx.app.log("MunHienoPeli", "resize"); } @Override public void render () { // Update and render game elements if(gameIsOn) { Gdx.app.log("MunHienoPeli", "render"); } } @Override public void pause() { // Game loses focus // -> home button pressed // -> incoming call Gdx.app.log("MunHienoPeli", "pause"); gameIsOn = false; } @Override public void resume() { // Restore game after pause Gdx.app.log("MunHienoPeli", "resume"); gameIsOn = true; } @Override public void dispose() { // Free resources Gdx.app.log("MunHienoPeli", "dispose"); gameIsOn = false; } }
  37. Logging in Android • Log output should be visible in

    IntelliJ IDEA • If not, use in command line (adb must be in path) – adb logcat
  38. OTHER MODULES AND ASSETS

  39. Accessing Modules • All modules (previous slide) can be accessed

    via static fields of Gdx – class – Global public variables for easy access • Example – AudioDevice audioDevice = Gdx.audio.newAudioDevice(44100, false);
  40. Other important Modules (Interfaces) • Application – Informs your game

    about events • Files – Exposes the underlying file system • Input – Mouse, keyboard, touch, accelerometer • Net* – Access HTTP(S) • Audio – Enables sound • Graphics – Exposes OpenGL ES 2.0 where available
  41. Javadoc: Gdx Gdx.audio (Interface) is a reference to the backend

    implementation that has been instantiated on application startup by the Application instance.
  42. Javadoc: Audio

  43. Querying • Application (Gdx.app) interface provides various methods to query

    properties – if(Gdx.app.getType() == Android) { … }
  44. Assets • Drag sounds, images to android/assets folder – Desktop

    app has a link to that folder
  45. Asset loading public class TestGame implements ApplicationListener { private Texture

    gorbaTexture; private Sound soundEffect; private Music backgroundMusic; private SpriteBatch batch; @Override public void create() { // gorba.png uploaded to GPU and is ready to be used by OpenGL. Image format // must be .jpg, .png, .bmp gorbaTexture = new Texture(Gdx.files.internal("gorba.png")); // Stored in RAM soundEffect = Gdx.audio.newSound(Gdx.files.internal("beep.wav")); // Streamed from wherever it’s stored backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("soviet-anthem.mp3")); // start the playback of the background music immediately backgroundMusic.setLooping(true); backgroundMusic.play(); batch = new SpriteBatch(); } @Override public void dispose() { gorbaTexture.dispose(); soundEffect.dispose(); backgroundMusic.dispose(); }
  46. CAMERA AND GRAPHICS

  47. Rendering: Camera • Camera is like “virtual window into our

    world” – What part of the “world” is visible? • World may be bigger than visible area • Camera – OrthographicCamera • When the human eye looks at a scene, objects in the distance appear smaller than objects close by. Orthographic projection ignores this effect – PerspectiveCamera • Closer objects appear bigger in PerspectiveCamera
  48. Using Camera private OrthographicCamera camera; @Override public void create() {

    // When running in Desktop, the window size is // 800 x 400 pixels: // config.width = 800; // config.height = 400; // But window size may change (resize)! Or we could run the same // game in android and in ios device. The screen resolution and pixel density // varies! // It's easier to forget the "real resolution" and use "world resolution". // The world resolution is the same regardless of real resolution. // Most important thing about world resolution is the aspect ration. It // does not matter if the world resolution is 1000 x 500 or 10 x 5 as long // as you are comfortable with the aspect ratio. // If dealing with a game using Box2D it's recommendation that you use // world resolution defined in meters. So we could have a world size // 10 meters x 5 meters. // When the world width and height is set, we need to // configure what part of the world the camera is filming. // By using false, we simple state that y-axis is pointing // up and camera is centered to width / 2 and height / 2 camera = new OrthographicCamera(); camera.setToOrtho(false, 10, 5);
  49. Word about OpenGL • OpenGL (Open Graphics Library) is a

    cross-language, multi- platform application programming interface (API) for rendering 2D and 3D vector graphics. • The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering. • Widely used in CAD, virtual reality, scientific visualization, information visualization, flight simulation, and video games. • libGDX uses OpenGL ES and has interface also for direct access for OpenGL
  50. Texture Mapping • A Texture – class wraps a standard

    OpenGL ES texture. – A texture is an OpenGL Object that contains one or more images that all have the same image format. • Image loaded into the GPU’s memory in raw format • Texture mapping is process of working out where in space the texture will be applied – “To stick a poster on a wall, one needs to figure out where on the wall he will be gluing the corners of the paper” – Space ó Wall – Mesh (Rectangle) ó Paper – Image on paper ó Texture
  51. SpriteBatch • SpriteBatch class takes care of texture mapping •

    Convenience class which makes drawing onto the screen easy
  52. Creating SpriteBatch public class TestGame implements ApplicationListener { private Texture

    gorbaTexture; private Sound soundEffect; private Music backgroundMusic; private SpriteBatch batch; @Override public void create() { gorbaTexture = new Texture(Gdx.files.internal("gorba.png")); soundEffect = Gdx.audio.newSound(Gdx.files.internal("beep.wav")); backgroundMusic = Gdx.audio.newMusic(Gdx.files.internal("soviet-anthem.mp3")); backgroundMusic.setLooping(true); backgroundMusic.play(); batch = new SpriteBatch(); }
  53. @Override public void render() { // Spritebatch uses coordinates specified

    by the camera! batch.setProjectionMatrix(camera.combined); Gdx.gl.glClearColor(1f, 0f, 0f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); float x = 0.0f; // meters! float y = 0.0f; // meters! float gorbaWidth = 0.74f; // meters! float gorbaHeight = 0.86f; // meters! batch.draw(gorbaTexture, x, y, gorbaWidth, gorbaHeight); batch.draw(gorbaTexture, x + gorbaWidth, y, gorbaWidth, gorbaHeight); batch.end(); }
  54. None
  55. Clear Screen public void render() { // Direct OpenGL call

    // float red [0,1] // green // blue // alpha // https://www.opengl.org/sdk/docs/man/html/glClearColor.xhtml Gdx.gl.glClearColor(0, 0, 0.2f, 1); // Clear the screen with the color chosen // http://www.opengl.org/sdk/docs/man/html/glClear.xhtml Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // SpriteBatch is ready for commands batch.begin(); .... // No commands anymore, proceed to process the batch of commands // received batch.end(); }
  56. About Resolution • Game can be cross-platform, what is the

    resolution? • We have two resolutions – The real resolution – The world resolution • Mapping must be done between these two!
  57. Real resolution 0, 0 World resolution 0, 5 Real resolution

    0, 400 World resolution 0, 0 Window resolution 800 x 400 World resolution set to 10 x 5 Real resolution 800, 0 World resolution 10, 5 Real resolution 800, 400 World resolution 10, 0
  58. Conversion between Real and World - Resolutions public void render()

    { // Spritebatch uses coordinates specified by the camera! batch.setProjectionMatrix(camera.combined); if(Gdx.input.isTouched()) { int realX = Gdx.input.getX(); int realY = Gdx.input.getY(); // Encapsulated 3D Vector, only 2D is used // Vectors can be used for represent a direction and position // Bad practice to instantiate every render – call! Vector3 touchPos = new Vector3(realX, realY, 0); // Function to translate a point given in screen // coordinates to world space. camera.unproject(touchPos); Gdx.app.log("MyGame", "real X = " + realX); Gdx.app.log("MyGame", "real Y = " + realY); Gdx.app.log("MyGame", "world X = " + touchPos.x); Gdx.app.log("MyGame", "world Y = " + touchPos.y); }
  59. Gdx.input • Getting input from user is very easy •

    Touch – Gdx.input.isTouched() – Gdx.input.getX() – Gdx.input.getY() • Accelerometer – Gdx.input.getAccelerometerX() – Gdx.input.getAccelerometerY() – Gdx.input.getAccelerometerZ()
  60. Collision • Simple collision detection is done using overlaps method

    of Rectangle • Create rectangle for each texture! – if(rect1.overlaps(rect2)) { .. }
  61. class TestGame implements ApplicationListener { private Texture gorbaTexture; private Rectangle

    gorbaRectangle; private OrthographicCamera camera; private SpriteBatch batch; @Override public void create() { camera = new OrthographicCamera(); camera.setToOrtho(false, 10, 5); gorbaTexture = new Texture(Gdx.files.internal("gorba.png")); gorbaRectangle = new Rectangle(0, 0, gorbaTexture.getWidth() / 100.0f, // 84 pixels -> 0.84 meters gorbaTexture.getHeight() / 100.0f); // 74 pixels -> 0.76 meters batch = new SpriteBatch(); } @Override public void render() { batch.setProjectionMatrix(camera.combined); Gdx.gl.glClearColor(1f, 0f, 0f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); if(Gdx.input.isTouched()) { int realX = Gdx.input.getX(); int realY = Gdx.input.getY(); Vector3 touchPos = new Vector3(realX, realY, 0); camera.unproject(touchPos); gorbaRectangle.x = touchPos.x; gorbaRectangle.y = touchPos.y; } batch.draw(gorbaTexture, gorbaRectangle.x, gorbaRectangle.y, gorbaRectangle.width, gorbaRectangle.height); batch.end(); }
  62. public class TestGame implements ApplicationListener { private Texture gorbaTexture; private

    Rectangle gorbaRectangle; private Texture phoneTexture; private Rectangle phoneRectangle; @Override public void create() { ... gorbaTexture = new Texture(Gdx.files.internal("gorba.png")); gorbaRectangle = new Rectangle(0, 0, gorbaTexture.getWidth() / 100.0f, // 84 pixels -> 0.84 meters gorbaTexture.getHeight() / 100.0f); // 74 pixels -> 0.76 meters phoneTexture = new Texture(Gdx.files.internal("phone.png")); phoneRectangle = new Rectangle(10.0f / 2.0f, 5.0f / 2.0f, phoneTexture.getWidth() / 100.0f, phoneTexture.getHeight() / 100.0f); } @Override public void render() { ... if(Gdx.input.isTouched()) { int realX = Gdx.input.getX(); int realY = Gdx.input.getY(); Vector3 touchPos = new Vector3(realX, realY, 0); camera.unproject(touchPos); gorbaRectangle.x = touchPos.x; gorbaRectangle.y = touchPos.y; } batch.draw(gorbaTexture, gorbaRectangle.x, gorbaRectangle.y, gorbaRectangle.width, gorbaRectangle.height); batch.draw(phoneTexture, phoneRectangle.x, phoneRectangle.y, phoneRectangle.width, phoneRectangle.height); if(phoneRectangle.overlaps(gorbaRectangle)) { soundEffect.play(); phoneRectangle.x = MathUtils.random(0f, 10f); phoneRectangle.y = MathUtils.random(0f, 5f); } }
  63. FPS AND DELTATIME

  64. FPS? • Frame rate, also known frames per second (FPS),

    rate at which an imaging device produces unique consecutive images called frames • LibGDX tries to call the render() method as fast as possible • When it reaches 60 fps, it stops, no need to update any faster • You can query the FPS: – Gdx.graphics.getFramesPerSecond()
  65. Moving Game Objects • Problem? – if(left arrow is pressed)

    { x = x + speed ) • If this is called 60 times per second, the object moves fast • If this is called 10 times per second, the object moves slow! • Now your game is implemented so that the game speed varies in different mobile devices!
  66. Delta time • Delta time – elapsed time since the

    last update in millisecs – If fps is 60, delta time is 1 / 60 => 0.016 – If fps is 30, delta time is 1 / 30 => 0.033 • To get the delta time – Gdx.graphics.getDeltaTime() • Now use: • if(left arrow is pressed) { x = x + speed * delta )
  67. Example final int GORBASPEED = 5; if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)) { gorbaRectangle.x =

    gorbaRectangle.x + GORBASPEED * Gdx.graphics.getDeltaTime(); } if(Gdx.input.isKeyPressed(Input.Keys.LEFT)) { gorbaRectangle.x = gorbaRectangle.x - GORBASPEED * Gdx.graphics.getDeltaTime(); } if(Gdx.input.isKeyPressed(Input.Keys.UP)) { gorbaRectangle.y = gorbaRectangle.y + GORBASPEED * Gdx.graphics.getDeltaTime(); } if(Gdx.input.isKeyPressed(Input.Keys.DOWN)) { gorbaRectangle.y = gorbaRectangle.y - GORBASPEED * Gdx.graphics.getDeltaTime(); }
  68. TIPS FOR CREATING A GAME

  69. Tips • If you create everything in one class, it's

    going to be crowded... • You could create class for each game object • For example – AlienGameObject, BulletGameObject, SpaceShipGameObject
  70. GameObject • Game object may hold – Texture texture; –

    Rectangle rectangle; – float speedX; – float speedY; • If all of your game objects hold common features, you could use inheritance – class Spaceship extends GameObject – class Alien extends GameObject • In fact we already have the "GameObject" in LibGDX, it's called Sprite