You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Godot预加载敌人场景卡顿,移动端崩溃问题求助

塔防游戏敌人场景加载优化方案

当前用Godot制作塔防游戏,通过@onready预加载敌人场景列表:

@onready var enemylist = {"giant": preload("res://Enemies/giant.tscn"),"banshee":preload("res://Enemies/banshee.tscn"),"blue_behemot": preload("res://Enemies/blue_behemot.tscn"),"blue_tank":preload("res://Enemies/blue_tank.tscn")}

电脑端启动加载时间可接受,但运行时每2秒冻结甚至卡死;移动端预加载直接崩溃。改用load()动态加载虽能启动,但大量敌人生成时卡顿,把加载逻辑移到生成时机也未解决问题。

以下是可行的优化方案:

  • 异步加载+资源缓存
    放弃@onready同步预加载,改用ResourceLoader.load_async()在游戏启动阶段(比如主菜单界面)异步加载所有敌人场景,加载完成后缓存到字典中。这样不会阻塞主线程,避免移动端因同步加载资源过载崩溃。示例代码:

    var enemylist = {}
    var enemy_paths = {
        "giant": "res://Enemies/giant.tscn",
        "banshee": "res://Enemies/banshee.tscn",
        "blue_behemot": "res://Enemies/blue_behemot.tscn",
        "blue_tank": "res://Enemies/blue_tank.tscn"
    }
    
    func _ready():
        # 在主菜单或加载界面启动异步加载
        for enemy_name in enemy_paths:
            ResourceLoader.load_async(enemy_paths[enemy_name])
    
    func get_enemy_scene(enemy_name):
        if enemy_name not in enemylist:
            var path = enemy_paths[enemy_name]
            match ResourceLoader.get_load_status(path):
                ResourceLoader.LOAD_STATUS_LOADED:
                    enemylist[enemy_name] = ResourceLoader.load(path)
                ResourceLoader.LOAD_STATUS_UNLOADED:
                    ResourceLoader.load_async(path)
                    return null
                _:
                    return null
        return enemylist[enemy_name]
    

    可在波次生成前提前调用get_enemy_scene()确认资源已加载,或在加载界面等待所有资源加载完成再进入游戏主场景。

  • 敌人实例池复用
    提前创建一定数量的敌人实例,隐藏在场景外,需要生成时直接取出激活,用完后放回池内。彻底避免重复实例化场景带来的性能损耗。示例代码:

    var enemy_pool = {}
    var base_pool_size = 10 # 根据波次规模调整
    
    func _ready():
        # 初始化实例池
        for enemy_name in enemylist:
            enemy_pool[enemy_name] = []
            for _ in range(base_pool_size):
                var enemy = enemylist[enemy_name].instantiate()
                enemy.visible = false
                enemy.set_process(false)
                add_child(enemy)
                enemy_pool[enemy_name].append(enemy)
    
    func spawn_enemy(enemy_name):
        # 优先从池内取实例
        if enemy_pool[enemy_name].size() > 0:
            var enemy = enemy_pool[enemy_name].pop_front()
            enemy.visible = true
            enemy.set_process(true)
            enemy.reset_state() # 需在敌人脚本中实现状态重置方法
            return enemy
        # 池内无可用实例时临时创建
        var enemy = enemylist[enemy_name].instantiate()
        add_child(enemy)
        return enemy
    
    func despawn_enemy(enemy):
        enemy.visible = false
        enemy.set_process(false)
        # 根据敌人类型放回对应池
        var enemy_name = get_enemy_name_from_path(enemy.scene_file_path)
        enemy_pool[enemy_name].append(enemy)
    

    务必给敌人脚本添加reset_state()方法,重置血量、位置、状态等属性,确保复用逻辑正常。

  • 精简敌人场景资源
    移动端崩溃大概率是敌人场景资源过载,需针对性优化:

    • 压缩纹理,降低分辨率,使用Godot适配移动端的纹理压缩格式(如ETC2)
    • 合并敌人Mesh,减少Draw Call数量
    • 移除场景中冗余的节点、脚本和未使用的资源
    • 禁用敌人非必要的碰撞体、特效,按需启用
  • 分帧生成敌人
    若需生成大量敌人,避免一帧内完成所有生成操作,分多帧分批处理,降低主线程负载:

    var pending_enemies = []
    var spawn_per_frame = 2 # 每帧生成数量,根据性能调整
    
    func _process(delta):
        if pending_enemies.empty():
            return
        var spawn_count = min(spawn_per_frame, pending_enemies.size())
        for _ in range(spawn_count):
            var enemy_name = pending_enemies.pop_front()
            var enemy = spawn_enemy(enemy_name)
            enemy.position = get_spawn_point() # 设置敌人出生点
    

    波次生成时,将需要生成的敌人名称加入pending_enemies数组,由_process()分帧处理生成逻辑。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:23:10