Merge pull request #42582 from naithar/feature/3.2-arc-refactor

[3.2] [iOS] Port 4.0 changes
This commit is contained in:
Rémi Verschelde 2020-10-07 21:17:01 +02:00 committed by GitHub
commit 4040cd350d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2214 additions and 1652 deletions

View File

@ -44,6 +44,12 @@
// forward declaration for some needed objects
class ARKitShader;
#ifdef __OBJC__
typedef NSObject GodotARAnchor;
#else
typedef void GodotARAnchor;
#endif
class ARKitInterface : public ARVRInterface {
GDCLASS(ARKitInterface, ARVRInterface);
@ -115,8 +121,8 @@ public:
virtual void process();
// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
void _add_or_update_anchor(void *p_anchor);
void _remove_anchor(void *p_anchor);
void _add_or_update_anchor(GodotARAnchor *p_anchor);
void _remove_anchor(GodotARAnchor *p_anchor);
ARKitInterface();
~ARKitInterface();

View File

@ -305,10 +305,8 @@ void ARKitInterface::uninitialize() {
remove_all_anchors();
if (@available(iOS 11.0, *)) {
[ar_session release];
ar_session = NULL;
ar_session = nil;
}
[ar_delegate release];
ar_delegate = NULL;
initialized = false;
@ -469,10 +467,8 @@ void ARKitInterface::process() {
if (@available(iOS 13, *)) {
orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
orientation = [[UIApplication sharedApplication] statusBarOrientation];
#endif
}
// Grab our camera image for our backbuffer
@ -686,7 +682,7 @@ void ARKitInterface::process() {
}
}
void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
void ARKitInterface::_add_or_update_anchor(GodotARAnchor *p_anchor) {
_THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {
@ -748,7 +744,7 @@ void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
}
}
void ARKitInterface::_remove_anchor(void *p_anchor) {
void ARKitInterface::_remove_anchor(GodotARAnchor *p_anchor) {
_THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {

View File

@ -124,18 +124,12 @@
if (output) {
[self removeOutput:output];
[output setSampleBufferDelegate:nil queue:NULL];
[output release];
output = nil;
}
[self commitConfiguration];
}
- (void)dealloc {
// bye bye
[super dealloc];
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
// This gets called every time our camera has a new image for us to process.
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
@ -158,7 +152,14 @@
} else if (dataCbCr == NULL) {
print_line("Couldn't access CbCr pixel buffer data");
} else {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
if (@available(iOS 13, *)) {
orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
} else {
orientation = [[UIApplication sharedApplication] statusBarOrientation];
}
Ref<Image> img[2];
{
@ -263,7 +264,6 @@ CameraFeedIOS::CameraFeedIOS() {
void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
device = p_device;
[device retain];
// get some info
NSString *device_name = p_device.localizedName;
@ -277,15 +277,13 @@ void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
};
CameraFeedIOS::~CameraFeedIOS() {
if (capture_session != NULL) {
[capture_session release];
capture_session = NULL;
};
if (capture_session) {
capture_session = nil;
}
if (device != NULL) {
[device release];
device = NULL;
};
if (device) {
device = nil;
}
};
bool CameraFeedIOS::activate_feed() {
@ -303,9 +301,8 @@ void CameraFeedIOS::deactivate_feed() {
// end camera capture if we have one
if (capture_session) {
[capture_session cleanup];
[capture_session release];
capture_session = NULL;
};
capture_session = nil;
}
};
//////////////////////////////////////////////////////////////////////////
@ -338,8 +335,6 @@ void CameraFeedIOS::deactivate_feed() {
// remove notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
[super dealloc];
}
@end
@ -442,5 +437,5 @@ CameraIOS::CameraIOS() {
};
CameraIOS::~CameraIOS() {
[device_notifications release];
device_notifications = nil;
};

View File

@ -131,8 +131,7 @@ GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *
NSTimeZone *tz = nil;
if (p_name) {
NSString *n = [[NSString alloc] initWithUTF8String:p_name];
tz = [[[NSTimeZone alloc] initWithName:n] autorelease];
[n release];
tz = [[NSTimeZone alloc] initWithName:n];
} else {
tz = [NSTimeZone localTimeZone];
}

View File

@ -3,10 +3,9 @@
Import("env")
iphone_lib = [
"godot_iphone.cpp",
"os_iphone.mm",
"semaphore_iphone.cpp",
"godot_view.mm",
"godot_iphone.mm",
"os_iphone.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
@ -14,7 +13,13 @@ iphone_lib = [
"in_app_store.mm",
"icloud.mm",
"ios.mm",
"joypad_iphone.mm",
"godot_view.mm",
"display_layer.mm",
"godot_view_renderer.mm",
"godot_view_gesture_recognizer.mm",
"device_metrics.m",
"native_video_view.m",
]
env_ios = env.Clone()

View File

@ -28,19 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_view.h"
#import "view_controller.h"
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
@class ViewController;
@interface AppDelegate : NSObject <UIApplicationDelegate, GodotViewDelegate> {
//@property (strong, nonatomic) UIWindow *window;
ViewController *view_controller;
bool is_focus_out;
};
@interface AppDelegate : NSObject <UIApplicationDelegate>
@property(strong, class, readonly, nonatomic) ViewController *viewController;
@property(strong, nonatomic) UIWindow *window;
@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end

View File

@ -35,52 +35,19 @@
#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
#import "view_controller.h"
#import "GameController/GameController.h"
#import <AudioToolbox/AudioServices.h>
#define kFilteringFactor 0.1
#define kRenderingFrequency 60
#define kAccelerometerFrequency 100.0 // Hz
Error _shell_open(String);
void _set_keep_screen_on(bool p_enabled);
Error _shell_open(String p_uri) {
NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
NSURL *url = [NSURL URLWithString:urlPath];
[urlPath release];
if (![[UIApplication sharedApplication] canOpenURL:url]) {
[url release];
return ERR_CANT_OPEN;
}
printf("opening url %ls\n", p_uri.c_str());
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
};
void _set_keep_screen_on(bool p_enabled) {
[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
};
void _vibrate() {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
};
@implementation AppDelegate
@synthesize window;
extern int gargc;
extern char **gargv;
extern int iphone_main(int, int, int, char **, String);
extern int iphone_main(int, char **, String);
extern void iphone_finish();
CMMotionManager *motionManager;
bool motionInitialised;
@implementation AppDelegate
static ViewController *mainViewController = nil;
@ -88,522 +55,34 @@ static ViewController *mainViewController = nil;
return mainViewController;
}
NSMutableDictionary *ios_joysticks = nil;
NSMutableArray *pending_ios_joysticks = nil;
- (GCControllerPlayerIndex)getFreePlayerIndex {
bool have_player_1 = false;
bool have_player_2 = false;
bool have_player_3 = false;
bool have_player_4 = false;
if (ios_joysticks == nil) {
NSArray *keys = [ios_joysticks allKeys];
for (NSNumber *key in keys) {
GCController *controller = [ios_joysticks objectForKey:key];
if (controller.playerIndex == GCControllerPlayerIndex1) {
have_player_1 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex2) {
have_player_2 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex3) {
have_player_3 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex4) {
have_player_4 = true;
};
};
};
if (!have_player_1) {
return GCControllerPlayerIndex1;
} else if (!have_player_2) {
return GCControllerPlayerIndex2;
} else if (!have_player_3) {
return GCControllerPlayerIndex3;
} else if (!have_player_4) {
return GCControllerPlayerIndex4;
} else {
return GCControllerPlayerIndexUnset;
};
};
void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
// get a new id for our controller
int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
if (joy_id != -1) {
// assign our player index
if (controller.playerIndex == GCControllerPlayerIndexUnset) {
controller.playerIndex = [delegate getFreePlayerIndex];
};
// tell Godot about our new controller
OSIPhone::get_singleton()->joy_connection_changed(
joy_id, true, [controller.vendorName UTF8String]);
// add it to our dictionary, this will retain our controllers
[ios_joysticks setObject:controller
forKey:[NSNumber numberWithInt:joy_id]];
// set our input handler
[delegate setControllerInputHandler:controller];
} else {
printf("Couldn't retrieve new joy id\n");
};
}
static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
if (!*is_focus_out) {
*is_focus_out = true;
if (OS::get_singleton()->get_main_loop())
OS::get_singleton()->get_main_loop()->notification(
MainLoop::NOTIFICATION_WM_FOCUS_OUT);
[view_controller.godotView stopAnimation];
if (OS::get_singleton()->native_video_is_playing()) {
OSIPhone::get_singleton()->native_video_focus_out();
}
AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
if (audio)
audio->stop();
}
}
static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
if (*is_focus_out) {
*is_focus_out = false;
if (OS::get_singleton()->get_main_loop())
OS::get_singleton()->get_main_loop()->notification(
MainLoop::NOTIFICATION_WM_FOCUS_IN);
[view_controller.godotView startAnimation];
if (OSIPhone::get_singleton()->native_video_is_playing()) {
OSIPhone::get_singleton()->native_video_unpause();
}
AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
if (audio)
audio->start();
}
}
- (void)controllerWasConnected:(NSNotification *)notification {
// create our dictionary if we don't have one yet
if (ios_joysticks == nil) {
ios_joysticks = [[NSMutableDictionary alloc] init];
};
// get our controller
GCController *controller = (GCController *)notification.object;
if (controller == nil) {
printf("Couldn't retrieve new controller\n");
} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
printf("Controller is already registered\n");
} else if (frame_count > 1) {
_ios_add_joystick(controller, self);
} else {
if (pending_ios_joysticks == nil)
pending_ios_joysticks = [[NSMutableArray alloc] init];
[pending_ios_joysticks addObject:controller];
};
};
- (void)controllerWasDisconnected:(NSNotification *)notification {
if (ios_joysticks != nil) {
// find our joystick, there should be only one in our dictionary
GCController *controller = (GCController *)notification.object;
NSArray *keys = [ios_joysticks allKeysForObject:controller];
for (NSNumber *key in keys) {
// tell Godot this joystick is no longer there
int joy_id = [key intValue];
OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
// and remove it from our dictionary
[ios_joysticks removeObjectForKey:key];
};
};
};
- (int)getJoyIdForController:(GCController *)controller {
if (ios_joysticks != nil) {
// find our joystick, there should be only one in our dictionary
NSArray *keys = [ios_joysticks allKeysForObject:controller];
for (NSNumber *key in keys) {
int joy_id = [key intValue];
return joy_id;
};
};
return -1;
};
- (void)setControllerInputHandler:(GCController *)controller {
// Hook in the callback handler for the correct gamepad profile.
// This is a bit of a weird design choice on Apples part.
// You need to select the most capable gamepad profile for the
// gamepad attached.
if (controller.extendedGamepad != nil) {
// The extended gamepad profile has all the input you could possibly find on
// a gamepad but will only be active if your gamepad actually has all of
// these...
controller.extendedGamepad.valueChangedHandler = ^(
GCExtendedGamepad *gamepad, GCControllerElement *element) {
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.leftTrigger) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
gamepad.leftTrigger.isPressed);
} else if (element == gamepad.rightTrigger) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
gamepad.rightTrigger.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
InputDefault::JoyAxis jx;
jx.min = -1;
if (element == gamepad.leftThumbstick) {
jx.value = gamepad.leftThumbstick.xAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
jx.value = -gamepad.leftThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
} else if (element == gamepad.rightThumbstick) {
jx.value = gamepad.rightThumbstick.xAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
jx.value = -gamepad.rightThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
} else if (element == gamepad.leftTrigger) {
jx.value = gamepad.leftTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
} else if (element == gamepad.rightTrigger) {
jx.value = gamepad.rightTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
};
};
} else if (controller.microGamepad != nil) {
// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
controller.microGamepad.valueChangedHandler =
^(GCMicroGamepad *gamepad, GCControllerElement *element) {
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
gamepad.buttonX.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
};
}
///@TODO need to add support for controller.motion which gives us access to
/// the orientation of the device (if supported)
///@TODO need to add support for controllerPausedHandler which should be a
/// toggle
};
- (void)initGameControllers {
// get told when controllers connect, this will be called right away for
// already connected controllers
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasConnected:)
name:GCControllerDidConnectNotification
object:nil];
// get told when controllers disconnect
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasDisconnected:)
name:GCControllerDidDisconnectNotification
object:nil];
};
- (void)deinitGameControllers {
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:GCControllerDidConnectNotification
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:GCControllerDidDisconnectNotification
object:nil];
if (ios_joysticks != nil) {
[ios_joysticks dealloc];
ios_joysticks = nil;
};
if (pending_ios_joysticks != nil) {
[pending_ios_joysticks dealloc];
pending_ios_joysticks = nil;
};
};
OS::VideoMode _get_video_mode() {
int backingWidth;
int backingHeight;
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
OS::VideoMode vm;
vm.fullscreen = true;
vm.width = backingWidth;
vm.height = backingHeight;
vm.resizable = false;
return vm;
};
static int frame_count = 0;
- (void)drawView:(GodotView *)view {
switch (frame_count) {
case 0: {
OS::get_singleton()->set_video_mode(_get_video_mode());
if (!OS::get_singleton()) {
exit(0);
};
++frame_count;
}; break;
case 1: {
Main::setup2();
++frame_count;
if (pending_ios_joysticks != nil) {
for (GCController *controller in pending_ios_joysticks) {
_ios_add_joystick(controller, self);
}
[pending_ios_joysticks dealloc];
pending_ios_joysticks = nil;
}
// this might be necessary before here
NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
for (NSString *key in dict) {
NSObject *value = [dict objectForKey:key];
String ukey = String::utf8([key UTF8String]);
// we need a NSObject to Variant conversor
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
String uval = String::utf8([str UTF8String]);
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *n = (NSNumber *)value;
double dval = [n doubleValue];
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
};
// do stuff
}
}; break;
case 2: {
Main::start();
++frame_count;
}; break; // no fallthrough
default: {
if (OSIPhone::get_singleton()) {
// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
// accel[2]);
if (motionInitialised) {
// Just using polling approach for now, we can set this up so it sends
// data to us in intervals, might be better. See Apple reference pages
// for more details:
// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
// Apple splits our accelerometer date into a gravity and user movement
// component. We add them back together
CMAcceleration gravity = motionManager.deviceMotion.gravity;
CMAcceleration acceleration =
motionManager.deviceMotion.userAcceleration;
///@TODO We don't seem to be getting data here, is my device broken or
/// is this code incorrect?
CMMagneticField magnetic =
motionManager.deviceMotion.magneticField.field;
///@TODO we can access rotationRate as a CMRotationRate variable
///(processed date) or CMGyroData (raw data), have to see what works
/// best
CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
// Adjust for screen orientation.
// [[UIDevice currentDevice] orientation] changes even if we've fixed
// our orientation which is not a good thing when you're trying to get
// your user to move the screen in all directions and want consistent
// output
///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
/// is a bit of a hack. Godot obviously knows the orientation so maybe
/// we
// can use that instead? (note that left and right seem swapped)
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationLandscapeLeft: {
OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
gravity.z);
OSIPhone::get_singleton()->update_accelerometer(
-(acceleration.y + gravity.y), (acceleration.x + gravity.x),
acceleration.z + gravity.z);
OSIPhone::get_singleton()->update_magnetometer(
-magnetic.y, magnetic.x, magnetic.z);
OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
rotation.z);
}; break;
case UIInterfaceOrientationLandscapeRight: {
OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
gravity.z);
OSIPhone::get_singleton()->update_accelerometer(
(acceleration.y + gravity.y), -(acceleration.x + gravity.x),
acceleration.z + gravity.z);
OSIPhone::get_singleton()->update_magnetometer(
magnetic.y, -magnetic.x, magnetic.z);
OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
rotation.z);
}; break;
case UIInterfaceOrientationPortraitUpsideDown: {
OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
gravity.z);
OSIPhone::get_singleton()->update_accelerometer(
-(acceleration.x + gravity.x), (acceleration.y + gravity.y),
acceleration.z + gravity.z);
OSIPhone::get_singleton()->update_magnetometer(
-magnetic.x, magnetic.y, magnetic.z);
OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
rotation.z);
}; break;
default: { // assume portrait
OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
gravity.z);
OSIPhone::get_singleton()->update_accelerometer(
acceleration.x + gravity.x, acceleration.y + gravity.y,
acceleration.z + gravity.z);
OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
magnetic.z);
OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
rotation.z);
}; break;
};
}
OSIPhone::get_singleton()->iterate();
};
}; break;
};
};
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(
MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
}
};
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CGRect rect = [[UIScreen mainScreen] bounds];
is_focus_out = false;
// disable idle timer
// application.idleTimerDisabled = YES;
// Create a full-screen window
window = [[UIWindow alloc] initWithFrame:rect];
OS::VideoMode vm = _get_video_mode();
CGRect windowBounds = [[UIScreen mainScreen] bounds];
self.window = [[UIWindow alloc] initWithFrame:windowBounds];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
return FALSE;
};
}
// WARNING: We must *always* create the GodotView after we have constructed the
// OS with iphone_main. This allows the GodotView to access project settings so
// it can properly initialize the OpenGL context
GodotView *glView = [[GodotView alloc] initWithFrame:rect];
glView.delegate = self;
view_controller = [[ViewController alloc] init];
view_controller.view = glView;
window.rootViewController = view_controller;
ViewController *viewController = [[ViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
glView.useCADisplayLink =
bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
printf("cadisaplylink: %d", glView.useCADisplayLink);
glView.animationInterval = 1.0 / kRenderingFrequency;
[glView startAnimation];
self.window.rootViewController = viewController;
// Show the window
[window makeKeyAndVisible];
// Configure and start accelerometer
if (!motionInitialised) {
motionManager = [[CMMotionManager alloc] init];
if (motionManager.deviceMotionAvailable) {
motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:
CMAttitudeReferenceFrameXMagneticNorthZVertical];
motionInitialised = YES;
};
};
[self initGameControllers];
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
@ -611,42 +90,39 @@ static int frame_count = 0;
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
// OSIPhone::screen_width = rect.size.width - rect.origin.x;
// OSIPhone::screen_height = rect.size.height - rect.origin.y;
mainViewController = view_controller;
mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
OSIPhone::get_singleton()->set_keep_screen_on(keep_screen_on);
return TRUE;
};
}
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
on_focus_out(view_controller, &is_focus_out);
OSIPhone::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
on_focus_in(view_controller, &is_focus_out);
OSIPhone::get_singleton()->on_focus_in();
}
}
};
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(
MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
[self deinitGameControllers];
if (motionInitialised) {
///@TODO is this the right place to clean this up?
[motionManager stopDeviceMotionUpdates];
[motionManager release];
motionManager = nil;
motionInitialised = NO;
};
iphone_finish();
};
}
// When application goes to background (e.g. user switches to another app or presses Home),
// then applicationWillResignActive -> applicationDidEnterBackground are called.
@ -659,16 +135,15 @@ static int frame_count = 0;
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
on_focus_out(view_controller, &is_focus_out);
OSIPhone::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
on_focus_in(view_controller, &is_focus_out);
OSIPhone::get_singleton()->on_focus_in();
}
- (void)dealloc {
[window release];
[super dealloc];
self.window = nil;
}
@end

