Unity复刻尼尔:机械纪元-动作整理分析

Xsens动作捕捉 2022-10-09 15806

申明:本文为笔记,仅供学习交流,严禁用于商业用途。

一、工具准备


二、重新命名文件(十六进制转十进制)

  • 我们会发现动作数据mot文件名称是用十六进制命名的,
Unity复刻尼尔:机械纪元-动作整理分析  第1张

很难阅读而且在软件中查看也是乱序的,所以我们现在把mot文件重新命名为十进制方便我们后续使用。

  • 使用Bayonetta Audio Tools工具,拖拽pl000f.dat文件到Bayonetta Audio Tools工具下的PlatinumDat.exe上面,软件就会解包.dat数据。解包后我们会得到一批mot、bxm文件
Unity复刻尼尔:机械纪元-动作整理分析  第2张


  • 编写代码对文件名称进行批量转换
    [MenuItem("尼尔工具箱/重新命名文件(十六进制转十进制)")]

public static void RenameFile()

{

string _path = EditorUtility.SaveFolderPanel("选择文件夹", "F:/NieRData/pl/", "");

string _newPath = _path + "_new";

if (!Directory.Exists(_newPath))

{

Directory.CreateDirectory(_newPath);

}

string[] _files = System.IO.Directory.GetFiles(_path);

foreach (var _filePath in _files)

{

var _extension = Path.GetExtension(_filePath);

var _fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_filePath);

var _newFileName = _fileNameWithoutExtension;

string[] _tmp = _fileNameWithoutExtension.Split(_);

if (_tmp.Length >= 2)

{

//进制转换

int _id = Convert.ToInt32(_tmp[1], 16);

//保留五位数,列如:00001

var _format = string.Format("{0:d5}", _id);

_tmp[1] = _format;

_newFileName = _tmp[0];

for (int i = 1; i < _tmp.Length; i++)

{

_newFileName += "_" + _tmp[i];

}

var _srcfileName = _newPath + "/" + _newFileName + _extension;

File.Copy(_filePath, _srcfileName);

}

else

{

var _srcfileName = _newPath + "/" + _newFileName + _extension;

File.Copy(_filePath, _srcfileName);

}

}

EditorUtility.DisplayDialog("", "处理完成!", "OK");

}

  • 得到我们想要的数据
Unity复刻尼尔:机械纪元-动作整理分析  第3张

这些数据继续保留着后面还要使用,再把模型命名后的文件夹pl000f_new拖拽到Bayonetta Audio Tools工具下的PlatinumDat.exe上面,软件会重新把数据打包为.dat文件pl000f_new.dat


三、自动拆分FBX动画片段

  • 导入到unity里后我们会发现所有动作在一个Clips里,我们还得手动拆分太麻烦了,上面我们已经得到.dat数据解包后已经重新命名过的xxx.mot文件,.mot就是包含动作数据的,现在我们要解析.mot文件生成我们要的动画数据,最后自动化拆分。
  • .mot文件头数据结构
struct {

char id[4]; // "mot\0"

uint32 hash;

uint16 flag;

int16 frameCount;

uint32 recordOffset;

uint32 recordNumber;

uint32 unknown; // usually 0 or 0x003c0000, maybe two uint16

string animName; // found at most 12 bytes with terminating 0

}

  • 解析mot文件数据生成动画帧数据文件
    [MenuItem("尼尔工具箱/解析mot文件生成数据")]

public static void ReadMotFile()

{

string _path = EditorUtility.SaveFolderPanel("选择文件夹", "F:/NieRData/pl/", "");

var _directoryName = Path.GetFileName(_path);

int _firstFrame = 1;

int _lastFrame = _firstFrame - 1;

List<string> _list = new List<string>();

string[] _files = System.IO.Directory.GetFiles(_path);

foreach (var _filePath in _files)

{

var _extension = Path.GetExtension(_filePath);

var _fileNameWithoutExtension = Path.GetFileNameWithoutExtension(_filePath);

/*

mot文件头结构

struct {

char id[4]; // "mot\0"

uint32 hash;

uint16 flag;

int16 frameCount;

uint32 recordOffset;

uint32 recordNumber;

uint32 unknown; // usually 0 or 0x003c0000, maybe two uint16

string animName; // found at most 12 bytes with terminating 0

}

*/

if (_extension.Equals(".mot"))

{

var _fByte = File.ReadAllBytes(_filePath);

//读取动画片段总帧数

var _frameCount = BitConverter.ToInt16(_fByte, 10);

_lastFrame += _frameCount;

_list.Add($"{_firstFrame}|{_lastFrame}|{_fileNameWithoutExtension}");

_firstFrame += _frameCount;

}

}

File.WriteAllLines(_path + $"/{_directoryName}.txt", _list);

EditorUtility.DisplayDialog("", "解析成功!", "OK");

}

  • 读取帧数据自动拆分FBX动画数据
    /// <summary>

