📜 ⬆️ ⬇️

LibGDX + Scene2d (we program on Kotlin). Part 0

And hello again! I want to share, I had a great weekend! For a day and a half, I was thinking about the option of presenting the material, sawing the mock-up and generally trying my best to do well. What is good in the context of the teaching material? In my opinion, this is “interestingness”, brevity, correctness and clarity. For me personally, writing such an article is a feat. But a series of articles - just capacious and responsible task. We will explore Scene2d in the process of writing a game from scratch! The process of our creativity will stretch for a long ten to twelve days. I want to believe that the frequency of materials will be about once a day. For me personally, this is a very ambitious task, because it requires not so much to program, but also to describe in articles with detailed analysis. I am not a supporter of rushing into the raging ocean, hoping to learn how to swim. We jump into a puddle and will consistently deepen and expand it. So we begin.

I strongly advise you to start developing any program by drawing up a product card. From mandatory - goal. I make a product card in Google Docs and this is how the card looks in our case.

Medieval Tycoon


Project Goals


  1. Demonstration of the game development process for the site habrahabr.ru (public, informational, short-term)
  2. Creating materials that can later be used as educational materials (personal, reputation, long-term; public, informational, long-term)
  3. Creating the basis for a commercial game (personal, short-term)
  4. Attracting downloads from Google Play (personal, financial, long-term)

First of all, we try to deal with our motivation as honestly as possible. Why do we even get into this case. What will serve us as a guiding star when everything is good and kick ass when everything is bad. Please try to avoid altruism and “peace in the world.” The human psyche is designed in such a way that personal motivations mean more than social ones (I do not consider visionary psychology here).

Game world


Middle Ages / Fantasy
The goal of the game is a lot of money
Resource collection
Sale
')

Description of the game


Since the first and main goal of this development is to demonstrate the work, and the game world, and the description of the gameplay will be simplified to please the brevity and clarity of the presentation of the material.

The player collects resources and sells them locally. Food / energy is spent on searching and collecting resources. When the energy is over, it must be bought in the city.

Interface prototype


example


Despite the fact that in the previous article I recommended the notebook and pencil as tools for prototyping, this layout was made in Adobe Experience Design CC (Beta). At the time of publication of the article, it can be downloaded for free. I spent a day and a half on working with him, but I think this is justified. The fact is that the publication on Habré is a group work, even if I do everything alone. The more quality reference materials I provide, the easier it will be to perceive the information. Here is the Adobe Experience Design project file . It can be downloaded, launched in the presentation mode, and even poke a little on the buttons. Technically, you can file a separate article, but I don’t know if this is necessary. Comments will judge.

Well, what kind of development without a public repository? Here is the link .

To work, we need Android Studio 3.0 (Canary 5 version is currently available), Android SKD and LibGDX . I’ll skip the installation of all these vagaries, here are all the big boys and girls. In extreme cases, there are comments.

Launching the LibGDX configuration wizard occurs from the command line:

java -jar gdx-setup.jar 

Project parameters


Who was not aware, LibGDX is a cross-platform framework that allows you to write simultaneously for PC, Android, iOS and even HTML (for the latter, GWT is used, and we have Kotlin, so HTML does not threaten us). I chose two extensions:

Freetype - allows to generate raster fonts from ttf / otf
Tools - among other things, allows you to generate texture atlases

A commit with the resulting project is available in the repository. I tried to chop up and name commits in such a way that it was easy to understand which fragment was responsible for what. Since LibGDX is cross-platform, I prefer to spend most of the development on a PC and test / correct errors under Android just before release. As a rule, this work takes no more than 2-3 hours.

Further in this article



Launching a project through DesktopLauncher


Configuration


Please note that the working folder for DesktopLauncher is located in android / assets. Launch DesktopLauncher on commit:

initial commit after libgdx wizard


Please note that even the newly configured project does not run under android. We will fix this in the next step.

Transfer of the project to Kotlin


