Unity 项目打包 Framework 方式嵌入 UnityGetGLView 到 iOS 工程 (1)

 • 

距离上次写这个话题快有一年了,上次也是做外包的时候有嵌入 Unity View 到 iOS 工程的需求。这次为了双方的便捷,参考 unity-ios-framework 采用了打包整个 Unity 为静态 framework 的方法,这种方法对甲方非常友好,一次配置,以后每次只需更新 framework 和 Data 即可,而自己需要多做些配置,为了把这块也自动化,写了个 Build Script。

#!/usr/bin/env bash
if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then  
source "$HOME/.rvm/scripts/rvm"  
elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then  
source "/usr/local/rvm/scripts/rvm"  
else  
printf "ERROR: An RVM installation was not found.\n"  
exit 128  
fi

# rvm default
# ruby -v
# rvm use system
# ruby -v

echo $SHELL  
which ruby  
set -e

export LANG=en_US.UTF-8  
export LANGUAGE=en_US.UTF-8  
export LC_ALL=en_US.UTF-8

if ! which ruby > /dev/null; then  
echo "error: ruby is not installed."  
exit 1  
fi

if ! which xcodeproj > /dev/null; then  
echo "error: xcodeproj 没有安装,请 [sudo] gem install xcodeproj [--user-install],如果通过 rvm 使用了其他版本 ruby 请先 rvm use system 再 install"  
exit 1  
fi

chmod u+x $SRCROOT/UnityOutFramework/UnityPreBuild.rb  
cd $SRCROOT/UnityOutFramework  
ruby UnityPreBuild.rb  

在 $SRCROOT 下面新建文件夹 UnityOutFramework,放置 Info.plist 和 UnityPreBuild.rb,内容如下

require 'xcodeproj'  
include Xcodeproj::Project::Object

puts "检查 Unity 项目所需设定"  
project_path = '../Unity-iPhone.xcodeproj'  
project = Xcodeproj::Project.open(project_path)

deleted_targets, preserved_targets = project.targets.partition{ |target| target.name == 'UnityOutFramework'}

puts 'Deleted : '  
puts deleted_targets  
puts 'Preserved : '  
puts preserved_targets

project.targets.replace(preserved_targets)

puts 'Added : '  
target = project.new_target(:framework,'UnityOutFramework',:ios)  
puts target

