首先我要说一下,只有在Windows8.0/8.1/RT系统中才会遇到这种问题。也就是传说中的Windows平板电脑。
首先描述一下问题。使用Unity开发游戏的同志们都知道,Unity提供了TouchScreenKeyboard类来实现虚拟键盘的弹出和使用。但是当你导出应用程序装在平板电脑上的时候,你会悲剧的发现,它不好使!一开始我还以为是一个小BUG,但是当我深入研究的时候,发现这特么的是个大坑!Unity给出的答复是:微软没有提供接口。而微软提供的答复是:API就是这么设计的,平板电脑屏幕右下角有一个小图标,点击它才可以出键盘。否则就必须只能用Windows自带的XAML控件去唤醒虚拟键盘。但是游戏是全屏幕的好伐?!然后,微软的技术支持给我提供了一个非常非常复杂的解决方案。我没有试验,因为我后来采用其他方式解决了这个问题,那就是我给你贴个Windows自带的输入框上去!
前面说过,如果要解决这个问题,需要导出成XAML C# Solution。在这个情况下,Unity会为我们构建一个基于XAML的工程。这个工程基本上是下面这个样子:
(代码在公司的机器上,我不能随便用,以下给大家看的是网上下载的AppSample,另外我家里也没有Windows8的机器,不过基本上是一回事。)
(另外我也没装2013,只是给大家提供个思路)
下面是实际步骤
1. 首先你需要去了解一些关于XAML的知识:http://zh.wikipedia.org/zh-cn/XAML
2. 在MainPage.xaml中创建一个输入框控件,如果需要,让你的美术给你提供一张png格式的图片作为输入框的背板。代码如下
<Page
x:Class="Template.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Template"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<SwapChainBackgroundPanel x:Name="DXSwapChainBackgroundPanel">
<SwapChainBackgroundPanel.Resources>
<ImageBrush x:Key="img_small" ImageSource="Assets/xaml_input_small.png"></ImageBrush>
<ImageBrush x:Key="img_big" ImageSource="Assets/xaml_input_big.png"></ImageBrush>
</SwapChainBackgroundPanel.Resources>
<Grid x:Name="ExtendedSplashGrid">
<Image x:Name="ExtendedSplashImage" Source="Assets/SplashScreen.png"/>
</Grid>
<TextBox BorderThickness="0" Background="{StaticResource img_small}" x:Name="textBase_Small" Height="48" Width="484"
FontSize="20" KeyDown="fuckingText_KeyDown" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<TextBox BorderThickness="0" Background="{StaticResource img_big}" x:Name="textBase_Big" Height="98" Width="358"
FontSize="20" KeyDown="fuckingText_KeyDown" />
<TextBox BorderThickness="0" x:Name="textBase_Chat" Height="48" Width="484" KeyDown="fuckingText_KeyDown" FontSize="20" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center" HorizontalContentAlignment="Center"/>
<PasswordBox BorderThickness="0" Background="{StaticResource img_small}" x:Name="passwordBase" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center" HorizontalContentAlignment="Center" KeyDown="fuckingPassword_KeyDown" FontSize="20" />
<PasswordBox BorderThickness="0" Background="{StaticResource img_small}" x:Name="passwordBaseRepeat" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center" HorizontalContentAlignment="Center" KeyDown="fuckingPassword_KeyDown" FontSize="20" />
</SwapChainBackgroundPanel>
</Page>
上面的代码中,我定义了若干种输入框,在游戏中用在了不同的地方。这个需要看实际需要来决定到底要多少。一般情况下这些输入框需要在初始化的时候被隐藏,在需要的时候再让它显示。
3. 下面需要解决的问题就是如何在游戏内控制“外壳”中的这些输入框。这里涉及到的最主要的核心技术就是Unity提供的WinRTBridge/AppCallbacks两个类(细节请移步http://docs.unity3d.com/Manual/windowsstore-appcallbacks.html)。这两个类为我们提供了从游戏内调用游戏外以及从游戏外调用游戏内的机制。值得一提的是,两者之间的调用大多是异步的,也就是说,想建立游戏外到游戏内的关系,还是要费一些周折。首先我们来看从游戏内到游戏外的调用。
4. 首先是游戏内,需要建立一个事件对象来监听游戏内对游戏外的请求。比如我们需要将某个输入框显示出来:
// ### SHOW ###
public delegate void OnShowTextBoxEvent(
double centerX, double centerY, string initText, Main unityMain,
double screenWidth, double screenHeight, int maxLength, byte kindOfBox);
public OnShowTextBoxEvent onShowTextBoxEvent = null;
这样我们就定义了一个打开文本框的事件,很简单。
5. 然后我们需要在外面接收这个事件。先在外壳中的MainPage.xaml.cs中找个地方写下文本框显示的基础代码:
public void ShowTextBox(double centerX, double centerY, string initText,
double unityScreenWidth, double unityScreenHeight, int maxLength, byte kindOfBox)
{
// 此处为了节省篇幅,不写了,XAML相关的显示而已,很简单。
}
接下来,找个地方写下对事件的处理。这里要和游戏内定义好的事件参数对上:
private double _centerX, _centerY;
private double _unityScreenWidth, _unityScreenHeight;
string _initText;
private int _maxLength;
private byte _kindOfBox;
public void OnShowTextBoxEvent(double centerX, double centerY, string initText, Main unityMain,
double unityScreenWidth, double unityScreenHeight, int maxLength, byte kindOfBox)
{
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
<span style="white-space:pre"> </span>CoreDispatcherPriority.High,
<span style="white-space:pre"> </span>ShowTextBox);
this._centerX = centerX;
this._centerY = centerY;
this._initText = initText;
this._unityMain = unityMain;
this._unityScreenWidth = unityScreenWidth;
this._unityScreenHeight = unityScreenHeight;
this._maxLength = maxLength;
this._kindOfBox = kindOfBox;
}
private async void ShowTextBox()
{
// 此处略过一些没有必要占用篇幅的代码
if (AppCallbacks.Instance.IsInitialized())
{
AppCallbacks.Instance.InvokeOnAppThread(new AppCallbackItem(() =>
{
if (_unityMain != null)
{
_unityMain.ShowTextBoxCallback(__isSuccess);
}
}
), false);
}
#endif
}
代码比较乱,我就说两点。第一是CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync这个函数发出了一个异步执行ShowTextBox函数的请求。但是被执行的函数不能带参数,所以我定义了一些全局变量来暂存这些参数。第二是上面这段代码同时带了一个游戏外向游戏内的回调,这是我下一步要说的,现在先不说,一会儿说。
现在接收游戏内事件的代码已经写好了,然后我们需要把这个事件接收逻辑“挂”上。打开App.xaml.cs,在OnLaunched函数里面的末尾添加如下代码:
AppCallbacks.Instance.InvokeOnAppThread(new AppCallbackItem(() =>
{
UnityEngine.GameObject.Find("GameWorld").GetComponent<Main>().onShowTextBoxEvent
= new Main.OnShowTextBoxEvent(xamlManager.OnShowTextBoxEvent);
}), false);
这段代码的功用就是将游戏内的事件和游戏外的事件接收器“连接”起来。如果你要调用游戏外的输入框,只需要发出事件就可以了:
public void TryToShowTextBox(
double centerX, double centerY, string initText, int maxLength, byte kindOfBox)
{
if (onShowTextBoxEvent != null)
{
onShowTextBoxEvent(centerX, centerY, initText, this, Screen.width, Screen.height, maxLength, kindOfBox);
}
}
6. 回调。
上面已经一笔带过了一下AppCallbacks,这段代码相对简单。
if (AppCallbacks.Instance.IsInitialized())
{
AppCallbacks.Instance.InvokeOnAppThread(new AppCallbackItem(() =>
{
if (_unityMain != null)
{
_unityMain.ShowTextBoxCallback(__isSuccess);
}
}
), false);
}
这段代码是直接调用游戏内的一个函数,所以我就不解释了。唯一需要注意的是如果你需要通过某个对象来调用一个方法,那么这个对象要预先传递到外面。这里就不解释了。