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.

Jussi Pohjolainen

January 26, 2021
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. 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..
  2. 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
  3. 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/
  4. 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.
  5. 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
  6. 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
  7. Gradle Tasks • What can you do with the project?

    – gradle tasks • Will output something like – run – build – clean – ...
  8. 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!
  9. 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!
  10. 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' }
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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 …
  16. 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
  17. 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/
  18. 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); } }
  19. 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"); } } }
  20. 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>
  21. 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
  22. 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; } }
  23. Logging in Android • Log output should be visible in

    IntelliJ IDEA • If not, use in command line (adb must be in path) – adb logcat
  24. 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);
  25. 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
  26. Javadoc: Gdx Gdx.audio (Interface) is a reference to the backend

    implementation that has been instantiated on application startup by the Application instance.
  27. Querying • Application (Gdx.app) interface provides various methods to query

    properties – if(Gdx.app.getType() == Android) { … }
  28. 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(); }
  29. 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
  30. 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);
  31. 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
  32. 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
  33. SpriteBatch • SpriteBatch class takes care of texture mapping •

    Convenience class which makes drawing onto the screen easy
  34. 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(); }
  35. @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(); }
  36. 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(); }
  37. 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!
  38. 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
  39. 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); }
  40. 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()
  41. Collision • Simple collision detection is done using overlaps method

    of Rectangle • Create rectangle for each texture! – if(rect1.overlaps(rect2)) { .. }
  42. 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(); }
  43. 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); } }
  44. 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()
  45. 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!
  46. 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 )
  47. 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(); }
  48. 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
  49. 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