View File

@ -115,18 +115,18 @@ def configure(env):
CCFLAGS=(
"-arch "
+ arch_flag
+ " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+ " -fobjc-arc -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
).split()
)
elif env["arch"] == "arm":
detect_darwin_sdk_path("iphone", env)
env.Append(
CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
CCFLAGS='-fobjc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
)
elif env["arch"] == "arm64":
detect_darwin_sdk_path("iphone", env)
env.Append(
CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
CCFLAGS="-fobjc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
)
env.Append(CPPDEFINES=["NEED_LONG_INT"])
env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@ -138,6 +138,9 @@ def configure(env):
else:
env.Append(CCFLAGS=["-fno-exceptions"])
# Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
## Link flags
if env["arch"] == "x86" or env["arch"] == "x86_64":

View File

@ -0,0 +1,37 @@
/*************************************************************************/
/* device_metrics.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <Foundation/Foundation.h>
@interface GodotDeviceMetrics : NSObject
@property(nonatomic, class, readonly, strong) NSDictionary<NSArray *, NSNumber *> *dpiList;
@end

View File

@ -0,0 +1,152 @@
/*************************************************************************/
/* device_metrics.m */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "device_metrics.h"
@implementation GodotDeviceMetrics
+ (NSDictionary *)dpiList {
return @{
@[
@"iPad1,1",
@"iPad2,1",
@"iPad2,2",
@"iPad2,3",
@"iPad2,4",
] : @132,
@[
@"iPhone1,1",
@"iPhone1,2",
@"iPhone2,1",
@"iPad2,5",
@"iPad2,6",
@"iPad2,7",
@"iPod1,1",
@"iPod2,1",
@"iPod3,1",
] : @163,
@[
@"iPad3,1",
@"iPad3,2",
@"iPad3,3",
@"iPad3,4",
@"iPad3,5",
@"iPad3,6",
@"iPad4,1",
@"iPad4,2",
@"iPad4,3",
@"iPad5,3",
@"iPad5,4",
@"iPad6,3",
@"iPad6,4",
@"iPad6,7",
@"iPad6,8",
@"iPad6,11",
@"iPad6,12",
@"iPad7,1",
@"iPad7,2",
@"iPad7,3",
@"iPad7,4",
@"iPad7,5",
@"iPad7,6",
@"iPad7,11",
@"iPad7,12",
@"iPad8,1",
@"iPad8,2",
@"iPad8,3",
@"iPad8,4",
@"iPad8,5",
@"iPad8,6",
@"iPad8,7",
@"iPad8,8",
@"iPad8,9",
@"iPad8,10",
@"iPad8,11",
@"iPad8,12",
@"iPad11,3",
@"iPad11,4",
] : @264,
@[
@"iPhone3,1",
@"iPhone3,2",
@"iPhone3,3",
@"iPhone4,1",
@"iPhone5,1",
@"iPhone5,2",
@"iPhone5,3",
@"iPhone5,4",
@"iPhone6,1",
@"iPhone6,2",
@"iPhone7,2",
@"iPhone8,1",
@"iPhone8,4",
@"iPhone9,1",
@"iPhone9,3",
@"iPhone10,1",
@"iPhone10,4",
@"iPhone11,8",
@"iPhone12,1",
@"iPhone12,8",
@"iPad4,4",
@"iPad4,5",
@"iPad4,6",
@"iPad4,7",
@"iPad4,8",
@"iPad4,9",
@"iPad5,1",
@"iPad5,2",
@"iPad11,1",
@"iPad11,2",
@"iPod4,1",
@"iPod5,1",
@"iPod7,1",
@"iPod9,1",
] : @326,
@[
@"iPhone7,1",
@"iPhone8,2",
@"iPhone9,2",
@"iPhone9,4",
@"iPhone10,2",
@"iPhone10,5",
] : @401,
@[
@"iPhone10,3",
@"iPhone10,6",
@"iPhone11,2",
@"iPhone11,4",
@"iPhone11,6",
@"iPhone12,3",
@"iPhone12,5",
] : @458,
};
}
@end

