分解FBX文件
作者:追风剑情 发布于:2018-12-7 18:33 分类:Unity3d
一、导入一个FBX文件
二、分解FBX文件
示例代码
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Collections;
using System.Collections.Generic;
public class TestEditor {
public const string PREFIX_BONE = "bone";
public const string PREFIX_M2B = "smb";
public const string PREFIX_MODEL = "model";
[MenuItem("Assets/分解FBX", false, 100)]
public static void DetachFBX()
{
GameObject go = Selection.activeGameObject;
if (go == null){
EditorUtility.DisplayDialog("提示", "请选择FBX文件", "OK");
return;
}
string asset_path = AssetDatabase.GetAssetPath(go);
string postfix = Path.GetExtension(asset_path).ToLower();
if (postfix != ".fbx") {
EditorUtility.DisplayDialog("错误", "选中的不是FBX文件", "OK");
return;
}
//创建导出目录
string asset_folder = Path.GetDirectoryName(asset_path);
string export_folder = Path.Combine(asset_folder, go.name).Replace("\\","/");
if (!Directory.Exists(export_folder)) {
AssetDatabase.CreateFolder(asset_folder, go.name);
}
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(asset_path);
//遍历FBX内部所有资源
for (int i = 0; i < assets.Length; i++)
{
if (!(assets[i] is GameObject))
continue;
GameObject asset = assets[i] as GameObject;
asset_path = Path.Combine(export_folder, asset.name);
//导出带动画的模型
SkinnedMeshRenderer smr = asset.GetComponent<SkinnedMeshRenderer>();
if (smr != null) {
ExportSkinnedBone(smr, export_folder);
ExportModel(asset, export_folder);
}
//导出无动画的模型
MeshFilter mf = asset.GetComponent<MeshFilter>();
if (mf != null) {
ExportModel(asset, export_folder);
}
}
//导出骨架
ExportSkeleton(go, export_folder);
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
}
[MenuItem("Assets/导出选中", false, 200)]
public static void ExportSelected()
{
Object[] objects = Selection.objects;
for(int i=0; i<objects.Length; i++)
{
Object obj = objects[i];
if (obj == null)
continue;
string name = obj.name;
if (name.StartsWith(PREFIX_BONE) || name.StartsWith(PREFIX_M2B) || name.StartsWith(PREFIX_MODEL))
BuildAsset(obj);
}
}
// 导出骨架
public static void ExportSkeleton(GameObject asset, string export_folder)
{
GameObject go = GameObject.Instantiate(asset);
//去掉模型资源
foreach (var smr in go.GetComponentsInChildren<SkinnedMeshRenderer>())
GameObject.DestroyImmediate(smr.gameObject);
foreach (var mf in go.GetComponentsInChildren<MeshFilter>())
GameObject.DestroyImmediate(mf.gameObject);
string prefab_path = string.Format("{0}/{1}_{2}.prefab", export_folder, PREFIX_BONE, asset.name);
CreatePrefab(prefab_path, go);
//设置bundle信息
string bundle_name = Path.GetFileNameWithoutExtension(prefab_path);
SetAssetBundleNameAndVariant(prefab_path, bundle_name, PREFIX_BONE);
//生成AssetBundle资源
BuildAsset(prefab_path, export_folder, bundle_name, PREFIX_BONE);
GameObject.DestroyImmediate(go);
}
// 导出蒙皮骨骼信息
public static void ExportSkinnedBone(SkinnedMeshRenderer smr, string export_folder)
{
string asset_path = string.Format("{0}/{1}_{2}.asset", export_folder, PREFIX_M2B, smr.name);
SkinnedMeshBone obj = SkinnedMeshBone.Create(smr);
AssetDatabase.CreateAsset(obj, asset_path);
//设置bundle信息
string bundle_name = Path.GetFileNameWithoutExtension(asset_path);
SetAssetBundleNameAndVariant(asset_path, bundle_name, PREFIX_M2B);
//生成AssetBundle资源
BuildAsset(asset_path, export_folder, bundle_name, PREFIX_M2B, false);
}
// 导出模型
public static void ExportModel(GameObject asset, string export_folder)
{
GameObject go = GameObject.Instantiate(asset);
string prefab_path = string.Format("{0}/{1}_{2}.prefab", export_folder, PREFIX_MODEL, asset.name);
CreatePrefab(prefab_path, go);
//设置bundle信息
string bundle_name = Path.GetFileNameWithoutExtension(prefab_path);
SetAssetBundleNameAndVariant(prefab_path, bundle_name, PREFIX_MODEL);
//生成AssetBundle资源
BuildAsset(prefab_path, export_folder, bundle_name, PREFIX_MODEL);
GameObject.DestroyImmediate(go);
}
// 创建Prefab
public static void CreatePrefab(string asset_path, GameObject go)
{
Object prefab = PrefabUtility.CreateEmptyPrefab(asset_path);
PrefabUtility.ReplacePrefab(go, prefab);
}
// 设置资源的AssetBundle信息
static void SetAssetBundleNameAndVariant(Object asset, string bundle_name, string bundle_variant) {
AssetImporter importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(asset));
if (importer == null)
return;
importer.SetAssetBundleNameAndVariant(bundle_name, bundle_variant);
}
// 设置资源的AssetBundle信息
static void SetAssetBundleNameAndVariant(string asset_path, string bundle_name, string bundle_variant) {
AssetImporter importer = AssetImporter.GetAtPath(asset_path);
if (importer == null)
return;
importer.SetAssetBundleNameAndVariant(bundle_name, bundle_variant);
}
//打包AssetBundle资源
public static void BuildAsset(Object asset, string export_folder=null, string bundle_name=null, string bundle_variant=null, bool includeDependencies=true, bool compress=true)
{
if (asset is ScriptableObject) {
includeDependencies = false;
}
string asset_path = AssetDatabase.GetAssetPath(asset);
BuildAsset(asset_path, export_folder, bundle_name, bundle_variant, includeDependencies, compress);
}
public static void BuildAsset(string asset_path, string export_folder=null, string bundle_name=null, string bundle_variant=null, bool includeDependencies=true, bool compress=true)
{
if (string.IsNullOrEmpty(export_folder)) {
export_folder = Path.GetDirectoryName(asset_path);
}
if (string.IsNullOrEmpty(bundle_name)) {
AssetImporter importer = AssetImporter.GetAtPath(asset_path);
if (!string.IsNullOrEmpty(importer.assetBundleName)) {
bundle_name = importer.assetBundleName;
}
}
if (string.IsNullOrEmpty(bundle_variant)) {
AssetImporter importer = AssetImporter.GetAtPath(asset_path);
if (!string.IsNullOrEmpty(importer.assetBundleVariant)) {
bundle_variant = importer.assetBundleVariant;
}
}
//-----检查bundle信息-----
if (string.IsNullOrEmpty(bundle_name)) {
string msg = string.Format("未设置BundleName\n{0}", Path.GetFileName(asset_path));
EditorUtility.DisplayDialog("Error", msg, "OK");
return;
}
if (string.IsNullOrEmpty(bundle_variant)) {
string msg = string.Format("未设置BundleVariant\n{0}", Path.GetFileName(asset_path));
EditorUtility.DisplayDialog("Error", msg, "OK");
return;
}
//------------------------
string[] assetNames;
if (includeDependencies){
/*************************************************************
这里返回的依赖资源包含了自己
注意:
如果依赖资源未设置bundle信息,会全部打进ab文件中,
这里必须把依赖资源的path也放进assetNames数组中,否则无法从AssetBundle中读取依赖资源
如果未设置addressableNames,用资源名从AssetBundle从读取资源
示例: ab.LoadAsset("资源名")
如果设置了addressableNames,则只能用addressableName读取资源
示例: ab.LoadAsset("addressableName")
addressableNames数组长度必须和assetNames数组长度相同
**************************************************************/
assetNames = AssetDatabase.GetDependencies(asset_path);
}else{
assetNames = new string[] { asset_path };
}
AssetBundleBuild abb = new AssetBundleBuild();
abb.assetBundleName = bundle_name;
abb.assetBundleVariant = bundle_variant;
abb.assetNames = assetNames;
//abb.addressableNames = addressableNames;//自定义资源名称
BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression;
if (!compress) options = BuildAssetBundleOptions.UncompressedAssetBundle;
options |= BuildAssetBundleOptions.StrictMode;
BuildPipeline.BuildAssetBundles(export_folder, new AssetBundleBuild[] { abb }, options, EditorUserBuildSettings.activeBuildTarget);
AssetDatabase.Refresh();
//删除生成的manifest文件
AssetDatabase.DeleteAsset(string.Format("{0}/{1}.{2}.manifest", export_folder, bundle_name, bundle_variant));
string folder_name = export_folder.Substring(export_folder.LastIndexOf("/") + 1);
AssetDatabase.DeleteAsset(string.Format("{0}/{1}.manifest", export_folder, folder_name));
AssetDatabase.DeleteAsset(string.Format("{0}/{1}", export_folder, folder_name));
}
}
三、将生成的AssetBundle文件放到StreamingAssets目录下
测试代码 Test.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 测试: 重新将骨架、模型、蒙皮骨骼配置信息组装成角色
/// </summary>
public class Test : MonoBehaviour
{
CRoleObject ro;
void Start()
{
ModelConfig cfg = new ModelConfig();
cfg.skeletonUrl = string.Format("file://{0}/bone_lady01.bone", Application.streamingAssetsPath);
cfg.smbUrl = string.Format("file://{0}/smb_lady01.smb", Application.streamingAssetsPath);
cfg.modelUrl = string.Format("file://{0}/model_lady01.model", Application.streamingAssetsPath);
ro = new CRoleObject(cfg);
}
void Update()
{
if (ro != null)
ro.Update();
}
}
public abstract class IRenderObject
{
public bool completed { get; protected set; }
public void Update() {
OnUpdate();
}
public void Destroy() {
OnDestroy();
}
protected virtual void OnUpdate() { }
protected virtual void OnDestroy() { }
}
public class CRoleObject : IRenderObject
{
protected CSkeleton mSkeleton = null;
protected CSkinnedMeshBone mSkinnedMeshBone = null;
protected CSkinnedMesh mSkinnedMesh = null;
//构建状态
public enum BuildState
{
None,
SkeletonLoading,
MeshToBoneMapLoading,
SkinnedMeshLoading,
LoadCompleted,
BuildCompleted
}
public BuildState buildState { get; protected set; }
public CRoleObject() { }
public CRoleObject(ModelConfig cfg)
{
Load(cfg);
}
public void Load(ModelConfig cfg)
{
this.completed = false;
mSkeleton = new CSkeleton();
mSkeleton.Load(cfg);
mSkinnedMeshBone = new CSkinnedMeshBone();
mSkinnedMeshBone.Load(cfg);
mSkinnedMesh = new CSkinnedMesh();
mSkinnedMesh.Load(cfg);
buildState = BuildState.SkeletonLoading;
}
protected override void OnUpdate()
{
if (mSkeleton != null)
mSkeleton.Update();
if (mSkinnedMeshBone != null)
mSkinnedMeshBone.Update();
if (mSkinnedMesh != null)
mSkinnedMesh.Update();
switch(buildState)
{
case BuildState.SkeletonLoading:
if (mSkeleton.completed)
buildState = BuildState.MeshToBoneMapLoading;
break;
case BuildState.MeshToBoneMapLoading:
if (mSkinnedMeshBone.completed)
buildState = BuildState.SkinnedMeshLoading;
break;
case BuildState.SkinnedMeshLoading:
if (mSkinnedMesh.completed)
buildState = BuildState.LoadCompleted;
break;
case BuildState.LoadCompleted:
mSkinnedMesh.SetBones(mSkinnedMeshBone.rootBone, mSkinnedMeshBone.bones);
mSkinnedMesh.AttachSkeleton(mSkeleton);
buildState = BuildState.BuildCompleted;
this.completed = true;
Debug.Log("Role build completed.");
break;
}
Debug.Log("build state: " + buildState);
}
protected override void OnDestroy()
{
if (mSkeleton != null) {
mSkeleton.Destroy();
mSkeleton = null;
}
if (mSkinnedMeshBone != null) {
mSkinnedMeshBone.Destroy();
mSkinnedMeshBone = null;
}
if (mSkinnedMesh != null) {
mSkinnedMesh.Destroy();
mSkinnedMesh = null;
}
}
}
public class CSkeleton : IRenderObject
{
protected Dictionary<string, Transform> mBoneDic = null;
protected WWW www = null;
protected Animation animation = null;
protected override void OnUpdate() {
if (this.www == null)
return;
if (!string.IsNullOrEmpty(this.www.error)) {
Debug.LogErrorFormat("load skeleton failed. url={0}", this.www.url);
return;
}
if (!this.www.isDone)
return;
AssetBundle ab = this.www.assetBundle;
Object[] assets = ab.LoadAllAssets();
if (assets == null || assets.Length <= 0) {
Debug.LogErrorFormat("load skeleton asset failed. url={0}", this.www.url);
return;
}
GameObject mainAsset = null;
//找到骨架资源作为主资源
for(int i=0; i < assets.Length; i++) {
if (assets[i].name.StartsWith("bone_")) {
mainAsset = assets[i] as GameObject;
break;
}
}
if (mainAsset == null) {
Debug.LogErrorFormat("not skeleton asset. url={0}", this.www.url);
return;
}
GameObject skeleton = GameObject.Instantiate(mainAsset);
skeleton.name = mainAsset.name;
//是否挂有动画组件
animation = skeleton.GetComponent<Animation>();
//建立 骨骼名-Transform 映射表
mBoneDic = new Dictionary<string, Transform>();
Transform[] transforms = skeleton.GetComponentsInChildren<Transform>();
int length = transforms.Length;
for (int i = 0; i < length; ++i) {
string bone_name = transforms[i].name;
if (!this.mBoneDic.ContainsKey(bone_name)) {
this.mBoneDic[bone_name] = transforms[i];
} else {
Debug.LogErrorFormat("bone name '{0}' repeated", bone_name);
}
}
ab.Unload(false);
this.www.Dispose();
this.www = null;
this.completed = true;
Debug.Log("load skeleton completed.");
}
public void Load(ModelConfig cfg)
{
this.Dispose();
this.completed = false;
this.www = WWW.LoadFromCacheOrDownload(cfg.skeletonUrl, cfg.version);
}
public Transform FindBoneByName(string boneName)
{
if (this.mBoneDic == null || string.IsNullOrEmpty(boneName) || !this.mBoneDic.ContainsKey(boneName))
return null;
return this.mBoneDic[boneName];
}
public Transform[] FindBonesByName(string[] boneNames)
{
if (this.mBoneDic == null || boneNames == null)
return null;
List<Transform> list = new List<Transform>();
string bone_name;
for(int i=0; i<boneNames.Length; i++){
bone_name = boneNames[i];
if (string.IsNullOrEmpty(bone_name))
continue;
if (this.mBoneDic.ContainsKey(bone_name)) {
list.Add(this.mBoneDic[bone_name]);
}else{
Debug.LogWarningFormat("can't find bone transform:{0}", bone_name);
}
}
return list.ToArray();
}
public void Dispose()
{
if (www != null) {
www.Dispose();
www = null;
}
}
protected override void OnDestroy()
{
Dispose();
if (mBoneDic != null) {
mBoneDic.Clear();
mBoneDic = null;
}
}
}
public class CSkinnedMesh : IRenderObject
{
protected WWW www;
protected SkinnedMeshRenderer smr;
protected string rootBone;
protected string[] bones;
protected override void OnUpdate() {
if (this.www == null)
return;
if (!string.IsNullOrEmpty(this.www.error)) {
Debug.LogErrorFormat("load model failed. url={0}", this.www.url);
return;
}
if (!this.www.isDone)
return;
AssetBundle ab = this.www.assetBundle;
Object[] assets = ab.LoadAllAssets();
if (assets == null || assets.Length <= 0) {
Debug.LogErrorFormat("load model asset failed. url={0}", this.www.url);
return;
}
GameObject mainAsset = null;
//找到模型资源作为主资源
for(int i=0; i < assets.Length; i++) {
if (assets[i].name.StartsWith("model_")) {
mainAsset = assets[i] as GameObject;
break;
}
}
if (mainAsset == null) {
Debug.LogErrorFormat("not model asset. url={0}", this.www.url);
return;
}
GameObject model = GameObject.Instantiate(mainAsset);
model.name = mainAsset.name;
this.smr = model.GetComponent<SkinnedMeshRenderer>();
ab.Unload(false);
this.www.Dispose();
this.www = null;
this.completed = true;
Debug.Log("load model completed.");
}
public void Load(ModelConfig cfg)
{
Dispose();
this.completed = false;
this.www = WWW.LoadFromCacheOrDownload(cfg.modelUrl, cfg.version);
}
public void SetBones(string rootBone, string[] bones)
{
this.rootBone = rootBone;
this.bones = bones;
}
public void AttachSkeleton(CSkeleton skeleton)
{
if (skeleton == null || smr == null)
return;
Transform rootTran = skeleton.FindBoneByName(rootBone);
smr.transform.parent = rootTran.parent != null ? rootTran.parent : rootTran;
smr.rootBone = rootTran;
smr.bones = skeleton.FindBonesByName(bones);
smr.enabled = true;
}
public void Dispose()
{
if (www != null) {
www.Dispose();
www = null;
}
}
protected override void OnDestroy()
{
Dispose();
if (smr != null) {
smr.rootBone = null;
smr.bones = null;
smr = null;
}
bones = null;
}
}
public class CSkinnedMeshBone : IRenderObject
{
public string rootBone;
public string[] bones;
protected WWW www;
protected override void OnUpdate() {
if (this.www == null)
return;
if (!string.IsNullOrEmpty(this.www.error)) {
Debug.LogErrorFormat("load smb failed. url={0}", this.www.url);
return;
}
if (!this.www.isDone)
return;
AssetBundle ab = this.www.assetBundle;
Object[] assets = ab.LoadAllAssets();
if (assets == null || assets.Length <= 0) {
Debug.LogErrorFormat("load smb asset failed. url={0}", this.www.url);
return;
}
SkinnedMeshBone smb = assets[0] as SkinnedMeshBone;
if (smb == null) {
Debug.LogErrorFormat("not smb asset. url={0}", this.www.url);
return;
}
this.rootBone = smb.rootBone;
this.bones = smb.bones.Clone() as string[];
ab.Unload(false);
this.www.Dispose();
this.www = null;
this.completed = true;
Debug.Log("load smb completed.");
}
public void Load(ModelConfig cfg)
{
this.Dispose();
this.completed = false;
this.www = WWW.LoadFromCacheOrDownload(cfg.smbUrl, cfg.version);
}
public void Dispose()
{
if (www != null) {
www.Dispose();
www = null;
}
}
protected override void OnDestroy()
{
Dispose();
bones = null;
}
}
public class ModelConfig
{
public int version = 6;
public string skeletonUrl;
public string modelUrl;
public string smbUrl;
}
运行效果
Hierarchy窗口
Console日志
标签: Unity3d
日历
最新文章
随机文章
热门文章
分类
存档
- 2025年11月(1)
- 2025年9月(3)
- 2025年7月(4)
- 2025年6月(5)
- 2025年5月(1)
- 2025年4月(5)
- 2025年3月(4)
- 2025年2月(3)
- 2025年1月(1)
- 2024年12月(5)
- 2024年11月(5)
- 2024年10月(5)
- 2024年9月(3)
- 2024年8月(3)
- 2024年7月(11)
- 2024年6月(3)
- 2024年5月(9)
- 2024年4月(10)
- 2024年3月(11)
- 2024年2月(24)
- 2024年1月(12)
- 2023年12月(3)
- 2023年11月(9)
- 2023年10月(7)
- 2023年9月(2)
- 2023年8月(7)
- 2023年7月(9)
- 2023年6月(6)
- 2023年5月(7)
- 2023年4月(11)
- 2023年3月(6)
- 2023年2月(11)
- 2023年1月(8)
- 2022年12月(2)
- 2022年11月(4)
- 2022年10月(10)
- 2022年9月(2)
- 2022年8月(13)
- 2022年7月(7)
- 2022年6月(11)
- 2022年5月(18)
- 2022年4月(29)
- 2022年3月(5)
- 2022年2月(6)
- 2022年1月(8)
- 2021年12月(5)
- 2021年11月(3)
- 2021年10月(4)
- 2021年9月(9)
- 2021年8月(14)
- 2021年7月(8)
- 2021年6月(5)
- 2021年5月(2)
- 2021年4月(3)
- 2021年3月(7)
- 2021年2月(2)
- 2021年1月(8)
- 2020年12月(7)
- 2020年11月(2)
- 2020年10月(6)
- 2020年9月(9)
- 2020年8月(10)
- 2020年7月(9)
- 2020年6月(18)
- 2020年5月(4)
- 2020年4月(25)
- 2020年3月(38)
- 2020年1月(21)
- 2019年12月(13)
- 2019年11月(29)
- 2019年10月(44)
- 2019年9月(17)
- 2019年8月(18)
- 2019年7月(25)
- 2019年6月(25)
- 2019年5月(17)
- 2019年4月(10)
- 2019年3月(36)
- 2019年2月(35)
- 2019年1月(28)
- 2018年12月(30)
- 2018年11月(22)
- 2018年10月(4)
- 2018年9月(7)
- 2018年8月(13)
- 2018年7月(13)
- 2018年6月(6)
- 2018年5月(5)
- 2018年4月(13)
- 2018年3月(5)
- 2018年2月(3)
- 2018年1月(8)
- 2017年12月(35)
- 2017年11月(17)
- 2017年10月(16)
- 2017年9月(17)
- 2017年8月(20)
- 2017年7月(34)
- 2017年6月(17)
- 2017年5月(15)
- 2017年4月(32)
- 2017年3月(8)
- 2017年2月(2)
- 2017年1月(5)
- 2016年12月(14)
- 2016年11月(26)
- 2016年10月(12)
- 2016年9月(25)
- 2016年8月(32)
- 2016年7月(14)
- 2016年6月(21)
- 2016年5月(17)
- 2016年4月(13)
- 2016年3月(8)
- 2016年2月(8)
- 2016年1月(18)
- 2015年12月(13)
- 2015年11月(15)
- 2015年10月(12)
- 2015年9月(18)
- 2015年8月(21)
- 2015年7月(35)
- 2015年6月(13)
- 2015年5月(9)
- 2015年4月(4)
- 2015年3月(5)
- 2015年2月(4)
- 2015年1月(13)
- 2014年12月(7)
- 2014年11月(5)
- 2014年10月(4)
- 2014年9月(8)
- 2014年8月(16)
- 2014年7月(26)
- 2014年6月(22)
- 2014年5月(28)
- 2014年4月(15)
友情链接
- Unity官网
- Unity圣典
- Unity在线手册
- Unity中文手册(圣典)
- Unity官方中文论坛
- Unity游戏蛮牛用户文档
- Unity下载存档
- Unity引擎源码下载
- Unity服务
- Unity Ads
- wiki.unity3d
- Visual Studio Code官网
- SenseAR开发文档
- MSDN
- C# 参考
- C# 编程指南
- .NET Framework类库
- .NET 文档
- .NET 开发
- WPF官方文档
- uLua
- xLua
- SharpZipLib
- Protobuf-net
- Protobuf.js
- OpenSSL
- OPEN CASCADE
- JSON
- MessagePack
- C在线工具
- 游戏蛮牛
- GreenVPN
- 聚合数据
- 热云
- 融云
- 腾讯云
- 腾讯开放平台
- 腾讯游戏服务
- 腾讯游戏开发者平台
- 腾讯课堂
- 微信开放平台
- 腾讯实时音视频
- 腾讯即时通信IM
- 微信公众平台技术文档
- 白鹭引擎官网
- 白鹭引擎开放平台
- 白鹭引擎开发文档
- FairyGUI编辑器
- PureMVC-TypeScript
- 讯飞开放平台
- 亲加通讯云
- Cygwin
- Mono开发者联盟
- Scut游戏服务器引擎
- KBEngine游戏服务器引擎
- Photon游戏服务器引擎
- 码云
- SharpSvn
- 腾讯bugly
- 4399原创平台
- 开源中国
- Firebase
- Firebase-Admob-Unity
- google-services-unity
- Firebase SDK for Unity
- Google-Firebase-SDK
- AppsFlyer SDK
- android-repository
- CQASO
- Facebook开发者平台
- gradle下载
- GradleBuildTool下载
- Android Developers
- Google中国开发者
- AndroidDevTools
- Android社区
- Android开发工具
- Google Play Games Services
- Google商店
- Google APIs for Android
- 金钱豹VPN
- TouchSense SDK
- MakeHuman
- Online RSA Key Converter
- Windows UWP应用
- Visual Studio For Unity
- Open CASCADE Technology
- 慕课网
- 阿里云服务器ECS
- 在线免费文字转语音系统
- AI Studio
- 网云穿
- 百度网盘开放平台
- 迅捷画图
- 菜鸟工具
- [CSDN] 程序员研修院
- 华为人脸识别
- 百度AR导航导览SDK
- 海康威视官网
- 海康开放平台
- 海康SDK下载
- git download
- Open CASCADE
- CascadeStudio
交流QQ群
-
Flash游戏设计: 86184192
Unity游戏设计: 171855449
游戏设计订阅号














