Ebiten加载资源的方式是通过把资源文件,例如png、mp3文件等转换成为byte数组,然后在程序中直接读取即可。

这样的好处是资源文件可以在任何目录下工作,包括浏览器。

file2byteslice

Ebiten官方已经出来一个工具专门用来转化资源文件。

项目地址:https://github.com/hajimehoshi/file2byteslice

使用命令把项目克隆下来:

git clone https://github.com/hajimehoshi/file2byteslice

然后进入/cmd/file2byteslice目录,在CMD下面执行命令:

go build -o file2byteslice.exe

生成exe之后就可以直接使用下面的命令把资源文件转化成对应的byte数组

file2byteslice -input INPUT_FILE -output OUTPUT_FILE -package PACKAGE_NAME -var VARIABLE_NAME
INPUT_FILE 输入的资源文件
OUTPUT_FILE 输入的Go文件
PACKAGE_NAME 包名
VARIABLE_NAME 变量名

还等什么!赶紧把exe文件拷到res目录下试一下,在CMD下面执行命令:

file2byteslice.exe -input ChessBoard.png -output  ChessBoard.go -package res -var ImgChessBoard

执行完成之后就可以看到res目录下生成了一个ChessBoard.go的文件,里面定义了一个ImgChessBoard的变量,以后在代码中就直接用ImgChessBoard表示这张图片了。

自定义filetobyte

在使用file2byteslice的时候,会发现几个问题:

  1. 一个个文件去转换太麻烦
  2. 每个资源都会生成一个Go文件
  3. 能不能用一个map保存所有的资源呢?

带着这些问题,充分发挥了主观能动性造了一个轮子,代码如下:

//FileToByte 把文件转成Byte数组
func FileToByte(inPath, outPath string) error {
	dir, err := ioutil.ReadDir(inPath)
	if err != nil {
		return err
	}

	fOut, err := os.Create(outPath + "/resources.go")
	if err != nil {
		return err
	}
	defer fOut.Close()

	//写入包名
	if _, err := fmt.Fprintf(fOut, "package chess\n\n"); err != nil {
		return err
	}

	//初始化map
	if _, err := fmt.Fprintf(fOut, "var resMap = map[int][]byte {\n"); err != nil {
		return err
	}

	//目录下所有文件
	for _, fIn := range dir {
		//生成变量名
		varName := ""
		if ok := strings.HasSuffix(fIn.Name(), ".png"); ok {
			varName = "Img" + strings.TrimSuffix(fIn.Name(), ".png")
		} else if ok := strings.HasSuffix(fIn.Name(), ".wav"); ok {
			varName = "Music" + strings.TrimSuffix(fIn.Name(), ".wav")
		} else {
			continue
		}

		//打开输入文件
		f, err := os.Open(inPath + "/" + fIn.Name())
		if err != nil {
			return err
		}
		defer f.Close()

		bs, err := ioutil.ReadAll(f)
		if err != nil {
			return err
		}

		//写入输出文件
		if _, err := fmt.Fprintf(fOut, " %s : []byte(%q),\n", varName, bs); err != nil {
			return err
		}
	}

	if _, err := fmt.Fprintln(fOut, "}"); err != nil {
		return err
	}

	return nil
}

下面来一步步分析下这个代码。

FileToByte分析

要解决上面三个问题,其实就是要定义一个resources.go文件,这个文件初始化了一个map[int][]byte,把每个资源文件转化后的byte数组用key-value存起来。

//ReadDir就是打开inPath目录,dir里面保存了目录下面所有文件的信息。
dir, err := ioutil.ReadDir(inPath)
//创建输出文件resources.go。
fOut, err := os.Create(outPath + "/resources.go")
//遍历目录下的资源文件。
for _, fIn := range dir
//这是字符串截取,用来获取文件名。
strings.HasSuffix(fIn.Name(), ".png")
//os.Open打开文件,ioutil.ReadAll读取文件里面的所有内容。
f, err := os.Open(inPath + "/" + fIn.Name())
bs, err := ioutil.ReadAll(f)
//把内容写入输出文件,即resources.go里面。
fmt.Fprintf(fOut, " %s : []byte(%q),\n", varName, bs)

