Image(Texture("backgrounds/main-screen-background.png"))
then we create excess latency. In this case, the texture “backgrounds / main-screen-background.png” will be read from the drive in synchronous mode. It is not always evil. As a rule, downloading a single background image does not spoil the experience of working with the program. But if we read each element of our scene in this way, the speed and smoothness of the application can seriously sink. object AtlasGenerator { @JvmStatic fun main(args: Array<String>) { val settings = TexturePacker.Settings() settings.maxWidth = 2048 settings.maxHeight = 2048 TexturePacker.process(settings, "images", "atlas", "game") } }
In principle, everything is simple. Parameters: the name of the source folder, the name of the folder for the location of the atlas and the name of the atlas itself. In large applications, it makes sense to make several atlases. For example, the level of "ancient Egypt" - some pictures, the level of "space" - others. At the same time they are not used. Much faster in time to load only the part that is needed at the moment. But in our application graphics will be at least, you can do with one atlas. Loading atlas and reading textures looks like this: val atlas = TextureAtlas(Gdx.files.internal("atlas/game.atlas")) atlas.findRegion("texture-name")
{ "com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle": { "default": { "font": "regular-font" }, "large": { "font": "large-font" }, "small": { "font": "small-font" }, "pane-caption": { "font": "large-font", "fontColor": "color-mongoose" } } }
And here is an example of using the skin Label("some text here", uiSkin, "pane-caption")
val atlas = TextureAtlas(Gdx.files.internal("atlas/game.atlas")) val skin = Skin(atlas) skin.getDrawable("texture-name") skin.get("default", Label.LabelStyle::class.java) Label("some text here", skin , "pane-caption")
row().let { add(Image(Texture("backgrounds/main-screen-background.png")).apply { setScaling(Scaling.fill) }).expand() }
In the center of the window we have placed the picture. Now we want to use this central part as a container. There are two options. Use Container with background or use Stack. A stack is a layout container that draws all its children on top of itself in the order it was added. Element dimensions are always set as Stack dimensions. We will focus on the first option, because The picture is a “stub” again. In the final version, we will use the TiledMapRenderer to draw the map. val centralPanel = Container<WidgetGroup>() row().let { add(centralPanel.apply { background = TextureRegionDrawable(TextureRegion(Texture("backgrounds/main-screen-background.png"))) fill() pad(AppConstants.PADDING * 2) }).expand() }
In this case, we declare the variable CentralPanel outside of row (). Let {...} because we will pass it as a parameter. Such an idea, CommandPanel (panel with buttons below) should not know where it is located and where to insert new elements into the overall scene. Therefore, we pass the centralPanel to the constructor and inside the CommandPanel we hang the handler on the button: class CommandPanel(val centralPanel: Container<WidgetGroup>) : Table() { ... add(Button(uiSkin.getDrawable("command-move")).apply { addListener(object : ChangeListener() { override fun changed(event: ChangeEvent?, actor: Actor?) { when (isChecked) { false -> centralPanel.actor = null true -> centralPanel.actor = ExplorePanel() } } }) })
Since the parameter has the keyword val in the constructor, this final field will be available anywhere in the class. If it were not, then this parameter would be available only in the block init {...}. Instead of if-then I used when (java-switch). it gives better readability. When the button is pressed, ExplorePanel is embedded in the panel; when it is pressed, the center panel is cleared. class ExplorePanel : Table() { init { background = uiSkin.getDrawable("panel-background") pad(AppConstants.PADDING) row().let { add(TerrainPane()) } row().let { add(SearchPane()) } row().let { add(MovePane()) } row().let { add(TownPortalPane()) } row().let { add().expand() // } } }
In this case, ExplorePanel is implemented through a table, but no one bothers to do it through VerticalGroup. This is basically a matter of taste. The bottommost element is the addition of an empty cell with the expand modifier. This cell tries to occupy the maximum space, thereby “springing” the first elements upwards. class TerrainPane : WoodenPane() { init { add(Image(uiSkin.getDrawable("terrain-meadow"))).width(160f).height(160f).top() add(VerticalGroup().apply { space(AppConstants.PADDING) addActor(Label(i18n["terrain.meadow"], uiSkin, "pane-caption")) addActor(HorizontalGroup().apply { space(AppConstants.PADDING) addActor(Image(uiSkin.getDrawable("herbs-01"))) addActor(Image(uiSkin.getDrawable("herbs-unidentified"))) addActor(Image(uiSkin.getDrawable("herbs-unidentified"))) addActor(Image(uiSkin.getDrawable("herbs-unidentified"))) addActor(Image(uiSkin.getDrawable("herbs-unidentified"))) }) }).expandX().fill() } }
For now, do the “razvidet” internationalization (i18n) and just pay attention to the layout. WoodenPane is actually a Table (in fact, Button, which, as I mentioned, is the heir to the Table). It adds two actors. Picture of the area and vertical group. In the vertical group one cell is text, the second cell is a horizontal group of five pictures. The action planes are made in a similar way - Search, Movement and Return to the city. As I already mentioned, we will hang the logic and associate with the data model in the next part.medieval-tycoon.properties
medieval-tycoon_en.properties
medieval-tycoon_ru.properties
... ...
explore.move=
explore.search=
explore.town-portal=
terrain.forest=
terrain.meadow=
terrain.swamp=
val i18n: I18NBundle get() = assets.i18n class MedievalTycoonGame : Game() { lateinit var assets: Assets
class Assets { val i18n: I18NBundle by lazy { manager.get(i18nDescriptor) }
Again, the download goes through the asset manager. The classic I18NBundle boot option looks like this: val i18n = I18NBundle.createBundle(Gdx.files.internal("i18n/fifteen-puzzle"), Locale.getDefault())
Further, instead of text, we simply insert i18n.get (“name.key”) { "com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle": { "pane-caption": { "font": "large-font", "fontColor": "color-mongoose" } } }
The point is not even that LibGDX knows nothing about the color “mongoose”, skins by default do not even know about “black” & “white”. But when creating the skin, we can pass the ObjectMap <String, Any> () parameter into which to put the running colors and the base colors of the application palette. It looks like this: private val skinResources = ObjectMap<String, Any>() private val skinDescriptor = AssetDescriptor("default-ui-skin.json", Skin::class.java, SkinLoader.SkinParameter("atlas/game.atlas", skinResources)) ... loadColors() manager.load(skinDescriptor) ... private fun loadColors() { skinResources.put("color-mongoose", Color.valueOf("BAA083")) skinResources.put("clear", Color.CLEAR) skinResources.put("black", Color.BLACK) skinResources.put("white", Color.WHITE) skinResources.put("light_gray", Color.LIGHT_GRAY) skinResources.put("gray", Color.GRAY) skinResources.put("dark_gray", Color.DARK_GRAY) skinResources.put("blue", Color.BLUE) skinResources.put("navy", Color.NAVY) skinResources.put("royal", Color.ROYAL) skinResources.put("slate", Color.SLATE) skinResources.put("sky", Color.SKY) skinResources.put("cyan", Color.CYAN) skinResources.put("teal", Color.TEAL) skinResources.put("green", Color.GREEN) skinResources.put("chartreuse", Color.CHARTREUSE) skinResources.put("lime", Color.LIME) skinResources.put("forest", Color.FOREST) skinResources.put("olive", Color.OLIVE) skinResources.put("yellow", Color.YELLOW) skinResources.put("gold", Color.GOLD) skinResources.put("goldenrod", Color.GOLDENROD) skinResources.put("orange", Color.ORANGE) skinResources.put("brown", Color.BROWN) skinResources.put("tan", Color.TAN) skinResources.put("firebrick", Color.FIREBRICK) skinResources.put("red", Color.RED) skinResources.put("scarlet", Color.SCARLET) skinResources.put("coral", Color.CORAL) skinResources.put("salmon", Color.SALMON) skinResources.put("pink", Color.PINK) skinResources.put("magenta", Color.MAGENTA) skinResources.put("purple", Color.PURPLE) skinResources.put("violet", Color.VIOLET) skinResources.put("maroon", Color.MAROON) }
uiSkin.add("black", Color.BLACK) uiSkin.load(Gdx.files.internal("uiskin.json"))
color = Color.BLACK // style.fontColor = Color.BLACK //
I do not have enough knowledge to explain the mechanics of rendering. On the fingers it is like this: any actor can be drawn with a touch. Take a picture made in shades of white and gray, set the color and instead of the white and gray image you get for example yellow-dark yellow or red-dark red. The problem is that the final shade is "multiplication." And if instead of the white-gray base there is a red picture and a shade of blue, then the result will be black. In fact, this is a very bad and time-consuming option to get a good result. To pick up the intensity of gray so that the red-green-yellow-blue variants looked reliably very difficult. Plus, if I'm not mistaken, there is some kind of problem with maintaining transparency. val largeFont = FreetypeFontLoader.FreeTypeFontLoaderParameter() largeFont.fontFileName = "fonts/Merriweather-Bold.ttf" ... largeFont.fontParameters.borderColor = Color.valueOf("00000080") largeFont.fontParameters.borderWidth = 4f ...
Source: https://habr.com/ru/post/333386/
All Articles