Scene2D绘制大量Actor卡顿,安卓端无法正常运行问题求助
解决Scene2D大量Actor绘制的性能问题
兄弟,你这代码里有几个致命的性能坑,直接导致了桌面加载耗时过长、安卓设备无法正常运行的问题,咱们一个个来解决:
核心问题分析
你现在的代码每创建一个Puzzle Actor,都会重复做这两件极度消耗资源的事:
- 加载一次
rect.png纹理,100个Puzzle就会生成100个独立的Texture对象,严重占用显存且加载缓慢 - 调用
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