生成resources.go

注释掉main函数下的代码,调用上面的FileToByte接口:

package main

import (
	"ChineseChess/res"
)

func main() {
	//chess.NewGame()
	res.FileToByte("./res", "./chess")
}

F5执行之后,可以看到在chess目录下面生成了一个resources.go文件,里面定义了resMap。但是这个时候还有错误,因为我们还没有定义这些常量。

define.go

在chess目录下面新建一个define.go文件,这个文件主要用来放一些常量定义,通用接口等。

打开文件,复制下面的代码进去:

package chess

const (
	//ImgChessBoard 棋盘
	ImgChessBoard = 1
	//ImgSelect 选中
	ImgSelect = 2
	//ImgRedShuai 红帅
	ImgRedShuai = 8
	//ImgRedShi 红士
	ImgRedShi = 9
	//ImgRedXiang 红相
	ImgRedXiang = 10
	//ImgRedMa 红马
	ImgRedMa = 11
	//ImgRedJu 红车
	ImgRedJu = 12
	//ImgRedPao 红炮
	ImgRedPao = 13
	//ImgRedBing 红兵
	ImgRedBing = 14
	//ImgBlackJiang 黑将
	ImgBlackJiang = 16
	//ImgBlackShi 黑士
	ImgBlackShi = 17
	//ImgBlackXiang 黑相
	ImgBlackXiang = 18
	//ImgBlackMa 黑马
	ImgBlackMa = 19
	//ImgBlackJu 黑车
	ImgBlackJu = 20
	//ImgBlackPao 黑炮
	ImgBlackPao = 21
	//ImgBlackBing 黑兵
	ImgBlackBing = 22
)

const (
	//MusicSelect 选子
	MusicSelect = 100
	//MusicPut 落子
	MusicPut = 101
	//MusicEat 吃子
	MusicEat = 102
	//MusicJiang 将军
	MusicJiang = 103
	//MusicGameWin 胜利
	MusicGameWin = 104
	//MusicGameLose 失败
	MusicGameLose = 105
)

这样定义的好处是判断棋子的颜色非常简单:(ImgRedShi & 8) != 0 表示红方的棋子,(ImgBlackShi & 16) != 0 表示黑方的棋子。

loadResource

在Game的结构体中新增2个map保存图片和音效,代码如下:

type Game struct {
	images         map[int]*ebiten.Image //图片资源
	audios         map[int]*audio.Player //音效
	audioContext   *audio.Context        //音效器
}

在game.go中新建函数loadResource,这个函数的作用就是把resMap中的二进制内容解码存入到Ebiten对应的结构体中,内容如下:

//loadResource 加载资源
func (g *Game) loadResource() bool {
	for k, v := range resMap {
		if k >= MusicSelect {
			//加载音效
			d, err := wav.Decode(g.audioContext, audio.BytesReadSeekCloser(v))
			if err != nil {
				fmt.Print(err)
				return false
			}
			player, err := audio.NewPlayer(g.audioContext, d)
			if err != nil {
				fmt.Print(err)
				return false
			}
			g.audios[k] = player
		} else {
			//加载图片
			img, _, err := image.Decode(bytes.NewReader(v))
			if err != nil {
				fmt.Print(err)
				return false
			}
			ebitenImage, _ := ebiten.NewImageFromImage(img, ebiten.FilterDefault)
			g.images[k] = ebitenImage
		}
	}

	return true
}

//playAudio 播放音效
func (g *Game) playAudio(value int) {
	if player, ok := g.audios[value]; ok {
		player.Rewind()
		player.Play()
	}
}

NewPlayerFromBytes 把二进制音效内容转换成Ebiten的player结构。

image.Decode 把二进制图片解码成Image。

ebiten.NewImageFromImage 把Image转换成Ebiten的图片结构。

在保存成功之后,编辑器会自动import所需要的包,但是这里需要注意:因为我们用的是png,所以需要手动添加png包:

import _ "image/png"

在下一节中,我们将学习如何定义棋局的基本数据结构。

0

本文为原创文章,转载请注明出处,欢迎访问作者网站(和而不同)

发表评论

error: Content is protected !!