/// Clip数据

/// </summary>

public class FbxClipData

{

public string mName;

public int mFirstFrame;

public int mLastFrame;

}

[MenuItem("尼尔工具箱/自动拆分FBX动画片段")]

public static void SplitFbxClip()

{

if (EditorUtility.DisplayDialog("自动拆分FBX动画片段", "确认是否已在Project窗口下选中了要处理的FBX", "确定", "取消"))

{

UnityEngine.Object[] _objects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets);

int _count = 0;

int _objectsLength = _objects.Length;

foreach (var obj in _objects)

{

_count++;

string _assetPath = AssetDatabase.GetAssetPath(obj);

if (Path.GetExtension(_assetPath)?.ToLower() != ".fbx")

{

continue;

}

EditorUtility.DisplayProgressBar("正在处理,请稍后", _assetPath, (float) (_count) / _objectsLength);

List<FbxClipData> _fbxClipDataList = new List<FbxClipData>();

string _extension = Path.ChangeExtension(_assetPath, "txt");

if (!File.Exists(_extension))

{

Debug.LogError(_extension + " 数据文件不存在!");

continue;

}

if (string.IsNullOrEmpty(_extension))

{

continue;

}

StreamReader _reader = new StreamReader(_extension);

string _line;

while ((_line = _reader.ReadLine()) != null)

{

string[] _info = _line.Split(|);

FbxClipData _fbxClipData = new FbxClipData

{

mFirstFrame = int.Parse(_info[0]),

mLastFrame = int.Parse(_info[1]),

mName = _info[2]

};

_fbxClipDataList.Add(_fbxClipData);

}

_reader.Close();

ModelImporter _modelImporter = AssetImporter.GetAtPath(_assetPath) as ModelImporter;

ArrayList _clipsList = new ArrayList();

for (int i = 0; i < _fbxClipDataList.Count; i++)

{

ModelImporterClipAnimation _clipAnimation = new ModelImporterClipAnimation

{

name = _fbxClipDataList[i].mName,

firstFrame = _fbxClipDataList[i].mFirstFrame,

lastFrame = _fbxClipDataList[i].mLastFrame

};

_clipsList.Add(_clipAnimation);

}

if (_modelImporter != null)

{

_modelImporter.clipAnimations =

(ModelImporterClipAnimation[]) _clipsList.ToArray(typeof(ModelImporterClipAnimation));

_modelImporter.SaveAndReimport();

}

}

AssetDatabase.Refresh();

EditorUtility.ClearProgressBar();

EditorUtility.DisplayDialog("提示", "处理完成", "OK");

}

}

最后就可以得到拆分好的动作片段的数据了

Unity复刻尼尔:机械纪元-动作整理分析  第4张

四、数据整理分析

  • 尼尔资源格式解析:
    • 文件夹:
      • pl: 主角模型和动作 wd:场景 wp:武器、 um:NPC、 em:敌人
    • 文件类型:
      • .dtt:模型和贴图 .dat:动画


  • 基本操作
    • 弱攻击、强攻击、冲刺、跳跃、二段跳
    • 跳跃+强攻击 = 上挑
    • 长按弱|强攻击键 = 特殊攻击
    • 冲刺状态下+强攻击 = 冲刺攻击
    • 冲刺+弱攻击 = 旋转攻击
    • 空中状态+强攻击 = 下劈
    • 空中状态+冲刺+强攻击 = 斜向下下劈
    • 闪避后攻击+弱攻击|强攻击 = 闪避攻击


角色动作id整理

在查看武器动画时会发现武器种类是有划分id的,而且人物动画和武器动画id是有匹配关系的

    • 通用动作: 动画id:[0~256) 十六进制:[0~99)
    • 小型剑000: 动画id:[256~512) 十六进制:[100~200)
    • 大型剑200: 动画id:[512~768) 十六进制:[200~300)
    • 抢400: 动画id:[768~1024) 十六进制:[300~400)
    • 格斗600: 动画id:[1024~1280) 十六进制:[400~500)
    • 空手: 动画id:[1280~1536) 十六进制:[500~600)


  • 小型剑连招:
    • 弱连击:动作id【00256、00257、00258、00259、00260、00261、00262】
    • 弱长按:动作id【】
    • 强连击:动作id【00272、00275、00278】
    • 强长按:动作id【】


  • 抢连招:
    • 弱连击:动作id【00768、00769、00770、00771、00772、00773】


五、最后把角色动画和武器动画匹配上

参考:

The End