Files
UnrealEngine/Engine/Plugins/2D/Paper2D/Source/SpriterImporter/Private/SpriterDataModel.cpp
2025-05-18 13:04:45 +08:00

1656 lines
51 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SpriterDataModel.h"
#include "SpriterImporterLog.h"
#include "Dom/JsonObject.h"
#include "Serialization/JsonSerializer.h"
#include "PaperJSONHelpers.h"
#define LOCTEXT_NAMESPACE "SpriterImporter"
//////////////////////////////////////////////////////////////////////////
// FSpriterAuditTools
#define UE_AUDIT_SPRITER_IMPORT 1
#if UE_AUDIT_SPRITER_IMPORT
// This class will help to catch unknown/newly introduced properties in the future
class FSpriterAuditTools
{
public:
TSet<FString> KnownSpatialInfoKeys;
TSet<FString> KnownFileKeys;
TSet<FString> KnownFolderKeys;
TSet<FString> KnownMapInstructionKeys;
TSet<FString> KnownTagLineKeyTagKeys;
TSet<FString> KnownTagLineKeyKeys;
TSet<FString> KnownTagLineKeys;
TSet<FString> KnownValLineKeyKeys;
TSet<FString> KnownValLineKeys;
TSet<FString> KnownMetaKeys;
TSet<FString> KnownRefKeys;
TSet<FString> KnownObjectRefKeys;
TSet<FString> KnownMainlineKeyKeys;
TSet<FString> KnownBasicTimelineKeyKeys;
TSet<FString> KnownTimelineBoneKeyKeys;
TSet<FString> KnownTimelineObjectKeyKeys;
TSet<FString> KnownTimelineKeys;
TSet<FString> KnownEventLineKeyKeys;
TSet<FString> KnownEventLineKeys;
TSet<FString> KnownAnimationKeys;
TSet<FString> KnownCharacterMapKeys;
TSet<FString> KnownVariableDefinitionKeys;
TSet<FString> KnownObjInfoKeys;
TSet<FString> KnownEntityKeys;
TSet<FString> KnownSCONTagListKeys;
TSet<FString> KnownSCONKeys;
static FSpriterAuditTools& Get()
{
static FSpriterAuditTools StaticInstance;
return StaticInstance;
}
static void AuditKeys(const TSet<FString>& TestSet, const TSharedPtr<FJsonObject> Tree, const FString& ContextString)
{
for (const auto& KVP : Tree->Values)
{
if (!TestSet.Contains(KVP.Key))
{
const bool bSilent = false;
SPRITER_IMPORT_WARNING(TEXT("Unexpected field '%s' in context '%s'. Parsing will continue but not all information is being imported."), *KVP.Key, *ContextString);
static int32 A = 0;
++A;
}
}
}
private:
FSpriterAuditTools()
{
KnownSpatialInfoKeys.Add(TEXT("x"));
KnownSpatialInfoKeys.Add(TEXT("y"));
KnownSpatialInfoKeys.Add(TEXT("angle"));
KnownSpatialInfoKeys.Add(TEXT("scale_x"));
KnownSpatialInfoKeys.Add(TEXT("scale_y"));
KnownSpatialInfoKeys.Add(TEXT("r"));
KnownSpatialInfoKeys.Add(TEXT("g"));
KnownSpatialInfoKeys.Add(TEXT("b"));
KnownSpatialInfoKeys.Add(TEXT("a"));
KnownFileKeys.Add(TEXT("name"));
KnownFileKeys.Add(TEXT("pivot_x"));
KnownFileKeys.Add(TEXT("pivot_y"));
KnownFileKeys.Add(TEXT("width"));
KnownFileKeys.Add(TEXT("height"));
KnownFileKeys.Add(TEXT("type"));
KnownFileKeys.Add(TEXT("id")); // Known but being ignored
KnownFolderKeys.Add(TEXT("name"));
KnownFolderKeys.Add(TEXT("file"));
KnownFolderKeys.Add(TEXT("id")); // Known but being ignored
KnownMapInstructionKeys.Add(TEXT("file"));
KnownMapInstructionKeys.Add(TEXT("folder"));
KnownMapInstructionKeys.Add(TEXT("target_file"));
KnownMapInstructionKeys.Add(TEXT("target_folder"));
KnownTagLineKeyTagKeys.Add(TEXT("t"));
KnownTagLineKeyTagKeys.Add(TEXT("id")); // Known but being ignored
KnownTagLineKeyKeys.Add(TEXT("time"));
KnownTagLineKeyKeys.Add(TEXT("tag"));
KnownTagLineKeyKeys.Add(TEXT("id")); // Known but being ignored
KnownTagLineKeys.Add(TEXT("key"));
KnownValLineKeyKeys.Add(TEXT("time"));
KnownValLineKeyKeys.Add(TEXT("val"));
KnownValLineKeyKeys.Add(TEXT("id")); // Known but being ignored
KnownValLineKeys.Add(TEXT("def"));
KnownValLineKeys.Add(TEXT("key"));
KnownValLineKeys.Add(TEXT("id")); // Known but being ignored
KnownMetaKeys.Add(TEXT("tagline"));
KnownMetaKeys.Add(TEXT("valline"));
KnownRefKeys.Add(TEXT("key"));
KnownRefKeys.Add(TEXT("parent"));
KnownRefKeys.Add(TEXT("timeline"));
KnownRefKeys.Add(TEXT("id")); // Known but being ignored
KnownObjectRefKeys.Append(KnownRefKeys);
KnownObjectRefKeys.Add(TEXT("z_index"));
KnownMainlineKeyKeys.Add(TEXT("time"));
KnownMainlineKeyKeys.Add(TEXT("bone_ref"));
KnownMainlineKeyKeys.Add(TEXT("object_ref"));
KnownMainlineKeyKeys.Add(TEXT("curve_type"));
KnownMainlineKeyKeys.Add(TEXT("id")); // Known but being ignored
KnownBasicTimelineKeyKeys.Add(TEXT("time"));
KnownBasicTimelineKeyKeys.Add(TEXT("curve_type"));
KnownBasicTimelineKeyKeys.Add(TEXT("c1"));
KnownBasicTimelineKeyKeys.Add(TEXT("c2"));
KnownBasicTimelineKeyKeys.Add(TEXT("spin"));
KnownBasicTimelineKeyKeys.Add(TEXT("id")); // Known but being ignored
KnownBasicTimelineKeyKeys.Add(TEXT("object"));
KnownBasicTimelineKeyKeys.Add(TEXT("bone"));
KnownTimelineBoneKeyKeys.Append(KnownSpatialInfoKeys);
KnownTimelineObjectKeyKeys.Append(KnownSpatialInfoKeys);
KnownTimelineObjectKeyKeys.Add(TEXT("file"));
KnownTimelineObjectKeyKeys.Add(TEXT("folder"));
KnownTimelineObjectKeyKeys.Add(TEXT("pivot_x"));
KnownTimelineObjectKeyKeys.Add(TEXT("pivot_y"));
KnownTimelineKeys.Add(TEXT("name"));
KnownTimelineKeys.Add(TEXT("object_type"));
KnownTimelineKeys.Add(TEXT("obj"));
KnownTimelineKeys.Add(TEXT("key"));
KnownTimelineKeys.Add(TEXT("meta"));
KnownTimelineKeys.Add(TEXT("id")); // Known but being ignored
KnownEventLineKeyKeys.Add(TEXT("time"));
KnownEventLineKeyKeys.Add(TEXT("id")); // Known but being ignored
KnownEventLineKeys.Add(TEXT("name"));
KnownEventLineKeys.Add(TEXT("obj"));
KnownEventLineKeys.Add(TEXT("key"));
KnownEventLineKeys.Add(TEXT("id")); // Known but being ignored
KnownAnimationKeys.Add(TEXT("name"));
KnownAnimationKeys.Add(TEXT("length"));
KnownAnimationKeys.Add(TEXT("interval"));
KnownAnimationKeys.Add(TEXT("mainline"));
KnownAnimationKeys.Add(TEXT("looping"));
KnownAnimationKeys.Add(TEXT("timeline"));
//KnownAnimationKeys.Add(TEXT("soundline")); //@TODO: Not supported yet
KnownAnimationKeys.Add(TEXT("eventline"));
KnownAnimationKeys.Add(TEXT("gline")); //@TODO: Not supported yet
KnownAnimationKeys.Add(TEXT("meta"));
KnownAnimationKeys.Add(TEXT("id")); // Known but being ignored
KnownCharacterMapKeys.Add(TEXT("name"));
KnownCharacterMapKeys.Add(TEXT("map"));
KnownCharacterMapKeys.Add(TEXT("id")); // Known but being ignored
KnownVariableDefinitionKeys.Add(TEXT("name"));
KnownVariableDefinitionKeys.Add(TEXT("default"));
KnownVariableDefinitionKeys.Add(TEXT("type"));
KnownVariableDefinitionKeys.Add(TEXT("id")); // Known but being ignored
KnownObjInfoKeys.Add(TEXT("name"));
KnownObjInfoKeys.Add(TEXT("type"));
KnownObjInfoKeys.Add(TEXT("w"));
KnownObjInfoKeys.Add(TEXT("h"));
KnownObjInfoKeys.Add(TEXT("pivot_x"));
KnownObjInfoKeys.Add(TEXT("pivot_y"));
KnownObjInfoKeys.Add(TEXT("id")); // Known but being ignored
KnownObjInfoKeys.Add(TEXT("frames")); //@TODO: Not supported yet
KnownObjInfoKeys.Add(TEXT("var_defs"));
KnownEntityKeys.Add(TEXT("name"));
KnownEntityKeys.Add(TEXT("obj_info"));
KnownEntityKeys.Add(TEXT("animation"));
KnownEntityKeys.Add(TEXT("character_map"));
KnownEntityKeys.Add(TEXT("id")); // Known but being ignored
KnownEntityKeys.Add(TEXT("var_defs"));
KnownSCONTagListKeys.Add(TEXT("name"));
KnownSCONTagListKeys.Add(TEXT("id")); // Known but being ignored
KnownSCONKeys.Add(TEXT("scon_version"));
KnownSCONKeys.Add(TEXT("generator"));
KnownSCONKeys.Add(TEXT("generator_version"));
KnownSCONKeys.Add(TEXT("entity"));
KnownSCONKeys.Add(TEXT("folder"));
KnownSCONKeys.Add(TEXT("tag_list"));
}
};
#define UE_DO_SPRITER_AUDIT(KeySet, Object, Message) FSpriterAuditTools::AuditKeys(FSpriterAuditTools::Get().KeySet, Object, Message);
#else
#define UE_DO_SPRITER_AUDIT(KeySet, Object, Message)
#endif
//////////////////////////////////////////////////////////////////////////
// FSpriterEnumHelper
ESpriterObjectType FSpriterEnumHelper::StringToObjectType(const FString& InString)
{
if (InString == TEXT("sprite"))
{
return ESpriterObjectType::Sprite;
}
else if (InString == TEXT("bone"))
{
return ESpriterObjectType::Bone;
}
else if (InString == TEXT("box"))
{
return ESpriterObjectType::Box;
}
else if (InString == TEXT("point"))
{
return ESpriterObjectType::Point;
}
else if (InString == TEXT("sound"))
{
return ESpriterObjectType::Sound;
}
else if (InString == TEXT("entity"))
{
return ESpriterObjectType::Entity;
}
else if (InString == TEXT("variable"))
{
return ESpriterObjectType::Variable;
}
else if (InString == TEXT("event"))
{
return ESpriterObjectType::Event;
}
else
{
return ESpriterObjectType::INVALID;
}
}
ESpriterCurveType FSpriterEnumHelper::StringToCurveType(const FString& InString)
{
if (InString == TEXT("linear"))
{
return ESpriterCurveType::Linear;
}
else if (InString == TEXT("instant"))
{
return ESpriterCurveType::Instant;
}
else if (InString == TEXT("quadratic"))
{
return ESpriterCurveType::Quadratic;
}
else if (InString == TEXT("cubic"))
{
return ESpriterCurveType::Cubic;
}
else
{
return ESpriterCurveType::INVALID;
}
}
ESpriterVariableType FSpriterEnumHelper::StringToVariableType(const FString& InString)
{
if (InString == TEXT("float"))
{
return ESpriterVariableType::Float;
}
else if (InString == TEXT("int"))
{
return ESpriterVariableType::Integer;
}
else if (InString == TEXT("string"))
{
return ESpriterVariableType::String;
}
else
{
return ESpriterVariableType::INVALID;
}
}
ESpriterFileType FSpriterEnumHelper::StringToFileType(const FString& InString)
{
if (InString == TEXT("sprite"))
{
return ESpriterFileType::Sprite;
}
else if (InString == TEXT("sound"))
{
return ESpriterFileType::Sound;
}
else
{
return ESpriterFileType::INVALID;
}
}
//////////////////////////////////////////////////////////////////////////
// FSpriterSpatialInfo
FSpriterSpatialInfo::FSpriterSpatialInfo()
: X(0.0f)
, Y(0.0f)
, AngleInDegrees(0.0f)
, ScaleX(1.0f)
, ScaleY(1.0f)
, Color(FLinearColor::White)
{
}
bool FSpriterSpatialInfo::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
Tree->TryGetNumberField(TEXT("x"), /*out*/ X);
Tree->TryGetNumberField(TEXT("y"), /*out*/ Y);
Tree->TryGetNumberField(TEXT("angle"), /*out*/ AngleInDegrees);
Tree->TryGetNumberField(TEXT("scale_x"), /*out*/ ScaleX);
Tree->TryGetNumberField(TEXT("scale_y"), /*out*/ ScaleY);
double DR = 1.0;
double DG = 1.0;
double DB = 1.0;
double DA = 1.0;
Tree->TryGetNumberField(TEXT("r"), /*out*/ DR);
Tree->TryGetNumberField(TEXT("g"), /*out*/ DG);
Tree->TryGetNumberField(TEXT("b"), /*out*/ DB);
Tree->TryGetNumberField(TEXT("a"), /*out*/ DA);
Color = FLinearColor(DR, DG, DB, DA);
return true;
}
FTransform FSpriterSpatialInfo::ConvertToTransform() const
{
FTransform Result;
Result.SetTranslation((X * PaperAxisX) + (Y * PaperAxisY));
Result.SetRotation(FRotator(AngleInDegrees, 0.0f, 0.0f).Quaternion());
Result.SetScale3D((ScaleX * PaperAxisX) + (ScaleY * PaperAxisY) + (PaperAxisZ * 1.0f));
return Result;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterFile
FSpriterFile::FSpriterFile()
: PivotX(0.0f)
, PivotY(1.0f)
, Width(0)
, Height(0)
, FileType(ESpriterFileType::INVALID)
{
}
bool FSpriterFile::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Parse the name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the file object of '%s'."), *NameForErrors);
Name = TEXT("(missing file name)");
bSuccessfullyParsed = false;
}
// Optionally parse the type property
FString FileTypeAsString;
if (Tree->TryGetStringField(TEXT("type"), /*out*/ FileTypeAsString))
{
FileType = FSpriterEnumHelper::StringToFileType(FileTypeAsString);
if (FileType == ESpriterFileType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in file '%s' in '%s'."), *FileTypeAsString, *Name, *NameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
// Defaults to sprite
FileType = ESpriterFileType::Sprite;
}
Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX);
Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY);
Tree->TryGetNumberField(TEXT("width"), /*out*/ Width);
Tree->TryGetNumberField(TEXT("height"), /*out*/ Height);
UE_DO_SPRITER_AUDIT(KnownFileKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterFolder
FSpriterFolder::FSpriterFolder()
{
}
bool FSpriterFolder::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the folder name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the folder object of '%s'."), *NameForErrors);
Name = TEXT("(missing folder name)");
bSuccessfullyParsed = false;
}
// Try parsing the list of files
const TArray<TSharedPtr<FJsonValue>>* FileDescriptors;
if (Tree->TryGetArrayField(TEXT("file"), /*out*/ FileDescriptors))
{
const FString LocalNameForErrors = FString::Printf(TEXT("%s folder '%s'"), *NameForErrors, *Name);
for (TSharedPtr<FJsonValue> FileDescriptor : *FileDescriptors)
{
FSpriterFile& File = *new (Files) FSpriterFile();
const bool bParsedFileOK = File.ParseFromJSON(FileDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedFileOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no entities."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownFolderKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterMapInstruction
FSpriterMapInstruction::FSpriterMapInstruction()
: Folder(0)
, File(0)
, TargetFolder(INDEX_NONE)
, TargetFile(INDEX_NONE)
{
}
bool FSpriterMapInstruction::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// These two are required
if (!Tree->TryGetNumberField(TEXT("file"), /*out*/ File))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'file' field in the map object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
if (!Tree->TryGetNumberField(TEXT("folder"), /*out*/ Folder))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'folder' field in the map object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
// These two are optional
Tree->TryGetNumberField(TEXT("target_file"), /*out*/ TargetFile);
Tree->TryGetNumberField(TEXT("target_folder"), /*out*/ TargetFolder);
UE_DO_SPRITER_AUDIT(KnownMapInstructionKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterTagLineKey
FSpriterTagLineKey::FSpriterTagLineKey()
: TimeInMS(0)
{
}
bool FSpriterTagLineKey::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Read the time of the key (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS))
{
// Assume 0 when missing?
TimeInMS = 0;
}
// Parse the tag array
const TArray<TSharedPtr<FJsonValue>>* TagDescriptors;
if (Tree->TryGetArrayField(TEXT("tag"), /*out*/ TagDescriptors))
{
for (const TSharedPtr<FJsonValue> TagDescriptorUntyped : *TagDescriptors)
{
const TSharedPtr<FJsonObject> TagDescriptor = TagDescriptorUntyped->AsObject();
int32 NewTagIndex = INDEX_NONE;
if (TagDescriptor->TryGetNumberField(TEXT("t"), /*out*/ NewTagIndex))
{
Tags.Add(NewTagIndex);
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 't' field in the objects inside the tags array of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownTagLineKeyTagKeys, TagDescriptor, NameForErrors);
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'tag' field in the tag line key '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownTagLineKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterTagLine
FSpriterTagLine::FSpriterTagLine()
{
}
bool FSpriterTagLine::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Parse the key array
const TArray<TSharedPtr<FJsonValue>>* KeyDescriptors;
if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors))
{
for (const TSharedPtr<FJsonValue> KeyDescriptor : *KeyDescriptors)
{
FSpriterTagLineKey& Key = *new (Keys) FSpriterTagLineKey();
const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the tag line '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownTagLineKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterValLineKey
FSpriterValLineKey::FSpriterValLineKey()
: TimeInMS(0)
, bReadAsNumber(false)
, ValueAsNumber(0.0)
{
}
bool FSpriterValLineKey::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Read the time of the key (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS))
{
// Assume 0 when missing?
TimeInMS = 0;
}
const TSharedPtr<FJsonValue> ValField = Tree->TryGetField(TEXT("val"));
if (ValField.IsValid())
{
if (ValField->Type == EJson::String)
{
bReadAsNumber = false;
ValueAsString = ValField->AsString();
}
else if (ValField->Type == EJson::Number)
{
bReadAsNumber = true;
ValueAsNumber = ValField->AsNumber();
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected the 'val' field to be a string or number in the val line key of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'val' field in the val line key of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownValLineKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterValLine
FSpriterValLine::FSpriterValLine()
: DefinitionIndex(INDEX_NONE)
{
}
bool FSpriterValLine::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Read the definition index
if (!Tree->TryGetNumberField(TEXT("def"), /*out*/ DefinitionIndex))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'def' field in the val line of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Parse the key array
const TArray<TSharedPtr<FJsonValue>>* KeyDescriptors;
if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors))
{
for (const TSharedPtr<FJsonValue> KeyDescriptor : *KeyDescriptors)
{
FSpriterValLineKey& Key = *new (Keys) FSpriterValLineKey();
const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the val line '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownValLineKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterMeta
FSpriterMeta::FSpriterMeta()
{
}
bool FSpriterMeta::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Parse the tagline array (optional)
const TArray<TSharedPtr<FJsonValue>>* TagLineDescriptors;
if (Tree->TryGetArrayField(TEXT("tagline"), /*out*/ TagLineDescriptors))
{
for (const TSharedPtr<FJsonValue> TagLineDescriptor : *TagLineDescriptors)
{
FSpriterTagLine& TagLine = *new (TagLines) FSpriterTagLine();
const bool bParsedTagLineSuccessfully = TagLine.ParseFromJSON(TagLineDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedTagLineSuccessfully;
}
}
// Parse the valline array (optional)
const TArray<TSharedPtr<FJsonValue>>* ValLineDescriptors;
if (Tree->TryGetArrayField(TEXT("valline"), /*out*/ ValLineDescriptors))
{
for (const TSharedPtr<FJsonValue> ValLineDescriptor : *ValLineDescriptors)
{
FSpriterValLine& ValLine = *new (ValLines) FSpriterValLine();
const bool bParsedValLineProperly = ValLine.ParseFromJSON(ValLineDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedValLineProperly;
}
}
UE_DO_SPRITER_AUDIT(KnownMetaKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterRef
FSpriterRefCommon::FSpriterRefCommon()
: ParentIndex(INDEX_NONE)
, TimelineIndex(INDEX_NONE)
, KeyIndex(INDEX_NONE)
{
}
bool FSpriterRefCommon::ParseCommonFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
if (!Tree->TryGetNumberField(TEXT("key"), /*out*/ KeyIndex))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the ref object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
if (!Tree->TryGetNumberField(TEXT("parent"), /*out*/ ParentIndex))
{
ParentIndex = INDEX_NONE;
}
if (!Tree->TryGetNumberField(TEXT("timeline"), /*out*/ TimelineIndex))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'timeline' field in the ref object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterRef
bool FSpriterRef::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
const bool bSuccessfullyParsed = ParseCommonFromJSON(Tree, NameForErrors, bSilent);
UE_DO_SPRITER_AUDIT(KnownRefKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterObjectRef
FSpriterObjectRef::FSpriterObjectRef()
: ZIndex(0)
{
}
bool FSpriterObjectRef::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = ParseCommonFromJSON(Tree, NameForErrors, bSilent);
if (!Tree->TryGetNumberField(TEXT("z_index"), /*out*/ ZIndex))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'z_index' field in the object ref object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownObjectRefKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterMainlineKey
FSpriterMainlineKey::FSpriterMainlineKey()
: TimeInMS(INDEX_NONE)
, CurveType(ESpriterCurveType::INVALID)
{
}
bool FSpriterMainlineKey::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Read the time of the key (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS))
{
// Assume 0 when missing?
TimeInMS = 0;
}
// Parse the bone_ref array
const TArray<TSharedPtr<FJsonValue>>* BoneRefDescriptors;
if (Tree->TryGetArrayField(TEXT("bone_ref"), /*out*/ BoneRefDescriptors))
{
for (TSharedPtr<FJsonValue> BoneRefDescriptor : *BoneRefDescriptors)
{
FSpriterRef& BoneRef = *new (BoneRefs) FSpriterRef();
const bool bParsedBoneRefOK = BoneRef.ParseFromJSON(BoneRefDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedBoneRefOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'bone_ref' field in '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Optionally parse the curve_type property
FString CurveTypeAsString;
if (Tree->TryGetStringField(TEXT("curve_type"), /*out*/ CurveTypeAsString))
{
CurveType = FSpriterEnumHelper::StringToCurveType(CurveTypeAsString);
if (CurveType == ESpriterCurveType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'curve_type' in '%s'."), *CurveTypeAsString, *NameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
// Defaults to linear
CurveType = ESpriterCurveType::Linear;
}
// Parse the object_ref array
const TArray<TSharedPtr<FJsonValue>>* ObjectRefDescriptors;
if (Tree->TryGetArrayField(TEXT("object_ref"), /*out*/ ObjectRefDescriptors))
{
for (TSharedPtr<FJsonValue> ObjectRefDescriptor : *ObjectRefDescriptors)
{
FSpriterObjectRef& ObjectRef = *new (ObjectRefs) FSpriterObjectRef();
const bool bParsedObjectRefOK = ObjectRef.ParseFromJSON(ObjectRefDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectRefOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'object_ref' field in '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownMainlineKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterTimelineKey
FSpriterTimelineKey::FSpriterTimelineKey()
: TimeInMS(INDEX_NONE)
, CurveType(ESpriterCurveType::INVALID)
, C1(0.0)
, C2(0.0)
, Spin(1)
{
}
bool FSpriterTimelineKey::ParseBasicsFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Read the time of the key (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS))
{
// Assume 0 when missing?
TimeInMS = 0;
}
// Optionally parse the curve_type property
FString CurveTypeAsString;
if (Tree->TryGetStringField(TEXT("curve_type"), /*out*/ CurveTypeAsString))
{
CurveType = FSpriterEnumHelper::StringToCurveType(CurveTypeAsString);
if (CurveType == ESpriterCurveType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'curve_type' in '%s'."), *CurveTypeAsString, *NameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
// Defaults to linear
CurveType = ESpriterCurveType::Linear;
}
// Optionally parse c1 and c2
Tree->TryGetNumberField(TEXT("c1"), /*out*/ C1);
if ((C1 < 0.0) || (C1 > 1.0))
{
SPRITER_IMPORT_ERROR(TEXT("Unexpected value '%f' for 'c1' in '%s' (expected 0..1)."), C1, *NameForErrors);
bSuccessfullyParsed = false;
}
Tree->TryGetNumberField(TEXT("c2"), /*out*/ C2);
if ((C2 < 0.0) || (C2 > 1.0))
{
SPRITER_IMPORT_ERROR(TEXT("Unexpected value '%f' for 'c2' in '%s' (expected 0..1)."), C2, *NameForErrors);
bSuccessfullyParsed = false;
}
// Optionally parse the spin
Tree->TryGetNumberField(TEXT("spin"), /*out*/ Spin);
if ((Spin != 1) && (Spin != -1) & (Spin != 0))
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%d' for 'spin' in '%s' (expected -1, 0, or 1)."), Spin, *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownBasicTimelineKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterFatTimelineKey
FSpriterFatTimelineKey::FSpriterFatTimelineKey()
: Folder(INDEX_NONE)
, File(INDEX_NONE)
, bUseDefaultPivot(true)
, PivotX(0.0f)
, PivotY(1.0f)
{
}
bool FSpriterFatTimelineKey::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent, const ESpriterObjectType ObjectType)
{
// Parse the common stuff shared for all object types
bool bSuccessfullyParsed = ParseBasicsFromJSON(Tree, NameForErrors, bSilent);
if (ObjectType == ESpriterObjectType::Bone)
{
// Parse the bone child
const TSharedPtr<FJsonObject>* BoneDescriptor;
if (Tree->TryGetObjectField(TEXT("bone"), /*out*/ BoneDescriptor))
{
const bool bParsedBoneOK = ParseBoneFromJSON(*BoneDescriptor, NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed & bParsedBoneOK;
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'bone' field in '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
// Parse the object child
const TSharedPtr<FJsonObject>* ObjectDescriptor;
if (Tree->TryGetObjectField(TEXT("object"), /*out*/ ObjectDescriptor))
{
const bool bParsedObjectOK = ParseObjectFromJSON(*ObjectDescriptor, NameForErrors, bSilent, ObjectType);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectOK;
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'object' field in '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
}
return bSuccessfullyParsed;
}
bool FSpriterFatTimelineKey::ParseBoneFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
const bool bSuccessfullyParsed = Info.ParseFromJSON(Tree, NameForErrors, bSilent);
UE_DO_SPRITER_AUDIT(KnownTimelineBoneKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
bool FSpriterFatTimelineKey::ParseObjectFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent, const ESpriterObjectType ObjectType)
{
const bool bSuccessfullyParsed = Info.ParseFromJSON(Tree, NameForErrors, bSilent);
Tree->TryGetNumberField(TEXT("file"), /*out*/ File);
Tree->TryGetNumberField(TEXT("folder"), /*out*/ Folder);
const bool bHasPivotX = Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX);
const bool bHasPivotY = Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY);
bUseDefaultPivot = !bHasPivotX && !bHasPivotY;
UE_DO_SPRITER_AUDIT(KnownTimelineObjectKeyKeys, Tree, NameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterTimeline
FSpriterTimeline::FSpriterTimeline()
: ObjectIndex(INDEX_NONE)
, ObjectType(ESpriterObjectType::INVALID)
{
}
bool FSpriterTimeline::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the timeline name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the timeline object of '%s'."), *NameForErrors);
Name = TEXT("(missing timeline name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s timeline '%s'"), *NameForErrors, *Name);
// Optionally parse the object_type property
FString ObjectTypeAsString;
if (Tree->TryGetStringField(TEXT("object_type"), /*out*/ ObjectTypeAsString))
{
ObjectType = FSpriterEnumHelper::StringToObjectType(ObjectTypeAsString);
if (ObjectType == ESpriterObjectType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'object_type' in '%s'."), *ObjectTypeAsString, *LocalNameForErrors);
bSuccessfullyParsed = false;
}
}
else
{
// Defaults to sprite
ObjectType = ESpriterObjectType::Sprite;
}
// Optionally parse the obj property
if (!Tree->TryGetNumberField(TEXT("obj"), /*out*/ ObjectIndex))
{
ObjectIndex = INDEX_NONE;
}
// Parse the key array
const TArray<TSharedPtr<FJsonValue>>* TimelineKeyDescriptors;
if (Tree->TryGetArrayField(TEXT("key"), /*out*/ TimelineKeyDescriptors))
{
for (TSharedPtr<FJsonValue> TimelineKeyDescriptor : *TimelineKeyDescriptors)
{
FSpriterFatTimelineKey& Key = *new (Keys) FSpriterFatTimelineKey();
const bool bParsedKeyOK = Key.ParseFromJSON(TimelineKeyDescriptor->AsObject(), LocalNameForErrors, bSilent, ObjectType);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeyOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Read the meta block (optional)
const TSharedPtr<FJsonObject>* MetaDescriptor;
if (Tree->TryGetObjectField(TEXT("meta"), /*out*/ MetaDescriptor))
{
const bool bParsedMetadataOK = Metadata.ParseFromJSON(*MetaDescriptor, NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedMetadataOK;
}
UE_DO_SPRITER_AUDIT(KnownTimelineKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterEventLineKey
FSpriterEventLineKey::FSpriterEventLineKey()
: TimeInMS(0)
{
}
bool FSpriterEventLineKey::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bParsedSuccessfully = true;
// Read the time of the key (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("time"), /*out*/ TimeInMS))
{
// Assume 0 when missing?
TimeInMS = 0;
}
UE_DO_SPRITER_AUDIT(KnownEventLineKeyKeys, Tree, NameForErrors);
return bParsedSuccessfully;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterEventLine
FSpriterEventLine::FSpriterEventLine()
: ObjectIndex(INDEX_NONE)
{
}
bool FSpriterEventLine::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the event line name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the event line of '%s'."), *NameForErrors);
Name = TEXT("(missing event line name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s event line '%s'"), *NameForErrors, *Name);
// Parse the object index
if (!Tree->TryGetNumberField(TEXT("obj"), /*out*/ ObjectIndex))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'obj' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Parse the key array
const TArray<TSharedPtr<FJsonValue>>* KeyDescriptors;
if (Tree->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors))
{
for (const TSharedPtr<FJsonValue> KeyDescriptor : *KeyDescriptors)
{
FSpriterEventLineKey& Key = *new (Keys) FSpriterEventLineKey();
const bool bParsedKeySuccessfully = Key.ParseFromJSON(KeyDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeySuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterAnimation
FSpriterAnimation::FSpriterAnimation()
: LengthInMS(INDEX_NONE)
, IntervalInMS(INDEX_NONE)
, bIsLooping(true)
{
}
bool FSpriterAnimation::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the animation name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the animation object of '%s'."), *NameForErrors);
Name = TEXT("(missing animation name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s animation '%s'"), *NameForErrors, *Name);
// Read the length of the animation (in milliseconds)
if (!Tree->TryGetNumberField(TEXT("length"), /*out*/ LengthInMS))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'length' field in the animation object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Read the interval of the animation (in milliseconds - I think this is probably optional (it's not mentioned in the reference))
Tree->TryGetNumberField(TEXT("interval"), /*out*/ IntervalInMS);
// Read the mainline
const TSharedPtr<FJsonObject>* MainlineDescriptor;
if (Tree->TryGetObjectField(TEXT("mainline"), /*out*/ MainlineDescriptor))
{
// Parse the keys array inside of the mainline object
const TArray<TSharedPtr<FJsonValue>>* KeyDescriptors;
if ((*MainlineDescriptor)->TryGetArrayField(TEXT("key"), /*out*/ KeyDescriptors))
{
for (TSharedPtr<FJsonValue> KeyDescriptor : *KeyDescriptors)
{
FSpriterMainlineKey& Key = *new (MainlineKeys) FSpriterMainlineKey();
const bool bParsedKeyOK = Key.ParseFromJSON(KeyDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedKeyOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'key' field in the 'mainline' object in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
//@TODO: Should we do a sub-audit in the mainline object here?
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'mainline' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Read the looping flag
if (!Tree->TryGetBoolField(TEXT("looping"), /*out*/ bIsLooping))
{
// Default to looping
bIsLooping = true;
}
// Parse the timeline array
const TArray<TSharedPtr<FJsonValue>>* TimelineDescriptors;
if (Tree->TryGetArrayField(TEXT("timeline"), /*out*/ TimelineDescriptors))
{
for (TSharedPtr<FJsonValue> TimelineDescriptor : *TimelineDescriptors)
{
FSpriterTimeline& Timeline = *new (Timelines) FSpriterTimeline();
const bool bParsedTimelineSuccessfully = Timeline.ParseFromJSON(TimelineDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedTimelineSuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'timeline' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Read the eventline array (optional)
const TArray<TSharedPtr<FJsonValue>>* EventLineDescriptors;
if (Tree->TryGetArrayField(TEXT("eventline"), /*out*/ EventLineDescriptors))
{
for (const TSharedPtr<FJsonValue> EventLineDescriptor : *EventLineDescriptors)
{
FSpriterEventLine& EventLine = *new (EventLines) FSpriterEventLine();
const bool bParsedEventLineOK = EventLine.ParseFromJSON(EventLineDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedEventLineOK;
}
}
// Read the meta block (optional)
const TSharedPtr<FJsonObject>* MetaDescriptor;
if (Tree->TryGetObjectField(TEXT("meta"), /*out*/ MetaDescriptor))
{
const bool bParsedMetadataOK = Metadata.ParseFromJSON(*MetaDescriptor, NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedMetadataOK;
}
//@TODO: Figure out what "gline": [], is
UE_DO_SPRITER_AUDIT(KnownAnimationKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterCharacterMap
FSpriterCharacterMap::FSpriterCharacterMap()
{
}
bool FSpriterCharacterMap::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the character map name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the character_map object of '%s'."), *NameForErrors);
Name = TEXT("(missing character_map name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s character map '%s'"), *NameForErrors, *Name);
// Parse the map array
const TArray<TSharedPtr<FJsonValue>>* MapDescriptors;
if (Tree->TryGetArrayField(TEXT("map"), /*out*/ MapDescriptors))
{
for (TSharedPtr<FJsonValue> MapDescriptor : *MapDescriptors)
{
FSpriterMapInstruction& Map = *new (Maps) FSpriterMapInstruction();
const bool bParsedMapInstructionSuccessfully = Map.ParseFromJSON(MapDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedMapInstructionSuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'map' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownCharacterMapKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterVariableDefinition
FSpriterVariableDefinition::FSpriterVariableDefinition()
: VariableType(ESpriterVariableType::INVALID)
, DefaultValueNumber(0.0)
{
}
bool FSpriterVariableDefinition::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the variable name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the variable defintion of '%s'."), *NameForErrors);
Name = TEXT("(missing variable name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s variable '%s'"), *NameForErrors, *Name);
// Parse the type property
FString VariableTypeAsString;
if (Tree->TryGetStringField(TEXT("type"), /*out*/ VariableTypeAsString))
{
VariableType = FSpriterEnumHelper::StringToVariableType(VariableTypeAsString);
}
if (VariableType == ESpriterVariableType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in '%s'."), *VariableTypeAsString, *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Parse the default value
if ((VariableType == ESpriterVariableType::Float) || (VariableType == ESpriterVariableType::Integer))
{
if (!Tree->TryGetNumberField(TEXT("default"), /*out*/ DefaultValueNumber))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a number field named 'default' in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
}
else if (VariableType == ESpriterVariableType::String)
{
if (!Tree->TryGetStringField(TEXT("default"), /*out*/ DefaultValueString))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a string field named 'default' in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
}
else if (VariableType != ESpriterVariableType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("No handling for 'default' in '%s' for an unknown variable type (update this when a new type is added)."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownVariableDefinitionKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterObjectInfo
FSpriterObjectInfo::FSpriterObjectInfo()
: Width(0.0)
, Height(0.0)
, PivotX(0.0)
, PivotY(0.0)
, ObjectType(ESpriterObjectType::INVALID)
{
}
bool FSpriterObjectInfo::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the object name
FString ObjectNameAsString;
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ ObjectNameAsString))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the object of '%s'."), *NameForErrors);
ObjectNameAsString = TEXT("(missing object name)");
bSuccessfullyParsed = false;
}
ObjectName = *ObjectNameAsString;
const FString LocalNameForErrors = FString::Printf(TEXT("%s object '%s'"), *NameForErrors, *ObjectNameAsString);
// Parse the type property
FString ObjectTypeAsString;
if (Tree->TryGetStringField(TEXT("type"), /*out*/ ObjectTypeAsString))
{
ObjectType = FSpriterEnumHelper::StringToObjectType(ObjectTypeAsString);
}
if (ObjectType == ESpriterObjectType::INVALID)
{
SPRITER_IMPORT_ERROR(TEXT("Unknown value '%s' for 'type' in '%s'."), *ObjectTypeAsString, *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Optionally parse the width and height properties
Tree->TryGetNumberField(TEXT("w"), /*out*/ Width);
Tree->TryGetNumberField(TEXT("h"), /*out*/ Height);
// Optionally parse the pivot properties
Tree->TryGetNumberField(TEXT("pivot_x"), /*out*/ PivotX);
Tree->TryGetNumberField(TEXT("pivot_y"), /*out*/ PivotY);
//@TODO: Parse the frames[] field of an 'event' type (once I see one that isn't empty...)
// Parse the var_defs array (optional; can be missing)
const TArray<TSharedPtr<FJsonValue>>* VariableDefinitionDescriptors;
if (Tree->TryGetArrayField(TEXT("var_defs"), /*out*/ VariableDefinitionDescriptors))
{
for (TSharedPtr<FJsonValue> VariableDefinitionDescriptor : *VariableDefinitionDescriptors)
{
FSpriterVariableDefinition& VariableDef = *new (VariableDefinitions)FSpriterVariableDefinition();
const bool bParsedVariableDefOK = VariableDef.ParseFromJSON(VariableDefinitionDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedVariableDefOK;
}
}
UE_DO_SPRITER_AUDIT(KnownObjInfoKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterEntity
FSpriterEntity::FSpriterEntity()
{
}
bool FSpriterEntity::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent)
{
bool bSuccessfullyParsed = true;
// Try parsing the entity name
if (!Tree->TryGetStringField(TEXT("name"), /*out*/ Name))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the entity object of '%s'."), *NameForErrors);
Name = TEXT("(missing entity name)");
bSuccessfullyParsed = false;
}
const FString LocalNameForErrors = FString::Printf(TEXT("%s entity '%s'"), *NameForErrors, *Name);
// Parse the obj_info array
const TArray<TSharedPtr<FJsonValue>>* ObjectDescriptors;
if (Tree->TryGetArrayField(TEXT("obj_info"), /*out*/ ObjectDescriptors))
{
for (TSharedPtr<FJsonValue> ObjectDescriptor : *ObjectDescriptors)
{
FSpriterObjectInfo& ObjectInfo = *new (Objects) FSpriterObjectInfo();
const bool bParsedObjectInfoOK = ObjectInfo.ParseFromJSON(ObjectDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedObjectInfoOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'obj_info' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Parse the var_defs array (optional; can be missing)
const TArray<TSharedPtr<FJsonValue>>* VariableDefinitionDescriptors;
if (Tree->TryGetArrayField(TEXT("var_defs"), /*out*/ VariableDefinitionDescriptors))
{
for (TSharedPtr<FJsonValue> VariableDefinitionDescriptor : *VariableDefinitionDescriptors)
{
FSpriterVariableDefinition& VariableDef = *new (VariableDefinitions) FSpriterVariableDefinition();
const bool bParsedVariableDefOK = VariableDef.ParseFromJSON(VariableDefinitionDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedVariableDefOK;
}
}
// Parse the animation array
const TArray<TSharedPtr<FJsonValue>>* AnimationDescriptors;
if (Tree->TryGetArrayField(TEXT("animation"), /*out*/ AnimationDescriptors))
{
for (TSharedPtr<FJsonValue> AnimationDescriptor : *AnimationDescriptors)
{
FSpriterAnimation& Animation = *new (Animations) FSpriterAnimation();
const bool bParsedAnimationOK = Animation.ParseFromJSON(AnimationDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedAnimationOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'animation' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
// Parse the character_map array
const TArray<TSharedPtr<FJsonValue>>* CharacterMapDescriptors;
if (Tree->TryGetArrayField(TEXT("character_map"), /*out*/ CharacterMapDescriptors))
{
for (TSharedPtr<FJsonValue> CharacterMapDescriptor : *CharacterMapDescriptors)
{
FSpriterCharacterMap& CharacterMap = *new (CharacterMaps) FSpriterCharacterMap();
const bool bParsedCharacterMapSuccessfully = CharacterMap.ParseFromJSON(CharacterMapDescriptor->AsObject(), LocalNameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedCharacterMapSuccessfully;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'character_map' field in '%s'."), *LocalNameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownEntityKeys, Tree, LocalNameForErrors);
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
// FSpriterSCON
FSpriterSCON::FSpriterSCON()
: bSuccessfullyParsed(false)
{
}
void FSpriterSCON::ParseFromJSON(TSharedPtr<FJsonObject> Tree, const FString& NameForErrors, bool bSilent, bool bPreparseOnly)
{
bSuccessfullyParsed = true;
// Try parsing the SCON version
if (!Tree->TryGetStringField(TEXT("scon_version"), /*out*/ SconVersion))
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'scon_version' field in the top level object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Try parsing the generator and generator version strings
if (!Tree->TryGetStringField(TEXT("generator"), /*out*/ Generator))
{
// No good, probably isn't the right kind of file
Generator = FString();
SPRITER_IMPORT_ERROR(TEXT("Expected a 'generator' field in the top level object of '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
if (!Tree->TryGetStringField(TEXT("generator_version"), /*out*/ GeneratorVersion))
{
GeneratorVersion = TEXT("(missing generator_version)");
}
// Validate the SCON version
const FString ExpectedSCONVersion(TEXT("1.0"));
if (SconVersion != ExpectedSCONVersion)
{
// Not 100% we can handle it but we'll try
SPRITER_IMPORT_WARNING(TEXT("Unknown 'scon_version' '%s' (expected '%s') SCON file '%s'. Parsing will continue but the format may not be fully supported"), *SconVersion, *ExpectedSCONVersion, *NameForErrors);
}
// Validate the generator
const FString BrashMonkeySpriterGenerator(TEXT("BrashMonkey Spriter"));
if (Generator.StartsWith(BrashMonkeySpriterGenerator))
{
// Cool, we (mostly) know how to handle these sorts of files!
if (!bSilent)
{
UE_LOG(LogSpriterImporter, Log, TEXT("Parsing Spriter character SCON v%s exported from '%s' '%s'"), *SconVersion, *Generator, *GeneratorVersion);
}
}
else if (!Generator.IsEmpty())
{
// Not 100% we can handle it but we'll try
SPRITER_IMPORT_WARNING(TEXT("Unexpected 'generator' named '%s' '%s' while parsing SCON v%s file '%s'. Parsing will continue but the format may not be fully supported"), *Generator, *GeneratorVersion, *SconVersion, *NameForErrors);
}
// Load the rest of the data if we're doing a full parse
if (!bPreparseOnly)
{
// Parse the entities array
const TArray<TSharedPtr<FJsonValue>>* EntityDescriptors;
if (Tree->TryGetArrayField(TEXT("entity"), /*out*/ EntityDescriptors))
{
for (TSharedPtr<FJsonValue> EntityDescriptor : *EntityDescriptors)
{
FSpriterEntity& Entity = *new (Entities) FSpriterEntity();
const bool bParsedEntityOK = Entity.ParseFromJSON(EntityDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedEntityOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no entities."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Parse the folders array
const TArray<TSharedPtr<FJsonValue>>* FolderDescriptors;
if (Tree->TryGetArrayField(TEXT("folder"), /*out*/ FolderDescriptors))
{
for (TSharedPtr<FJsonValue> FolderDescriptor : *FolderDescriptors)
{
FSpriterFolder& Folder = *new (Folders) FSpriterFolder();
const bool bParsedFolderOK = Folder.ParseFromJSON(FolderDescriptor->AsObject(), NameForErrors, bSilent);
bSuccessfullyParsed = bSuccessfullyParsed && bParsedFolderOK;
}
}
else
{
SPRITER_IMPORT_ERROR(TEXT("JSON exported from Spriter in file '%s' has no folders."), *NameForErrors);
bSuccessfullyParsed = false;
}
// Parse the tag list array (optional)
const TArray<TSharedPtr<FJsonValue>>* TagListDescriptors;
if (Tree->TryGetArrayField(TEXT("tag_list"), /*out*/ TagListDescriptors))
{
for (const TSharedPtr<FJsonValue> TagListDescriptorUntyped : *TagListDescriptors)
{
const TSharedPtr<FJsonObject> TagListDescriptor = TagListDescriptorUntyped->AsObject();
FString NewTag;
if (TagListDescriptor->TryGetStringField(TEXT("name"), /*out*/ NewTag))
{
Tags.Add(NewTag);
}
else
{
SPRITER_IMPORT_ERROR(TEXT("Expected a 'name' field in the tag object in file '%s'."), *NameForErrors);
bSuccessfullyParsed = false;
}
UE_DO_SPRITER_AUDIT(KnownSCONTagListKeys, TagListDescriptor, NameForErrors);
}
}
}
UE_DO_SPRITER_AUDIT(KnownSCONKeys, Tree, NameForErrors);
}
bool FSpriterSCON::IsValid() const
{
return bSuccessfullyParsed;
}
//////////////////////////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE