imagedd/sprite/named_sprite_board.go

523 lines
12 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/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 {
Width int
Height int
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(width, height int) *NamedSpriteBoard {
return &NamedSpriteBoard{
Width: width,
Height: height,
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 {
// 创建画布
img := image.NewRGBA(image.Rect(0, 0, b.Width, b.Height))
// 按索引排序(已经在链表中排好)遍历所有精灵组
currentGroup := b.indexHead
for currentGroup != nil {
// 在每个索引组中,按名称排序(已经在链表中排好)遍历所有精灵
currentNode := currentGroup.Head
for currentNode != nil {
sprite := currentNode.Sprite
if sprite.Image != nil {
// 获取精灵的边界
bounds := sprite.Image.Bounds()
// 计算目标区域
min := image.Point{
X: sprite.Position.X,
Y: sprite.Position.Y,
}
max := image.Point{
X: sprite.Position.X + bounds.Dx(),
Y: sprite.Position.Y + bounds.Dy(),
}
// 绘制精灵到画布上
draw.Draw(img, image.Rectangle{Min: min, Max: max}, sprite.Image, bounds.Min, draw.Over)
}
// 移动到下一个精灵
currentNode = currentNode.Next
}
// 移动到下一个索引组
currentGroup = currentGroup.Next
}
return img
}
// 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
}