UE4:iOS CLLocationManagerを使って、緯度経度の値を取得する
UE4でGPS情報取得
UE4からiOS CLLocationManagerを使って、緯度経度の値を取得してみました。
iOSのObjective-Cのapiを呼び出しているのですが、色々わかったことがあったので記します。
わかったこと
- プラットホームがiOSの場合にのみ、ビルドされるように、iOS特有のコードは、#if PLATFORM_IOS 〜 #endif で囲むこと。
- xcodeのProjectは、UE4側で管理されているので、Objective-Cのクラスを追加する場合もUE4EditorのAdd Code To Projectで親クラスNoneで*.cpp/*.hを追加して、そこにObjective-Cのコードを記述する。既存のクラスの追加は、Add Code To Projectで同じ名前のファイルを追加して、そこにコードをコピペしました。(ちゃんとした方法があるかも)
- plistは、UE4側で管理され自動生成されるので、追加記述する必要がある場合は、UE4EditorのProjectSettings->iOS->ExtraPListData->AdditionalPListDataに記述する。記述は、PlistのXML形式で書く必要があり説明がない。
- framework(CoreLocation.frameworkなど)の追加は、UE4側で生成された*.Build.csで追加指定を記述しないと追加されない。
if (Target.Platform == UnrealTargetPlatform.IOS) { // for GPS PublicFrameworks.Add("CoreLocation"); }
- C++のActorのクラスからObjective-Cのメソッドやクラスは普通に呼び出せた。
- UE4のアクターの実行は、どうもメインスレッドではないので、iOSのapiを利用するクラスは、メインスレッドで生成して実行しないとうまく動かない。
dispatch_async(dispatch_get_main_queue(), ^{ MyLocationManager* myLocMgr=[MyLocationManager sharedInstance]; [myLocMgr start]; });
サンプルコード
UE4にObjective-Cのクラスやapiを組み込む時の参考にソースも一部載せておきます。
- GPS情報を取得する為のObjective-CのMyLocationManagerクラスをGPSActorからメインスレッド上に生成しています。
- GPSActorクラスで、緯度と経度をUTextRenderComponentで表示しています。
- CLLocationManagerを使えるようにするためplistに以下の設定を追加(AdditionalPListDataに記述する。)
<key>NSLocationAlwaysUsageDescription</key>\n<string>ddd</string>\n<key>NSLocationWhenInUseUsageDescription</key>\n<string>xyz</string>\n
- CoreLocation.frameworkを追加するため、プロジェクト名.Build.cs に追加設定を記述
GPSActor.h
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "GameFramework/Actor.h" #include "GPSActor.generated.h" UCLASS() class TESTIOSAPI_API AGPSActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AGPSActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; private: UTextRenderComponent* InfoText; void UpdateInfo(); float WaitTime; int UpdateCnt; };
GPSActor.cpp
// Fill out your copyright notice in the Description page of Project Settings. #include "TestIOSAPI.h" #include "GPSActor.h" #include "MyLocationManager.h" // Sets default values AGPSActor::AGPSActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; InfoText=CreateAbstractDefaultSubobject<UTextRenderComponent>(TEXT("InfoText")); InfoText->SetHorizontalAlignment(EHTA_Center); InfoText->SetWorldSize(150.0f); InfoText->SetTextRenderColor(FColor::Red); InfoText->AttachTo(RootComponent); RootComponent=InfoText; } // Called when the game starts or when spawned void AGPSActor::BeginPlay() { Super::BeginPlay(); #if PLATFORM_IOS //MyLocationManagerは、メインスレッドで動かさないとうまく動かないです。 dispatch_async(dispatch_get_main_queue(), ^{ MyLocationManager* myLocMgr=[MyLocationManager sharedInstance]; [myLocMgr start]; }); #endif WaitTime=0; } // Called every frame void AGPSActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); WaitTime+=DeltaTime; if(WaitTime>3.0){ WaitTime=0.0; UpdateInfo(); } } void AGPSActor::UpdateInfo(){ #if PLATFORM_IOS // Objective-Cのコードは、C++から普通によべるみたい。 float latitude,longitude; MyLocationManager* myLocMgr=[MyLocationManager sharedInstance]; CLLocation *location = [myLocMgr.locationManager location]; CLLocationCoordinate2D coordinate = [location coordinate]; NSLog(@"request ReadTextData (%@ ,alti:%f)%d,%d",[location description],location.altitude,myLocMgr.updateCount,myLocMgr.errorCount); InfoText->SetText(FString::Printf(TEXT("GPSValue(%f,%f)%d,%d,%d"),coordinate.latitude,coordinate.longitude,myLocMgr.updateCount,myLocMgr.errorCount,UpdateCnt)); #endif }
MyLocationManager.h
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #if PLATFORM_IOS #import <Foundation/Foundation.h> #import <CoreLocation/CoreLocation.h> #endif #if PLATFORM_IOS @class MyLocationManager; @interface MyLocationManager : NSObject<CLLocationManagerDelegate> { @private int updateCount_; int errorCount_; CLLocationManager* locationManager_; } @property (readonly) CLLocationManager* locationManager; @property (assign) int updateCount; @property (assign) int errorCount; //位置取得サービスが有効かどうか -(BOOL)serviceEnabled ; //開始 -(void)start; //停止 -(void)stop; //Class Method + (MyLocationManager*)sharedInstance; @end #endif
MyLocationManager.cpp
// Fill out your copyright notice in the Description page of Project Settings. #include "TestIOSAPI.h" #include "MyLocationManager.h" #if PLATFORM_IOS @implementation MyLocationManager @synthesize locationManager=locationManager_; @synthesize updateCount=updateCount_; @synthesize errorCount=errorCount_; //初期化 -(id)init { if(self=[super init]){ // 位置取得関係初期か //NotificationCenterに一時停止時と再開時に位置情報処理の再開停止関数を登録 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; locationManager_=[[CLLocationManager alloc] init]; locationManager_.delegate = self; locationManager_.desiredAccuracy = kCLLocationAccuracyBestForNavigation; locationManager_.activityType = CLActivityTypeFitness; locationManager_.pausesLocationUpdatesAutomatically = YES; locationManager_.distanceFilter = kCLDistanceFilterNone; [self start]; updateCount_++; } return self; } //位置取得サービスが有効かどうか -(BOOL)serviceEnabled { return locationManager_!=nil && [CLLocationManager locationServicesEnabled]; } //アプリが再開するときNotificationCenterからよばれる -(void) applicationDidBecomeActive { [self start]; } //アプリが一時停止するときNotificationCenterからよばれる -(void) applicationWillResignActive { [self stop]; } // 位置情報更新時によばれる。ずっと同じ位置にいるとよばれない - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { if([locations count]>0){ CLLocation* newLocation=[locations objectAtIndex:0]; //緯度・経度を出力 NSLog(@"didUpdateToLocation latitude=%f, longitude=%f", [newLocation coordinate].latitude, [newLocation coordinate].longitude); // [locationManager_ stopUpdatingLocation]; updateCount_++; } } // 測位失敗時や、位置情報の利用をユーザーが「不許可」とした場合などに呼ばれる - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{ NSLog(@"locationManager:didFailWithError"); errorCount_++; } - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status { if (status == kCLAuthorizationStatusNotDetermined) { if([locationManager_ respondsToSelector:@selector(requestAlwaysAuthorization)]) { updateCount_++; [locationManager_ startUpdatingLocation]; [locationManager_ requestAlwaysAuthorization]; } } } -(void)start { if([self serviceEnabled]){ [locationManager_ startUpdatingLocation]; //didChangeAuthorizationStatusでよんでるけど、うまくいかなかったので、ここでもリクエストする。 if([locationManager_ respondsToSelector:@selector(requestAlwaysAuthorization)]) { [locationManager_ requestAlwaysAuthorization]; } } } -(void)stop { if([self serviceEnabled]){ [locationManager_ stopUpdatingLocation]; } } ///========================================= //シングルトン制御用メソッド //========================================= static MyLocationManager* sharedInstance_=nil; + (MyLocationManager*)sharedInstance{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance_=[[MyLocationManager alloc] init]; }); return sharedInstance_; } @end #endif
TestIOSAPI.Build.cs (プロジェクト名.Build.cs)
// Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class TestIOSAPI : ModuleRules { public TestIOSAPI(TargetInfo Target) { PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // if ((Target.Platform == UnrealTargetPlatform.Win32) || (Target.Platform == UnrealTargetPlatform.Win64)) // { // if (UEBuildConfiguration.bCompileSteamOSS == true) // { // DynamicallyLoadedModuleNames.Add("OnlineSubsystemSteam"); // } // } // CoreLocation.frameworkを追加するために追加したコード if (Target.Platform == UnrealTargetPlatform.IOS) { // for GPS PublicFrameworks.Add("CoreLocation"); } //ここまで } }