Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing a Simple AVG Game Effect with libgdx

Tech 1

It has been a while since the last libgdx tutorial, mainly because of a recent fascination with various algorithms...

This article discusses implementing a simple AVG game effect, and there may be several more.

Using libgdx to create AVG effects stems from the fact that most AVG games (mainly dating sims) currently running on Android are based on ports of the NScripter engine. My modest ZTE phone runs them extremely slowly, so I decided to make my own.

What is an AVG Game?

Adventure games (AVG) typically involve the player controlling a character through a virtual adventure, with the storyline often revolving around completing a task or solving a puzzle.

Here, AVG specifically refers to Japanese-style AVG, a format that enhances the original text adventure games with beautiful CG images and compelling audio, relying on quality writing and storytelling to captivate the player.

For example, "Crystal before Dawn" (夜明前的琉璃色)

[Image: Game screenshot omitted due to missing source]

Simple Analysis

AVG games mainly consist of dialogue, CG images, and sound effects.

Breaking it down is quite simple, as shown below:

[Image: Thinking diagram omitted due to missing source]

Another example:

[Image: Thinking diagram 2 omitted due to missing source]

Each scene essentially consists of a background (scene image), dialogue, and characters. As long as you've played before, it's clear: background changes are infrequent, character changes are minor, and text advances to the next line on click or touch.

I chose libgdx's Stage for implementation. The background and characters are implemented as Image, dialogue as Label, and the dialogue border as NinePatch. Touching the dialogue area advances to the next line.

Text Processing

Dialogue constitutes a large portion of AVG games, so handling Chinese text is important.

libgdx supports Chinese in two ways: using Hiero to generate bitmap fonts, or using TTF fonts (which still have some isues and are not recommended for now).

The most comprehensive Chinese character set has over 90,000 characters, but commonly used ones are far fewer.

Comparing the common character standards of mainland China, Taiwan, and Hong Kong, we get approximately 3,500 frequently used characters. These can all be made into libgdx-compatible font files.

A total of 12 texture pages are generated, weighing 1.90 MB in total.

[Image: Chinese characters spread across pages omitted due to missing source]

This is still somewhat large. I prefer to start development with this common character set and later recreeate the font library based on actual usage.

Implementation with libgdx

I used Stage, with the game controlled by Game and Screen. Create a new class AVGScreen implementing the Screen interface.

1 private Stage stage;  // Stage
2 private TextureRegion background; // Background
3 private List<String[]> dialogues; // Dialogues
4 private BitmapFont bitmapFont; // Font
5 private NinePatch border; // Border
6 private int currentSize; // Current dialogue index

In the show method, instantiate the Stage, add the background, add the border, and modify the text in the render method.

[Image: Example screen omitted due to missing source]

The background is stretched to fill the screen. The dialogue border has a width of 100% and a height of about 25%.

The text is drawn inside the border. The border I use is:

[Image: Bordder image omitted due to missing source]

The specific partitioning is as follows:

[Image: Border partition omitted due to missing source]

Label positioning code:

1 label.x = border.getLeftWidth() + 10;
2 label.y = borderImage.height - border.getTopHeight() - 10;

For touch-to-advance, add a ClickListener to the border image:

1 borderImage.setClickListener(new ClickListener() {
2     @Override
3     public void click(Actor actor, float x, float y) {
4         System.out.println("click");
5         if (currentSize < dialogues.size() - 1) {
6             currentSize++;
7         }
8     }
9 });

The data in List<String[]> dialogues consists of [character name] and [dialogue].

For example:

1 List<String[]> list = new ArrayList<String[]>();
2 for (int i = 0; i < 10; i++) {
3     list.add(new String[] { "Character", "This is dialogue " + i });
4 }

Complete AVGScreen code:

package com.cnblogs.htynkn.ui;

import java.util.List;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;

public class AVGScreen implements Screen {

    private Stage stage;
    private TextureRegion background;
    private List<String[]> dialogues;
    private BitmapFont bitmapFont;
    private NinePatch border;
    private int currentSize;

    public AVGScreen(TextureRegion background, NinePatch border,
            List<String[]> dialogues) {
        this.background = background;
        this.dialogues = dialogues;
        this.border = border;
        bitmapFont = new BitmapFont();
    }
    
    public AVGScreen(TextureRegion background, NinePatch border,
            List<String[]> dialogues, BitmapFont bitmapFont) {
        this.background = background;
        this.dialogues = dialogues;
        this.border = border;
        this.bitmapFont = bitmapFont;
    }

    @Override
    public void dispose() {
    }

    @Override
    public void hide() {
    }

    @Override
    public void pause() {
    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        ((Label) stage.findActor("label"))
                .setText(dialogues.get(currentSize)[0] + " : "
                        + dialogues.get(currentSize)[1]);
        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void resume() {
    }

    @Override
    public void show() {
        currentSize = 0;
        stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
        Image backgroundImage = new Image(background);
        backgroundImage.x = backgroundImage.y = 0;
        backgroundImage.setFillParent(true);

        Image borderImage = new Image(border);
        borderImage.x = borderImage.y = 0;
        borderImage.width = Gdx.graphics.getWidth();
        borderImage.height = Gdx.graphics.getHeight() / 4;
        borderImage.setClickListener(new ClickListener() {
            @Override
            public void click(Actor actor, float x, float y) {
                System.out.println("click");
                if (currentSize < dialogues.size() - 1) {
                    currentSize++;
                }
            }
        });

        LabelStyle labelStyle = new LabelStyle(bitmapFont, Color.BLACK);
        Label label = new Label("", labelStyle, "label");
        label.x = border.getLeftWidth() + 10;
        label.y = borderImage.height - border.getTopHeight() - 10;
        stage.addActor(backgroundImage);
        stage.addActor(borderImage);
        stage.addActor(label);
        Gdx.input.setInputProcessor(stage);
    }
}

Demonstration effect:

[Image: Demo omitted due to missing source]

The basic effect is achieved, but there are still issues such as adding characters and beautifying the dialogue box. Moreover, the code is messy, not easily extensible, and lacks separation of concerns.

The next article will cover adding characters and using configuration files to store dialogue and other data.

Finally, a screenshot of "Crystal before Dawn":

[Image: Demo2 omitted due to missing source]

Tags: LibGDXAVG

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.