Merge pull request #1 from FlaxEngine/master

Update
This commit is contained in:
Mr L Watson
2021-04-29 18:52:01 +01:00
committed by GitHub
280 changed files with 8247 additions and 22831 deletions

Binary file not shown.

View File

@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 1,
"Build": 6217
"Build": 6218
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.",

View File

@@ -234,6 +234,7 @@
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/Name/@EntryValue">Deprecated</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/Pattern/@EntryValue">(?&lt;=\W|^)(?&lt;TAG&gt;\[Deprecated)(\W|$)(.*)</s:String>
<s:String x:Key="/Default/PatternsAndTemplates/Todo/TodoPatterns/=EEA05B0ED8200E4BA9D2D3F1052EBFFD/TodoIconStyle/@EntryValue">Normal</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ackermann/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=analytics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Antialiasing/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=backbuffer/@EntryIndexedValue">True</s:Boolean>

View File

@@ -53,6 +53,11 @@ namespace FlaxEditor.Content.Create
/// </summary>
NavigationSettings,
/// <summary>
/// The localization settings.
/// </summary>
LocalizationSettings,
/// <summary>
/// The build settings.
/// </summary>
@@ -92,6 +97,11 @@ namespace FlaxEditor.Content.Create
/// The Android settings
/// </summary>
AndroidPlatformSettings,
/// <summary>
/// The Switch settings
/// </summary>
SwitchPlatformSettings,
}
private static readonly Type[] _types =
@@ -103,6 +113,7 @@ namespace FlaxEditor.Content.Create
typeof(PhysicsSettings),
typeof(GraphicsSettings),
typeof(NavigationSettings),
typeof(LocalizationSettings),
typeof(BuildSettings),
typeof(InputSettings),
typeof(WindowsPlatformSettings),
@@ -111,6 +122,7 @@ namespace FlaxEditor.Content.Create
TypeUtils.GetManagedType(GameSettings.PS4PlatformSettingsTypename),
TypeUtils.GetManagedType(GameSettings.XboxScarlettPlatformSettingsTypename),
typeof(AndroidPlatformSettings),
TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename),
};
/// <summary>

View File

@@ -116,8 +116,7 @@ namespace FlaxEditor.Content.Import
if (FileTypes.TryGetValue(extension, out ImportFileEntryHandler createDelegate))
return createDelegate(ref request);
// Use default type
return request.IsBinaryAsset ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request);
return request.IsInBuilt ? new AssetImportEntry(ref request) : new ImportFileEntry(ref request);
}
internal static void RegisterDefaultTypes()

View File

@@ -21,9 +21,9 @@ namespace FlaxEditor.Content.Import
public string OutputPath;
/// <summary>
/// Flag set to true for binary assets handled by the engine internally.
/// Flag set to true for the assets handled by the engine internally.
/// </summary>
public bool IsBinaryAsset;
public bool IsInBuilt;
/// <summary>
/// Flag used to skip showing import settings dialog to used. Can be used for importing assets from code by plugins.

View File

@@ -75,6 +75,7 @@ namespace FlaxEditor.Content
}
}
/// <inheritdoc />
public int MetadataToken => 0;
/// <inheritdoc />

View File

@@ -2,7 +2,6 @@
using System;
using FlaxEditor.Content.Create;
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Windows;
@@ -45,18 +44,12 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return item is JsonAssetItem;
return item is JsonAssetItem json && json.TypeName == TypeName;
}
/// <inheritdoc />
public override Color AccentColor => Color.FromRGB(0xd14f67);
/// <inheritdoc />
public override bool AcceptsAsset(string typeName, string path)
{
return typeName == TypeName && base.AcceptsAsset(typeName, path);
}
/// <inheritdoc />
public override AssetItem ConstructItem(string path, string typeName, ref Guid id)
{
@@ -143,6 +136,12 @@ namespace FlaxEditor.Content
return path.EndsWith(FileExtension, StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return item is JsonAssetItem;
}
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// <see cref="LocalizedStringTable"/> proxy.
/// </summary>
/// <seealso cref="FlaxEditor.Content.JsonAssetProxy" />
public class LocalizedStringTableProxy : JsonAssetProxy
{
/// <inheritdoc />
public override EditorWindow Open(Editor editor, ContentItem item)
{
return new LocalizedStringTableWindow(editor, (JsonAssetItem)item);
}
/// <inheritdoc />
public override string TypeName => "FlaxEngine.LocalizedStringTable";
}
}

View File

@@ -7,6 +7,7 @@
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/HashSet.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Core/Types/Guid.h"
class GameCooker;
class PlatformTools;
@@ -87,34 +88,14 @@ API_ENUM() enum class BuildPlatform
/// </summary>
API_ENUM(Attributes="EditorDisplay(null, \"Android ARM64 (arm64-v8a)\")")
AndroidARM64 = 9,
/// <summary>
/// Switch.
/// </summary>
Switch = 10,
};
inline const Char* ToString(const BuildPlatform platform)
{
switch (platform)
{
case BuildPlatform::Windows32:
return TEXT("Windows x86");
case BuildPlatform::Windows64:
return TEXT("Windows x64");
case BuildPlatform::UWPx86:
return TEXT("Windows Store x86");
case BuildPlatform::UWPx64:
return TEXT("Windows Store x64");
case BuildPlatform::XboxOne:
return TEXT("Xbox One");
case BuildPlatform::LinuxX64:
return TEXT("Linux x64");
case BuildPlatform::PS4:
return TEXT("PlayStation 4");
case BuildPlatform::XboxScarlett:
return TEXT("Xbox Scarlett");
case BuildPlatform::AndroidARM64:
return TEXT("Android ARM64");
default:
return TEXT("?");
}
}
extern FLAXENGINE_API const Char* ToString(const BuildPlatform platform);
/// <summary>
/// Game build configuration modes.
@@ -137,20 +118,7 @@ API_ENUM() enum class BuildConfiguration
Release = 2,
};
inline const Char* ToString(const BuildConfiguration configuration)
{
switch (configuration)
{
case BuildConfiguration::Debug:
return TEXT("Debug");
case BuildConfiguration::Development:
return TEXT("Development");
case BuildConfiguration::Release:
return TEXT("Release");
default:
return TEXT("?");
}
}
extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuration);
#define BUILD_STEP_CANCEL_CHECK if (GameCooker::IsCancelRequested()) return true
@@ -185,9 +153,14 @@ struct FLAXENGINE_API CookingData
String OriginalOutputPath;
/// <summary>
/// The output path.
/// The output path for data files (Content, Mono, etc.).
/// </summary>
String OutputPath;
String DataOutputPath;
/// <summary>
/// The output path for binaries (executable and code libraries).
/// </summary>
String CodeOutputPath;
/// <summary>
/// The platform tools.

View File

