iOS: Vulkan support

Implemented Vulkan Support.
Use DisplayServer for rendering and input handling
Use single view for rendering in both GLES2 (not supported yet) and Vulkan
Use @available checks where it's required (otherwise compiler would fail compilation)
Simulator checks
This commit is contained in:
Sergey Minakov 2020-07-15 21:59:57 +03:00
parent 8dc2b267f9
commit bfc005d462
32 changed files with 3323 additions and 2366 deletions

View file

@ -3,10 +3,8 @@
Import("env")
iphone_lib = [
"godot_iphone.cpp",
"os_iphone.cpp",
"semaphore_iphone.cpp",
"gl_view.mm",
"godot_iphone.mm",
"os_iphone.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
@ -15,6 +13,11 @@ iphone_lib = [
"icloud.mm",
"ios.mm",
"vulkan_context_iphone.mm",
"display_server_iphone.mm",
"joypad_iphone.mm",
"godot_view.mm",
"display_layer.mm",
"godot_view_renderer.mm",
]
env_ios = env.Clone()

View file

@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#if defined(OPENGL_ENABLED)
#import "gl_view.h"
#endif
#import "view_controller.h"
#import <UIKit/UIKit.h>
#import <CoreMotion/CoreMotion.h>
@class ViewController;
// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
// so it can't be done with compilation time branching.
//#if defined(OPENGL_ENABLED)
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
//#endif
#if defined(VULKAN_ENABLED)
@interface AppDelegate : NSObject <UIApplicationDelegate> {
#endif
//@property (strong, nonatomic) UIWindow *window;
ViewController *view_controller;
bool is_focus_out;
};
//#if defined(VULKAN_ENABLED)
@interface AppDelegate : NSObject <UIApplicationDelegate>
//#endif
@property(strong, nonatomic) UIWindow *window;
+ (ViewController *)getViewController;
@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end

View file

@ -29,644 +29,60 @@
/*************************************************************************/
#import "app_delegate.h"
#include "core/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#if defined(OPENGL_ENABLED)
#import "gl_view.h"
#endif
#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 *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
return ERR_CANT_OPEN;
printf("opening url %ls\n", p_uri.c_str());
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
[url release];
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;
+ (ViewController *)getViewController {
+ (ViewController *)viewController {
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.view 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.view 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_A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_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_AXIS_LEFT_X, jx);
jx.value = -gamepad.leftThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
} else if (element == gamepad.rightThumbstick) {
jx.value = gamepad.rightThumbstick.xAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
jx.value = -gamepad.rightThumbstick.yAxis.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
} else if (element == gamepad.leftTrigger) {
jx.value = gamepad.leftTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
} else if (element == gamepad.rightTrigger) {
jx.value = gamepad.rightTrigger.value;
OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
};
};
} else if (controller.gamepad != nil) {
// gamepad is the standard profile with 4 buttons, shoulder buttons and a
// D-pad
controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
GCControllerElement *element) {
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
};
#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
// while we are setting that as the minimum, seems our
// build environment doesn't like it
} 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_A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonX) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.dpad) {
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
gamepad.dpad.up.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
gamepad.dpad.down.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
gamepad.dpad.left.isPressed);
OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
};
#endif
};
///@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;
#if defined(OPENGL_ENABLED)
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
#endif
OS::VideoMode vm;
vm.fullscreen = true;
vm.width = backingWidth;
vm.height = backingHeight;
vm.resizable = false;
return vm;
};
static int frame_count = 0;
- (void)drawView:(UIView *)view;
{
switch (frame_count) {
case 0: {
OS::get_singleton()->set_video_mode(_get_video_mode());
if (!OS::get_singleton()) {
exit(0);
};
++frame_count;
NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
OSIPhone::get_singleton()->set_locale(
String::utf8([locale_code UTF8String]));
NSString *uuid;
if ([[UIDevice currentDevice]
respondsToSelector:@selector(identifierForVendor)]) {
uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
} else {
// before iOS 6, so just generate an identifier and store it
uuid = [[NSUserDefaults standardUserDefaults]
objectForKey:@"identiferForVendor"];
if (!uuid) {
CFUUIDRef cfuuid = CFUUIDCreate(NULL);
uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
CFRelease(cfuuid);
[[NSUserDefaults standardUserDefaults]
setObject:uuid
forKey:@"identifierForVendor"];
}
}
OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
}; 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 UIDeviceOrientationLandscapeLeft: {
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 UIDeviceOrientationLandscapeRight: {
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 UIDeviceOrientationPortraitUpsideDown: {
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;
};
}
bool quit_request = 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];
// TODO: might be required to make an early return, so app wouldn't crash because of timeout.
// TODO: logo screen is not displayed while shaders are compiling
// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
is_focus_out = false;
[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
// disable idle timer
// application.idleTimerDisabled = YES;
CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
window = [[UIWindow alloc] initWithFrame:rect];
self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
OS::VideoMode vm = _get_video_mode();
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
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;
return NO;
};
#if defined(OPENGL_ENABLED)
// WARNING: We must *always* create the GLView after we have constructed the
// OS with iphone_main. This allows the GLView to access project settings so
// it can properly initialize the OpenGL context
GLView *glView = [[GLView alloc] initWithFrame:rect];
glView.delegate = self;
ViewController *viewController = [[ViewController alloc] init];
viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
view_controller = [[ViewController alloc] init];
view_controller.view = glView;
_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];
#endif
#if defined(VULKAN_ENABLED)
view_controller = [[ViewController alloc] init];
#endif
window.rootViewController = view_controller;
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
@ -674,40 +90,33 @@ 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];
return TRUE;
return YES;
};
- (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();
};
@ -722,15 +131,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];
self.window = nil;
[super dealloc];
}

View file

@ -143,6 +143,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,58 @@
/*************************************************************************/
/* 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)renderDisplayLayer;
- (void)initializeDisplayLayer;
- (void)layoutDisplayLayer;
@end
// An ugly workaround for iOS simulator
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
#if defined(__IPHONE_13_0)
API_AVAILABLE(ios(13.0))
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
#else
@interface GodotMetalLayer : CALayer <DisplayLayer>
#endif
#else
@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
#endif
@end
API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
@end

View file

@ -0,0 +1,186 @@
/*************************************************************************/
/* 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 "display_server_iphone.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>
@implementation GodotMetalLayer
- (void)initializeDisplayLayer {
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
if (@available(iOS 13, *)) {
// Simulator supports Metal since iOS 13
} else {
NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
}
#endif
}
- (void)layoutDisplayLayer {
}
- (void)renderDisplayLayer {
}
@end
@implementation GodotOpenGLLayer {
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
EAGLContext *context;
GLuint viewRenderbuffer, viewFramebuffer;
GLuint depthRenderbuffer;
}
- (void)initializeDisplayLayer {
// Get our backing layer
// 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];
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
// Create GL ES 2 context
if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
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)renderDisplayLayer {
[EAGLContext setCurrentContext:context];
}
- (void)dealloc {
if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}
if (context) {
[context release];
context = nil;
}
[super dealloc];
}
- (BOOL)createFramebuffer {
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 CAself)
// 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

@ -0,0 +1,202 @@
/*************************************************************************/
/* display_server_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. */
/*************************************************************************/
#ifndef display_server_iphone_h
#define display_server_iphone_h
#include "core/input/input.h"
#include "servers/display_server.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
#include "vulkan_context_iphone.h"
#import <QuartzCore/CAMetalLayer.h>
#include <vulkan/vulkan_metal.h>
#endif
class DisplayServerIPhone : public DisplayServer {
GDCLASS(DisplayServerIPhone, DisplayServer)
_THREAD_SAFE_CLASS_
#if defined(VULKAN_ENABLED)
VulkanContextIPhone *context_vulkan;
RenderingDeviceVulkan *rendering_device_vulkan;
#endif
DisplayServer::ScreenOrientation screen_orientation;
ObjectID window_attached_instance_id;
Callable window_event_callback;
Callable window_resize_callback;
Callable input_event_callback;
Callable input_text_callback;
int virtual_keyboard_height = 0;
void perform_event(const Ref<InputEvent> &p_event);
DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
~DisplayServerIPhone();
public:
String rendering_driver;
static DisplayServerIPhone *get_singleton();
static void register_iphone_driver();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
static Vector<String> get_rendering_drivers_func();
// MARK: - Events
virtual void process_events() override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void send_input_event(const Ref<InputEvent> &p_event) const;
void send_input_text(const String &p_text) const;
void send_window_event(DisplayServer::WindowEvent p_event) const;
void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
// MARK: - Input
// MARK: Touches
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(int p_idx);
// MARK: Keyboard
void key(uint32_t p_key, bool p_pressed);
// MARK: Motion
void update_gravity(float p_x, float p_y, float p_z);
void update_accelerometer(float p_x, float p_y, float p_z);
void update_magnetometer(float p_x, float p_y, float p_z);
void update_gyroscope(float p_x, float p_y, float p_z);
// MARK: -
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
virtual int get_screen_count() const override;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
virtual WindowID
get_window_at_screen_position(const Point2i &p_position) const override;
virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual float screen_get_max_scale() const override;
virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual bool can_any_window_draw() const override;
virtual bool screen_is_touchscreen(int p_screen) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) override;
virtual void virtual_keyboard_hide() override;
void virtual_keyboard_set_height(int height);
virtual int virtual_keyboard_get_height() const override;
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
virtual bool native_video_is_playing() const override;
virtual void native_video_pause() override;
virtual void native_video_unpause() override;
virtual void native_video_stop() override;
void resize_window(CGSize size);
};
#endif /* display_server_iphone_h */