View File

@ -0,0 +1,45 @@
/*************************************************************************/
/* display_layer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <OpenGLES/EAGLDrawable.h>
#import <QuartzCore/QuartzCore.h>
@protocol DisplayLayer <NSObject>
- (void)startRenderDisplayLayer;
- (void)stopRenderDisplayLayer;
- (void)initializeDisplayLayer;
- (void)layoutDisplayLayer;
@end
@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
@end

View File

@ -0,0 +1,189 @@
/*************************************************************************/
/* display_layer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "display_layer.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "main/main.h"
#include "os_iphone.h"
#include "servers/audio_server.h"
#import <AudioToolbox/AudioServices.h>
#import <GameController/GameController.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
int gl_view_base_fb;
bool gles3_available = true;
@implementation GodotOpenGLLayer {
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
EAGLContext *context;
GLuint viewRenderbuffer, viewFramebuffer;
GLuint depthRenderbuffer;
}
- (void)initializeDisplayLayer {
// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
self.opaque = YES;
self.drawableProperties = [NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat,
nil];
bool fallback_gl2 = false;
// Create a GL ES 3 context based on the gl driver from project settings
if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3") {
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
NSLog(@"Setting up an OpenGL ES 3.0 context. Based on Project Settings \"rendering/quality/driver/driver_name\"");
if (!context && GLOBAL_GET("rendering/quality/driver/fallback_to_gles2")) {
gles3_available = false;
fallback_gl2 = true;
NSLog(@"Failed to create OpenGL ES 3.0 context. Falling back to OpenGL ES 2.0");
}
}
// Create GL ES 2 context
if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2" || fallback_gl2) {
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
NSLog(@"Setting up an OpenGL ES 2.0 context.");
if (!context) {
NSLog(@"Failed to create OpenGL ES 2.0 context!");
return;
}
}
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"Failed to set EAGLContext!");
return;
}
if (![self createFramebuffer]) {
NSLog(@"Failed to create frame buffer!");
return;
}
}
- (void)layoutDisplayLayer {
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
}
- (void)startRenderDisplayLayer {
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
}
- (void)stopRenderDisplayLayer {
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
#ifdef DEBUG_ENABLED
GLenum err = glGetError();
if (err) {
NSLog(@"DrawView: %x error", err);
}
#endif
}
- (void)dealloc {
if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}
if (context) {
context = nil;
}
}
- (BOOL)createFramebuffer {
// Generate IDs for a framebuffer object and a color renderbuffer
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
if (OS::get_singleton()) {
OS::VideoMode vm;
vm.fullscreen = true;
vm.width = backingWidth;
vm.height = backingHeight;
vm.resizable = false;
OS::get_singleton()->set_video_mode(vm);
OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
}
gl_view_base_fb = viewFramebuffer;
return YES;
}
// Clean up any buffers we have allocated.
- (void)destroyFramebuffer {
glDeleteFramebuffersOES(1, &viewFramebuffer);
viewFramebuffer = 0;
glDeleteRenderbuffersOES(1, &viewRenderbuffer);
viewRenderbuffer = 0;
if (depthRenderbuffer) {
glDeleteRenderbuffersOES(1, &depthRenderbuffer);
depthRenderbuffer = 0;
}
}
@end

View File

@ -32,20 +32,9 @@
#include "game_center.h"
#ifdef __IPHONE_9_0
#import <GameKit/GameKit.h>
extern "C" {
#else
extern "C" {
#import <GameKit/GameKit.h>
#endif
#import "app_delegate.h"
};
#import "view_controller.h"
#import <GameKit/GameKit.h>
GameCenter *GameCenter::instance = NULL;
@ -82,7 +71,12 @@ Error GameCenter::authenticate() {
// after the view is cancelled or the user logs in. Or if the user's already logged in, it's
// called just once to confirm they're authenticated. This is why no result needs to be specified
// in the presentViewController phase. In this case, more calls to this function will follow.
_weakify(root_controller);
_weakify(player);
player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
_strongify(root_controller);
_strongify(player);
if (controller) {
[root_controller presentViewController:controller animated:YES completion:nil];
} else {
@ -117,8 +111,8 @@ Error GameCenter::post_score(Variant p_score) {
float score = params["score"];
String category = params["category"];
NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()];
GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str];
reporter.value = score;
ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
@ -148,8 +142,8 @@ Error GameCenter::award_achievement(Variant p_params) {
String name = params["name"];
float progress = params["progress"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str];
ERR_FAIL_COND_V(!achievement, FAILED);
ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
@ -313,7 +307,7 @@ Error GameCenter::show_game_center(Variant p_params) {
controller.leaderboardIdentifier = nil;
if (params.has("leaderboard_name")) {
String name = params["leaderboard_name"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
controller.leaderboardIdentifier = name_str;
}
}

View File

@ -1,5 +1,5 @@
/*************************************************************************/
/* godot_iphone.cpp */
/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -38,15 +38,39 @@
static OSIPhone *os = NULL;
extern "C" {
int add_path(int p_argc, char **p_args);
int add_cmdline(int p_argc, char **p_args);
};
int add_path(int p_argc, char **p_args) {
NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
if (!str) {
return p_argc;
}
int iphone_main(int, int, int, char **, String);
p_args[p_argc++] = (char *)"--path";
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
p_args[p_argc] = NULL;
int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
return p_argc;
}
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
if (!arr) {
return p_argc;
}
for (NSUInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str) {
continue;
}
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
}
p_args[p_argc] = NULL;
return p_argc;
}
int iphone_main(int argc, char **argv, String data_dir) {
size_t len = strlen(argv[0]);
while (len--) {
@ -65,12 +89,12 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
os = new OSIPhone(width, height, data_dir);
os = new OSIPhone(data_dir);
char *fargv[64];
for (int i = 0; i < argc; i++) {
fargv[i] = argv[i];
};
}
fargv[argc] = NULL;
argc = add_path(argc, fargv);
argc = add_cmdline(argc, fargv);
@ -78,15 +102,15 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
printf("os created\n");
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
if (err != OK)
if (err != OK) {
return 255;
}
return 0;
};
}
void iphone_finish() {
printf("iphone_finish\n");
Main::cleanup();
delete os;
};
}

View File

@ -35,93 +35,28 @@
#import <OpenGLES/ES1/glext.h>
#import <UIKit/UIKit.h>
@protocol GodotViewDelegate;
@class GodotViewGestureRecognizer;
class String;
@interface GodotView : UIView <UIKeyInput> {
@private
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
@protocol DisplayLayer;
@protocol GodotViewRendererProtocol;
EAGLContext *context;
@interface GodotView : UIView <UIKeyInput>
// OpenGL names for the renderbuffer and framebuffers used to render to this view
GLuint viewRenderbuffer, viewFramebuffer;
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
GLuint depthRenderbuffer;
@property(assign, readonly, nonatomic) BOOL isActive;
BOOL useCADisplayLink;
// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
CADisplayLink *displayLink;
@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
// Only used if CADisplayLink is not
NSTimer *animationTimer;
@property(assign, nonatomic) NSTimeInterval renderingInterval;
NSTimeInterval animationInterval;
- (CALayer<DisplayLayer> *)initializeRendering;
- (void)stopRendering;
- (void)startRendering;
// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
id<GodotViewDelegate> delegate;
- (BOOL)becomeFirstResponderWithString:(String)p_existing;
// Flag to denote that the -setupView method of a delegate has been called.
// Resets to NO whenever the delegate changes.
BOOL delegateSetup;
BOOL active;
float screen_scale;
// Delay gesture recognizer
GodotViewGestureRecognizer *delayGestureRecognizer;
}
@property(nonatomic, assign) id<GodotViewDelegate> delegate;
// AVPlayer-related properties
@property(strong, nonatomic) AVAsset *avAsset;
@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
@property(strong, nonatomic) AVPlayer *avPlayer;
@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
@property(strong, nonatomic) UIWindow *backgroundWindow;
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
- (void)startAnimation;
- (void)stopAnimation;
- (void)drawView;
- (BOOL)canBecomeFirstResponder;
- (void)open_keyboard;
- (void)hide_keyboard;
- (void)deleteBackward;
- (BOOL)hasText;
- (void)insertText:(NSString *)p_text;
- (id)initGLES;
- (BOOL)createFramebuffer;
- (void)destroyFramebuffer;
- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
- (void)keyboardOnScreen:(NSNotification *)notification;
- (void)keyboardHidden:(NSNotification *)notification;
@property(nonatomic, assign) NSTimeInterval animationInterval;
@property(nonatomic, assign) BOOL useCADisplayLink;
@end
@protocol GodotViewDelegate <NSObject>
@required
// Draw with OpenGL ES
- (void)drawView:(GodotView *)view;
@optional
// Called whenever you need to do some initialization before rendering.
- (void)setupView:(GodotView *)view;
@end

File diff suppressed because it is too large Load Diff

View File