@@ -39,7 +39,6 @@
#endif
#if PLATFORM_TOOLS_PS4
#include "Platforms/PS4/Editor/PlatformTools/PS4PlatformTools.h"
#include "Platforms/PS4/Engine/Platform/PS4PlatformSettings.h"
#endif
#if PLATFORM_TOOLS_XBOX_SCARLETT
#include "Platforms/XboxScarlett/Editor/PlatformTools/XboxScarlettPlatformTools.h"
@@ -47,6 +46,9 @@
#if PLATFORM_TOOLS_ANDROID
#include "Platform/Android/AndroidPlatformTools.h"
#endif
#if PLATFORM_TOOLS_SWITCH
#include "Platforms/Switch/Editor/PlatformTools/SwitchPlatformTools.h"
#endif
namespace GameCookerImpl
{
@@ -89,6 +91,50 @@ using namespace GameCookerImpl;
Delegate<GameCooker::EventType> GameCooker::OnEvent;
Delegate<const String&, float> GameCooker::OnProgress;
const Char* ToString(const BuildPlatform platform)
{
switch (platform)
{
case BuildPlatform::Windows32:
return TEXT("Windows x86");
case BuildPlatform::Windows64:
return TEXT("Windows x64");
case BuildPlatform::UWPx86:
return TEXT("Windows Store x86");
case BuildPlatform::UWPx64:
return TEXT("Windows Store x64");
case BuildPlatform::XboxOne:
return TEXT("Xbox One");
case BuildPlatform::LinuxX64:
return TEXT("Linux x64");
case BuildPlatform::PS4:
return TEXT("PlayStation 4");
case BuildPlatform::XboxScarlett:
return TEXT("Xbox Scarlett");
case BuildPlatform::AndroidARM64:
return TEXT("Android ARM64");
case BuildPlatform::Switch:
return TEXT("Switch");
default:
return TEXT("?");
}
}
const Char* ToString(const BuildConfiguration configuration)
{
switch (configuration)
{
case BuildConfiguration::Debug:
return TEXT("Debug");
case BuildConfiguration::Development:
return TEXT("Development");
case BuildConfiguration::Release:
return TEXT("Release");
default:
return TEXT("?");
}
}
bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& other) const
{
if (ContentSize != other.ContentSize)
@@ -250,6 +296,11 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::AndroidARM64:
result = New<AndroidPlatformTools>(ArchitectureType::ARM64);
break;
#endif
#if PLATFORM_TOOLS_SWITCH
case BuildPlatform::Switch:
result = New<SwitchPlatformTools>();
break;
#endif
}
Tools.Add(platform, result);
@@ -282,9 +333,10 @@ void GameCooker::Build(BuildPlatform platform, BuildConfiguration configuration,
data.Configuration = configuration;
data.Options = options;
data.CustomDefines = customDefines;
data.OutputPath = outputPath;
FileSystem::NormalizePath(data.OutputPath);
data.OutputPath = data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OutputPath);
data.OriginalOutputPath = outputPath;
FileSystem::NormalizePath(data.OriginalOutputPath);
data.OriginalOutputPath = FileSystem::ConvertRelativePathToAbsolute(Globals::ProjectFolder, data.OriginalOutputPath);
data.CodeOutputPath = data.DataOutputPath = data.OriginalOutputPath;
data.CacheDirectory = Globals::ProjectCacheFolder / TEXT("Cooker") / tools->GetName();
if (!FileSystem::DirectoryExists(data.CacheDirectory))
{
@@ -367,7 +419,7 @@ bool GameCookerImpl::Build()
CookingData& data = Data;
LOG(Info, "Starting Game Cooker...");
LOG(Info, "Platform: {0}, Configuration: {2}, Options: {1}", ::ToString(data.Platform), (int32)data.Options, ::ToString(data.Configuration));
LOG(Info, "Output Path: {0}", data.OutputPath);
LOG(Info, "Output Path: {0}", data.OriginalOutputPath);
// Late init feature
if (Steps.IsEmpty())

View File

@@ -104,7 +104,8 @@ PixelFormat AndroidPlatformTools::GetTextureFormat(CookingData& data, TextureBas
void AndroidPlatformTools::OnBuildStarted(CookingData& data)
{
// Adjust the cooking output folder to be located inside the Gradle assets directory
data.OutputPath /= TEXT("app/assets");
data.DataOutputPath /= TEXT("app/assets");
data.CodeOutputPath /= TEXT("app/assets");
PlatformTools::OnBuildStarted(data);
}
@@ -114,7 +115,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const auto gameSettings = GameSettings::Get();
const auto platformSettings = AndroidPlatformSettings::Get();
const auto platformDataPath = data.GetPlatformBinariesRoot();
const auto assetsPath = data.OutputPath;
const auto assetsPath = data.DataOutputPath;
const auto jniLibsPath = data.OriginalOutputPath / TEXT("app/jniLibs");
const auto projectVersion = Editor::Project->Version.ToString();
const Char* abi;

View File

@@ -38,7 +38,7 @@ bool LinuxPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto gameSettings = GameSettings::Get();
const auto platformSettings = LinuxPlatformSettings::Get();
const auto outputPath = data.OutputPath;
const auto outputPath = data.DataOutputPath;
// Copy binaries
{

View File

@@ -25,7 +25,7 @@ bool UWPPlatformTools::OnScriptsStepDone(CookingData& data)
{
// Override Newtonsoft.Json.dll for some platforms (that don't support runtime code generation)
const String customBinPath = data.GetPlatformBinariesRoot() / TEXT("Newtonsoft.Json.dll");
const String assembliesPath = data.OutputPath;
const String assembliesPath = data.CodeOutputPath;
if (FileSystem::CopyFile(assembliesPath / TEXT("Newtonsoft.Json.dll"), customBinPath))
{
data.Error(TEXT("Failed to copy deploy custom assembly."));
@@ -64,7 +64,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
if (FileSystem::CopyFile(data.OutputPath / StringUtils::GetFileName(files[i]), files[i]))
if (FileSystem::CopyFile(data.DataOutputPath / StringUtils::GetFileName(files[i]), files[i]))
{
data.Error(TEXT("Failed to setup output directory."));
return true;
@@ -92,7 +92,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
// Prepare certificate
const auto srcCertificatePath = Globals::ProjectFolder / platformSettings->CertificateLocation;
const auto dstCertificatePath = data.OutputPath / TEXT("WSACertificate.pfx");
const auto dstCertificatePath = data.DataOutputPath / TEXT("WSACertificate.pfx");
if (platformSettings->CertificateLocation.HasChars() && FileSystem::FileExists(srcCertificatePath))
{
// Use cert from settings
@@ -115,7 +115,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Copy assets
const auto dstAssetsPath = data.OutputPath / TEXT("Assets");
const auto dstAssetsPath = data.DataOutputPath / TEXT("Assets");
const auto srcAssetsPath = uwpDataPath / TEXT("Assets");
if (!FileSystem::DirectoryExists(dstAssetsPath))
{
@@ -125,7 +125,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
const auto dstPropertiesPath = data.OutputPath / TEXT("Properties");
const auto dstPropertiesPath = data.DataOutputPath / TEXT("Properties");
if (!FileSystem::DirectoryExists(dstPropertiesPath))
{
if (FileSystem::CreateDirectory(dstPropertiesPath))
@@ -176,7 +176,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
const auto dstAppPath = data.OutputPath / TEXT("App.cs");
const auto dstAppPath = data.DataOutputPath / TEXT("App.cs");
const auto srcAppPath = uwpDataPath / TEXT("App.cs");
if (!FileSystem::FileExists(dstAppPath))
{
@@ -205,7 +205,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
return true;
}
}
const auto dstFlaxGeneratedPath = data.OutputPath / TEXT("FlaxGenerated.cs");
const auto dstFlaxGeneratedPath = data.DataOutputPath / TEXT("FlaxGenerated.cs");
const auto srcFlaxGeneratedPath = uwpDataPath / TEXT("FlaxGenerated.cs");
{
// Get template
@@ -264,7 +264,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create solution
const auto dstSolutionPath = data.OutputPath / projectName + TEXT(".sln");
const auto dstSolutionPath = data.DataOutputPath / projectName + TEXT(".sln");
const auto srcSolutionPath = uwpDataPath / TEXT("Solution.sln");
if (!FileSystem::FileExists(dstSolutionPath))
{
@@ -297,7 +297,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create project
const auto dstProjectPath = data.OutputPath / projectName + TEXT(".csproj");
const auto dstProjectPath = data.DataOutputPath / projectName + TEXT(".csproj");
const auto srcProjectPath = uwpDataPath / TEXT("Project.csproj");
{
// Get template
@@ -347,7 +347,7 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data)
}
// Create manifest
const auto dstManifestPath = data.OutputPath / TEXT("Package.appxmanifest");
const auto dstManifestPath = data.DataOutputPath / TEXT("Package.appxmanifest");
const auto srcManifestPath = uwpDataPath / TEXT("Package.appxmanifest");
if (!FileSystem::FileExists(dstManifestPath))
{
@@ -484,8 +484,8 @@ bool UWPPlatformTools::OnPostProcess(CookingData& data)
// Special case for UWP
// FlaxEngine.dll cannot be added to the solution as `Content` item (due to conflicts with C++ /CX FlaxEngine.dll)
// Use special directory for it (generated UWP project handles this case and copies lib to the output)
const String assembliesPath = data.OutputPath;
const auto dstPath1 = data.OutputPath / TEXT("DataSecondary");
const String assembliesPath = data.DataOutputPath;
const auto dstPath1 = data.DataOutputPath / TEXT("DataSecondary");
if (!FileSystem::DirectoryExists(dstPath1))
{
if (FileSystem::CreateDirectory(dstPath1))

View File

@@ -36,7 +36,7 @@ ArchitectureType WindowsPlatformTools::GetArchitecture() const
bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
{
const auto platformSettings = WindowsPlatformSettings::Get();
const auto& outputPath = data.OutputPath;
const auto& outputPath = data.CodeOutputPath;
// Apply executable icon
Array<String> files;

View File

@@ -144,8 +144,8 @@ public:
AotConfig(CookingData& data)
{
Platform::GetEnvironmentVariables(EnvVars);
EnvVars[TEXT("MONO_PATH")] = data.OutputPath / TEXT("Mono/lib/mono/4.5");
AssembliesSearchDirs.Add(data.OutputPath / TEXT("Mono/lib/mono/4.5"));
EnvVars[TEXT("MONO_PATH")] = data.DataOutputPath / TEXT("Mono/lib/mono/4.5");
AssembliesSearchDirs.Add(data.DataOutputPath / TEXT("Mono/lib/mono/4.5"));
}
};

View File

@@ -119,7 +119,7 @@ bool CompileScriptsStep::DeployBinaries(CookingData& data, const String& path, c
}
for (auto& file : files)
{
const String dst = data.OutputPath / StringUtils::GetFileName(file);
const String dst = data.CodeOutputPath / StringUtils::GetFileName(file);
if (dst != file && FileSystem::CopyFile(dst, file))
{
data.Error(TEXT("Failed to copy file from {0} to {1}."), file, dst);
@@ -180,7 +180,12 @@ bool CompileScriptsStep::Perform(CookingData& data)
platform = TEXT("Android");
architecture = TEXT("ARM64");
break;
case BuildPlatform::Switch:
platform = TEXT("Switch");
architecture = TEXT("ARM64");
break;
default:
LOG(Error, "Unknown or unsupported build platform.");
return true;
}
_extensionsToSkip.Clear();
@@ -289,7 +294,7 @@ bool CompileScriptsStep::Perform(CookingData& data)
}
writer.EndObject();
const String outputBuildInfo = data.OutputPath / TEXT("Game.Build.json");
const String outputBuildInfo = data.CodeOutputPath / TEXT("Game.Build.json");
if (File::WriteAllBytes(outputBuildInfo, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Error, "Failed to save binary modules info file {0}.", outputBuildInfo);

View File

@@ -453,6 +453,14 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
break;
}
#endif
#if PLATFORM_TOOLS_SWITCH
case BuildPlatform::Switch:
{
const char* platformDefineName = "PLATFORM_SWITCH";
COMPILE_PROFILE(Vulkan_SM5, SHADER_FILE_CHUNK_INTERNAL_VULKAN_SM5_CACHE);
break;
}
#endif
default:
{
@@ -861,7 +869,7 @@ public:
// Create package
// Note: FlaxStorage::Create overrides chunks locations in file so don't use files anymore (only readonly)
const String localPath = String::Format(TEXT("Content/Data_{0}.{1}"), _packageIndex, PACKAGE_FILES_EXTENSION);
const String path = data.OutputPath / localPath;
const String path = data.DataOutputPath / localPath;
if (FlaxStorage::Create(path, assetsData, false, &CustomData))
{
data.Error(TEXT("Failed to create assets package."));
@@ -1027,7 +1035,7 @@ bool CookAssetsStep::Perform(CookingData& data)
gameFlags |= GameHeaderFlags::ShowSplashScreen;
// Open file
auto stream = FileWriteStream::Open(data.OutputPath / TEXT("Content/head"));
auto stream = FileWriteStream::Open(data.DataOutputPath / TEXT("Content/head"));
if (stream == nullptr)
{
data.Error(TEXT("Failed to create game data file."));
@@ -1121,7 +1129,7 @@ bool CookAssetsStep::Perform(CookingData& data)
BUILD_STEP_CANCEL_CHECK;
// Save assets cache
if (AssetsCache::Save(data.OutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths))
if (AssetsCache::Save(data.DataOutputPath / TEXT("Content/AssetsCache.dat"), AssetsRegistry, AssetPathsMapping, AssetsCacheFlags::RelativePaths))
{
data.Error(TEXT("Failed to create assets registry."));
return true;

View File

@@ -15,7 +15,7 @@ bool DeployDataStep::Perform(CookingData& data)
const auto gameSettings = GameSettings::Get();
// Setup output folders and copy required data
const auto contentDir = data.OutputPath / TEXT("Content");
const auto contentDir = data.DataOutputPath / TEXT("Content");
if (FileSystem::DirectoryExists(contentDir))
{
// Remove old content files
@@ -26,7 +26,7 @@ bool DeployDataStep::Perform(CookingData& data)
}
FileSystem::CreateDirectory(contentDir);
const auto srcMono = depsRoot / TEXT("Mono");
const auto dstMono = data.OutputPath / TEXT("Mono");
const auto dstMono = data.DataOutputPath / TEXT("Mono");
if (!FileSystem::DirectoryExists(dstMono))
{
if (!FileSystem::DirectoryExists(srcMono))

View File

@@ -24,7 +24,7 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
data.Tools->OnConfigureAOT(data, config);
// Prepare output directory
config.AotCachePath = data.OutputPath / TEXT("Mono/lib/mono/aot-cache");
config.AotCachePath = data.DataOutputPath / TEXT("Mono/lib/mono/aot-cache");
switch (data.Tools->GetArchitecture())
{
case ArchitectureType::x86:
@@ -52,9 +52,9 @@ bool PrecompileAssembliesStep::Perform(CookingData& data)
FileSystem::DirectoryGetFiles(config.Assemblies, dir, TEXT("*.dll"), DirectorySearchOption::TopDirectoryOnly);
for (auto& binaryModule : data.BinaryModules)
if (binaryModule.ManagedPath.HasChars())
config.Assemblies.Add(data.OutputPath / binaryModule.ManagedPath);
config.Assemblies.Add(data.CodeOutputPath / binaryModule.ManagedPath);
// TODO: move AOT to Flax.Build and perform it on all C# assemblies used in target build
config.Assemblies.Add(data.OutputPath / TEXT("Newtonsoft.Json.dll"));
config.Assemblies.Add(data.CodeOutputPath / TEXT("Newtonsoft.Json.dll"));
// Perform AOT for the assemblies
for (int32 i = 0; i < config.Assemblies.Count(); i++)

View File

@@ -11,9 +11,17 @@ bool ValidateStep::Perform(CookingData& data)
data.StepProgress(TEXT("Performing validation"), 0);
// Ensure output and cache directories exist
if (!FileSystem::DirectoryExists(data.OutputPath))
if (!FileSystem::DirectoryExists(data.CodeOutputPath))
{
if (FileSystem::CreateDirectory(data.OutputPath))
if (FileSystem::CreateDirectory(data.CodeOutputPath))
{
data.Error(TEXT("Failed to create build output directory."));
return true;
}
}
if (!FileSystem::DirectoryExists(data.DataOutputPath))
{
if (FileSystem::CreateDirectory(data.DataOutputPath))
{
data.Error(TEXT("Failed to create build output directory."));
return true;

View File

@@ -250,6 +250,15 @@ namespace FlaxEditor.CustomEditors
_children[i].RefreshInternal();
}
/// <summary>
/// Synchronizes the value of the <see cref="Values"/> container. Called during Refresh to flush property after editing it in UI.
/// </summary>
/// <param name="value">The value to set.</param>
protected virtual void SynchronizeValue(object value)
{
_values.Set(_parent.Values, value);
}
internal virtual void RefreshInternal()
{
if (_values == null)
@@ -264,7 +273,7 @@ namespace FlaxEditor.CustomEditors
_valueToSet = null;
// Assign value
_values.Set(_parent.Values, val);
SynchronizeValue(val);
// Propagate values up (eg. when member of structure gets modified, also structure should be updated as a part of the other object)
var obj = _parent;

View File

@@ -139,7 +139,7 @@ namespace FlaxEditor.CustomEditors
/// <inheritdoc />
protected override void OnModified()
{
Presenter.Modified?.Invoke();
Presenter.OnModified();
base.OnModified();
}
@@ -354,6 +354,14 @@ namespace FlaxEditor.CustomEditors
ExpandGroups(this, false);
}
/// <summary>
/// Invokes <see cref="Modified"/> event.
/// </summary>
public void OnModified()
{
Modified?.Invoke();
}
/// <summary>
/// Called when selection gets changed.
/// </summary>

View File

@@ -0,0 +1,429 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors.Editors;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Object = FlaxEngine.Object;
namespace FlaxEditor.CustomEditors.Dedicated
{
[CustomEditor(typeof(LocalizationSettings))]
sealed class LocalizationSettingsEditor : GenericEditor
{
private CultureInfo _theMostTranslatedCulture;
private int _theMostTranslatedCultureCount;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
Profiler.BeginEvent("LocalizationSettingsEditor.Initialize");
var settings = (LocalizationSettings)Values[0];
var tablesLength = settings.LocalizedStringTables?.Length ?? 0;
var tables = new List<LocalizedStringTable>(tablesLength);
for (int i = 0; i < tablesLength; i++)
{
var table = settings.LocalizedStringTables[i];
if (table && !table.WaitForLoaded())
tables.Add(table);
}
var locales = tables.GroupBy(x => x.Locale);
var tableEntries = new Dictionary<LocalizedStringTable, Dictionary<string, string[]>>();
var allKeys = new HashSet<string>();
foreach (var e in locales)
{
foreach (var table in e)
{
var entries = table.Entries;
tableEntries[table] = entries;
allKeys.AddRange(entries.Keys);
}
}
{
var group = layout.Group("Preview");
// Current language and culture preview management
group.Object("Current Language", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentLanguage, (instance, index) => Localization.CurrentLanguage, (instance, index, value) => Localization.CurrentLanguage = value as CultureInfo), null, "Current UI display language for the game preview.");
group.Object("Current Culture", new CustomValueContainer(new ScriptType(typeof(CultureInfo)), Localization.CurrentCulture, (instance, index) => Localization.CurrentCulture, (instance, index, value) => Localization.CurrentCulture = value as CultureInfo), null, "Current values formatting culture for the game preview.");
}
{
var group = layout.Group("Locales");
// Show all existing locales
_theMostTranslatedCulture = null;
_theMostTranslatedCultureCount = -1;
foreach (var e in locales)
{
var culture = new CultureInfo(e.Key);
var prop = group.AddPropertyItem(CultureInfoEditor.GetName(culture), culture.NativeName);
int count = e.Sum(x => tableEntries[x].Count);
int validCount = e.Sum(x => tableEntries[x].Values.Count(y => y != null && y.Length != 0 && !string.IsNullOrEmpty(y[0])));
if (count > _theMostTranslatedCultureCount)
{
_theMostTranslatedCulture = culture;
_theMostTranslatedCultureCount = count;
}
prop.Label(string.Format("Progress: {0}% ({1}/{2})", (int)(((float)validCount / allKeys.Count * 100.0f)), validCount, allKeys.Count));
prop.Label("Tables:");
foreach (var table in e)
{
var namePath = table.Path;
if (namePath.StartsWith(Globals.ProjectFolder))
namePath = namePath.Substring(Globals.ProjectFolder.Length + 1);
var tableLabel = prop.ClickableLabel(namePath).CustomControl;
tableLabel.TextColorHighlighted = Color.Wheat;
tableLabel.DoubleClick += delegate { Editor.Instance.Windows.ContentWin.Select(table); };
}
group.Space(10);
}
// Update add button
var update = group.Button("Update").Button;
update.TooltipText = "Refreshes the dashboard statistics";
update.Height = 16.0f;
update.Clicked += RebuildLayout;
// New locale add button
var addLocale = group.Button("Add Locale...").Button;
addLocale.TooltipText = "Shows a locale picker and creates new localization for it with not translated string tables";
addLocale.Height = 16.0f;
addLocale.ButtonClicked += delegate(Button button)
{
var menu = CultureInfoEditor.CreatePicker(null, culture =>
{
var displayName = CultureInfoEditor.GetName(culture);
if (locales.Any(x => x.Key == culture.Name))
{
MessageBox.Show($"Culture '{displayName}' is already added.");
return;
}
Profiler.BeginEvent("LocalizationSettingsEditor.AddLocale");
Editor.Log($"Adding culture '{displayName}' to localization settings");
var newTables = settings.LocalizedStringTables.ToList();
if (_theMostTranslatedCulture != null)
{
// Duplicate localization for culture with the highest amount of keys
var g = locales.First(x => x.Key == _theMostTranslatedCulture.Name);
foreach (var e in g)
{
var path = e.Path;
var filename = Path.GetFileNameWithoutExtension(path);
if (filename.EndsWith(_theMostTranslatedCulture.Name))
filename = filename.Substring(0, filename.Length - _theMostTranslatedCulture.Name.Length);
path = Path.Combine(Path.GetDirectoryName(path), filename + culture.Name + ".json");
var table = FlaxEngine.Content.CreateVirtualAsset<LocalizedStringTable>();
table.Locale = culture.Name;
var entries = new Dictionary<string, string[]>();
foreach (var ee in tableEntries[e])
{
var vv = (string[])ee.Value.Clone();
for (var i = 0; i < vv.Length; i++)
vv[i] = string.Empty;
entries.Add(ee.Key, vv);
}
table.Entries = entries;
if (!table.Save(path))
{
Object.Destroy(table);
newTables.Add(FlaxEngine.Content.LoadAsync<LocalizedStringTable>(path));
}
}
}
else
{
// No localization so initialize with empty table
var path = Path.Combine(Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization", culture.Name + ".json"));
var table = FlaxEngine.Content.CreateVirtualAsset<LocalizedStringTable>();
table.Locale = culture.Name;
if (!table.Save(path))
{
Object.Destroy(table);
newTables.Add(FlaxEngine.Content.LoadAsync<LocalizedStringTable>(path));
}
}
settings.LocalizedStringTables = newTables.ToArray();
Presenter.OnModified();
RebuildLayout();
Profiler.EndEvent();
});
menu.Show(button, new Vector2(0, button.Height));
};
// Export button
var exportLocalization = group.Button("Export...").Button;
exportLocalization.TooltipText = "Exports the localization strings into .pot file for translation";
exportLocalization.Height = 16.0f;
exportLocalization.Clicked += delegate
{
if (FileSystem.ShowSaveFileDialog(null, null, "*.pot", false, "Export localization for translation to .pot file", out var filenames))
return;
Profiler.BeginEvent("LocalizationSettingsEditor.Export");
if (!filenames[0].EndsWith(".pot"))
filenames[0] += ".pot";
var nplurals = 1;
foreach (var e in tableEntries)
{
foreach (var value in e.Value.Values)
{
if (value != null && value.Length > nplurals)
nplurals = value.Length;
}
}
using (var writer = new StreamWriter(filenames[0], false, Encoding.UTF8))
{
writer.WriteLine("msgid \"\"");
writer.WriteLine("msgstr \"\"");
writer.WriteLine("\"Language: English\\n\"");
writer.WriteLine("\"MIME-Version: 1.0\\n\"");
writer.WriteLine("\"Content-Type: text/plain; charset=UTF-8\\n\"");
writer.WriteLine("\"Content-Transfer-Encoding: 8bit\\n\"");
writer.WriteLine($"\"Plural-Forms: nplurals={nplurals}; plural=(n != 1);\\n\"");
writer.WriteLine("\"X-Generator: FlaxEngine\\n\"");
var written = new HashSet<string>();
foreach (var e in tableEntries)
{
foreach (var pair in e.Value)
{
if (written.Contains(pair.Key))
continue;
written.Add(pair.Key);
writer.WriteLine("");
writer.WriteLine($"msgid \"{pair.Key}\"");
if (pair.Value == null || pair.Value.Length < 2)
{
writer.WriteLine("msgstr \"\"");
}
else
{
writer.WriteLine("msgid_plural \"\"");
for (int i = 0; i < pair.Value.Length; i++)
writer.WriteLine($"msgstr[{i}] \"\"");
}
}
if (written.Count == allKeys.Count)
break;
}
}
Profiler.EndEvent();
};
// Find localized strings in code button
var findStringsCode = group.Button("Find localized strings in code").Button;
findStringsCode.TooltipText = "Searches for localized string usage in inside a project source files";
findStringsCode.Height = 16.0f;
findStringsCode.Clicked += delegate
{
var newKeys = new Dictionary<string, string>();
Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInSource");
// C#
var files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cs", SearchOption.AllDirectories);
var filesCount = files.Length;
foreach (var file in files)
FindNewKeysCSharp(file, newKeys, allKeys);
// C++
files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.cpp", SearchOption.AllDirectories);
filesCount += files.Length;
foreach (var file in files)
FindNewKeysCpp(file, newKeys, allKeys);
files = Directory.GetFiles(Globals.ProjectSourceFolder, "*.h", SearchOption.AllDirectories);
filesCount += files.Length;
foreach (var file in files)
FindNewKeysCpp(file, newKeys, allKeys);
AddNewKeys(newKeys, filesCount, locales, tableEntries);
Profiler.EndEvent();
};
// Find localized strings in content button
var findStringsContent = group.Button("Find localized strings in content").Button;
findStringsContent.TooltipText = "Searches for localized string usage in inside a project content files (scenes, prefabs)";
findStringsContent.Height = 16.0f;
findStringsContent.Clicked += delegate
{
var newKeys = new Dictionary<string, string>();
Profiler.BeginEvent("LocalizationSettingsEditor.FindLocalizedStringsInContent");
// Scenes
var files = Directory.GetFiles(Globals.ProjectContentFolder, "*.scene", SearchOption.AllDirectories);
var filesCount = files.Length;
foreach (var file in files)
FindNewKeysJson(file, newKeys, allKeys);
// Prefabs
files = Directory.GetFiles(Globals.ProjectContentFolder, "*.prefab", SearchOption.AllDirectories);
filesCount += files.Length;
foreach (var file in files)
FindNewKeysJson(file, newKeys, allKeys);
AddNewKeys(newKeys, filesCount, locales, tableEntries);
Profiler.EndEvent();
};
}
{
// Raw asset data editing
var group = layout.Group("Data");
base.Initialize(group);
}
Profiler.EndEvent();
}
private static void FindNewKeysCSharp(string file, Dictionary<string, string> newKeys, HashSet<string> allKeys)
{
var startToken = "Localization.GetString";
var textToken = "\"";
FindNewKeys(file, newKeys, allKeys, startToken, textToken);
}
private static void FindNewKeysCpp(string file, Dictionary<string, string> newKeys, HashSet<string> allKeys)
{
var startToken = "Localization::GetString";
var textToken = "TEXT(\"";
FindNewKeys(file, newKeys, allKeys, startToken, textToken);
}
private static void FindNewKeys(string file, Dictionary<string, string> newKeys, HashSet<string> allKeys, string startToken, string textToken)
{
var contents = File.ReadAllText(file);
var idx = contents.IndexOf(startToken);
while (idx != -1)
{
idx += startToken.Length + 1;
int braces = 1;
int start = idx;
while (idx < contents.Length && braces != 0)
{
if (contents[idx] == '(')
braces++;
if (contents[idx] == ')')
braces--;
idx++;
}
if (idx == contents.Length)
break;
var inside = contents.Substring(start, idx - start - 1);
var textStart = inside.IndexOf(textToken);
if (textStart != -1)
{
textStart += textToken.Length;
var textEnd = textStart;
while (textEnd < inside.Length && inside[textEnd] != '\"')
{
if (inside[textEnd] == '\\')
textEnd++;
textEnd++;
}
var id = inside.Substring(textStart, textEnd - textStart);
textStart = inside.Length > textEnd + 2 ? inside.IndexOf(textToken, textEnd + 2) : -1;
string value = null;
if (textStart != -1)
{
textStart += textToken.Length;
textEnd = textStart;
while (textEnd < inside.Length && inside[textEnd] != '\"')
{
if (inside[textEnd] == '\\')
textEnd++;
textEnd++;
}
value = inside.Substring(textStart, textEnd - textStart);
}
if (!allKeys.Contains(id))
newKeys[id] = value;
}
idx = contents.IndexOf(startToken, idx);
}
}
private static void FindNewKeysJson(Dictionary<string, string> newKeys, HashSet<string> allKeys, JToken token)
{
if (token is JObject o)
{
foreach (var p in o)
{
if (string.Equals(p.Key, "Id", StringComparison.Ordinal) && p.Value is JValue i && i.Value is string id && !allKeys.Contains(id))
{
var count = o.Properties().Count();
if (count == 1)
{
newKeys[id] = null;
return;
}
if (count == 2)
{
var v = o.Property("Value")?.Value as JValue;
if (v?.Value is string value)
{
newKeys[id] = value;
return;
}
}
}
FindNewKeysJson(newKeys, allKeys, p.Value);
}
}
else if (token is JArray a)
{
foreach (var p in a)
{
FindNewKeysJson(newKeys, allKeys, p);
}
}
}
private static void FindNewKeysJson(string file, Dictionary<string, string> newKeys, HashSet<string> allKeys)
{
using (var reader = new StreamReader(file))
using (var jsonReader = new JsonTextReader(reader))
{
var token = JToken.ReadFrom(jsonReader);
FindNewKeysJson(newKeys, allKeys, token);
}
}
private void AddNewKeys(Dictionary<string, string> newKeys, int filesCount, IEnumerable<IGrouping<string, LocalizedStringTable>> locales, Dictionary<LocalizedStringTable, Dictionary<string, string[]>> tableEntries)
{
Editor.Log($"Found {newKeys.Count} new localized strings in {filesCount} files");
if (newKeys.Count == 0)
return;
foreach (var e in newKeys)
Editor.Log(e.Key + (e.Value != null ? " = " + e.Value : string.Empty));
foreach (var locale in locales)
{
var table = locale.First();
var entries = tableEntries[table];
if (table.Locale == "en")
{
foreach (var e in newKeys)
entries[e.Key] = new[] { e.Value };
}
else
{
foreach (var e in newKeys)
entries[e.Key] = new[] { string.Empty };
}
table.Entries = entries;
table.Save();
}
RebuildLayout();
}
}
}

View File

@@ -32,17 +32,13 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_presets != value)
{
_presets = value;
OnPresetsChanged();
TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
}
}
}
public bool IsSelected;
private void OnPresetsChanged()
{
TooltipText = CustomEditorsUtil.GetPropertyNameUI(_presets.ToString());
}
public bool SupportsShiftModulation;
/// <inheritdoc />
public override void Draw()
@@ -79,6 +75,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
borderColor = BorderColorHighlighted;
}
if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Shift))
{
backgroundColor = BackgroundColorSelected;
}
// Calculate fill area
float fillSize = rect.Width / 3;
Rectangle fillArea;
@@ -154,24 +155,83 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
Render2D.DrawRectangle(rect, style.BackgroundSelected.AlphaMultiplied(0.8f), 1.1f);
}
// Draw pivot point
if (SupportsShiftModulation && Input.GetKey(KeyboardKeys.Control))
{
Vector2 pivotPoint;
switch (_presets)
{
case AnchorPresets.Custom:
pivotPoint = Vector2.Minimum;
break;
case AnchorPresets.TopLeft:
pivotPoint = new Vector2(0, 0);
break;
case AnchorPresets.TopCenter:
case AnchorPresets.HorizontalStretchTop:
pivotPoint = new Vector2(rect.Width / 2, 0);
break;
case AnchorPresets.TopRight:
pivotPoint = new Vector2(rect.Width, 0);
break;
case AnchorPresets.MiddleLeft:
case AnchorPresets.VerticalStretchLeft:
pivotPoint = new Vector2(0, rect.Height / 2);
break;
case AnchorPresets.MiddleCenter:
case AnchorPresets.VerticalStretchCenter:
case AnchorPresets.HorizontalStretchMiddle:
case AnchorPresets.StretchAll:
pivotPoint = new Vector2(rect.Width / 2, rect.Height / 2);
break;
case AnchorPresets.MiddleRight:
case AnchorPresets.VerticalStretchRight:
pivotPoint = new Vector2(rect.Width, rect.Height / 2);
break;
case AnchorPresets.BottomLeft:
pivotPoint = new Vector2(0, rect.Height);
break;
case AnchorPresets.BottomCenter:
case AnchorPresets.HorizontalStretchBottom:
pivotPoint = new Vector2(rect.Width / 2, rect.Height);
break;
case AnchorPresets.BottomRight:
pivotPoint = new Vector2(rect.Width, rect.Height);
break;
default: throw new ArgumentOutOfRangeException();
}
var pivotPointSize = new Vector2(3.0f);
Render2D.DrawRectangle(new Rectangle(pivotPoint - pivotPointSize * 0.5f, pivotPointSize), style.ProgressNormal, 1.1f);
}
}
}
class AnchorPresetsEditorPopup : ContextMenuBase
/// <summary>
/// Context menu for anchors presets editing.
/// </summary>
/// <seealso cref="FlaxEditor.GUI.ContextMenu.ContextMenuBase" />
public sealed class AnchorPresetsEditorPopup : ContextMenuBase
{
const float ButtonsMargin = 10.0f;
const float ButtonsMarginStretch = 8.0f;
const float ButtonsSize = 32.0f;
const float TitleHeight = 23.0f;
const float InfoHeight = 23.0f;
const float DialogWidth = ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
const float DialogHeight = TitleHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
const float DialogHeight = TitleHeight + InfoHeight + ButtonsSize * 4 + ButtonsMargin * 5 + ButtonsMarginStretch;
private readonly bool _supportsShiftModulation;
/// <summary>
/// Initializes a new instance of the <see cref="AnchorPresetsEditorPopup"/> class.
/// </summary>
/// <param name="presets">The initial value.</param>
public AnchorPresetsEditorPopup(AnchorPresets presets)
/// <param name="supportsShiftModulation">If the popup should react to shift</param>
public AnchorPresetsEditorPopup(AnchorPresets presets, bool supportsShiftModulation = true)
{
_supportsShiftModulation = supportsShiftModulation;
var style = FlaxEngine.GUI.Style.Current;
Tag = presets;
Size = new Vector2(DialogWidth, DialogHeight);
@@ -184,9 +244,17 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = this
};
// Info
var info = new Label(0, title.Bottom, DialogWidth, InfoHeight)
{
Font = new FontReference(style.FontSmall),
Text = "Shift: also set bounds\nControl: also set pivot",
Parent = this
};
// Buttons
var buttonsX = ButtonsMargin;
var buttonsY = title.Bottom + ButtonsMargin;
var buttonsY = info.Bottom + ButtonsMargin;
var buttonsSpacingX = ButtonsSize + ButtonsMargin;
var buttonsSpacingY = ButtonsSize + ButtonsMargin;
//
@@ -219,6 +287,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Parent = this,
Presets = presets,
IsSelected = presets == (AnchorPresets)Tag,
SupportsShiftModulation = _supportsShiftModulation,
Tag = presets,
};
button.ButtonClicked += OnButtonClicked;
@@ -278,7 +347,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void OnButtonClicked()
{
var location = _button.Center + new Vector2(3.0f);
var editor = new AnchorPresetsEditorPopup(_button.Presets);
var editor = new AnchorPresetsEditorPopup(_button.Presets, true);
editor.VisibleChanged += OnEditorVisibleChanged;
editor.Show(_button.Parent, location);
}
@@ -290,6 +359,30 @@ namespace FlaxEditor.CustomEditors.Dedicated
SetValue(control.Tag);
}
/// <inheritdoc/>
protected override void SynchronizeValue(object value)
{
// Custom anchors editing for Control to handle bounds preservation via key modifiers
if (ParentEditor != null)
{
var centerToPosition = Input.GetKey(KeyboardKeys.Shift);
var setPivot = Input.GetKey(KeyboardKeys.Control);
var editedAny = false;
foreach (var parentValue in ParentEditor.Values)
{
if (parentValue is Control parentControl)
{
parentControl.SetAnchorPreset((AnchorPresets)value, !centerToPosition, setPivot);
editedAny = true;
}
}
if (editedAny)
return;
}
base.SynchronizeValue(value);
}
/// <inheritdoc />
public override void Refresh()
{
@@ -311,7 +404,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
/// Dedicated custom editor for <see cref="UIControl.Control"/> object.
/// </summary>
/// <seealso cref="FlaxEditor.CustomEditors.Editors.GenericEditor" />
public sealed class UIControlControlEditor : GenericEditor
public class UIControlControlEditor : GenericEditor
{
private Type _cachedType;
@@ -423,9 +516,9 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void BuildLocationSizeOffsets(LayoutElementsContainer horUp, LayoutElementsContainer horDown, bool xEq, bool yEq, ScriptType[] valueTypes)
{
ScriptMemberInfo xInfo = valueTypes[0].GetProperty("X");
ScriptMemberInfo xInfo = valueTypes[0].GetProperty("LocalX");
ItemInfo xItem = new ItemInfo(xInfo);
ScriptMemberInfo yInfo = valueTypes[0].GetProperty("Y");
ScriptMemberInfo yInfo = valueTypes[0].GetProperty("LocalY");
ItemInfo yItem = new ItemInfo(yInfo);
ScriptMemberInfo widthInfo = valueTypes[0].GetProperty("Width");
ItemInfo widthItem = new ItemInfo(widthInfo);

View File

@@ -154,9 +154,20 @@ namespace FlaxEditor.CustomEditors.Editors
if (i != 0 && spacing > 0f)
{
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
if (propertiesListElement.Labels.Count > 0)
{
var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
var margin = label.Margin;
margin.Bottom += spacing;
label.Margin = margin;
}
propertiesListElement.Space(spacing);
}
else
{
layout.Space(spacing);
}
}
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;

View File

@@ -0,0 +1,164 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using FlaxEditor.GUI;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Default implementation of the inspector used to edit <see cref="CultureInfo"/> value type properties. Supports editing property of <see cref="string"/> type (as culture name).
/// </summary>
[CustomEditor(typeof(CultureInfo)), DefaultEditor]
internal class CultureInfoEditor : CustomEditor
{
private ClickableLabel _label;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
_label = layout.ClickableLabel(GetName(Culture)).CustomControl;
_label.RightClick += ShowPicker;
var button = new Button
{
Width = 16.0f,
Text = "...",
Parent = _label,
};
button.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
button.Clicked += ShowPicker;
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
_label.Text = GetName(Culture);
}
/// <inheritdoc />
protected override void Deinitialize()
{
_label = null;
base.Deinitialize();
}
private CultureInfo Culture
{
get
{
if (Values[0] is CultureInfo asCultureInfo)
return asCultureInfo;
if (Values[0] is string asString)
return new CultureInfo(asString);
return null;
}
set
{
if (Values[0] is CultureInfo)
SetValue(value);
else if (Values[0] is string)
SetValue(value.Name);
}
}
private class CultureInfoComparer : IComparer<CultureInfo>
{
public int Compare(CultureInfo a, CultureInfo b)
{
return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
}
}
private static void UpdateFilter(TreeNode node, string filterText)
{
// Update children
bool isAnyChildVisible = false;
for (int i = 0; i < node.Children.Count; i++)
{
if (node.Children[i] is TreeNode child)
{
UpdateFilter(child, filterText);
isAnyChildVisible |= child.Visible;
}
}
// Update itself
bool noFilter = string.IsNullOrWhiteSpace(filterText);
bool isThisVisible = noFilter || QueryFilterHelper.Match(filterText, node.Text);
bool isExpanded = isAnyChildVisible;
if (isExpanded)
node.Expand(true);
else
node.Collapse(true);
node.Visible = isThisVisible | isAnyChildVisible;
}
private void ShowPicker()
{
var menu = CreatePicker(Culture, value => { Culture = value; });
menu.Show(_label, new Vector2(0, _label.Height));
}
internal static ContextMenuBase CreatePicker(CultureInfo value, Action<CultureInfo> changed)
{
var menu = Utilities.Utils.CreateSearchPopup(out var searchBox, out var tree);
tree.Margin = new Margin(-16.0f, 0.0f, -16.0f, -0.0f); // Hide root node
var root = tree.AddChild<TreeNode>();
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
Array.Sort(cultures, 1, cultures.Length - 2, new CultureInfoComparer()); // at 0 there is Invariant Culture
var lcidToNode = new Dictionary<int, ContainerControl>();
for (var i = 0; i < cultures.Length; i++)
{
var culture = cultures[i];
var node = new TreeNode
{
Tag = culture,
Text = GetName(culture),
};
if (!lcidToNode.TryGetValue(culture.Parent.LCID, out ContainerControl parent))
parent = root;
node.Parent = parent;
lcidToNode[culture.LCID] = node;
}
if (value != null)
tree.Select((TreeNode)lcidToNode[value.LCID]);
tree.SelectedChanged += delegate(List<TreeNode> before, List<TreeNode> after)
{
if (after.Count == 1)
{
menu.Hide();
changed((CultureInfo)after[0].Tag);
}
};
searchBox.TextChanged += delegate
{
if (tree.IsLayoutLocked)
return;
root.LockChildrenRecursive();
var query = searchBox.Text;
UpdateFilter(root, query);
root.UnlockChildrenRecursive();
menu.PerformLayout();
};
root.ExpandAll(true);
return menu;
}
internal static string GetName(CultureInfo value)
{
return value != null ? string.Format("{0} - {1}", value.Name, value.EnglishName) : null;
}
}
}

View File

@@ -61,7 +61,7 @@ namespace FlaxEditor.CustomEditors.Editors
var keyType = _editor.Values.Type.GetGenericArguments()[0];
if (keyType == typeof(string) || keyType.IsPrimitive)
{
var popup = RenamePopup.Show(Parent, Bounds, Text, false);
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
popup.Validate += (renamePopup, value) =>
{
object newKey;
@@ -86,7 +86,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
else if (keyType.IsEnum)
{
var popup = RenamePopup.Show(Parent, Bounds, Text, false);
var popup = RenamePopup.Show(Parent, Rectangle.Margin(Bounds, Margin), Text, false);
var picker = new EnumComboBox(keyType)
{
AnchorPreset = AnchorPresets.StretchAll,
@@ -220,9 +220,20 @@ namespace FlaxEditor.CustomEditors.Editors
if (i != 0 && spacing > 0f)
{
if (layout.Children.Count > 0 && layout.Children[layout.Children.Count - 1] is PropertiesListElement propertiesListElement)
{
if (propertiesListElement.Labels.Count > 0)
{
var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
var margin = label.Margin;
margin.Bottom += spacing;
label.Margin = margin;
}
propertiesListElement.Space(spacing);
}
else
{
layout.Space(spacing);
}
}
var key = keys.ElementAt(i);

View File

@@ -9,7 +9,7 @@ using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Default implementation of the inspector used to edit float value type properties.
/// Default implementation of the inspector used to edit enum value type properties.
/// </summary>
[CustomEditor(typeof(Enum)), DefaultEditor]
public class EnumEditor : CustomEditor

View File

@@ -214,6 +214,7 @@ namespace FlaxEditor.CustomEditors.Editors
public ScriptMemberInfo Target;
public ScriptMemberInfo Source;
public PropertiesListElement PropertiesList;
public GroupElement Group;
public bool Invert;
public int LabelIndex;
@@ -379,26 +380,22 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
}
if (item.VisibleIf != null)
if (item.VisibleIf != null && itemLayout.Children.Count > 0)
{
PropertiesListElement list;
if (itemLayout.Children.Count > 0 && itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1)
{
PropertiesListElement list = null;
GroupElement group = null;
if (itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement list1)
list = list1;
}
else if (itemLayout.Children[itemLayout.Children.Count - 1] is GroupElement group1)
group = group1;
else
{
// TODO: support inlined objects hiding?
return;
}
// Get source member used to check rule
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
if (sourceMember == ScriptType.Null)
return;
// Find the target control to show/hide
// Resize cache
if (_visibleIfCaches == null)
_visibleIfCaches = new VisibleIfCache[8];
@@ -414,6 +411,7 @@ namespace FlaxEditor.CustomEditors.Editors
Target = item.Info,
Source = sourceMember,
PropertiesList = list,
Group = group,
LabelIndex = labelIndex,
Invert = item.VisibleIf.Invert,
};
@@ -569,8 +567,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
for (int i = 0; i < _visibleIfCaches.Length; i++)
{
var c = _visibleIfCaches[i];
ref var c = ref _visibleIfCaches[i];
if (c.Target == ScriptMemberInfo.Null)
break;
@@ -586,7 +583,7 @@ namespace FlaxEditor.CustomEditors.Editors
}
// Apply the visibility (note: there may be no label)
if (c.LabelIndex != -1 && c.PropertiesList.Labels.Count > c.LabelIndex)
if (c.LabelIndex != -1 && c.PropertiesList != null && c.PropertiesList.Labels.Count > c.LabelIndex)
{
var label = c.PropertiesList.Labels[c.LabelIndex];
label.Visible = visible;
@@ -599,6 +596,10 @@ namespace FlaxEditor.CustomEditors.Editors
child.Visible = visible;
}
}
if (c.Group != null)
{
c.Group.Panel.Visible = visible;
}
}
}
catch (Exception ex)

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using FlaxEditor.Content.Settings;
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.GUI.Tree;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using Utils = FlaxEditor.Utilities.Utils;
namespace FlaxEditor.CustomEditors.Editors
{
/// <summary>
/// Default implementation of the inspector used to edit localized string properties.
/// </summary>
[CustomEditor(typeof(LocalizedString)), DefaultEditor]
public sealed class LocalizedStringEditor : GenericEditor
{
private TextBoxElement _idElement, _valueElement;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
if (layout.Children.Count == 0)
return;
var propList = layout.Children[layout.Children.Count - 1] as PropertiesListElement;
if (propList == null || propList.Children.Count != 2)
return;
var idElement = propList.Children[0] as TextBoxElement;
var valueElement = propList.Children[1] as TextBoxElement;
if (idElement == null || valueElement == null)
return;
_idElement = idElement;
_valueElement = valueElement;
var attributes = Values.GetAttributes();
var multiLine = attributes?.FirstOrDefault(x => x is MultilineTextAttribute);
if (multiLine != null)
{
valueElement.TextBox.IsMultiline = true;
valueElement.TextBox.Height *= 3;
}
var selectString = new Button
{
Width = 16.0f,
Text = "...",
TooltipText = "Select localized text from Localization Settings...",
Parent = idElement.TextBox,
};
selectString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
selectString.ButtonClicked += OnSelectStringClicked;
var addString = new Button
{
Width = 16.0f,
Text = "+",
TooltipText = "Add new localized text to Localization Settings (all used locales)",
Parent = _valueElement.TextBox,
Enabled = IsSingleObject,
};
addString.SetAnchorPreset(AnchorPresets.MiddleRight, false, true);
addString.ButtonClicked += OnAddStringClicked;
}
/// <inheritdoc />
internal override void RefreshInternal()
{
base.RefreshInternal();
if (_valueElement != null)
{
_valueElement.TextBox.WatermarkText = Localization.GetString(_idElement.Text);
}
}
/// <inheritdoc />
protected override void Deinitialize()
{
base.Deinitialize();
_idElement = null;
_valueElement = null;
}
private void OnSelectStringClicked(Button button)
{
var settings = GameSettings.Load<LocalizationSettings>();
if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0)
{
MessageBox.Show("No valid localization settings setup.");
return;
}
Profiler.BeginEvent("LocalizedStringEditor.OnSelectStringClicked");
var allKeys = new HashSet<string>();
for (int i = 0; i < settings.LocalizedStringTables.Length; i++)
{
var table = settings.LocalizedStringTables[i];
if (table && !table.WaitForLoaded())
{
var entries = table.Entries;
foreach (var e in entries)
allKeys.Add(e.Key);
}
}
var allKeysSorted = allKeys.ToList();
allKeysSorted.Sort();
var value = _idElement?.TextBox.Text;
var menu = Utils.CreateSearchPopup(out var searchBox, out var tree);
var idToNode = new TreeNode[allKeysSorted.Count];
for (var i = 0; i < allKeysSorted.Count; i++)
{
var key = allKeysSorted[i];
var node = new TreeNode
{
Text = key,
TooltipText = Localization.GetString(key),
Parent = tree,
};
if (key == value)
tree.Select(node);
idToNode[i] = node;
}
tree.SelectedChanged += delegate(List<TreeNode> before, List<TreeNode> after)
{
if (after.Count == 1)
{
menu.Hide();
_idElement.TextBox.SetTextAsUser(after[0].Text);
}
};
searchBox.TextChanged += delegate
{
if (tree.IsLayoutLocked)
return;
tree.LockChildrenRecursive();
var query = searchBox.Text;
for (int i = 0; i < idToNode.Length; i++)
{
var node = idToNode[i];
node.Visible = string.IsNullOrWhiteSpace(query) || QueryFilterHelper.Match(query, node.Text);
}
tree.UnlockChildrenRecursive();
menu.PerformLayout();
};
menu.Show(button, new Vector2(0, button.Height));
Profiler.EndEvent();
}
private void OnAddStringClicked(Button button)
{
var settings = GameSettings.Load<LocalizationSettings>();
if (settings?.LocalizedStringTables == null || settings.LocalizedStringTables.Length == 0)
{
MessageBox.Show("No valid localization settings setup.");
return;
}
Profiler.BeginEvent("LocalizedStringEditor.OnAddStringClicked");
var allKeys = new HashSet<string>();
for (int i = 0; i < settings.LocalizedStringTables.Length; i++)
{
var table = settings.LocalizedStringTables[i];
if (table && !table.WaitForLoaded())
{
var entries = table.Entries;
foreach (var e in entries)
allKeys.Add(e.Key);
}
}
_valueElement.TextBox.SetTextAsUser(null);
string newKey = null;
if (string.IsNullOrEmpty(_idElement.Text))
{
CustomEditor customEditor = this;
while (customEditor?.Values != null)
{
if (customEditor.Values.Info != ScriptMemberInfo.Null)
if (newKey == null)
newKey = customEditor.Values.Info.Name;
else
newKey = customEditor.Values.Info.Name + '.' + newKey;
else if (customEditor.Values[0] is SceneObject sceneObject)
if (newKey == null)
newKey = sceneObject.GetNamePath('.');
else
newKey = sceneObject.GetNamePath('.') + '.' + newKey;
else
break;
customEditor = customEditor.ParentEditor;
}
if (string.IsNullOrWhiteSpace(newKey))
newKey = Guid.NewGuid().ToString("N");
}
else
{
newKey = _idElement.Text;
}
if (allKeys.Contains(newKey))
{
Profiler.EndEvent();
if (_idElement.Text != newKey)
_idElement.TextBox.SetTextAsUser(newKey);
else
MessageBox.Show("Already added.");
return;
}
var newValue = _valueElement.Text;
Editor.Log(newKey + (newValue != null ? " = " + newValue : string.Empty));
var locales = settings.LocalizedStringTables.GroupBy(x => x.Locale);
foreach (var locale in locales)
{
var table = locale.First();
var entries = table.Entries;
if (table.Locale == "en")
entries[newKey] = new[] { newValue };
else
entries[newKey] = new[] { string.Empty };
table.Entries = entries;
table.Save();
}
_idElement.TextBox.SetTextAsUser(newKey);
Profiler.EndEvent();
}
}
}

View File

@@ -3,7 +3,6 @@
using System;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
{

View File

@@ -12,6 +12,9 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Quaternion)), DefaultEditor]
public class QuaternionEditor : CustomEditor
{
private Vector3 _cachedAngles = Vector3.Zero;
private object _cachedToken;
/// <summary>
/// The X component element
/// </summary>
@@ -58,15 +61,36 @@ namespace FlaxEditor.CustomEditors.Editors
if (IsSetBlocked)
return;
float x = XElement.FloatValue.Value;
float y = YElement.FloatValue.Value;
float z = ZElement.FloatValue.Value;
var isSliding = XElement.IsSliding || YElement.IsSliding || ZElement.IsSliding;
var token = isSliding ? this : null;
var useCachedAngles = isSliding && token == _cachedToken;
float x = (useCachedAngles && !XElement.IsSliding) ? _cachedAngles.X : XElement.FloatValue.Value;
float y = (useCachedAngles && !YElement.IsSliding) ? _cachedAngles.Y : YElement.FloatValue.Value;
float z = (useCachedAngles && !ZElement.IsSliding) ? _cachedAngles.Z : ZElement.FloatValue.Value;
x = Mathf.UnwindDegrees(x);
y = Mathf.UnwindDegrees(y);
z = Mathf.UnwindDegrees(z);
if (!useCachedAngles)
{
_cachedAngles = new Vector3(x, y, z);
}
_cachedToken = token;
Quaternion.Euler(x, y, z, out Quaternion value);
SetValue(value, token);
}
/// <inheritdoc />
protected override void ClearToken()
{
_cachedToken = null;
base.ClearToken();
}
/// <inheritdoc />
public override void Refresh()
{

View File

@@ -5,15 +5,15 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements
{
/// <summary>
/// The vertical panel element.
/// The horizontal panel element.
/// </summary>
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
public class VerticalPanelElement : LayoutElementsContainer
public class HorizontalPanelElement : LayoutElementsContainer
{
/// <summary>
/// The panel.
/// </summary>
public readonly VerticalPanel Panel = new VerticalPanel();
public readonly HorizontalPanel Panel = new HorizontalPanel();
/// <inheritdoc />
public override ContainerControl ContainerControl => Panel;

View File

@@ -5,15 +5,15 @@ using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Elements
{
/// <summary>
/// The horizontal panel element.
/// The vertical panel element.
/// </summary>
/// <seealso cref="FlaxEditor.CustomEditors.LayoutElement" />
public class HorizontalPanelElement : LayoutElementsContainer
public class VerticalPanelElement : LayoutElementsContainer
{
/// <summary>
/// The panel.
/// </summary>
public readonly HorizontalPanel Panel = new HorizontalPanel();
public readonly VerticalPanel Panel = new VerticalPanel();
/// <inheritdoc />
public override ContainerControl ContainerControl => Panel;

View File

@@ -112,7 +112,7 @@ namespace FlaxEditor.CustomEditors
OnAddElement(element);
return element;
}
/// <summary>
/// Adds new horizontal panel element.
/// </summary>
@@ -690,6 +690,17 @@ namespace FlaxEditor.CustomEditors
return element;
}
/// <summary>
/// Adds custom element to the layout.
/// </summary>
/// <param name="element">The element.</param>
public void AddElement(LayoutElement element)
{
if (element == null)
throw new ArgumentNullException();
OnAddElement(element);
}
/// <summary>
/// Called when element is added to the layout.
/// </summary>

View File

@@ -59,6 +59,7 @@ public class Editor : EditorModule
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "PS4", "PLATFORM_TOOLS_PS4");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "XboxScarlett", "PLATFORM_TOOLS_XBOX_SCARLETT");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Android", "PLATFORM_TOOLS_ANDROID");
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Switch", "PLATFORM_TOOLS_SWITCH");
}
AddPlatformTools(options, platformToolsRoot, platformToolsRootExternal, "Linux", "PLATFORM_TOOLS_LINUX");

View File

@@ -752,10 +752,11 @@ namespace FlaxEditor
/// <param name="type">The collision data type.</param>
/// <param name="model">The source model.</param>
/// <param name="modelLodIndex">The source model LOD index.</param>
/// <param name="materialSlotsMask">The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking.</param>
/// <param name="convexFlags">The convex mesh generation flags.</param>
/// <param name="convexVertexLimit">The convex mesh vertex limit. Use values in range [8;255]</param>
/// <returns>True if failed, otherwise false.</returns>
public static bool CookMeshCollision(string path, CollisionDataType type, Model model, int modelLodIndex = 0, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
public static bool CookMeshCollision(string path, CollisionDataType type, ModelBase model, int modelLodIndex = 0, uint materialSlotsMask = uint.MaxValue, ConvexMeshGenerationFlags convexFlags = ConvexMeshGenerationFlags.None, int convexVertexLimit = 255)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
@@ -764,7 +765,7 @@ namespace FlaxEditor
if (type == CollisionDataType.None)
throw new ArgumentException(nameof(type));
return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, convexFlags, convexVertexLimit);
return Internal_CookMeshCollision(path, type, FlaxEngine.Object.GetUnmanagedPtr(model), modelLodIndex, materialSlotsMask, convexFlags, convexVertexLimit);
}
/// <summary>
@@ -877,10 +878,12 @@ namespace FlaxEditor
/// Checks if can import asset with the given extension.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <param name="outputExtension">The output file extension (flax, json, etc.).</param>
/// <returns>True if can import files with given extension, otherwise false.</returns>
public static bool CanImport(string extension)
public static bool CanImport(string extension, out string outputExtension)
{
return Internal_CanImport(extension);
outputExtension = Internal_CanImport(extension);
return outputExtension != null;
}
/// <summary>
@@ -1344,7 +1347,7 @@ namespace FlaxEditor
internal static extern string Internal_GetShaderAssetSourceCode(IntPtr obj);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
internal static extern bool Internal_CookMeshCollision(string path, CollisionDataType type, IntPtr model, int modelLodIndex, uint materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void Internal_GetCollisionWires(IntPtr collisionData, out Vector3[] triangles, out int[] indices);
@@ -1368,7 +1371,7 @@ namespace FlaxEditor
internal static extern bool Internal_CreateVisualScript(string outputPath, string baseTypename);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CanImport(string extension);
internal static extern string Internal_CanImport(string extension);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Internal_CanExport(string path);

View File

@@ -29,7 +29,7 @@ namespace FlaxEditor.GUI
/// <summary>
/// The cached value from the UI.
/// </summary>
protected int _cachedValue;
protected ulong _cachedValue;
/// <summary>
/// True if has value cached, otherwise false.
@@ -54,7 +54,7 @@ namespace FlaxEditor.GUI
/// <summary>
/// The value.
/// </summary>
public int Value;
public ulong Value;
/// <summary>
/// Initializes a new instance of the <see cref="Entry"/> struct.
@@ -62,7 +62,7 @@ namespace FlaxEditor.GUI
/// <param name="name">The name.</param>
/// <param name="tooltip">The tooltip.</param>
/// <param name="value">The value.</param>
public Entry(string name, int value, string tooltip = null)
public Entry(string name, ulong value, string tooltip = null)
{
Name = name;
Tooltip = tooltip;
@@ -88,13 +88,13 @@ namespace FlaxEditor.GUI
public object EnumTypeValue
{
get => Enum.ToObject(_enumType, Value);
set => Value = Convert.ToInt32(value);
set => Value = Convert.ToUInt64(value);
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public int Value
public ulong Value
{
get => _cachedValue;
set
@@ -209,13 +209,13 @@ namespace FlaxEditor.GUI
/// </summary>
protected void CacheValue()
{
int value = 0;
ulong value = 0;
if (IsFlags)
{
var selection = Selection;
for (int i = 0; i < selection.Count; i++)
{
int index = selection[i];
var index = selection[i];
value |= _entries[index].Value;
}
}
@@ -276,7 +276,7 @@ namespace FlaxEditor.GUI
tooltip = tooltipAttr.Text;
}
entries.Add(new Entry(name, Convert.ToInt32(field.GetRawConstantValue()), tooltip));
entries.Add(new Entry(name, Convert.ToUInt64(field.GetRawConstantValue()), tooltip));
}
}
@@ -295,9 +295,9 @@ namespace FlaxEditor.GUI
}
// Calculate value that will be set after change
int valueAfter = 0;
ulong valueAfter = 0;
bool isSelected = _selectedIndices.Contains(index);
int selectedValue = entries[index].Value;
ulong selectedValue = entries[index].Value;
for (int i = 0; i < _selectedIndices.Count; i++)
{
int selectedIndex = _selectedIndices[i];

View File

@@ -89,6 +89,7 @@ namespace FlaxEditor.GUI
new PlatformData(PlatformType.PS4, icons.PS4, "PlayStation 4"),
new PlatformData(PlatformType.XboxScarlett, icons.XboxSeriesX, "Xbox Scarlett"),
new PlatformData(PlatformType.Android, icons.Android, "Android"),
new PlatformData(PlatformType.Switch, icons.ColorWheel, "Switch"),
};
const float IconSize = 48.0f;

View File

@@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using FlaxEditor.Utilities;
using FlaxEngine;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor.History
{
@@ -114,6 +116,8 @@ namespace FlaxEditor.History
public object TargetInstance;
}
internal static JsonSerializerSettings JsonSettings;
// For objects that cannot be referenced in undo action like: FlaxEngine.Object or SceneGraphNode we store them in DataStorage,
// otherwise here:
private readonly object TargetInstance;
@@ -177,6 +181,24 @@ namespace FlaxEditor.History
};
}
/// <inheritdoc />
public override DataStorage Data
{
protected set
{
// Inject objects typename serialization to prevent data type mismatch when loading from saved state
var settings = JsonSettings;
if (settings == null)
{
settings = JsonSerializer.CreateDefaultSettings(false);
settings.TypeNameHandling = TypeNameHandling.All;
JsonSettings = settings;
}
_data = JsonConvert.SerializeObject(value, Formatting.Indented, settings);
//Editor.Log(_data);
}
}
/// <inheritdoc />
public override string ActionString { get; }

View File

@@ -521,13 +521,14 @@ public:
return AssetsImportingManager::Create(AssetsImportingManager::CreateVisualScriptTag, outputPath, &baseTypename);
}
static bool CanImport(MonoString* extensionObj)
static MonoString* CanImport(MonoString* extensionObj)
{
String extension;
MUtils::ToString(extensionObj, extension);
if (extension.Length() > 0 && extension[0] == '.')
extension.Remove(0, 1);
return AssetsImportingManager::GetImporter(extension) != nullptr;
const AssetImporter* importer = AssetsImportingManager::GetImporter(extension);
return importer ? MUtils::ToString(importer->ResultExtension) : nullptr;
}
static bool Import(MonoString* inputPathObj, MonoString* outputPathObj, void* arg)
@@ -715,7 +716,7 @@ public:
return str;
}
static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, Model* modelObj, int32 modelLodIndex, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
static bool CookMeshCollision(MonoString* pathObj, CollisionDataType type, ModelBase* modelObj, int32 modelLodIndex, uint32 materialSlotsMask, ConvexMeshGenerationFlags convexFlags, int32 convexVertexLimit)
{
#if COMPILE_WITH_PHYSICS_COOKING
CollisionCooking::Argument arg;
@@ -725,9 +726,9 @@ public:
arg.Type = type;
arg.Model = modelObj;
arg.ModelLodIndex = modelLodIndex;
arg.MaterialSlotsMask = materialSlotsMask;
arg.ConvexFlags = convexFlags;
arg.ConvexVertexLimit = convexVertexLimit;
return CreateCollisionData::CookMeshCollision(path, arg);
#else
LOG(Warning, "Collision cooking is disabled.");
@@ -743,8 +744,8 @@ public:
const auto& debugLines = collisionData->GetDebugLines();
const int32 linesCount = debugLines.Count() / 2;
*triangles = mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count());
*indices = mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3);
mono_gc_wbarrier_generic_store(triangles, (MonoObject*)mono_array_new(mono_domain_get(), StdTypesContainer::Instance()->Vector3Class->GetNative(), debugLines.Count()));
mono_gc_wbarrier_generic_store(indices, (MonoObject*)mono_array_new(mono_domain_get(), mono_get_int32_class(), linesCount * 3));
// Use one triangle per debug line
for (int32 i = 0; i < debugLines.Count(); i++)

View File

@@ -923,6 +923,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new SkeletonMaskProxy());
Proxy.Add(new GameplayGlobalsProxy());
Proxy.Add(new VisualScriptProxy());
Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());
@@ -933,6 +934,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new SettingsProxy(typeof(PhysicsSettings)));
Proxy.Add(new SettingsProxy(typeof(GraphicsSettings)));
Proxy.Add(new SettingsProxy(typeof(NavigationSettings)));
Proxy.Add(new SettingsProxy(typeof(LocalizationSettings)));
Proxy.Add(new SettingsProxy(typeof(BuildSettings)));
Proxy.Add(new SettingsProxy(typeof(InputSettings)));
Proxy.Add(new SettingsProxy(typeof(WindowsPlatformSettings)));
@@ -945,6 +947,9 @@ namespace FlaxEditor.Modules
if (typeXboxScarlettPlatformSettings != null)
Proxy.Add(new SettingsProxy(typeXboxScarlettPlatformSettings));
Proxy.Add(new SettingsProxy(typeof(AndroidPlatformSettings)));
var typeSwitchPlatformSettings = TypeUtils.GetManagedType(GameSettings.SwitchPlatformSettingsTypename);
if (typeSwitchPlatformSettings != null)
Proxy.Add(new SettingsProxy(typeSwitchPlatformSettings));
Proxy.Add(new SettingsProxy(typeof(AudioSettings)));
// Last add generic json (won't override other json proxies)

