Slide 1

Slide 1 text

Building Android Games using LibGDX Jussi Pohjolainen

Slide 2

Slide 2 text

INTRO TO LIBGDX

Slide 3

Slide 3 text

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..

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

PROJECT STRUCTURE

Slide 7

Slide 7 text

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/

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Gradle Tasks • What can you do with the project? – gradle tasks • Will output something like – run – build – clean – ...

Slide 12

Slide 12 text

Compiling and Running gradle build gradle run

Slide 13

Slide 13 text

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!

Slide 14

Slide 14 text

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!

Slide 15

Slide 15 text

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; ^

Slide 16

Slide 16 text

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' }

Slide 17

Slide 17 text

LIBGDX PROJECT

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

STARTER CLASSES

Slide 24

Slide 24 text

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 …

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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/

Slide 30

Slide 30 text

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); } }

Slide 31

Slide 31 text

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"); } } }

Slide 32

Slide 32 text

Android Manifest

Slide 33

Slide 33 text

Android Permissions • Add permissions if your android app requires certain functionality – – – • Add these to manifest file • See – http://developer.android.com/guide/topics/manifest/manifest- intro.html#perms

Slide 34

Slide 34 text

APP LIFE CYCLE AND LOGGING

Slide 35

Slide 35 text

App Lifecycle

Slide 36

Slide 36 text

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; } }

Slide 37

Slide 37 text

Logging in Android • Log output should be visible in IntelliJ IDEA • If not, use in command line (adb must be in path) – adb logcat

Slide 38

Slide 38 text

OTHER MODULES AND ASSETS

Slide 39

Slide 39 text

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);

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Javadoc: Gdx Gdx.audio (Interface) is a reference to the backend implementation that has been instantiated on application startup by the Application instance.

Slide 42

Slide 42 text

Javadoc: Audio

Slide 43

Slide 43 text

Querying • Application (Gdx.app) interface provides various methods to query properties – if(Gdx.app.getType() == Android) { … }

Slide 44

Slide 44 text

Assets • Drag sounds, images to android/assets folder – Desktop app has a link to that folder

Slide 45

Slide 45 text

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(); }

Slide 46

Slide 46 text

CAMERA AND GRAPHICS

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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);

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

SpriteBatch • SpriteBatch class takes care of texture mapping • Convenience class which makes drawing onto the screen easy

Slide 52

Slide 52 text

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(); }

Slide 53

Slide 53 text

@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(); }

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

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(); }

Slide 56

Slide 56 text

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!

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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); }

Slide 59

Slide 59 text

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()

Slide 60

Slide 60 text

Collision • Simple collision detection is done using overlaps method of Rectangle • Create rectangle for each texture! – if(rect1.overlaps(rect2)) { .. }

Slide 61

Slide 61 text

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(); }

Slide 62

Slide 62 text

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); } }

Slide 63

Slide 63 text

FPS AND DELTATIME

Slide 64

Slide 64 text

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()

Slide 65

Slide 65 text

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!

Slide 66

Slide 66 text

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 )

Slide 67

Slide 67 text

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(); }

Slide 68

Slide 68 text

TIPS FOR CREATING A GAME

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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