ヒマがあるならゲームつくってね

こんなとこ見てないでゲームをつくろう

UE4:iOS CLLocationManagerを使って、緯度経度の値を取得する

UE4でGPS情報取得

UE4からiOS CLLocationManagerを使って、緯度経度の値を取得してみました。
iOSObjective-Capiを呼び出しているのですが、色々わかったことがあったので記します。


わかったこと

  • プラットホームが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形式で書く必要があり説明がない。

f:id:siu3:20150517195437p:plain

  • framework(CoreLocation.frameworkなど)の追加は、UE4側で生成された*.Build.csで追加指定を記述しないと追加されない。
  if (Target.Platform == UnrealTargetPlatform.IOS)

  {
      // for GPS
        PublicFrameworks.Add("CoreLocation");

  }
  • C++のActorのクラスからObjective-Cメソッドやクラスは普通に呼び出せた。
  • UE4のアクターの実行は、どうもメインスレッドではないので、iOSapiを利用するクラスは、メインスレッドで生成して実行しないとうまく動かない。
  dispatch_async(dispatch_get_main_queue(), ^{
    MyLocationManager*  myLocMgr=[MyLocationManager sharedInstance];
    [myLocMgr start];
  });
  • iOSに特化したコード(クラス)は、UE4とは別のテスト用のプログラムを用いて、動作が正常に動くことを確認した後に、組み込まないとデバッグはやりずらい。

サンプルコード

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");

  }
//ここまで
	}
}

f:id:siu3:20150517192233p:plain