Unity性能优化之特效合并

时间:2022-12-23 19:13:22

特效合并,意思是说将粒子所用的零碎图片,以shader为单位合并成一张图集,好处就是可以降低draw call。试想,合并前每个粒子使用一个material,而每一个material就要占用一个drawcall,而合并后多个粒子可以用同一个material,这样就降低了drawcall,提升了性能。

转载请注明出处:http://www.cnblogs.com/jietian331/p/8625078.html

合并工具的代码如下:

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine; namespace AssetBundle
{
public class ParticleSystemCombiner : ScriptableObject
{
public const string
AtlasFolder = "Assets/Cloth/Resources/ParticleSystemAtlas",
ParticleAtlasPath = "Assets/Cloth/Resources/ParticleSystemAtlas/particle_atlas.prefab",
SettingFilepath = "Assets/Editor/ParticleSystemCombinerSetting.csv"; static string[] EffectObjFolders = new string[]
{
"Assets/Cloth/Resources/Effect/Cloth",
"Assets/Cloth/Resources/Model/Equip",
}; static ParticleAtlases s_atlasesData;
static List<string> s_materials;
static Dictionary<string, int> s_texturesSize; static ParticleAtlases AtlasesData
{
get
{
if (s_atlasesData == null)
s_atlasesData = AssetDatabase.LoadAssetAtPath<ParticleAtlases>(ParticleAtlasPath);
return s_atlasesData;
}
} static Dictionary<string, int> TexturesSize
{
get
{
if (s_texturesSize == null)
{
s_texturesSize = new Dictionary<string, int>(); string[] lines = File.ReadAllLines(SettingFilepath);
bool decode = false;
foreach (var line in lines)
{
if (!decode)
{
if (line.StartsWith("# Texture Size"))
{
decode = true;
}
}
else
{
if (line.StartsWith("#"))
{
decode = false;
}
} if (decode)
{
string[] words = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (words.Length > )
{
string name = words[];
int size;
int.TryParse(words[], out size);
if (size != )
s_texturesSize[name] = size;
}
}
}
}
return s_texturesSize;
}
} static List<string> NotCombineTextures
{
get
{
List<string> list = new List<string>();
string[] lines = File.ReadAllLines(SettingFilepath);
bool decode = false;
foreach (var line in lines)
{
if (!decode)
{
if (line.StartsWith("# Not Combine Textures"))
{
decode = true;
}
}
else
{
if (line.StartsWith("#"))
{
decode = false;
}
} if (decode)
{
string[] words = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (words.Length > && !string.IsNullOrEmpty(words[]))
{
list.Add(words[]);
}
}
}
return list;
}
} #region for build public static void ClearCache()
{
s_atlasesData = null;
s_materials = null;
} public static List<ParticleAtlases.TextureItem> GetParticlesUsedAtlas(GameObject effectObj)
{
List<ParticleAtlases.TextureItem> texturesData = new List<ParticleAtlases.TextureItem>();
ParticleSystem[] particles = effectObj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer render = ps.GetComponent<ParticleSystemRenderer>();
if (!render || !render.sharedMaterial)
{
Debug.LogWarning("Particle no material: " + ps.name);
continue;
} Texture texture = render.sharedMaterial.mainTexture;
if (ps.textureSheetAnimation.enabled || !texture)
continue; foreach (var atlasData in AtlasesData.Atlases)
{
foreach (var t in atlasData.Textures)
{
if (t.Name == texture.name)
{
texturesData.Add(t);
break;
}
}
}
} return texturesData;
} public static void ProcessEffectObj(GameObject obj)
{
ParticleSystem[] particles = obj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer render = ps.GetComponent<ParticleSystemRenderer>();
if (!render || !render.sharedMaterial)
{
Debug.LogWarning("Particle no material: " + ps.name);
continue;
} Texture texture = render.sharedMaterial.mainTexture;
if (ps.textureSheetAnimation.enabled || !texture)
continue; ParticleAtlases.Atlas target = AtlasesData.Atlases.FirstOrDefault(a => a.Textures.Any(t => t.Name == texture.name && t.ShaderName == render.sharedMaterial.shader.name)); if (target != null)
{
ParticleLoader loader = ps.GetComponent<ParticleLoader>();
if (!loader)
loader = ps.gameObject.AddComponent<ParticleLoader>();
loader.TextureName = texture.name;
loader.ShaderName = render.sharedMaterial.shader.name;
render.sharedMaterial = null; if (!ps.trails.enabled)
render.trailMaterial = null;
}
} EditorUtility.SetDirty(obj);
AssetDatabase.SaveAssets();
} public static List<string> GetAllMaterials()
{
if (s_materials != null)
return s_materials; List<string> effects;
Dictionary<Shader, List<ParticleSystem>> dic = GetAllParticles(out effects);
s_materials = new List<string>(); foreach (var pair in dic)
{
foreach (ParticleSystem ps in pair.Value)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
string path = AssetDatabase.GetAssetPath(r.sharedMaterial);
if (!s_materials.Contains(path))
s_materials.Add(path);
}
} return s_materials;
} #endregion [MenuItem("BuildTool/AssetBundle/Combine Particle System")]
static void Init()
{
CombineAllEffectTextures(); EditorUtility.DisplayDialog("finished", "All work finished.", "ok");
} static Dictionary<Shader, List<ParticleSystem>> GetAllParticles(out List<string> effectObjs)
{
// 获取所有的粒子
List<ParticleSystem> particlesList = new List<ParticleSystem>();
List<string> objPaths = new List<string>();
foreach (var effectObjFolder in EffectObjFolders)
{
string[] paths = Directory.GetFiles(effectObjFolder, "*.prefab", SearchOption.AllDirectories);
objPaths.AddRange(paths);
}
effectObjs = new List<string>(); foreach (string path in objPaths)
{
string pathFixed = path.Replace("\\", "/");
effectObjs.Add(pathFixed);
GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(pathFixed);
ParticleSystem[] particles = obj.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem ps in particles)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
bool needCombine = !ps.textureSheetAnimation.enabled
&& r.sharedMaterial
&& r.sharedMaterial.shader.name != "Particles/Alpha Blended Premultiply"
&& r.sharedMaterial.mainTexture
&& r.sharedMaterial.mainTexture.width == r.sharedMaterial.mainTexture.height;
if (needCombine)
particlesList.Add(ps);
}
} // 分类
Dictionary<Shader, List<ParticleSystem>> dic = new Dictionary<Shader, List<ParticleSystem>>();
foreach (ParticleSystem ps in particlesList)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
var shader = r.sharedMaterial.shader;
if (!dic.ContainsKey(shader))
dic[shader] = new List<ParticleSystem>();
dic[shader].Add(ps);
} return dic;
} public static List<string> CombineAllEffectTextures()
{
List<string> atlases = new List<string>(); // 获取所有的粒子
List<string> effects;
Dictionary<Shader, List<ParticleSystem>> dic = GetAllParticles(out effects); // combine
Dictionary<Texture2D, Material> dictTextures = new Dictionary<Texture2D, Material>();
List<ParticleAtlases.Atlas> atlasesData = new List<ParticleAtlases.Atlas>();
List<string> notCombineTextures = NotCombineTextures; foreach (var pair in dic)
{
// get textures
dictTextures.Clear();
foreach (ParticleSystem ps in pair.Value)
{
ParticleSystemRenderer r = ps.GetComponent<ParticleSystemRenderer>();
Texture2D texture = (Texture2D)r.sharedMaterial.mainTexture;
if (!notCombineTextures.Contains(texture.name) && !dictTextures.ContainsKey(texture))
dictTextures.Add(texture, r.sharedMaterial);
} if (dictTextures.Count < )
continue; Texture2D[] texturesArray = dictTextures.Keys.ToArray(); // combine texture
string atlasName = string.Format("ParticleAtlas_{0}", Path.GetFileNameWithoutExtension(pair.Key.name));
string atlasPath = string.Format("{0}/{1}.png", AtlasFolder, atlasName);
Uploader.CreateDirectory(atlasPath);
Rect[] rects;
Vector2[] textureSizes;
Texture2D atlas = CombineTextures(texturesArray, atlasPath, out rects, out textureSizes);
atlases.Add(atlasPath); // create material
string matPath = string.Format("{0}/{1}.mat", AtlasFolder, atlasName);
Material mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
if (mat == null)
{
mat = new Material(pair.Key);
AssetDatabase.CreateAsset(mat, matPath);
}
mat.mainTexture = atlas; // get config
ParticleAtlases.TextureItem[] texturesData = new ParticleAtlases.TextureItem[texturesArray.Length];
for (int i = ; i < texturesArray.Length; i++)
{
Rect rect = rects[i];
Texture2D texture2D = texturesArray[i];
Vector2 textureSize = textureSizes[i]; // will resize temp texture, so cann't use texture2D.width
int numTilesX = (int)(atlas.width / textureSize.x);
int numTilesY = (int)(atlas.height / textureSize.y);
int colIndex = (int)(rect.x * numTilesX);
int rowIndex = (int)(numTilesY - - rect.y * numTilesY);
int index = rowIndex * numTilesX + colIndex; // get color
Material oldMat = dictTextures[texture2D];
Color32 oldColor = oldMat.GetColor("_TintColor");
string strColor = string.Format("{0}_{1}_{2}_{3}", oldColor.r, oldColor.g, oldColor.b, oldColor.a); string shaderName = oldMat.shader.name; int depth = oldMat.renderQueue; texturesData[i] = new ParticleAtlases.TextureItem()
{
Color = oldColor,
Depth = depth,
Index = index,
Name = texture2D.name,
NumTilesX = numTilesX,
NumTilesY = numTilesY,
ShaderName = shaderName,
};
} ParticleAtlases.Atlas atlasData = new ParticleAtlases.Atlas()
{
Material = mat,
Textures = texturesData,
};
atlasesData.Add(atlasData);
} GameObject prefabObj = AssetDatabase.LoadAssetAtPath<GameObject>(ParticleAtlasPath);
if (!prefabObj)
prefabObj = PrefabUtility.CreatePrefab(ParticleAtlasPath, new GameObject());
ParticleAtlases atlasesCom = prefabObj.GetComponent<ParticleAtlases>();
if (!atlasesCom)
atlasesCom = prefabObj.AddComponent<ParticleAtlases>();
atlasesCom.Atlases = atlasesData.ToArray();
prefabObj.name = Path.GetFileNameWithoutExtension(ParticleAtlasPath); EditorUtility.SetDirty(prefabObj);
AssetDatabase.SaveAssets(); Debug.Log("All cloth effect textures combine finished!"); return atlases;
} static Texture2D CombineTextures(Texture2D[] textures, string path, out Rect[] rects, out Vector2[] textureSizes)
{
if (textures == null || textures.Length < )
{
Debug.LogError("None textures");
rects = null;
textureSizes = null;
return null;
} string tempFolderName = "_TempImages";
string tempFolder = "Assets/" + tempFolderName;
AssetDatabase.DeleteAsset(tempFolder);
AssetDatabase.CreateFolder("Assets", tempFolderName); List<string> newPaths = new List<string>();
var newTextures = new Texture2D[textures.Length];
textureSizes = new Vector2[textures.Length]; // 将原来的图片复制一份出来
for (int i = ; i < textures.Length; i++)
{
string texPath = AssetDatabase.GetAssetPath(textures[i]);
if (File.Exists(texPath))
{
string newPath = string.Format("{0}/{1}", tempFolder, Path.GetFileName(texPath));
AssetDatabase.CopyAsset(texPath, newPath);
newPaths.Add(newPath);
}
else
{
Debug.Log("File not exists: " + texPath);
}
} // make it readable
for (int i = ; i < newPaths.Count; i++)
{
string newPath = newPaths[i];
SetSourceTextureReadalbe(newPath);
Texture2D t = AssetDatabase.LoadAssetAtPath<Texture2D>(newPath);
textureSizes[i] = new Vector2(t.width, t.height); // 去掉边缘的一个像素
if (t.width > )
{
for (int j = ; j < t.width; j++)
{
t.SetPixel(j, , new Color(, , , ));
t.SetPixel(j, , new Color(, , , ));
t.SetPixel(j, t.height - , new Color(, , , ));
t.SetPixel(j, t.height - , new Color(, , , ));
}
} if (t.height > )
{
for (int z = ; z < t.height; z++)
{
t.SetPixel(, z, new Color(, , , ));
t.SetPixel(, z, new Color(, , , ));
t.SetPixel(t.width - , z, new Color(, , , ));
t.SetPixel(t.width - , z, new Color(, , , ));
}
} newTextures[i] = t;
} // pack
Texture2D atlas = new Texture2D(, , TextureFormat.ARGB32, false);
rects = atlas.PackTextures(newTextures, , , false); // save
byte[] bytes = atlas.EncodeToPNG();
File.WriteAllBytes(path, bytes);
AssetDatabase.Refresh();
AssetDatabase.SaveAssets(); // setting
TextureCompresser.CompressRGBA(path); // 删除临时目录
AssetDatabase.DeleteAsset(tempFolder); AssetDatabase.Refresh();
AssetDatabase.SaveAssets(); return AssetDatabase.LoadAssetAtPath<Texture2D>(path);
} static void SetSourceTextureReadalbe(string path)
{
string name = Path.GetFileNameWithoutExtension(path);
int maxSize;
TexturesSize.TryGetValue(name, out maxSize);
if (maxSize == )
maxSize = ; bool readable = true;
TextureImporterNPOTScale npotScale = TextureImporterNPOTScale.ToNearest;
TextureWrapMode wrapMode = TextureWrapMode.Clamp;
TextureImporterCompression compression = TextureImporterCompression.Uncompressed; bool changed = false; var importer = (TextureImporter)AssetImporter.GetAtPath(path);
TextureImporterSettings settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings); settings.alphaIsTransparency = true;
settings.mipmapEnabled = false; if (settings.readable != readable)
{
settings.readable = readable;
changed = true;
} if (settings.npotScale != npotScale)
{
settings.npotScale = npotScale;
changed = true;
} if (settings.wrapMode != wrapMode)
{
settings.wrapMode = wrapMode;
changed = true;
} if (importer.maxTextureSize != maxSize)
{
importer.maxTextureSize = maxSize;
changed = true;
} if (importer.textureCompression != compression)
{
importer.textureCompression = compression;
changed = true;
} // set platform overriten as false
var androidSetting = importer.GetPlatformTextureSettings("Android");
var iosSetting = importer.GetPlatformTextureSettings("iPhone");
var pcSetting = importer.GetPlatformTextureSettings("Standalone");
if (androidSetting.overridden)
{
androidSetting.overridden = false;
changed = true;
}
if (iosSetting.overridden)
{
iosSetting.overridden = false;
changed = true;
}
if (pcSetting.overridden)
{
pcSetting.overridden = false;
changed = true;
}
importer.SetPlatformTextureSettings(androidSetting);
importer.SetPlatformTextureSettings(iosSetting);
importer.SetPlatformTextureSettings(pcSetting); if (changed)
{
importer.SetTextureSettings(settings);
AssetDatabase.ImportAsset(path);
}
} }
}