puts '-------- Config Names --------'  
target.build_configurations.each { |config|  
    puts config.name

    config.build_settings['HEADER_SEARCH_PATHS'] = '$(inherited) $(SRCROOT)/Classes $(SRCROOT) $(SRCROOT)/Classes/Native $(SRCROOT)/Libraries/bdwgc/include $(SRCROOT)/Libraries/libil2cpp/include'
    config.build_settings['LIBRARY_SEARCH_PATHS'] = '$(inherited) $(SRCROOT) $(SRCROOT)/Libraries'
    config.build_settings['GCC_PRECOMPILE_PREFIX_HEADER'] = 'YES'
    config.build_settings['GCC_PREFIX_HEADER'] = 'Classes/Prefix.pch'
    config.build_settings['ALWAYS_SEARCH_USER_PATHS'] = 'NO'
    config.build_settings['ARCHS'] = '$(ARCHS_STANDARD)'
    config.build_settings['INFOPLIST_FILE'] = 'UnityOutFramework/Info.plist' 
    config.build_settings['CODE_SIGN_STYLE'] = 'Automatic'
    config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'com.JustZht.UnityOutFramework'
    config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '8.0'
    config.build_settings['ENABLE_BITCODE'] = 'YES'
    config.build_settings['MACH_O_TYPE'] = 'staticlib'
    config.build_settings['DEFINES_MODULE'] = 'YES'
    config.build_settings['GCC_C_LANGUAGE_STANDARD'] = 'gnu11'
    config.build_settings['GCC_ENABLE_CPP_EXCEPTIONS'] = 'YES'
    config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'gnu++14'
    config.build_settings['CLANG_CXX_LIBRARY'] = 'libc++'
    config.build_settings['GCC_ENABLE_CPP_EXCEPTIONS'] = 'YES'
    config.build_settings['CLANG_ENABLE_MODULES'] = 'YES'
    config.build_settings['CLANG_ENABLE_OBJC_ARC'] = 'YES'
    config.build_settings['CLANG_ENABLE_OBJC_WEAK'] = 'YES'
    config.build_settings['CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING'] = 'YES'
    config.build_settings['CLANG_WARN__DUPLICATE_METHOD_MATCH'] = 'YES'
    config.build_settings['GCC_WARN_ABOUT_RETURN_TYPE'] = 'YES'
    config.build_settings['GCC_THUMB_SUPPORT'] = 'NO'
    config.build_settings['GCC_USE_INDIRECT_FUNCTION_CALLS'] = 'NO'
    config.build_settings['MTL_ENABLE_DEBUG_INFO'] = 'NO'
    config.build_settings['UNITY_SCRIPTING_BACKEND'] = 'il2cpp'

    if  (config.build_settings['OTHER_CFLAGS'] == nil)
        config.build_settings['OTHER_CFLAGS'] = '$(inherited)'
    end

    if !config.build_settings['OTHER_CFLAGS'].include? "$(inherited)"
        config.build_settings['OTHER_CFLAGS'] << ' $(inherited) '
    end

    if !config.build_settings['OTHER_CFLAGS'].include? "-DRUNTIME_IL2CPP=1"
        config.build_settings['OTHER_CFLAGS'] << ' -DRUNTIME_IL2CPP=1'
    end

    # LINKER FLAGS
    if  (config.build_settings['OTHER_LDFLAGS'] == nil)
        config.build_settings['OTHER_LDFLAGS'] = '$(inherited)'
    end

    if !config.build_settings['OTHER_LDFLAGS'].include? "$(inherited)"
        config.build_settings['OTHER_LDFLAGS'] << ' $(inherited) '
    end


    if  !config.build_settings['OTHER_LDFLAGS'].include? "CoreMotion"
        config.build_settings['OTHER_LDFLAGS'] << ' -weak_framework'
        config.build_settings['OTHER_LDFLAGS'] << ' CoreMotion'
    end

    if  !config.build_settings['OTHER_LDFLAGS'].include? "-weak-lSystem"
        config.build_settings['OTHER_LDFLAGS'] << ' -weak-lSystem'
    end

    # Configs

    if config.name == 'Debug'

        if !config.build_settings['OTHER_CFLAGS'].include? "-fembed-bitcode-marker"
            config.build_settings['OTHER_CFLAGS'] << ' -fembed-bitcode-marker'
        end

    elsif config.name == 'Release' || config.name == 'ReleaseForProfiling' || config.name == 'ReleaseForRunning'

        if !config.build_settings['OTHER_CFLAGS'].include? "-fembed-bitcode"
            config.build_settings['OTHER_CFLAGS'] << ' -fembed-bitcode'
        end
    end
}

puts '-------- Build Pharses Names --------'  
target.build_phases.each { |build_phase|  
    puts build_phase.isa
    if (build_phase.isa == 'PBXSourcesBuildPhase')
            build_phase.clear 
    end
}

puts '-------- Objects Names --------'  
project.objects.each { |object|  
    puts object.display_name + ' TYPE: ' + object.isa
}

accepted_compile_sources_formats = [".cpp", ".mm", ".m"]  
unaccepted_compile_sources_file_names = ['Unity_iPhone_Tests.m','RegisterMonoModules.cpp','main.mm']

accepted_header_formats = [".h"]  
accepted_public_header_file_names = ["LifeCycleListener.h",'Preprocessor.h','RegisterFeatures.h','RenderPluginDelegate.h','UnityAppController.h','UnityForwardDecls.h','UnityInterface.h','UnityRendering.h','UnityViewControllerBase.h','UnityViewControllerBase+iOS.h']  
unaccepted_header_file_names = ['RegisterMonoModules.h']