View file

@ -0,0 +1,751 @@
/*************************************************************************/
/* display_server_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. */
/*************************************************************************/
#include "display_server_iphone.h"
#import "app_delegate.h"
#include "core/io/file_access_pack.h"
#include "core/project_settings.h"
#import "godot_view.h"
#include "ios.h"
#include "os_iphone.h"
#import "view_controller.h"
#import <Foundation/Foundation.h>
#import <sys/utsname.h>
static const float kDisplayServerIPhoneAcceleration = 1;
static NSDictionary *iOSModelToDPI = @{
@[
@"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,
};
DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
return (DisplayServerIPhone *)DisplayServer::get_singleton();
}
DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
rendering_driver = p_rendering_driver;
#if defined(OPENGL_ENABLED)
// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
// again,
if (rendering_driver == "opengl_es") {
bool gl_initialization_error = false;
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
if (RasterizerGLES2::is_viable() == OK) {
RasterizerGLES2::register_config();
RasterizerGLES2::make_current();
} else {
gl_initialization_error = true;
}
if (gl_initialization_error) {
OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
// return ERR_UNAVAILABLE;
}
// rendering_server = memnew(RenderingServerRaster);
// // FIXME: Reimplement threaded rendering
// if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
// rendering_server = memnew(RenderingServerWrapMT(rendering_server,
// false));
// }
// rendering_server->init();
// rendering_server->cursor_set_visible(false, 0);
// reset this to what it should be, it will have been set to 0 after
// rendering_server->init() is called
// RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
}
#endif
#if defined(VULKAN_ENABLED)
rendering_driver = "vulkan";
context_vulkan = nullptr;
rendering_device_vulkan = nullptr;
if (rendering_driver == "vulkan") {
context_vulkan = memnew(VulkanContextIPhone);
if (context_vulkan->initialize() != OK) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to initialize Vulkan context");
}
CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
if (!layer) {
ERR_FAIL_MSG("Failed to create iOS rendering layer.");
}
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
memdelete(context_vulkan);
context_vulkan = nullptr;
ERR_FAIL_MSG("Failed to create Vulkan window.");
}
rendering_device_vulkan = memnew(RenderingDeviceVulkan);
rendering_device_vulkan->initialize(context_vulkan);
RasterizerRD::make_current();
}
#endif
bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
screen_set_keep_on(keep_screen_on);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
}
DisplayServerIPhone::~DisplayServerIPhone() {
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
if (rendering_device_vulkan) {
rendering_device_vulkan->finalize();
memdelete(rendering_device_vulkan);
rendering_device_vulkan = NULL;
}
if (context_vulkan) {
context_vulkan->window_destroy(MAIN_WINDOW_ID);
memdelete(context_vulkan);
context_vulkan = NULL;
}
}
#endif
}
DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
}
Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
Vector<String> drivers;
#if defined(VULKAN_ENABLED)
drivers.push_back("vulkan");
#endif
#if defined(OPENGL_ENABLED)
drivers.push_back("opengl_es");
#endif
return drivers;
}
void DisplayServerIPhone::register_iphone_driver() {
register_create_function("iphone", create_func, get_rendering_drivers_func);
}
// MARK: Events
void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
window_resize_callback = p_callable;
}
void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
window_event_callback = p_callable;
}
void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
input_event_callback = p_callable;
}
void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
input_text_callback = p_callable;
}
void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerIPhone::process_events() {
}
void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
DisplayServerIPhone::get_singleton()->send_input_event(p_event);
}
void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
_window_callback(input_event_callback, p_event);
}
void DisplayServerIPhone::send_input_text(const String &p_text) const {
_window_callback(input_text_callback, p_text);
}
void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
_window_callback(window_event_callback, int(p_event));
}
void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
if (!p_callable.is_null()) {
const Variant *argp = &p_arg;
Variant ret;
Callable::CallError ce;
p_callable.call((const Variant **)&argp, 1, ret, ce);
}
}
// MARK: - Input
// MARK: Touches
void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
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));
perform_event(ev);
}
};
void DisplayServerIPhone::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)) {
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));
perform_event(ev);
};
};
void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
Input::get_singleton()->parse_input_event(p_event);
};
void DisplayServerIPhone::touches_cancelled(int p_idx) {
touch_press(p_idx, -1, -1, false, false);
};
// MARK: Keyboard
void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
Ref<InputEventKey> ev;
ev.instance();
ev->set_echo(false);
ev->set_pressed(p_pressed);
ev->set_keycode(p_key);
ev->set_physical_keycode(p_key);
ev->set_unicode(p_key);
perform_event(ev);
};
// MARK: Motion
void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
};
void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
float p_z) {
// Found out the Z should not be negated! Pass as is!
Vector3 v_accelerometer = Vector3(
p_x / kDisplayServerIPhoneAcceleration,
p_y / kDisplayServerIPhoneAcceleration,
p_z / kDisplayServerIPhoneAcceleration);
Input::get_singleton()->set_accelerometer(v_accelerometer);
/*
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 DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
};
void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
};
// MARK: -
bool DisplayServerIPhone::has_feature(Feature p_feature) const {
switch (p_feature) {
// case FEATURE_CONSOLE_WINDOW:
// case FEATURE_CURSOR_SHAPE:
// case FEATURE_CUSTOM_CURSOR_SHAPE:
// case FEATURE_GLOBAL_MENU:
// case FEATURE_HIDPI:
// case FEATURE_ICON:
// case FEATURE_IME:
// case FEATURE_MOUSE:
// case FEATURE_MOUSE_WARP:
// case FEATURE_NATIVE_DIALOG:
// case FEATURE_NATIVE_ICON:
// case FEATURE_NATIVE_VIDEO:
// case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_ORIENTATION:
case FEATURE_TOUCHSCREEN:
case FEATURE_VIRTUAL_KEYBOARD:
return true;
default:
return false;
}
}
String DisplayServerIPhone::get_name() const {
return "iPhone";
}
void DisplayServerIPhone::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());
}
int DisplayServerIPhone::get_screen_count() const {
return 1;
}
Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
return Size2i();
}
Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
if (!layer) {
return Size2i();
}
return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
}
Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
struct utsname systemInfo;
uname(&systemInfo);
NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
for (NSArray *keyArray in iOSModelToDPI) {
if ([keyArray containsObject:string]) {
NSNumber *value = iOSModelToDPI[keyArray];
return [value intValue];
}
}
return 163;
}
float DisplayServerIPhone::screen_get_scale(int p_screen) const {
return [UIScreen mainScreen].nativeScale;
}
Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
Vector<DisplayServer::WindowID> list;
list.push_back(MAIN_WINDOW_ID);
return list;
}
DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
return MAIN_WINDOW_ID;
}
void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
window_attached_instance_id = p_instance;
}
ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
return window_attached_instance_id;
}
void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
// Probably not supported for iOS
}
int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
return SCREEN_OF_MAIN_WINDOW;
}
void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
// Probably not supported for iOS
}
Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
return Point2i();
}
void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
// Probably not supported for single window iOS app
}
void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
// Probably not supported for iOS
}
void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
return Size2i();
}
void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
// Probably not supported for iOS
}
Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
CGRect screenBounds = [UIScreen mainScreen].bounds;
return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
}
Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
return window_get_size(p_window);
}
void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
// Probably not supported for iOS
}
DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
return WindowMode::WINDOW_MODE_FULLSCREEN;
}
bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
return false;
}
void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
// Probably not supported for iOS
}
bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
return false;
}
void DisplayServerIPhone::window_request_attention(WindowID p_window) {
// Probably not supported for iOS
}
void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
// Probably not supported for iOS
}
float DisplayServerIPhone::screen_get_max_scale() const {
return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
};
void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
screen_orientation = p_orientation;
}
DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
return screen_orientation;
}
bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
return true;
}
bool DisplayServerIPhone::can_any_window_draw() const {
return true;
}
bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
return true;
}
void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
}
void DisplayServerIPhone::virtual_keyboard_hide() {
[AppDelegate.viewController.godotView resignFirstResponder];
}
void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
virtual_keyboard_height = height * screen_get_max_scale();
}
int DisplayServerIPhone::virtual_keyboard_get_height() const {
return virtual_keyboard_height;
}
void DisplayServerIPhone::clipboard_set(const String &p_text) {
[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
}
String DisplayServerIPhone::clipboard_get() const {
NSString *text = [UIPasteboard generalPasteboard].string;
return String::utf8([text UTF8String]);
}
void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
[UIApplication sharedApplication].idleTimerDisabled = p_enable;
}
bool DisplayServerIPhone::screen_is_kept_on() const {
return [UIApplication sharedApplication].idleTimerDisabled;
}
Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
bool exists = f && f->is_open();
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)) {
printf("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
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:/", user_data_dir);
}
memdelete(f);
printf("Playing video: %S\n", p_path.c_str());
String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease];
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 DisplayServerIPhone::native_video_is_playing() const {
return [AppDelegate.viewController isVideoPlaying];
}
void DisplayServerIPhone::native_video_pause() {
if (native_video_is_playing()) {
[AppDelegate.viewController pauseVideo];
}
}
void DisplayServerIPhone::native_video_unpause() {
[AppDelegate.viewController unpauseVideo];
};
void DisplayServerIPhone::native_video_stop() {
if (native_video_is_playing()) {
[AppDelegate.viewController stopVideo];
}
}
void DisplayServerIPhone::resize_window(CGSize viewSize) {
Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
#if defined(VULKAN_ENABLED)
if (rendering_driver == "vulkan") {
if (context_vulkan) {
context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
}
}
#endif
Variant resize_rect = Rect2i(Point2i(), size);
_window_callback(window_resize_callback, resize_rect);
}

View file

@ -51,12 +51,12 @@ public:
void connect();
bool is_authenticated();
Error post_score(Variant p_score);
Error award_achievement(Variant p_params);
Error post_score(Dictionary p_score);
Error award_achievement(Dictionary p_params);
void reset_achievements();
void request_achievements();
void request_achievement_descriptions();
Error show_game_center(Variant p_params);
Error show_game_center(Dictionary p_params);
Error request_identity_verification_signature();
void game_center_closed();

View file

@ -47,13 +47,15 @@ extern "C" {
#import "app_delegate.h"
};
#import "view_controller.h"
GameCenter *GameCenter::instance = NULL;
void GameCenter::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
@ -105,7 +107,14 @@ void GameCenter::connect() {
ret["type"] = "authentication";
if (player.isAuthenticated) {
ret["result"] = "ok";
ret["player_id"] = [player.playerID UTF8String];
if (@available(iOS 13, *)) {
ret["player_id"] = [player.teamPlayerID UTF8String];
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
ret["player_id"] = [player.playerID UTF8String];
#endif
}
GameCenter::get_singleton()->authenticated = true;
} else {
ret["result"] = "error";
@ -123,11 +132,10 @@ bool GameCenter::is_authenticated() {
return authenticated;
};
Error GameCenter::post_score(Variant p_score) {
Dictionary params = p_score;
ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
float score = params["score"];
String category = params["category"];
Error GameCenter::post_score(Dictionary p_score) {
ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
float score = p_score["score"];
String category = p_score["category"];
NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
@ -153,11 +161,10 @@ Error GameCenter::post_score(Variant p_score) {
return OK;
};
Error GameCenter::award_achievement(Variant p_params) {
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
String name = params["name"];
float progress = params["progress"];
Error GameCenter::award_achievement(Dictionary p_params) {
ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
String name = p_params["name"];
float progress = p_params["progress"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
@ -167,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {
achievement.percentComplete = progress;
achievement.showsCompletionBanner = NO;
if (params.has("show_completion_banner")) {
achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
if (p_params.has("show_completion_banner")) {
achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
}
[GKAchievement reportAchievements:@[ achievement ]
@ -202,7 +209,7 @@ void GameCenter::request_achievement_descriptions() {
Array hidden;
Array replayable;
for (int i = 0; i < [descriptions count]; i++) {
for (NSUInteger i = 0; i < [descriptions count]; i++) {
GKAchievementDescription *description = [descriptions objectAtIndex:i];
const char *str = [description.identifier UTF8String];
@ -250,7 +257,7 @@ void GameCenter::request_achievements() {
PackedStringArray names;
PackedFloat32Array percentages;
for (int i = 0; i < [achievements count]; i++) {
for (NSUInteger i = 0; i < [achievements count]; i++) {
GKAchievement *achievement = [achievements objectAtIndex:i];
const char *str = [achievement.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
@ -285,14 +292,12 @@ void GameCenter::reset_achievements() {
}];
};
Error GameCenter::show_game_center(Variant p_params) {
Error GameCenter::show_game_center(Dictionary p_params) {
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
Dictionary params = p_params;
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
if (params.has("view")) {
String view_name = params["view"];
if (p_params.has("view")) {
String view_name = p_params["view"];
if (view_name == "default") {
view_state = GKGameCenterViewControllerStateDefault;
} else if (view_name == "leaderboards") {
@ -306,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {
}
}
GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];
ERR_FAIL_COND_V(!controller, FAILED);
ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
@ -316,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {
controller.viewState = view_state;
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
controller.leaderboardIdentifier = nil;
if (params.has("leaderboard_name")) {
String name = params["leaderboard_name"];
if (p_params.has("leaderboard_name")) {
String name = p_params["leaderboard_name"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
controller.leaderboardIdentifier = name_str;
}
@ -341,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
ret["timestamp"] = timestamp;
ret["player_id"] = [player.playerID UTF8String];
if (@available(iOS 13, *)) {
ret["player_id"] = [player.teamPlayerID UTF8String];
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
ret["player_id"] = [player.playerID UTF8String];
#endif
}
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;

View file

@ -1,123 +0,0 @@
/*************************************************************************/
/* gl_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 <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import <UIKit/UIKit.h>
@protocol GLViewDelegate;
@interface GLView : UIView <UIKeyInput> {
@private
// The pixel dimensions of the backbuffer
GLint backingWidth;
GLint backingHeight;
EAGLContext *context;
// OpenGL names for the renderbuffer and framebuffers used to render to this view
GLuint viewRenderbuffer, viewFramebuffer;
// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
GLuint depthRenderbuffer;
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;
// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
// Only used if CADisplayLink is not
NSTimer *animationTimer;
NSTimeInterval animationInterval;
// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
id<GLViewDelegate> delegate;
// 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;
}
@property(nonatomic, assign) id<GLViewDelegate> 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 NSTimeInterval animationInterval;
@property(nonatomic, assign) BOOL useCADisplayLink;
@end
@protocol GLViewDelegate <NSObject>
@required
// Draw with OpenGL ES
- (void)drawView:(GLView *)view;
@optional
// Called whenever you need to do some initialization before rendering.
- (void)setupView:(GLView *)view;
@end

View file

@ -1,702 +0,0 @@
/*************************************************************************/
/* gl_view.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 "gl_view.h"
#include "core/os/keyboard.h"
#include "core/project_settings.h"
#include "os_iphone.h"
#include "servers/audio_server.h"
#import <OpenGLES/EAGLDrawable.h>
#import <QuartzCore/QuartzCore.h>
/*
@interface GLView (private)
- (id)initGLES;
- (BOOL)createFramebuffer;
- (void)destroyFramebuffer;
@end
*/
int gl_view_base_fb;
static String keyboard_text;
static GLView *_instance = NULL;
static bool video_found_error = false;
static bool video_playing = false;
static CMTime video_current_time;
void _show_keyboard(String);
void _hide_keyboard();
bool _play_video(String, float, String, String);
bool _is_video_playing();
void _pause_video();
void _focus_out_video();
void _unpause_video();
void _stop_video();
CGFloat _points_to_pixels(CGFloat);
void _show_keyboard(String p_existing) {
keyboard_text = p_existing;
printf("instance on show is %p\n", _instance);
[_instance open_keyboard];
};
void _hide_keyboard() {
printf("instance on hide is %p\n", _instance);
[_instance hide_keyboard];
keyboard_text = "";
};
Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
insets = [_instance safeAreaInsets];
}
ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
Rect2(0, 0, p_window_width, p_window_height));
UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
}
bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
_instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
_instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
[_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
_instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
_instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
[_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
[[NSNotificationCenter defaultCenter]
addObserver:_instance
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[_instance.avPlayer currentItem]];
[_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
[_instance.avPlayerLayer setFrame:_instance.bounds];
[_instance.layer addSublayer:_instance.avPlayerLayer];
[_instance.avPlayer play];
AVMediaSelectionGroup *audioGroup = [_instance.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:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:p_volume atTime:kCMTimeZero];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
[audioMix setInputParameters:allAudioParams];
[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
[_instance.avPlayer.currentItem setAudioMix:audioMix];
break;
}
}
AVMediaSelectionGroup *subtitlesGroup = [_instance.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:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
break;
}
}
video_playing = true;
return true;
}
bool _is_video_playing() {
if (_instance.avPlayer.error) {
printf("Error during playback\n");
}
return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
}
void _pause_video() {
video_current_time = _instance.avPlayer.currentTime;
[_instance.avPlayer pause];
video_playing = false;
}
void _focus_out_video() {
printf("focus out pausing video\n");
[_instance.avPlayer pause];
};
void _unpause_video() {
[_instance.avPlayer play];
video_playing = true;
};
void _stop_video() {
[_instance.avPlayer pause];
[_instance.avPlayerLayer removeFromSuperlayer];
_instance.avPlayer = nil;
video_playing = false;
}
CGFloat _points_to_pixels(CGFloat points) {
float pixelPerInch;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
pixelPerInch = 132;
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
pixelPerInch = 163;
} else {
pixelPerInch = 160;
}
CGFloat pointsPerInch = 72.0;
return (points / pointsPerInch * pixelPerInch);
}
@implementation GLView
@synthesize animationInterval;
static const int max_touches = 8;
static UITouch *touches[max_touches];
static void init_touches() {
for (int i = 0; i < max_touches; i++) {
touches[i] = NULL;
};
};
static int get_touch_id(UITouch *p_touch) {
int first = -1;
for (int i = 0; i < max_touches; i++) {
if (first == -1 && touches[i] == NULL) {
first = i;
continue;
};
if (touches[i] == p_touch)
return i;
};
if (first != -1) {
touches[first] = p_touch;
return first;
};
return -1;
};
static int remove_touch(UITouch *p_touch) {
int remaining = 0;
for (int i = 0; i < max_touches; i++) {
if (touches[i] == NULL)
continue;
if (touches[i] == p_touch)
touches[i] = NULL;
else
++remaining;
};
return remaining;
};
static void clear_touches() {
for (int i = 0; i < max_touches; i++) {
touches[i] = NULL;
};
};
// Implement this to override the default layer class (which is [CALayer class]).
// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
+ (Class)layerClass {
return [CAEAGLLayer class];
}
//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder *)coder {
active = FALSE;
if ((self = [super initWithCoder:coder])) {
self = [self initGLES];
}
return self;
}
- (id)initGLES {
// Get our backing layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat,
nil];
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
// Create GL ES 2 context
if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
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 nil;
}
}
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"Failed to set EAGLContext!");
return nil;
}
if (![self createFramebuffer]) {
NSLog(@"Failed to create frame buffer!");
return nil;
}
// Default the animation interval to 1/60th of a second.
animationInterval = 1.0 / 60.0;
return self;
}
- (id<GLViewDelegate>)delegate {
return delegate;
}
// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
- (void)setDelegate:(id<GLViewDelegate>)d {
delegate = d;
delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
}
@synthesize useCADisplayLink;
// If our view is resized, we'll be asked to layout subviews.
// This is the perfect opportunity to also update the framebuffer so that it is
// the same size as our display area.
- (void)layoutSubviews {
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];
[self drawView];
}
- (BOOL)createFramebuffer {
// Generate IDs for a framebuffer object and a color renderbuffer
UIScreen *mainscr = [UIScreen mainScreen];
printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
self.contentScaleFactor = mainscr.nativeScale;
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.layer];
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;
}
}
- (void)startAnimation {
if (active)
return;
active = TRUE;
printf("start animation!\n");
if (useCADisplayLink) {
// Approximate frame rate
// assumes device refreshes at 60 fps
int frameInterval = (int)floor(animationInterval * 60.0f);
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
[displayLink setFrameInterval:frameInterval];
// Setup DisplayLink in main thread
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
if (video_playing) {
_unpause_video();
}
}
- (void)stopAnimation {
if (!active)
return;
active = FALSE;
printf("******** stop animation!\n");
if (useCADisplayLink) {
[displayLink invalidate];
displayLink = nil;
} else {
[animationTimer invalidate];
animationTimer = nil;
}
clear_touches();
if (video_playing) {
// save position
}
}
- (void)setAnimationInterval:(NSTimeInterval)interval {
animationInterval = interval;
if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
[self stopAnimation];
[self startAnimation];
}
}
// Updates the OpenGL view when the timer fires
- (void)drawView {
if (!active) {
printf("draw view not active!\n");
return;
};
if (useCADisplayLink) {
// Pause the CADisplayLink to avoid recursion
[displayLink setPaused:YES];
// Process all input events
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
;
// We are good to go, resume the CADisplayLink
[displayLink setPaused:NO];
}
// Make sure that you are drawing to the current context
[EAGLContext setCurrentContext:context];
// If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
if (!delegateSetup) {
[delegate setupView:self];
delegateSetup = YES;
}
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
[delegate drawView:self];
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)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [[event allTouches] allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseBegan)
continue;
int tid = get_touch_id(touch);
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
};
};
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [[event allTouches] allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseMoved)
continue;
int tid = get_touch_id(touch);
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
CGPoint prev_point = [touch previousLocationInView:self];
OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
};
};
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [[event allTouches] allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseEnded)
continue;
int tid = get_touch_id(touch);
ERR_FAIL_COND(tid == -1);
remove_touch(touch);
CGPoint touchPoint = [touch locationInView:self];
OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
};
};
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
OSIPhone::get_singleton()->touches_cancelled();
clear_touches();
};
- (BOOL)canBecomeFirstResponder {
return YES;
};
- (void)open_keyboard {
//keyboard_text = p_existing;
[self becomeFirstResponder];
};
- (void)hide_keyboard {
//keyboard_text = p_existing;
[self resignFirstResponder];
};
- (void)keyboardOnScreen:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
CGRect rawFrame = [value CGRectValue];
CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
}
- (void)keyboardHidden:(NSNotification *)notification {
OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
}
- (void)deleteBackward {
if (keyboard_text.length())
keyboard_text.erase(keyboard_text.length() - 1, 1);
OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
};
- (BOOL)hasText {
return keyboard_text.length() ? YES : NO;
};
- (void)insertText:(NSString *)p_text {
String character;
character.parse_utf8([p_text UTF8String]);
keyboard_text = keyboard_text + character;
OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
printf("inserting text with character %lc\n", (CharType)character[0]);
};
- (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 (_is_video_playing()) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[_instance.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;
}
}
// When created via code however, we get initWithFrame
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
_instance = self;
printf("after init super %p\n", self);
if (self != nil) {
self = [self initGLES];
printf("after init gles %p\n", self);
}
init_touches();
self.multipleTouchEnabled = YES;
self.autocorrectionType = UITextAutocorrectionTypeNo;
printf("******** adding observer for sound routing changes\n");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
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];
//self.autoresizesSubviews = YES;
//[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
return self;
}
//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
// return YES;
//}
//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
// return YES;
//}
// Stop animating and release resources when they are no longer needed.
- (void)dealloc {
[self stopAnimation];
if ([EAGLContext currentContext] == context) {
[EAGLContext setCurrentContext:nil];
}
[context release];
context = nil;
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
_stop_video();
video_found_error = true;
}
if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
_instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
//NSLog(@"time: %@", video_current_time);
[_instance.avPlayer seekToTime:video_current_time];
video_current_time = kCMTimeZero;
}
}
if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[_instance.avPlayer play]; // NOTE: change this line according your current player implementation
NSLog(@"resumed play");
});
NSLog(@" . . . PAUSED (or just started)");
}
}
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
_stop_video();
}
@end

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* godot_iphone.cpp */
/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -38,19 +38,53 @@
static OSIPhone *os = nullptr;
extern "C" {
int add_path(int p_argc, char **p_args);
int add_cmdline(int p_argc, char **p_args);
int add_path(int, char **);
int add_cmdline(int, char **);
int iphone_main(int, char **, String);
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";
[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
p_args[p_argc] = NULL;
[str release];
return p_argc;
};
int iphone_main(int, int, int, char **, String);
int add_cmdline(int p_argc, char **p_args) {
NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
if (!arr) {
return p_argc;
}
int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
for (NSUInteger i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str) {
continue;
}
[str retain]; // @todo delete these at some point
p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
[str release];
};
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--) {
if (argv[0][len] == '/')
if (argv[0][len] == '/') {
break;
}
}
if (len >= 0) {
@ -65,7 +99,7 @@ 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);
// We must override main when testing is enabled
TEST_MAIN_OVERRIDE
@ -79,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
argc = add_cmdline(argc, fargv);
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;
}
os->initialize_modules();
return 0;
};

