@hugebug4ever/react-native-unity 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +77 -0
  2. package/android/build.gradle +38 -0
  3. package/android/consumer-rules.pro +3 -0
  4. package/android/src/main/AndroidManifest.xml +4 -0
  5. package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityBridge.kt +5 -0
  6. package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityHostView.kt +95 -0
  7. package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityPackage.kt +31 -0
  8. package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityViewManager.kt +30 -0
  9. package/android/src/main/java/com/hugebug4ever/rnunity/UnityPlayerAdapter.kt +58 -0
  10. package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeController.kt +329 -0
  11. package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeModule.kt +46 -0
  12. package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeState.kt +16 -0
  13. package/app.plugin.js +2 -0
  14. package/cli/index.js +140 -0
  15. package/ios/RNUnityNativeBridge.mm +6 -0
  16. package/ios/RNUnityRuntimeController.h +29 -0
  17. package/ios/RNUnityRuntimeController.mm +316 -0
  18. package/ios/RNUnityView.h +17 -0
  19. package/ios/RNUnityView.mm +105 -0
  20. package/ios/UnityRuntime.mm +49 -0
  21. package/lib/commonjs/UnityRuntime.js +53 -0
  22. package/lib/commonjs/UnityRuntime.js.map +1 -0
  23. package/lib/commonjs/UnityView.js +35 -0
  24. package/lib/commonjs/UnityView.js.map +1 -0
  25. package/lib/commonjs/index.js +20 -0
  26. package/lib/commonjs/index.js.map +1 -0
  27. package/lib/commonjs/package.json +1 -0
  28. package/lib/commonjs/specs/NativeUnityRuntime.js +9 -0
  29. package/lib/commonjs/specs/NativeUnityRuntime.js.map +1 -0
  30. package/lib/commonjs/specs/NativeUnityView.js +10 -0
  31. package/lib/commonjs/specs/NativeUnityView.js.map +1 -0
  32. package/lib/commonjs/types.js +6 -0
  33. package/lib/commonjs/types.js.map +1 -0
  34. package/lib/typescript/UnityRuntime.d.ts +12 -0
  35. package/lib/typescript/UnityRuntime.d.ts.map +1 -0
  36. package/lib/typescript/UnityView.d.ts +3 -0
  37. package/lib/typescript/UnityView.d.ts.map +1 -0
  38. package/lib/typescript/index.d.ts +4 -0
  39. package/lib/typescript/index.d.ts.map +1 -0
  40. package/lib/typescript/specs/NativeUnityRuntime.d.ts +15 -0
  41. package/lib/typescript/specs/NativeUnityRuntime.d.ts.map +1 -0
  42. package/lib/typescript/specs/NativeUnityView.d.ts +32 -0
  43. package/lib/typescript/specs/NativeUnityView.d.ts.map +1 -0
  44. package/lib/typescript/types.d.ts +63 -0
  45. package/lib/typescript/types.d.ts.map +1 -0
  46. package/package/UnityRuntime.ts +64 -0
  47. package/package/UnityView.tsx +32 -0
  48. package/package/index.ts +20 -0
  49. package/package/specs/NativeUnityRuntime.ts +17 -0
  50. package/package/specs/NativeUnityView.ts +27 -0
  51. package/package/types.ts +85 -0
  52. package/package.json +90 -0
  53. package/plugin/build/index.js +132 -0
  54. package/plugin/ios/configure_unity.rb +108 -0
  55. package/plugin/src/index.ts +175 -0
  56. package/plugin/tsconfig.json +13 -0
  57. package/react-native-unity.podspec +34 -0
  58. package/react-native.config.js +9 -0
  59. package/unity-package/Editor/Exporter.cs +91 -0
  60. package/unity-package/Editor/Exporter.cs.meta +2 -0
  61. package/unity-package/Editor/ReactNativeUnity.Editor.asmdef +8 -0
  62. package/unity-package/Editor/ReactNativeUnity.Editor.asmdef.meta +7 -0
  63. package/unity-package/Editor.meta +8 -0
  64. package/unity-package/Runtime/ReactNativeBridge.cs +48 -0
  65. package/unity-package/Runtime/ReactNativeBridge.cs.meta +2 -0
  66. package/unity-package/Runtime/ReactNativeUnity.Runtime.asmdef +6 -0
  67. package/unity-package/Runtime/ReactNativeUnity.Runtime.asmdef.meta +7 -0
  68. package/unity-package/Runtime.meta +8 -0
  69. package/unity-package/package.json +11 -0
  70. package/unity-package/package.json.meta +7 -0