@ -75,7 +75,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
[self.view touchesBegan:delayedTouches withEvent:delayedEvent];
}
[delayedTouches release];
delayedTouches = nil;
delayedEvent = nil;
}
@ -103,16 +102,13 @@ const CGFloat kGLGestureMovementDistance = 0.5;
if (distance > kGLGestureMovementDistance) {
[delayTimer fire];
[self.view touchesMoved:cleared withEvent:event];
[cleared release];
return;
}
}
[cleared release];
return;
}
[self.view touchesMoved:cleared withEvent:event];
[cleared release];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
@ -120,7 +116,6 @@ const CGFloat kGLGestureMovementDistance = 0.5;
NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
[self.view touchesEnded:cleared withEvent:event];
[cleared release];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {

View File

@ -0,0 +1,44 @@
/*************************************************************************/
/* godot_view_renderer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@protocol GodotViewRendererProtocol <NSObject>
@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
- (BOOL)setupView:(UIView *)view;
- (void)renderOnView:(UIView *)view;
@end
@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
@end

View File

@ -0,0 +1,117 @@
/*************************************************************************/
/* godot_view_renderer.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "godot_view_renderer.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "main/main.h"
#include "os_iphone.h"
#include "servers/audio_server.h"
#import <AudioToolbox/AudioServices.h>
#import <CoreMotion/CoreMotion.h>
#import <GameController/GameController.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
@interface GodotViewRenderer ()
@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
@property(assign, nonatomic) BOOL hasStartedMain;
@property(assign, nonatomic) BOOL hasFinishedSetup;
@end
@implementation GodotViewRenderer
- (BOOL)setupView:(UIView *)view {
if (self.hasFinishedSetup) {
return NO;
}
if (!OS::get_singleton()) {
exit(0);
}
if (!self.hasFinishedProjectDataSetup) {
[self setupProjectData];
return YES;
}
if (!self.hasStartedMain) {
self.hasStartedMain = YES;
OSIPhone::get_singleton()->start();
return YES;
}
self.hasFinishedSetup = YES;
return NO;
}
- (void)setupProjectData {
self.hasFinishedProjectDataSetup = YES;
Main::setup2();
// this might be necessary before here
NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
for (NSString *key in dict) {
NSObject *value = [dict objectForKey:key];
String ukey = String::utf8([key UTF8String]);
// we need a NSObject to Variant conversor
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
String uval = String::utf8([str UTF8String]);
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *n = (NSNumber *)value;
double dval = [n doubleValue];
ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
};
// do stuff
}
}
- (void)renderOnView:(UIView *)view {
if (!OSIPhone::get_singleton()) {
return;
}
OSIPhone::get_singleton()->iterate();
}
@end

View File

@ -32,18 +32,10 @@
#include "icloud.h"
#ifndef __IPHONE_9_0
extern "C" {
#endif
#import "app_delegate.h"
#import <Foundation/Foundation.h>
#ifndef __IPHONE_9_0
};
#endif
ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
@ -150,7 +142,7 @@ Variant nsobject_to_variant(NSObject *object) {
NSObject *variant_to_nsobject(Variant v) {
if (v.get_type() == Variant::STRING) {
return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()];
} else if (v.get_type() == Variant::REAL) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
@ -158,11 +150,11 @@ NSObject *variant_to_nsobject(Variant v) {
} else if (v.get_type() == Variant::BOOL) {
return [NSNumber numberWithBool:BOOL((bool)v)];
} else if (v.get_type() == Variant::DICTIONARY) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
Dictionary dic = v;
Array keys = dic.keys();
for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
if (key == NULL || value == NULL) {
@ -173,7 +165,7 @@ NSObject *variant_to_nsobject(Variant v) {
}
return result;
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
NSMutableArray *result = [[NSMutableArray alloc] init];
Array arr = v;
for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
@ -196,7 +188,7 @@ NSObject *variant_to_nsobject(Variant v) {
Error ICloud::remove_key(Variant p_param) {
String param = p_param;
NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
@ -219,7 +211,7 @@ Variant ICloud::set_key_values(Variant p_params) {
String variant_key = keys[i];
Variant variant_value = params[variant_key];
NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()];
if (key == NULL) {
error_keys.push_back(variant_key);
continue;
@ -242,7 +234,7 @@ Variant ICloud::set_key_values(Variant p_params) {
Variant ICloud::get_key_value(Variant p_param) {
String param = p_param;
NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {

View File

@ -32,10 +32,8 @@
#include "in_app_store.h"
extern "C" {
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
};
bool auto_finish_transactions = true;
NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
@ -55,7 +53,6 @@ static NSArray *latestProducts;
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:self.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:self.price];
[numberFormatter release];
return formattedString;
}
@end
@ -125,8 +122,6 @@ void InAppStore::_bind_methods() {
ret["invalid_ids"] = invalid_ids;
InAppStore::get_singleton()->_post_event(ret);
[request release];
};
@end
@ -139,14 +134,14 @@ Error InAppStore::request_product_info(Variant p_params) {
PoolStringArray pids = params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
for (int i = 0; i < pids.size(); i++) {
printf("******** adding %ls to product list\n", pids[i].c_str());
NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease];
NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
[array addObject:pid];
};
NSSet *products = [[[NSSet alloc] initWithArray:array] autorelease];
NSSet *products = [[NSSet alloc] initWithArray:array];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products];
ProductsDelegate *delegate = [[ProductsDelegate alloc] init];
@ -189,32 +184,17 @@ Error InAppStore::restore_purchases() {
ret["transaction_id"] = transactionId;
NSData *receipt = nil;
int sdk_version = 6;
int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
NSURL *receiptFileURL = nil;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
// Get the transaction receipt file path location in the app bundle.
receiptFileURL = [bundle appStoreReceiptURL];
NSURL *receiptFileURL = [bundle appStoreReceiptURL];
// Read in the contents of the transaction file.
receipt = [NSData dataWithContentsOfURL:receiptFileURL];
sdk_version = 7;
} else {
// Fall back to deprecated transaction receipt,
// which is still available in iOS 7.
// Use SKPaymentTransaction's transactionReceipt.
receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
} else {
receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
NSString *receipt_to_send = nil;
if (receipt != nil) {
receipt_to_send = [receipt base64EncodedStringWithOptions:0];
}
@ -273,7 +253,7 @@ Error InAppStore::purchase(Variant p_params) {
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
NSString *pid = [[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()];
SKProduct *product = nil;
@ -318,7 +298,7 @@ void InAppStore::_post_event(Variant p_event) {
void InAppStore::_record_purchase(String product_id) {
String skey = "purchased/" + product_id;
NSString *key = [[[NSString alloc] initWithUTF8String:skey.utf8().get_data()] autorelease];
NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
};

View File

@ -31,6 +31,7 @@
#include "ios.h"
#import "app_delegate.h"
#import "view_controller.h"
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
@ -71,25 +72,12 @@ String iOS::get_model() const {
}
String iOS::get_rate_url(int p_app_id) const {
String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID";
String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID";
String templ_iOS8 = "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=APP_ID&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software";
String app_url_path = "itms-apps://itunes.apple.com/app/idAPP_ID";
//ios7 before
String ret = templ;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 && [[[UIDevice currentDevice] systemVersion] floatValue] < 7.1) {
// iOS 7 needs a different templateReviewURL @see https://github.com/arashpayan/appirater/issues/131
ret = templ_iOS7;
} else if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
// iOS 8 needs a different templateReviewURL also @see https://github.com/arashpayan/appirater/issues/182
ret = templ_iOS8;
}
// ios7 for everything?
ret = templ_iOS7.replace("APP_ID", String::num(p_app_id));
String ret = app_url_path.replace("APP_ID", String::num(p_app_id));
printf("returning rate url %ls\n", ret.c_str());
return ret;
};

View File

@ -0,0 +1,50 @@
/*************************************************************************/
/* joypad_iphone.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <GameController/GameController.h>
@interface JoypadIPhoneObserver : NSObject
- (void)startObserving;
- (void)startProcessing;
- (void)finishObserving;
@end
class JoypadIPhone {
private:
JoypadIPhoneObserver *observer;
public:
JoypadIPhone();
~JoypadIPhone();
void start_processing();
};

View File

@ -0,0 +1,352 @@
/*************************************************************************/
/* joypad_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "joypad_iphone.h"
#include "core/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
JoypadIPhone::JoypadIPhone() {
observer = [[JoypadIPhoneObserver alloc] init];
[observer startObserving];
}
JoypadIPhone::~JoypadIPhone() {
if (observer) {
[observer finishObserving];
observer = nil;
}
}
void JoypadIPhone::start_processing() {
if (observer) {
[observer startProcessing];
}
}
@interface JoypadIPhoneObserver ()
@property(assign, nonatomic) BOOL isObserving;
@property(assign, nonatomic) BOOL isProcessing;
@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
@property(strong, nonatomic) NSMutableArray *joypadsQueue;
@end
@implementation JoypadIPhoneObserver
- (instancetype)init {
self = [super init];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.isObserving = NO;
self.isProcessing = NO;
}
- (void)startProcessing {
self.isProcessing = YES;
for (GCController *controller in self.joypadsQueue) {
[self addiOSJoypad:controller];
}
[self.joypadsQueue removeAllObjects];
}
- (void)startObserving {
if (self.isObserving) {
return;
}
self.isObserving = YES;
self.connectedJoypads = [NSMutableDictionary dictionary];
self.joypadsQueue = [NSMutableArray array];
// get told when controllers connect, this will be called right away for
// already connected controllers
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasConnected:)
name:GCControllerDidConnectNotification
object:nil];
// get told when controllers disconnect
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(controllerWasDisconnected:)
name:GCControllerDidDisconnectNotification
object:nil];
}
- (void)finishObserving {
if (self.isObserving) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
self.isObserving = NO;
self.isProcessing = NO;
self.connectedJoypads = nil;
self.joypadsQueue = nil;
}
- (void)dealloc {
[self finishObserving];
}
- (int)getJoyIdForController:(GCController *)controller {
NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
for (NSNumber *key in keys) {
int joy_id = [key intValue];
return joy_id;
};
return -1;
};
- (void)addiOSJoypad:(GCController *)controller {
// get a new id for our controller
int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
if (joy_id == -1) {
printf("Couldn't retrieve new joy id\n");
return;
}
// assign our player index
if (controller.playerIndex == GCControllerPlayerIndexUnset) {
controller.playerIndex = [self getFreePlayerIndex];
};
// tell Godot about our new controller
OSIPhone::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
// add it to our dictionary, this will retain our controllers
[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
// set our input handler
[self setControllerInputHandler:controller];
}
- (void)controllerWasConnected:(NSNotification *)notification {
// get our controller
GCController *controller = (GCController *)notification.object;
if (!controller) {
printf("Couldn't retrieve new controller\n");
return;
}
if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
printf("Controller is already registered\n");
} else if (!self.isProcessing) {
[self.joypadsQueue addObject:controller];
} else {
[self addiOSJoypad:controller];
}
}
- (void)controllerWasDisconnected:(NSNotification *)notification {
// find our joystick, there should be only one in our dictionary
GCController *controller = (GCController *)notification.object;
if (!controller) {
return;
}
NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
for (NSNumber *key in keys) {
// tell Godot this joystick is no longer there
int joy_id = [key intValue];
OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
// and remove it from our dictionary
[self.connectedJoypads removeObjectForKey:key];
};
};
- (GCControllerPlayerIndex)getFreePlayerIndex {
bool have_player_1 = false;
bool have_player_2 = false;
bool have_player_3 = false;
bool have_player_4 = false;
if (self.connectedJoypads == nil) {
NSArray *keys = [self.connectedJoypads allKeys];
for (NSNumber *key in keys) {
GCController *controller = [self.connectedJoypads objectForKey:key];
if (controller.playerIndex == GCControllerPlayerIndex1) {
have_player_1 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex2) {
have_player_2 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex3) {
have_player_3 = true;
} else if (controller.playerIndex == GCControllerPlayerIndex4) {
have_player_4 = true;
};
};
};
if (!have_player_1) {
return GCControllerPlayerIndex1;
} else if (!have_player_2) {
return GCControllerPlayerIndex2;
} else if (!have_player_3) {
return GCControllerPlayerIndex3;
} else if (!have_player_4) {
return GCControllerPlayerIndex4;
} else {
return GCControllerPlayerIndexUnset;
};
}
- (void)setControllerInputHandler:(GCController *)controller {
// Hook in the callback handler for the correct gamepad profile.
// This is a bit of a weird design choice on Apples part.
// You need to select the most capable gamepad profile for the
// gamepad attached.
if (controller.extendedGamepad != nil) {
// The extended gamepad profile has all the input you could possibly find on
// a gamepad but will only be active if your gamepad actually has all of
// these...
_weakify(self);
_weakify(controller);
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
_strongify(self);
_strongify(controller);
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_1,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_3,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_L,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_R,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.leftTrigger) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_L2,
gamepad.leftTrigger.isPressed);
} else if (element == gamepad.rightTrigger) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_R2,
gamepad.rightTrigger.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
InputDefault::JoyAxis jx;
jx.min = -1;
if (element == gamepad.leftThumbstick) {
jx.value = gamepad.leftThumbstick.xAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LX, jx);
jx.value = -gamepad.leftThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_LY, jx);
} else if (element == gamepad.rightThumbstick) {
jx.value = gamepad.rightThumbstick.xAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RX, jx);
jx.value = -gamepad.rightThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_RY, jx);
} else if (element == gamepad.leftTrigger) {
jx.value = gamepad.leftTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_L2, jx);
} else if (element == gamepad.rightTrigger) {
jx.value = gamepad.rightTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_ANALOG_R2, jx);
}
};
} else if (controller.microGamepad != nil) {
// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
_weakify(self);
_weakify(controller);
controller.microGamepad.valueChangedHandler = ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
_strongify(self);
_strongify(controller);
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_0,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_2,
gamepad.buttonX.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
}
};
}
///@TODO need to add support for controller.motion which gives us access to
/// the orientation of the device (if supported)
///@TODO need to add support for controllerPausedHandler which should be a
/// toggle
};
@end

View File

@ -0,0 +1,42 @@
/*************************************************************************/
/* native_video_view.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import <UIKit/UIKit.h>
@interface GodotNativeVideoView : UIView
- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
- (BOOL)isVideoPlaying;
- (void)pauseVideo;
- (void)unfocusVideo;
- (void)unpauseVideo;
- (void)stopVideo;
@end

View File

@ -0,0 +1,265 @@
/*************************************************************************/
/* native_video_view.m */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#import "native_video_view.h"
#import <AVFoundation/AVFoundation.h>
@interface GodotNativeVideoView ()
@property(strong, nonatomic) AVAsset *avAsset;
@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
@property(strong, nonatomic) AVPlayer *avPlayer;
@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
@property(assign, nonatomic) CMTime videoCurrentTime;
@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
@end
@implementation GodotNativeVideoView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.isVideoCurrentlyPlaying = NO;
self.videoCurrentTime = kCMTimeZero;
[self observeVideoAudio];
}
- (void)observeVideoAudio {
printf("******** adding observer for sound routing changes\n");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
[self handleVideoOrPlayerStatus];
}
if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
[self handleVideoPlayRate];
}
}
// MARK: Video Audio
- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
printf("*********** route changed!\n");
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
NSLog(@"Headphone/Line plugged in");
} break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
NSLog(@"Headphone/Line was pulled. Resuming video play....");
if ([self isVideoPlaying]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self.avPlayer play]; // NOTE: change this line according your current player implementation
NSLog(@"resumed play");
});
}
} break;
case AVAudioSessionRouteChangeReasonCategoryChange: {
// called at start - also when other audio wants to play
NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
} break;
}
}
// MARK: Native Video Player
- (void)handleVideoOrPlayerStatus {
if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
[self stopVideo];
}
if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
// NSLog(@"time: %@", self.video_current_time);
[self.avPlayer seekToTime:self.videoCurrentTime];
self.videoCurrentTime = kCMTimeZero;
}
}
- (void)handleVideoPlayRate {
NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self.avPlayer play]; // NOTE: change this line according your current player implementation
NSLog(@"resumed play");
});
NSLog(@" . . . PAUSED (or just started)");
}
}
- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
[self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
[self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.avPlayer currentItem]];
[self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
[self.avPlayerLayer setFrame:self.bounds];
[self.layer addSublayer:self.avPlayerLayer];
[self.avPlayer play];
AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
NSMutableArray *allAudioParams = [NSMutableArray array];
for (id track in audioGroup.options) {
NSString *language = [[track locale] localeIdentifier];
NSLog(@"subtitle lang: %@", language);
if ([language isEqualToString:audioTrack]) {
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
[audioMix setInputParameters:allAudioParams];
[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
[self.avPlayer.currentItem setAudioMix:audioMix];
break;
}
}
AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
for (id track in useableTracks) {
NSString *language = [[track locale] localeIdentifier];
NSLog(@"subtitle lang: %@", language);
if ([language isEqualToString:subtitleTrack]) {
[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
break;
}
}
self.isVideoCurrentlyPlaying = YES;
return true;
}
- (BOOL)isVideoPlaying {
if (self.avPlayer.error) {
printf("Error during playback\n");
}
return (self.avPlayer.rate > 0 && !self.avPlayer.error);
}
- (void)pauseVideo {
self.videoCurrentTime = self.avPlayer.currentTime;
[self.avPlayer pause];
self.isVideoCurrentlyPlaying = NO;
}
- (void)unfocusVideo {
[self.avPlayer pause];
}
- (void)unpauseVideo {
[self.avPlayer play];
self.isVideoCurrentlyPlaying = YES;
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[self stopVideo];
}
- (void)finishPlayingVideo {
[self.avPlayer pause];
[self.avPlayerLayer removeFromSuperlayer];
self.avPlayerLayer = nil;
if (self.avPlayerItem) {
[self.avPlayerItem removeObserver:self forKeyPath:@"status"];
self.avPlayerItem = nil;
}
if (self.avPlayer) {
[self.avPlayer removeObserver:self forKeyPath:@"status"];
self.avPlayer = nil;
}
self.avAsset = nil;
self.isVideoCurrentlyPlaying = NO;
}
- (void)stopVideo {
[self finishPlayingVideo];
[self removeFromSuperview];
}
@end

View File

@ -36,6 +36,7 @@
#include "core/os/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "joypad_iphone.h"
#include "game_center.h"
#include "icloud.h"
@ -49,11 +50,6 @@
class OSIPhone : public OS_Unix {
private:
enum {
MAX_MOUSE_COUNT = 8,
MAX_EVENTS = 64,
};
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
@ -72,6 +68,8 @@ private:
#endif
iOS *ios;
JoypadIPhone *joypad_iphone;
MainLoop *main_loop;
VideoMode video_mode;
@ -91,39 +89,56 @@ private:
virtual void finalize();
struct MouseList {
void perform_event(const Ref<InputEvent> &p_event);
bool pressed[MAX_MOUSE_COUNT];
MouseList() {
for (int i = 0; i < MAX_MOUSE_COUNT; i++)
pressed[i] = false;
};
};
MouseList touch_list;
Vector3 last_accel;
Ref<InputEvent> event_queue[MAX_EVENTS];
int event_count;
void queue_event(const Ref<InputEvent> &p_event);
void set_data_dir(String p_dir);
String data_dir;
InputDefault *input;
int virtual_keyboard_height;
int virtual_keyboard_height = 0;
int video_driver_index;
bool is_focused = false;
public:
static OSIPhone *get_singleton();
OSIPhone(String p_data_dir);
~OSIPhone();
bool iterate();
uint8_t get_orientations() const;
void start();
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
virtual Error close_dynamic_library(void *p_library_handle);
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual String get_name() const;
virtual String get_model_name() const;
Error shell_open(String p_uri);
String get_user_data_dir() const;
String get_locale() const;
String get_unique_id() const;
virtual void vibrate_handheld(int p_duration_ms = 500);
virtual bool _check_internal_feature_support(const String &p_feature);
virtual int get_screen_dpi(int p_screen = -1) const;
void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
void touches_cancelled();
void touches_cancelled(int p_idx);
void key(uint32_t p_key, bool p_pressed);
void set_virtual_keyboard_height(int p_height);
@ -139,23 +154,17 @@ public:
void joy_button(int p_device, int p_button, bool p_pressed);
void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
static OSIPhone *get_singleton();
virtual void set_mouse_show(bool p_show);
virtual void set_mouse_grab(bool p_grab);
virtual bool is_mouse_grab_enabled() const;
virtual Point2 get_mouse_position() const;
virtual int get_mouse_button_state() const;
virtual void set_window_title(const String &p_title);
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
virtual Error close_dynamic_library(void *p_library_handle);
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
virtual VideoMode get_video_mode(int p_screen = 0) const;
virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
virtual void set_keep_screen_on(bool p_enabled);
@ -172,30 +181,15 @@ public:
virtual bool has_touchscreen_ui_hint() const;
void set_data_dir(String p_dir);
virtual String get_name() const;
virtual String get_model_name() const;
Error shell_open(String p_uri);
String get_user_data_dir() const;
String get_locale() const;
String get_unique_id() const;
virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
virtual bool native_video_is_playing() const;
virtual void native_video_pause();
virtual void native_video_unpause();
virtual void native_video_focus_out();
virtual void native_video_stop();
virtual void vibrate_handheld(int p_duration_ms = 500);
virtual bool _check_internal_feature_support(const String &p_feature);
OSIPhone(int width, int height, String p_data_dir);
~OSIPhone();
void on_focus_out();
void on_focus_in();
};
#endif // OS_IPHONE_H

View File

@ -30,11 +30,6 @@
#ifdef IPHONE_ENABLED
// System headers are at top
// to workaround `ambiguous expansion` warning/error
#import <UIKit/UIKit.h>
#include <dlfcn.h>
#include "os_iphone.h"
#include "drivers/gles2/rasterizer_gles2.h"
@ -52,13 +47,33 @@
#include "semaphore_iphone.h"
int OSIPhone::get_video_driver_count() const {
#import "app_delegate.h"
#import "device_metrics.h"
#import "godot_view.h"
#import "native_video_view.h"
#import "view_controller.h"
#import <UIKit/UIKit.h>
#include <dlfcn.h>
#import <sys/utsname.h>
extern int gl_view_base_fb; // from gl_view.mm
extern bool gles3_available; // from gl_view.mm
// Initialization order between compilation units is not guaranteed,
// so we use this as a hack to ensure certain code is called before
// everything else, but after all units are initialized.
typedef void (*init_callback)();
static init_callback *ios_init_callbacks = NULL;
static int ios_init_callbacks_count = 0;
static int ios_init_callbacks_capacity = 0;
HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
int OSIPhone::get_video_driver_count() const {
return 2;
};
const char *OSIPhone::get_video_driver_name(int p_driver) const {
switch (p_driver) {
case VIDEO_DRIVER_GLES3:
return "GLES3";
@ -69,14 +84,10 @@ const char *OSIPhone::get_video_driver_name(int p_driver) const {
};
OSIPhone *OSIPhone::get_singleton() {
return (OSIPhone *)OS::get_singleton();
};
extern int gl_view_base_fb; // from gl_view.mm
void OSIPhone::set_data_dir(String p_dir) {
DirAccess *da = DirAccess::open(p_dir);
data_dir = da->get_current_dir();
@ -101,7 +112,13 @@ int OSIPhone::get_current_video_driver() const {
return video_driver_index;
}
extern bool gles3_available; // from gl_view.mm
void OSIPhone::start() {
Main::start();
if (joypad_iphone) {
joypad_iphone->start_processing();
}
}
Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
@ -153,10 +170,11 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
//visual_server->cursor_set_visible(false, 0);
// reset this to what it should be, it will have been set to 0 after visual_server->init() is called
if (use_gl3)
if (use_gl3) {
RasterizerStorageGLES3::system_fbo = gl_view_base_fb;
else
} else {
RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
}
AudioDriverManager::initialize(p_audio_driver);
@ -180,6 +198,8 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
joypad_iphone = memnew(JoypadIPhone);
return OK;
};
@ -199,78 +219,57 @@ void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
};
bool OSIPhone::iterate() {
if (!main_loop)
if (!main_loop) {
return true;
if (main_loop) {
for (int i = 0; i < event_count; i++) {
input->parse_input_event(event_queue[i]);
};
};
event_count = 0;
}
return Main::iteration();
};
void OSIPhone::key(uint32_t p_key, bool p_pressed) {
Ref<InputEventKey> ev;
ev.instance();
ev->set_echo(false);
ev->set_pressed(p_pressed);
ev->set_scancode(p_key);
ev->set_unicode(p_key);
queue_event(ev);
perform_event(ev);
};
void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
if (GLOBAL_DEF("debug/disable_touch", false)) {
return;
}
if (!GLOBAL_DEF("debug/disable_touch", false)) {
Ref<InputEventScreenTouch> ev;
ev.instance();
ev->set_index(p_idx);
ev->set_pressed(p_pressed);
ev->set_position(Vector2(p_x, p_y));
queue_event(ev);
};
touch_list.pressed[p_idx] = p_pressed;
perform_event(ev);
};
void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
if (!GLOBAL_DEF("debug/disable_touch", false)) {
if (GLOBAL_DEF("debug/disable_touch", false)) {
return;
}
Ref<InputEventScreenDrag> ev;
ev.instance();
ev->set_index(p_idx);
ev->set_position(Vector2(p_x, p_y));
ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
queue_event(ev);
};
};
perform_event(ev);
}
void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
void OSIPhone::perform_event(const Ref<InputEvent> &p_event) {
input->parse_input_event(p_event);
}
ERR_FAIL_INDEX(event_count, MAX_EVENTS);
event_queue[event_count++] = p_event;
};
void OSIPhone::touches_cancelled() {
for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
if (touch_list.pressed[i]) {
// send a mouse_up outside the screen
touch_press(i, -1, -1, false, false);
};
};
};
void OSIPhone::touches_cancelled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
}
static const float ACCEL_RANGE = 1;
@ -282,39 +281,6 @@ void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
// Found out the Z should not be negated! Pass as is!
input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
/*
if (p_x != last_accel.x) {
//printf("updating accel x %f\n", p_x);
InputEvent ev;
ev.type = InputEvent::JOYPAD_MOTION;
ev.device = 0;
ev.joy_motion.axis = JOY_ANALOG_0;
ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
last_accel.x = p_x;
queue_event(ev);
};
if (p_y != last_accel.y) {
//printf("updating accel y %f\n", p_y);
InputEvent ev;
ev.type = InputEvent::JOYPAD_MOTION;
ev.device = 0;
ev.joy_motion.axis = JOY_ANALOG_1;
ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
last_accel.y = p_y;
queue_event(ev);
};
if (p_z != last_accel.z) {
//printf("updating accel z %f\n", p_z);
InputEvent ev;
ev.type = InputEvent::JOYPAD_MOTION;
ev.device = 0;
ev.joy_motion.axis = JOY_ANALOG_2;
ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
last_accel.z = p_z;
queue_event(ev);
};
*/
};
void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
@ -355,59 +321,76 @@ void OSIPhone::finalize() {
delete_main_loop();
if (joypad_iphone) {
memdelete(joypad_iphone);
}
if (input) {
memdelete(input);
}
if (ios) {
memdelete(ios);
}
#ifdef GAME_CENTER_ENABLED
if (game_center) {
memdelete(game_center);
}
#endif
#ifdef STOREKIT_ENABLED
if (store_kit) {
memdelete(store_kit);
}
#endif
#ifdef ICLOUD_ENABLED
if (icloud) {
memdelete(icloud);
}
#endif
visual_server->finish();
memdelete(visual_server);
// memdelete(rasterizer);
}
// Free unhandled events before close
for (int i = 0; i < MAX_EVENTS; i++) {
event_queue[i].unref();
};
event_count = 0;
};
void OSIPhone::set_mouse_show(bool p_show) {
// Not supported for iOS
}
void OSIPhone::set_mouse_show(bool p_show){};
void OSIPhone::set_mouse_grab(bool p_grab){};
void OSIPhone::set_mouse_grab(bool p_grab) {
// Not supported for iOS
}
bool OSIPhone::is_mouse_grab_enabled() const {
// Not supported for iOS
return true;
};
}
Point2 OSIPhone::get_mouse_position() const {
// Not supported for iOS
return Point2();
};
}
int OSIPhone::get_mouse_button_state() const {
// Not supported for iOS
return 0;
};
}
void OSIPhone::set_window_title(const String &p_title){};
void OSIPhone::set_window_title(const String &p_title) {
// Not supported for iOS
}
void OSIPhone::alert(const String &p_alert, const String &p_title) {
const CharString utf8_alert = p_alert.utf8();
const CharString utf8_title = p_title.utf8();
iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
}
// MARK: Dynamic Libraries
Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
if (p_path.length() == 0) {
p_library_handle = RTLD_SELF;
@ -423,7 +406,6 @@ Error OSIPhone::close_dynamic_library(void *p_library_handle) {
return OS_Unix::close_dynamic_library(p_library_handle);
}
HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
void register_dynamic_symbol(char *name, void *address) {
OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
}
@ -440,55 +422,46 @@ Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const
}
void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
video_mode = p_video_mode;
};
}
OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
return video_mode;
};
}
void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
p_list->push_back(video_mode);
};
}
bool OSIPhone::can_draw() const {
if (native_video_is_playing())
return false;
return true;
};
}
int OSIPhone::set_base_framebuffer(int p_fb) {
// gl_view_base_fb has not been updated yet
RasterizerStorageGLES3::system_fbo = p_fb;
return 0;
};
}
bool OSIPhone::has_virtual_keyboard() const {
return true;
};
extern void _show_keyboard(String p_existing);
extern void _hide_keyboard();
extern Error _shell_open(String p_uri);
extern void _set_keep_screen_on(bool p_enabled);
extern void _vibrate();
void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
_show_keyboard(p_existing_text);
[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
};
void OSIPhone::hide_virtual_keyboard() {
_hide_keyboard();
};
[AppDelegate.viewController.godotView resignFirstResponder];
}
void OSIPhone::set_virtual_keyboard_height(int p_height) {
virtual_keyboard_height = p_height;
virtual_keyboard_height = p_height * [UIScreen mainScreen].nativeScale;
}
int OSIPhone::get_virtual_keyboard_height() const {
@ -496,46 +469,103 @@ int OSIPhone::get_virtual_keyboard_height() const {
}
Error OSIPhone::shell_open(String p_uri) {
return _shell_open(p_uri);
};
NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
NSURL *url = [NSURL URLWithString:urlPath];
if (![[UIApplication sharedApplication] canOpenURL:url]) {
return ERR_CANT_OPEN;
}
printf("opening url %s\n", p_uri.utf8().get_data());
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return OK;
}
void OSIPhone::set_keep_screen_on(bool p_enabled) {
OS::set_keep_screen_on(p_enabled);
_set_keep_screen_on(p_enabled);
[UIApplication sharedApplication].idleTimerDisabled = p_enabled;
};
String OSIPhone::get_user_data_dir() const {
return data_dir;
};
}
String OSIPhone::get_name() const {
return "iOS";
};
}
String OSIPhone::get_model_name() const {
String model = ios->get_model();
if (model != "")
if (model != "") {
return model;
}
return OS_Unix::get_model_name();
}
Size2 OSIPhone::get_window_size() const {
return Vector2(video_mode.width, video_mode.height);
}
extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
int OSIPhone::get_screen_dpi(int p_screen) const {
struct utsname systemInfo;
uname(&systemInfo);
NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
NSDictionary *iOSModelToDPI = [GodotDeviceMetrics dpiList];
for (NSArray *keyArray in iOSModelToDPI) {
if ([keyArray containsObject:string]) {
NSNumber *value = iOSModelToDPI[keyArray];
return [value intValue];
}
}
// If device wasn't found in dictionary
// make a best guess from device metrics.
CGFloat scale = [UIScreen mainScreen].scale;
UIUserInterfaceIdiom idiom = [UIDevice currentDevice].userInterfaceIdiom;
switch (idiom) {
case UIUserInterfaceIdiomPad:
return scale == 2 ? 264 : 132;
case UIUserInterfaceIdiomPhone: {
if (scale == 3) {
CGFloat nativeScale = [UIScreen mainScreen].nativeScale;
return nativeScale == 3 ? 458 : 401;
}
return 326;
}
default:
return 72;
}
}
Rect2 OSIPhone::get_window_safe_area() const {
return _get_ios_window_safe_area(video_mode.width, video_mode.height);
if (@available(iOS 11, *)) {
UIEdgeInsets insets = UIEdgeInsetsZero;
UIView *view = AppDelegate.viewController.godotView;
if ([view respondsToSelector:@selector(safeAreaInsets)]) {
insets = [view safeAreaInsets];
}
float scale = [UIScreen mainScreen].nativeScale;
Size2i insets_position = Size2i(insets.left, insets.top) * scale;
Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
return Rect2i(insets_position, get_window_size() - insets_size);
} else {
return Rect2i(Size2i(0, 0), get_window_size());
}
}
bool OSIPhone::has_touchscreen_ui_hint() const {
return true;
}
@ -550,79 +580,82 @@ String OSIPhone::get_locale() const {
return String::utf8([localeIdentifier UTF8String]).replace("-", "_");
}
extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
extern bool _is_video_playing();
extern void _pause_video();
extern void _unpause_video();
extern void _stop_video();
extern void _focus_out_video();
Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
bool exists = f && f->is_open();
String tempFile = get_user_data_dir();
if (!exists)
String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
if (!exists) {
return FAILED;
}
String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
if (p_path.begins_with("res://")) {
if (PackedData::get_singleton()->has_path(p_path)) {
print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data());
return ERR_INVALID_PARAMETER;
} else {
p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
}
} else if (p_path.begins_with("user://"))
p_path = p_path.replace("user:/", get_user_data_dir());
} else if (p_path.begins_with("user://")) {
p_path = p_path.replace("user:/", user_data_dir);
}
memdelete(f);
print("Playing video: %S\n", p_path.c_str());
if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
printf("Playing video: %s\n", p_path.utf8().get_data());
String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
NSString *filePath = [[NSString alloc] initWithUTF8String:file_path.utf8().get_data()];
NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
if (![AppDelegate.viewController playVideoAtPath:filePath
volume:p_volume
audio:audioTrack
subtitle:subtitleTrack]) {
return OK;
}
return FAILED;
}
bool OSIPhone::native_video_is_playing() const {
return _is_video_playing();
return [AppDelegate.viewController.videoView isVideoPlaying];
}
void OSIPhone::native_video_pause() {
if (native_video_is_playing())
_pause_video();
if (native_video_is_playing()) {
[AppDelegate.viewController.videoView pauseVideo];
}
}
void OSIPhone::native_video_unpause() {
_unpause_video();
};
[AppDelegate.viewController.videoView unpauseVideo];
}
void OSIPhone::native_video_focus_out() {
_focus_out_video();
};
[AppDelegate.viewController.videoView unfocusVideo];
}
void OSIPhone::native_video_stop() {
if (native_video_is_playing())
_stop_video();
if (native_video_is_playing()) {
[AppDelegate.viewController.videoView stopVideo];
}
}
void OSIPhone::vibrate_handheld(int p_duration_ms) {
// iOS does not support duration for vibration
_vibrate();
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
return p_feature == "mobile";
}
// Initialization order between compilation units is not guaranteed,
// so we use this as a hack to ensure certain code is called before
// everything else, but after all units are initialized.
typedef void (*init_callback)();
static init_callback *ios_init_callbacks = NULL;
static int ios_init_callbacks_count = 0;
static int ios_init_callbacks_capacity = 0;
void add_ios_init_callback(init_callback cb) {
if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
@ -637,7 +670,7 @@ void add_ios_init_callback(init_callback cb) {
}
}
OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
OSIPhone::OSIPhone(String p_data_dir) {
for (int i = 0; i < ios_init_callbacks_count; ++i) {
ios_init_callbacks[i]();
}
@ -649,15 +682,6 @@ OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
main_loop = NULL;
visual_server = NULL;
VideoMode vm;
vm.fullscreen = true;
vm.width = width;
vm.height = height;
vm.resizable = false;
set_video_mode(vm);
event_count = 0;
virtual_keyboard_height = 0;
// can't call set_data_dir from here, since it requires DirAccess
// which is initialized in initialize_core
data_dir = p_data_dir;
@ -676,4 +700,40 @@ OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
OSIPhone::~OSIPhone() {
}
void OSIPhone::on_focus_out() {
if (is_focused) {
is_focused = false;
if (get_main_loop()) {
get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
}
[AppDelegate.viewController.godotView stopRendering];
if (native_video_is_playing()) {
native_video_focus_out();
}
audio_driver.stop();
}
}
void OSIPhone::on_focus_in() {
if (!is_focused) {
is_focused = true;
if (get_main_loop()) {
get_main_loop()->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
}
[AppDelegate.viewController.godotView startRendering];
if (native_video_is_playing()) {
native_video_unpause();
}
audio_driver.start();
}
}
#endif

View File

@ -36,3 +36,10 @@
#define PLATFORM_REFCOUNT
#define PTHREAD_RENAME_SELF
#define _weakify(var) __weak typeof(var) GDWeak_##var = var;
#define _strongify(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong typeof(var) var = GDWeak_##var; \
_Pragma("clang diagnostic pop")

View File

@ -32,10 +32,15 @@
#import <UIKit/UIKit.h>
@class GodotView;
@class GodotNativeVideoView;
@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
};
@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
- (GodotView *)godotView;
@property(nonatomic, readonly, strong) GodotView *godotView;
@property(nonatomic, readonly, strong) GodotNativeVideoView *videoView;
// MARK: Native Video Player
- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
@end

