跳转到主要内容

Sign in with Google 环境设置(兼容 v1)

  • 这是 Google 登录的 Web 认证方式,兼容 Google v1 方式。
  • 为使用 Google 账号进行环境设置。
  • 建议使用 PlayNANOO SDK 5.0.0.6 及以上版本。(修复浏览器缓存问题)
  • 最低要求版本:5.0.0.3

注册 Google 用户 OAuth 客户端 ID​

  • 选择 Google Cloud Platform > 凭据 > 创建凭据 > OAuth 客户端 ID。
  • 选择应用类型为 Web application。
  • 在授权重定向 URI 中添加以下 2 个 URL:
    • https://www.playnanoo.com/oauth2redirect.html(向后兼容)
    • https://www.playnanoo.com/oauth2redirect.html?v=2(新版本)
  • 确认已添加的 OAuth 客户端 ID 的客户端 ID 信息。

Unity 深度链接注册步骤 Android​

深度链接 scheme 变更通知

原有的 mygame 共享 scheme 已被 弃用。 当多个使用相同 scheme 的应用安装在同一设备上时,认证完成后会出现返回到错误应用的问题。 SDK 会自动使用在 PlayNANOO 控制台注册的 Game ID 作为 scheme,因此无需单独设置 scheme。

Game ID 可以在 PlayNANOO 控制台 > 游戏&应用设置 页面的 "游戏&应用 ID" 项目中查看。

  • 在 Assets/Editor/ 文件夹中创建 AndroidDeepLinkPostProcessor.cs 文件并添加以下内容。
  • SDK 会自动使用在 PlayNANOO Settings 中注册的 Game ID 作为深度链接 scheme。
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Android;
using System.IO;
using System.Xml;