View file

@ -0,0 +1,56 @@
/*************************************************************************/
/* godot_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>
class String;
@protocol DisplayLayer;
@protocol GodotViewRendererProtocol;
@interface GodotView : UIView <UIKeyInput>
@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
@property(assign, readonly, nonatomic) BOOL isActive;
@property(assign, nonatomic) BOOL useCADisplayLink;
@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
@property(assign, readonly, nonatomic) BOOL canRender;
@property(assign, nonatomic) NSTimeInterval renderingInterval;
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
- (void)stopRendering;
- (void)startRendering;
- (BOOL)becomeFirstResponderWithString:(String)p_existing;
@end

View file

@ -0,0 +1,498 @@
/*************************************************************************/
/* godot_view.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.h"
#include "core/os/keyboard.h"
#include "core/ustring.h"
#import "display_layer.h"
#include "display_server_iphone.h"
#import "godot_view_renderer.h"
#import <CoreMotion/CoreMotion.h>
static const int max_touches = 8;
@interface GodotView () {
UITouch *godot_touches[max_touches];
String keyboard_text;
}
@property(assign, nonatomic) BOOL isActive;
// 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
@property(strong, nonatomic) CADisplayLink *displayLink;
// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
// Only used if CADisplayLink is not
@property(strong, nonatomic) NSTimer *animationTimer;
@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
@property(strong, nonatomic) CMMotionManager *motionManager;
@end
@implementation GodotView
- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
if (self.renderingLayer) {
return self.renderingLayer;
}
CALayer<DisplayLayer> *layer;
if ([driverName isEqualToString:@"vulkan"]) {
layer = [GodotMetalLayer layer];
} else if ([driverName isEqualToString:@"opengl_es"]) {
if (@available(iOS 13, *)) {
NSLog(@"OpenGL ES is deprecated on iOS 13");
}
#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
return nil;
#else
layer = [GodotOpenGLLayer layer];
#endif
} else {
return nil;
}
layer.frame = self.bounds;
layer.contentsScale = self.contentScaleFactor;
[self.layer addSublayer:layer];
self.renderingLayer = layer;
[layer initializeDisplayLayer];
return self.renderingLayer;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
[self godot_commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)dealloc {
[self stopRendering];
self.renderer = nil;
if (self.renderingLayer) {
[self.renderingLayer removeFromSuperlayer];
self.renderingLayer = nil;
}
if (self.motionManager) {
[self.motionManager stopDeviceMotionUpdates];
self.motionManager = nil;
}
if (self.displayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
}
if (self.animationTimer) {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
[super dealloc];
}
- (void)godot_commonInit {
self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
[self initTouches];
// Configure and start accelerometer
if (!self.motionManager) {
self.motionManager = [[[CMMotionManager alloc] init] autorelease];
if (self.motionManager.deviceMotionAvailable) {
self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
} else {
self.motionManager = nil;
}
}
}
- (void)stopRendering {
if (!self.isActive) {
return;
}
self.isActive = NO;
printf("******** stop animation!\n");
if (self.useCADisplayLink) {
[self.displayLink invalidate];
self.displayLink = nil;
} else {
[self.animationTimer invalidate];
self.animationTimer = nil;
}
[self clearTouches];
}
- (void)startRendering {
if (self.isActive) {
return;
}
self.isActive = YES;
printf("start animation!\n");
if (self.useCADisplayLink) {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
// if (@available(iOS 10, *)) {
self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
// } else {
// // Approximate frame rate
// // assumes device refreshes at 60 fps
// int frameInterval = (int)floor(self.renderingInterval * 60.0f);
// [self.displayLink setFrameInterval:frameInterval];
// }
// Setup DisplayLink in main thread
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
} else {
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}
}
- (void)drawView {
if (!self.isActive) {
printf("draw view not active!\n");
return;
}
if (self.useCADisplayLink) {
// Pause the CADisplayLink to avoid recursion
[self.displayLink setPaused:YES];
// Process all input events
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
;
// We are good to go, resume the CADisplayLink
[self.displayLink setPaused:NO];
}
[self.renderingLayer renderDisplayLayer];
if (!self.renderer) {
return;
}
if ([self.renderer setupView:self]) {
return;
}
[self handleMotion];
[self.renderer renderOnView:self];
}
- (BOOL)canRender {
if (self.useCADisplayLink) {
return self.displayLink != nil;
} else {
return self.animationTimer != nil;
}
}
- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
_renderingInterval = renderingInterval;
if (self.canRender) {
[self stopRendering];
[self startRendering];
}
}
- (void)layoutSubviews {
if (self.renderingLayer) {
self.renderingLayer.frame = self.bounds;
[self.renderingLayer layoutDisplayLayer];
if (DisplayServerIPhone::get_singleton()) {
DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
}
}
[super layoutSubviews];
}
// MARK: - Input
// MARK: Keyboard
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (BOOL)becomeFirstResponderWithString:(String)p_existing {
keyboard_text = p_existing;
return [self becomeFirstResponder];
}
- (BOOL)resignFirstResponder {
keyboard_text = String();
return [super resignFirstResponder];
}
- (void)deleteBackward {
if (keyboard_text.length()) {
keyboard_text.erase(keyboard_text.length() - 1, 1);
}
DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
}
- (BOOL)hasText {
return keyboard_text.length() > 0;
}
- (void)insertText:(NSString *)p_text {
String character;
character.parse_utf8([p_text UTF8String]);
keyboard_text = keyboard_text + character;
DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
}
// MARK: Touches
- (void)initTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = NULL;
}
}
- (int)getTouchIDForTouch:(UITouch *)p_touch {
int first = -1;
for (int i = 0; i < max_touches; i++) {
if (first == -1 && godot_touches[i] == NULL) {
first = i;
continue;
}
if (godot_touches[i] == p_touch) {
return i;
}
}
if (first != -1) {
godot_touches[first] = p_touch;
return first;
}
return -1;
}
- (int)removeTouch:(UITouch *)p_touch {
int remaining = 0;
for (int i = 0; i < max_touches; i++) {
if (godot_touches[i] == NULL) {
continue;
}
if (godot_touches[i] == p_touch) {
godot_touches[i] = NULL;
} else {
++remaining;
}
}
return remaining;
}
- (void)clearTouches {
for (int i = 0; i < max_touches; i++) {
godot_touches[i] = NULL;
}
}
- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseBegan) {
continue;
}
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseMoved) {
continue;
}
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
CGPoint touchPoint = [touch locationInView:self];
CGPoint prev_point = [touch previousLocationInView:self];
DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseEnded) {
continue;
}
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
[self removeTouch:touch];
CGPoint touchPoint = [touch locationInView:self];
DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
}
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *tlist = [event.allTouches allObjects];
for (unsigned int i = 0; i < [tlist count]; i++) {
if ([touches containsObject:[tlist objectAtIndex:i]]) {
UITouch *touch = [tlist objectAtIndex:i];
if (touch.phase != UITouchPhaseCancelled) {
continue;
}
int tid = [self getTouchIDForTouch:touch];
ERR_FAIL_COND(tid == -1);
DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
}
}
[self clearTouches];
}
// MARK: Motion
- (void)handleMotion {
if (!self.motionManager) {
return;
}
// 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 = self.motionManager.deviceMotion.gravity;
CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
///@TODO We don't seem to be getting data here, is my device broken or
/// is this code incorrect?
CMMagneticField magnetic = self.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 = self.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)
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
if (@available(iOS 13, *)) {
interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
} else {
interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
#endif
}
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft: {
DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
} break;
case UIInterfaceOrientationLandscapeRight: {
DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
} break;
case UIInterfaceOrientationPortraitUpsideDown: {
DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
} break;
default: { // assume portrait
DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
} break;
}
}
@end

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,146 @@
/*************************************************************************/
/* 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"
#import "display_server_iphone.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 hasFinishedLocaleSetup;
@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 (!self.hasFinishedLocaleSetup) {
[self setupLocaleAndUUID];
return YES;
}
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)setupLocaleAndUUID {
self.hasFinishedLocaleSetup = YES;
if (!OS::get_singleton()) {
exit(0);
}
NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
OSIPhone::get_singleton()->set_locale(String::utf8([locale_code UTF8String]));
NSString *uuid;
if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
} else {
// before iOS 6, so just generate an identifier and store it
uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"];
if (!uuid) {
CFUUIDRef cfuuid = CFUUIDCreate(NULL);
uuid = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease];
CFRelease(cfuuid);
[[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
}
}
OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
}
- (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

@ -44,9 +44,9 @@ class ICloud : public Object {
List<Variant> pending_events;
public:
Error remove_key(Variant p_param);
Variant set_key_values(Variant p_param);
Variant get_key_value(Variant p_param);
Error remove_key(String p_param);
Array set_key_values(Dictionary p_params);
Variant get_key_value(String p_param);
Error synchronize_key_values();
Variant get_all_key_values();

View file

@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {
} else if ([object isKindOfClass:[NSArray class]]) {
Array result;
NSArray *array = (NSArray *)object;
for (unsigned int i = 0; i < [array count]; ++i) {
for (NSUInteger i = 0; i < [array count]; ++i) {
NSObject *value = [array objectAtIndex:i];
result.push_back(nsobject_to_variant(value));
}
@ -149,7 +151,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];
} else if (v.get_type() == Variant::REAL) {
} else if (v.get_type() == Variant::FLOAT) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
return [NSNumber numberWithLongLong:(long)(int)v];
@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
Dictionary dic = v;
Array keys = dic.keys();
for (unsigned int i = 0; i < keys.size(); ++i) {
for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
Array arr = v;
for (unsigned int i = 0; i < arr.size(); ++i) {
for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
if (value == NULL) {
//trying to add something unsupported to the array. cancel the whole array
@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {
return NULL;
}
Error ICloud::remove_key(Variant p_param) {
String param = p_param;
NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
Error ICloud::remove_key(String p_param) {
NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {
}
//return an array of the keys that could not be set
Variant ICloud::set_key_values(Variant p_params) {
Dictionary params = p_params;
Array keys = params.keys();
Array ICloud::set_key_values(Dictionary p_params) {
Array keys = p_params.keys();
Array error_keys;
for (unsigned int i = 0; i < keys.size(); ++i) {
for (int i = 0; i < keys.size(); ++i) {
String variant_key = keys[i];
Variant variant_value = params[variant_key];
Variant variant_value = p_params[variant_key];
NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
if (key == NULL) {
@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {
return error_keys;
}
Variant ICloud::get_key_value(Variant p_param) {
String param = p_param;
NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
Variant ICloud::get_key_value(String p_param) {
NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {

View file

@ -44,9 +44,9 @@ class InAppStore : public Object {
List<Variant> pending_events;
public:
Error request_product_info(Variant p_params);
Error request_product_info(Dictionary p_params);
Error restore_purchases();
Error purchase(Variant p_params);
Error purchase(Dictionary p_params);
int get_pending_event_count();
Variant pop_pending_event();

View file

@ -39,8 +39,10 @@ extern "C" {
bool auto_finish_transactions = true;
NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
static NSArray *latestProducts;
@interface SKProduct (LocalizedPrice)
@property(nonatomic, readonly) NSString *localizedPrice;
@end
@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
latestProducts = products;
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "ok";
@ -126,11 +130,10 @@ void InAppStore::_bind_methods() {
@end
Error InAppStore::request_product_info(Variant p_params) {
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
Error InAppStore::request_product_info(Dictionary p_params) {
ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
PackedStringArray pids = params["product_ids"];
PackedStringArray pids = p_params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {
// which is still available in iOS 7.
// Use SKPaymentTransaction's transactionReceipt.
receipt = transaction.transactionReceipt;
receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
} else {
receipt = transaction.transactionReceipt;
receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
NSString *receipt_to_send = nil;
@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {
@end
Error InAppStore::purchase(Variant p_params) {
Error InAppStore::purchase(Dictionary p_params) {
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
if (![SKPaymentQueue canMakePayments])
return ERR_UNAVAILABLE;
printf("purchasing!\n");
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
SKProduct *product = nil;
if (latestProducts) {
for (SKProduct *storedProduct in latestProducts) {
if ([storedProduct.productIdentifier isEqualToString:pid]) {
product = storedProduct;
break;
}
}
}
if (!product) {
return ERR_INVALID_PARAMETER;
}
SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
[defq addPayment:payment];
printf("purchase sent!\n");

View file

@ -29,17 +29,27 @@
/*************************************************************************/
#include "ios.h"
#include <sys/sysctl.h>
#import "app_delegate.h"
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
};
void iOS::alert(const char *p_alert, const char *p_title) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
[alert show];
NSString *title = [NSString stringWithUTF8String:p_title];
NSString *message = [NSString stringWithUTF8String:p_alert];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:^(id){
}];
[alert addAction:button];
[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {

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,380 @@
/*************************************************************************/
/* 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"
#include "main/main.h"
#import "godot_view.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];
[super dealloc];
}
- (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 = Input::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
Input::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];
Input::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...
controller.extendedGamepad.valueChangedHandler = ^(
GCExtendedGamepad *gamepad, GCControllerElement *element) {
int joy_id = [self getJoyIdForController:controller];
if (element == gamepad.buttonA) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
gamepad.buttonA.isPressed);
} else if (element == gamepad.buttonB) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
gamepad.buttonB.isPressed);
} else if (element == gamepad.buttonX) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
gamepad.buttonX.isPressed);
} else if (element == gamepad.buttonY) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
gamepad.buttonY.isPressed);
} else if (element == gamepad.leftShoulder) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
gamepad.leftShoulder.isPressed);
} else if (element == gamepad.rightShoulder) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
gamepad.rightShoulder.isPressed);
} else if (element == gamepad.dpad) {
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
gamepad.dpad.up.isPressed);
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
gamepad.dpad.down.isPressed);
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
gamepad.dpad.left.isPressed);
Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
gamepad.dpad.right.isPressed);
};
Input::JoyAxis jx;
jx.min = -1;
if (element == gamepad.leftThumbstick) {
jx.value = gamepad.leftThumbstick.xAxis.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
jx.value = -gamepad.leftThumbstick.yAxis.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
} else if (element == gamepad.rightThumbstick) {
jx.value = gamepad.rightThumbstick.xAxis.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
jx.value = -gamepad.rightThumbstick.yAxis.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
} else if (element == gamepad.leftTrigger) {
jx.value = gamepad.leftTrigger.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
} else if (element == gamepad.rightTrigger) {
jx.value = gamepad.rightTrigger.value;
Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
};
};
}
// else if (controller.gamepad != nil) {
// // gamepad is the standard profile with 4 buttons, shoulder buttons and a
// // D-pad
// controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
// GCControllerElement *element) {
// int joy_id = [self getJoyIdForController:controller];
//
// if (element == gamepad.buttonA) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
// gamepad.buttonA.isPressed);
// } else if (element == gamepad.buttonB) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
// gamepad.buttonB.isPressed);
// } else if (element == gamepad.buttonX) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
// gamepad.buttonX.isPressed);
// } else if (element == gamepad.buttonY) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
// gamepad.buttonY.isPressed);
// } else if (element == gamepad.leftShoulder) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
// gamepad.leftShoulder.isPressed);
// } else if (element == gamepad.rightShoulder) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
// gamepad.rightShoulder.isPressed);
// } else if (element == gamepad.dpad) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
// gamepad.dpad.up.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
// gamepad.dpad.down.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
// gamepad.dpad.left.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
// gamepad.dpad.right.isPressed);
// };
// };
//#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
// // while we are setting that as the minimum, seems our
// // build environment doesn't like it
// } 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) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
// gamepad.buttonA.isPressed);
// } else if (element == gamepad.buttonX) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
// gamepad.buttonX.isPressed);
// } else if (element == gamepad.dpad) {
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
// gamepad.dpad.up.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
// gamepad.dpad.down.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
// gamepad.dpad.left.isPressed);
// Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
// gamepad.dpad.right.isPressed);
// };
// };
//#endif
// };
///@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

@ -32,20 +32,25 @@
#import <UIKit/UIKit.h>
#include <stdio.h>
#include <vulkan/vulkan.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
#if defined(VULKAN_ENABLED)
//MoltenVK - enable full component swizzling support
setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
#endif
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
NSAutoreleasePool *pool = [NSAutoreleasePool new];
AppDelegate *app = [AppDelegate alloc];
printf("running app main\n");
UIApplicationMain(argc, argv, nil, @"AppDelegate");
printf("main done, pool release\n");
[pool release];
@autoreleasepool {
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
printf("main done\n");
return 0;
}

View file

@ -1,632 +0,0 @@
/*************************************************************************/
/* os_iphone.cpp */
/*************************************************************************/
/* 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. */
/*************************************************************************/
#ifdef IPHONE_ENABLED
#include "os_iphone.h"
#if defined(OPENGL_ENABLED)
#include "drivers/gles2/rasterizer_gles2.h"
#endif
#if defined(VULKAN_ENABLED)
#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
// #import <QuartzCore/CAMetalLayer.h>
#include <vulkan/vulkan_metal.h>
#endif
#include "servers/rendering/rendering_server_raster.h"
#include "servers/rendering/rendering_server_wrap_mt.h"
#include "main/main.h"
#include "core/io/file_access_pack.h"
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "drivers/unix/syslog_logger.h"
#include "semaphore_iphone.h"
#include <dlfcn.h>
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_GLES2:
return "GLES2";
}
ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
};
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();
printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
memdelete(da);
};
void OSIPhone::set_unique_id(String p_id) {
unique_id = p_id;
};
String OSIPhone::get_unique_id() const {
return unique_id;
};
void OSIPhone::initialize_core() {
OS_Unix::initialize_core();
set_data_dir(data_dir);
};
int OSIPhone::get_current_video_driver() const {
return video_driver_index;
}
Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
video_driver_index = p_video_driver;
#if defined(OPENGL_ENABLED)
bool gl_initialization_error = false;
// FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
if (RasterizerGLES2::is_viable() == OK) {
RasterizerGLES2::register_config();
RasterizerGLES2::make_current();
} else {
gl_initialization_error = true;
}
if (gl_initialization_error) {
OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
"Unable to initialize video driver");
return ERR_UNAVAILABLE;
}
#endif
#if defined(VULKAN_ENABLED)
RasterizerRD::make_current();
#endif
rendering_server = memnew(RenderingServerRaster);
// FIXME: Reimplement threaded rendering
if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
}
rendering_server->init();
//rendering_server->cursor_set_visible(false, 0);
#if defined(OPENGL_ENABLED)
// reset this to what it should be, it will have been set to 0 after rendering_server->init() is called
RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
#endif
AudioDriverManager::initialize(p_audio_driver);
input = memnew(InputDefault);
#ifdef GAME_CENTER_ENABLED
game_center = memnew(GameCenter);
Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
game_center->connect();
#endif
#ifdef STOREKIT_ENABLED
store_kit = memnew(InAppStore);
Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
#endif
#ifdef ICLOUD_ENABLED
icloud = memnew(ICloud);
Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
//icloud->connect();
#endif
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
return OK;
};
MainLoop *OSIPhone::get_main_loop() const {
return main_loop;
};
void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
if (main_loop) {
input->set_main_loop(p_main_loop);
main_loop->init();
}
};
bool OSIPhone::iterate() {
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_keycode(p_key);
ev->set_physical_keycode(p_key);
ev->set_unicode(p_key);
queue_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)) {
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;
};
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)) {
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);
};
};
void OSIPhone::queue_event(const Ref<InputEvent> &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);
};
};
};
static const float ACCEL_RANGE = 1;
void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
input->set_gravity(Vector3(p_x, p_y, p_z));
};
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) {
input->set_magnetometer(Vector3(p_x, p_y, p_z));
};
void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
input->set_gyroscope(Vector3(p_x, p_y, p_z));
};
int OSIPhone::get_unused_joy_id() {
return input->get_unused_joy_id();
};
void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
input->joy_connection_changed(p_idx, p_connected, p_name);
};
void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
input->joy_button(p_device, p_button, p_pressed);
};
void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
input->joy_axis(p_device, p_axis, p_value);
};
void OSIPhone::delete_main_loop() {
if (main_loop) {
main_loop->finish();
memdelete(main_loop);
};
main_loop = nullptr;
};
void OSIPhone::finalize() {
delete_main_loop();
memdelete(input);
memdelete(ios);
#ifdef GAME_CENTER_ENABLED
memdelete(game_center);
#endif
#ifdef STOREKIT_ENABLED
memdelete(store_kit);
#endif
#ifdef ICLOUD_ENABLED
memdelete(icloud);
#endif
rendering_server->finish();
memdelete(rendering_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) {}
void OSIPhone::set_mouse_grab(bool p_grab) {}
bool OSIPhone::is_mouse_grab_enabled() const {
return true;
};
Point2 OSIPhone::get_mouse_position() const {
return Point2();
};
int OSIPhone::get_mouse_button_state() const {
return 0;
};
void OSIPhone::set_window_title(const String &p_title) {}
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());
}
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;
return OK;
}
return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
}
Error OSIPhone::close_dynamic_library(void *p_library_handle) {
if (p_library_handle == RTLD_SELF) {
return OK;
}
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;
}
Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
if (p_library_handle == RTLD_SELF) {
void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
if (ptr) {
p_symbol_handle = *ptr;
return OK;
}
}
return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
}
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) {
#if defined(OPENGL_ENABLED)
// gl_view_base_fb has not been updated yet
RasterizerStorageGLES2::system_fbo = p_fb;
#endif
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, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
_show_keyboard(p_existing_text);
};
void OSIPhone::hide_virtual_keyboard() {
_hide_keyboard();
};
void OSIPhone::set_virtual_keyboard_height(int p_height) {
virtual_keyboard_height = p_height;
}
int OSIPhone::get_virtual_keyboard_height() const {
return virtual_keyboard_height;
}
Error OSIPhone::shell_open(String p_uri) {
return _shell_open(p_uri);
};
void OSIPhone::set_keep_screen_on(bool p_enabled) {
OS::set_keep_screen_on(p_enabled);
_set_keep_screen_on(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 != "")
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);
Rect2 OSIPhone::get_window_safe_area() const {
return _get_ios_window_safe_area(video_mode.width, video_mode.height);
}
bool OSIPhone::has_touchscreen_ui_hint() const {
return true;
}
void OSIPhone::set_locale(String p_locale) {
locale_code = p_locale;
}
String OSIPhone::get_locale() const {
return locale_code;
}
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)
return FAILED;
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());
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());
memdelete(f);
print("Playing video: %S\n", p_path.c_str());
if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
return OK;
return FAILED;
}
bool OSIPhone::native_video_is_playing() const {
return _is_video_playing();
}
void OSIPhone::native_video_pause() {
if (native_video_is_playing())
_pause_video();
}
void OSIPhone::native_video_unpause() {
_unpause_video();
};
void OSIPhone::native_video_focus_out() {
_focus_out_video();
};
void OSIPhone::native_video_stop() {
if (native_video_is_playing())
_stop_video();
}
void OSIPhone::vibrate_handheld(int p_duration_ms) {
// iOS does not support duration for vibration
_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 = nullptr;
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);
if (new_ptr) {
ios_init_callbacks = (init_callback *)(new_ptr);
ios_init_callbacks_capacity += 32;
}
}
if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
ios_init_callbacks[ios_init_callbacks_count] = cb;
++ios_init_callbacks_count;
}
}
OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
for (int i = 0; i < ios_init_callbacks_count; ++i) {
ios_init_callbacks[i]();
}
free(ios_init_callbacks);
ios_init_callbacks = nullptr;
ios_init_callbacks_count = 0;
ios_init_callbacks_capacity = 0;
main_loop = nullptr;
rendering_server = nullptr;
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;
Vector<Logger *> loggers;
loggers.push_back(memnew(SyslogLogger));
#ifdef DEBUG_ENABLED
// it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
loggers.push_back(memnew(StdLogger));
#endif
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
};
OSIPhone::~OSIPhone() {
}
#endif