View File

@@ -309,6 +309,7 @@ namespace FlaxEditor.Modules
{ "FlaxEditor.Content.Settings.InputSettings", "Settings" },
{ "FlaxEditor.Content.Settings.LayersAndTagsSettings", "Settings" },
{ "FlaxEditor.Content.Settings.NavigationSettings", "Settings" },
{ "FlaxEditor.Content.Settings.LocalizationSettings", "Settings" },
{ "FlaxEditor.Content.Settings.PhysicsSettings", "Settings" },
{ "FlaxEditor.Content.Settings.TimeSettings", "Settings" },
{ "FlaxEditor.Content.Settings.UWPPlatformSettings", "Settings" },

View File

@@ -193,12 +193,10 @@ namespace FlaxEditor.Modules
var extension = System.IO.Path.GetExtension(inputPath) ?? string.Empty;
// Check if given file extension is a binary asset (.flax files) and can be imported by the engine
bool isBinaryAsset = Editor.CanImport(extension);
string outputExtension;
if (isBinaryAsset)
bool isBuilt = Editor.CanImport(extension, out var outputExtension);
if (isBuilt)
{
// Flax it up!
outputExtension = ".flax";
outputExtension = '.' + outputExtension;
if (!targetLocation.CanHaveAssets)
{
@@ -234,7 +232,7 @@ namespace FlaxEditor.Modules
var shortName = System.IO.Path.GetFileNameWithoutExtension(inputPath);
var outputPath = System.IO.Path.Combine(targetLocation.Path, shortName + outputExtension);
Import(inputPath, outputPath, isBinaryAsset, skipSettingsDialog, settings);
Import(inputPath, outputPath, isBuilt, skipSettingsDialog, settings);
}
/// <summary>
@@ -243,10 +241,10 @@ namespace FlaxEditor.Modules
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="isBinaryAsset">True if output file is a binary asset.</param>
/// <param name="isInBuilt">True if use in-built importer (engine backend).</param>
/// <param name="skipSettingsDialog">True if skip any popup dialogs showing for import options adjusting. Can be used when importing files from code.</param>
/// <param name="settings">Import settings to override. Use null to skip this value.</param>
private void Import(string inputPath, string outputPath, bool isBinaryAsset, bool skipSettingsDialog = false, object settings = null)
private void Import(string inputPath, string outputPath, bool isInBuilt, bool skipSettingsDialog = false, object settings = null)
{
lock (_requests)
{
@@ -254,7 +252,7 @@ namespace FlaxEditor.Modules
{
InputPath = inputPath,
OutputPath = outputPath,
IsBinaryAsset = isBinaryAsset,
IsInBuilt = isInBuilt,
SkipSettingsDialog = skipSettingsDialog,
Settings = settings,
});

View File

@@ -194,7 +194,7 @@ namespace FlaxEditor.SceneGraph
{
get
{
var scene = _actor.Scene;
var scene = _actor ? _actor.Scene : null;
return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null;
}
}
@@ -235,7 +235,6 @@ namespace FlaxEditor.SceneGraph
{
if (!(value is ActorNode))
throw new InvalidOperationException("ActorNode can have only ActorNode as a parent node.");
base.ParentNode = value;
}
}
@@ -289,6 +288,13 @@ namespace FlaxEditor.SceneGraph
{
}
/// <summary>
/// Action called after pasting actor in editor.
/// </summary>
public virtual void PostPaste()
{
}
/// <inheritdoc />
protected override void OnParentChanged()
{
@@ -299,6 +305,7 @@ namespace FlaxEditor.SceneGraph
// (eg. we build new node for spawned actor and link it to the game)
if (_treeNode.Parent != null && !_treeNode.Parent.IsLayoutLocked)
{
_treeNode.IndexInParent = _actor.OrderInParent;
_treeNode.Parent.SortChildren();
// Update UI

View File

@@ -25,5 +25,20 @@ namespace FlaxEditor.SceneGraph.Actors
if (Actor is UIControl uiControl)
DebugDraw.DrawWireBox(uiControl.Bounds, Color.BlueViolet);
}
/// <inheritdoc />
public override void PostPaste()
{
base.PostPaste();
var control = ((UIControl)Actor).Control;
if (control != null)
{
if (control.Parent != null)
control.Parent.PerformLayout();
else
control.PerformLayout();
}
}
}
}