@@ -0,0 +1,27 @@
1
+ import type { ViewProps } from 'react-native';
2
+ import type {
3
+ DirectEventHandler,
4
+ Double,
5
+ } from 'react-native/Libraries/Types/CodegenTypes';
6
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
7
+
8
+ type ReadyEvent = Readonly<{ state: string; generation: Double; hostId: string }>;
9
+ type MessageEvent = Readonly<{ generation: Double; message: string }>;
10
+ type StateEvent = Readonly<{ state: string; generation: Double }>;
11
+ type ErrorEvent = Readonly<{
12
+ code: string;
13
+ message: string;
14
+ state: string;
15
+ generation: Double;
16
+ nativeCause?: string;
17
+ }>;
18
+
19
+ export interface NativeProps extends ViewProps {
20
+ detachedBehavior?: string;
21
+ onReady?: DirectEventHandler<ReadyEvent>;
22
+ onMessage?: DirectEventHandler<MessageEvent>;
23
+ onStateChange?: DirectEventHandler<StateEvent>;
24
+ onError?: DirectEventHandler<ErrorEvent>;
25
+ }
26
+
27
+ export default codegenNativeComponent<NativeProps>('RNUnityView');
@@ -0,0 +1,85 @@
1
+ import type { NativeSyntheticEvent, ViewProps } from 'react-native';
2
+
3
+ export type UnityRuntimeState =
4
+ | 'idle'
5
+ | 'starting'
6
+ | 'runningDetached'
7
+ | 'runningAttached'
8
+ | 'pausedDetached'
9
+ | 'pausedAttached'
10
+ | 'unloading'
11
+ | 'quitted'
12
+ | 'failed';
13
+
14
+ export type UnityDetachedBehavior = 'pause' | 'keepRunning';
15
+
16
+ export interface UnityStartOptions {
17
+ detachedBehavior?: UnityDetachedBehavior;
18
+ }
19
+
20
+ export interface UnitySendMessageInput {
21
+ gameObject: string;
22
+ methodName: string;
23
+ message: string;
24
+ }
25
+
26
+ export interface UnityError {
27
+ code: string;
28
+ message: string;
29
+ state: UnityRuntimeState;
30
+ generation: number;
31
+ nativeCause?: string;
32
+ }
33
+
34
+ export interface UnityMessage {
35
+ generation: number;
36
+ message: string;
37
+ }
38
+
39
+ export interface UnityStateChange {
40
+ state: UnityRuntimeState;
41
+ generation: number;
42
+ }
43
+
44
+ export interface UnityReady {
45
+ state: UnityRuntimeState;
46
+ generation: number;
47
+ hostId: string;
48
+ }
49
+
50
+ export interface UnityDiagnostics {
51
+ state: UnityRuntimeState;
52
+ generation: number;
53
+ hostId?: string;
54
+ pauseReasons: string[];
55
+ lastTransitionAt: number;
56
+ lastError?: UnityError;
57
+ unityVersion?: string;
58
+ reactNativeVersion?: string;
59
+ packageVersion: string;
60
+ }
61
+
62
+ export type UnityReadyEvent = NativeSyntheticEvent<UnityReady>;
63
+ export type UnityMessageEvent = NativeSyntheticEvent<UnityMessage>;
64
+ export type UnityStateEvent = NativeSyntheticEvent<UnityStateChange>;
65
+ export type UnityErrorEvent = NativeSyntheticEvent<UnityError>;
66
+
67
+ export interface UnityViewProps extends ViewProps {
68
+ autoStart?: boolean;
69
+ detachedBehavior?: UnityDetachedBehavior;
70
+ onReady?: (event: UnityReadyEvent) => void;
71
+ onMessage?: (event: UnityMessageEvent) => void;
72
+ onStateChange?: (event: UnityStateEvent) => void;
73
+ onError?: (event: UnityErrorEvent) => void;
74
+ }
75
+
76
+ export interface UnityEventMap {
77
+ stateChange: UnityStateChange;
78
+ message: UnityMessage;
79
+ error: UnityError;
80
+ }
81
+
82
+ export interface UnitySubscription {
83
+ remove(): void;
84
+ }
85
+
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "@hugebug4ever/react-native-unity",
3
+ "version": "0.0.1",
4
+ "description": "Lifecycle-safe Unity 6 as a Library integration for React Native",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/hugebug4ever/react-native-unity.git"
9
+ },
10
+ "main": "lib/commonjs/index.js",
11
+ "module": "lib/commonjs/index.js",
12
+ "types": "lib/typescript/index.d.ts",
13
+ "react-native": "package/index.ts",
14
+ "source": "package/index.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/typescript/index.d.ts",
18
+ "react-native": "./package/index.ts",
19
+ "default": "./lib/commonjs/index.js"
20
+ },
21
+ "./app.plugin.js": "./app.plugin.js",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "bin": {
25
+ "rn-unity": "cli/index.js"
26
+ },
27
+ "files": [
28
+ "android",
29
+ "app.plugin.js",
30
+ "cli",
31
+ "ios",
32
+ "lib",
33
+ "package",
34
+ "plugin",
35
+ "react-native-unity.podspec",
36
+ "react-native.config.js",
37
+ "unity-package",
38
+ "!**/__tests__",
39
+ "!**/*.test.*"
40
+ ],
41
+ "scripts": {
42
+ "build": "bob build && tsc -p plugin/tsconfig.json",
43
+ "clean": "bob clean && node -e \"require('fs').rmSync('plugin/build',{recursive:true,force:true})\"",
44
+ "lint": "tsc --noEmit -p tsconfig.json",
45
+ "test": "jest --runInBand",
46
+ "pack:sample": "npm run build && node scripts/pack-sample.js",
47
+ "prepare": "npm run build"
48
+ },
49
+ "peerDependencies": {
50
+ "react": ">=19.2.0",
51
+ "react-native": ">=0.86.0 <0.88.0"
52
+ },
53
+ "dependencies": {
54
+ "@expo/config-plugins": "^57.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@react-native/babel-preset": "0.86.3",
58
+ "@react-native/eslint-config": "0.86.3",
59
+ "@react-native/jest-preset": "0.86.3",
60
+ "@react-native/typescript-config": "0.86.3",
61
+ "@types/jest": "^29.5.14",
62
+ "@types/react": "^19.2.0",
63
+ "jest": "^29.7.0",
64
+ "react": "19.2.3",
65
+ "react-native": "0.86.3",
66
+ "react-native-builder-bob": "^0.40.18",
67
+ "typescript": "^5.9.2"
68
+ },
69
+ "codegenConfig": {
70
+ "name": "RNUnitySpec",
71
+ "type": "all",
72
+ "jsSrcsDir": "package/specs",
73
+ "android": {
74
+ "javaPackageName": "com.hugebug4ever.rnunity"
75
+ },
76
+ "ios": {
77
+ "componentProvider": {
78
+ "RNUnityView": "RNUnityView"
79
+ }
80
+ }
81
+ },
82
+ "react-native-builder-bob": {
83
+ "source": "package",
84
+ "output": "lib",
85
+ "targets": [
86
+ ["commonjs", { "esm": true }],
87
+ "typescript"
88
+ ]
89
+ }
90
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.replaceMarkedBlock = replaceMarkedBlock;
7
+ exports.replacePropertiesBlock = replacePropertiesBlock;
8
+ const config_plugins_1 = require("@expo/config-plugins");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const pkg = require('../../package.json');
12
+ const START = '// @generated by @hugebug4ever/react-native-unity';
13
+ const END = '// @end @hugebug4ever/react-native-unity';
14
+ const PROPERTIES_START = '# @generated by @hugebug4ever/react-native-unity';
15
+ const PROPERTIES_END = '# @end @hugebug4ever/react-native-unity';
16
+ function replaceMarkedBlock(source, body) {
17
+ const block = `${START}\n${body.trim()}\n${END}`;
18
+ const pattern = new RegExp(`${START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
19
+ return pattern.test(source)
20
+ ? source.replace(pattern, block)
21
+ : `${source.trimEnd()}\n\n${block}\n`;
22
+ }
23
+ function replacePropertiesBlock(source, body) {
24
+ const legacyPattern = /^\/\/ @generated by @hugebug4ever\/react-native-unit.*?[\r\n]+[\s\S]*?^\/\/ @end @hugebug4ever\/react-native-unit.*?(?:[\r\n]+|$)/m;
25
+ const cleaned = source.replace(legacyPattern, '');
26
+ const block = `${PROPERTIES_START}\n${body.trim()}\n${PROPERTIES_END}`;
27
+ const pattern = new RegExp(`${PROPERTIES_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${PROPERTIES_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
28
+ return pattern.test(cleaned)
29
+ ? cleaned.replace(pattern, block)
30
+ : `${cleaned.trimEnd()}\n\n${block}\n`;
31
+ }
32
+ const withAndroidUnity = (config, props) => {
33
+ config = (0, config_plugins_1.withSettingsGradle)(config, (mod) => {
34
+ const exportPath = props.androidExportPath.replace(/\\/g, '/');
35
+ mod.modResults.contents = replaceMarkedBlock(mod.modResults.contents, `include ':unityLibrary'\nproject(':unityLibrary').projectDir = new File(rootProject.projectDir, '../${exportPath}')`);
36
+ return mod;
37
+ });
38
+ config = (0, config_plugins_1.withAppBuildGradle)(config, (mod) => {
39
+ if (mod.modResults.language !== 'groovy') {
40
+ throw new Error('RN Unity currently requires a Groovy app/build.gradle');
41
+ }
42
+ mod.modResults.contents = replaceMarkedBlock(mod.modResults.contents, `dependencies {\n implementation project(':unityLibrary')\n}`);
43
+ return mod;
44
+ });
45
+ config = (0, config_plugins_1.withProjectBuildGradle)(config, (mod) => {
46
+ if (mod.modResults.language !== 'groovy') {
47
+ throw new Error('RN Unity currently requires a Groovy project/build.gradle');
48
+ }
49
+ const body = `ext.minSdkVersion = Math.max((ext.has('minSdkVersion') ? ext.minSdkVersion : 24) as Integer, 25)`;
50
+ const block = `${START}\n${body}\n${END}`;
51
+ const withBlock = replaceMarkedBlock(mod.modResults.contents, body);
52
+ const withoutBlock = withBlock.replace(block, '').trimEnd();
53
+ const expoPlugin = 'apply plugin: "expo-root-project"';
54
+ if (!withoutBlock.includes(expoPlugin)) {
55
+ throw new Error('RN Unity could not locate the Expo root Gradle plugin');
56
+ }
57
+ mod.modResults.contents = withoutBlock.replace(expoPlugin, `${block}\n\n${expoPlugin}`);
58
+ return mod;
59
+ });
60
+ config = (0, config_plugins_1.withAndroidManifest)(config, (mod) => {
61
+ const manifest = mod.modResults.manifest;
62
+ manifest.$ = manifest.$ ?? {};
63
+ manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
64
+ const application = manifest.application?.[0];
65
+ if (application) {
66
+ application.$ = application.$ ?? {};
67
+ const replacements = new Set((application.$['tools:replace'] ?? '')
68
+ .split(',')
69
+ .map((value) => value.trim())
70
+ .filter(Boolean));
71
+ replacements.add('android:enableOnBackInvokedCallback');
72
+ application.$['tools:replace'] = [...replacements].join(',');
73
+ }
74
+ const mainActivity = config_plugins_1.AndroidConfig.Manifest.getMainActivityOrThrow(mod.modResults);
75
+ mainActivity.$ = mainActivity.$ ?? {};
76
+ mainActivity.$['android:hardwareAccelerated'] = 'false';
77
+ return mod;
78
+ });
79
+ config = (0, config_plugins_1.withDangerousMod)(config, [
80
+ 'android',
81
+ async (mod) => {
82
+ const appProperties = path_1.default.join(mod.modRequest.platformProjectRoot, 'gradle.properties');
83
+ const unityProperties = path_1.default.resolve(mod.modRequest.projectRoot, props.androidExportPath, '..', 'gradle.properties');
84
+ if (!fs_1.default.existsSync(appProperties))
85
+ return mod;
86
+ let body = 'unityStreamingAssets=';
87
+ if (fs_1.default.existsSync(unityProperties)) {
88
+ body = fs_1.default
89
+ .readFileSync(unityProperties, 'utf8')
90
+ .split(/\r?\n/)
91
+ .filter((line) => line.startsWith('unity.') || line.startsWith('unityStreamingAssets='))
92
+ .join('\n');
93
+ }
94
+ const original = fs_1.default.readFileSync(appProperties, 'utf8');
95
+ fs_1.default.writeFileSync(appProperties, replacePropertiesBlock(original, body));
96
+ return mod;
97
+ },
98
+ ]);
99
+ return config_plugins_1.AndroidConfig.Permissions.withPermissions(config, []);
100
+ };
101
+ const withIosUnity = (config, props) => (0, config_plugins_1.withDangerousMod)(config, [
102
+ 'ios',
103
+ async (mod) => {
104
+ const podfilePath = path_1.default.join(mod.modRequest.platformProjectRoot, 'Podfile');
105
+ if (!fs_1.default.existsSync(podfilePath))
106
+ return mod;
107
+ const packageRoot = path_1.default.dirname(require.resolve(`${pkg.name}/package.json`, {
108
+ paths: [mod.modRequest.projectRoot],
109
+ }));
110
+ const rubyScript = path_1.default.join(packageRoot, 'plugin', 'ios', 'configure_unity.rb');
111
+ const relativeScript = path_1.default.relative(mod.modRequest.platformProjectRoot, rubyScript).replace(/\\/g, '/');
112
+ const exportPath = path_1.default.resolve(mod.modRequest.projectRoot, props.iosExportPath).replace(/\\/g, '/');
113
+ const body = [
114
+ `ENV['RN_UNITY_IOS_EXPORT_PATH'] = '${exportPath}'`,
115
+ `require File.expand_path('${relativeScript}', __dir__)`,
116
+ `ReactNativeUnity.configure_unity_project(self, ENV['RN_UNITY_IOS_EXPORT_PATH'])`,
117
+ ].join('\n');
118
+ const original = fs_1.default.readFileSync(podfilePath, 'utf8');
119
+ fs_1.default.writeFileSync(podfilePath, replaceMarkedBlock(original, body));
120
+ return mod;
121
+ },
122
+ ]);
123
+ const plugin = (config, options = {}) => {
124
+ const props = {
125
+ androidExportPath: options.androidExportPath ?? 'unity/builds/android/unityLibrary',
126
+ iosExportPath: options.iosExportPath ?? 'unity/builds/ios',
127
+ };
128
+ config = withAndroidUnity(config, props);
129
+ config = withIosUnity(config, props);
130
+ return config;
131
+ };
132
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(plugin, pkg.name, pkg.version);
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ReactNativeUnity
4
+ MARKER = '# @generated by @hugebug4ever/react-native-unity'
5
+
6
+ def self.same_path?(left, right)
7
+ File.expand_path(left.to_s) == File.expand_path(right.to_s)
8
+ end
9
+
10
+ def self.find_or_add_project_reference(project, group, unity_project_path)
11
+ existing = project.root_object.project_references.find do |reference|
12
+ project_ref = reference[:project_ref]
13
+ project_ref && same_path?(project_ref.real_path, unity_project_path)
14
+ end
15
+ return existing if existing
16
+
17
+ project_ref = group.new_file(unity_project_path)
18
+ products = group.find_subpath('Products', true)
19
+ reference = { product_group: products, project_ref: project_ref }
20
+ project.root_object.project_references << reference
21
+ reference
22
+ end
23
+
24
+ def self.find_or_add_unity_product(reference)
25
+ products = reference[:product_group]
26
+ product = products.files.find { |file| file.path == 'UnityFramework.framework' }
27
+ return product if product
28
+
29
+ product = products.new_file('UnityFramework.framework')
30
+ product.source_tree = 'BUILT_PRODUCTS_DIR'
31
+ product.explicit_file_type = 'wrapper.framework'
32
+ product.include_in_index = '0'
33
+ product
34
+ end
35
+
36
+ def self.embed_unity_framework(target, product)
37
+ add_build_file_once(target.frameworks_build_phase, product)
38
+ phase = target.copy_files_build_phases.find { |item| item.name == 'Embed Frameworks' }
39
+ phase ||= target.new_copy_files_build_phase('Embed Frameworks')
40
+ phase.dst_subfolder_spec = '10'
41
+ build_file = add_build_file_once(phase, product)
42
+ build_file.settings = { 'ATTRIBUTES' => %w[CodeSignOnCopy RemoveHeadersOnCopy] }
43
+ end
44
+
45
+ # Xcodeproj's add_file_reference always creates a new PBXBuildFile. Keep
46
+ # generated phases stable when CocoaPods runs post_install more than once.
47
+ def self.add_build_file_once(phase, file_reference)
48
+ matches = phase.files.select { |build_file| build_file.file_ref == file_reference }
49
+ build_file = matches.shift || phase.add_file_reference(file_reference, true)
50
+ matches.each { |duplicate| phase.remove_build_file(duplicate) }
51
+ build_file
52
+ end
53
+
54
+ def self.configure_target(target, unity_target, product, export_path)
55
+ has_dependency = target.dependencies.any? do |dependency|
56
+ dependency.target_proxy&.remote_global_id_string == unity_target.uuid
57
+ end
58
+ target.add_dependency(unity_target) unless has_dependency
59
+ embed_unity_framework(target, product)
60
+
61
+ target.build_configurations.each do |configuration|
62
+ headers = configuration.build_settings['HEADER_SEARCH_PATHS'] || ['$(inherited)']
63
+ headers = [headers] unless headers.is_a?(Array)
64
+ [export_path, File.join(export_path, 'UnityFramework')].each do |path|
65
+ headers << path unless headers.include?(path)
66
+ end
67
+ configuration.build_settings['HEADER_SEARCH_PATHS'] = headers
68
+ configuration.build_settings['RN_UNITY_IOS_EXPORT_PATH'] = export_path
69
+ end
70
+ end
71
+
72
+ # Called while the Podfile is evaluated. The post_install hook performs the
73
+ # project-reference changes after CocoaPods has created its project model.
74
+ def self.configure_unity_project(podfile_context, export_path)
75
+ unity_project_path = File.join(export_path, 'Unity-iPhone.xcodeproj')
76
+ unless File.directory?(unity_project_path)
77
+ Pod::UI.warn("[RNUnity] iOS Unity export is missing: #{unity_project_path}")
78
+ return
79
+ end
80
+
81
+ return if podfile_context.instance_variable_get(:@rn_unity_hook_installed)
82
+
83
+ podfile_context.instance_variable_set(:@rn_unity_hook_installed, true)
84
+ podfile_context.post_install do |installer|
85
+ unity_project = Xcodeproj::Project.open(unity_project_path)
86
+ unity_target = unity_project.targets.find { |target| target.name == 'UnityFramework' }
87
+ unless unity_target
88
+ Pod::UI.warn("[RNUnity] UnityFramework target is missing from #{unity_project_path}")
89
+ next
90
+ end
91
+
92
+ data_wired = unity_target.resources_build_phase.files_references.any? do |reference|
93
+ reference.path.to_s == 'Data' || reference.real_path.to_s.end_with?('/Data')
94
+ end
95
+ Pod::UI.warn('[RNUnity] UnityFramework target does not contain the exported Data resource') unless data_wired
96
+
97
+ installer.aggregate_targets.map(&:user_project).compact.uniq.each do |project|
98
+ group = project.main_group.find_subpath('Unity', true)
99
+ reference = find_or_add_project_reference(project, group, unity_project_path)
100
+ product = find_or_add_unity_product(reference)
101
+ project.targets.select { |target| target.product_type == 'com.apple.product-type.application' }.each do |target|
102
+ configure_target(target, unity_target, product, export_path)
103
+ end
104
+ project.save
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,175 @@
1
+ import {
2
+ AndroidConfig,
3
+ ConfigPlugin,
4
+ createRunOncePlugin,
5
+ withAppBuildGradle,
6
+ withAndroidManifest,
7
+ withDangerousMod,
8
+ withProjectBuildGradle,
9
+ withSettingsGradle,
10
+ } from '@expo/config-plugins';
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ export interface ReactNativeUnityPluginProps {
15
+ androidExportPath?: string;
16
+ iosExportPath?: string;
17
+ }
18
+
19
+ const pkg = require('../../package.json') as { name: string; version: string };
20
+ const START = '// @generated by @hugebug4ever/react-native-unity';
21
+ const END = '// @end @hugebug4ever/react-native-unity';
22
+ const PROPERTIES_START = '# @generated by @hugebug4ever/react-native-unity';
23
+ const PROPERTIES_END = '# @end @hugebug4ever/react-native-unity';
24
+
25
+ export function replaceMarkedBlock(source: string, body: string): string {
26
+ const block = `${START}\n${body.trim()}\n${END}`;
27
+ const pattern = new RegExp(`${START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
28
+ return pattern.test(source)
29
+ ? source.replace(pattern, block)
30
+ : `${source.trimEnd()}\n\n${block}\n`;
31
+ }
32
+
33
+ export function replacePropertiesBlock(source: string, body: string): string {
34
+ const legacyPattern = /^\/\/ @generated by @hugebug4ever\/react-native-unit.*?[\r\n]+[\s\S]*?^\/\/ @end @hugebug4ever\/react-native-unit.*?(?:[\r\n]+|$)/m;
35
+ const cleaned = source.replace(legacyPattern, '');
36
+ const block = `${PROPERTIES_START}\n${body.trim()}\n${PROPERTIES_END}`;
37
+ const pattern = new RegExp(
38
+ `${PROPERTIES_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?${PROPERTIES_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
39
+ 'm',
40
+ );
41
+ return pattern.test(cleaned)
42
+ ? cleaned.replace(pattern, block)
43
+ : `${cleaned.trimEnd()}\n\n${block}\n`;
44
+ }
45
+
46
+ const withAndroidUnity: ConfigPlugin<Required<ReactNativeUnityPluginProps>> = (
47
+ config,
48
+ props,
49
+ ) => {
50
+ config = withSettingsGradle(config, (mod) => {
51
+ const exportPath = props.androidExportPath.replace(/\\/g, '/');
52
+ mod.modResults.contents = replaceMarkedBlock(
53
+ mod.modResults.contents,
54
+ `include ':unityLibrary'\nproject(':unityLibrary').projectDir = new File(rootProject.projectDir, '../${exportPath}')`,
55
+ );
56
+ return mod;
57
+ });
58
+
59
+ config = withAppBuildGradle(config, (mod) => {
60
+ if (mod.modResults.language !== 'groovy') {
61
+ throw new Error('RN Unity currently requires a Groovy app/build.gradle');
62
+ }
63
+ mod.modResults.contents = replaceMarkedBlock(
64
+ mod.modResults.contents,
65
+ `dependencies {\n implementation project(':unityLibrary')\n}`,
66
+ );
67
+ return mod;
68
+ });
69
+
70
+ config = withProjectBuildGradle(config, (mod) => {
71
+ if (mod.modResults.language !== 'groovy') {
72
+ throw new Error('RN Unity currently requires a Groovy project/build.gradle');
73
+ }
74
+ const body = `ext.minSdkVersion = Math.max((ext.has('minSdkVersion') ? ext.minSdkVersion : 24) as Integer, 25)`;
75
+ const block = `${START}\n${body}\n${END}`;
76
+ const withBlock = replaceMarkedBlock(mod.modResults.contents, body);
77
+ const withoutBlock = withBlock.replace(block, '').trimEnd();
78
+ const expoPlugin = 'apply plugin: "expo-root-project"';
79
+ if (!withoutBlock.includes(expoPlugin)) {
80
+ throw new Error('RN Unity could not locate the Expo root Gradle plugin');
81
+ }
82
+ mod.modResults.contents = withoutBlock.replace(
83
+ expoPlugin,
84
+ `${block}\n\n${expoPlugin}`,
85
+ );
86
+ return mod;
87
+ });
88
+
89
+ config = withAndroidManifest(config, (mod) => {
90
+ const manifest = mod.modResults.manifest;
91
+ manifest.$ = manifest.$ ?? {};
92
+ manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
93
+ const application = manifest.application?.[0];
94
+ if (application) {
95
+ application.$ = application.$ ?? {};
96
+ const replacements = new Set(
97
+ (application.$['tools:replace'] ?? '')
98
+ .split(',')
99
+ .map((value) => value.trim())
100
+ .filter(Boolean),
101
+ );
102
+ replacements.add('android:enableOnBackInvokedCallback');
103
+ application.$['tools:replace'] = [...replacements].join(',');
104
+ }
105
+ const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(mod.modResults);
106
+ mainActivity.$ = mainActivity.$ ?? {};
107
+ mainActivity.$['android:hardwareAccelerated'] = 'false';
108
+ return mod;
109
+ });
110
+
111
+ config = withDangerousMod(config, [
112
+ 'android',
113
+ async (mod) => {
114
+ const appProperties = path.join(mod.modRequest.platformProjectRoot, 'gradle.properties');
115
+ const unityProperties = path.resolve(
116
+ mod.modRequest.projectRoot,
117
+ props.androidExportPath,
118
+ '..',
119
+ 'gradle.properties',
120
+ );
121
+ if (!fs.existsSync(appProperties)) return mod;
122
+ let body = 'unityStreamingAssets=';
123
+ if (fs.existsSync(unityProperties)) {
124
+ body = fs
125
+ .readFileSync(unityProperties, 'utf8')
126
+ .split(/\r?\n/)
127
+ .filter((line) => line.startsWith('unity.') || line.startsWith('unityStreamingAssets='))
128
+ .join('\n');
129
+ }
130
+ const original = fs.readFileSync(appProperties, 'utf8');
131
+ fs.writeFileSync(appProperties, replacePropertiesBlock(original, body));
132
+ return mod;
133
+ },
134
+ ]);
135
+
136
+ return AndroidConfig.Permissions.withPermissions(config, []);
137
+ };
138
+
139
+ const withIosUnity: ConfigPlugin<Required<ReactNativeUnityPluginProps>> = (
140
+ config,
141
+ props,
142
+ ) =>
143
+ withDangerousMod(config, [
144
+ 'ios',
145
+ async (mod) => {
146
+ const podfilePath = path.join(mod.modRequest.platformProjectRoot, 'Podfile');
147
+ if (!fs.existsSync(podfilePath)) return mod;
148
+ const packageRoot = path.dirname(require.resolve(`${pkg.name}/package.json`, {
149
+ paths: [mod.modRequest.projectRoot],
150
+ }));
151
+ const rubyScript = path.join(packageRoot, 'plugin', 'ios', 'configure_unity.rb');
152
+ const relativeScript = path.relative(mod.modRequest.platformProjectRoot, rubyScript).replace(/\\/g, '/');
153
+ const exportPath = path.resolve(mod.modRequest.projectRoot, props.iosExportPath).replace(/\\/g, '/');
154
+ const body = [
155
+ `ENV['RN_UNITY_IOS_EXPORT_PATH'] = '${exportPath}'`,
156
+ `require File.expand_path('${relativeScript}', __dir__)`,
157
+ `ReactNativeUnity.configure_unity_project(self, ENV['RN_UNITY_IOS_EXPORT_PATH'])`,
158
+ ].join('\n');
159
+ const original = fs.readFileSync(podfilePath, 'utf8');
160
+ fs.writeFileSync(podfilePath, replaceMarkedBlock(original, body));
161
+ return mod;
162
+ },
163
+ ]);
164
+
165
+ const plugin: ConfigPlugin<ReactNativeUnityPluginProps> = (config, options = {}) => {
166
+ const props = {
167
+ androidExportPath: options.androidExportPath ?? 'unity/builds/android/unityLibrary',
168
+ iosExportPath: options.iosExportPath ?? 'unity/builds/ios',
169
+ };
170
+ config = withAndroidUnity(config, props);
171
+ config = withIosUnity(config, props);
172
+ return config;
173
+ };
174
+
175
+ export default createRunOncePlugin(plugin, pkg.name, pkg.version);
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "moduleResolution": "Node",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "build"
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }
13
+
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+ unity_export = ENV['RN_UNITY_IOS_EXPORT_PATH']
5
+
6
+ Pod::Spec.new do |s|
7
+ s.name = 'react-native-unity'
8
+ s.version = package['version']
9
+ s.summary = package['description']
10
+ s.homepage = 'https://github.com/hugebug4ever/react-native-unity'
11
+ s.license = package['license']
12
+ s.author = 'hugebug4ever'
13
+ s.platforms = { :ios => '15.1' }
14
+ s.source = { :git => 'https://github.com/hugebug4ever/react-native-unity.git', :tag => s.version.to_s }
15
+ s.source_files = 'ios/**/*.{h,m,mm,cpp}'
16
+ s.requires_arc = true
17
+
18
+ if respond_to?(:install_modules_dependencies, true)
19
+ install_modules_dependencies(s)
20
+ else
21
+ s.dependency 'React-Core'
22
+ s.dependency 'React-RCTFabric'
23
+ s.dependency 'React-Codegen'
24
+ end
25
+
26
+ if unity_export
27
+ s.pod_target_xcconfig = {
28
+ 'HEADER_SEARCH_PATHS' => "$(inherited) \"#{unity_export}/UnityFramework\" \"#{unity_export}\"",
29
+ 'FRAMEWORK_SEARCH_PATHS' => "$(inherited) \"#{unity_export}/build\"",
30
+ 'OTHER_LDFLAGS' => '$(inherited) -framework UnityFramework'
31
+ }
32
+ end
33
+ end
34
+
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: { sourceDir: './android' },
5
+ ios: { podspecPath: './react-native-unity.podspec' },
6
+ },
7
+ },
8
+ };
9
+