View file

@ -33,16 +33,15 @@
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
#include "core/input/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "game_center.h"
#include "icloud.h"
#include "in_app_store.h"
#include "ios.h"
#include "joypad_iphone.h"
#include "servers/audio_server.h"
#include "servers/rendering/rasterizer.h"
#include "servers/rendering_server.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
@ -51,16 +50,9 @@
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);
RenderingServer *rendering_server;
AudioDriverCoreAudio audio_driver;
#ifdef GAME_CENTER_ENABLED
@ -74,139 +66,72 @@ private:
#endif
iOS *ios;
JoypadIPhone *joypad_iphone;
MainLoop *main_loop;
#if defined(VULKAN_ENABLED)
VulkanContextIPhone *context_vulkan;
RenderingDeviceVulkan *rendering_device_vulkan;
#endif
VideoMode video_mode;
virtual void initialize_core() override;
virtual void initialize() override;
virtual int get_video_driver_count() const;
virtual const char *get_video_driver_name(int p_driver) const;
virtual void initialize_joypads() override {
}
virtual int get_current_video_driver() const;
virtual void set_main_loop(MainLoop *p_main_loop) override;
virtual MainLoop *get_main_loop() const override;
virtual void initialize_core();
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
virtual void delete_main_loop() override;
virtual void set_main_loop(MainLoop *p_main_loop);
virtual MainLoop *get_main_loop() const;
virtual void finalize() override;
virtual void delete_main_loop();
virtual void finalize();
struct MouseList {
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);
String data_dir;
String user_data_dir;
String unique_id;
String locale_code;
InputDefault *input;
bool is_focused = false;
int virtual_keyboard_height;
int video_driver_index;
void deinitialize_modules();
public:
bool iterate();
uint8_t get_orientations() 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 key(uint32_t p_key, bool p_pressed);
void set_virtual_keyboard_height(int p_height);
int set_base_framebuffer(int p_fb);
void update_gravity(float p_x, float p_y, float p_z);
void update_accelerometer(float p_x, float p_y, float p_z);
void update_magnetometer(float p_x, float p_y, float p_z);
void update_gyroscope(float p_x, float p_y, float p_z);
int get_unused_joy_id();
void joy_connection_changed(int p_idx, bool p_connected, String p_name);
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);
OSIPhone(String p_data_dir);
~OSIPhone();
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
void initialize_modules();
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);
bool iterate();
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;
void start();
virtual void set_keep_screen_on(bool p_enabled);
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
virtual bool can_draw() const;
virtual void alert(const String &p_alert,
const String &p_title = "ALERT!") override;
virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual int get_virtual_keyboard_height() const;
virtual String get_name() const override;
virtual String get_model_name() const override;
virtual Size2 get_window_size() const;
virtual Rect2 get_window_safe_area() const;
virtual Error shell_open(String p_uri) override;
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;
void set_user_data_dir(String p_dir);
virtual String get_user_data_dir() const override;
void set_locale(String p_locale);
String get_locale() const;
virtual String get_locale() const override;
void set_unique_id(String p_id);
String get_unique_id() const;
virtual String get_unique_id() const override;
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 void vibrate_handheld(int p_duration_ms = 500) override;
virtual bool _check_internal_feature_support(const String &p_feature);
OSIPhone(int width, int height, String p_data_dir);
~OSIPhone();
virtual bool _check_internal_feature_support(const String &p_feature) override;
void on_focus_out();
void on_focus_in();
};
#endif // OS_IPHONE_H
#endif
#endif // IPHONE_ENABLED

