Recreate the C++ API OAuth sample level

Create a new project

To launch Unreal Engine, open the Epic Games Launcher and click on Unreal Engine > Launch. When you launch Unreal Engine (UE4), the Unreal Project Browser automatically opens.

  1. Under New Project Categories, select Games.

    create-new-project

  2. Select a Blank template.

    select-template

  3. On the Project Settings page, set the project to a C++ project and keep the other default options.

    select-project-settings

  4. At the bottom of the Project Settings page, select where you want to store your project, and give your project a name. Click Create Project to initialize the project.

Install the plugin

  1. Create a Plugins folder in the main project directory.

    create-plugin-folder

  2. Extract the ArcGIS Maps SDK folder from the .zip file and copy it to the new Plugins folder.

    place-folder

Initialize the API

C++ applications need at least one Header file and a Source Code file (.h and .cpp).

You can find a sample header file in:

 
1
Plugins\ArcGISMapsSDK\Source\ArcGISSamples\Public\SampleCppOAuth.h

And a sample C++ file in:

 
1
Plugins\ArcGISMapsSDK\Source\ArcGISSamples\Private\SampleCppOAuth.cpp
  1. To create these scripts in your project, go to the content folder, right click to bring up the context menu, then select the New C++ Class menu item.

  2. This will show the Choose Parent Class wizard. You want your script to attach to an Actor. Select the Actor parent class, then click on the Next button.

    choose-parent-class

  3. Give the Actor script a name and place it wherever you like. It can be set in the host project or in a different module. The Public/Private button will define whether this class will be public or private. You can also define the path to the file. After you fill out the parameters click Create Class.

    name-new-actor

  4. After Unreal Engine compiles the project, you can see a new object in your Content Browser with the name of your class. You can double click on it and Visual Studio (or your default code editor) will automatically open.

Create header file

  1. Review the header file. Add your first include after Actor.h. Keep SampleCppOAuth.generated.h as the last include.

      
    1
    2
    #include "GameFramework/Actor.h"
    #include "SampleCppOAuth.generated.h"

    This is the complete source code for our header file:

                                 
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    
    #include "SampleCppOAuth.generated.h"
    
    class UArcGISRendererComponent;
    
    UCLASS()
    class ASampleCppOAuth : public AActor
    {
           GENERATED_BODY()
    
    public:
        // Sets default values for this actor's properties
        ASampleCppOAuth();
    
    protected:
        // Called when the game starts or when spawned
        virtual void BeginPlay() override;
    
    public:
        // Called every frame
        virtual void Tick(float DeltaTime) override;
    
    private:
        UArcGISRendererComponent* ArcGISRendererComponent;
    };
    

Create C++ file

