673 lines
16 KiB
Go
673 lines
16 KiB
Go
package sprite
|
||
|
||
import (
|
||
"image"
|
||
"image/draw"
|
||
"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) 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
|
||
}
|