ParticleSystemCombiner

加载的代码:

 using UnityEngine;

 public partial class ParticleLoader : MonoBehaviour
{
public string TextureName;
public string ShaderName;
}

ParticleLoader

 using Common;
using UnityEngine; public partial class ParticleLoader : MonoBehaviour
{
static ParticleAtlases s_atlases; // 加载图集
public static void Initialize()
{
BundleLoader.Singleton.LoadAssetBundle("atlases/particlesystematlas.u", r =>
{
GameObject[] objs = r.Bundle.LoadAllAssets<GameObject>();
s_atlases = objs[].GetComponent<ParticleAtlases>();
});
} void OnEnable()
{
Load();
} void Load()
{
ParticleSystemRenderer renderer = GetComponent<ParticleSystemRenderer>();
if (!renderer)
return; ParticleAtlases.Atlas targetAtlas = null;
ParticleAtlases.TextureItem targetTextureConfig = null; // 找对应的配置
for (int i = ; i < s_atlases.Atlases.Length; i++)
{
ParticleAtlases.Atlas atlasData = s_atlases.Atlases[i];
for (int j = ; j < atlasData.Textures.Length; j++)
{
ParticleAtlases.TextureItem textureData = atlasData.Textures[j];
if (textureData.Name == this.TextureName && textureData.ShaderName == this.ShaderName)
{
targetAtlas = atlasData;
targetTextureConfig = textureData;
break;
}
} if (targetAtlas != null)
break;
} if (targetAtlas != null && targetTextureConfig != null)
{
ParticleSystem ps = GetComponent<ParticleSystem>(); renderer.sharedMaterial = targetAtlas.Material; // 渲染 SetTextureSheet(ps, targetTextureConfig); // 设置格子 SetStartColor(ps, renderer, targetTextureConfig.Color); // 设置颜色 SetDepth(renderer, targetTextureConfig); // 排序
}
} // 设置格子
void SetTextureSheet(ParticleSystem ps, ParticleAtlases.TextureItem targetTextureConfig)
{
var tsa = ps.textureSheetAnimation;
float curveConstant = (float)targetTextureConfig.Index / targetTextureConfig.NumTilesX / targetTextureConfig.NumTilesY;
tsa.enabled = true;
tsa.numTilesX = targetTextureConfig.NumTilesX;
tsa.numTilesY = targetTextureConfig.NumTilesY;
tsa.animation = ParticleSystemAnimationType.WholeSheet;
tsa.startFrame = new ParticleSystem.MinMaxCurve();
tsa.frameOverTime = new ParticleSystem.MinMaxCurve(curveConstant);
tsa.cycleCount = ;
} // 设置颜色
void SetStartColor(ParticleSystem ps, ParticleSystemRenderer renderer, Color matColor)
{
var main = ps.main;
switch (main.startColor.mode)
{
case ParticleSystemGradientMode.Color:
case ParticleSystemGradientMode.Gradient:
case ParticleSystemGradientMode.RandomColor:
case ParticleSystemGradientMode.TwoGradients:
var targetColor = main.startColor.color * matColor;
main.startColor = new UnityEngine.ParticleSystem.MinMaxGradient(targetColor);
break; case ParticleSystemGradientMode.TwoColors:
var colorMin = main.startColor.colorMin * matColor;
var colorMax = main.startColor.colorMax * matColor;
main.startColor = new UnityEngine.ParticleSystem.MinMaxGradient(colorMin, colorMax);
break; default:
Debug.LogError("Unknown mode: " + main.startColor.mode);
break;
} renderer.sharedMaterial.SetColor("_TintColor", Color.white);
} // 排序
void SetDepth(ParticleSystemRenderer renderer, ParticleAtlases.TextureItem targetTextureConfig)
{
int depth = targetTextureConfig.Depth;
if (depth > && renderer.sharedMaterial.renderQueue < depth)
renderer.sharedMaterial.renderQueue = depth;
} }

