需求

为什么要三番五次的折腾这个Ps2D插件,一遍又一遍的探幽源代码?这次本博的需求是什么?

因为我有一个疑问,我发现在photop上的一个图层的像素位置和包围盒pixel position,到这个图层在Unity Scene场景中transform position我对不上,肉眼看不出规律,所以内部是怎么计算的? (不要在意细节,这个具体案例就不放了,角色太脆弱,已经被我玩的稀碎了)

调试代码

一,刚打开编辑器发生了这些:

LayoutEditor.cs,从unity编辑器Window下打开这个工具就会自动调用这个初始化

void OnEnable(){if (layout == null)layout = ScriptableObject.CreateInstance<Layout>();// reset our map listMapWatcher.Refresh();// fires when the map list change.MapWatcher.mapListChanged += HandleMapChanged;LoadDocument();}

MapWatcher.cs 遍历所有.asset资源,只要以.ps2dmap.json结尾的资源,并对这些资源排序

public static void Refresh(){string[] everyPath = AssetDatabase.GetAllAssetPaths();availableMaps.Clear();foreach (string path in everyPath){if (IsMapFile(path)){availableMaps.Add(path);}}SortMaps();}
static bool IsMapFile(string assetPath){if (assetPath == null) return false;return assetPath.EndsWith(Backpack.MapExtension + ".json");}
static void SortMaps(){availableMaps.Sort(delegate(string a, string b) { return a.CompareTo(b); });}

注册监视,当以.ps2dmap.json结尾的map资源有更新等动静时,就会调用下面的这个函数(因为MapWatcher继承自AssetPostprocessor,就是管理asset资源的):

void HandleMapChanged(){// sanity//if (_layout == null || _layout.document == null || _layout.selectedMapAssetPath == null) return;// did our map file just go away?if (layout.selectedMapAssetPath != null){if (!MapWatcher.availableMaps.Contains(layout.selectedMapAssetPath)){ResetEditor();}else{// lets reload, we could have changed// TODO: an optimization could be to know what if the selected map has changed.LoadDocument();}}#if PS2D_TK2D_forceRefreshOfTk2dCollection = true;
#else
#endifthis.Repaint();}

由于LoadDocument()这时候layout.layoutDocumentAsset == null,所以没啥东西,界面就是这样的:

控制界面的代码如下:

public void OnGUI(){// start a scrollable windowlayout.scrollPositionForOptions = EditorGUILayout.BeginScrollView(layout.scrollPositionForOptions,GUILayout.ExpandWidth(true),GUILayout.ExpandHeight(true));MakeHeader(); //显示版本号if (MapWatcher.availableMaps.Count == 0){MakeNoMaps();MakeGettingStarted();}else{GUILayout.Space(10);MakeMapPopup(); //显示唯一的选择项(如上图所示)if (layout == null || layout.document == null || layout.layoutDocumentAsset == null){MakeChooseMap();}else{// show the image source popupMakeImageSourcePopup();// what did they select?switch (layout.imageSource){case TextureSource.None:EditorGUILayout.HelpBox("Texture could not be auto-detected.  Please choose your textures.", MessageType.Error, true);break;case TextureSource.AssetFolder:MakeImageAssetsFolderField();break;case TextureSource.Spritesheet:MakeSpritesheetField();break;
#if PS2D_TK2Dcase TextureSource.Tk2dSpriteCollection:MakeTk2dSpriteCollectionField();break;
#endifdefault:break;}// do we have a valid iamge source?if (layout.hasValidImageSource()){MakeAssembleButton();MakeSettings();}}}EditorGUILayout.EndScrollView();if (GUI.changed){EditorUtility.SetDirty(layout);}}
void MakeMapPopup(){// grab the friendly names of the mapsList<string> availableMaps = MapWatcher.availableMaps;List<string> mapNames = availableMaps.ConvertAll<string>(each => Path.GetFileNameWithoutExtension(each).Replace(Backpack.MapExtension, ""));// create a friendly list for users// TODO:  Popups don't support duplicates, so might want to deal with this with nested lists or somethingList<string> chooseables = new List<string>();chooseables.Add("-"); //默认不选择资源的时候就是这样的chooseables.AddRange(mapNames);List<GUIContent> guiChooseables = chooseables.ConvertAll<GUIContent>(each => new GUIContent(each));// remember the old choicestring previousChoice = layout.selectedMapAssetPath;// what has the user selected?int selectedIndex = availableMaps.IndexOf(layout.selectedMapAssetPath);GUIContent labelContent = new GUIContent();labelContent.text = string.Format("{0} Map", Backpack.Name);labelContent.tooltip = "Choose a map file that was generated by the Photoshop plugin.";// show the popupselectedIndex++;selectedIndex = EditorGUILayout.Popup(labelContent, selectedIndex, guiChooseables.ToArray());selectedIndex--;// did the user get something good?if (selectedIndex >= 0){layout.selectedMapAssetPath = availableMaps[selectedIndex];}else{layout.selectedMapAssetPath = null;}bool isNull = layout.selectedMapAssetPath == null;bool isDifferent = layout.selectedMapAssetPath != previousChoice;bool isNotLoaded = layout.selectedMapAssetPath != null && layout.document == null;if (isNull){layout.layoutDocumentAsset = null;layout.ResetLayout();}else if (isDifferent || isNotLoaded){layout.layoutDocumentAsset = (TextAsset)AssetDatabase.LoadMainAssetAtPath(layout.selectedMapAssetPath);LoadDocument(); //如果选择了资源,会再次调用这个函数,Load一些有用的信息}}

二,选择Ps2D Map文件后发生了这些:

通过OnGUI(),调用到LoadDocument()函数,获取json文件中的图层信息,并在本地文件夹下找到对应的png图片资源,建立对应关系:

void LoadDocument(){if (layout.layoutDocumentAsset == null) return;// load the maplayout.Load();// update the root friendly nameif (layout.document != null & layout.document.allLayers != null){layout.document.allLayers[0].photoshopLayerName = layout.GetFriendlyDocumentName();}// try to auto-detect the texture sourceFormatGuesser guesser = new FormatGuesser();guesser.layout = layout;switch (guesser.Guess()){case TextureSource.AssetFolder:SpriteAssigner.AssignSpritesFromFolder(layout);break;case TextureSource.Spritesheet:SpriteAssigner.AssignSpritesFromSpritesheet(layout);break;
#if PS2D_TK2Dcase TextureSource.Tk2dSpriteCollection:_spriteCollectionData = guesser.spriteCollectionData;SpriteAssigner.AssignSpritesFromTk2dCollection(layout, _spriteCollectionData);break;
#endifdefault:break;}}

Layout.cs,加载这个选中的json文件内容,通过Loader类和LayerMap等类进行一系列通信,创建图层信息(Unity)

public void Load(){ResetLayout();if (layoutDocumentAsset == null) return;// load!Loader _loader = new Loader(); //这个不多介绍,可以认为是载入json文件的解析器_loader.input = layoutDocumentAsset.text;_loader.Load();// assign the documentif (!_loader.errorParsing){document = _loader.document;CreateSpriteLayers();ResetHierarchyLabels();}}
public void ResetLayout(){startingLayer = null;document = null;//if (spriteLayers != null){spriteLayers.Clear();}}
void CreateSpriteLayers(){// safety firstif (document == null ||document.allLayers == null) return;// with each photoshop layerforeach (Layer layer in document.allLayers){// create a sprite layerUnitySpriteLayer spriteLayer = ScriptableObject.CreateInstance<UnitySpriteLayer>();// that holds these detailsspriteLayer.spriteLayerStyle = SpriteLayerStyle.None;spriteLayer.layer = layer;spriteLayer.name = layer.photoshopLayerName;spriteLayer.isVisible = layer.isVisible;// add to our listspriteLayers.Add(spriteLayer);}// put them in the correct orderspriteLayers.Sort(delegate(UnitySpriteLayer a, UnitySpriteLayer b){return a.layer.order.CompareTo(b.layer.order);});}
public void ResetHierarchyLabels(){// wipe em outhierarchyLabels.Clear();// safety jetif (document == null || document.allLayers == null) return;foreach (Layer layer in document.allLayers){StringBuilder label = new StringBuilder();// build some indentsfor (int i = 1; i < layer.indentLevel; i++){label.Append("   ");}if (layer.indentLevel > 1){label.Append("> ");}// add the best layer namelabel.Append(GetBestName(layer));// add this the labelhierarchyLabels.Add(label.ToString());}// change the root node to something specialhierarchyLabels[0] = "-- Everything --";}

显示接下来的UI界面:

void MakeImageAssetsFolderField(){if (layout == null ||layout.document == null ||layout.imageSource != TextureSource.AssetFolder) return;var previousValue = layout.imageSourceAssetFolder;GUIContent contentLabel = new GUIContent();contentLabel.text = "Asset Folder";contentLabel.tooltip = "The asset folder which contains the textures.";layout.imageSourceAssetFolder = EditorGUILayout.ObjectField(contentLabel, layout.imageSourceAssetFolder, typeof(Object), false);// did we change?if (layout.imageSourceAssetFolder != null && previousValue != layout.imageSourceAssetFolder){SpriteAssigner.AssignSpritesFromFolder(layout);}}
void MakeAssembleButton(){GUILayout.Space(10);GUIContent buttonContent = new GUIContent("Assemble!");buttonContent.image = layout.editorGraphics.usain;GUIStyle buttonStyle = new GUIStyle(GUI.skin.GetStyle("button"));buttonStyle.padding = new RectOffset(10, 10, 10, 10);if (GUILayout.Button(buttonContent, buttonStyle, GUILayout.ExpandWidth(true))){layout.startingLayer = null;
#if PS2D_TK2DSpriteCreator.CreateSprites(layout, null, _spriteCollectionData);
#elseSpriteCreator.CreateSprites(layout, null);
#endif}GUILayout.Space(10);}
private void MakeSettings(){CreateSubtitle("Settings");GUIStyle optionsStyle = new GUIStyle();optionsStyle.padding = new RectOffset(4, 8, 0, 8);optionsStyle.margin = new RectOffset(4, 4, 0, 8);GUILayout.BeginVertical(optionsStyle);layout.foldoutPosition = EditorGUILayout.Foldout(layout.foldoutPosition, "Position");if (layout.foldoutPosition){EditorGUI.indentLevel++;MakeAnchorField();MakeTrimToSpritesField();EditorGUI.indentLevel--;}layout.foldoutScale = EditorGUILayout.Foldout(layout.foldoutScale, "Scale");if (layout.foldoutScale){EditorGUI.indentLevel++;MakePixelsToUnits();MakeCoordinatesScalingField();MakeScalingField();EditorGUI.indentLevel--;}layout.foldoutPrefabs = EditorGUILayout.Foldout(layout.foldoutPrefabs, "Prefabs");if (layout.foldoutPrefabs){EditorGUI.indentLevel++;MakeReplicatorField();EditorGUI.indentLevel--;}layout.foldoutDepth = EditorGUILayout.Foldout(layout.foldoutDepth, "Depth");if (layout.foldoutDepth){EditorGUI.indentLevel++;MakeSortingLayerNameField();MakeSortingOrderField();MakeSortingOrderStepField();MakeZOffsetField();EditorGUI.indentLevel--;}layout.foldoutPhysics = EditorGUILayout.Foldout(layout.foldoutPhysics, "Physics");if (layout.foldoutPhysics){EditorGUI.indentLevel++;MakeRootColliderField();MakeRootColliderIsTrigger();EditorGUI.indentLevel--;}layout.foldoutNesting = EditorGUILayout.Foldout(layout.foldoutNesting, "Nesting");if (layout.foldoutNesting){EditorGUI.indentLevel++;MakePhotoshopLayoutOption();MakeSpriteParentOption();EditorGUI.indentLevel--;}MakeAssembleFromLayer();GUILayout.EndVertical();}


重点是要展示Assemble Frome Layer(7)下面都有啥,就是展示json文件中的图层信息和asset文件夹下的png图片是否对应上:

private void MakeAssembleFromLayer(){bool hasLayers = layout != null && layout.document != null && layout.document.allLayers != null;string folderLabel = "Assemble From Layer";if (hasLayers){folderLabel = string.Format("{0} ({1:d})", folderLabel, layout.document.allLayers.Count);}layout.foldoutFiltering = EditorGUILayout.Foldout(layout.foldoutFiltering, folderLabel);if (layout.foldoutFiltering){EditorGUI.indentLevel++;if (layout != null && layout.document != null && layout.document.allLayers != null){foreach (Layer layer in layout.document.allLayers){MakeAssembleFromLayerNode(layer);}}EditorGUI.indentLevel--;}}

这里,好像bound等信息有变化,但是我没注意到。后续再更新

三,点击Assemble按钮发生了这些:

创建精灵到Scene中:

public static void CreateSprites(Layout layout, Layer startingLayer){SpriteCreator creator = new SpriteCreator();creator.layout        = layout;creator.CreateSprites(startingLayer);}
public void CreateSprites(Layer startingLayer){// line up the duckslayout.UpdateSpriteLayerProperties();// create the root object to attach all these spritesstring assetPathToDocument = AssetDatabase.GetAssetPath(layout.layoutDocumentAsset);string rootName            = Path.GetFileNameWithoutExtension(assetPathToDocument).Replace(Backpack.MapExtension, "");// do we need to trim the sprite bounds?if (layout.trimToSprites){// calculate the new bounds and cache it.layout.trimmedSpriteBounds = layout.GetTrimmedSpriteBounds();}// are we starting from a specific node?if (startingLayer != null){rootName = layout.GetBestName(startingLayer);}GameObject root;// recreate the nested photoshop layers?if (layout.preservePhotoshopLayers){Layer fromHere = startingLayer;if (fromHere == null){fromHere = layout.document.root;}root = CreateGameObjectHierarchyForLayer(fromHere, null);if (startingLayer == null && root != null){root.name = rootName;}}else{List<UnitySpriteLayer> spriteLayers = layout.GetSelectedSpriteLayers();// if we're just doing 1 sprite, make that sprite become the rootif (spriteLayers.Count == 1){root = CreateGameObjectForSpriteLayer(spriteLayers[0], null);}else{root = new GameObject(rootName);// nah, just use flat spritesforeach (UnitySpriteLayer spriteLayer in spriteLayers){CreateGameObjectForSpriteLayer(spriteLayer, root.transform);}}}
public void UpdateSpriteLayerProperties(){UpdateSpriteLayerZOffsets();UpdateSpriteLayerSortingNames();UpdateSpriteLayerSortOrder();UpdateTk2dSpriteCollections();}

其中用到了修剪Bounds,通过遍历查看,我明白了这个函数是要返回所有图层的最左上角和最右下角,也就是整体图层的大包围盒:

public PixelBounds GetTrimmedSpriteBounds(){PixelBounds bounds = new PixelBounds();bounds.x = int.MaxValue;bounds.y = int.MaxValue;int maxX = int.MinValue;int maxY = int.MinValue;// grab the list of visible spritesList<UnitySpriteLayer> spriteLayers = GetSelectedSpriteLayers();foreach (UnitySpriteLayer spriteLayer in spriteLayers){PixelBounds layerBounds = spriteLayer.layer.bounds;bounds.x = System.Math.Min(bounds.x, layerBounds.x);bounds.y = System.Math.Min(bounds.y, layerBounds.y);maxX = System.Math.Max(maxX, layerBounds.x + layerBounds.width);maxY = System.Math.Max(maxY, layerBounds.y + layerBounds.height);}bounds.width = maxX - bounds.x;bounds.height = maxY - bounds.y;return bounds;}

这里我们列表一下其中一个图层的信息:

然后最关键的让我懵逼的函数来了!!!!!!!!!!!!

        public GameObject CreateGameObjectForSpriteLayer(UnitySpriteLayer spriteLayer, Transform parentTransform){// spawn up the game objectGameObject goSprite;if (layout.replicator != null){goSprite = GameObject.Instantiate(layout.replicator, Vector3.zero, Quaternion.identity) as GameObject;}else{goSprite = new GameObject();}goSprite.name = spriteLayer.name;// attach the appropriate sprite// do we have a sprite to set?if (spriteLayer.hasSprite){#if PS2D_TK2Dif (spriteLayer.sprite != null){spriteLayer.AttachUnitySprite(goSprite);}else if (spriteLayer.tk2dSpriteName != null){spriteLayer.AttachTk2dSprite(goSprite, spriteCollectionData);}
#elsespriteLayer.AttachUnitySprite(goSprite);
#endif}// where should this sprite be?PixelBounds bounds = layout.trimToSprites ? layout.trimmedSpriteBounds : layout.document.bounds;Vector3 position = spriteLayer.GetPixelPosition(layout.coordinatesScale, bounds);float x = position.x;float y = position.y;float z = position.z;// layout the sprite based on the anchorVector2 offset = new Vector2(bounds.width, bounds.height);switch (layout.anchor){case TextAnchor.LowerCenter:offset.x = offset.x * 0.5f;offset.y = offset.y * 0f;break;case TextAnchor.LowerLeft:offset.x = offset.x * 0f;offset.y = offset.y * 0f;break;case TextAnchor.LowerRight:offset.x = offset.x * 1f;offset.y = offset.y * 0f;break;case TextAnchor.MiddleCenter:offset.x = offset.x * 0.5f;offset.y = offset.y * 0.5f;break;case TextAnchor.MiddleLeft:offset.x = offset.x * 0f;offset.y = offset.y * 0.5f;break;case TextAnchor.MiddleRight:offset.x = offset.x * 1f;offset.y = offset.y * 0.5f;break;case TextAnchor.UpperCenter:offset.x = offset.x * 0.5f;offset.y = offset.y * 1f;break;case TextAnchor.UpperLeft:offset.x = offset.x * 0f;offset.y = offset.y * 1f;break;case TextAnchor.UpperRight:offset.x = offset.x * 1f;offset.y = offset.y * 1f;break;default:break;}// let's move it!x -= offset.x * layout.coordinatesScale;y -= offset.y * layout.coordinatesScale;// the pixels to units ratiox *= 1f / layout.pixelsToUnits;y *= 1f / layout.pixelsToUnits;// should we create an extra parent for the sprite?if (layout.addExtraParentToSprite){GameObject goSpriteParent         = new GameObject(goSprite.name);goSpriteParent.transform.position = new Vector3(x, y, z);goSpriteParent.transform.parent   = parentTransform;goSprite.transform.parent         = goSpriteParent.transform;goSprite.transform.localPosition  = Vector3.zero;}else{goSprite.transform.position = new Vector3(x, y, z);goSprite.transform.parent = parentTransform;}return goSprite;}


继续单步执行走下去,走到了这里,这个函数应该是根据整体的包围盒重新更正该图层的一些信息,包括center,pivot等信息:

public Vector3 GetPixelPosition(float coordinatesScale, PixelBounds fromBounds){if (!hasSprite){return Vector3.zero;}// figure out the placementVector2 layerCenter = layer.bounds.GetCenter();float x             = (layerCenter.x - fromBounds.x) * coordinatesScale;float y             = (fromBounds.height - layerCenter.y + fromBounds.y) * coordinatesScale;float z             = this.z;// adjust for pivots (literally a day to figure this out... i fail so hard)Vector3 spriteCenter  = bounds.center;Vector3 spriteExtents = bounds.extents;float pivotX          = spriteCenter.x / -spriteExtents.x * (layer.bounds.halfWidth * coordinatesScale);float pivotY          = spriteCenter.y / -spriteExtents.y * (layer.bounds.halfHeight * coordinatesScale);Vector2 pivotOffset   = new Vector2(pivotX, pivotY);x += pivotOffset.x;y += pivotOffset.y;return new Vector3(x, y, z);}

还拿第一个图层举例子上面的函数到底都经历了什么:


经历了裁剪和坐标转换,就成了在场景中的transform的位置。但是我就想要原汁原味的,那就是更改成下面的参数就行了,但是注意这里的局部坐标系是这样的,和unity的方向是一致的:

其实这个参数更改的就是模型在unity中局部坐标系原点位置~~~在unity下用如下情况就能看出区别

框架梳理

类图:

课堂小知识

1、
发现了一个神奇的东西,一个json文件依然可以被看做是asset资源,通过

(TextAsset)AssetDatabase.LoadMainAssetAtPath(layout.selectedMapAssetPath)

可以直接读取到这个json文件的文件内容,转化为字符串的那种!!

2、

【Unity】对 Center/Pivot 和 Global/Local 的理解

Ps2D插件源码再分析相关推荐

  1. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  2. Vuex源码阅读分析

    Vuex源码阅读分析 Vuex是专为Vue开发的统一状态管理工具.当我们的项目不是很复杂时,一些交互可以通过全局事件总线解决,但是这种观察者模式有些弊端,开发时可能没什么感觉,但是当项目变得复杂,维护 ...

  3. MyBatis源码骨架分析

    源码包分析 MyBatis 源码下载地址:https://github.com/MyBatis/MyBatis-3 MyBatis源码导入过程: 下载MyBatis的源码 检查maven的版本,必须是 ...

  4. [Java] HashMap 源码简要分析

    特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似. 源码简要分析 publ ...

  5. android view 源码分析,Android ViewPager源码详细分析

    1.问题 由于Android Framework源码很庞大,所以读源码必须带着问题来读!没有问题,创造问题再来读!否则很容易迷失在无数的方法与属性之中,最后无功而返. 那么,关于ViewPager有什 ...

  6. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

  7. 推荐 7 个 Vue2、Vue3 源码解密分析的开源项目

    大家好,我是你们的 猫哥,那个不喜欢吃鱼.又不喜欢喵 的超级猫 ~ 1. 为什么要学习源码 ? 阅读优秀的代码的目的是让我们能够写出优秀的代码. 不给自己设限,不要让你周围人的技术上限成为你的上限.其 ...

  8. SpringBoot源码笔记分析

    SpringBoot源码笔记分析 1.基础 1.配置SpringBoot热部署 1.引入依赖 <dependency><groupId>org.springframework. ...

  9. Toast源码深度分析

    目录介绍 1.最简单的创建方法 1.1 Toast构造方法 1.2 最简单的创建 1.3 简单改造避免重复创建 1.4 为何会出现内存泄漏 1.5 吐司是系统级别的 2.源码分析 2.1 Toast( ...

最新文章

  1. NHibernate从入门到精通系列(7)——多对一关联映射
  2. Python 在编程语言中是什么地位?为什么很多大学不教 Python?
  3. hadoop 2.0 详细配置教程
  4. 给妹子讲python-S01E19解析Python内嵌作用域与函数闭包
  5. Leaflet绘制热力图【转】
  6. TCP/IP详解--学习笔记(3)-IP协议,ARP协议,RARP协议
  7. ORA-07445导致实例崩溃的解决【The solution of instance crush by ORA-07445】
  8. 通过崩溃地址找错误行数之Delphi版
  9. 【iOS XMPP】使用XMPPFramewok(二):用户登录
  10. cmap参数 plt_plt.imshow的参数有哪些?
  11. Thunder团队第二周 - Scrum会议3
  12. matlab数据的获取、预处理、统计、可视化、降维 | 《matlab数学建模方法与实践(第三版)》学习笔记
  13. Python 函数的嵌套
  14. 【转载】C++编码规范与指导
  15. HNOI 2015 落忆枫音 题解
  16. grafana配置alert
  17. put与mput_ftpput命令详解 ftp put命令使用哪个端口?
  18. 经济学基础(本)【1】
  19. 徐辉 北大计算机,学院信息
  20. Windows提权基本原理,各位表哥了解下!

热门文章

  1. JS中函数传参按照值传递
  2. Pandas常用方法总结
  3. python k-means聚类算法 物流分配预测实战(超详细,附源码)
  4. I2C 挂死原因分析及解决方案
  5. Qt和C++学习笔记
  6. 深入虚拟内存(Virtual Memory,VM)
  7. Android如何实时监控CPU频率
  8. 解析世界杯超大规模直播场景下的码率控制
  9. 服务器监控-grafana,influxdb,prometheus
  10. ‘collections.defaultdict‘ object has no attribute ‘iteritems‘