View File

@@ -86,6 +86,10 @@ namespace FlaxEditor.SceneGraph.GUI
}
parent.SortChildren();
}
else if (Actor)
{
_orderInParent = Actor.OrderInParent;
}
}
internal void OnNameChanged()

View File

@@ -112,18 +112,9 @@ namespace FlaxEditor.SceneGraph
{
if (parentNode != value)
{
if (parentNode != null)
{
parentNode.ChildNodes.Remove(this);
}
parentNode?.ChildNodes.Remove(this);
parentNode = value;
if (parentNode != null)
{
parentNode.ChildNodes.Add(this);
}
parentNode?.ChildNodes.Add(this);
OnParentChanged();
}
}
@@ -374,6 +365,7 @@ namespace FlaxEditor.SceneGraph
/// <summary>
/// Gets or sets the node state.
/// </summary>
[NoSerialize]
public virtual StateData State
{
get => throw new NotImplementedException();

View File

@@ -137,7 +137,7 @@ namespace FlaxEditor.Surface.Archetypes
{
int* dataValues = (int*)dataPtr;
for (int i = 0; i < entries.Count; i++)
dataValues[i] = entries[i].Value;
dataValues[i] = (int)entries[i].Value;
}
}
else

View File

@@ -177,7 +177,7 @@ namespace FlaxEditor.Surface.Archetypes
{
int* dataValues = (int*)dataPtr;
for (int i = 0; i < entries.Count; i++)
dataValues[i] = entries[i].Value;
dataValues[i] = (int)entries[i].Value;
}
}
else

View File

@@ -672,6 +672,24 @@ namespace FlaxEditor.Surface.Archetypes
}
}
private class ThisNode : SurfaceNode
{
/// <inheritdoc />
public ThisNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{}
/// <inheritdoc />
public override void OnLoaded()
{
base.OnLoaded();
var vss = (VisualScriptSurface)this.Context.Surface;
var type = TypeUtils.GetType(vss.Script.ScriptTypeName);
var box = (OutputBox)GetBox(0);
box.CurrentType = type ? type : new ScriptType(typeof(VisualScript));
}
}
private class AssetReferenceNode : SurfaceNode
{
/// <inheritdoc />
@@ -1326,9 +1344,9 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Platform Switch",
Description = "Gets the input value based on the runtime-platform type",
Flags = NodeFlags.AllGraphs,
Size = new Vector2(220, 160),
Size = new Vector2(220, 180),
ConnectionsHints = ConnectionsHint.Value,
IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8 },
IndependentBoxes = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 },
DependentBoxes = new[] { 0 },
Elements = new[]
{
@@ -1341,6 +1359,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(5, "PlayStation 4", true, null, 6),
NodeElementArchetype.Factory.Input(6, "Xbox Scarlett", true, null, 7),
NodeElementArchetype.Factory.Input(7, "Android", true, null, 8),
NodeElementArchetype.Factory.Input(8, "Switch", true, null, 9),
}
},
new NodeArchetype
@@ -1365,6 +1384,7 @@ namespace FlaxEditor.Surface.Archetypes
{
TypeID = 19,
Title = "This Instance",
Create = (id, context, arch, groupArch) => new ThisNode(id, context, arch, groupArch),
Description = "Gets the reference to this script object instance (self).",
Flags = NodeFlags.VisualScriptGraph,
Size = new Vector2(140, 20),

View File

@@ -32,13 +32,13 @@ namespace FlaxEditor.Surface.Elements
Width = archetype.Size.X;
ParentNode = parentNode;
Archetype = archetype;
Value = (int)ParentNode.Values[Archetype.ValueIndex];
Value = (ulong)(int)ParentNode.Values[Archetype.ValueIndex];
}
/// <inheritdoc />
protected override void OnValueChanged()
{
if ((int)ParentNode.Values[Archetype.ValueIndex] != Value)
if ((int)ParentNode.Values[Archetype.ValueIndex] != (int)Value)
{
// Edit value
ParentNode.SetValue(Archetype.ValueIndex, Value);

View File

@@ -140,22 +140,27 @@ namespace FlaxEditor.Actions
for (int i = 0; i < nodeParents.Count; i++)
{
// Fix name collisions (only for parents)
var node = nodeParents[i];
var parent = node.Actor?.Parent;
if (parent != null)
{
// Fix name collisions
string name = node.Name;
Actor[] children = parent.Children;
if (children.Any(x => x.Name == name))
{
// Generate new name
node.Actor.Name = StringUtils.IncrementNameNumber(name, x => children.All(y => y.Name != x));
}
}
Editor.Instance.Scene.MarkSceneEdited(node.ParentScene);
}
for (int i = 0; i < nodeParents.Count; i++)
{
var node = nodeParents[i];
node.PostPaste();
}
}
/// <summary>

View File

@@ -4,7 +4,6 @@ using System;
using FlaxEditor.SceneGraph;
using FlaxEngine;
using Newtonsoft.Json;
using JsonSerializer = FlaxEngine.Json.JsonSerializer;
namespace FlaxEditor
{
@@ -28,7 +27,6 @@ namespace FlaxEditor
var id = Guid.Parse((string)reader.Value);
return SceneGraphFactory.FindNode(id);
}
return null;
}
@@ -56,19 +54,11 @@ namespace FlaxEditor
/// <summary>
/// Gets or sets the serialized undo data.
/// </summary>
/// <value>
/// The data.
/// </value>
[NoSerialize]
public TData Data
public virtual TData Data
{
get => JsonConvert.DeserializeObject<TData>(_data, JsonSerializer.Settings);
protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, JsonSerializer.Settings);
/*protected set
{
_data = JsonConvert.SerializeObject(value, Formatting.Indented, JsonSerializer.Settings);
Debug.Info(_data);
}*/
get => JsonConvert.DeserializeObject<TData>(_data, FlaxEngine.Json.JsonSerializer.Settings);
protected set => _data = JsonConvert.SerializeObject(value, Formatting.None, FlaxEngine.Json.JsonSerializer.Settings);
}
/// <inheritdoc />

View File

@@ -6,6 +6,8 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Tree;
using FlaxEditor.SceneGraph;
using FlaxEditor.Scripting;
using FlaxEngine;
@@ -67,6 +69,10 @@ namespace FlaxEditor.Utilities
Dictionary,
ManagedObject,
Typename,
Int2,
Int3,
Int4
}
/// <summary>
@@ -446,6 +452,12 @@ namespace FlaxEditor.Utilities
variantType = VariantType.Vector3;
else if (type == typeof(Vector4))
variantType = VariantType.Vector4;
else if (type == typeof(Int2))
variantType = VariantType.Int2;
else if (type == typeof(Int3))
variantType = VariantType.Int3;
else if (type == typeof(Int4))
variantType = VariantType.Int4;
else if (type == typeof(Color))
variantType = VariantType.Color;
else if (type == typeof(Guid))
@@ -682,6 +694,21 @@ namespace FlaxEditor.Utilities
new Vector3(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()));
break;
}
case 19: // CommonType::Int2
{
value = stream.ReadInt2();
break;
}
case 20: // CommonType::Int3
{
value = stream.ReadInt3();
break;
}
case 21: // CommonType::Int4
{
value = stream.ReadInt4();
break;
}
default: throw new SystemException();
}
}
@@ -733,6 +760,9 @@ namespace FlaxEditor.Utilities
case VariantType.Vector2: return new ScriptType(typeof(Vector2));
case VariantType.Vector3: return new ScriptType(typeof(Vector3));
case VariantType.Vector4: return new ScriptType(typeof(Vector4));
case VariantType.Int2: return new ScriptType(typeof(Int2));
case VariantType.Int3: return new ScriptType(typeof(Int3));
case VariantType.Int4: return new ScriptType(typeof(Int4));
case VariantType.Color: return new ScriptType(typeof(Color));
case VariantType.Guid: return new ScriptType(typeof(Guid));
case VariantType.BoundingBox: return new ScriptType(typeof(BoundingBox));
@@ -797,6 +827,9 @@ namespace FlaxEditor.Utilities
case VariantType.Vector2: return typeof(Vector2);
case VariantType.Vector3: return typeof(Vector3);
case VariantType.Vector4: return typeof(Vector4);
case VariantType.Int2: return typeof(Int2);
case VariantType.Int3: return typeof(Int3);
case VariantType.Int4: return typeof(Int4);
case VariantType.Color: return typeof(Color);
case VariantType.Guid: return typeof(Guid);
case VariantType.BoundingBox: return typeof(BoundingBox);
@@ -894,6 +927,9 @@ namespace FlaxEditor.Utilities
case VariantType.Vector2: return stream.ReadVector2();
case VariantType.Vector3: return stream.ReadVector3();
case VariantType.Vector4: return stream.ReadVector4();
case VariantType.Int2: return stream.ReadInt2();
case VariantType.Int3: return stream.ReadInt3();
case VariantType.Int4: return stream.ReadInt4();
case VariantType.Color: return stream.ReadColor();
case VariantType.Guid: return stream.ReadGuid();
case VariantType.BoundingBox: return stream.ReadBoundingBox();
@@ -1087,6 +1123,21 @@ namespace FlaxEditor.Utilities
stream.Write(asRay.Direction.Y);
stream.Write(asRay.Direction.Z);
}
else if (value is Int2 asInt2)
{
stream.Write((byte)19);
stream.Write(asInt2);
}
else if (value is Int3 asInt3)
{
stream.Write((byte)20);
stream.Write(asInt3);
}
else if (value is Int4 asInt4)
{
stream.Write((byte)21);
stream.Write(asInt4);
}
else
{
throw new NotSupportedException(string.Format("Invalid Common Value type {0}", value != null ? value.GetType().ToString() : "null"));
@@ -1194,6 +1245,15 @@ namespace FlaxEditor.Utilities
case VariantType.Vector4:
stream.Write((Vector4)value);
break;
case VariantType.Int2:
stream.Write((Int2)value);
break;
case VariantType.Int3:
stream.Write((Int3)value);
break;
case VariantType.Int4:
stream.Write((Int4)value);
break;
case VariantType.Color:
stream.Write((Color)value);
break;
@@ -1386,6 +1446,51 @@ namespace FlaxEditor.Utilities
stream.WriteEndObject();
break;
}
case VariantType.Int2:
{
var asInt2 = (Int2)value;
stream.WriteStartObject();
stream.WritePropertyName("X");
stream.WriteValue(asInt2.X);
stream.WritePropertyName("Y");
stream.WriteValue(asInt2.Y);
stream.WriteEndObject();
break;
}
case VariantType.Int3:
{
var asInt3 = (Int3)value;
stream.WriteStartObject();
stream.WritePropertyName("X");
stream.WriteValue(asInt3.X);
stream.WritePropertyName("Y");
stream.WriteValue(asInt3.Y);
stream.WritePropertyName("Z");
stream.WriteValue(asInt3.Z);
stream.WriteEndObject();
break;
}
case VariantType.Int4:
{
var asInt4 = (Int4)value;
stream.WriteStartObject();
stream.WritePropertyName("X");
stream.WriteValue(asInt4.X);
stream.WritePropertyName("Y");
stream.WriteValue(asInt4.Y);
stream.WritePropertyName("Z");
stream.WriteValue(asInt4.Z);
stream.WritePropertyName("W");
stream.WriteValue(asInt4.W);
stream.WriteEndObject();
break;
}
case VariantType.Color:
{
var asColor = (Color)value;
@@ -1777,5 +1882,41 @@ namespace FlaxEditor.Utilities
}
}
}
/// <summary>
/// Creates the search popup with a tree.
/// </summary>
/// <param name="searchBox">The search box.</param>
/// <param name="tree">The tree control.</param>
/// <returns>The created menu to setup and show.</returns>
public static ContextMenuBase CreateSearchPopup(out TextBox searchBox, out Tree tree)
{
var menu = new ContextMenuBase
{
Size = new Vector2(320, 220),
};
searchBox = new TextBox(false, 1, 1)
{
Width = menu.Width - 3,
WatermarkText = "Search...",
Parent = menu,
};
var panel1 = new Panel(ScrollBars.Vertical)
{
Bounds = new Rectangle(0, searchBox.Bottom + 1, menu.Width, menu.Height - searchBox.Bottom - 2),
Parent = menu
};
var panel2 = new VerticalPanel
{
Parent = panel1,
AnchorPreset = AnchorPresets.HorizontalStretchTop,
IsScrollable = true,
};
tree = new Tree(false)
{
Parent = panel2,
};
return menu;
}
}
}

View File

@@ -974,7 +974,8 @@ namespace FlaxEditor.Viewport
// Get input buttons and keys (skip if viewport has no focus or mouse is over a child control)
bool useMouse = Mathf.IsInRange(_viewMousePos.X, 0, Width) && Mathf.IsInRange(_viewMousePos.Y, 0, Height);
_prevInput = _input;
if (ContainsFocus && GetChildAt(_viewMousePos, c => c.Visible) == null)
var hit = GetChildAt(_viewMousePos, c => c.Visible && !(c is CanvasRootControl));
if (ContainsFocus && hit == null)
_input.Gather(win.Window, useMouse);
else
_input.Clear();

View File

@@ -0,0 +1,122 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
{
/// <summary>
/// Model Base asset preview editor viewport.
/// </summary>
/// <seealso cref="AssetPreview" />
public class ModelBasePreview : AssetPreview
{
/// <summary>
/// Gets or sets the model asset to preview.
/// </summary>
public ModelBase Asset
{
get => (ModelBase)StaticModel.Model ?? AnimatedModel.SkinnedModel;
set
{
StaticModel.Model = value as Model;
AnimatedModel.SkinnedModel = value as SkinnedModel;
}
}
/// <summary>
/// The static model for display.
/// </summary>
public StaticModel StaticModel;
/// <summary>
/// The animated model for display.
/// </summary>
public AnimatedModel AnimatedModel;
/// <summary>
/// Gets or sets a value indicating whether scale the model to the normalized bounds.
/// </summary>
public bool ScaleToFit { get; set; } = true;
/// <summary>
/// Initializes a new instance of the <see cref="ModelBasePreview"/> class.
/// </summary>
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
public ModelBasePreview(bool useWidgets)
: base(useWidgets)
{
Task.Begin += OnBegin;
// Setup preview scene
StaticModel = new StaticModel();
AnimatedModel = new AnimatedModel();
// Link actors for rendering
Task.AddCustomActor(StaticModel);
Task.AddCustomActor(AnimatedModel);
if (useWidgets)
{
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () =>
{
StaticModel.ForcedLOD = previewLODValue.Value;
AnimatedModel.ForcedLOD = previewLODValue.Value;
};
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD;
}
}
}
private void OnBegin(RenderTask task, GPUContext context)
{
var position = Vector3.Zero;
var scale = Vector3.One;
// Update preview model scale to fit the preview
var model = Asset;
if (ScaleToFit && model && model.IsLoaded)
{
float targetSize = 50.0f;
BoundingBox box = model is Model ? ((Model)model).GetBox() : ((SkinnedModel)model).GetBox();
float maxSize = Mathf.Max(0.001f, box.Size.MaxValue);
scale = new Vector3(targetSize / maxSize);
position = box.Center * (-0.5f * scale.X) + new Vector3(0, -10, 0);
}
StaticModel.Transform = new Transform(position, StaticModel.Orientation, scale);
AnimatedModel.Transform = new Transform(position, AnimatedModel.Orientation, scale);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
break;
}
return base.OnKeyDown(key);
}
/// <inheritdoc />
public override void OnDestroy()
{
// Ensure to cleanup created actor objects
Object.Destroy(ref StaticModel);
Object.Destroy(ref AnimatedModel);
base.OnDestroy();
}
}
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Xml;
using FlaxEditor.Content;
using FlaxEditor.Content.Create;
@@ -10,6 +11,7 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Windows.Assets
{
@@ -20,6 +22,15 @@ namespace FlaxEditor.Windows.Assets
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class CollisionDataWindow : AssetEditorWindowBase<CollisionData>
{
[Flags]
private enum MaterialSlotsMask : uint
{
// @formatter:off
Slot0=1u<<0,Slot1=1u<<1,Slot2=1u<<2,Slot3=1u<<3,Slot4=1u<<4,Slot5=1u<<5,Slot6=1u<<6,Slot7=1u<<7,Slot8=1u<<8,Slot9=1u<<9,Slot10=1u<<10,Slot11=1u<<11,Slot12=1u<<12,Slot13=1u<<13,Slot14=1u<<14,Slot15=1u<<15,Slot16=1u<<16,Slot17=1u<<17,Slot18=1u<<18,Slot19=1u<<19,Slot20=1u<<20,Slot21=1u<<21,Slot22=1u<<22,Slot23=1u<<23,Slot24=1u<<24,Slot25=1u<<25,Slot26=1u<<26,Slot27=1u<<27,Slot28=1u<<28,Slot29=1u<<29,Slot30=1u<<30,Slot31=1u<<31,
// @formatter:on
All = uint.MaxValue,
}
/// <summary>
/// The asset properties proxy object.
/// </summary>
@@ -34,11 +45,14 @@ namespace FlaxEditor.Windows.Assets
public CollisionDataType Type;
[EditorOrder(10), EditorDisplay("General"), Tooltip("Source model asset to use for collision data generation")]
public Model Model;
public ModelBase Model;
[EditorOrder(20), Limit(0, 5), EditorDisplay("General", "Model LOD Index"), Tooltip("Source model LOD index to use for collision data generation (will be clamped to the actual model LODs collection size)")]
public int ModelLodIndex;
[EditorOrder(30), EditorDisplay("General"), Tooltip("The source model material slots mask. One bit per-slot. Can be sued to exclude particular material slots from collision cooking.")]
public MaterialSlotsMask MaterialSlotsMask = MaterialSlotsMask.All;
[EditorOrder(100), EditorDisplay("Convex Mesh", "Convex Flags"), Tooltip("Convex mesh generation flags")]
public ConvexMeshGenerationFlags ConvexFlags;
@@ -88,28 +102,23 @@ namespace FlaxEditor.Windows.Assets
private class CookData : CreateFileEntry
{
private PropertiesProxy Proxy;
private CollisionDataType Type;
private Model Model;
private int ModelLodIndex;
private ConvexMeshGenerationFlags ConvexFlags;
private int ConvexVertexLimit;
public PropertiesProxy Proxy;
public CollisionDataType Type;
public ModelBase Model;
public int ModelLodIndex;
public uint MaterialSlotsMask;
public ConvexMeshGenerationFlags ConvexFlags;
public int ConvexVertexLimit;
public CookData(PropertiesProxy proxy, string resultUrl, CollisionDataType type, Model model, int modelLodIndex, ConvexMeshGenerationFlags convexFlags, int convexVertexLimit)
public CookData(string resultUrl)
: base("Collision Data", resultUrl)
{
Proxy = proxy;
Type = type;
Model = model;
ModelLodIndex = modelLodIndex;
ConvexFlags = convexFlags;
ConvexVertexLimit = convexVertexLimit;
}
/// <inheritdoc />
public override bool Create()
{
bool failed = FlaxEditor.Editor.CookMeshCollision(ResultUrl, Type, Model, ModelLodIndex, ConvexFlags, ConvexVertexLimit);
bool failed = FlaxEditor.Editor.CookMeshCollision(ResultUrl, Type, Model, ModelLodIndex, MaterialSlotsMask, ConvexFlags, ConvexVertexLimit);
Proxy._isCooking = false;
Proxy.Window.UpdateWiresModel();
@@ -121,7 +130,16 @@ namespace FlaxEditor.Windows.Assets
public void Cook()
{
_isCooking = true;
Window.Editor.ContentImporting.Create(new CookData(this, Asset.Path, Type, Model, ModelLodIndex, ConvexFlags, ConvexVertexLimit));
Window.Editor.ContentImporting.Create(new CookData(Asset.Path)
{
Proxy = this,
Type = Type,
Model = Model,
ModelLodIndex = ModelLodIndex,
MaterialSlotsMask = (uint)MaterialSlotsMask,
ConvexFlags = ConvexFlags,
ConvexVertexLimit = ConvexVertexLimit,
});
}
public void OnLoad(CollisionDataWindow window)
@@ -135,7 +153,7 @@ namespace FlaxEditor.Windows.Assets
Type = options.Type;
if (Type == CollisionDataType.None)
Type = CollisionDataType.ConvexMesh;
Model = FlaxEngine.Content.LoadAsync<Model>(options.Model);
Model = FlaxEngine.Content.LoadAsync<ModelBase>(options.Model);
ModelLodIndex = options.ModelLodIndex;
ConvexFlags = options.ConvexFlags;
ConvexVertexLimit = options.ConvexVertexLimit;
@@ -151,7 +169,7 @@ namespace FlaxEditor.Windows.Assets
}
private readonly SplitPanel _split;
private readonly ModelPreview _preview;
private readonly ModelBasePreview _preview;
private readonly CustomEditorPresenter _propertiesPresenter;
private readonly PropertiesProxy _properties;
private Model _collisionWiresModel;
@@ -176,7 +194,7 @@ namespace FlaxEditor.Windows.Assets
};
// Model preview
_preview = new ModelPreview(true)
_preview = new ModelBasePreview(true)
{
ViewportCamera = new FPSCamera(),
Parent = _split.Panel1
@@ -195,7 +213,7 @@ namespace FlaxEditor.Windows.Assets
// Sync helper actor size with actual preview model (preview scales model for better usage experience)
if (_collisionWiresShowActor && _collisionWiresShowActor.IsActive)
{
_collisionWiresShowActor.Transform = _preview.PreviewActor.Transform;
_collisionWiresShowActor.Transform = _preview.StaticModel.Transform;
}
base.Update(deltaTime);
@@ -230,14 +248,14 @@ namespace FlaxEditor.Windows.Assets
}
_collisionWiresShowActor.Model = _collisionWiresModel;
_collisionWiresShowActor.SetMaterial(0, FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial));
_preview.Model = FlaxEngine.Content.LoadAsync<Model>(_asset.Options.Model);
_preview.Asset = FlaxEngine.Content.LoadAsync<ModelBase>(_asset.Options.Model);
}
/// <inheritdoc />
protected override void UnlinkItem()
{
_properties.OnClean();
_preview.Model = null;
_preview.Asset = null;
base.UnlinkItem();
}
@@ -245,7 +263,7 @@ namespace FlaxEditor.Windows.Assets
/// <inheritdoc />
protected override void OnAssetLinked()
{
_preview.Model = null;
_preview.Asset = null;
base.OnAssetLinked();
}

View File

@@ -17,6 +17,9 @@ namespace FlaxEditor.Windows.Assets
{
private readonly CustomEditorPresenter _presenter;
private readonly ToolStripButton _saveButton;
private readonly ToolStripButton _undoButton;
private readonly ToolStripButton _redoButton;
private readonly Undo _undo;
private object _object;
private bool _isRegisteredForScriptsReload;
@@ -24,8 +27,16 @@ namespace FlaxEditor.Windows.Assets
public JsonAssetWindow(Editor editor, AssetItem item)
: base(editor, item)
{
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
_undo.RedoDone += OnUndoRedo;
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
// Panel
var panel = new Panel(ScrollBars.Vertical)
@@ -36,9 +47,19 @@ namespace FlaxEditor.Windows.Assets
};
// Properties
_presenter = new CustomEditorPresenter(null, "Loading...");
_presenter = new CustomEditorPresenter(_undo, "Loading...");
_presenter.Panel.Parent = panel;
_presenter.Modified += MarkAsEdited;
// Setup input actions
InputActions.Add(options => options.Undo, _undo.PerformUndo);
InputActions.Add(options => options.Redo, _undo.PerformRedo);
}
private void OnUndoRedo(IUndoAction action)
{
MarkAsEdited();
UpdateToolstrip();
}
private void OnScriptsReloadBegin()
@@ -64,13 +85,14 @@ namespace FlaxEditor.Windows.Assets
}
ClearEditedFlag();
_item.RefreshThumbnail();
}
/// <inheritdoc />
protected override void UpdateToolstrip()
{
_saveButton.Enabled = IsEdited;
_undoButton.Enabled = _undo.CanUndo;
_redoButton.Enabled = _undo.CanRedo;
base.UpdateToolstrip();
}
@@ -80,6 +102,7 @@ namespace FlaxEditor.Windows.Assets
{
_object = Asset.CreateInstance();
_presenter.Select(_object);
_undo.Clear();
ClearEditedFlag();
// Auto-close on scripting reload if json asset is from game scripts (it might be reloaded)

View File

@@ -0,0 +1,212 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using FlaxEditor.Content;
using FlaxEditor.CustomEditors;
using FlaxEditor.GUI;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.Windows.Assets
{
/// <summary>
/// Editor window to view/modify <see cref="LocalizedStringTable"/> asset.
/// </summary>
/// <seealso cref="LocalizedStringTable" />
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class LocalizedStringTableWindow : AssetEditorWindowBase<LocalizedStringTable>
{
private readonly CustomEditorPresenter _presenter;
private readonly ToolStripButton _saveButton;
private readonly ToolStripButton _undoButton;
private readonly ToolStripButton _redoButton;
private readonly Undo _undo;
private Proxy _proxy;
private class EntryEditor : CustomEditor
{
private TextBox[] _textBoxes;
private bool _isRefreshing;
public override void Initialize(LayoutElementsContainer layout)
{
var values = (string[])Values[0];
if (values == null || values.Length == 0)
{
values = new string[1];
values[0] = string.Empty;
}
if (_textBoxes == null || _textBoxes.Length != values.Length)
_textBoxes = new TextBox[values.Length];
for (int i = 0; i < values.Length; i++)
{
var value = values[i];
var textBox = layout.TextBox(value.IsMultiline());
textBox.TextBox.Tag = i;
textBox.TextBox.Text = value;
textBox.TextBox.TextBoxEditEnd += OnEditEnd;
_textBoxes[i] = textBox.TextBox;
}
}
public override void Refresh()
{
base.Refresh();
var values = (string[])Values[0];
if (values != null && values.Length == _textBoxes.Length)
{
_isRefreshing = true;
var style = FlaxEngine.GUI.Style.Current;
var wrongColor = new Color(1.0f, 0.0f, 0.02745f, 1.0f);
var wrongColorBorder = Color.Lerp(wrongColor, style.TextBoxBackground, 0.6f);
for (int i = 0; i < _textBoxes.Length; i++)
{
var textBox = _textBoxes[i];
if (!textBox.IsEditing)
{
textBox.Text = values[i];
if (string.IsNullOrEmpty(textBox.Text))
{
textBox.BorderColor = wrongColorBorder;
textBox.BorderSelectedColor = wrongColor;
}
else
{
textBox.BorderColor = Color.Transparent;
textBox.BorderSelectedColor = style.BackgroundSelected;
}
}
}
_isRefreshing = false;
}
}
protected override void Deinitialize()
{
base.Deinitialize();
_textBoxes = null;
_isRefreshing = false;
}
private void OnEditEnd(TextBoxBase textBox)
{
if (_isRefreshing)
return;
var values = (string[])Values[0];
var length = Mathf.Max(values?.Length ?? 0, 1);
var toSet = new string[length];
if (values != null && values.Length == length)
Array.Copy(values, toSet, length);
var index = (int)textBox.Tag;
toSet[index] = textBox.Text;
SetValue(toSet);
}
}
private class Proxy
{
[EditorOrder(0), EditorDisplay("General"), Tooltip("The locale of the localized string table (eg. pl-PL)."),]
[CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.CultureInfoEditor))]
public string Locale;
[EditorOrder(10), EditorDisplay("Entries", EditorDisplayAttribute.InlineStyle), Tooltip("The string table. Maps the message id into the localized text. For plural messages the list contains separate items for value numbers.")]
[Collection(Spacing = 10, OverrideEditorTypeName = "FlaxEditor.Windows.Assets.LocalizedStringTableWindow+EntryEditor")]
public Dictionary<string, string[]> Entries;
}
/// <inheritdoc />
public LocalizedStringTableWindow(Editor editor, AssetItem item)
: base(editor, item)
{
// Undo
_undo = new Undo();
_undo.UndoDone += OnUndoRedo;
_undo.RedoDone += OnUndoRedo;
// Toolstrip
_saveButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Save32, Save).LinkTooltip("Save");
_toolstrip.AddSeparator();
_undoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Undo32, _undo.PerformUndo).LinkTooltip("Undo (Ctrl+Z)");
_redoButton = (ToolStripButton)_toolstrip.AddButton(Editor.Icons.Redo32, _undo.PerformRedo).LinkTooltip("Redo (Ctrl+Y)");
// Panel
var panel = new Panel(ScrollBars.Vertical)
{
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0, 0, _toolstrip.Bottom, 0),
Parent = this
};
// Properties
_presenter = new CustomEditorPresenter(_undo, "Loading...");
_presenter.Panel.Parent = panel;
_presenter.Modified += MarkAsEdited;
// Setup input actions
InputActions.Add(options => options.Undo, _undo.PerformUndo);
InputActions.Add(options => options.Redo, _undo.PerformRedo);
}
private void OnUndoRedo(IUndoAction action)
{
MarkAsEdited();
UpdateToolstrip();
}
/// <inheritdoc />
public override void Save()
{
if (!IsEdited)
return;
_asset.Locale = _proxy.Locale;
_asset.Entries = _proxy.Entries;
if (_asset.Save(_item.Path))
{
Editor.LogError("Cannot save asset.");
return;
}
ClearEditedFlag();
}
/// <inheritdoc />
protected override void UpdateToolstrip()
{
_saveButton.Enabled = IsEdited;
_undoButton.Enabled = _undo.CanUndo;
_redoButton.Enabled = _undo.CanRedo;
base.UpdateToolstrip();
}
/// <inheritdoc />
protected override void OnAssetLoaded()
{
_proxy = new Proxy
{
Locale = _asset.Locale,
Entries = _asset.Entries,
};
_presenter.Select(_proxy);
_undo.Clear();
ClearEditedFlag();
base.OnAssetLoaded();
}
/// <inheritdoc />
public override void OnItemReimported(ContentItem item)
{
// Refresh the properties (will get new data in OnAssetLoaded)
_presenter.Deselect();
ClearEditedFlag();
base.OnItemReimported(item);
}
}
}

View File

@@ -565,7 +565,6 @@ namespace FlaxEditor.Windows.Assets
public UVsLayoutPreviewControl()
{
Offsets = new Margin(4);
AnchorPreset = AnchorPresets.HorizontalStretchMiddle;
AutomaticInvalidate = false;
}
@@ -619,9 +618,9 @@ namespace FlaxEditor.Windows.Assets
}
/// <inheritdoc />
protected override void DrawChildren()
public override void DrawSelf()
{
base.DrawChildren();
base.DrawSelf();
var size = Size;
if (_channel == UVChannel.None || size.MaxValue < 5.0f)

View File

@@ -114,9 +114,9 @@ namespace FlaxEditor.Windows.Assets
}
/// <inheritdoc />
public override void Draw()
public override void DrawSelf()
{
base.Draw();
base.DrawSelf();
var style = Style.Current;
var asset = _window.Asset;
@@ -676,7 +676,6 @@ namespace FlaxEditor.Windows.Assets
public UVsLayoutPreviewControl()
{
Offsets = new Margin(4);
AnchorPreset = AnchorPresets.HorizontalStretchMiddle;
AutomaticInvalidate = false;
}

View File

@@ -43,6 +43,7 @@ namespace FlaxEditor.Windows
{ PlatformType.PS4, new PS4() },
{ PlatformType.XboxScarlett, new XboxScarlett() },
{ PlatformType.Android, new Android() },
{ PlatformType.Switch, new Switch() },
};
public BuildTabProxy(GameCookerWindow win, PlatformSelector platformSelector)
@@ -57,6 +58,7 @@ namespace FlaxEditor.Windows
PerPlatformOptions[PlatformType.PS4].Init("Output/PS4", "PS4");
PerPlatformOptions[PlatformType.XboxScarlett].Init("Output/XboxScarlett", "XboxScarlett");
PerPlatformOptions[PlatformType.Android].Init("Output/Android", "Android");
PerPlatformOptions[PlatformType.Switch].Init("Output/Switch", "Switch");
}
public abstract class Platform
@@ -188,6 +190,11 @@ namespace FlaxEditor.Windows
protected override BuildPlatform BuildPlatform => BuildPlatform.AndroidARM64;
}
public class Switch : Platform
{
protected override BuildPlatform BuildPlatform => BuildPlatform.Switch;
}
public class Editor : CustomEditor
{
private PlatformType _platform;
@@ -229,6 +236,9 @@ namespace FlaxEditor.Windows
case PlatformType.Android:
name = "Android";
break;
case PlatformType.Switch:
name = "Switch";
break;
default:
name = CustomEditorsUtil.GetPropertyNameUI(_platform.ToString());
break;

View File

@@ -120,6 +120,17 @@ const Char* SplashScreenQuotes[] =
TEXT("All we had to do was follow the damn train, CJ"),
TEXT("28 stab wounds"),
TEXT("Here we go again"),
TEXT("@everyone"),
TEXT("uwu some spiders on the ceiling"),
TEXT("There you are you little shit"),
TEXT("potato"),
TEXT("python is a programming snek"),
TEXT("Flax will start when pigs will fly"),
TEXT("I'm the android sent by CyberLife"),
TEXT("fancy-ass ray tracing rtx on lighting"),
TEXT("ZOINKS"),
TEXT("Scooby dooby doo"),
TEXT("You shall not load"),
};
SplashScreen::~SplashScreen()

View File

@@ -2,7 +2,8 @@
#pragma once
#include "Curve.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Animations/Curve.h"
#include "Engine/Core/Math/Transform.h"
/// <summary>

View File

