Texture 与 Sprite 的类实现:从原理走进代码

Texture 与 Sprite 的类实现:从原理走进代码 引子从知道到做到的最后一步在上一篇里我们用布料与成衣的比喻把 Texture 和 Sprite 的区别与联系讲得明明白白。你已经知道了Texture 是原始的布料Sprite 是加工后的成衣成衣从布料裁出一块布可裁多件衣。但知道和做到之间还隔着一条河。你可能会问这些比喻很美可到了代码里它们究竟长什么样Texture 是一个什么样的类我能不能用代码亲手创建一块布料、往上面一个像素一个像素地画画我能不能用代码把一块布料裁成一件成衣还指定裁剪的位置和大小下载来的图片、生成的图案又该如何一步步变成能显示的东西今天我们就渡过这条河。我们要钻进代码内部看看Texture2D和Sprite这两个类的具体实现——它们有哪些关键成员、如何被创建、如何被操作。我们会用一个个可运行的实例把前面所有抽象的比喻全部变成你指尖能敲出来的真实代码。一、Texture2D可以逐像素操作的布料在 Unity 里我们平时用得最多的纹理类叫做Texture2D。它是 Texture 家族里最常见的成员——一张二维的、由像素网格构成的图像。我们先来认识它最核心的几个零件。它的基本属性布料的尺寸一块布料最基本的信息就是它的宽和高。Texture2D 用两个属性来表达Texture2DtexsomeTexture;intwtex.width;// 布料的宽度单位是像素inthtex.height;// 布料的高度单位是像素Debug.Log($这块布料的尺寸是{w}×{h}像素);这两个只读属性告诉你这块布料到底有多大。就像量一块布你首先得知道它几尺宽、几尺长。亲手创建一块空白布料Texture2D 最迷人的地方在于——它不一定非得从外部图片文件导入你完全可以用代码凭空创建一块空白的布料// 创建一块 256 × 256 的空白布料Texture2DtexnewTexture2D(256,256);这一行代码就等于向裁缝铺里凭空变出了一卷 256×256 的新布。此刻它上面的像素还是一片默认的杂乱颜色但它已经是一块实实在在、可以任你涂画的布料了。逐像素作画SetPixel 与 GetPixel既然是布料我们当然可以在上面画画。Texture2D 提供了两个最基础的方法让你能够读写每一个像素的颜色// 往指定坐标 (x, y) 的像素上涂一种颜色tex.SetPixel(10,20,Color.red);// 读取指定坐标 (x, y) 处像素的颜色Colorctex.GetPixel(10,20);SetPixel就像你拿着画笔精准地在布料的第 10 列、第 20 行那个点上点了一滴红色。GetPixel则相反是你凑近去看那个点读出它现在是什么颜色。不过这里有一个极其关键、新手最容易踩坑的细节——你光画还不够画完必须定妆。你在 CPU 这边一通涂涂画画改动其实只停留在内存里屏幕GPU那边还完全不知情。你必须调用一个提交方法才能把你的改动真正应用上去tex.Apply();// 关键把之前所有的像素改动真正提交生效记住这个Apply()。无数人写代码画了半天运行起来纹理却纹丝不动最后发现罪魁祸首就是漏掉了这一句定妆。SetPixel 是画草稿Apply 才是落墨定型。实战案例用代码画一块纯色布料我们把上面的知识串起来做一个完整的小例子——用代码生成一张纯蓝色的纹理usingUnityEngine;publicclassMakeBlueTexture:MonoBehaviour{publicRenderertargetRenderer;// 用来展示纹理的物体voidStart(){intsize128;// 第一步创建一块 128×128 的空白布料Texture2DtexnewTexture2D(size,size);// 第二步一个像素一个像素地涂满蓝色for(intx0;xsize;x){for(inty0;ysize;y){tex.SetPixel(x,y,Color.blue);}}// 第三步定妆提交所有改动tex.Apply();// 第四步把这块布料贴到物体上展示targetRenderer.material.mainTexturetex;}}运行它那个物体就会披上一层我们亲手织出来的纯蓝布料。虽然只是简单的蓝色但这个过程完整演示了 Texture2D 从无中生有到逐像素作画再到定妆生效的全流程。这就是那块布料在代码世界里的真实模样。更高效的批量作画SetPixels如果一个像素一个像素地画遇到大图会非常慢。Texture2D 还提供了批量作画的方法一次性把一整片像素刷上去Color[]colorsnewColor[size*size];for(inti0;icolors.Length;i){colors[i]Color.green;// 先在数组里备好每个像素的颜色}tex.SetPixels(colors);// 一次性全部铺上比逐个 SetPixel 快得多tex.Apply();// 别忘了定妆SetPixels就像不用一笔一笔画而是直接把一整块调好色的布啪地铺上去效率高出许多。处理大纹理时一定优先用它。二、Sprite从布料裁出的成衣认识完布料我们来看那件成衣——Sprite类。前面反复强调过Sprite 是从 Texture 加工出来的它额外携带了裁剪位置、尺寸等信息。那么在代码里我们该如何完成这道裁剪加工呢核心方法Sprite.Create——裁缝的剪刀Sprite 类提供了一个静态的裁缝方法——Sprite.Create。它就是那把把布料裁成成衣的剪刀。我们来看它最常用的形式SpritespriteSprite.Create(texture,// 参数一用哪块布料newRect(0,0,texture.width,texture.height),// 参数二从布料的哪个区域裁newVector2(0.5f,0.5f)// 参数三轴心点设在哪里);我们逐一拆解这三个关键参数它们恰好对应了裁衣服要交代的三件事参数一用哪块布料。就是那张作为原料的 Texture2D。没有布裁缝无从下剪。参数二从布料的哪个位置裁多大。这是一个矩形Rect描述从布料的哪个坐标开始、裁多宽多高的一块。上面例子里Rect(0, 0, width, height)表示从左下角开始把整块布都裁下来。而如果你写Rect(0, 0, 32, 32)就表示只从左下角裁一块 32×32 的小方块——这正是从一张大图集里切出单个小图标的原理参数三轴心点。Vector2(0.5f, 0.5f)表示轴心在正中央。轴心就是这件成衣的锚点决定了它在被定位、旋转时以哪个点为基准。0.5, 0.5 是居中0, 0 是左下角用得最多的就是居中。实战案例把一张 Texture 变成 Sprite 并显示我们做一个完整的例子——把一张纹理加工成 Sprite然后显示到 Image 上usingUnityEngine;usingUnityEngine.UI;publicclassTextureToSprite:MonoBehaviour{publicTexture2DsourceTexture;// 原始布料publicImagetargetImage;// 学院派画师 ImagevoidStart(){// 用裁缝剪刀把整块布料裁成一件成衣SpritespriteSprite.Create(sourceTexture,newRect(0,0,sourceTexture.width,sourceTexture.height),newVector2(0.5f,0.5f));// 把成衣交给 Image 显示targetImage.spritesprite;}}这段代码就是布料变成衣、成衣被穿上的完整代码演绎。还记得上一篇说的吗Image 只认成衣Sprite不认布料Texture。这段代码正是当我们手里只有一块布料Texture时用Sprite.Create现场把它加工成成衣好让挑剔的 Image 能够接受。实战案例从图集里裁出多个小图标Sprite.Create 最能体现一块布裁多件衣威力的场景是从一张大图集里切出多个 Sprite。假设我们有一张 128×128 的图集上面均匀排列着 4×4 共 16 个 32×32 的小图标usingUnityEngine;usingSystem.Collections.Generic;publicclassSliceAtlas:MonoBehaviour{publicTexture2Datlas;// 一整张图集布料voidStart(){ListSpritespritesnewListSprite();intcell32;// 每个小图标 32×32// 一行一行、一列一列地裁for(inty0;yatlas.height;ycell){for(intx0;xatlas.width;xcell){// 每次从不同位置裁下一块 32×32 的小方块SpritesSprite.Create(atlas,newRect(x,y,cell,cell),newVector2(0.5f,0.5f));sprites.Add(s);}}Debug.Log($从一块布料上一共裁出了{sprites.Count}件成衣);}}运行后你会看到从一整张图集布料上一次性裁出了 16 个独立的 Sprite。这就是一块布料裁多件成衣在代码里的真实写照——通过改变那个裁剪矩形 Rect 的位置同一张 Texture 就贡献出了众多各不相同的 Sprite。这正是游戏里图集技术的核心实现原理。Sprite 的一些关键属性裁好的 Sprite也携带着自己的信息我们可以读取它们SpritessomeSprite;Texture2DitsTextures.texture;// 它源自哪块布料成衣追溯到布料RectitsRects.rect;// 它对应布料上的哪块区域Vector2itsPivots.pivot;// 它的轴心点在哪Rectborders.border;// 它的九宫格边界信息特别注意那个s.texture——它印证了我们反复强调的联系每一件成衣都能追溯到它所来自的那块布料。剥开 Sprite 的外衣里面装的确实还是 Texture。代码在这里为我们那个比喻提供了最有力的证据。三、动态下载图片布料从远方来理解了创建和裁剪我们来看一个综合实战——处理从网络下载的图片。这正是上一篇提到的 Raw Image 的经典场景。我们看看在代码里一张远道而来的图片是如何一步步落地的usingUnityEngine;usingUnityEngine.UI;usingUnityEngine.Networking;usingSystem.Collections;publicclassDownloadImage:MonoBehaviour{publicRawImagerawImage;// 自由派画师直接认布料publicstringurl;voidStart(){StartCoroutine(Download());}IEnumeratorDownload(){// 发起一个专门用来下载纹理的网络请求UnityWebRequestrequestUnityWebRequestTexture.GetTexture(url);yieldreturnrequest.SendWebRequest();if(request.resultUnityWebRequest.Result.Success){// 从下载结果里直接取出一块布料TextureTexture2DtexDownloadHandlerTexture.GetContent(request);// 布料直接交给自由派画师 RawImage 显示rawImage.texturetex;Debug.Log($下载成功得到一块{tex.width}×{tex.height}的布料);}else{Debug.LogError(下载失败request.error);}}}这段代码把前几篇的知识全部串了起来从网络下载回来的天然就是一块Texture布料这块布料来不及、也没必要加工成成衣于是直接交给只认布料的自由派画师RawImage来显示。整个过程行云流水正是动态内容找 Texture、找 Raw Image这条原则的代码化身。如果你偏要用挑剔的 Image 来显示它那就得多走一步——先用Sprite.Create把这块布料现场加工成成衣SpritespriteSprite.Create(tex,newRect(0,0,tex.width,tex.height),Vector2.one*0.5f);someImage.spritesprite;一步之差正是两位画师用料标准不同的直接体现。四、别忘了收尾内存的管理最后要提一个容易被忽略、却很重要的话题——用代码创建的纹理是需要收拾的。当你用new Texture2D创建了纹理或者从网络下载了纹理它们会占用内存。这些不是从项目资源里加载的、而是运行时动态产生的纹理Unity 不会自动帮你清理。当你不再需要它们时应该主动销毁释放内存Destroy(myTexture);// 用完了主动销毁这块动态创建的布料释放内存这就像裁缝铺里用剩的边角料如果堆着不清理久而久之就会占满整个房间。养成用完即清的好习惯你的程序才不会因为纹理越攒越多而内存爆满。这是一条重要的实战纪律凡是代码动态创建或下载的纹理用完记得销毁。而从项目里正常导入、被 Unity 管理的资源则不需要你手动操心。五、尾声让比喻在指尖落地我们从上一篇的布料与成衣比喻出发这一篇终于让它在代码里彻底落了地。我们认识了Texture2D这块可以逐像素作画的布料——它有宽高能被凭空创建能用 SetPixel 一笔笔涂、用 SetPixels 一片片刷而每次改动之后都别忘了那句至关重要的定妆Apply()。我们认识了Sprite这件从布料裁出的成衣——它靠Sprite.Create这把裁缝剪刀诞生靠那三个参数交代清楚用哪块布、从哪里裁多大、轴心设在哪一块布料就能裁出千百件各异的成衣。而它的texture属性又时时提醒着我们它与布料那斩不断的血缘。我们还走了一遍从网络下载布料、直接交给 RawImage 的完整流程印证了那条动态内容找 Texture的实战原则也没忘了叮嘱那条容易被忽视的收尾纪律——动态创建的纹理用完要主动销毁。回头再看那个曾经只是抽象比喻的布料与成衣如今已经变成了你指尖能敲出来的一行行真实代码创建布料是new Texture2D在布料上作画是SetPixel加Apply把布料裁成成衣是Sprite.Create成衣追溯布料是sprite.texture布料从远方来是UnityWebRequestTexture。从知道它们是什么到能亲手创造和操作它们——你对 Texture 与 Sprite 的理解终于完成了从比喻到实现、从原理到代码的完整闭环。而这正是一个开发者成长路上最踏实的一步不仅懂得道理更能让道理在自己的指尖真实地运转起来。