accepted_lib_formats = [".a"]

puts '-------- Add Files --------'  
project.objects.each { |object| 

    if object.isa == 'PBXBuildFile'

        if object.file_ref.path != nil && File.extname(object.file_ref.path) != nil
            ext = File.extname(object.file_ref.path)
            fullname = File.basename(object.file_ref.path)

            if !unaccepted_compile_sources_file_names.include?(fullname) && accepted_compile_sources_formats.include?(ext)
                target.source_build_phase.add_file_reference(object.file_ref,true)
                puts 'ADDED TO COMIPLE SOURCES -> ' + object.file_ref.path
            end

            if !unaccepted_header_file_names.include?(fullname) && accepted_header_formats.include?(ext)
                is_public = false
                file = target.headers_build_phase.add_file_reference(object.file_ref,true)
                if accepted_public_header_file_names.include?(fullname)
                    file.settings = { 'ATTRIBUTES' => ['Public']}
                end
                puts 'ADDED TO HEADERS -> ' + object.file_ref.path + ' via PBXBuildFile PUBLIC -> ' + (is_public ? '1' : '0')
            end

        end

    elsif object.isa == 'PBXFileReference'

        if object.path != nil && File.extname(object.path) != nil
            ext = File.extname(object.path)
            fullname = File.basename(object.path)

            if !unaccepted_compile_sources_file_names.include?(fullname) && accepted_compile_sources_formats.include?(ext)
                target.source_build_phase.add_file_reference(object,true)
                puts 'ADDED TO COMIPLE SOURCES -> ' + object.path
            end

            if !unaccepted_header_file_names.include?(fullname) && accepted_header_formats.include?(ext)
                target.headers_build_phase.add_file_reference(object,true)
                puts 'ADDED TO HEADERS -> ' + object.path + ' via PBXFileReference'
            end

            if accepted_lib_formats.include?(ext)
                target.frameworks_build_phase.add_file_reference(object,true)
                puts 'ADDED TO FRAMEWORKS -> ' + object.path
            end
        end

    end
}

deleted_phases, preserved_phases = target.build_phases.partition{ |phase| phase.isa == 'PBXShellScriptBuildPhase' && phase.name == 'Replace Headers'}  
target.build_phases.replace(preserved_phases)  
uuid = Digest::MD5.hexdigest('Replace Headers').upcase[0,24]  
run_script_phase = PBXShellScriptBuildPhase.new(project, uuid)  
run_script_phase.initialize_defaults  
run_script_phase.name = 'Replace Headers'  
run_script_phase.shell_script = 'inputDirectory=$PROJECT_DIR/${TARGET_NAME}/FlatPathHeaders  
outputDirectory=${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/Headers

echo "in $inputDirectory"  
echo "out $outputDirectory"

if [ ! -d "$outputDirectory" ]; then  
echo "no out path,and create: $outputDirectory"  
mkdir $outputDirectory  
fi

cp -R $inputDirectory/. $outputDirectory/'  
target.build_phases.insert(target.build_phases.length, run_script_phase)

# names = [ 'Foundation', 'CoreFoundation', 'MediaPlayer', 'CoreMotion', 'MediaToolBox', 'AudioToolbox', 'AVFoundation', 'CoreMedia', 'CoreLocation' ]
# names.each do |name|
#     target.add_system_framework(name)
# end

project.save

使用时将 Run Script 添加到导出的 Xcode 项目默认 target 中(其实这块也可以直接在 Unity 内通过 PBXProject 自动添加到 target 里去),然后直接 Run。项目并不会 Run 而是会 Cancel,因为 ruby 脚本在最后保存了工程。然后就会出现 UnityOutFramework,Build 即可。