diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index 9d3280f4b..5259ca2df 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -511,11 +511,12 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) // Rename app const String newName = EditorUtilities::GetOutputName(); - if (newName != StringUtils::GetFileNameWithoutExtension(files[0])) + const StringView oldName = StringUtils::GetFileNameWithoutExtension(files[0]); + if (newName != oldName) { if (FileSystem::MoveFile(data.NativeCodeOutputPath / newName + TEXT(".exe"), files[0], true)) { - data.Error(TEXT("Failed to change output executable name.")); + data.Error(String::Format(TEXT("Failed to change output executable name from '{}' to '{}'."), oldName, newName)); return true; } } diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index 84a36e5c6..ff22247ef 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -275,7 +275,7 @@ bool DeployDataStep::Perform(CookingData& data) DEPLOY_NATIVE_FILE("libmonosgen-2.0.dylib"); DEPLOY_NATIVE_FILE("libSystem.IO.Compression.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Native.dylib"); - DEPLOY_NATIVE_FILE("libSystem.NET.Security.Native.dylib"); + DEPLOY_NATIVE_FILE("libSystem.Net.Security.Native.dylib"); DEPLOY_NATIVE_FILE("libSystem.Security.Cryptography.Native.Apple.dylib"); break; #undef DEPLOY_NATIVE_FILE diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index d2aa85269..e71f9a8bc 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -95,7 +95,21 @@ namespace FlaxEditor.CustomEditors.Dedicated var cm = new ItemsListContextMenu(180); for (int i = 0; i < scripts.Count; i++) { - cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); + var script = scripts[i]; + var item = new TypeSearchPopup.TypeItemView(script); + if (script.GetAttributes(false).FirstOrDefault(x => x is RequireActorAttribute) is RequireActorAttribute requireActor) + { + var actors = ScriptsEditor.ParentEditor.Values; + foreach (var a in actors) + { + if (a.GetType() != requireActor.RequiredType) + { + item.Enabled = false; + break; + } + } + } + cm.AddItem(item); } cm.TextChanged += text => { diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index d8493a70a..ca053dcab 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using FlaxEditor.GUI.Docking; using FlaxEditor.Utilities; using FlaxEngine; +using FlaxEngine.GUI; namespace FlaxEditor.Options { @@ -217,6 +218,21 @@ namespace FlaxEditor.Options [EditorDisplay("Interface"), EditorOrder(320), Tooltip("Toggles tree line visibility in places like the Scene or Content Panel.")] public bool ShowTreeLines { get; set; } = true; + /// + /// Gets or sets tooltip text alignment. + /// + [DefaultValue(TextAlignment.Center)] + [EditorDisplay("Interface"), EditorOrder(321)] + public TextAlignment TooltipTextAlignment { get => _tooltipTextAlignment; + set + { + _tooltipTextAlignment = value; + Style.Current.SharedTooltip.HorizontalTextAlignment = value; + } + } + + private TextAlignment _tooltipTextAlignment = TextAlignment.Center; + /// /// Gets or sets the timestamps prefix mode for output log messages. /// diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index d43af963e..24dea810e 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -331,6 +331,7 @@ namespace FlaxEditor.Options SharedTooltip = new Tooltip(), }; + style.SharedTooltip.HorizontalTextAlignment = Editor.Instance.Options.Options.Interface.TooltipTextAlignment; style.DragWindow = style.BackgroundSelected * 0.7f; return style; } @@ -386,6 +387,7 @@ namespace FlaxEditor.Options SharedTooltip = new Tooltip(), }; + style.SharedTooltip.HorizontalTextAlignment = Editor.Instance.Options.Options.Interface.TooltipTextAlignment; return style; } diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index 0fc614af5..e3f521437 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -5,10 +5,11 @@ #include "Engine/Platform/File.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/CreateProcessSettings.h" -#include "Engine/Core/Log.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Types/StringBuilder.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" #include "Engine/Content/Content.h" @@ -28,6 +29,7 @@ String EditorUtilities::GetOutputName() outputName.Replace(TEXT("${COMPANY_NAME}"), *gameSettings->CompanyName, StringSearchCase::IgnoreCase); if (outputName.IsEmpty()) outputName = TEXT("FlaxGame"); + ValidatePathChars(outputName, 0); return outputName; } @@ -360,6 +362,28 @@ bool EditorUtilities::IsInvalidPathChar(Char c) return false; } +void EditorUtilities::ValidatePathChars(String& filename, char invalidCharReplacement) +{ + if (invalidCharReplacement == 0) + { + StringBuilder result; + for (int32 i = 0; i < filename.Length(); i++) + { + if (!IsInvalidPathChar(filename[i])) + result.Append(filename[i]); + } + filename = result.ToString(); + } + else + { + for (int32 i = 0; i < filename.Length(); i++) + { + if (IsInvalidPathChar(filename[i])) + filename[i] = invalidCharReplacement; + } + } +} + bool EditorUtilities::ReplaceInFiles(const String& folderPath, const Char* searchPattern, DirectorySearchOption searchOption, const String& findWhat, const String& replaceWith) { Array files; @@ -391,7 +415,7 @@ bool EditorUtilities::ReplaceInFile(const StringView& file, const Dictionarytrue if the given character cannot be used as a path because it is illegal character; otherwise, false. static bool IsInvalidPathChar(Char c); + /// + /// Validates path characters and replaces any incorrect ones. + /// + /// The input and output filename string to process. + /// The character to use for replacement for any invalid characters in the path. Use '0' to remove them. + static void ValidatePathChars(String& filename, char invalidCharReplacement = ' '); + /// /// Replaces the given text with other one in the files. /// diff --git a/Source/Editor/Windows/ContentWindow.cs b/Source/Editor/Windows/ContentWindow.cs index bb7c594b3..6089b2e07 100644 --- a/Source/Editor/Windows/ContentWindow.cs +++ b/Source/Editor/Windows/ContentWindow.cs @@ -638,12 +638,14 @@ namespace FlaxEditor.Windows var toDelete = new List(items); toDelete.Sort((a, b) => a.IsFolder ? 1 : b.IsFolder ? -1 : a.Compare(b)); + string singularPlural = toDelete.Count > 1 ? "s" : ""; + string msg = toDelete.Count == 1 - ? string.Format("Are you sure to delete \'{0}\'?\nThis action cannot be undone. Files will be deleted permanently.", items[0].Path) - : string.Format("Are you sure to delete {0} selected items?\nThis action cannot be undone. Files will be deleted permanently.", items.Count); + ? string.Format("Delete \'{0}\'?\n\nThis action cannot be undone.\nFile will be deleted permanently.", items[0].Path) + : string.Format("Delete {0} selected items?\n\nThis action cannot be undone.\nFiles will be deleted permanently.", items.Count); // Ask user - if (MessageBox.Show(msg, "Delete asset(s)", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) + if (MessageBox.Show(msg, "Delete asset" + singularPlural, MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) return; // Clear navigation diff --git a/Source/Editor/Windows/GameCookerWindow.cs b/Source/Editor/Windows/GameCookerWindow.cs index 855faed63..cbe211eca 100644 --- a/Source/Editor/Windows/GameCookerWindow.cs +++ b/Source/Editor/Windows/GameCookerWindow.cs @@ -328,6 +328,19 @@ namespace FlaxEditor.Windows group.Object(new ReadOnlyValueContainer(platformObj)); + layout.Space(2); + var openOutputButton = layout.Button("Open output folder").Button; + openOutputButton.TooltipText = "Opens the defined out folder if the path exists."; + openOutputButton.Clicked += () => + { + string output = StringUtils.ConvertRelativePathToAbsolute(Globals.ProjectFolder, StringUtils.NormalizePath(proxy.PerPlatformOptions[_platform].Output)); + if (Directory.Exists(output)) + FlaxEngine.FileSystem.ShowFileExplorer(output); + else + FlaxEditor.Editor.LogWarning($"Can not open path: {output} because it does not exist."); + }; + layout.Space(2); + _buildButton = layout.Button("Build").Button; _buildButton.Clicked += OnBuildClicked; } diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 2ae7756da..077bf9b80 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -2145,4 +2145,8 @@ void DebugDraw::DrawText(const StringView& text, const Transform& transform, con t.TimeLeft = duration; } +void DebugDraw::Clear(void* context) +{ + DebugDraw::UpdateContext(context, MAX_float); +} #endif diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index 9c58aa727..bb5cf2e50 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -689,6 +689,12 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The font size. /// The duration (in seconds). Use 0 to draw it only once. API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f); + + /// + /// Clear all debug draw displayed on sceen. + /// + /// + API_FUNCTION() static void Clear(void* context = nullptr); }; #define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, size, duration, depthTest) DebugDraw::DrawAxisFromDirection(origin, direction, size, duration, depthTest); @@ -721,6 +727,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) +#define DEBUG_DRAW_CLEAR(context) DebugDraw::Clear(context) #else @@ -753,5 +760,6 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) +#define DEBUG_DRAW_CLEAR(context) #endif diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 11157783e..010add151 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -190,6 +190,11 @@ void WheeledVehicle::SetThrottle(float value) _throttle = Math::Clamp(value, -1.0f, 1.0f); } +float WheeledVehicle::GetThrottle() +{ + return _throttle; +} + void WheeledVehicle::SetSteering(float value) { _steering = Math::Clamp(value, -1.0f, 1.0f); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index 16e226546..fe518a41d 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -541,6 +541,12 @@ public: /// The value (-1,1 range). When using UseReverseAsBrake it can be negative and will be used as brake and backward driving. API_FUNCTION() void SetThrottle(float value); + /// + /// Get the vehicle throttle. It is the analog accelerator pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state. + /// + /// The vehicle throttle. + API_FUNCTION() float GetThrottle(); + /// /// Sets the input for vehicle steering. Steer is the analog steer value in range (-1,1) where -1 represents the steering wheel at left lock and +1 represents the steering wheel at right lock. /// diff --git a/Source/Engine/Tests/TestScripting.h b/Source/Engine/Tests/TestScripting.h index 19fd76351..dc05750e1 100644 --- a/Source/Engine/Tests/TestScripting.h +++ b/Source/Engine/Tests/TestScripting.h @@ -8,6 +8,35 @@ #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/SerializableScriptingObject.h" +// Test default values init on fields. +API_STRUCT(NoDefault) struct TestDefaultValues +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(TestDefaultValues); + + // Default value case 1 + API_FIELD() float TestFloat1 = {}; + // Default value case 2 + API_FIELD() float TestFloat2 = { }; + // Default value case 3 + API_FIELD() float TestFloat3 = {1.0f}; + // Default value case 4 + API_FIELD() float TestFloat4 = float{}; + // Default value case 5 + API_FIELD() float TestFloat5 = float{ }; + // Default value case 6 + API_FIELD() float TestFloat6 = float{1.0f}; + // Default value case 7 + API_FIELD() float TestFloat7 {}; + // Default value case 8 + API_FIELD() float TestFloat8 {1.0f}; + // Default value case 9 + API_FIELD() float TestFloat9 = 1.0f; + // Default value case 10 + API_FIELD() float TestFloat10 = 1.f; + // Default value case 11 + API_FIELD() float TestFloat11 = 1; +}; + // Test interface (name conflict with namespace) API_INTERFACE(Namespace="Foo") class FLAXENGINE_API IFoo { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 408fcb887..91c2edc06 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1080,11 +1080,7 @@ void TrySetupMaterialParameter(MaterialInstance* instance, Span par String GetAdditionalImportPath(const String& autoImportOutput, Array& importedFileNames, const String& name) { String filename = name; - for (int32 j = filename.Length() - 1; j >= 0; j--) - { - if (EditorUtilities::IsInvalidPathChar(filename[j])) - filename[j] = ' '; - } + EditorUtilities::ValidatePathChars(filename); if (importedFileNames.Contains(filename)) { int32 counter = 1; diff --git a/Source/Engine/UI/GUI/Tooltip.cs b/Source/Engine/UI/GUI/Tooltip.cs index a7f80e152..40aae0067 100644 --- a/Source/Engine/UI/GUI/Tooltip.cs +++ b/Source/Engine/UI/GUI/Tooltip.cs @@ -17,6 +17,11 @@ namespace FlaxEngine.GUI private string _currentText; private Window _window; + /// + /// The horizontal alignment of the text. + /// + public TextAlignment HorizontalTextAlignment = TextAlignment.Center; + /// /// Gets or sets the time in seconds that mouse have to be over the target to show the tooltip. /// @@ -236,7 +241,14 @@ namespace FlaxEngine.GUI // Padding for text var textRect = GetClientArea(); - textRect.X += 5; + float textX = HorizontalTextAlignment switch + { + TextAlignment.Near => 15, + TextAlignment.Center => 5, + TextAlignment.Far => -5, + _ => throw new ArgumentOutOfRangeException() + }; + textRect.X += textX; textRect.Width -= 10; // Tooltip text @@ -245,7 +257,7 @@ namespace FlaxEngine.GUI _currentText, textRect, style.Foreground, - TextAlignment.Center, + HorizontalTextAlignment, TextAlignment.Center, TextWrapping.WrapWords ); diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.dll b/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.dll index 7ef160291..e64bb6cd7 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.dll +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4bc89abf5a72c9c3dede29d1cbe5ced923d99e99ba82b76c7b25e940e6f4f25 -size 1111552 +oid sha256:a54bca64e921d32525ed58c52ce802b4ca2c3f8a92c342296da48c55ec1ea58d +size 1112064 diff --git a/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.lib b/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.lib index 3f70d273b..ae9715a8b 100644 --- a/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.lib +++ b/Source/Platforms/Windows/Binaries/ThirdParty/x64/OpenAL32.lib @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7636bd763b099f84d2015fa89635d27d4298b2c8c38ce0335b545ae9e2b4db4 +oid sha256:6a4ac5af61abb19624dc2ae23bc16af09a062655f3eab89fedf6c0ead6cd935a size 37562 diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 06d2b9712..9642c8b02 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -153,6 +153,28 @@ namespace Flax.Build.Bindings case "false": return value; } + // Handle float{_} style type of default values + if (valueType != null && value.StartsWith($"{valueType.Type}") && value.EndsWith("}")) + { + value = value.Replace($"{valueType.Type}", "").Replace("{", "").Replace("}", "").Trim(); + if (string.IsNullOrEmpty(value)) + { + value = $"default({valueType.Type})"; + return value; + } + } + + // Handle C++ bracket default values that are not arrays + if (value.StartsWith("{") && value.EndsWith("}") && valueType != null && !valueType.IsArray && valueType.Type != "Array") + { + value = value.Replace("{", "").Replace("}", "").Trim(); + if (string.IsNullOrEmpty(value)) + { + value = $"default({valueType.Type})"; + return value; + } + } + // Numbers if (float.TryParse(value, out _) || (value[value.Length - 1] == 'f' && float.TryParse(value.Substring(0, value.Length - 1), out _))) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index b2a3604bb..d99fa8b92 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -1352,11 +1352,17 @@ namespace Flax.Build.Bindings desc.Name = ParseName(ref context); // Read ';' or default value or array size or bit-field size - token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.Colon }); + token = context.Tokenizer.ExpectAnyTokens(new[] { TokenType.SemiColon, TokenType.Equal, TokenType.LeftBracket, TokenType.LeftCurlyBrace, TokenType.Colon }); if (token.Type == TokenType.Equal) { context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); } + // Handle ex: API_FIELD() Type FieldName {DefaultValue}; + else if (token.Type == TokenType.LeftCurlyBrace) + { + context.Tokenizer.SkipUntil(TokenType.SemiColon, out desc.DefaultValue, false); + desc.DefaultValue = '{' + desc.DefaultValue; + } else if (token.Type == TokenType.LeftBracket) { // Read the fixed array length diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs index b6b5fa83d..f13449192 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs @@ -77,7 +77,7 @@ namespace Flax.Deps.Dependencies var buildDir = Path.Combine(root, "build-" + architecture.ToString()); var solutionPath = Path.Combine(buildDir, "OpenAL.sln"); - RunCmake(root, platform, architecture, $"-B\"{buildDir}\" -DBUILD_SHARED_LIBS=OFF"); + RunCmake(root, platform, architecture, $"-B\"{buildDir}\" -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS=\"/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR /EHsc\" -DCMAKE_CXX_FLAGS=\"/D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR /EHsc\""); Deploy.VCEnvironment.BuildSolution(solutionPath, configuration, architecture.ToString()); var depsFolder = GetThirdPartyFolder(options, platform, architecture); foreach (var file in binariesToCopy) diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 1830b8efe..88b7a8df5 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -301,7 +301,8 @@ namespace Flax.Build.Projects.VisualStudioCode json.BeginArray("configurations"); { var cppProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.Name || x.Name == solution.Name); - var csharpProject = solution.Projects.FirstOrDefault(x => x.BaseName == solution.MainProject.Targets[0].Modules[0] || x.Name == solution.MainProject.Targets[0].Modules[0]); + var mainProjectModule = solution.MainProject?.Targets.Length != 0 ? solution.MainProject.Targets[0].Modules[0] : null; + var csharpProject = mainProjectModule != null ? solution.Projects.FirstOrDefault(x => x.BaseName == mainProjectModule || x.Name == mainProjectModule) : null; if (cppProject != null) {