@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.
- package/README.md +77 -0
- package/android/build.gradle +38 -0
- package/android/consumer-rules.pro +3 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityBridge.kt +5 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityHostView.kt +95 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityPackage.kt +31 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/RNUnityViewManager.kt +30 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/UnityPlayerAdapter.kt +58 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeController.kt +329 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeModule.kt +46 -0
- package/android/src/main/java/com/hugebug4ever/rnunity/UnityRuntimeState.kt +16 -0
- package/app.plugin.js +2 -0
- package/cli/index.js +140 -0
- package/ios/RNUnityNativeBridge.mm +6 -0
- package/ios/RNUnityRuntimeController.h +29 -0
- package/ios/RNUnityRuntimeController.mm +316 -0
- package/ios/RNUnityView.h +17 -0
- package/ios/RNUnityView.mm +105 -0
- package/ios/UnityRuntime.mm +49 -0
- package/lib/commonjs/UnityRuntime.js +53 -0
- package/lib/commonjs/UnityRuntime.js.map +1 -0
- package/lib/commonjs/UnityView.js +35 -0
- package/lib/commonjs/UnityView.js.map +1 -0
- package/lib/commonjs/index.js +20 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NativeUnityRuntime.js +9 -0
- package/lib/commonjs/specs/NativeUnityRuntime.js.map +1 -0
- package/lib/commonjs/specs/NativeUnityView.js +10 -0
- package/lib/commonjs/specs/NativeUnityView.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/typescript/UnityRuntime.d.ts +12 -0
- package/lib/typescript/UnityRuntime.d.ts.map +1 -0
- package/lib/typescript/UnityView.d.ts +3 -0
- package/lib/typescript/UnityView.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/specs/NativeUnityRuntime.d.ts +15 -0
- package/lib/typescript/specs/NativeUnityRuntime.d.ts.map +1 -0
- package/lib/typescript/specs/NativeUnityView.d.ts +32 -0
- package/lib/typescript/specs/NativeUnityView.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +63 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package/UnityRuntime.ts +64 -0
- package/package/UnityView.tsx +32 -0
- package/package/index.ts +20 -0
- package/package/specs/NativeUnityRuntime.ts +17 -0
- package/package/specs/NativeUnityView.ts +27 -0
- package/package/types.ts +85 -0
- package/package.json +90 -0
- package/plugin/build/index.js +132 -0
- package/plugin/ios/configure_unity.rb +108 -0
- package/plugin/src/index.ts +175 -0
- package/plugin/tsconfig.json +13 -0
- package/react-native-unity.podspec +34 -0
- package/react-native.config.js +9 -0
- package/unity-package/Editor/Exporter.cs +91 -0
- package/unity-package/Editor/Exporter.cs.meta +2 -0
- package/unity-package/Editor/ReactNativeUnity.Editor.asmdef +8 -0
- package/unity-package/Editor/ReactNativeUnity.Editor.asmdef.meta +7 -0
- package/unity-package/Editor.meta +8 -0
- package/unity-package/Runtime/ReactNativeBridge.cs +48 -0
- package/unity-package/Runtime/ReactNativeBridge.cs.meta +2 -0
- package/unity-package/Runtime/ReactNativeUnity.Runtime.asmdef +6 -0
- package/unity-package/Runtime/ReactNativeUnity.Runtime.asmdef.meta +7 -0
- package/unity-package/Runtime.meta +8 -0
- package/unity-package/package.json +11 -0
- 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');
|
package/package/types.ts
ADDED
|
@@ -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,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
|
+
|