在进行 Scratch 3 的二次开发过程中,我遇到了一个特别棘手的问题:如何通过文生图模型生成的图片,在 Scratch 的角色区成功添加一个新的角色。看似简单的任务背后,却隐藏着不少意想不到的困难。以下是我解决这个问题的完整经历。
遇到的问题
起初,我的目标很明确——通过文本生成图片,并将该图片添加为一个新的角色到 Scratch 的角色区。然而,实际操作过程中,我遭遇了多个问题:
图片的处理:将通过文生图模型生成的图片 URL 转换为 Scratch 可以识别和使用的角色数据,过程中涉及到图片的加载、数据格式的转换,以及图片与 Scratch 内部数据结构的对接,这一步的复杂程度超出了我的预期。
角色的创建与渲染:在 Scratch 中,添加新角色不仅仅是将图片插入这么简单,它需要严格遵守 Scratch 的角色生成与渲染机制。我在这一步中多次遇到渲染失败的情况,角色始终无法正确显示在舞台上。
层次管理:Scratch 的角色具有层次关系,如何在不影响其他角色的前提下正确设置新角色的层次顺序,也是一个必须解决的问题。
解决方法
经过多次调试与查阅文档,我逐渐理清了问题的脉络,并最终找到了有效的解决方法。以下是关键的代码片段及其背后的思路:
1 2 3 4
| async TextGeneratedPic(args, util) { const imageUrl = this.imageUrl await this.addNewSprite(imageUrl, util) }
|
图片处理与角色创建
首先,我编写了 addNewSprite 方法,负责将生成的图片转换为 Scratch 可以识别的格式,并创建新角色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| async addNewSprite(imageUrl, util) { try { const response = await fetch(imageUrl) const blob = await response.blob() const imageBitmap = await createImageBitmap(blob) const extractedContent = imageUrl.split('.png')[0] const md5 = extractedContent
const canvas = document.createElement('canvas') canvas.width = imageBitmap.width canvas.height = imageBitmap.height const context = canvas.getContext('2d') context.drawImage(imageBitmap, 0, 0)
const pngDataUrl = canvas.toDataURL('image/png') const base64Data = pngDataUrl.split(',')[1]
const binaryString = atob(base64Data) const binaryLen = binaryString.length const bytes = new Uint8Array(binaryLen) for (let i = 0; i < binaryLen; i++) { const ascii = binaryString.charCodeAt(i) bytes[i] = ascii }
const costume = { asset: null, md5: `${md5}.png`, name: this.fileName + new Date().getTime(), bitmapResolution: 2, rotationCenterX: imageBitmap.width / 2, rotationCenterY: imageBitmap.height / 2, skinId: null, dataFormat: 'png' }
const storage = this.runtime.storage const asset = storage.createAsset( AssetType.ImageBitmap, 'png', bytes, md5 ) costume.asset = asset costume.assetId = asset.assetId
const skinId = await this.runtime.renderer.createBitmapSkin(imageBitmap, costume.bitmapResolution) costume.skinId = skinId
const spriteName = this.dddabc const sprite = new Sprite(null, this.runtime) sprite.costumes = [costume] sprite.name = spriteName + new Date().getTime() sprite.sounds = [] sprite.variables = {} sprite.lists = {} sprite.broadcasts = {} sprite.comments = {} sprite.clones = [] sprite.layerOrder = 0
const blocks = new Blocks(this.runtime, sprite) blocks.forceNoGlow = false sprite.blocks = blocks
const target = new RenderedTarget(sprite, this.runtime) target.id = uid() target.blocks = blocks target.runtime = this.runtime
sprite.clones.push(target)
const group = 'sprite' if (this.runtime.renderer._layerGroups && this.runtime.renderer._layerGroups[group]) { const drawableID = this.runtime.renderer.createDrawable(group) if (drawableID === undefined) { console.error('Failed to create drawable') return }
target.drawableID = drawableID this.runtime.renderer.updateDrawableSkinId(drawableID, skinId) target.setVisible(true)
let maxLayerOrder = 0 for (let i = 0; i < this.runtime.targets.length; i++) { if (!this.runtime.targets[i].isStage && this.runtime.targets[i].sprite.layerOrder > maxLayerOrder) { maxLayerOrder = this.runtime.targets[i].sprite.layerOrder } } target.sprite.layerOrder = maxLayerOrder + 1
this.runtime.addTarget(target) this.runtime.setEditingTarget(target.id)
this.runtime.emit("SAY", util.target, "say", '') console.log('角色已成功添加') } else { console.error(`Layer group '${group}' not found in renderer._layerGroups`) } } catch (error) { console.error('添加角色时出错:', error) } }
|
关键点解析
图片的加载与转换:通过 fetch 方法获取图片数据,并使用 canvas 将其绘制为 Base64 格式,确保图片能被 Scratch 识别并处理。
角色的创建与初始化:创建 costume 对象,包含图片的相关信息,并利用 Scratch 的内部 renderer 机制生成角色的皮肤 ID(skinId)。然后,通过创建 sprite 和 target 对象,将角色成功添加到 Scratch 的角色区。
层次管理:通过遍历现有角色,确定新的角色应处于的层次顺序,确保角色在舞台上的显示效果正确。
最终的收获
解决这个问题的过程充满了挑战,但也让我更深入地理解了 Scratch 的内部机制。从图片的加载处理,到角色的生成与渲染,每一步都需要细心调试与思考。
通过这次经验,我也更明确了在进行二次开发时,深入了解底层机制的重要性。希望这篇文章能对遇到类似问题的开发者有所帮助。

