924 lines
22 KiB
Go
924 lines
22 KiB
Go
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
|
||
}
|