ParticleLoader

合并后的图集如下:

Unity性能优化之特效合并

合并后的粒子如下:

Unity性能优化之特效合并

效果如下:

Unity性能优化之特效合并

Unity性能优化之特效合并的更多相关文章

  1. Unity 性能优化(力荐)

    开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...

  2. Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译

    本文是Unity官方教程,性能优化系列的第三篇<Optimizing garbage collection in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  3. Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译

    本文是Unity官方教程,性能优化系列的第四篇<Optimizing graphics rendering in Unity games>的翻译. 相关文章: Unity性能优化(1)-官 ...

  4. Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译

    本文是Unity官方教程,性能优化系列的第二篇<Diagnosing performance problems using the Profiler window>的简单翻译. 相关文章: ...

  5. Unity性能优化(1)-官方教程The Profiler window翻译

    本文是Unity官方教程,性能优化系列的第一篇<The Profiler window>的简单翻译. 相关文章: Unity性能优化(1)-官方教程The Profiler window翻 ...

  6. Unity性能优化的N种武器

    贴图: l  控制贴图大小,尽量不要超过 1024 x1024: l  尽量使用2的n次幂大小的贴图,否则GfxDriver里会有2份贴图: l  尽量使用压缩格式减小贴图大小: l  若干种贴图合并 ...

  7. Unity性能优化专题---腾讯牛人分享经验

    这里从三个纬度来分享下内存的优化经验:代码层面.贴图层面.框架设计层面. 一.代码层面. 1.foreach. Mono下的foreach使用需谨慎.频繁调用容易触及堆上限,导致GC过早触发,出现卡顿 ...

  8. Unity性能优化-音频设置

    没想到Unity的音频会成为内存杀手,在实际的商业项目中,音频的优化必不可少. 1. Unity支持许多不同的音频格式,但最终它将它们全部转换为首选格式.音频压缩格式有PCM.ADPCM.Vorbis ...

  9. Unity性能优化-DrawCall

    1. DrawCall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西.所以,是谁去调用这些接口呢?CPU.比如有上千个物体,每一个的渲染都需要去调用一次底层接口 ...