View file

@ -0,0 +1,369 @@
/*************************************************************************/
/* os_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. */
/*************************************************************************/
#ifdef IPHONE_ENABLED
#include "os_iphone.h"
#import "app_delegate.h"
#include "core/io/file_access_pack.h"
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/project_settings.h"
#include "display_server_iphone.h"
#include "drivers/unix/syslog_logger.h"
#import "godot_view.h"
#include "main/main.h"
#import "view_controller.h"
#import <AudioToolbox/AudioServices.h>
#import <UIKit/UIKit.h>
#import <dlfcn.h>
#if defined(OPENGL_ENABLED)
#include "drivers/gles2/rasterizer_gles2.h"
#endif
#if defined(VULKAN_ENABLED)
#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
#import <QuartzCore/CAMetalLayer.h>
#include <vulkan/vulkan_metal.h>
#endif
// 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 = nullptr;
static int ios_init_callbacks_count = 0;
static int ios_init_callbacks_capacity = 0;
HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
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);
if (new_ptr) {
ios_init_callbacks = (init_callback *)(new_ptr);
ios_init_callbacks_capacity += 32;
}
}
if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
ios_init_callbacks[ios_init_callbacks_count] = cb;
++ios_init_callbacks_count;
}
}
void register_dynamic_symbol(char *name, void *address) {
OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
}
OSIPhone *OSIPhone::get_singleton() {
return (OSIPhone *)OS::get_singleton();
}
OSIPhone::OSIPhone(String p_data_dir) {
for (int i = 0; i < ios_init_callbacks_count; ++i) {
ios_init_callbacks[i]();
}
free(ios_init_callbacks);
ios_init_callbacks = nullptr;
ios_init_callbacks_count = 0;
ios_init_callbacks_capacity = 0;
main_loop = nullptr;
// can't call set_data_dir from here, since it requires DirAccess
// which is initialized in initialize_core
user_data_dir = p_data_dir;
Vector<Logger *> loggers;
loggers.push_back(memnew(SyslogLogger));
#ifdef DEBUG_ENABLED
// it seems iOS app's stdout/stderr is only obtainable if you launch it from
// Xcode
loggers.push_back(memnew(StdLogger));
#endif
_set_logger(memnew(CompositeLogger(loggers)));
AudioDriverManager::add_driver(&audio_driver);
DisplayServerIPhone::register_iphone_driver();
}
OSIPhone::~OSIPhone() {}
void OSIPhone::initialize_core() {
OS_Unix::initialize_core();
set_user_data_dir(user_data_dir);
}
void OSIPhone::initialize() {
initialize_core();
}
void OSIPhone::initialize_modules() {
#ifdef GAME_CENTER_ENABLED
game_center = memnew(GameCenter);
Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
game_center->connect();
#endif
#ifdef STOREKIT_ENABLED
store_kit = memnew(InAppStore);
Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
#endif
#ifdef ICLOUD_ENABLED
icloud = memnew(ICloud);
Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
#endif
ios = memnew(iOS);
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
joypad_iphone = memnew(JoypadIPhone);
}
void OSIPhone::deinitialize_modules() {
if (joypad_iphone) {
memdelete(joypad_iphone);
}
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
}
void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
main_loop = p_main_loop;
if (main_loop) {
main_loop->init();
}
}
MainLoop *OSIPhone::get_main_loop() const {
return main_loop;
}
void OSIPhone::delete_main_loop() {
if (main_loop) {
main_loop->finish();
memdelete(main_loop);
};
main_loop = nullptr;
}
bool OSIPhone::iterate() {
if (!main_loop) {
return true;
}
if (DisplayServer::get_singleton()) {
DisplayServer::get_singleton()->process_events();
}
return Main::iteration();
}
void OSIPhone::start() {
Main::start();
if (joypad_iphone) {
joypad_iphone->start_processing();
}
}
void OSIPhone::finalize() {
deinitialize_modules();
// Already gets called
// delete_main_loop();
}
// 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;
return OK;
}
return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
}
Error OSIPhone::close_dynamic_library(void *p_library_handle) {
if (p_library_handle == RTLD_SELF) {
return OK;
}
return OS_Unix::close_dynamic_library(p_library_handle);
}
Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
if (p_library_handle == RTLD_SELF) {
void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
if (ptr) {
p_symbol_handle = *ptr;
return OK;
}
}
return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
}
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());
}
String OSIPhone::get_name() const {
return "iOS";
};
String OSIPhone::get_model_name() const {
String model = ios->get_model();
if (model != "")
return model;
return OS_Unix::get_model_name();
}
Error OSIPhone::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]) {
return ERR_CANT_OPEN;
}
printf("opening url %ls\n", p_uri.c_str());
// if (@available(iOS 10, *)) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
// } else {
// [[UIApplication sharedApplication] openURL:url];
// }
return OK;
};
void OSIPhone::set_user_data_dir(String p_dir) {
DirAccess *da = DirAccess::open(p_dir);
user_data_dir = da->get_current_dir();
printf("setting data dir to %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str());
memdelete(da);
}
String OSIPhone::get_user_data_dir() const {
return user_data_dir;
}
void OSIPhone::set_locale(String p_locale) {
locale_code = p_locale;
}
String OSIPhone::get_locale() const {
return locale_code;
}
void OSIPhone::set_unique_id(String p_id) {
unique_id = p_id;
}
String OSIPhone::get_unique_id() const {
return unique_id;
}
void OSIPhone::vibrate_handheld(int p_duration_ms) {
// iOS does not support duration for vibration
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
return p_feature == "mobile";
}
void OSIPhone::on_focus_out() {
if (is_focused) {
is_focused = false;
if (DisplayServerIPhone::get_singleton()) {
DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
}
[AppDelegate.viewController.godotView stopRendering];
if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
DisplayServerIPhone::get_singleton()->native_video_pause();
}
audio_driver.stop();
}
}
void OSIPhone::on_focus_in() {
if (!is_focused) {
is_focused = true;
if (DisplayServerIPhone::get_singleton()) {
DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
}
[AppDelegate.viewController.godotView startRendering];
if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
DisplayServerIPhone::get_singleton()->native_video_unpause();
}
audio_driver.start();
}
}
#endif // IPHONE_ENABLED