LibGDX projects are configured as a multimodular gradle. There is a project build.gradle and modular build.gradle for core, android and desktop. Almost all the code we will write in core. In the android project, later we will have the AdMob + configuration immersive mode + shopping on the Google Play market.

To translate the project from java to kotlin, we change all apply plugin: “java” to apply plugin: “kotlin”. In android / build.gradle add the apply plugin: 'kotlin-android'. The biggest changes occurred in the project build.gradle

build.gradle
  mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } jcenter() + + maven { url 'https://maven.google.com' } } + + ext.kotlin_version = '1.1.3' + dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' - - + // uncomment for desktop version + // classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:3.0.0-alpha5' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -37,7 +43,7 @@ } project(":desktop") { - apply plugin: "java" + apply plugin: "kotlin" dependencies { @@ -74,13 +80,13 @@ } project(":core") { - apply plugin: "java" + apply plugin: "kotlin" dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" - + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" } } 


A google repository was added, kotlin-gradle-plugin was added to buildscript.dependencies and the compile-dependency kotlin-stdlib was added to the core project (in our case, kotlin-stdlib-jre8).

This version works on android, but does not work in the desktop version due to the error of Android Studio 3.0 Canary 5. Why I think this is the reason - launching the gradle target desktop-run does launch the application (although it requires running Android device / emulator to launch android: run). But launching from Android Studio throws Exception in thread "main" java.lang.NoClassDefFoundError: kotlin / jvm / internal / Intrinsics. If anyone can beat the launch of DesktopLauncher with a fresh gradle version - let me know please!

Translation of java files into kt is elementary - select the file / folder and press Ctrl + Alt + Shitf + K. The only mistake you will have after this operation is to require Kotlin to initialize the property at the moment of its definition:

java
 public class MedievalTycoonGame extends ApplicationAdapter { SpriteBatch batch; Texture img; @Override public void create () { batch = new SpriteBatch(); img = new Texture("badlogic.jpg"); } @Override public void render () { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(img, 0, 0); batch.end(); } @Override public void dispose () { batch.dispose(); img.dispose(); } } 


