2025-07-08 10:46:31 +00:00
using System ;
using System.Linq ;
using UnityEditor ;
using UnityEditor.SceneManagement ;
using UnityEditor.UIElements ;
using UnityEngine ;
using UnityEngine.UIElements ;
using UnityEditor.Compilation ;
using UnityEditor.Scripting.ScriptCompilation ;
namespace Pathfinding {
internal class WelcomeScreen : UnityEditor . EditorWindow {
[SerializeField]
private VisualTreeAsset m_VisualTreeAsset = default ;
public bool isImportingSamples ;
private bool askedAboutQuitting ;
[InitializeOnLoadMethod]
public static void TryCreate ( ) {
if ( ! PathfindingEditorSettings . instance . hasShownWelcomeScreen ) {
// Wait a bit before showing the window to avoid stuttering
// as all the other windows in Unity load.
// This makes the animation smoother.
var delay = 0.5f ;
var t0 = Time . realtimeSinceStartup ;
EditorApplication . CallbackFunction create = null ;
create = ( ) = > {
if ( Time . realtimeSinceStartup - t0 > delay ) {
EditorApplication . update - = create ;
PathfindingEditorSettings . instance . hasShownWelcomeScreen = true ;
PathfindingEditorSettings . instance . Save ( ) ;
Create ( ) ;
}
} ;
EditorApplication . update + = create ;
}
}
public static void Create ( ) {
var window = GetWindow < WelcomeScreen > (
true ,
"A* Pathfinding Project" ,
true
) ;
window . minSize = window . maxSize = new Vector2 ( 400 , 400 * 1.618f ) ;
window . ShowUtility ( ) ;
}
public void CreateGUI ( ) {
VisualElement root = rootVisualElement ;
VisualElement labelFromUXML = m_VisualTreeAsset . Instantiate ( ) ;
root . Add ( labelFromUXML ) ;
var sampleButton = root . Query < Button > ( "importSamples" ) . First ( ) ;
var samplesImportedIndicator = root . Query ( "samplesImported" ) . First ( ) ;
samplesImportedIndicator . visible = GetSamples ( out var sample ) & & sample . isImported ;
sampleButton . clicked + = ImportSamples ;
root . Query < Button > ( "documentation" ) . First ( ) . clicked + = OpenDocumentation ;
root . Query < Button > ( "getStarted" ) . First ( ) . clicked + = OpenGetStarted ;
root . Query < Button > ( "changelog" ) . First ( ) . clicked + = OpenChangelog ;
root . Query < Label > ( "version" ) . First ( ) . text = "Version " + AstarPath . Version . ToString ( ) ;
AnimateLogo ( root . Query ( "logo" ) . First ( ) ) ;
}
static string FirstSceneToLoad = "Recast3D" ;
public void OnEnable ( ) {
if ( isImportingSamples ) {
// This will be after the domain reload that happened after the samples were imported
OnPostImportedSamples ( ) ;
}
}
public void OnPostImportedSamples ( ) {
isImportingSamples = false ;
// Load the example scene
var sample = UnityEditor . PackageManager . UI . Sample . FindByPackage ( "com.arongranberg.astar" , "" ) . First ( ) ;
if ( sample . isImported ) {
var relativePath = "Assets/" + System . IO . Path . GetRelativePath ( Application . dataPath , sample . importPath ) ;
Debug . Log ( relativePath ) ;
var scenes = AssetDatabase . FindAssets ( "t:scene" , new string [ ] { relativePath } ) ;
string bestScene = null ;
for ( int i = 0 ; i < scenes . Length ; i + + ) {
scenes [ i ] = AssetDatabase . GUIDToAssetPath ( scenes [ i ] ) ;
if ( scenes [ i ] . Contains ( FirstSceneToLoad ) ) {
bestScene = scenes [ i ] ;
}
}
if ( bestScene = = null ) bestScene = scenes . FirstOrDefault ( ) ;
if ( bestScene ! = null ) {
if ( EditorSceneManager . SaveCurrentModifiedScenesIfUserWantsTo ( ) ) {
EditorSceneManager . OpenScene ( bestScene ) ;
}
}
}
}
void AnimateLogo ( VisualElement logo ) {
var t0 = Time . realtimeSinceStartup ;
EditorApplication . CallbackFunction introAnimation = null ;
int ticks = 0 ;
introAnimation = ( ) = > {
var t = Time . realtimeSinceStartup - t0 ;
if ( ticks = = 1 ) {
logo . RemoveFromClassList ( "largeIconEntry" ) ;
}
Repaint ( ) ;
ticks + + ;
if ( ticks > 1 & & t > 5 ) {
EditorApplication . update - = introAnimation ;
}
} ;
EditorApplication . update + = introAnimation ;
}
bool GetSamples ( out UnityEditor . PackageManager . UI . Sample sample ) {
sample = default ;
System . Collections . Generic . IEnumerable < UnityEditor . PackageManager . UI . Sample > samples ;
try {
samples = UnityEditor . PackageManager . UI . Sample . FindByPackage ( "com.arongranberg.astar" , "" ) ;
if ( samples = = null ) {
return false ;
}
} catch ( System . NullReferenceException ) {
// The package manager api is buggy, and will throw an exception if the package is not installed
// via the package manager.
// In any case, we can't import samples if the package is not installed via the package manager.
return false ;
}
var samplesArr = samples . ToArray ( ) ;
if ( samplesArr . Length ! = 1 ) {
Debug . LogError ( "Expected exactly 1 sample. Found " + samplesArr . Length + ". This should not happen" ) ;
return false ;
}
sample = samplesArr [ 0 ] ;
return true ;
}
private void ImportSamples ( ) {
if ( ! GetSamples ( out var sample ) ) {
Debug . LogError ( "The A* Pathfinding Project is not installed via the Unity package manager. Cannot import samples." ) ;
return ;
}
if ( sample . isImported ) {
// Show dialog box
if ( ! EditorUtility . DisplayDialog ( "Import samples" , "Samples are already imported. Do you want to reimport them?" , "Reimport" , "Cancel" ) ) {
return ;
}
}
isImportingSamples = true ;
CompilationPipeline . assemblyCompilationFinished + = OnAssemblyCompilationFinished ;
if ( ! sample . Import ( UnityEditor . PackageManager . UI . Sample . ImportOptions . OverridePreviousImports ) ) {
Debug . LogError ( "Failed to import samples" ) ;
return ;
}
OnPostImportedSamples ( ) ;
}
void OnAssemblyCompilationFinished ( string assembly , CompilerMessage [ ] message ) {
for ( int i = 0 ; i < message . Length ; i + + ) {
// E.g.
// error CS0006: Metadata file 'Assets/AstarPathfindingProject/Plugins/Clipper/Pathfinding.Clipper2Lib.dll' could not be found
// error CS0006: Metadata file 'Assets/AstarPathfindingProject/Plugins/DotNetZip/Pathfinding.Ionic.Zip.Reduced.dll' could not be found
// I believe this can happen if the user previously has had the package imported into the Assets folder (e.g. version 4),
// and then it is imported via the package manager, and the samples imported.
// Unity seems to miss that the dll files now have new locations, and gets confused.
if ( message [ i ] . type = = CompilerMessageType . Error & & message [ i ] . message . Contains ( "CS0006" ) ) {
Debug . LogError ( "Compilation failed due to a Unity bug. Asking user to restart Unity." ) ;
if ( ! askedAboutQuitting ) {
if ( EditorUtility . DisplayDialog ( "Restart Unity" , "Your version of Unity has a bug that, unfortunately, requires the editor to be restarted, after importing the samples." , "Quit Unity" , "Cancel" ) ) {
askedAboutQuitting = true ;
EditorApplication . update + = ( ) = > {
if ( EditorSceneManager . SaveCurrentModifiedScenesIfUserWantsTo ( ) ) {
EditorApplication . Exit ( 0 ) ;
}
} ;
}
}
}
}
}
private void OpenDocumentation ( ) {
Application . OpenURL ( AstarUpdateChecker . GetURL ( "documentation" ) ) ;
}
private void OpenGetStarted ( ) {
Application . OpenURL ( AstarUpdateChecker . GetURL ( "documentation" ) + "getstarted.html" ) ;
}
private void OpenChangelog ( ) {
Application . OpenURL ( AstarUpdateChecker . GetURL ( "changelog" ) ) ;
}
}
}