You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Scene2D绘制大量Actor卡顿,安卓端无法正常运行问题求助

解决Scene2D大量Actor绘制的性能问题

兄弟,你这代码里有几个致命的性能坑,直接导致了桌面加载耗时过长、安卓设备无法正常运行的问题,咱们一个个来解决:

核心问题分析

你现在的代码每创建一个Puzzle Actor,都会重复做这两件极度消耗资源的事:

  1. 加载一次rect.png纹理,100个Puzzle就会生成100个独立的Texture对象,严重占用显存且加载缓慢
  2. 调用createFont()生成一次字体,每次生成都会初始化FreeTypeFontGenerator,这是CPU和内存的双重杀手,100次生成直接把性能拉爆

解决方案

1. 共享纹理资源

把关卡按钮的纹理提前加载一次,所有Puzzle共享同一个Texture对象,避免重复加载:

  • 在全局初始化时加载rect.png,不要在Puzzle构造方法里重复加载
  • 所有Puzzle使用同一个TextureRegion实例(基于共享的Texture)

2. 复用字体对象

只创建两种字体(已解锁的金色、未解锁的深灰色),所有Puzzle复用这两个字体,同时注意释放字体生成器的资源:

  • 在全局初始化时生成这两种字体,不要在每个Puzzle里单独生成
  • 修改createFont()方法,用完FreeTypeFontGenerator后必须调用dispose()释放资源,避免内存泄漏

3. 资源清理

在游戏退出或场景销毁时,手动释放所有全局的纹理、字体资源,避免内存泄漏


修改后的代码示例

全局资源初始化(在create()方法中)

Texture rectTexture;
BitmapFont solvedFont;
BitmapFont unsolvedFont;

@Override
public void create() {
    stage = new Stage(new ScalingViewport(Scaling.fill, 800, 1280));
    Gdx.input.setInputProcessor(stage);
    skin = new Skin(Gdx.files.internal("data/uiskin.json"));
    Image play = new Image(new Texture(Gdx.files.internal("play.png")));
    stage.addActor(play);

    // 全局加载纹理和字体
    rectTexture = new Texture(Gdx.files.internal("rect.png"));
    solvedFont = HelpingMethods.createFont(38, Color.GOLD);
    unsolvedFont = HelpingMethods.createFont(38, Color.DARK_GRAY);

    play.addListener(new ClickListener() {
        @Override
        public void clicked(InputEvent event, float x, float y) {
            Table container = new Table();
            stage.addActor(container);
            container.setFillParent(true);
            Table table = new Table();
            Puzzle[] puzzles = new Puzzle[100];
            for (int i=0; i<puzzles.length; i++) {
                table.padTop(60);
                table.padBottom(60);
                // 传入共享的纹理和字体
                puzzles[i] = new Puzzle(i, false, rectTexture, solvedFont, unsolvedFont);
                if (i%6 == 0) table.row();
                table.add(puzzles[i]).pad(5);
            }
            ScrollPane scroll = new ScrollPane(table, skin);
            container.add(scroll).expand().fill().colspan(4);
        }
    });
}

修改createFont()方法

public static BitmapFont createFont(int size, Color color) {
    FreeTypeFontGenerator generator = new FreeTypeFontGenerator (Gdx.files.internal("fonts/font.ttf"));
    FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
    parameter.size = size;
    parameter.color = color;
    parameter.minFilter = Texture.TextureFilter.Linear;
    parameter.magFilter = Texture.TextureFilter.Linear;
    BitmapFont font = generator.generateFont(parameter);
    generator.dispose(); // 必须释放生成器资源!
    return font;
}

修改Puzzle

private class Puzzle extends Actor {
    TextureRegion rect;
    BitmapFont font;
    float w,h;
    boolean solved;
    int drawNum;

    // 接收共享的纹理和字体
    public Puzzle(int number, boolean solved, Texture rectTexture, BitmapFont solvedFont, BitmapFont unsolvedFont) {
        rect = new TextureRegion(rectTexture); // 使用共享纹理
        setSize(rect.getRegionWidth(), rect.getRegionHeight());
        this.drawNum = number + 1;
        this.solved = solved;
        this.font = solved ? solvedFont : unsolvedFont; // 复用字体

        GlyphLayout layout = new GlyphLayout();
        layout.setText(font, "" + this.drawNum);
        w = layout.width;
        h = layout.height;
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        Color color = getColor();
        batch.setColor(solved ? new Color(0,0,1, color.a * parentAlpha) : new Color(1,1,1, color.a * parentAlpha));
        font.setColor(color.r, color.g, color.b, color.a * parentAlpha);
        batch.draw(rect, getX(), getY());
        font.draw(batch, "" + drawNum, getX() + getWidth()/2 - w/2, getY() + getHeight()/2 + h/2);
    }
}

资源清理(在dispose()方法中)

@Override
public void dispose() {
    stage.dispose();
    skin.dispose();
    rectTexture.dispose();
    solvedFont.dispose();
    unsolvedFont.dispose();
    // 记得释放其他资源(比如play按钮的纹理等)
}

额外优化建议

  • 如果关卡数量还可能增加,考虑使用对象池来复用Puzzle Actor,避免频繁创建销毁对象
  • 可以把Puzzle改成ImageButton(如果需要点击事件),Scene2D的内置组件在绘制优化上更成熟
  • 对于ScrollPane,可以设置setFlickScroll(true)提升安卓设备上的滑动体验

内容的提问来源于stack exchange,提问作者MAGS94

火山引擎 最新活动