523 lines
12 KiB
Go
523 lines
12 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 {
|
||
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
|
||
}
|