public class AndroidDeepLinkPostProcessor : IPostGenerateGradleAndroidProject
{
public int callbackOrder => 999;

public void OnPostGenerateGradleAndroidProject(string path)
{
string manifestPath = Path.Combine(path, "src/main/AndroidManifest.xml");

if (!File.Exists(manifestPath))
{
UnityEngine.Debug.LogError("[DeepLink] AndroidManifest.xml not found!");
return;
}

// PlayNANOOSettings에서 game_id 로드
PlayNANOOSettings settings = AssetDatabase.LoadAssetAtPath<PlayNANOOSettings>(
"Assets/PlayNANOO/Resources/PlayNANOOSettings.asset");

if (settings == null || string.IsNullOrEmpty(settings.gameID))
{
UnityEngine.Debug.LogError("[DeepLink] PlayNANOOSettings not found or gameID is empty!");
return;
}

string gameId = settings.gameID;

XmlDocument manifest = new();
manifest.Load(manifestPath);

XmlNamespaceManager nsmgr = new(manifest.NameTable);
nsmgr.AddNamespace("android", "http://schemas.android.com/apk/res/android");

AddDeepLinkIntentFilter(manifest, nsmgr, gameId);
AddInternetPermission(manifest, nsmgr);
AddBrowserQueries(manifest, nsmgr);

manifest.Save(manifestPath);
UnityEngine.Debug.Log($"[DeepLink] AndroidManifest 설정 완료 (scheme: {gameId})");
}

private void AddDeepLinkIntentFilter(XmlDocument manifest, XmlNamespaceManager nsmgr, string gameId)
{
XmlNode applicationNode = manifest.SelectSingleNode("/manifest/application", nsmgr);
XmlNode activityNode = applicationNode?.SelectSingleNode(
"activity[@android:name='com.unity3d.player.UnityPlayerActivity']", nsmgr);
activityNode ??= applicationNode?.SelectSingleNode(
"activity[contains(@android:name, 'UnityPlayerActivity')]", nsmgr);

if (activityNode == null)
{
UnityEngine.Debug.LogError("[DeepLink] UnityPlayerActivity not found!");
return;
}

// 이미 game_id scheme 딥링크가 있는지 확인
XmlNodeList intentFilters = activityNode.SelectNodes("intent-filter", nsmgr);
foreach (XmlNode filter in intentFilters)
{
XmlNode dataNode = filter.SelectSingleNode(
$"data[@android:scheme='{gameId}']", nsmgr);
if (dataNode != null)
{
UnityEngine.Debug.Log($"[DeepLink] 딥링크 이미 등록됨 (scheme: {gameId})");
return;
}
}

// 딥링크 Intent Filter 생성
XmlElement intentFilter = manifest.CreateElement("intent-filter");

XmlElement action = manifest.CreateElement("action");
action.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.intent.action.VIEW");
intentFilter.AppendChild(action);

XmlElement categoryDefault = manifest.CreateElement("category");
categoryDefault.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.intent.category.DEFAULT");
intentFilter.AppendChild(categoryDefault);

XmlElement categoryBrowsable = manifest.CreateElement("category");
categoryBrowsable.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.intent.category.BROWSABLE");
intentFilter.AppendChild(categoryBrowsable);

XmlElement data = manifest.CreateElement("data");
data.SetAttribute("scheme", "http://schemas.android.com/apk/res/android", gameId);
intentFilter.AppendChild(data);

activityNode.AppendChild(intentFilter);
UnityEngine.Debug.Log($"[DeepLink] 딥링크 Intent Filter 추가 완료 (scheme: {gameId})");
}

private void AddInternetPermission(XmlDocument manifest, XmlNamespaceManager nsmgr)
{
XmlNode existingPermission = manifest.SelectSingleNode(
"/manifest/uses-permission[@android:name='android.permission.INTERNET']", nsmgr);
if (existingPermission != null) return;

XmlElement permission = manifest.CreateElement("uses-permission");
permission.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.permission.INTERNET");

XmlNode manifestNode = manifest.SelectSingleNode("/manifest");
manifestNode?.AppendChild(permission);
}

private void AddBrowserQueries(XmlDocument manifest, XmlNamespaceManager nsmgr)
{
XmlNode existingQueries = manifest.SelectSingleNode("/manifest/queries", nsmgr);
if (existingQueries != null) return;

XmlElement queries = manifest.CreateElement("queries");
XmlElement intent = manifest.CreateElement("intent");

XmlElement action = manifest.CreateElement("action");
action.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.intent.action.VIEW");
intent.AppendChild(action);

XmlElement category = manifest.CreateElement("category");
category.SetAttribute("name", "http://schemas.android.com/apk/res/android",
"android.intent.category.BROWSABLE");
intent.AppendChild(category);

XmlElement data = manifest.CreateElement("data");
data.SetAttribute("scheme", "http://schemas.android.com/apk/res/android", "https");
intent.AppendChild(data);

queries.AppendChild(intent);

XmlNode manifestNode = manifest.SelectSingleNode("/manifest");
manifestNode?.AppendChild(queries);
}
}
#endif
构建结果示例

如果在 PlayNANOO 控制台注册的 Game ID 为 yourgameid,构建时 AndroidManifest.xml 中将如下注册:

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourgameid" />
</intent-filter>

Unity 深度链接注册步骤 iOS​

  • 在 Info.plist 中添加以下内容。
  • 将 CFBundleURLSchemes 值设置为 在 PlayNANOO 控制台注册的 Game ID。
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{your_game_id}</string>
</array>
<key>CFBundleURLName</key>
<string>com.mycompany.{your_game_id}</string>
</dict>
</array>
设置示例

如果在 PlayNANOO 控制台注册的 Game ID 为 yourgameid:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourgameid</string>
</array>
<key>CFBundleURLName</key>
<string>com.mycompany.yourgameid</string>
</dict>
</array>

Android Chrome Custom Tabs 设置(可选)​

在 Android 上,如需以应用内叠加形式打开浏览器,需要 Chrome Custom Tabs 库。 如果没有该库,将自动使用外部浏览器打开。