随机推荐

  1. &lbrack;C&num;&rsqb; C&num; 知识回顾 - 你真的懂异常(Exception)吗?

    你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...

  2. Oracle&lpar;创建视图&rpar;

    概念: 视图:所谓视图就是提取一张或者多张表的数据生成一个映射,管理视图可以同样达到操作原表的效果,方便数据的管理以及安全操作. 视图其实就是一条查询sql语句,用于显示一个或多个表或其他视图中的相关 ...

  3. ios中二维码的使用之二: 二维码的扫描

    二维码的扫描: 1,导入支持框架,<AVFoundation/AVFoundation.h> 2 ,扫描:

  4. centos安装oracle 11g 完全图解

    摘要: 说明: Linux服务器操作系统:CentOS 5.8 32位(注意:系统安装时请单独分区/data用来安装oracle数据库) Linux服务器IP地址:192.168.21.150 Ora ...

  5. &lbrack;Q&rsqb;无法卸载怎么办

    正确卸载CAD批量打图精灵的方法是进入操作系统,“控制面版”,然后运行“添加和删除程序”,找到CAD批量打图精灵,选“更改/删除”,按照提示操作,即可进行卸载. 若使用强制卸载工具(如360等)卸载可 ...

  6. &lbrack;bzoj2462&rsqb; &lbrack;BeiJing2011&rsqb;矩阵模板

    二维的hash.. 注意n的范围是1000........ 真相似乎是全部输出1就行了233 #include<cstdio> #include<iostream> #incl ...

  7. delphi 线程教学第六节:TList与泛型

    第六节: TList 与泛型   TList 是一个重要的容器,用途广泛,配合泛型,更是如虎添翼. 我们先来改进一下带泛型的 TList 基类,以便以后使用. 本例源码下载(delphi XE8版本) ...

  8. mvc、mvp和mvvm理解

    MVC.MVP.MVVM这些模式是为了解决开发过程中的实际问题而提出来的,目前作为主流的几种架构模式而被广泛使用. 一.MVC(Model-View-Controller) MVC是比较直观的架构模式 ...

  9. QPushButton按钮

    需要 from PyQt5.QtWidgets import QPushButton继承 QAbstractButton 创建按钮控件:QPushButton() 创建一个无父控件的按钮控件QPush ...

  10. 迭代器模式和组合模式(head first设计模式——8)

    把迭代器模式和组合模式放在同一篇的原因是其联系比较紧密. 一.迭代器模式 1.1迭代器模式定义 迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露其内部的表示. 这个模式提供了一种方法 ...