View File

@ -30,53 +30,17 @@
#import "view_controller.h"
#include "core/project_settings.h"
#import "godot_view.h"
#import "godot_view_renderer.h"
#import "native_video_view.h"
#include "os_iphone.h"
#include "core/project_settings.h"
extern "C" {
int add_path(int, char **);
int add_cmdline(int, char **);
int add_path(int p_argc, char **p_args) {
NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
if (!str) {
return p_argc;
}
p_args[p_argc++] = (char *)"--path";
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
p_args[p_argc] = NULL;
return p_argc;
};
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
if (!arr) {
return p_argc;
}
for (NSUInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str) {
continue;
}
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
}
p_args[p_argc] = NULL;
return p_argc;
};
}; // extern "C"
@interface ViewController ()
@property(strong, nonatomic) GodotViewRenderer *renderer;
@property(strong, nonatomic) GodotNativeVideoView *videoView;
@end
@implementation ViewController
@ -85,6 +49,42 @@ int add_cmdline(int p_argc, char **p_args) {
return (GodotView *)self.view;
}
- (void)loadView {
GodotView *view = [[GodotView alloc] init];
[view initializeRendering];
GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
self.renderer = renderer;
self.view = view;
view.renderer = self.renderer;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
// Initialize view controller values.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
@ -93,16 +93,48 @@ int add_cmdline(int p_argc, char **p_args) {
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
- (void)observeKeyboard {
printf("******** adding observer for keyboard show/hide\n");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardOnScreen:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(keyboardHidden:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)dealloc {
[self.videoView stopVideo];
self.videoView = nil;
self.videoView = nil;
self.renderer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// MARK: Orientation
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
if (!OSIPhone::get_singleton()) {
return NO;
}
switch (OS::get_singleton()->get_screen_orientation()) {
case OS::SCREEN_SENSOR:
case OS::SCREEN_SENSOR_LANDSCAPE:
@ -111,9 +143,13 @@ int add_cmdline(int p_argc, char **p_args) {
default:
return NO;
}
};
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if (!OSIPhone::get_singleton()) {
return UIInterfaceOrientationMaskAll;
}
switch (OS::get_singleton()->get_screen_orientation()) {
case OS::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
@ -130,7 +166,7 @@ int add_cmdline(int p_argc, char **p_args) {
case OS::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
}
- (BOOL)prefersStatusBarHidden {
return YES;
@ -144,6 +180,41 @@ int add_cmdline(int p_argc, char **p_args) {
}
}
// MARK: Keyboard
- (void)keyboardOnScreen:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
CGRect rawFrame = [value CGRectValue];
CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
if (OSIPhone::get_singleton()) {
OSIPhone::get_singleton()->set_virtual_keyboard_height(keyboardFrame.size.height);
}
}
- (void)keyboardHidden:(NSNotification *)notification {
if (OSIPhone::get_singleton()) {
OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
}
}
// MARK: Native Video Player
- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
// If we are showing some video already, reuse existing view for new video.
if (self.videoView) {
return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
} else {
// Create autoresizing view for video playback.
GodotNativeVideoView *videoView = [[GodotNativeVideoView alloc] initWithFrame:self.view.bounds];
videoView.autoresizingMask = UIViewAutoresizingFlexibleWidth & UIViewAutoresizingFlexibleHeight;
[self.view addSubview:videoView];
return [self.videoView playVideoAtPath:filePath volume:videoVolume audio:audioTrack subtitle:subtitleTrack];
}
}
#ifdef GAME_CENTER_ENABLED
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone