[JAVA] A crappy guy who makes layout adjustments sober and efficient with the development of libGDX

Introduction

I would like to write my own tips to make the layout adjustment that I started recently a little more efficient. There is no novelty, so thank you. If there is something better or better, I would be grateful if you could respond as a comment in this article or as a comment.

Premise

Development using the following

problem

When using Scene2d, the coordinate position is roughly the local coordinates confined in the Group, so Due to the layout of the screen, the adjustment of the position does not deviate so much.

However, at the stage of fine position adjustment (for example, the stage of making a screen mock), it is necessary to change the coordinate position and size of the screen UI many times. So if you just want to do it, you'll have to recompile your Java classes each time. Although I'm no longer suffering from Java compilation speed these days (citation needed), compiling and relaunching every time for simple alignment is ridiculous and time consuming.

solution

Use PropertiesUtils to have layout related values in the properties file. This Util has been summarized earlier. Notes on Utils class of libGDX

What to do in practice

Hoge.java



import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.PropertiesUtils;
import com.badlogic.gdx.utils.StreamUtils;

import java.io.IOException;
import java.io.InputStreamReader;

/**
 * Created by yy_yank on 2017/02/01.
 */
public class Hoge extends Group {


    ObjectMap<String, String> debugCache = new ObjectMap<>();

    public Hoge() {
        //Various processing
        try{
            InputStreamReader reader = new InputStreamReader(getClass().getClassLoader().getResourceAsStream("debugCache.properties"));
            PropertiesUtils.load(debugCache, reader);
            StreamUtils.closeQuietly(reader);
        } catch (IOException e) {
            //I'm squeezing it with all my strength, but don't imitate a good girl
        }

        init();
    }
    
    private void init() {

        Actor a = new Actor();
        a.setScale(Float.parseFloat(debugCache.get("a.scale")));
        addActor(a);
        a.setPosition(Float.parseFloat(debugCache.get("a.x")), Float.parseFloat(debugCache.get("a.y")));
        Actor b = new Actor();
        b.setPosition(Float.parseFloat(debugCache.get("b.x")), Float.parseFloat(debugCache.get("b.y")));
        addActor(b);
        Actor c = new Actor();
        c.setPosition(Float.parseFloat(debugCache.get("c.x")), Float.parseFloat(debugCache.get("c.y")));
        addActor(c);
        Actor d = new Actor();
        d.setPosition(Float.parseFloat(debugCache.get("d.x")), Float.parseFloat(debugCache.get("d.y")));
        addActor(d);
    }
}

And put the property file in a place like android / assets.

debugCache.properties


a.x=100
a.y=200
a.scale=1
b.x=100
b.y=300
c.x=100
c.y=400
d.x=100
d.y=500

It's kind of muddy, but this way you can adjust the layout using the values in the properties file.

Then launch the game app. It is supposed to start the desktop version.

We will adjust the position of the Actors such as a, b, c, and d in the source code we wrote earlier. Specifically, a file called debugCache.properties should have been generated under desktop / build / resources / main, so I will play with that. By doing so, the position can be adjusted each time an Actor of the Hoge class is instantiated. What a crap. But it's faster than compiling every time.

What to do after the position adjustment is finished

There is no trick just to parse this ```ObjectMap <String, String> `` `type every time and convert it to a numeric type. Assuming that the coordinates, scale, and font size have been decided, I will write the code to rewrite the source code so that the values in the property file are applied. Please close your eyes to see that it is equivalent to Java SE 7 with a little skipping.

import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.PropertiesUtils;
import com.badlogic.gdx.utils.StreamUtils;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;


public class DebugPropertiesConverter implements InternalConverter{


    private ObjectMap<String, String> debugCache;
    
    CacheConfigurationFactory factory = new CacheConfigurationFactory() {
        @Override
        public String createCacheVariableName() {
            return "debugCache";
        }

        @Override
        public String createCacheFileName() {
            return "debugCache.properties";
        }

        @Override
        public List<Pair<String, String>> cretateTargetStringPair(String between, String value) {
            return Arrays.asList(
                    new Pair<>("Integer.parseInt("+createCacheVariableName()+".get(\""+ between +"\"))", value),
                    new Pair<>("Double.parseDouble("+createCacheVariableName()+".get(\""+ between +"\"))", value),
                    new Pair<>("Float.parseFloat("+createCacheVariableName()+".get(\""+ between +"\"))", value + "f")
            );
        }
    };

    
    public static void main(String[] args) throws Exception {
        new DebugPropertiesConverter().run();
    }

    public void run() throws Exception {
        FileVisitor<Path> visitor = new InternalFileVisitor(this);
        Files.walkFileTree(Paths.get("/Users/yy_yank/work/projects/hogehoge/core/src/com/github/yyYank"
                ,"your"
                ,"package"
                ,"dir"
        ), visitor);
    }


    @Override
    public ObjectMap<String, String> readBetweenString() throws IOException {
        if(debugCache != null) {
            return debugCache;
        }
        InputStreamReader reader = new InputStreamReader(getClass().getClassLoader().getResourceAsStream(factory.createCacheFileName()));
        this.debugCache = new ObjectMap<>();
        PropertiesUtils.load(debugCache, reader);
        System.out.println(debugCache);
        StreamUtils.closeQuietly(reader);
        return debugCache;
    }

    @Override
    public void executeOnFile(Path file, BasicFileAttributes attrs) throws IOException {
        ObjectMap<String, String> betweens = readBetweenString();
        List<String> lines = Files.readAllLines(file, Charset.forName("UTF-8"));

        boolean isRewrite = false;
        for (ObjectMap.Entry<String, String> between : betweens) {
            List<Pair<String, String>> targets = factory.cretateTargetStringPair(between.key, between.value);
            for (Pair<String, String> target : targets) {
                for (int i = 0; i < lines.size(); i++) {
                    String line = lines.get(i);
                    isRewrite = isRewrite ? isRewrite : line.contains(target.key);
                    lines.set(i, line.replace(target.key, target.value));
                }
            }
        }
        // rewrite
        if(isRewrite) {
            System.out.println("rewrite file.... -> " + file.getFileName());
            Files.write(file, lines, Charset.forName("UTF-8"));
        } else {
            System.out.println("[SKIP]" + file.getFileName());
        }
    }

    @Override
    public void executeOnDir(Path dir, BasicFileAttributes attrs) {
        // do nothing
    }

    @Override
    public boolean filterExtension(Path file) {
        return file.getFileName().toString().endsWith("java");
    }
}

interface CacheConfigurationFactory {
    String createCacheVariableName();
    String createCacheFileName();
    List<Pair<String, String>> cretateTargetStringPair(String between, String value);
}

interface InternalConverter {
    ObjectMap<String, String> readBetweenString() throws IOException;
    void executeOnFile(Path file, BasicFileAttributes attrs) throws IOException;
    void executeOnDir(Path dir, BasicFileAttributes attrs);
    boolean filterExtension(Path file);

}


class InternalFileVisitor implements FileVisitor<Path> {


    private final DebugPropertiesConverter converter;

    public InternalFileVisitor(DebugPropertiesConverter converter) {
        this.converter = converter;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        print("preVisitDirectory : " + dir.getFileName());
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        if(converter.filterExtension(file)) {
            print("visitFile : " + file.getFileName());
            converter.executeOnFile(file, attrs);
        } else {
            print("[SKIP]visitFile : " + file.getFileName());
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        String error = String.format(" [exception=%s, message=%s]", exc.getClass(), exc.getMessage());
        print("visitFileFailed : " + file.getFileName() + error);
        return FileVisitResult.CONTINUE;
    }

    protected void print(String message) {
        System.out.println(message);
    }
}

class Pair<K, V>{
    public final K key;
    public final V value;
    Pair(K k, V v) {
        this.key = k;
        this.value = v;
    }
}

When you do this, the source code will be rewritten to the values set in the properties file.

Summary

I'm grateful for static typing, but I also want to make it dynamic! I really want to feel smarter.

Recommended Posts

A crappy guy who makes layout adjustments sober and efficient with the development of libGDX
The guy who tries-with-resources with kotlin
Make a daily build of the TOPPERS kernel with Gitlab and Docker
The guy who replicates sessions with tomcat
A reminder of Docker and development environment construction
[Rough explanation] How to separate the operation of the production environment and the development environment with Rails
[Java] Cut out a part of the character string with Matcher and regular expression