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 }