@@ -636,7 +636,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
transform.Scale = (Vector3)tryGetValue(node->GetBox(4), Vector3::One);
// Skip if no change will be performed
if (boneIndex < 0 || boneIndex >= _skeletonBonesCount || transformMode == BoneTransformMode::None || transform.IsIdentity())
if (boneIndex < 0 || boneIndex >= _skeletonBonesCount || transformMode == BoneTransformMode::None || (transformMode == BoneTransformMode::Add && transform.IsIdentity()))
{
// Pass through the input
value = Value::Null;
@@ -1628,7 +1628,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
transform.Scale = (Vector3)tryGetValue(node->GetBox(4), Vector3::One);
// Skip if no change will be performed
if (nodeIndex < 0 || nodeIndex >= _skeletonNodesCount || transformMode == BoneTransformMode::None || transform.IsIdentity())
if (nodeIndex < 0 || nodeIndex >= _skeletonNodesCount || transformMode == BoneTransformMode::None || (transformMode == BoneTransformMode::Add && transform.IsIdentity()))
{
// Pass through the input
value = Value::Null;

View File

@@ -46,6 +46,10 @@ public class Audio : EngineModule
case TargetPlatform.Android:
useOpenAL = true;
break;
case TargetPlatform.Switch:
options.SourcePaths.Add(Path.Combine(Globals.EngineRoot, "Source", "Platforms", "Switch", "Engine", "Audio"));
options.CompileEnv.PreprocessorDefinitions.Add("AUDIO_API_SWITCH");
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}

View File

@@ -2,6 +2,8 @@
#pragma once
#include "Engine/Core/Types/BaseTypes.h"
namespace CSG
{
/// <summary>

View File

@@ -225,7 +225,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds)
while (!Engine::ShouldExit())
{
// Try to execute content tasks
while (task->IsQueued())
while (task->IsQueued() && !Engine::ShouldExit())
{
// Pick this task from the queue
ContentLoadTask* tmp;

View File

@@ -274,3 +274,9 @@ public:
OnSet(asset);
}
};
template<typename T>
uint32 GetHash(const AssetReference<T>& key)
{
return GetHash(key.GetID());
}

View File

@@ -396,21 +396,21 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
auto& meshData = meshesData[meshIndex];
// Vertex Buffer 0 (required)
auto task = mesh.ExtractDataAsync(MeshBufferType::Vertex0, meshData.VB0);
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
if (task == nullptr)
return true;
task->Start();
tasks.Add(task);
// Vertex Buffer 1 (required)
task = mesh.ExtractDataAsync(MeshBufferType::Vertex1, meshData.VB1);
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB1);
if (task == nullptr)
return true;
task->Start();
tasks.Add(task);
// Vertex Buffer 2 (optional)
task = mesh.ExtractDataAsync(MeshBufferType::Vertex2, meshData.VB2);
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB2);
if (task)
{
task->Start();
@@ -418,7 +418,7 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
}
// Index Buffer (required)
task = mesh.ExtractDataAsync(MeshBufferType::Index, meshData.IB);
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
if (task == nullptr)
return true;
task->Start();
@@ -618,6 +618,19 @@ void Model::SetupMaterialSlots(int32 slotsCount)
}
}
int32 Model::GetLODsCount() const
{
return LODs.Count();
}
void Model::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
}
void Model::InitAsVirtual()
{
// Init with a single LOD and one mesh
@@ -628,6 +641,21 @@ void Model::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
#if USE_EDITOR
void Model::GetReferences(Array<Guid>& output) const
{
// Base
BinaryAsset::GetReferences(output);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
}
#endif
int32 Model::GetMaxResidency() const
{
return LODs.Count();
@@ -837,3 +865,33 @@ AssetChunksFlag Model::getChunksToPreload() const
// Note: we don't preload any LODs here because it's done by the Streaming Manager
return GET_CHUNK_FLAG(0);
}
void ModelBase::SetupMaterialSlots(int32 slotsCount)
{
CHECK(slotsCount >= 0 && slotsCount < 4096);
if (!IsVirtual() && WaitForLoaded())
return;
ScopeLock lock(Locker);
const int32 prevCount = MaterialSlots.Count();
MaterialSlots.Resize(slotsCount, false);
// Initialize slot names
for (int32 i = prevCount; i < slotsCount; i++)
MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1);
}
MaterialSlot* ModelBase::GetSlot(const StringView& name)
{
MaterialSlot* result = nullptr;
for (auto& slot : MaterialSlots)
{
if (slot.Name == name)
{
result = &slot;
break;
}
}
return result;
}

View File

@@ -55,15 +55,6 @@ public:
return LODs.HasItems();
}
/// <summary>
/// Gets amount of the level of details in the model
/// </summary>
/// <returns>Amount of the level of details in the model</returns>
FORCE_INLINE int32 GetLODsCount() const
{
return LODs.Count();
}
/// <summary>
/// Gets the amount of loaded model LODs.
/// </summary>
@@ -237,18 +228,11 @@ public:
// [ModelBase]
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override
{
// Base
BinaryAsset::GetReferences(output);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
}
void GetReferences(Array<Guid>& output) const override;
#endif
// [StreamableResource]

View File

@@ -7,6 +7,8 @@
#include "Engine/Graphics/Models/MaterialSlot.h"
#include "Engine/Streaming/StreamableResource.h"
class MeshBase;
/// <summary>
/// Base class for asset types that can contain a model resource.
/// </summary>
@@ -44,38 +46,23 @@ public:
/// <summary>
/// Resizes the material slots collection. Updates meshes that were using removed slots.
/// </summary>
API_FUNCTION() virtual void SetupMaterialSlots(int32 slotsCount)
{
CHECK(slotsCount >= 0 && slotsCount < 4096);
if (!IsVirtual() && WaitForLoaded())
return;
ScopeLock lock(Locker);
const int32 prevCount = MaterialSlots.Count();
MaterialSlots.Resize(slotsCount, false);
// Initialize slot names
for (int32 i = prevCount; i < slotsCount; i++)
MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1);
}
API_FUNCTION() virtual void SetupMaterialSlots(int32 slotsCount);
/// <summary>
/// Gets the material slot by the name.
/// </summary>
/// <param name="name">The slot name.</param>
/// <returns>The material slot with the given name or null if cannot find it (asset may be not loaded yet).</returns>
API_FUNCTION() MaterialSlot* GetSlot(const StringView& name)
{
MaterialSlot* result = nullptr;
for (auto& slot : MaterialSlots)
{
if (slot.Name == name)
{
result = &slot;
break;
}
}
return result;
}
API_FUNCTION() MaterialSlot* GetSlot(const StringView& name);
/// <summary>
/// Gets amount of the level of details in the model.
/// </summary>
/// <returns>Amount of the level of details in the model.</returns>
virtual int32 GetLODsCount() const = 0;
/// <summary>
/// Gets the meshes for a particular LOD index.
/// </summary>
virtual void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
};

View File

@@ -137,6 +137,18 @@ Array<String> SkinnedModel::GetBlendShapes()
return result;
}
ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
GetChunkData(chunkIndex, data);
}
bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
{
return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh);
@@ -389,7 +401,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
{
auto& slot = MaterialSlots[materialSlotIndex];
const auto id =slot.Material.GetID();
const auto id = slot.Material.GetID();
stream->Write(&id);
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
stream->WriteString(slot.Name, 11);
@@ -505,14 +517,14 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
auto& meshData = meshesData[meshIndex];
// Vertex Buffer 0 (required)
auto task = mesh.DownloadDataAsyncGPU(MeshBufferType::Vertex0, meshData.VB0);
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB0);
if (task == nullptr)
return true;
task->Start();
tasks.Add(task);
// Index Buffer (required)
task = mesh.DownloadDataAsyncGPU(MeshBufferType::Index, meshData.IB);
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
if (task == nullptr)
return true;
task->Start();
@@ -701,6 +713,19 @@ void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
}
}
int32 SkinnedModel::GetLODsCount() const
{
return LODs.Count();
}
void SkinnedModel::GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex)
{
auto& lod = LODs[lodIndex];
meshes.Resize(lod.Meshes.Count());
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
meshes[meshIndex] = &lod.Meshes[meshIndex];
}
void SkinnedModel::InitAsVirtual()
{
// Init with one mesh and single bone
@@ -723,6 +748,21 @@ void SkinnedModel::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
#if USE_EDITOR
void SkinnedModel::GetReferences(Array<Guid>& output) const
{
// Base
BinaryAsset::GetReferences(output);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
}
#endif
int32 SkinnedModel::GetMaxResidency() const
{
return LODs.Count();

View File

@@ -61,15 +61,6 @@ public:
return LODs.HasItems();
}
/// <summary>
/// Gets amount of the level of details in the model
/// </summary>
/// <returns>Amount of the level of details in the model</returns>
FORCE_INLINE int32 GetLODsCount() const
{
return LODs.Count();
}
/// <summary>
/// Gets the amount of loaded model LODs.
/// </summary>
@@ -184,22 +175,14 @@ public:
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <returns>Task that will gather chunk data or null if already here.</returns>
ContentLoadTask* RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
ContentLoadTask* RequestLODDataAsync(int32 lodIndex);
/// <summary>
/// Gets the model LOD data (links bytes).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <param name="data">The data (may be missing if failed to get it).</param>
void GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
GetChunkData(chunkIndex, data);
}
void GetLODData(int32 lodIndex, BytesContainer& data) const;
public:
@@ -300,18 +283,11 @@ public:
// [ModelBase]
void SetupMaterialSlots(int32 slotsCount) override;
int32 GetLODsCount() const override;
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override
{
// Base
BinaryAsset::GetReferences(output);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
}
void GetReferences(Array<Guid>& output) const override;
#endif
// [StreamableResource]

View File

@@ -405,38 +405,42 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const StringView& path)
{
PROFILE_CPU();
ScopeLock lock(_locker);
// Mark registry as draft
_isDirty = true;
// Check if asset has been already added to the registry
bool isMissing = true;
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
auto& e = i->Value;
// Compare IDs
if (e.Info.ID == id)
{
// Update registry entry
e.Info.Path = path;
e.Info.TypeName = typeName;
// Back
if (e.Info.Path != path)
{
e.Info.Path = path;
_isDirty = true;
}
if (e.Info.TypeName != typeName)
{
e.Info.TypeName = typeName;
_isDirty = true;
}
isMissing = false;
break;
}
// Compare paths
if (e.Info.Path == path)
{
// Update registry entry
e.Info.ID = id;
e.Info.TypeName = typeName;
// Back
if (e.Info.ID != id)
{
e.Info.Path = path;
_isDirty = true;
}
if (e.Info.TypeName != typeName)
{
e.Info.TypeName = typeName;
_isDirty = true;
}
isMissing = false;
break;
}
@@ -445,9 +449,8 @@ void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const St
if (isMissing)
{
LOG(Info, "Register asset {0}:{1} \'{2}\'", id, typeName, path);
// Add new asset entry
_registry.Add(id, Entry(id, typeName, path));
_isDirty = true;
}
}

View File

