记一次用golang开发游戏客户端的内存占用优化

纯golang语言开发客户端

前提:
游戏库 ebiten
引擎介绍地址 ebiten.org/

Ebiten 是Go 编程语言的开源游戏库。Ebiten 的简单 API 允许您快速轻松地开发可以跨多个平台部署的 2D 游戏。有别于unity3d,UE,cocos creator,godot等游戏引擎,没有图形界面。

没优化之前代码:

1.玩家的结构体如下:

里面定义了6个map数据结构,在游戏初始化阶段,根据事先分类好的图片名字(一共368张散装图),分类存入map。

type Player struct {
    X           float64
    Y           float64
    State       int
    Direction   int
    MouseX      int
    MouseY      int
    ImgNow      map[int]*ebiten.Image
    ImgWeaNow   map[int]*ebiten.Image
    ImgSkillNow map[int]*ebiten.Image
    asset       map[string]*ebiten.Image //man
    assetWea    map[string]*ebiten.Image //weapon
    skill_asset map[string]*ebiten.Image //skill
    image       *embed.FS
}

func NewPlayer(x, y float64, state, dir, mx, my int, images *embed.FS) *Player {
    play := &Player{
        X:           x,
        Y:           y,
        State:       state,
        Direction:   dir,
        MouseX:      mx,
        MouseY:      my,
        ImgNow:      make(map[int]*ebiten.Image, 8),
        ImgWeaNow:   make(map[int]*ebiten.Image, 8),
        ImgSkillNow: make(map[int]*ebiten.Image, 6),
        asset:       make(map[string]*ebiten.Image, 160),
        assetWea:    make(map[string]*ebiten.Image, 160),
        skill_asset: make(map[string]*ebiten.Image, 48),
        image:       images,
    }
    return play
}

2.加载游戏素材

利用go协程并发加载游戏素材和手动GC

func (p *Player) LoadImages() {
    wg := sync.WaitGroup{}
    wg.Add(3)
    //load skill images
    go func() {
        for j := 0; j < 8; j++ {
            for i := 0; i < 6; i++ {
                s, _ := p.image.ReadFile("resource/man/skill/" + strconv.Itoa(j) + "_skill_" + strconv.Itoa(i) + ".png")
                mg := tools.GetEbitenImage(s)
                name := strconv.Itoa(j) + "_" + strconv.Itoa(i)
                p.skill_asset[name] = mg
            }
        }
        go func() {
            runtime.GC()
        }()
        wg.Done()
    }()
    ...........
}

此种模式下,一次性使用多个map存储大量的数据,导致游戏启动阶段。内存占用相当之高,高达890MB左右,随着游戏运行,golang自动GC,才缓慢降到300MB左右。

优化方案

1.使用图集打包工具,将散图合批成一张大图。

 为什么使用图集:
 减少游戏包体和内存占用
 减少drawcall,提高cpu和GPU效率。

打包后如下图:
| 一个json文件
| 一张图集
记一次用golang开发游戏客户端的内存占用优化

2.根据小图在大图中的位置,读取目标图块。
使用texturepacker工具库,去读取上面的JSON文件。
只支持”JSON (Hash)” 格式

https://github.com/fzipp/texturepacker

3.代码优化

少使用Map存储大量数据
type Player struct {
    X         float64
    Y         float64
    State     int
    Direction int
    MouseX    int
    MouseY    int
    image     *embed.FS
}

func NewPlayer(x, y float64, state, dir, mx, my int, images *embed.FS) *Player {
    play := &Player{
        X:         x,
        Y:         y,
        State:     state,
        Direction: dir,
        MouseX:    mx,
        MouseY:    my,
        image:     images,
    }
    return play
}

素材不事先存入大量map,当渲染到对应图像的时候,直接从图集中截取。这样大大提高程序效率和减少内存占用。

    imagess, x, y := g.player.GetAnimator("man", name)
    //player option
    op = &ebiten.DrawImageOptions{}
    op.GeoM.Translate(float64(SCREENWIDTH/2+OFFSETX+x), float64(SCREENHEIGHT/2+OFFSETY+y))
    op.GeoM.Scale(0.7, 0.7)
    op.Filter = ebiten.FilterLinear
    screen.DrawImage(imagess, op)
游戏素材加载资源后在代码尾部加入适当的手动GC
go func() {
    runtime.GC()
}()

通过上面的代码改造,现在启动阶段能稳定在240mb左右。

源码地址

github地址 Demo

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

兄弟哪里学的,快教教

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!