View file

@ -31,20 +31,18 @@
#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
};
@class GodotView;
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)p_orientation;
@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
- (void)didReceiveMemoryWarning;
- (GodotView *)godotView;
- (void)viewDidLoad;
// MARK: Native Video Player
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
- (BOOL)prefersStatusBarHidden;
- (BOOL)prefersHomeIndicatorAutoHidden;
- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
- (BOOL)isVideoPlaying;
- (void)pauseVideo;
- (void)unpauseVideo;
- (void)stopVideo;
@end

View file

@ -29,96 +29,174 @@
/*************************************************************************/
#import "view_controller.h"
#include "core/project_settings.h"
#include "display_server_iphone.h"
#import "godot_view.h"
#import "godot_view_renderer.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++] = "--path";
[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
p_args[p_argc++] = (char *)[str cString];
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 (int i = 0; i < [arr count]; i++) {
NSString *str = [arr objectAtIndex:i];
if (!str)
continue;
[str retain]; // @todo delete these at some point
p_args[p_argc++] = (char *)[str cString];
};
p_args[p_argc] = NULL;
return p_argc;
};
}; // extern "C"
#import <GameController/GameController.h>
@interface ViewController ()
@property(strong, nonatomic) GodotViewRenderer *renderer;
// TODO: separate view to handle video
// AVPlayer-related properties
@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;
@property(assign, nonatomic) BOOL videoHasFoundError;
@end
@implementation ViewController
- (GodotView *)godotView {
return (GodotView *)self.view;
}
- (void)loadView {
GodotView *view = [[GodotView alloc] init];
GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
self.renderer = renderer;
self.view = view;
view.renderer = self.renderer;
[renderer release];
[view release];
}
- (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 {
self.isVideoCurrentlyPlaying = NO;
self.videoCurrentTime = kCMTimeZero;
self.videoHasFoundError = false;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
};
}
- (void)viewDidLoad {
[super viewDidLoad];
[self observeKeyboard];
[self observeAudio];
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)observeAudio {
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];
}
}
- (void)dealloc {
[self stopVideo];
self.renderer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
// MARK: Orientation
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
switch (OS::get_singleton()->get_screen_orientation()) {
case OS::SCREEN_SENSOR:
case OS::SCREEN_SENSOR_LANDSCAPE:
case OS::SCREEN_SENSOR_PORTRAIT:
if (!DisplayServerIPhone::get_singleton()) {
return NO;
}
switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServer::SCREEN_SENSOR:
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
};
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
switch (OS::get_singleton()->get_screen_orientation()) {
case OS::SCREEN_PORTRAIT:
if (!DisplayServerIPhone::get_singleton()) {
return UIInterfaceOrientationMaskAll;
}
switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
case OS::SCREEN_REVERSE_LANDSCAPE:
case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
case OS::SCREEN_REVERSE_PORTRAIT:
case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
case OS::SCREEN_SENSOR_LANDSCAPE:
case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
case OS::SCREEN_SENSOR_PORTRAIT:
case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
case OS::SCREEN_SENSOR:
case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
case OS::SCREEN_LANDSCAPE:
case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
@ -135,6 +213,190 @@ 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 (DisplayServerIPhone::get_singleton()) {
DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
}
}
- (void)keyboardHidden:(NSNotification *)notification {
if (DisplayServerIPhone::get_singleton()) {
DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
}
}
// MARK: 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];
self.videoHasFoundError = true;
}
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.view.bounds];
[self.view.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)unpauseVideo {
[self.avPlayer play];
self.isVideoCurrentlyPlaying = YES;
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[self stopVideo];
}
- (void)stopVideo {
[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;
}
// MARK: Delegates
#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

View file

@ -32,13 +32,14 @@
#define VULKAN_CONTEXT_IPHONE_H
#include "drivers/vulkan/vulkan_context.h"
// #import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
class VulkanContextIPhone : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
public:
int window_create(void *p_window, int p_width, int p_height);
Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
VulkanContextIPhone();
~VulkanContextIPhone();

View file

@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) {
Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
CALayer *p_metal_layer, int p_width,
int p_height) {
VkIOSSurfaceCreateInfoMVK createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
createInfo.pView = p_window;
createInfo.pView = p_metal_layer;
VkSurfaceKHR surface;
VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
ERR_FAIL_COND_V(err, -1);
return _window_create(surface, p_width, p_height);
VkResult err =
vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
return _window_create(p_window_id, surface, p_width, p_height);
}
VulkanContextIPhone::VulkanContextIPhone() {
}
VulkanContextIPhone::VulkanContextIPhone() {}
VulkanContextIPhone::~VulkanContextIPhone() {
}
VulkanContextIPhone::~VulkanContextIPhone() {}