kotlin
 class MedievalTycoonGame : ApplicationAdapter() { internal var batch: SpriteBatch //   <-    private lateinit var batch: SpriteBatch internal var img: Texture //   <-    private lateinit var img: Texture override fun create() { batch = SpriteBatch() img = Texture("badlogic.jpg") } override fun render() { Gdx.gl.glClearColor(1f, 0f, 0f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() batch.draw(img, 0f, 0f) batch.end() } override fun dispose() { batch.dispose() img.dispose() } } 


internal = package visibility in java. We do not need packet visibility (and in general after a couple of commits we delete these fields). Not in all cases we can initialize the field at once, but to make it nullable is generally nonsense (we are interested in a cotlin just because of null-safety). For this, kotlin has a lateinit modifier that tells the compiler what the tooth programmer gives, at the time of using this field it will not be null. This is the so-called syntactic salt. In general, if you look at this code not as a result of an automatic conversion, then it would be more appropriate to look:

 private val batch = SpriteBatch() private val img = Texture("badlogic.jpg") 

Kotlin not configured error


You will see this error every time you launch Android Studio. Just click to sync gradle:



Configuration gradle for kotlin-desktop version


As I already said, I prefer to develop a desktop version of the application, and by changing a couple of lines we reanimate this mode. All you need is to specify in the project build.gradle
classpath 'com.android.tools.build:gradle:2.3.2', and in gradle-wrapper.properties version gradle-3.3-all.zip

Configuration portrait for desktop version


Add a handful of configuration options to DesktopLauncher. Three are window size and resizable. The fourth parameter vSync is disabled. there is a glitch, on some video cards in the desktop and only on config.foregroundFPS = 60 (by default), it loads one processor core at 100%.

  config.width = 576 config.height = 1024 config.resizable = false config.vSyncEnabled = false 

First use of Scene2D. Boot screen boot scene


So we got to the first use of Scene2D. Briefly a few words for what it is intended and what you can want from it.

Scene2D is a graph (tree) of elements and is intended primarily to create a UI. Directly "out of the box", you get the possibility of layout, transformation of elements (rotation, scale, shift, etc.). A huge plus is the touch processing. Well, the cherry on the cake is a system of actions. With an incomprehensible definition finished, now the same human language.

There is a scene, it takes up the whole screen. On the stage you can place a table, a picture in the table, a scroll bar, a dozen buttons and even a bald dash (the main thing is that it was Actor in the shower). With the help of the magic words top / center / left / width, etc. you implement the layout. An example is more complicated than hello world will be only tomorrow, and so the article turned out to be a big one. Then you hang up a touch handler on any arbitrary element and it works. You do not need to manually catch the coordinates of the click, check what is there, what is on the z-index objects, etc. But once again, about it tomorrow. And today, just a few code fragments at last:

 class MedievalTycoonGame : Game() { val viewport: FitViewport = FitViewport(AppConstants.APP_WIDTH, AppConstants.APP_HEIGHT) override fun create() { screen = LoadingScreen(viewport) } } 

Our class MedievalTycoonGame is now inherited from Game, the whole task of which is to shift the work to Screen. The first screen, which we will now show the user will be called LoadingScreen and will contain one scene - LoadingStage. Since these classes are not supposed to grow, I will place them in a single file LoadingScreen.kt

LoadingScreen.kt
 class LoadingScreen(val viewport: Viewport) : ScreenAdapter() { private val loadingStage = LoadingStage(viewport) override fun render(delta: Float) { Gdx.gl.glClearColor(0f, 0f, 0f, 0f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) loadingStage.act() loadingStage.draw() } override fun resize(width: Int, height: Int) { viewport.update(width, height) } } class LoadingStage(viewport: Viewport) : Stage(viewport) { init { val backgroundImage = Image(Texture("backgrounds/loading-logo.png")) addActor(backgroundImage.apply { setFillParent(true) setScaling(Scaling.fill) }) } } 


All that makes LoadingScreen overwrites the screen with black and calls the methods act () and draw () from the LoadingStage. On act () it is very convenient to hang the logic of the program, work with data. Draw () is just a drawing of all the elements in the scene.

The only thing I want to add is how the initialization of the java vs kotlin scene looks.

  init { val backgroundImage = Image(Texture("backgrounds/loading-logo.png")) addActor(backgroundImage.apply { setFillParent(true) setScaling(Scaling.fill) }) } 

  public LoadingStage() { Image backgroundImage = new Image(new Texture("backgrounds/loading-logo.png")); backgroundImage.setFillParent(true); backgroundImage.setScaling(Scaling.fill); addActor(backgroundImage); } 

In the case of kotlin, we always initialize the element and place it on adjacent lines. This is achieved by the apply extension function. The kotlin hierarchy automatically indents and is visually very easy to read. In java, all layout is indented. Initialization of an element and its placement is often not possible nearby. If the hierarchy consists of 3+ depth levels, it is impossible to arrange the elements nicely (and cheaply in support) in java.

That's all for today. Consider this article as an introductory, the actual disclosure of Scene2D and the implementation of the game will be tomorrow and beyond. Thank you for being with us;) And not for the sake of advertising (in this application, you need to make an effort to see the advertisement), my first project “Fifteen” on Scene2D when I was just learning. Of the advantages - ease of management. There are hundreds if not thousands of versions of the application and in 90% that I saw the movement of chips only by poking into the next to an empty cell. Try to collect a cat .

screen



In the following articles



PS In the boot screen used the work of the artist Vitaly Samarin aka Vitaly S Alexius.

Upd:
reference to the sources of Fifteen

Immediately I warn you, the code is scary. So we will not write with you. The picture parses on the fly, i.e. You can correct the code so that the image is sucked by the URL. The project is commited to run under Android. What you need to do to run the desktop version you should already know after reading the article.

Source: https://habr.com/ru/post/332144/


All Articles