mainTemplate.gradle 设置​

  • 在 Assets/Plugins/Android/mainTemplate.gradle 文件中添加以下依赖项。
dependencies {
implementation 'androidx.browser:browser:1.5.0'
// ... 기존 의존성들
}

使用 Custom Build 时​

  • 在 Project Settings > Player > Android > Publishing Settings 中勾选 "Custom Main Gradle Template"。
  • 在生成的 mainTemplate.gradle 文件中添加上述依赖项。

ProGuard 设置​

  • 在 Assets/Plugins/Android/proguard-user.txt 文件中添加以下规则。
  • 防止 ProGuard/R8 移除 Chrome Custom Tabs 类。
-keep class androidx.browser.customtabs.** { *; }
信息

如果未包含 Chrome Custom Tabs 库,SDK 将自动回退到外部 Chrome 浏览器。 使用外部浏览器时功能也能正常运行,并通过深度链接返回应用。

Android 403 错误 (disallowed_useragent)

根据 Google 安全策略,WebView(内置浏览器)中的 OAuth 认证已被阻止。 在 NAVER、LINE、KakaoTalk 等应用的内置浏览器中进行 Google 登录时,会出现 403 disallowed_useragent 错误。

SDK 浏览器选择行为:

  • 如果已安装 Chrome,优先使用 Chrome Custom Tabs 或 Chrome 浏览器。
  • 如果 Chrome 未安装或已禁用,将切换到系统默认浏览器。
  • 如果默认浏览器是基于 WebView 的应用(如 NAVER),则会发生 403 错误。

用户指导:

  • Google 登录需要 Chrome、Samsung Internet、Firefox 等独立浏览器。
  • 如果设备上未安装 Chrome,需要从 Google Play 安装。
  • 如果已安装 Chrome 但仍然出现错误,请检查设备设置中 Chrome 是否被禁用。

参考:Modernizing OAuth Interactions in Native Apps


已弃用:原有 mygame scheme 方式​

已弃用(SDK 5.0.0.5 之前的版本)

以下方式 请勿继续使用。 所有应用共享相同的 mygame:// scheme,当同一设备上安装了 2 个及以上使用 PlayNANOO SDK 的应用时,认证完成后会出现返回到错误应用的问题。

新方式中,SDK 自动使用在 PlayNANOO 控制台注册的 Game ID 作为 scheme,实现每个应用的唯一识别。

原有方式(仅供参考)

Android(原有)​

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Android;
using System.IO;

public class AndroidDeepLinkPostProcessor : IPostGenerateGradleAndroidProject
{
public int callbackOrder => 999;

public void OnPostGenerateGradleAndroidProject(string path)
{
string manifestPath = Path.Combine(path, "src/main/AndroidManifest.xml");

if (!File.Exists(manifestPath))
{
UnityEngine.Debug.LogError("[DeepLink] AndroidManifest.xml not found!");
return;
}

string manifest = File.ReadAllText(manifestPath);

if (!manifest.Contains("mygame://oauth2redirect"))
{
string intentFilter = @"
<intent-filter>
<action android:name=""android.intent.action.VIEW"" />
<category android:name=""android.intent.category.DEFAULT"" />
<category android:name=""android.intent.category.BROWSABLE"" />
<data android:scheme=""mygame"" android:host=""oauth2redirect"" />
</intent-filter>";

manifest = manifest.Replace(
"</activity>",
intentFilter + "\n</activity>"
);

File.WriteAllText(manifestPath, manifest);
UnityEngine.Debug.Log("[DeepLink] AndroidManifest 딥링크 등록 완료");
}
else
{
UnityEngine.Debug.Log("[DeepLink] AndroidManifest 이미 등록됨");
}
}
}
#endif

iOS(原有)​

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>mygame</string>
</array>
<key>CFBundleURLName</key>
<string>com.mycompany.mygame</string>
</dict>
</array>