Now we will write the C++ source code file.

  1. First, include all necessary headers.

                                           
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include "SampleCppOAuth.h"
    
    // Unreal
    
    #include "Engine/SkyLight.h"
    #include "Engine/World.h"
    #include "Kismet/GameplayStatics.h"
    
    // ArcGISMapsSDKLib
    
    #include "ArcGISMapsSDKLib/API/GameEngine/Camera/ArcGISCamera.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Elevation/ArcGISImageElevationSource.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Layers/ArcGISImageLayer.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Map/ArcGISBasemap.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Map/ArcGISMap.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Map/ArcGISMapElevation.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Map/ArcGISMapType.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Security/ArcGISAuthenticationConfiguration.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Security/ArcGISAuthenticationManager.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/Security/ArcGISOAuthAuthenticationConfiguration.h"
    #include "ArcGISMapsSDKLib/API/GameEngine/View/ArcGISRendererView.h"
    #include "ArcGISMapsSDKLib/API/Unreal/Collection.h"
    
    // ArcGISMapsSDK
    
    #include "Components/ArcGISDirectionalLightRepositionComponent.h"
    #include "Components/ArcGISRendererComponent.h"
    #include "Components/ArcGISSkyLightRepositionComponent.h"
    #include "Math/Convert.h"
    #include "Security/AuthenticationChallengeManager.h"
    
    // ArcGISSamples
    #include "SampleDefaultPawn.h"
    
    #if PLATFORM_ANDROID || PLATFORM_IOS
    #include "Security/MobileOAuthAuthenticationChallengeHandler.h"
    #else
    #include "Security/DesktopOAuthAuthenticationChallengeHandler.h"
    #endif
  2. In the constructor, create a new Renderer Component and attach it to your main actor with the following code.

      
    1
    2
    ArcGISRendererComponent = CreateDefaultSubobject<UArcGISRendererComponent>(TEXT("ArcGISRendererComponent"));
    AddOwnedComponent(ArcGISRendererComponent);
    
  3. In this example, your map should be created when the application starts; call the method CreateArcGISMap() in BeginPlay().

         
    1
    2
    3
    4
    5
    void AMyActor::BeginPlay()
    {
        Super::BeginPlay();
        CreateArcGISMap();
    }
    
  4. Create your main function.

        
    1
    2
    3
    4
    void AMyActor::CreateArcGISMap()
    {
    
    }
    
  5. Create the OauthChallengerHandler and set correct definitions for each platform.

         
    1
    2
    3
    4
    5
    #if PLATFORM_ANDROID || PLATFORM_IOS
        auto arcGISOAuthAuthenticationChallengeHandler = ::MakeShared<Esri::ArcGISMapsSDK::Security::FMobileOAuthAuthenticationChallengeHandler>();
    #else
        auto arcGISOAuthAuthenticationChallengeHandler = ::MakeShared<Esri::ArcGISMapsSDK::Security::FDesktopOAuthAuthenticationChallengeHandler>();
    #endif
  6. Add your OAuthAuthenticationChallengeHandler to the AuthenticationChallengeManager. This will set a global handler for the OAuthChallenge.

     
    1
    Esri::ArcGISMapsSDK::Security::AuthenticationChallengeManager::SetOAuthChallengeHandler(arcGISOAuthAuthenticationChallengeHandler);
  7. Clear all previously stored configurations.

     
    1
    Esri::GameEngine::Security::ArcGISAuthenticationManager::GetAuthenticationConfigurations().RemoveAll();
  8. Define a new variable that will contain authentication configuration data to be used for the OAuthChallenge.

     
    1
    Esri::GameEngine::Security::ArcGISOAuthAuthenticationConfiguration authenticationConfiguration;
  9. Initialize the authenticationConfiguration with the values that you have set up in the Details panel, or add it manually.

     
    1
    authenticationConfiguration = Esri::GameEngine::Security::ArcGISOAuthAuthenticationConfiguration(TCHAR_TO_ANSI(*ClientID), "", TCHAR_TO_ANSI(*RedirectURI));

    authenticationConfiguration

  10. Insert your protected service URL and the proper authentication configuration into the global AuthenticationConfigurations.

     
    1
    Esri::GameEngine::Security::ArcGISAuthenticationManager::GetAuthenticationConfigurations().Insert(TCHAR_TO_ANSI(*ServiceURL), authenticationConfiguration);
  11. Create a new layer from the protected service URL and add it to the map.

      
    1
    2
    auto imageLayer = std::make_shared<Esri::GameEngine::Layers::ArcGISImageLayer>(TCHAR_TO_ANSI(*ServiceURL), "MyLayer_1", 1.0f, true, "");
    arcGISMap->GetLayers().Add(*imageLayer);
    

    When the plugin asks for your ServiceURL data, it will display a login window.

    Log in

  12. Set the API key and set the type of map you are going to use (Local or Global) and create the map document.

          
    1
    2
    3
    4
    5
    6
    // API Key
    constexpr auto apiKey = "YOUR_API_KEY";
    
    // Create the map document
    auto mapType = Esri::GameEngine::Map::ArcGISMapType::Global;
    auto arcGISMap = Esri::MakeShared<Esri::GameEngine::Map::ArcGISMap>(mapType);
  13. Add a basemap layer with the service URL and the API key

         
    1
    2
    3
    4
    5
    // Add a basemap
    auto arcGISBasemapLayer = Esri::GameEngine::Layers::ArcGISBasemapLayer( "https://www.arcgis.com/sharing/rest/content/items/716b600dbbac433faa4bec9220c76b3a/data", apiKey);
    
    // Set the basemap
    arcGISMap->SetBasemapLayer(arcGISBasemapLayer);
    
  14. Create a new ArcGISElevationLayer and set your map elevation with it.

       
    1
    2
    3
    // Set the elevation
    auto elevationLayer = Esri::GameEngine::Layers::ArcGISElevationLayer( "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer", "Elevation", apiKey);
    arcGISMap->SetElevation(elevationLayer);
    
  15. To set your camera data, use the ArcGISGlobalCoordinatePosition and a new ArcGISRotation. These objects will set the camera position and orientation in your ArcGIS world. You can identify your camera by a name as well.

         
    1
    2
    3
    4
    5
    Esri::GameEngine::Location::ArcGISRotation orientation(68, 0, 65);
    Esri::GameEngine::Location::ArcGISGlobalCoordinatesPosition position(40.691242, -74.054921, 3000);
    
    // Create a camera
    auto arcGISCamera = Esri::MakeShared<Esri::GameEngine::Camera::ArcGISCamera>("Camera_1", position, orientation);
  16. You will need the ArcGISRendererView. This is the object that will take care of your camera and your map document so you need to set their values. After populating the ArcGISRendererView, assign it to the desired ArcGISRendererComponent. In this case it is the component that you created in the class constructor.

           
    1
    2
    3
    4
    5
    6
    7
    // Create the renderer view and set it the camera and the map
    auto rendererView = Esri::MakeShared<Esri::GameEngine::View::ArcGISRendererView>();
    rendererView->SetMap(arcGISMap);
    rendererView->SetCamera(arcGISCamera);
    
    // Set the renderer view to the renderer component
    ArcGISRendererComponent->RendererView = rendererView;
    
  17. To move around the scene and update the map, create an Unreal Pawn. The plugin contains a custom implementation of an Unreal Pawn named ArcGISPawn that allows you to move with the keyboard and mouse. The ArcGISPawn has an ArcGISCameraComponent attached to it that allows you to assign your renderer view to it. Finally, set the pawn’s location. In this example use the same position and rotation as the arcGISCamera.

            
    1
    2
    3
    4
    5
    6
    7
    8
    // Create our sample pawn
    ASampleDefaultPawn* arcGISPawn = GetWorld()->SpawnActor<ASampleDefaultPawn>();
    
    // Assign Rendererview to the current main camera component
    auto cameraComponent = arcGISPawn->FindComponentByClass<UArcGISCameraComponent>();
    cameraComponent->RendererView = rendererView;
    arcGISPawn->SetActorLocationAndRotation(Convert::ToFVector(rendererView->GetCameraLocalPosition()),
    Convert::ToFQuat(rendererView->GetCameraLocalRotation()));
    
  18. Add the following code to display view state information events in the Output Log:

                              
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
        // Adding callbacks to get state changes from view
        Esri::GameEngine::View::Event::ArcGISRendererViewStateChangedEvent arcGISRendererViewChanged =
            [](Esri::GameEngine::View::State::ArcGISRendererViewState& state) {
                UE_LOG(LogTemp, Warning, TEXT("ArcGISRendererView status changed : %d"), (int)state.GetStatus());
            };
    
        // Adding callbacks to get state changes from view of ArcGISVisualLayer
    
       Esri::GameEngine::View::Event::ArcGISVisualLayerViewStateChangedEvent arcGISVisualLayerViewChanged =
            [](Esri::GameEngine::Layers::Base::ArcGISVisualLayer& layer, Esri::GameEngine::View::State::ArcGISVisualLayerViewState& state) {
                UE_LOG(LogTemp, Warning, TEXT("ArcGISVisualLayer status changed : %s %d"), *(FString)UTF8_TO_TCHAR(layer.GetName().data()),
                       (int)state.GetStatus());
            };
    
        // Adding callbacks to get state changes from view of ArcGISElevationSource
    
        Esri::GameEngine::View::Event::ArcGISElevationSourceViewStateChangedEvent arcGISElevationSourceViewChanged =
            [](Esri::GameEngine::Elevation::Base::ArcGISElevationSource& elevation,
               Esri::GameEngine::View::State::ArcGISElevationSourceViewState& state) {
                UE_LOG(LogTemp, Warning, TEXT("ArcGISElevationSource status changed : %s %d"), *(FString)UTF8_TO_TCHAR(elevation.GetName().data()),
                       (int)state.GetStatus());
            };
    
        rendererView->SetArcGISRendererViewStateChanged(std::move(arcGISRendererViewChanged));
        rendererView->SetArcGISVisualLayerViewStateChanged(std::move(arcGISVisualLayerViewChanged));
        rendererView->SetArcGISElevationSourceViewStateChanged(std::move(arcGISElevationSourceViewChanged));
  19. The ArcGIS Maps SDK plugin uses a custom location system where the camera is always at the world origin to avoid problems related to floating point precision. For this reason there are different components to correct the movement of the light and the sky in order to synchronize with the camera movement. Add an ArcGISDirectionalLightRepositionComponent and an ArcGISSkyLightRepositionComponent

                      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Add reposition components to the directional and sky lights
    auto directionalLightActor = UGameplayStatics::GetActorOfClass(GetWorld(), ADirectionalLight::StaticClass());
    auto skyLightActor = UGameplayStatics::GetActorOfClass(GetWorld(), ASkyLight::StaticClass());
    
    if (directionalLightActor)
    {
        auto directionalLightReposition = NewObject<UArcGISDirectionalLightRepositionComponent>(
        directionalLightActor, UArcGISDirectionalLightRepositionComponent::StaticClass(), NAME_None, RF_Transient);
        directionalLightReposition->RendererView = rendererView;
        directionalLightReposition->RegisterComponent();
    }
    
    if (skyLightActor)
    {
        auto lightReposition = NewObject<UArcGISSkyLightRepositionComponent>(skyLightActor, UArcGISSkyLightRepositionComponent::StaticClass(), NAME_None, RF_Transient);
        lightReposition->RendererView = rendererView;
        lightReposition->RegisterComponent();
    }
    
  20. The actor is complete and ready to work. The final step is to go to the Content Browser, find your custom actor then drag and drop it into the Viewport. The Actor will be added to the world outliner.

Click on Play to run the app and see your map.

Use the WASD keys to move left, right, forward, and backward. Move the mouse to move the camera's perspective.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.