@@ -14,7 +14,7 @@ class FlaxStorage;
/// The binary assets factory base class.
/// </summary>
/// <seealso cref="IAssetFactory" />
class BinaryAssetFactoryBase : public IAssetFactory
class FLAXENGINE_API BinaryAssetFactoryBase : public IAssetFactory
{
public:

View File

@@ -15,7 +15,7 @@ class IAssetUpgrader;
/// <summary>
/// The asset objects factory.
/// </summary>
class IAssetFactory
class FLAXENGINE_API IAssetFactory
{
public:

View File

@@ -10,7 +10,7 @@
/// The Json assets factory base class.
/// </summary>
/// <seealso cref="IAssetFactory" />
class JsonAssetFactoryBase : public IAssetFactory
class FLAXENGINE_API JsonAssetFactoryBase : public IAssetFactory
{
protected:
@@ -23,7 +23,6 @@ public:
{
return Create(info);
}
Asset* NewVirtual(const AssetInfo& info) override
{
return Create(info);
@@ -47,12 +46,13 @@ protected:
}
};
#define REGISTER_JSON_ASSET(type, typeName) \
#define REGISTER_JSON_ASSET(type, typeName, supportsVirtualAssets) \
const String type::TypeName = TEXT(typeName); \
class CONCAT_MACROS(Factory, type) : public JsonAssetFactory<type> \
{ \
public: \
CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Add(type::TypeName, this); } \
~CONCAT_MACROS(Factory, type)() { IAssetFactory::Get().Remove(type::TypeName); } \
bool SupportsVirtualAssets() const override { return supportsVirtualAssets; } \
}; \
static CONCAT_MACROS(Factory, type) CONCAT_MACROS(CFactory, type)

View File

@@ -176,7 +176,7 @@ void JsonAssetBase::onRename(const StringView& newPath)
#endif
REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset");
REGISTER_JSON_ASSET(JsonAsset, "FlaxEngine.JsonAsset", true);
JsonAsset::JsonAsset(const SpawnParams& params, const AssetInfo* info)
: JsonAssetBase(params, info)

View File

@@ -3,6 +3,7 @@
#pragma once
#include "Engine/Threading/Task.h"
#include "Engine/Core/Types/String.h"
class Asset;
class LoadingThread;

View File

@@ -9,7 +9,7 @@
/// <summary>
/// Binary asset upgrading context structure.
/// </summary>
struct AssetMigrationContext
struct FLAXENGINE_API AssetMigrationContext
{
/// <summary>
/// The input data.
@@ -63,7 +63,7 @@ typedef bool (*UpgradeHandler)(AssetMigrationContext& context);
/// Binary Assets Upgrader base class
/// </summary>
/// <seealso cref="IAssetUpgrader" />
class BinaryAssetUpgrader : public IAssetUpgrader
class FLAXENGINE_API BinaryAssetUpgrader : public IAssetUpgrader
{
public:

View File

@@ -7,7 +7,7 @@
/// <summary>
/// The assets upgrading objects interface.
/// </summary>
class IAssetUpgrader
class FLAXENGINE_API IAssetUpgrader
{
public:

View File

@@ -8,6 +8,7 @@
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Asset.h"
/// <summary>
/// Model Asset Upgrader

View File

@@ -4,6 +4,7 @@
#include "BinaryAssetUpgrader.h"
#include "Engine/Platform/Platform.h"
#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h"
/// <summary>
/// Material Asset and Shader Asset Upgrader

View File

@@ -6,6 +6,11 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Graphics/Models/Types.h"
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Math/BoundingSphere.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Core/Math/Transform.h"
/// <summary>
/// Skinned Model Asset Upgrader

View File

@@ -229,3 +229,9 @@ public:
OnSet(asset);
}
};
template<typename T>
uint32 GetHash(const WeakAssetReference<T>& key)
{
return GetHash(key.GetID());
}

View File

@@ -31,6 +31,7 @@
#include "CreateParticleEmitterFunction.h"
#include "CreateAnimationGraphFunction.h"
#include "CreateVisualScript.h"
#include "CreateJson.h"
// Tags used to detect asset creation mode
const String AssetsImportingManager::CreateTextureTag(TEXT("Texture"));
@@ -91,6 +92,10 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback)
if (result != CreateAssetResult::Ok)
return result;
// Skip for non-flax assets (eg. json resource or custom asset type)
if (!TargetAssetPath.EndsWith(ASSET_FILES_EXTENSION))
return CreateAssetResult::Ok;
// Validate assigned TypeID
if (Data.Header.TypeName.IsEmpty())
{
@@ -112,8 +117,7 @@ CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback)
}
// Save file
result = Save();
result = FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok;
if (result == CreateAssetResult::Ok)
{
_applyChangesResult = CreateAssetResult::Abort;
@@ -161,11 +165,6 @@ void CreateAssetContext::AddMeta(JsonWriter& writer) const
writer.String(Platform::GetUserName());
}
CreateAssetResult CreateAssetContext::Save()
{
return FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok;
}
void CreateAssetContext::ApplyChanges()
{
// Get access
@@ -231,8 +230,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP
bool AssetsImportingManager::Import(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION)));
LOG(Info, "Importing file '{0}' to '{1}'...", inputPath, outputPath);
// Check if input file exists
@@ -246,8 +243,7 @@ bool AssetsImportingManager::Import(const StringView& inputPath, const StringVie
const String extension = FileSystem::GetExtension(inputPath).ToLower();
// Special case for raw assets
const String assetExtension = ASSET_FILES_EXTENSION;
if (assetExtension.Compare(extension, StringSearchCase::IgnoreCase) == 0)
if (StringView(ASSET_FILES_EXTENSION).Compare(StringView(extension), StringSearchCase::IgnoreCase) == 0)
{
// Simply copy file (content layer will resolve duplicated IDs, etc.)
return FileSystem::CopyFile(outputPath, inputPath);
@@ -266,8 +262,6 @@ bool AssetsImportingManager::Import(const StringView& inputPath, const StringVie
bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION)));
// Check if asset not exists
if (!FileSystem::FileExists(outputPath))
{
@@ -383,64 +377,67 @@ bool AssetsImportingManagerService::Init()
AssetImporter InBuildImporters[] =
{
// Textures and Cube Textures
{ TEXT("tga"), ImportTexture::Import },
{ TEXT("dds"), ImportTexture::Import },
{ TEXT("png"), ImportTexture::Import },
{ TEXT("bmp"), ImportTexture::Import },
{ TEXT("gif"), ImportTexture::Import },
{ TEXT("tiff"), ImportTexture::Import },
{ TEXT("tif"), ImportTexture::Import },
{ TEXT("jpeg"), ImportTexture::Import },
{ TEXT("jpg"), ImportTexture::Import },
{ TEXT("hdr"), ImportTexture::Import },
{ TEXT("raw"), ImportTexture::Import },
{ TEXT("tga"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("dds"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("png"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("bmp"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("gif"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("tiff"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("tif"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("jpeg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
// IES Profiles
{ TEXT("ies"), ImportTexture::ImportIES },
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },
// Shaders
{ TEXT("shader"), ImportShader::Import },
{ TEXT("shader"), ASSET_FILES_EXTENSION, ImportShader::Import },
// Audio
{ TEXT("wav"), ImportAudio::ImportWav },
{ TEXT("mp3"), ImportAudio::ImportMp3 },
{ TEXT("wav"), ASSET_FILES_EXTENSION, ImportAudio::ImportWav },
{ TEXT("mp3"), ASSET_FILES_EXTENSION, ImportAudio::ImportMp3 },
#if COMPILE_WITH_OGG_VORBIS
{ TEXT("ogg"), ImportAudio::ImportOgg },
{ TEXT("ogg"), ASSET_FILES_EXTENSION, ImportAudio::ImportOgg },
#endif
// Fonts
{ TEXT("ttf"), ImportFont::Import },
{ TEXT("otf"), ImportFont::Import },
{ TEXT("ttf"), ASSET_FILES_EXTENSION, ImportFont::Import },
{ TEXT("otf"), ASSET_FILES_EXTENSION, ImportFont::Import },
// Models
{ TEXT("obj"), ImportModelFile::Import },
{ TEXT("fbx"), ImportModelFile::Import },
{ TEXT("x"), ImportModelFile::Import },
{ TEXT("dae"), ImportModelFile::Import },
{ TEXT("gltf"), ImportModelFile::Import },
{ TEXT("glb"), ImportModelFile::Import },
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
// gettext PO files
{ TEXT("po"), TEXT("json"), CreateJson::ImportPo },
// Models (untested formats - may fail :/)
{ TEXT("blend"), ImportModelFile::Import },
{ TEXT("bvh"), ImportModelFile::Import },
{ TEXT("ase"), ImportModelFile::Import },
{ TEXT("ply"), ImportModelFile::Import },
{ TEXT("dxf"), ImportModelFile::Import },
{ TEXT("ifc"), ImportModelFile::Import },
{ TEXT("nff"), ImportModelFile::Import },
{ TEXT("smd"), ImportModelFile::Import },
{ TEXT("vta"), ImportModelFile::Import },
{ TEXT("mdl"), ImportModelFile::Import },
{ TEXT("md2"), ImportModelFile::Import },
{ TEXT("md3"), ImportModelFile::Import },
{ TEXT("md5mesh"), ImportModelFile::Import },
{ TEXT("q3o"), ImportModelFile::Import },
{ TEXT("q3s"), ImportModelFile::Import },
{ TEXT("ac"), ImportModelFile::Import },
{ TEXT("stl"), ImportModelFile::Import },
{ TEXT("lwo"), ImportModelFile::Import },
{ TEXT("lws"), ImportModelFile::Import },
{ TEXT("lxo"), ImportModelFile::Import },
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
};
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));

View File

@@ -9,7 +9,11 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Content/Cache/AssetsCache.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Localization/LocalizedStringTable.h"
#include "Engine/Utilities/TextProcessing.h"
#include "FlaxEngine.Gen.h"
bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename)
@@ -51,7 +55,7 @@ bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsi
LOG(Warning, "Failed to create directory");
return true;
}
}
}
}
rapidjson_flax::StringBuffer buffer;
@@ -88,8 +92,184 @@ bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsi
{
asset->Reload();
}
else
{
Content::GetRegistry()->RegisterAsset(id, String(dataTypename), path);
}
return false;
}
void FormatPoValue(String& value)
{
value.Replace(TEXT("\\n"), TEXT("\n"));
value.Replace(TEXT("%s"), TEXT("{}"));
value.Replace(TEXT("%d"), TEXT("{}"));
}
CreateAssetResult CreateJson::ImportPo(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(LocalizedStringTable, 1);
// Load file (UTF-16)
String inputData;
if (File::ReadAllText(context.InputPath, inputData))
{
return CreateAssetResult::InvalidPath;
}
// Use virtual asset for data storage and serialization
AssetReference<LocalizedStringTable> asset = Content::CreateVirtualAsset<LocalizedStringTable>();
if (!asset)
return CreateAssetResult::Error;
// Parse PO format
int32 pos = 0;
int32 pluralCount = 0;
int32 lineNumber = 0;
bool fuzzy = false, hasNewContext = false;
StringView msgctxt, msgid;
String idTmp;
while (pos < inputData.Length())
{
// Read line
const int32 startPos = pos;
while (pos < inputData.Length() && inputData[pos] != '\n')
pos++;
const StringView line(&inputData[startPos], pos - startPos);
lineNumber++;
pos++;
const int32 valueStart = line.Find('\"') + 1;
const int32 valueEnd = line.FindLast('\"');
const StringView value(line.Get() + valueStart, Math::Max(valueEnd - valueStart, 0));
if (line.StartsWith(StringView(TEXT("msgid_plural"))))
{
// Plural form
}
else if (line.StartsWith(StringView(TEXT("msgid"))))
{
// Id
msgid = value;
// Reset context if already used
if (!hasNewContext)
msgctxt = StringView();
hasNewContext = false;
}
else if (line.StartsWith(StringView(TEXT("msgstr"))))
{
// String
if (msgid.HasChars())
{
// Format message
String msgstr(value);
FormatPoValue(msgstr);
// Get message id
StringView id = msgid;
if (msgctxt.HasChars())
{
idTmp = String(msgctxt) + TEXT(".") + String(msgid);
id = idTmp;
}
int32 indexStart = line.Find('[');
if (indexStart != -1 && indexStart < valueStart)
{
indexStart++;
while (indexStart < line.Length() && StringUtils::IsWhitespace(line[indexStart]))
indexStart++;
int32 indexEnd = line.Find(']');
while (indexEnd > indexStart && StringUtils::IsWhitespace(line[indexEnd - 1]))
indexEnd--;
int32 index = -1;
StringUtils::Parse(line.Get() + indexStart, (uint32)(indexEnd - indexStart), &index);
if (pluralCount <= 0)
{
LOG(Error, "Missing 'nplurals'. Cannot use plural message at line {0}", lineNumber);
return CreateAssetResult::Error;
}
if (index < 0 || index > pluralCount)
{
LOG(Error, "Invalid plural message index at line {0}", lineNumber);
return CreateAssetResult::Error;
}
// Plural message
asset->AddPluralString(id, msgstr, index);
}
else
{
// Message
asset->AddString(id, msgstr);
}
}
}
else if (line.StartsWith(StringView(TEXT("msgctxt"))))
{
// Context
msgctxt = value;
hasNewContext = true;
}
else if (line.StartsWith('\"'))
{
// Config
const Char* pluralForms = StringUtils::Find(line.Get(), TEXT("Plural-Forms"));
if (pluralForms != nullptr && pluralForms < line.Get() + line.Length() - 1)
{
// Process plural forms rule
const Char* nplurals = StringUtils::Find(pluralForms, TEXT("nplurals"));
if (nplurals && nplurals < line.Get() + line.Length())
{
while (*nplurals && *nplurals != '=')
nplurals++;
while (*nplurals && (StringUtils::IsWhitespace(*nplurals) || *nplurals == '='))
nplurals++;
const Char* npluralsStart = nplurals;
while (*nplurals && !StringUtils::IsWhitespace(*nplurals) && *nplurals != ';')
nplurals++;
StringUtils::Parse(npluralsStart, (uint32)(nplurals - npluralsStart), &pluralCount);
if (pluralCount < 0 || pluralCount > 100)
{
LOG(Error, "Invalid 'nplurals' at line {0}", lineNumber);
return CreateAssetResult::Error;
}
}
// TODO: parse plural forms rule
}
const Char* language = StringUtils::Find(line.Get(), TEXT("Language"));
if (language != nullptr && language < line.Get() + line.Length() - 1)
{
// Process language locale
while (*language && *language != ':')
language++;
language++;
while (*language && StringUtils::IsWhitespace(*language))
language++;
const Char* languageStart = language;
while (*language && !StringUtils::IsWhitespace(*language) && *language != '\\' && *language != '\"')
language++;
asset->Locale.Set(languageStart, (int32)(language - languageStart));
if (asset->Locale == TEXT("English"))
asset->Locale = TEXT("en");
if (asset->Locale.Length() > 5)
LOG(Warning, "Imported .po file uses invalid locale '{0}'", asset->Locale);
}
}
else if (line.StartsWith('#') || line.IsEmpty())
{
// Comment
const Char* fuzzyPos = StringUtils::Find(line.Get(), TEXT("fuzzy"));
fuzzy |= fuzzyPos != nullptr && fuzzyPos < line.Get() + line.Length() - 1;
}
}
if (asset->Locale.IsEmpty())
LOG(Warning, "Imported .po file has missing locale");
// Save asset
return asset->Save(context.TargetAssetPath) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok;
}
#endif

View File

@@ -18,6 +18,7 @@ public:
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename);
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename);
static bool Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename);
static CreateAssetResult ImportPo(CreateAssetContext& context);
};
#endif

View File

@@ -111,12 +111,6 @@ public:
/// <param name="writer">The json metadata writer.</param>
void AddMeta(JsonWriter& writer) const;
/// <summary>
/// Save asset file data to the hard drive
/// </summary>
/// <returns>Saving result</returns>
CreateAssetResult Save();
private:
void ApplyChanges();
@@ -130,12 +124,17 @@ struct AssetImporter
public:
/// <summary>
/// Extension of the file to import with that importer
/// Extension of the file to import with that importer (without leading dot).
/// </summary>
String FileExtension;
/// <summary>
/// Call asset importing process
/// Extension of the output file as output with that importer (without leading dot).
/// </summary>
String ResultExtension;
/// <summary>
/// Callback for the asset importing process.
/// </summary>
CreateAssetFunction Callback;
};

View File

@@ -2,6 +2,7 @@
#pragma once
#include <initializer_list>
#include "Engine/Platform/Platform.h"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Core/Memory/Allocation.h"
@@ -47,6 +48,20 @@ public:
_allocation.Allocate(capacity);
}
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> class.
/// </summary>
/// <param name="initList">The initial values defined in the array.</param>
Array(std::initializer_list<T> initList)
{
_count = _capacity = (int32)initList.size();
if (_count > 0)
{
_allocation.Allocate(_count);
Memory::ConstructItems(Get(), initList.begin(), _count);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Array"/> class.
/// </summary>
@@ -123,6 +138,24 @@ public:
_allocation.Swap(other._allocation);
}
/// <summary>
/// The assignment operator that deletes the current collection of items and the copies items from the initializer list.
/// </summary>
/// <param name="initList">The other collection to copy.</param>
/// <returns>The reference to this.</returns>
Array& operator=(std::initializer_list<T> initList) noexcept
{
Memory::DestructItems(Get(), _count);
_count = _capacity = (int32)initList.size();
if (_capacity > 0)
{
_allocation.Allocate(_capacity);
Memory::ConstructItems(Get(), initList.begin(), _count);
}
return *this;
}
/// <summary>
/// The assignment operator that deletes the current collection of items and the copies items from the other array.
/// </summary>

View File

@@ -13,6 +13,7 @@
#include "Engine/Input/InputSettings.h"
#include "Engine/Audio/AudioSettings.h"
#include "Engine/Navigation/NavigationSettings.h"
#include "Engine/Localization/LocalizationSettings.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/JsonAsset.h"
#include "Engine/Content/AssetReference.h"
@@ -55,6 +56,8 @@ IMPLEMENT_SETTINGS_GETTER(PS4PlatformSettings, PS4Platform);
IMPLEMENT_SETTINGS_GETTER(XboxScarlettPlatformSettings, XboxScarlettPlatform);
#elif PLATFORM_ANDROID
IMPLEMENT_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
#elif PLATFORM_SWITCH
IMPLEMENT_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
#else
#error Unknown platform
#endif
@@ -125,6 +128,7 @@ bool GameSettings::Load()
PRELOAD_SETTINGS(Input);
PRELOAD_SETTINGS(Graphics);
PRELOAD_SETTINGS(Navigation);
PRELOAD_SETTINGS(Localization);
PRELOAD_SETTINGS(GameCooking);
#undef PRELOAD_SETTINGS
@@ -156,6 +160,7 @@ void GameSettings::Apply()
APPLY_SETTINGS(InputSettings);
APPLY_SETTINGS(GraphicsSettings);
APPLY_SETTINGS(NavigationSettings);
APPLY_SETTINGS(LocalizationSettings);
APPLY_SETTINGS(BuildSettings);
APPLY_SETTINGS(PlatformSettings);
#undef APPLY_SETTINGS
@@ -195,6 +200,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(Input);
DESERIALIZE(Graphics);
DESERIALIZE(Navigation);
DESERIALIZE(Localization);
DESERIALIZE(GameCooking);
// Per-platform settings containers
@@ -204,6 +210,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
DESERIALIZE(PS4Platform);
DESERIALIZE(XboxScarlettPlatform);
DESERIALIZE(AndroidPlatform);
DESERIALIZE(SwitchPlatform);
}
void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)

View File

@@ -11,6 +11,7 @@ namespace FlaxEditor.Content.Settings
{
internal const string PS4PlatformSettingsTypename = "FlaxEditor.Content.Settings.PS4PlatformSettings";
internal const string XboxScarlettPlatformSettingsTypename = "FlaxEditor.Content.Settings.XboxScarlettPlatformSettings";
internal const string SwitchPlatformSettingsTypename = "FlaxEditor.Content.Settings.SwitchPlatformSettings";
/// <summary>
/// The default application icon.
@@ -78,6 +79,12 @@ namespace FlaxEditor.Content.Settings
[EditorOrder(1045), EditorDisplay("Other Settings"), AssetReference(typeof(NavigationSettings), true), Tooltip("Reference to Navigation Settings asset")]
public JsonAsset Navigation;
/// <summary>
/// Reference to <see cref="LocalizationSettings"/> asset.
/// </summary>
[EditorOrder(1046), EditorDisplay("Other Settings"), AssetReference(typeof(LocalizationSettings), true), Tooltip("Reference to Localization Settings asset")]
public JsonAsset Localization;
/// <summary>
/// Reference to <see cref="BuildSettings"/> asset.
/// </summary>
@@ -138,6 +145,14 @@ namespace FlaxEditor.Content.Settings
public JsonAsset AndroidPlatform;
#endif
#if FLAX_EDITOR || PLATFORM_SWITCH
/// <summary>
/// Reference to Switch Platform Settings asset. Used to apply configuration on Switch platform.
/// </summary>
[EditorOrder(2070), EditorDisplay("Platform Settings", "Switch"), AssetReference(SwitchPlatformSettingsTypename, true), Tooltip("Reference to Switch Platform Settings asset")]
public JsonAsset SwitchPlatform;
#endif
/// <summary>
/// Gets the absolute path to the game settings asset file.
/// </summary>
@@ -210,6 +225,8 @@ namespace FlaxEditor.Content.Settings
return LoadAsset<GraphicsSettings>(gameSettings.Graphics) as T;
if (type == typeof(NavigationSettings))
return LoadAsset<NavigationSettings>(gameSettings.Navigation) as T;
if (type == typeof(LocalizationSettings))
return LoadAsset<LocalizationSettings>(gameSettings.Localization) as T;
if (type == typeof(BuildSettings))
return LoadAsset<BuildSettings>(gameSettings.GameCooking) as T;
if (type == typeof(InputSettings))
@@ -240,6 +257,10 @@ namespace FlaxEditor.Content.Settings
if (type == typeof(AndroidPlatformSettings))
return LoadAsset<AndroidPlatformSettings>(gameSettings.AndroidPlatform) as T;
#endif
#if FLAX_EDITOR || PLATFORM_SWITCH
if (type.FullName == SwitchPlatformSettingsTypename)
return LoadAsset(gameSettings.SwitchPlatform, SwitchPlatformSettingsTypename) as T;
#endif
if (gameSettings.CustomSettings != null)
{
@@ -308,6 +329,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.Graphics, obj);
if (type == typeof(NavigationSettings))
return SaveAsset(gameSettings, ref gameSettings.Navigation, obj);
if (type == typeof(LocalizationSettings))
return SaveAsset(gameSettings, ref gameSettings.Localization, obj);
if (type == typeof(BuildSettings))
return SaveAsset(gameSettings, ref gameSettings.GameCooking, obj);
if (type == typeof(InputSettings))
@@ -324,6 +347,8 @@ namespace FlaxEditor.Content.Settings
return SaveAsset(gameSettings, ref gameSettings.XboxScarlettPlatform, obj);
if (type == typeof(AndroidPlatformSettings))
return SaveAsset(gameSettings, ref gameSettings.AndroidPlatform, obj);
if (type.FullName == SwitchPlatformSettingsTypename)
return SaveAsset(gameSettings, ref gameSettings.SwitchPlatform, obj);
if (type == typeof(AudioSettings))
return SaveAsset(gameSettings, ref gameSettings.Audio, obj);

View File

@@ -68,6 +68,7 @@ public:
Guid Input;
Guid Graphics;
Guid Navigation;
Guid Localization;
Guid GameCooking;
// Per-platform settings containers
@@ -77,6 +78,7 @@ public:
Guid PS4Platform;
Guid XboxScarlettPlatform;
Guid AndroidPlatform;
Guid SwitchPlatform;
public:

View File

@@ -3,6 +3,8 @@
#pragma once
#include "Engine/Core/Config/Settings.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
/// Layers and objects tags settings.

View File

@@ -23,3 +23,6 @@
#if PLATFORM_ANDROID
#include "Engine/Platform/Android/AndroidPlatformSettings.h"
#endif
#if PLATFORM_SWITCH
#include "Platforms/Switch/Engine/Platform/SwitchPlatformSettings.h"
#endif

View File

@@ -15,31 +15,31 @@ public:
/// <summary>
/// The target amount of the game logic updates per second (script updates frequency).
/// </summary>
API_FIELD(Attributes="EditorOrder(1), DefaultValue(30.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")")
float UpdateFPS = 30.0f;
API_FIELD(Attributes="EditorOrder(1), Limit(0, 1000), EditorDisplay(\"General\", \"Update FPS\")")
float UpdateFPS = 60.0f;
/// <summary>
/// The target amount of the physics simulation updates per second (also fixed updates frequency).
/// </summary>
API_FIELD(Attributes="EditorOrder(2), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")")
API_FIELD(Attributes="EditorOrder(2), Limit(0, 1000), EditorDisplay(\"General\", \"Physics FPS\")")
float PhysicsFPS = 60.0f;
/// <summary>
/// The target amount of the frames rendered per second (actual game FPS).
/// </summary>
API_FIELD(Attributes="EditorOrder(3), DefaultValue(60.0f), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")")
API_FIELD(Attributes="EditorOrder(3), Limit(0, 1000), EditorDisplay(\"General\", \"Draw FPS\")")
float DrawFPS = 60.0f;
/// <summary>
/// The game time scale factor. Default is 1.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(1.0f), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")")
API_FIELD(Attributes="EditorOrder(10), Limit(0, 1000.0f, 0.1f), EditorDisplay(\"General\")")
float TimeScale = 1.0f;
/// <summary>
/// The maximum allowed delta time (in seconds) for the game logic update step.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(0.1f), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")")
API_FIELD(Attributes="EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")")
float MaxUpdateDeltaTime = 0.1f;
public:

View File

@@ -4,11 +4,6 @@
#include "Engine/Core/Memory/Allocation.h"
template<typename>
class Function;
template<typename... Params>
class Delegate;
/// <summary>
/// The function object.
/// </summary>

Some files were not shown because too many files have changed in this diff Show More