parse and use Spritesheet.xml
使用SpriteKit寫遊戲需要用到game asset [1, 2] 。從Kenney [2] 的網站下載的檔案通常會有一個類似xcode的.atlas的東東放在Spritesheet的目錄下,還有一個描述圖集名稱與位置的.xml。內容像是這樣的:
注意的是獨角鯨(narwhal.png)所在(0, 0)的位置,是在圖片的左上,而SpriteKit描述圖片的座標原點是在左下角。知道座標的關係後,從圖集裡挖出特定動物就比較直覺了。
首先解析.xml格式,把這張.xml 表格轉換成 [TextureModel],TextureModel只是很單純的struct:
import SpriteKit
import UIKit
struct TextureModel: Identifiable {
var id: String {
name
}
let name: String
let position: CGPoint
let size: CGSize
var uiImage: UIImage? = nil
}
滿足Identifiable只是方便之後在SwiftUI使用。解析.xml 的過程參考 [3]。我把處理的過
程包成一個
class ParseTextureAtlas
[4]。完整程式碼如下:
//
// ParseTextureAtlas.swift
// ParseSpriteSheet
//
// Created by hhwang on 2022/9/4.
//
import SwiftUI
import SpriteKit
class ParseTextureAtlas: NSObject, ObservableObject, XMLParserDelegate {
@Published var items = [TextureModel]()
var tagName: String? = nil
var imagePath: String? = nil
func parseTextureAtlas(_ filename: String) {
if let url = Bundle.main.url(forResource: filename, withExtension: "xml") {
if let xml = XMLParser(contentsOf: url) {
xml.delegate = self
xml.parse()
}
}
if let imagePath = imagePath {
getItemTexture(from: imagePath)
}
}
func getItemTexture(from imagePath: String) {
let textureAtlas = SKTexture(imageNamed: imagePath) //load the textureAtlas from the imagePath
let width = (textureAtlas.size().width) // get the width (in pixel) of the atlas; for normalization
let height = (textureAtlas.size().height) // get the height (in pixel) of the atlas; for normalization
for item in items {
// textureRect: the crop area in the textureAtlas for a specific item/sprite
// textureRect is using normalized coordinates between (0, 0) - (1, 1), with the origin located at the bottom-left corner
// the origin of the textureAtlas is NOT normalized (in pixel), and located at the top-left corner.
let textureRect = CGRect(x: item.position.x/width, y: 1-item.position.y/height - item.size.height / height , width: item.size.width/width, height: item.size.height / height)
DispatchQueue.main.async { [unowned self] in
if let index = self.items.firstIndex(where: {$0.name == item.name}) {
// crop the area and turn into an UIImage
self.items[index].uiImage = UIImage(cgImage: SKTexture(rect: textureRect, in: textureAtlas).cgImage())
}
}
}
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "TextureAtlas":
for key in attributeDict.keys {
if let value = attributeDict[key] {
// print("\(key): \(value)")
imagePath = value
tagName = key
}
}
case "SubTexture":
tagName = "SubTexture"
var x = 0
var y = 0
var width = 0
var height = 0
var name: String = ""
for key in attributeDict.keys {
switch key {
case "name":
name = attributeDict[key]!.components(separatedBy: ".")[0]
case "x":
x = Int(attributeDict[key]!)!
case "y":
y = Int(attributeDict[key]!)!
case "width":
width = Int(attributeDict[key]!)!
case "height":
height = Int(attributeDict[key]!)!
default:
break
}
}
let item = TextureModel(name: name, position: CGPoint(x: x, y: y), size: CGSize(width: width, height: height))
items.append(item)
// print("\(tagName!): \(item)")
default:
tagName = nil
break
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
guard tagName != nil else { return }
// print("\(tagName!) : \(string)")
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
tagName = nil
}
}
比較值得注意的是這個函式 func getItemTexture(from:)
裡面用到的座標轉換。最後在ContentView.swift裡使用這個class:
//
// ContentView.swift
// ParseSpriteSheet
//
// Created by hhwang on 2022/9/4.
//
import SwiftUI
import SpriteKit
struct ContentView: View {
@EnvironmentObject var textureParser: ParseTextureAtlas
var body: some View {
List {
ForEach(textureParser.items) { item in
if item.uiImage != nil {
HStack {
Image(uiImage: item.uiImage!)
.resizable()
.scaledToFit()
Text(item.name)
}
.frame(height: 150)
}
}
}
.onAppear {
textureParser.parseTextureAtlas("round_nodetails")
}
}
}
成果截圖:
References:
Comments
Post a Comment