imagedd/sprite/named_sprite_board.go

924 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sprite
import (
"image"
"image/color/palette"
"image/draw"
"image/gif"
"image/png"
"log"
"os"
"sort"
)
// 设置为true启用调试日志
var debugLog = false
func logDebug(format string, args ...interface{}) {
if debugLog {
log.Printf(format, args...)
}
}
// SpriteIndexNode 表示同一个Index下的精灵链表节点
type SpriteIndexNode struct {
Sprite *Sprite
Next *SpriteIndexNode // 指向同一Index下的下一个精灵
}
// IndexGroup 表示一个索引组包含所有具有相同Index的精灵
type IndexGroup struct {
Index int
Head *SpriteIndexNode // 指向第一个精灵
Count int // 该组中精灵数量
Next *IndexGroup // 指向下一个索引组
Prev *IndexGroup // 指向上一个索引组
}
// NamedSpriteBoard 提供按名称哈希查找,按索引排序的精灵图管理
type NamedSpriteBoard struct {
Sprites []*Sprite
// nameMap 用于O(1)的按名称查找键为Name值为精灵指针
nameMap map[string]*Sprite
// indexGroups 按Index排序的索引组链表
indexHead *IndexGroup // 索引组链表头
indexTail *IndexGroup // 索引组链表尾
// 索引组的映射表用于O(1)按索引查找索引组
indexMap map[int]*IndexGroup
// 总精灵数量
count int
}
// NewNamedSpriteBoard 创建一个新的NamedSpriteBoard
func NewNamedSpriteBoard() *NamedSpriteBoard {
return &NamedSpriteBoard{
Sprites: make([]*Sprite, 0),
nameMap: make(map[string]*Sprite),
indexMap: make(map[int]*IndexGroup),
indexHead: nil,
indexTail: nil,
count: 0,
}
}
// AddSprite 添加精灵O(1)时间复杂度查找O(logn)插入
func (b *NamedSpriteBoard) AddSprite(sprite *Sprite) {
// 必须有名称
if sprite.Name == "" {
return
}
// 如果已存在同名精灵,先移除
if _, exists := b.nameMap[sprite.Name]; exists {
b.RemoveSpriteByName(sprite.Name)
}
// 将精灵添加到名称映射
b.nameMap[sprite.Name] = sprite
// 查找或创建索引组
indexGroup, exists := b.indexMap[sprite.Index]
if !exists {
// 创建新的索引组
indexGroup = &IndexGroup{
Index: sprite.Index,
Head: nil,
Count: 0,
Next: nil,
Prev: nil,
}
// 将索引组添加到映射表
b.indexMap[sprite.Index] = indexGroup
// 插入到索引组链表中的正确位置
b.insertIndexGroup(indexGroup)
}
// 创建新的精灵节点
newNode := &SpriteIndexNode{
Sprite: sprite,
Next: nil,
}
// 在索引组中按名称排序插入
b.insertSpriteToGroup(indexGroup, newNode)
// 更新计数
indexGroup.Count++
b.count++
// 更新兼容层
b.updateSpriteArray()
}
// insertIndexGroup 将索引组插入到正确的位置O(logn)时间复杂度
func (b *NamedSpriteBoard) insertIndexGroup(group *IndexGroup) {
// 如果是第一个索引组
if b.indexHead == nil {
b.indexHead = group
b.indexTail = group
return
}
// 如果应该插入到头部
if group.Index < b.indexHead.Index {
group.Next = b.indexHead
b.indexHead.Prev = group
b.indexHead = group
return
}
// 如果应该插入到尾部
if group.Index > b.indexTail.Index {
group.Prev = b.indexTail
b.indexTail.Next = group
b.indexTail = group
return
}
// 找到插入位置 - 将链表的元素放入数组进行二分查找
groups := make([]*IndexGroup, 0)
current := b.indexHead
for current != nil {
groups = append(groups, current)
current = current.Next
}
// 二分查找插入位置
insertPos := sort.Search(len(groups), func(i int) bool {
return groups[i].Index >= group.Index
})
// 插入到找到的位置之前
prevGroup := groups[insertPos-1]
nextGroup := groups[insertPos]
group.Next = nextGroup
group.Prev = prevGroup
prevGroup.Next = group
nextGroup.Prev = group
}
// insertSpriteToGroup 将精灵节点按名称排序插入到索引组O(n)时间复杂度
// 由于同一个Index下的精灵数量通常不多所以使用简单的插入排序
func (b *NamedSpriteBoard) insertSpriteToGroup(group *IndexGroup, node *SpriteIndexNode) {
// 如果索引组为空
if group.Head == nil {
group.Head = node
return
}
// 如果应该插入到头部
if node.Sprite.Name < group.Head.Sprite.Name {
node.Next = group.Head
group.Head = node
return
}
// 查找插入位置
current := group.Head
for current.Next != nil && current.Next.Sprite.Name < node.Sprite.Name {
current = current.Next
}
// 插入节点
node.Next = current.Next
current.Next = node
}
// GetSpriteByName 通过名称获取精灵O(1)时间复杂度
func (b *NamedSpriteBoard) GetSpriteByName(name string) *Sprite {
sprite, exists := b.nameMap[name]
if exists {
return sprite
}
return nil
}
// GetSpritesByIndex 获取具有特定索引的所有精灵O(1)查找 + O(n)收集
func (b *NamedSpriteBoard) GetSpritesByIndex(index int) []*Sprite {
group, exists := b.indexMap[index]
if !exists {
return nil
}
sprites := make([]*Sprite, 0, group.Count)
current := group.Head
for current != nil {
sprites = append(sprites, current.Sprite)
current = current.Next
}
return sprites
}
// RemoveSpriteByName 通过名称删除精灵O(1)查找 + O(n)删除
func (b *NamedSpriteBoard) RemoveSpriteByName(name string) bool {
sprite, exists := b.nameMap[name]
if !exists {
return false
}
// 从名称映射中移除
delete(b.nameMap, name)
// 查找对应的索引组
group := b.indexMap[sprite.Index]
if group == nil {
return false
}
// 从索引组中移除精灵
removed := b.removeSpriteFromGroup(group, name)
if !removed {
return false
}
// 如果索引组变为空,则删除该索引组
if group.Count == 0 {
b.removeIndexGroup(group)
}
// 更新计数
b.count--
// 更新兼容层
b.updateSpriteArray()
return true
}
// removeSpriteFromGroup 从索引组中移除指定名称的精灵O(n)时间复杂度
func (b *NamedSpriteBoard) removeSpriteFromGroup(group *IndexGroup, name string) bool {
logDebug("removeSpriteFromGroup: 尝试从索引组 %d 中移除精灵 %s", group.Index, name)
// 如果索引组为空
if group.Head == nil {
logDebug("removeSpriteFromGroup: 索引组为空")
return false
}
// 如果是头节点
if group.Head.Sprite.Name == name {
logDebug("removeSpriteFromGroup: 移除头节点 %s", name)
group.Head = group.Head.Next
group.Count--
return true
}
// 查找节点
current := group.Head
for current.Next != nil && current.Next.Sprite.Name != name {
logDebug("removeSpriteFromGroup: 检查节点 %s", current.Next.Sprite.Name)
current = current.Next
}
// 如果找到了节点
if current.Next != nil {
logDebug("removeSpriteFromGroup: 找到并移除节点 %s", name)
current.Next = current.Next.Next
group.Count--
return true
}
logDebug("removeSpriteFromGroup: 未找到节点 %s", name)
return false
}
// removeIndexGroup 从索引组链表中移除索引组O(1)时间复杂度
func (b *NamedSpriteBoard) removeIndexGroup(group *IndexGroup) {
// 从索引映射表中移除
delete(b.indexMap, group.Index)
// 从链表中移除
if group.Prev != nil {
group.Prev.Next = group.Next
} else {
b.indexHead = group.Next // 头节点变更
}
if group.Next != nil {
group.Next.Prev = group.Prev
} else {
b.indexTail = group.Prev // 尾节点变更
}
}
// UpdateSpriteIndex 更新精灵的索引O(1)查找 + O(logn)插入
func (b *NamedSpriteBoard) UpdateSpriteIndex(name string, newIndex int) bool {
sprite, exists := b.nameMap[name]
if !exists {
return false
}
// 保存旧索引
oldIndex := sprite.Index
// 从旧索引组中移除
oldGroup := b.indexMap[oldIndex]
if oldGroup == nil {
return false
}
if !b.removeSpriteFromGroup(oldGroup, name) {
return false
}
// 如果旧索引组变为空,则删除
if oldGroup.Count == 0 {
b.removeIndexGroup(oldGroup)
}
// 更新精灵的索引
sprite.Index = newIndex
// 查找或创建新索引组
newGroup, exists := b.indexMap[newIndex]
if !exists {
// 创建新的索引组
newGroup = &IndexGroup{
Index: newIndex,
Head: nil,
Count: 0,
Next: nil,
Prev: nil,
}
// 将索引组添加到映射表
b.indexMap[newIndex] = newGroup
// 插入到索引组链表中的正确位置
b.insertIndexGroup(newGroup)
}
// 创建新的精灵节点
newNode := &SpriteIndexNode{
Sprite: sprite,
Next: nil,
}
// 在新索引组中按名称排序插入
b.insertSpriteToGroup(newGroup, newNode)
newGroup.Count++
// 更新兼容层
b.updateSpriteArray()
return true
}
// UpdateSpriteName 更新精灵的名称O(1)查找 + O(n)插入
func (b *NamedSpriteBoard) UpdateSpriteName(oldName, newName string) bool {
logDebug("UpdateSpriteName: 尝试将 %s 更新为 %s", oldName, newName)
sprite, exists := b.nameMap[oldName]
if !exists {
logDebug("UpdateSpriteName: 未找到精灵 %s", oldName)
return false
}
// 检查新名称是否已存在
if _, exists := b.nameMap[newName]; exists && newName != oldName {
logDebug("UpdateSpriteName: 新名称 %s 已存在", newName)
return false
}
// 保存索引
index := sprite.Index
logDebug("UpdateSpriteName: 精灵索引为 %d", index)
// 从索引组中移除再添加,以保持排序
group := b.indexMap[index]
if group == nil {
logDebug("UpdateSpriteName: 未找到索引组 %d", index)
return false
}
logDebug("UpdateSpriteName: 在索引组 %d 中查找名称 %s", index, oldName)
// 调试输出当前索引组的所有精灵
current := group.Head
i := 0
for current != nil {
logDebug("UpdateSpriteName: 索引组精灵[%d]: %s", i, current.Sprite.Name)
current = current.Next
i++
}
// 从索引组链表中删除节点
if !b.removeSpriteFromGroup(group, oldName) {
logDebug("UpdateSpriteName: 从索引组中移除精灵 %s 失败", oldName)
return false
}
// 从名称映射中移除旧名称
delete(b.nameMap, oldName)
// 更新精灵名称
sprite.Name = newName
// 添加到新名称映射
b.nameMap[newName] = sprite
// 创建新节点并添加回索引组
newNode := &SpriteIndexNode{
Sprite: sprite,
Next: nil,
}
b.insertSpriteToGroup(group, newNode)
group.Count++ // 增加计数因为removeSpriteFromGroup已经减少了计数
// 更新兼容层
b.updateSpriteArray()
logDebug("UpdateSpriteName: 成功更新名称从 %s 到 %s", oldName, newName)
return true
}
// GetAllSprites 获取所有精灵按Index主排序Name次排序
func (b *NamedSpriteBoard) GetAllSprites() []*Sprite {
sprites := make([]*Sprite, 0, b.count)
// 遍历所有索引组
currentGroup := b.indexHead
for currentGroup != nil {
// 遍历当前索引组中的所有精灵
currentNode := currentGroup.Head
for currentNode != nil {
sprites = append(sprites, currentNode.Sprite)
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
return sprites
}
// updateSpriteArray 更新BaseBoard中的Sprites数组以保持兼容性
func (b *NamedSpriteBoard) updateSpriteArray() {
b.Sprites = b.GetAllSprites()
}
// RenderToImage 将精灵板渲染为图像
func (b *NamedSpriteBoard) RenderToImage() *image.RGBA {
// 计算所有精灵图覆盖的最大区域
var minX, minY, maxX, maxY int
// 初始化为最大/最小可能值,以便于后续比较
initialized := false
// 遍历所有精灵计算边界
currentGroup := b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
currentImage := sprite.GetCurrentImage()
if currentImage != nil {
srcBounds := currentImage.Bounds()
spriteMinX := sprite.Position.X
spriteMinY := sprite.Position.Y
spriteMaxX := sprite.Position.X + srcBounds.Dx()
spriteMaxY := sprite.Position.Y + srcBounds.Dy()
// 首次初始化边界值或更新边界
if !initialized {
minX, minY = spriteMinX, spriteMinY
maxX, maxY = spriteMaxX, spriteMaxY
initialized = true
} else {
// 更新边界值
if spriteMinX < minX {
minX = spriteMinX
}
if spriteMinY < minY {
minY = spriteMinY
}
if spriteMaxX > maxX {
maxX = spriteMaxX
}
if spriteMaxY > maxY {
maxY = spriteMaxY
}
}
}
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
// 如果没有精灵图,则使用默认大小
if !initialized {
minX, minY = 0, 0
maxX, maxY = 1, 1
}
// 确保有最小尺寸
if maxX <= minX {
maxX = minX + 1
}
if maxY <= minY {
maxY = minY + 1
}
// 计算画布尺寸,保留负坐标空间
width := maxX - minX
height := maxY - minY
// 创建画布,大小由精灵图决定
img := image.NewRGBA(image.Rect(0, 0, width, height))
// 按索引排序遍历所有精灵组
currentGroup = b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
currentImage := sprite.GetCurrentImage()
if currentImage != nil {
// 获取精灵的边界
srcBounds := currentImage.Bounds()
// 计算原始目标区域相对于minX, minY偏移
dstMinX := sprite.Position.X - minX
dstMinY := sprite.Position.Y - minY
dstMaxX := dstMinX + srcBounds.Dx()
dstMaxY := dstMinY + srcBounds.Dy()
// 计算源图像的裁剪区域(如果精灵部分在画布外)
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
// 处理左边界超出
if dstMinX < 0 {
srcMinX -= dstMinX // 向右移动源图像起点
dstMinX = 0
}
// 处理上边界超出
if dstMinY < 0 {
srcMinY -= dstMinY // 向下移动源图像起点
dstMinY = 0
}
// 处理右边界超出
if dstMaxX > width {
dstMaxX = width
}
// 处理下边界超出
if dstMaxY > height {
dstMaxY = height
}
// 如果裁剪后的区域有效宽度和高度都大于0
if dstMinX < dstMaxX && dstMinY < dstMaxY {
dstRect := image.Rect(dstMinX, dstMinY, dstMaxX, dstMaxY)
srcPt := image.Point{X: srcMinX, Y: srcMinY}
// 绘制裁剪后的精灵到画布上
draw.Draw(img, dstRect, currentImage, srcPt, draw.Over)
}
}
// 移动到下一个精灵
currentNode = currentNode.Next
}
// 移动到下一个索引组
currentGroup = currentGroup.Next
}
return img
}
func (b *NamedSpriteBoard) RenderToAnimatedImage() (img []*image.RGBA, delays []int) {
// 查找所有精灵的最大总时间
maxTotalTime := 0
// 遍历所有精灵查找最大总时间
currentGroup := b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
if sprite.FrameCount() > 1 && len(sprite.Delay) > 0 {
totalTime := sprite.GetTotalTime()
if totalTime > maxTotalTime {
maxTotalTime = totalTime
}
}
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
// 如果没有动画精灵,返回单帧
if maxTotalTime == 0 {
// 渲染一个静态帧
staticFrame := b.RenderToImage()
return []*image.RGBA{staticFrame}, []int{10} // 默认延迟100ms
}
// 获取画布边界
minX, minY, maxX, maxY := b.GetRenderBounds()
width := maxX - minX
height := maxY - minY
// 初始化结果数组
img = make([]*image.RGBA, 0)
delays = make([]int, 0)
// 跟踪每个精灵的当前帧索引
spriteCurrentFrames := make(map[*Sprite]int)
// 需要检查的时间点列表 - 初始包含所有精灵的帧变化时间点
timePoints := make([]int, 0)
timePoints = append(timePoints, 0) // 起始点
// 收集所有精灵的帧变化时间点
currentGroup = b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
if sprite.FrameCount() > 1 && len(sprite.Delay) > 0 {
// 添加每个延迟增量点
timePoints = append(timePoints, sprite.delayIncreased...)
}
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
// 对时间点进行排序和去重
sort.Ints(timePoints)
uniqueTimePoints := make([]int, 0)
lastTime := -1
for _, t := range timePoints {
if t != lastTime {
uniqueTimePoints = append(uniqueTimePoints, t)
lastTime = t
}
}
// 对每个时间点生成一帧
lastFrameImage := (*image.RGBA)(nil)
for i, timePoint := range uniqueTimePoints {
// 渲染当前时间点的帧
currentFrame := image.NewRGBA(image.Rect(0, 0, width, height))
frameChanged := false
// 检查是否有任何精灵的帧发生变化
currentGroup = b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
// 获取当前精灵在当前时间点的帧
var currentImage image.Image
var frameIndex int
if sprite.FrameCount() > 1 && len(sprite.Delay) > 0 {
// 使用延迟时间获取当前帧和帧索引
currentImage, frameIndex = sprite.GetFrameOnDelay(timePoint)
// 检查帧是否变化
oldFrameIndex, exists := spriteCurrentFrames[sprite]
if !exists || oldFrameIndex != frameIndex {
frameChanged = true
spriteCurrentFrames[sprite] = frameIndex
}
} else {
// 静态精灵直接使用当前帧
currentImage = sprite.GetCurrentImage()
}
if currentImage != nil {
// 获取精灵的边界
srcBounds := currentImage.Bounds()
// 计算原始目标区域相对于minX, minY偏移
dstMinX := sprite.Position.X - minX
dstMinY := sprite.Position.Y - minY
dstMaxX := dstMinX + srcBounds.Dx()
dstMaxY := dstMinY + srcBounds.Dy()
// 计算源图像的裁剪区域(如果精灵部分在画布外)
srcMinX := srcBounds.Min.X
srcMinY := srcBounds.Min.Y
// 处理左边界超出
if dstMinX < 0 {
srcMinX -= dstMinX // 向右移动源图像起点
dstMinX = 0
}
// 处理上边界超出
if dstMinY < 0 {
srcMinY -= dstMinY // 向下移动源图像起点
dstMinY = 0
}
// 处理右边界超出
if dstMaxX > width {
dstMaxX = width
}
// 处理下边界超出
if dstMaxY > height {
dstMaxY = height
}
// 如果裁剪后的区域有效宽度和高度都大于0
if dstMinX < dstMaxX && dstMinY < dstMaxY {
dstRect := image.Rect(dstMinX, dstMinY, dstMaxX, dstMaxY)
srcPt := image.Point{X: srcMinX, Y: srcMinY}
// 绘制裁剪后的精灵到画布上
draw.Draw(currentFrame, dstRect, currentImage, srcPt, draw.Over)
}
}
// 移动到下一个精灵
currentNode = currentNode.Next
}
// 移动到下一个索引组
currentGroup = currentGroup.Next
}
// 如果是第一帧或者帧内容有变化,则添加该帧
if i == 0 || frameChanged {
// 计算这一帧的延迟时间
delay := 0
if i < len(uniqueTimePoints)-1 {
delay = uniqueTimePoints[i+1] - timePoint
} else {
delay = 10 // 最后一帧默认延迟100ms
}
// 将当前帧添加到结果中
img = append(img, currentFrame)
delays = append(delays, delay)
lastFrameImage = currentFrame
} else if lastFrameImage != nil {
// 如果帧没有变化,则累加到上一帧的延迟中
if len(delays) > 0 {
if i < len(uniqueTimePoints)-1 {
delays[len(delays)-1] += uniqueTimePoints[i+1] - timePoint
}
}
}
}
// 确保至少有一帧
if len(img) == 0 {
staticFrame := b.RenderToImage()
return []*image.RGBA{staticFrame}, []int{10}
}
return img, delays
}
func (b *NamedSpriteBoard) GetRenderBounds() (minX, minY, maxX, maxY int) {
// 计算所有精灵图覆盖的最大区域
// 初始化为最大/最小可能值,以便于后续比较
initialized := false
// 遍历所有精灵计算边界
currentGroup := b.indexHead
for currentGroup != nil {
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
currentImage := sprite.GetCurrentImage()
if currentImage != nil {
srcBounds := currentImage.Bounds()
spriteMinX := sprite.Position.X
spriteMinY := sprite.Position.Y
spriteMaxX := sprite.Position.X + srcBounds.Dx()
spriteMaxY := sprite.Position.Y + srcBounds.Dy()
// 首次初始化边界值或更新边界
if !initialized {
minX, minY = spriteMinX, spriteMinY
maxX, maxY = spriteMaxX, spriteMaxY
initialized = true
} else {
// 更新边界值
if spriteMinX < minX {
minX = spriteMinX
}
if spriteMinY < minY {
minY = spriteMinY
}
if spriteMaxX > maxX {
maxX = spriteMaxX
}
if spriteMaxY > maxY {
maxY = spriteMaxY
}
}
}
currentNode = currentNode.Next
}
currentGroup = currentGroup.Next
}
// 如果没有精灵图,则使用默认大小
if !initialized {
minX, minY = 0, 0
maxX, maxY = 1, 1
}
// 确保有最小尺寸
if maxX <= minX {
maxX = minX + 1
}
if maxY <= minY {
maxY = minY + 1
}
return minX, minY, maxX, maxY
}
// SaveToPng 将精灵板渲染为图像并保存到文件
func (b *NamedSpriteBoard) SaveToPng(filename string) error {
// 渲染图像
img := b.RenderToImage()
// 创建文件
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
// 将图像编码为PNG并写入文件
if err := png.Encode(f, img); err != nil {
return err
}
return nil
}
func (b *NamedSpriteBoard) SaveToGIF(filename string) error {
// 获取动画帧
frames, delays := b.RenderToAnimatedImage()
if len(frames) == 0 {
// 如果没有动画帧,则使用单一图像
img := b.RenderToImage()
// 创建文件
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
// 将RGBA图像转换为Paletted图像使用WebSafe调色板
bounds := img.Bounds()
palettedImg := image.NewPaletted(bounds, palette.WebSafe)
draw.Draw(palettedImg, bounds, img, bounds.Min, draw.Src)
// 将图像编码为GIF并写入文件
if err := gif.EncodeAll(f, &gif.GIF{
Image: []*image.Paletted{palettedImg},
Delay: []int{0},
}); err != nil {
return err
}
return nil
}
// 多帧动画情况
palettedFrames := make([]*image.Paletted, len(frames))
// 转换所有帧为Paletted图像
for i, frame := range frames {
bounds := frame.Bounds()
palettedFrames[i] = image.NewPaletted(bounds, palette.WebSafe)
draw.Draw(palettedFrames[i], bounds, frame, bounds.Min, draw.Src)
}
// 创建文件
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
// 将图像编码为GIF并写入文件
if err := gif.EncodeAll(f, &gif.GIF{
Image: palettedFrames,
Delay: delays,
}); err != nil {
return err
}
return nil
}