完善音频功能

This commit is contained in:
shenjianxing 2024-12-16 11:28:01 +08:00
parent 10d4c59218
commit a58c8fb921
48 changed files with 2593 additions and 10 deletions

View File

@ -1858,6 +1858,7 @@ GameObject:
- component: {fileID: 5242346012832629294}
- component: {fileID: 7581586130610749331}
- component: {fileID: 8788681420710167634}
- component: {fileID: 2190811092157322984}
m_Layer: 0
m_Name: InputName
m_TagString: Untagged
@ -2034,9 +2035,22 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
MarkType: 0
CustomComponentName:
CustomComponentName: InputName
CustomComment:
mComponentName: TMPro.TMP_InputField
--- !u!114 &2190811092157322984
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4232881240181435649}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: cc33a39070010f94fb1c2dd721c1286d, type: 3}
m_Name:
m_EditorClassIdentifier:
showHtmlElement: 0
--- !u!1 &4549258305985580856
GameObject:
m_ObjectHideFlags: 0
@ -3893,6 +3907,7 @@ GameObject:
- component: {fileID: 1368589810807828378}
- component: {fileID: 639293668922443356}
- component: {fileID: 644957437798113380}
- component: {fileID: 1459098634278024062}
m_Layer: 0
m_Name: InputId
m_TagString: Untagged
@ -4069,9 +4084,22 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
MarkType: 0
CustomComponentName:
CustomComponentName: InputId
CustomComment:
mComponentName: TMPro.TMP_InputField
--- !u!114 &1459098634278024062
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8227476442829037126}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: cc33a39070010f94fb1c2dd721c1286d, type: 3}
m_Name:
m_EditorClassIdentifier:
showHtmlElement: 0
--- !u!1 &8329961033367211886
GameObject:
m_ObjectHideFlags: 0

View File

@ -632,7 +632,7 @@
<Action type="SetScore" name="术前准备器械准备" value="6.5"></Action>
<Action type="Move" value="Main Camera" to="-3.206,3.24,-1.425" time="0"></Action>
<Action type="Rotate" value="Main Camera" to="27.9597,270,2.899792E-06" time="0"></Action>
<Action type="Hint" value="请在右侧物品栏中,点选当前实训所需的器械" time="-1" icon="true" audio="音频.mp3"></Action>
<Action type="Hint" value="请在右侧物品栏中,点选当前实训所需的器械" time="-1" icon="true" audio="Q001.mp3"></Action>
<Action type="UITools" devices="创巾钳,直止血钳,弯止血钳,组织钳,尖剪,钝剪,持针钳,无齿镊,手术刀柄3号,刀片23号,肠钳,肾形盘,器械盒,S拉钩,铁锤,撬骨板,咬骨钳,骨刀,手术刀柄4号,手术刀片16号" answers="创巾钳,直止血钳,弯止血钳,组织钳,尖剪,钝剪,持针钳,无齿镊,手术刀柄3号,刀片23号,肠钳,肾形盘,器械盒,S拉钩"
setActive="true"
rightLabel="提示:器械选择正确。"

View File

@ -72,7 +72,8 @@ namespace QFramework
new AssetResCreator(),
AssetBundleSceneResCreator,
new NetImageResCreator(),
new LocalImageResCreator()
new LocalImageResCreator(),
new LocalAudioResCreator()
};
}
@ -101,4 +102,16 @@ namespace QFramework
return LocalImageRes.Allocate(resSearchKeys.AssetName);
}
}
public class LocalAudioResCreator : IResCreator
{
public bool Match(ResSearchKeys resSearchKeys)
{
return resSearchKeys.AssetName.StartsWith("localaudio:");
}
public IRes Create(ResSearchKeys resSearchKeys)
{
return LocalAudioRes.Allocate(resSearchKeys.AssetName);
}
}
}

View File

@ -0,0 +1,282 @@
namespace QFramework
{
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public static class LocalAudioResUtil
{
public static string ToLocalAudioResName(this string selfFilePath)
{
return string.Format("LocalAudio:{0}", selfFilePath);
}
}
/// <summary>
/// 本地音频加载器
/// </summary>
public class LocalAudioRes : Res
{
private string mFullPath;
private string mHashCode;
private object mRawAsset;
UnityWebRequest request;
public static LocalAudioRes Allocate(string name)
{
var res = SafeObjectPool<LocalAudioRes>.Instance.Allocate();
if (res != null)
{
res.AssetName = name;
res.SetUrl(name.Substring(11));
}
return res;
}
public void SetDownloadProgress(int totalSize, int download)
{
}
public string LocalResPath
{
get { return string.Format("{0}{1}", AssetBundlePathHelper.PersistentDataPath4Photo, mHashCode); }
}
public virtual object RawAsset
{
get { return mRawAsset; }
}
public bool NeedDownload
{
get { return RefCount > 0; }
}
public string Url
{
get { return mFullPath; }
}
public int FileSize
{
get { return 1; }
}
public void SetUrl(string url)
{
if (string.IsNullOrEmpty(url))
{
return;
}
mFullPath = url;
mHashCode = string.Format("Photo_{0}", mFullPath.GetHashCode());
}
public override bool UnloadImage(bool flag)
{
return false;
}
public override bool LoadSync()
{
return false;
}
public override void LoadAsync()
{
if (!CheckLoadAble())
{
return;
}
if (string.IsNullOrEmpty(mAssetName))
{
return;
}
DoLoadWork();
}
private void DoLoadWork()
{
State = ResState.Loading;
OnDownLoadResult(true);
//检测本地文件是否存在
/*
if (!File.Exists(LocalResPath))
{
ResDownloader.S.AddDownloadTask(this);
}
else
{
OnDownLoadResult(true);
}
*/
}
protected override void OnReleaseRes()
{
if (mAsset != null)
{
GameObject.Destroy(mAsset);
mAsset = null;
}
mRawAsset = null;
}
public override void Recycle2Cache()
{
SafeObjectPool<LocalAudioRes>.Instance.Recycle(this);
}
public override void OnRecycled()
{
}
public void DeleteOldResFile()
{
//throw new NotImplementedException();
}
public void OnDownLoadResult(bool result)
{
if (!result)
{
OnResLoadFaild();
return;
}
if (RefCount <= 0)
{
State = ResState.Waiting;
return;
}
ResMgr.Instance.PushIEnumeratorTask(this);
}
//完全的WWW方式,Unity 帮助管理纹理缓存,并且效率貌似更高
// TODO:persistantPath 用 read
public override IEnumerator DoLoadAsync(System.Action finishCallback)
{
AudioType type = AudioType.MPEG;
if (mFullPath.EndsWith("mp3"))
{
type = AudioType.MPEG;
}
else if (mFullPath.EndsWith("ogg"))
{
type = AudioType.OGGVORBIS;
}
else if (mFullPath.EndsWith("wav"))
{
type = AudioType.WAV;
}
using (UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(mFullPath, type))
{
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError(string.Format("Res:{0}, WWW Errors:{1}", mFullPath, request.error));
OnResLoadFaild();
finishCallback();
yield break;
}
else
{
// Convert the downloaded data to an AudioClip
AudioClip clip = DownloadHandlerAudioClip.GetContent(request);
mAsset = clip;
}
if (RefCount <= 0)
{
OnResLoadFaild();
finishCallback();
yield break;
}
}
State = ResState.Ready;
finishCallback();
}
protected override float CalculateProgress()
{
if (request == null)
{
return 0;
}
return request.downloadProgress;
}
/*
public IEnumerator StartIEnumeratorTask2(Action finishCallback)
{
if (refCount <= 0)
{
OnResLoadFaild();
finishCallback();
yield break;
}
WWW www = new WWW("file://" + LocalResPath);
yield return www;
if (www.error != null)
{
Log.E("WWW Error:" + www.error);
OnResLoadFaild();
finishCallback();
yield break;
}
if (!www.isDone)
{
Log.E("NetImageRes WWW Not Done! Url:" + m_Url);
OnResLoadFaild();
finishCallback();
www.Dispose();
www = null;
yield break;
}
if (refCount <= 0)
{
OnResLoadFaild();
finishCallback();
www.Dispose();
www = null;
yield break;
}
TimeDebugger dt = new TimeDebugger("Tex");
dt.Begin("LoadingTask");
Texture2D tex = www.texture;
tex.Compress(true);
dt.End();
dt.Dump(-1);
m_Asset = tex;
www.Dispose();
www = null;
resState = eResState.kReady;
finishCallback();
}
*/
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9964b8282bdb1684b99f6eaefd2f2f1e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -2,6 +2,7 @@ using UnityEngine;
using UnityEngine.UI;
using QFramework;
using DG.Tweening;
using System.Runtime.CompilerServices;
namespace QFramework.Example
{
@ -39,17 +40,14 @@ namespace QFramework.Example
{
string path = Global.audioPath + mData.audio;
loader = ResLoader.Allocate();
loader.Add2Load(path.ToNetImageResName(), (success, res) =>
loader.Add2Load(path.ToLocalAudioResName(), (success, res) =>
{
if (success)
{
AudioKit.PlayVoice(res.As<AudioClip>(), false);
AudioKit.PlayVoice(res.Asset.As<AudioClip>(), false);
}
});
loader.LoadAsync(() =>
{
loader.Recycle2Cache();
});
loader.LoadAsync();
}
}

8
Assets/Third.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a7664b83b06ff8c4d8c951aa9012f9e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84df1f4338a421641ac99067546e8139
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 59d15a398f7a6ef4ca057f6d2471425f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
namespace WebGLSupport
{
public class Postprocessor
{
const string MenuPath = "Assets/WebGLSupport/OverwriteFullscreenButton";
#if UNITY_2021_1_OR_NEWER
static readonly bool supportedPostprocessor = true;
static readonly string defaultFullscreenFunc = "unityInstance.SetFullscreen(1);";
static readonly string fullscreenNode = "unity-container";
#else
static readonly bool supportedPostprocessor = false;
static readonly string defaultFullscreenFunc = "";
static readonly string fullscreenNode = "";
#endif
private static bool IsEnable => PlayerPrefs.GetInt(MenuPath, 1) == 1;
[PostProcessBuild(1)]
public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.WebGL) return;
if (!supportedPostprocessor) return;
if (!IsEnable) return;
var path = Path.Combine(pathToBuiltProject, "index.html");
if (!File.Exists(path)) return;
var html = File.ReadAllText(path);
// check node is exist
if (html.Contains(fullscreenNode))
{
html = html.Replace(defaultFullscreenFunc, $"document.makeFullscreen('{fullscreenNode}');");
File.WriteAllText(path, html);
}
}
[MenuItem(MenuPath)]
public static void OverwriteDefaultFullscreenButton()
{
var flag = !Menu.GetChecked(MenuPath);
Menu.SetChecked(MenuPath, flag);
PlayerPrefs.SetInt(MenuPath, flag ? 1 : 0);
}
[MenuItem(MenuPath, validate = true)]
private static bool OverwriteDefaultFullscreenButtonValidator()
{
Menu.SetChecked(MenuPath, IsEnable);
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a264ac0d7a7e8a746aa37a17a495e24f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 23d83eab8f753b04fb7f1384ac6676a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dd840bb14379149498902237a63bb794
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,64 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace WebGLSupport.Detail
{
public class RebuildChecker
{
IInputField input;
string beforeString;
int beforeCaretPosition;
int beforeSelectionFocusPosition;
int beforeSelectionAnchorPosition;
//Vector2 anchoredPosition;
public RebuildChecker(IInputField input)
{
this.input = input;
}
public bool NeedRebuild(bool debug = false)
{
var res = false;
// any not same
if (beforeString != input.text)
{
if(debug) Debug.Log(string.Format("beforeString : {0} != {1}", beforeString, input.text));
beforeString = input.text;
res = true;
}
if (beforeCaretPosition != input.caretPosition)
{
if (debug) Debug.Log(string.Format("beforeCaretPosition : {0} != {1}", beforeCaretPosition, input.caretPosition));
beforeCaretPosition = input.caretPosition;
res = true;
}
if (beforeSelectionFocusPosition != input.selectionFocusPosition)
{
if (debug) Debug.Log(string.Format("beforeSelectionFocusPosition : {0} != {1}", beforeSelectionFocusPosition, input.selectionFocusPosition));
beforeSelectionFocusPosition = input.selectionFocusPosition;
res = true;
}
if (beforeSelectionAnchorPosition != input.selectionAnchorPosition)
{
if (debug) Debug.Log(string.Format("beforeSelectionAnchorPosition : {0} != {1}", beforeSelectionAnchorPosition, input.selectionAnchorPosition));
beforeSelectionAnchorPosition = input.selectionAnchorPosition;
res = true;
}
//if (anchoredPosition != input.TextComponentRectTransform().anchoredPosition)
//{
// if (debug) Debug.Log(string.Format("anchoredPosition : {0} != {1}", anchoredPosition, input.TextComponentRectTransform().anchoredPosition));
// anchoredPosition = input.TextComponentRectTransform().anchoredPosition;
// res = true;
//}
return res;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cb8b49a6bee2384b888ba951eb2bdbd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
using UnityEngine;
namespace WebGLSupport.Detail
{
public static class Support
{
/// <summary>
/// 画面内の描画範囲を取得する
/// </summary>
/// <param name="uiElement"></param>
/// <returns></returns>
public static Rect GetScreenCoordinates(RectTransform uiElement)
{
var worldCorners = new Vector3[4];
uiElement.GetWorldCorners(worldCorners);
// try to support RenderMode:WorldSpace
var canvas = uiElement.GetComponentInParent<Canvas>();
var useCamera = (canvas.renderMode != RenderMode.ScreenSpaceOverlay);
if (canvas && useCamera)
{
var camera = canvas.worldCamera;
if (!camera) camera = Camera.main;
for (var i = 0; i < worldCorners.Length; i++)
{
worldCorners[i] = camera.WorldToScreenPoint(worldCorners[i]);
}
}
var min = new Vector3(float.MaxValue, float.MaxValue);
var max = new Vector3(float.MinValue, float.MinValue);
for (var i = 0; i < worldCorners.Length; i++)
{
min.x = Mathf.Min(min.x, worldCorners[i].x);
min.y = Mathf.Min(min.y, worldCorners[i].y);
max.x = Mathf.Max(max.x, worldCorners[i].x);
max.y = Mathf.Max(max.y, worldCorners[i].y);
}
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 12cf2979a42548647b4a59fab831a36b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 863f75c59ad9cf44b89143346c17e55b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
namespace WebGLSupport
{
public delegate void KeyboardEventHandler(WebGLInput input, KeyboardEvent keyboardEvent);
public sealed class KeyboardEvent
{
public string Key { get; }
public int Code { get; }
public bool ShiftKey { get; }
public bool CtrlKey { get; }
public bool AltKey { get; }
public KeyboardEvent(string key, int code, bool shiftKey, bool ctrlKey, bool altKey)
{
Key = key;
Code = code;
ShiftKey = shiftKey;
CtrlKey = ctrlKey;
AltKey = altKey;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81551fe61b23b9d41b0a261e062d1ba0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8fcb4aaeec72ef54486eff0172891c0b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Runtime.InteropServices; // for DllImport
using AOT;
using System;
namespace WebGLSupport
{
class WebGLInputMobilePlugin
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern int WebGLInputMobileRegister(Action<int> OnTouchEnd);
[DllImport("__Internal")]
public static extern void WebGLInputMobileOnFocusOut(int id, Action<int> OnFocusOut);
#else
/// <summary>
/// ID を割り振り
/// </summary>
/// <returns></returns>
public static int WebGLInputMobileRegister(Action<int> OnTouchEnd) { return 0; }
public static void WebGLInputMobileOnFocusOut(int id, Action<int> OnFocusOut) { }
#endif
}
public class WebGLInputMobile : MonoBehaviour, IPointerDownHandler
{
static Dictionary<int, WebGLInputMobile> instances = new Dictionary<int, WebGLInputMobile>();
int id = -1;
private void Awake()
{
#if !(UNITY_WEBGL && !UNITY_EDITOR)
// WebGL 以外、更新メソッドは動作しないようにします
enabled = false;
#endif
}
/// <summary>
/// 押されたら、touchend イベントを登録する
/// </summary>
/// <param name="eventData"></param>
public void OnPointerDown(PointerEventData eventData)
{
if (id != -1) return;
id = WebGLInputMobilePlugin.WebGLInputMobileRegister(OnTouchEnd);
instances[id] = this;
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void OnTouchEnd(int id)
{
var @this = instances[id];
@this.GetComponent<WebGLInput>().OnSelect();
@this.StartCoroutine(RegisterOnFocusOut(id));
}
static IEnumerator RegisterOnFocusOut(int id)
{
yield return null; // wait one frame.
WebGLInputMobilePlugin.WebGLInputMobileOnFocusOut(id, OnFocusOut);
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void OnFocusOut(int id)
{
var @this = instances[id];
@this.StartCoroutine(ExecFocusOut(id));
}
static IEnumerator ExecFocusOut(int id)
{
yield return null; // wait one frame.
var @this = instances[id];
@this.GetComponent<WebGLInput>().DeactivateInputField();
// release
@this.id = -1;
instances.Remove(id);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56393f797a0f7e94e95547f5052052a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
var WebGLInputMobile = {
$instances: [],
WebGLInputMobileRegister: function (touchend) {
var id = instances.push(null) - 1;
document.body.addEventListener("touchend", function () {
document.body.removeEventListener("touchend", arguments.callee);
Runtime.dynCall("vi", touchend, [id]);
});
return id;
},
WebGLInputMobileOnFocusOut: function (id, focusout) {
document.body.addEventListener("focusout", function () {
document.body.removeEventListener("focusout", arguments.callee);
Runtime.dynCall("vi", focusout, [id]);
});
},
}
autoAddDeps(WebGLInputMobile, '$instances');
mergeInto(LibraryManager.library, WebGLInputMobile);

View File

@ -0,0 +1,34 @@
fileFormatVersion: 2
guid: 4df3633103619754fb77d82a1d683868
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Facebook: WebGL
second:
enabled: 1
settings: {}
- first:
WebGL: WebGL
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1b7dfc1c4073fb3428679dbe79406c59
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,67 @@
using UnityEngine;
using UnityEngine.UIElements;
namespace WebGLSupport
{
public class WebGLInputManipulator : Manipulator
{
private GameObject go;
private bool showHtmlElement;
public WebGLInputManipulator(bool showHtmlElement = false)
{
this.showHtmlElement = showHtmlElement;
}
protected override void RegisterCallbacksOnTarget()
{
// uitoolkit is already support mobile.
if (!Application.isMobilePlatform)
{
var textInput = target.Q("unity-text-input");
textInput.RegisterCallback<FocusInEvent>(OnFocusInEvent);
textInput.RegisterCallback<FocusOutEvent>(OnFocusOutEvent);
}
}
protected override void UnregisterCallbacksFromTarget()
{
// uitoolkit is already support mobile.
if (!Application.isMobilePlatform)
{
var textInput = target.Q("unity-text-input");
textInput.UnregisterCallback<FocusInEvent>(OnFocusInEvent);
textInput.UnregisterCallback<FocusOutEvent>(OnFocusOutEvent);
}
}
private void OnFocusInEvent(FocusInEvent evt)
{
if (go != null)
{
GameObject.Destroy(go);
go = null;
}
go = new GameObject("WebGLInputManipulator");
// add WebGLUIToolkitMonoBehaviour for hold TextField!
var uitoolkit = go.AddComponent<WebGLUIToolkitTextField>();
uitoolkit.TextField = target as TextField;
// add WebGLInput to handle the event!
var webglInput = go.AddComponent<WebGLInput>();
webglInput.showHtmlElement = showHtmlElement;
// select it!!
webglInput.OnSelect();
}
private void OnFocusOutEvent(FocusOutEvent evt)
{
if (go != null)
{
GameObject.Destroy(go);
go = null;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0235a2af8956c404c88f16d375fad74b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.Windows;
namespace WebGLSupport
{
/// <summary>
/// UIToolkit TextField
/// </summary>
public class WebGLUIToolkitTextField : MonoBehaviour
{
public TextField TextField { get; set; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78226379c6d667741815566e6261c706
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,480 @@
#if UNITY_2018_2_OR_NEWER
#define TMP_WEBGL_SUPPORT
#endif
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using AOT;
using System.Runtime.InteropServices; // for DllImport
using System.Collections;
using UnityEngine.EventSystems;
namespace WebGLSupport
{
internal class WebGLInputPlugin
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern void WebGLInputInit();
[DllImport("__Internal")]
public static extern int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, string placeholder, bool isMultiLine, bool isPassword, bool isHidden, bool isMobile);
[DllImport("__Internal")]
public static extern void WebGLInputEnterSubmit(int id, bool flag);
[DllImport("__Internal")]
public static extern void WebGLInputTab(int id, Action<int, int> cb);
[DllImport("__Internal")]
public static extern void WebGLInputFocus(int id);
[DllImport("__Internal")]
public static extern void WebGLInputOnFocus(int id, Action<int> cb);
[DllImport("__Internal")]
public static extern void WebGLInputOnBlur(int id, Action<int> cb);
[DllImport("__Internal")]
public static extern void WebGLInputOnValueChange(int id, Action<int, string> cb);
[DllImport("__Internal")]
public static extern void WebGLInputOnEditEnd(int id, Action<int, string> cb);
[DllImport("__Internal")]
public static extern void WebGLInputOnKeyboardEvent(int id, Action<int, int, string, int, int, int, int> cb);
[DllImport("__Internal")]
public static extern int WebGLInputSelectionStart(int id);
[DllImport("__Internal")]
public static extern int WebGLInputSelectionEnd(int id);
[DllImport("__Internal")]
public static extern int WebGLInputSelectionDirection(int id);
[DllImport("__Internal")]
public static extern void WebGLInputSetSelectionRange(int id, int start, int end);
[DllImport("__Internal")]
public static extern void WebGLInputMaxLength(int id, int maxlength);
[DllImport("__Internal")]
public static extern void WebGLInputText(int id, string text);
[DllImport("__Internal")]
public static extern bool WebGLInputIsFocus(int id);
[DllImport("__Internal")]
public static extern void WebGLInputDelete(int id);
[DllImport("__Internal")]
public static extern void WebGLInputForceBlur(int id);
#if WEBGLINPUT_TAB
[DllImport("__Internal")]
public static extern void WebGLInputEnableTabText(int id, bool enable);
#endif
#else
public static void WebGLInputInit() { }
public static int WebGLInputCreate(string canvasId, int x, int y, int width, int height, int fontsize, string text, string placeholder, bool isMultiLine, bool isPassword, bool isHidden, bool isMobile) { return 0; }
public static void WebGLInputEnterSubmit(int id, bool flag) { }
public static void WebGLInputTab(int id, Action<int, int> cb) { }
public static void WebGLInputFocus(int id) { }
public static void WebGLInputOnFocus(int id, Action<int> cb) { }
public static void WebGLInputOnBlur(int id, Action<int> cb) { }
public static void WebGLInputOnValueChange(int id, Action<int, string> cb) { }
public static void WebGLInputOnEditEnd(int id, Action<int, string> cb) { }
public static void WebGLInputOnKeyboardEvent(int id, Action<int, int, string, int, int, int, int> cb) { }
public static int WebGLInputSelectionStart(int id) { return 0; }
public static int WebGLInputSelectionEnd(int id) { return 0; }
public static int WebGLInputSelectionDirection(int id) { return 0; }
public static void WebGLInputSetSelectionRange(int id, int start, int end) { }
public static void WebGLInputMaxLength(int id, int maxlength) { }
public static void WebGLInputText(int id, string text) { }
public static bool WebGLInputIsFocus(int id) { return false; }
public static void WebGLInputDelete(int id) { }
public static void WebGLInputForceBlur(int id) { }
#if WEBGLINPUT_TAB
public static void WebGLInputEnableTabText(int id, bool enable) { }
#endif
#endif
}
public class WebGLInput : MonoBehaviour, IComparable<WebGLInput>
{
public static event KeyboardEventHandler OnKeyboardDown;
public static event KeyboardEventHandler OnKeyboardUp;
static Dictionary<int, WebGLInput> instances = new Dictionary<int, WebGLInput>();
public static string CanvasId { get; set; }
#if WEBGLINPUT_TAB
public bool enableTabText = false;
#endif
static WebGLInput()
{
CanvasId = WebGLWindow.GetCanvasName();
WebGLInputPlugin.WebGLInputInit();
}
public int Id { get { return id; } }
internal int id = -1;
public IInputField input { get; private set; }
bool blurBlock = false;
[TooltipAttribute("show input element on canvas. this will make you select text by drag.")]
public bool showHtmlElement = false;
private IInputField Setup()
{
if (GetComponent<InputField>()) return new WrappedInputField(GetComponent<InputField>());
if (GetComponent<WebGLUIToolkitTextField>()) return new WrappedUIToolkit(GetComponent<WebGLUIToolkitTextField>());
#if TMP_WEBGL_SUPPORT
if (GetComponent<TMPro.TMP_InputField>()) return new WrappedTMPInputField(GetComponent<TMPro.TMP_InputField>());
#endif // TMP_WEBGL_SUPPORT
throw new Exception("Can not Setup WebGLInput!!");
}
private void Awake()
{
input = Setup();
#if !(UNITY_WEBGL && !UNITY_EDITOR)
// WebGL 以外、更新メソッドは動作しないようにします
enabled = false;
#endif
// for mobile platform
if (Application.isMobilePlatform)
{
if (input.EnableMobileSupport)
{
gameObject.AddComponent<WebGLInputMobile>();
}
else
{
// when disable mobile input. disable self!
enabled = false;
}
}
}
/// <summary>
/// Get the element rect of input
/// </summary>
/// <returns></returns>
RectInt GetElemetRect()
{
var rect = input.GetScreenCoordinates();
// モバイルの場合、強制表示する
if (showHtmlElement || Application.isMobilePlatform)
{
var x = (int)(rect.x);
var y = (int)(Screen.height - (rect.y + rect.height));
return new RectInt(x, y, (int)rect.width, (int)rect.height);
}
else
{
var x = (int)(rect.x);
var y = (int)(Screen.height - (rect.y));
return new RectInt(x, y, (int)rect.width, (int)1);
}
}
/// <summary>
/// 対象が選択されたとき
/// </summary>
/// <param name="eventData"></param>
public void OnSelect()
{
if (id != -1) throw new Exception("OnSelect : id != -1");
var rect = GetElemetRect();
bool isPassword = input.contentType == ContentType.Password;
var fontSize = Mathf.Max(14, input.fontSize); // limit font size : 14 !!
// モバイルの場合、強制表示する
var isHidden = !(showHtmlElement || Application.isMobilePlatform);
id = WebGLInputPlugin.WebGLInputCreate(WebGLInput.CanvasId, rect.x, rect.y, rect.width, rect.height, fontSize, input.text, input.placeholder, input.lineType != LineType.SingleLine, isPassword, isHidden, Application.isMobilePlatform);
instances[id] = this;
WebGLInputPlugin.WebGLInputEnterSubmit(id, input.lineType != LineType.MultiLineNewline);
WebGLInputPlugin.WebGLInputOnFocus(id, OnFocus);
WebGLInputPlugin.WebGLInputOnBlur(id, OnBlur);
WebGLInputPlugin.WebGLInputOnValueChange(id, OnValueChange);
WebGLInputPlugin.WebGLInputOnEditEnd(id, OnEditEnd);
WebGLInputPlugin.WebGLInputOnKeyboardEvent(id, OnKeyboardEvent);
WebGLInputPlugin.WebGLInputTab(id, OnTab);
// default value : https://www.w3schools.com/tags/att_input_maxlength.asp
WebGLInputPlugin.WebGLInputMaxLength(id, (input.characterLimit > 0) ? input.characterLimit : 524288);
WebGLInputPlugin.WebGLInputFocus(id);
#if WEBGLINPUT_TAB
WebGLInputPlugin.WebGLInputEnableTabText(id, enableTabText);
#endif
if (input.OnFocusSelectAll)
{
WebGLInputPlugin.WebGLInputSetSelectionRange(id, 0, input.text.Length);
}
else
{
WebGLInputPlugin.WebGLInputSetSelectionRange(id, input.caretPosition, input.caretPosition);
}
WebGLWindow.OnBlurEvent += OnWindowBlur;
}
/// <summary>
/// sync text from inputfield
/// </summary>
/// <param name="cursorIndex"></param>
public void SyncText(int? cursorIndex = null)
{
if (!instances.ContainsKey(id)) return;
var instance = instances[id];
WebGLInputPlugin.WebGLInputText(id, instance.input.text);
if (cursorIndex.HasValue)
{
WebGLInputPlugin.WebGLInputSetSelectionRange(id, cursorIndex.Value, cursorIndex.Value);
}
}
private void OnWindowBlur()
{
blurBlock = true;
}
internal void DeactivateInputField()
{
if (!instances.ContainsKey(id)) return;
WebGLInputPlugin.WebGLInputDelete(id);
input.DeactivateInputField();
instances.Remove(id);
id = -1; // reset id to -1;
WebGLWindow.OnBlurEvent -= OnWindowBlur;
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void OnFocus(int id)
{
#if UNITY_WEBGL && !UNITY_EDITOR
Input.ResetInputAxes(); // Inputの状態リセット
UnityEngine.WebGLInput.captureAllKeyboardInput = false;
#endif
}
[MonoPInvokeCallback(typeof(Action<int>))]
static void OnBlur(int id)
{
#if UNITY_WEBGL && !UNITY_EDITOR
UnityEngine.WebGLInput.captureAllKeyboardInput = true;
Input.ResetInputAxes(); // Inputの状態リセット
#endif
instances[id].StartCoroutine(Blur(id));
}
static IEnumerator Blur(int id)
{
yield return null;
if (!instances.ContainsKey(id)) yield break;
var block = instances[id].blurBlock; // get blur block state
instances[id].blurBlock = false; // reset instalce block state
if (block) yield break; // if block. break it!!
instances[id].DeactivateInputField();
}
[MonoPInvokeCallback(typeof(Action<int, string>))]
static void OnValueChange(int id, string value)
{
if (!instances.ContainsKey(id)) return;
var instance = instances[id];
if (!instance.input.ReadOnly)
{
instance.input.text = value;
}
// InputField.ContentType.Name が Name の場合、先頭文字が強制的大文字になるため小文字にして比べる
if (instance.input.contentType == ContentType.Name)
{
if (string.Compare(instance.input.text, value, true) == 0)
{
value = instance.input.text;
}
}
// InputField の ContentType による整形したテキストを HTML の input に再設定します
if (value != instance.input.text)
{
var start = WebGLInputPlugin.WebGLInputSelectionStart(id);
var end = WebGLInputPlugin.WebGLInputSelectionEnd(id);
// take the offset.when char remove from input.
var offset = instance.input.text.Length - value.Length;
WebGLInputPlugin.WebGLInputText(id, instance.input.text);
// reset the input element selection range!!
WebGLInputPlugin.WebGLInputSetSelectionRange(id, start + offset, end + offset);
}
}
[MonoPInvokeCallback(typeof(Action<int, string>))]
static void OnEditEnd(int id, string value)
{
if (!instances[id].input.ReadOnly)
{
instances[id].input.text = value;
}
}
[MonoPInvokeCallback(typeof(Action<int, int>))]
static void OnTab(int id, int value)
{
WebGLInputTabFocus.OnTab(instances[id], value);
}
[MonoPInvokeCallback(typeof(Action<int, int, string, int, int, int, int>))]
static void OnKeyboardEvent(int id, int mode, string key, int code, int shiftKey, int ctrlKey, int altKey)
{
if (!instances.ContainsKey(id)) return;
var instance = instances[id];
// mode : keydown(1) keyup(3)
var cb = mode switch
{
1 => OnKeyboardDown,
2 => OnKeyboardUp,
_ => default
};
if (cb != null)
{
cb(instance, new KeyboardEvent(key, code, shiftKey != 0, ctrlKey != 0, altKey != 0));
}
}
void Update()
{
if (input == null || !input.isFocused)
{
CheckOutFocus();
return;
}
// 未登録の場合、選択する
if (!instances.ContainsKey(id))
{
if (Application.isMobilePlatform)
{
return;
}
else
{
OnSelect();
}
}
else if (!WebGLInputPlugin.WebGLInputIsFocus(id))
{
if (Application.isMobilePlatform)
{
//input.DeactivateInputField();
return;
}
else
{
// focus this id
WebGLInputPlugin.WebGLInputFocus(id);
}
}
var start = WebGLInputPlugin.WebGLInputSelectionStart(id);
var end = WebGLInputPlugin.WebGLInputSelectionEnd(id);
// 選択方向によって設定します
if (WebGLInputPlugin.WebGLInputSelectionDirection(id) == -1)
{
input.selectionFocusPosition = start;
input.selectionAnchorPosition = end;
}
else
{
input.selectionFocusPosition = end;
input.selectionAnchorPosition = start;
}
input.Rebuild();
}
private void OnDestroy()
{
if (!instances.ContainsKey(id)) return;
#if UNITY_WEBGL && !UNITY_EDITOR
UnityEngine.WebGLInput.captureAllKeyboardInput = true;
Input.ResetInputAxes(); // Inputの状態リセット
#endif
DeactivateInputField();
}
private void OnEnable()
{
WebGLInputTabFocus.Add(this);
}
private void OnDisable()
{
WebGLInputTabFocus.Remove(this);
}
public int CompareTo(WebGLInput other)
{
var a = input.GetScreenCoordinates();
var b = other.input.GetScreenCoordinates();
var res = b.y.CompareTo(a.y);
if (res == 0) res = a.x.CompareTo(b.x);
return res;
}
private void CheckOutFocus()
{
if (!Application.isMobilePlatform) return;
if (!instances.ContainsKey(id)) return;
var current = EventSystem.current.currentSelectedGameObject;
if (current != null) return;
WebGLInputPlugin.WebGLInputForceBlur(id); // Input ではないし、キーボードを閉じる
}
/// <summary>
/// to manage tab focus
/// base on scene position
/// </summary>
static class WebGLInputTabFocus
{
static List<WebGLInput> inputs = new List<WebGLInput>();
public static void Add(WebGLInput input)
{
inputs.Add(input);
inputs.Sort();
}
public static void Remove(WebGLInput input)
{
inputs.Remove(input);
}
public static void OnTab(WebGLInput input, int value)
{
if (inputs.Count <= 1) return;
var index = inputs.IndexOf(input);
index += value;
if (index < 0) index = inputs.Count - 1;
else if (index >= inputs.Count) index = 0;
inputs[index].input.ActivateInputField();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc33a39070010f94fb1c2dd721c1286d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,216 @@
var WebGLInput = {
$instances: [],
WebGLInputInit : function() {
// use WebAssembly.Table : makeDynCall
// when enable. dynCall is undefined
if(typeof dynCall === "undefined")
{
// make Runtime.dynCall to undefined
Runtime = { dynCall : undefined }
}
else
{
// Remove the `Runtime` object from "v1.37.27: 12/24/2017"
// if Runtime not defined. create and add functon!!
if(typeof Runtime === "undefined") Runtime = { dynCall : dynCall }
}
},
WebGLInputCreate: function (canvasId, x, y, width, height, fontsize, text, placeholder, isMultiLine, isPassword, isHidden, isMobile) {
var container = document.getElementById(UTF8ToString(canvasId));
var canvas = container.getElementsByTagName('canvas')[0];
// if container is null and have canvas
if (!container && canvas)
{
// set the container to canvas.parentNode
container = canvas.parentNode;
}
if(canvas)
{
var scaleX = container.offsetWidth / canvas.width;
var scaleY = container.offsetHeight / canvas.height;
if(scaleX && scaleY)
{
x *= scaleX;
width *= scaleX;
y *= scaleY;
height *= scaleY;
}
}
var input = document.createElement(isMultiLine?"textarea":"input");
input.style.position = "absolute";
if(isMobile) {
input.style.bottom = 1 + "vh";
input.style.left = 5 + "vw";
input.style.width = 90 + "vw";
input.style.height = (isMultiLine? 18 : 10) + "vh";
input.style.fontSize = 5 + "vh";
input.style.borderWidth = 5 + "px";
input.style.borderColor = "#000000";
} else {
input.style.top = y + "px";
input.style.left = x + "px";
input.style.width = width + "px";
input.style.height = height + "px";
input.style.fontSize = fontsize + "px";
}
input.style.outlineWidth = 1 + 'px';
input.style.opacity = isHidden?0:1;
input.style.resize = 'none'; // for textarea
input.style.padding = '0px 1px';
input.style.cursor = "default";
input.style.touchAction = 'none';
input.spellcheck = false;
input.value = UTF8ToString(text);
input.placeholder = UTF8ToString(placeholder);
input.style.outlineColor = 'black';
if(isPassword){
input.type = 'password';
}
if(isMobile) {
document.body.appendChild(input);
} else {
container.appendChild(input);
}
return instances.push(input) - 1;
},
WebGLInputEnterSubmit: function(id, falg){
var input = instances[id];
// for enter key
input.addEventListener('keydown', function(e) {
if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) {
if(falg)
{
e.preventDefault();
input.blur();
}
}
});
},
WebGLInputTab:function(id, cb) {
var input = instances[id];
// for tab key
input.addEventListener('keydown', function (e) {
if ((e.which && e.which === 9) || (e.keyCode && e.keyCode === 9)) {
e.preventDefault();
// if enable tab text
if(input.enableTabText){
var val = input.value;
var start = input.selectionStart;
var end = input.selectionEnd;
input.value = val.substr(0, start) + '\t' + val.substr(end, val.length);
input.setSelectionRange(start + 1, start + 1);
input.oninput(); // call oninput to exe ValueChange function!!
} else {
(!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, e.shiftKey ? -1 : 1]) : {{{ makeDynCall("vii", "cb") }}}(id, e.shiftKey ? -1 : 1);
}
}
});
},
WebGLInputFocus: function(id){
var input = instances[id];
input.focus();
},
WebGLInputOnFocus: function (id, cb) {
var input = instances[id];
input.onfocus = function () {
(!!Runtime.dynCall) ? Runtime.dynCall("vi", cb, [id]) : {{{ makeDynCall("vi", "cb") }}}(id);
};
},
WebGLInputOnBlur: function (id, cb) {
var input = instances[id];
input.onblur = function () {
(!!Runtime.dynCall) ? Runtime.dynCall("vi", cb, [id]) : {{{ makeDynCall("vi", "cb") }}}(id);
};
},
WebGLInputIsFocus: function (id) {
return instances[id] === document.activeElement;
},
WebGLInputOnValueChange:function(id, cb){
var input = instances[id];
input.oninput = function () {
var returnStr = input.value;
var bufferSize = lengthBytesUTF8(returnStr) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(returnStr, buffer, bufferSize);
(!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, buffer]) : {{{ makeDynCall("vii", "cb") }}}(id, buffer);
};
},
WebGLInputOnEditEnd:function(id, cb){
var input = instances[id];
input.onchange = function () {
var returnStr = input.value;
var bufferSize = lengthBytesUTF8(returnStr) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(returnStr, buffer, bufferSize);
(!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, buffer]) : {{{ makeDynCall("vii", "cb") }}}(id, buffer);
};
},
WebGLInputOnKeyboardEvent:function(id, cb){
var input = instances[id];
var func = function(mode, e) {
if (e instanceof KeyboardEvent){
var bufferSize = lengthBytesUTF8(e.key) + 1;
var key = _malloc(bufferSize);
stringToUTF8(e.key, key, bufferSize);
var code = e.code;
var shift = e.shiftKey ? 1 : 0;
var ctrl = e.ctrlKey ? 1 : 0;
var alt = e.altKey ? 1 : 0;
(!!Runtime.dynCall) ? Runtime.dynCall("viiiiiii", cb, [id, mode, key, code, shift, ctrl, alt]) : {{{ makeDynCall("viiiiiii", "cb") }}}(id, mode, key, code, shift, ctrl, alt);
}
}
input.addEventListener('keydown', function(e) { func(1, e); });
input.addEventListener('keyup', function(e) { func(2, e); });
},
WebGLInputSelectionStart:function(id){
var input = instances[id];
return input.selectionStart;
},
WebGLInputSelectionEnd:function(id){
var input = instances[id];
return input.selectionEnd;
},
WebGLInputSelectionDirection:function(id){
var input = instances[id];
return (input.selectionDirection == "backward")?-1:1;
},
WebGLInputSetSelectionRange:function(id, start, end){
var input = instances[id];
input.setSelectionRange(start, end);
},
WebGLInputMaxLength:function(id, maxlength){
var input = instances[id];
input.maxLength = maxlength;
},
WebGLInputText:function(id, text){
var input = instances[id];
input.value = UTF8ToString(text);
},
WebGLInputDelete:function(id){
var input = instances[id];
input.parentNode.removeChild(input);
instances[id] = null;
},
WebGLInputEnableTabText:function(id, enable) {
var input = instances[id];
input.enableTabText = enable;
},
WebGLInputForceBlur:function(id) {
var input = instances[id];
input.blur();
},
}
autoAddDeps(WebGLInput, '$instances');
mergeInto(LibraryManager.library, WebGLInput);

View File

@ -0,0 +1,34 @@
fileFormatVersion: 2
guid: 7df541bf7b903fb45b24fab892876393
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Facebook: WebGL
second:
enabled: 1
settings: {}
- first:
WebGL: WebGL
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dfaa1fbdc36ec0d45b321b02a2089b55
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
using UnityEngine;
namespace WebGLSupport
{
public enum ContentType
{
Standard = 0,
Autocorrected = 1,
IntegerNumber = 2,
DecimalNumber = 3,
Alphanumeric = 4,
Name = 5,
EmailAddress = 6,
Password = 7,
Pin = 8,
Custom = 9
}
public enum LineType
{
SingleLine = 0,
MultiLineSubmit = 1,
MultiLineNewline = 2
}
public interface IInputField
{
ContentType contentType { get; }
LineType lineType { get; }
int fontSize { get; }
string text { get; set; }
string placeholder { get; }
int characterLimit { get; }
int caretPosition { get; }
bool isFocused { get; }
int selectionFocusPosition { get; set; }
int selectionAnchorPosition { get; set; }
bool ReadOnly { get; }
bool OnFocusSelectAll { get; }
bool EnableMobileSupport { get; }
Rect GetScreenCoordinates();
void ActivateInputField();
void DeactivateInputField();
void Rebuild();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8be52c4f26f76e04fbae3cb86e539bdb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,119 @@
using UnityEngine;
using UnityEngine.UI;
using WebGLSupport.Detail;
namespace WebGLSupport
{
/// <summary>
/// Wrapper for UnityEngine.UI.InputField
/// </summary>
class WrappedInputField : IInputField
{
InputField input;
RebuildChecker checker;
public bool ReadOnly { get { return input.readOnly; } }
public string text
{
get { return input.text; }
set { input.text = value; }
}
public string placeholder
{
get
{
if (!input.placeholder) return "";
var text = input.placeholder.GetComponent<Text>();
return text ? text.text : "";
}
}
public int fontSize
{
get { return input.textComponent.fontSize; }
}
public ContentType contentType
{
get { return (ContentType)input.contentType; }
}
public LineType lineType
{
get { return (LineType)input.lineType; }
}
public int characterLimit
{
get { return input.characterLimit; }
}
public int caretPosition
{
get { return input.caretPosition; }
}
public bool isFocused
{
get { return input.isFocused; }
}
public int selectionFocusPosition
{
get { return input.selectionFocusPosition; }
set { input.selectionFocusPosition = value; }
}
public int selectionAnchorPosition
{
get { return input.selectionAnchorPosition; }
set { input.selectionAnchorPosition = value; }
}
public bool OnFocusSelectAll
{
get { return true; }
}
public bool EnableMobileSupport
{
get
{
// return false to use unity mobile keyboard support
return false;
}
}
public WrappedInputField(InputField input)
{
this.input = input;
checker = new RebuildChecker(this);
}
public void ActivateInputField()
{
input.ActivateInputField();
}
public void DeactivateInputField()
{
input.DeactivateInputField();
}
public void Rebuild()
{
if (checker.NeedRebuild())
{
input.textComponent.SetAllDirty();
input.Rebuild(CanvasUpdate.LatePreRender);
}
}
public Rect GetScreenCoordinates()
{
return Support.GetScreenCoordinates(input.GetComponent<RectTransform>());
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c8464791034e144ca224c37c1816e37
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,212 @@
#if UNITY_2018_2_OR_NEWER
#define TMP_WEBGL_SUPPORT
#endif
#if TMP_WEBGL_SUPPORT
using UnityEngine;
using TMPro;
using WebGLSupport.Detail;
using UnityEngine.UI;
using System;
namespace WebGLSupport
{
/// <summary>
/// Wrapper for TMPro.TMP_InputField
/// </summary>
class WrappedTMPInputField : IInputField
{
TMP_InputField input;
RebuildChecker checker;
Coroutine delayedGraphicRebuild;
public bool ReadOnly { get { return input.readOnly; } }
public string text
{
get { return input.text; }
set { input.text = FixContentTypeByInputField(value); }
}
/// <summary>
/// workaround!!
/// when use TMP_InputField.text = "xxx"; is will set the text directly.
/// so, use InputField for match the ContentType!
/// </summary>
/// <param name="inText"></param>
/// <returns></returns>
private string FixContentTypeByInputField(string inText)
{
var go = new GameObject("FixContentTypeByInputField for WebGLInput");
go.SetActive(false);
var i = go.AddComponent<InputField>();
i.contentType = (InputField.ContentType)Enum.Parse(typeof(InputField.ContentType), input.contentType.ToString());
i.lineType = (InputField.LineType)Enum.Parse(typeof(InputField.LineType), input.lineType.ToString());
i.inputType = (InputField.InputType)Enum.Parse(typeof(InputField.InputType), input.inputType.ToString());
i.keyboardType = input.keyboardType;
i.characterValidation = (InputField.CharacterValidation)Enum.Parse(typeof(InputField.CharacterValidation), input.characterValidation.ToString());
i.characterLimit = input.characterLimit;
i.text = inText;
var res = i.text;
GameObject.Destroy(go);
return res;
}
public string placeholder
{
get
{
if (!input.placeholder) return "";
var text = input.placeholder.GetComponent<TMP_Text>();
return text ? text.text : "";
}
}
public int fontSize
{
get { return (int)input.textComponent.fontSize; }
}
public ContentType contentType
{
get { return (ContentType)input.contentType; }
}
public LineType lineType
{
get { return (LineType)input.lineType; }
}
public int characterLimit
{
get { return input.characterLimit; }
}
public int caretPosition
{
get { return input.caretPosition; }
}
public bool isFocused
{
get { return input.isFocused; }
}
public int selectionFocusPosition
{
get { return input.selectionStringFocusPosition; }
set { input.selectionStringFocusPosition = value; }
}
public int selectionAnchorPosition
{
get { return input.selectionStringAnchorPosition; }
set { input.selectionStringAnchorPosition = value; }
}
public bool OnFocusSelectAll
{
get { return input.onFocusSelectAll; }
}
public bool EnableMobileSupport
{
get
{
// [2023.2] Latest Development on TextMesh Pro
// https://forum.unity.com/threads/2023-2-latest-development-on-textmesh-pro.1434757/
// As of 2023.2, the TextMesh Pro package (com.unity.textmeshpro) has been merged into the uGUI package (com.unity.ugui) and the TextMesh Pro package has been deprecated.
// In this version, TextMeshPro is default support mobile input. so disable WebGLInput mobile support
#if UNITY_2023_2_OR_NEWER
// return false to use unity mobile keyboard support
return false;
#else
return true;
#endif
}
}
public WrappedTMPInputField(TMP_InputField input)
{
this.input = input;
checker = new RebuildChecker(this);
}
public Rect GetScreenCoordinates()
{
// 表示範囲
// MEMO :
// TMP では textComponent を移動させてクリッピングするため、
// 表示範囲外になる場合があるので、自分の範囲を返す
return Support.GetScreenCoordinates(input.GetComponent<RectTransform>());
}
public void ActivateInputField()
{
input.ActivateInputField();
}
public void DeactivateInputField()
{
input.DeactivateInputField();
}
public void Rebuild()
{
#if UNITY_2020_1_OR_NEWER
if (checker.NeedRebuild())
{
input.textComponent.SetVerticesDirty();
input.textComponent.SetLayoutDirty();
input.Rebuild(CanvasUpdate.LatePreRender);
}
#else
if (input.textComponent.enabled && checker.NeedRebuild())
{
//================================
// fix bug for tmp
// TMPの不具合で、正しく座標を設定されてなかったため、試しに対応する
var rt = input.textComponent.GetComponent<RectTransform>();
var size = input.textComponent.GetPreferredValues();
if (size.x < rt.rect.xMax)
{
// textComponent の座標を更新
var pos = rt.anchoredPosition;
pos.x = 0;
rt.anchoredPosition = pos;
// caret の座標更新
var caret = input.GetComponentInChildren<TMP_SelectionCaret>();
var caretRect = caret.GetComponent<RectTransform>();
caretRect.anchoredPosition = rt.anchoredPosition;
}
//==============================
// HACK : 1フレーム無効にする
// MEMO : 他にいい方法Rebuildがあれば対応する
// LayoutRebuilder.ForceRebuildLayoutImmediate(); で試してダメでした
input.textComponent.enabled = rectOverlaps(input.textComponent.rectTransform, input.textViewport);
input.textComponent.SetAllDirty();
input.Rebuild(CanvasUpdate.LatePreRender);
//Debug.Log(input.textComponent.enabled);
}
else
{
input.textComponent.enabled = true;
}
#endif
}
bool rectOverlaps(RectTransform rectTrans1, RectTransform rectTrans2)
{
Rect rect1 = new Rect(rectTrans1.localPosition.x, rectTrans1.localPosition.y, rectTrans1.rect.width, rectTrans1.rect.height);
Rect rect2 = new Rect(rectTrans2.localPosition.x, rectTrans2.localPosition.y, rectTrans2.rect.width, rectTrans2.rect.height);
return rect1.Overlaps(rect2);
}
}
}
#endif // TMP_WEBGL_SUPPORT

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1629c3a135d89a45aca880fa8052f5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,162 @@
using UnityEngine;
using UnityEngine.UIElements;
namespace WebGLSupport
{
/// <summary>
/// Wrapper for UnityEngine.UIElements.TextField
/// </summary>
class WrappedUIToolkit : IInputField
{
TextField input;
public bool ReadOnly { get { return input.isReadOnly; } }
public string text
{
get { return input.value; }
set { input.value = value; }
}
public string placeholder
{
get
{
#if UNITY_2023_1_OR_NEWER
return input.textEdition.placeholder;
#else
return "";
#endif
}
}
public int fontSize
{
/// MEMO : how to get the fontsize?
get { return 20; }
}
public ContentType contentType
{
get
{
if (input.isPasswordField)
{
return ContentType.Password;
}
#if UNITY_2022_1_OR_NEWER
var keyboardType = input.keyboardType;
#else
var keyboardType = TouchScreenKeyboardType.Default;
#endif
return keyboardType switch
{
TouchScreenKeyboardType.Default => ContentType.Standard,
TouchScreenKeyboardType.ASCIICapable => ContentType.Alphanumeric,
TouchScreenKeyboardType.NumbersAndPunctuation => ContentType.Standard,
TouchScreenKeyboardType.URL => ContentType.Standard,
TouchScreenKeyboardType.NumberPad => ContentType.IntegerNumber,
TouchScreenKeyboardType.PhonePad => ContentType.Standard,
TouchScreenKeyboardType.NamePhonePad => ContentType.Standard,
TouchScreenKeyboardType.EmailAddress => ContentType.EmailAddress,
//TouchScreenKeyboardType.NintendoNetworkAccount => throw new System.NotImplementedException(),
TouchScreenKeyboardType.Social => ContentType.Standard,
TouchScreenKeyboardType.Search => ContentType.Standard,
TouchScreenKeyboardType.DecimalPad => ContentType.DecimalNumber,
TouchScreenKeyboardType.OneTimeCode => ContentType.Standard,
_ => ContentType.Standard,
};
}
}
public LineType lineType
{
get
{
return input.multiline ? LineType.MultiLineNewline : LineType.SingleLine;
}
}
public int characterLimit
{
get { return input.maxLength; }
}
public int caretPosition
{
get { return input.cursorIndex; }
}
public bool isFocused
{
get
{
return true;
}
}
public int selectionFocusPosition
{
get { return input.cursorIndex; }
#if UNITY_2022_1_OR_NEWER
set { input.cursorIndex = value; }
#else
set { input.SelectRange(value, input.selectIndex); }
#endif
}
public int selectionAnchorPosition
{
get { return input.selectIndex; }
#if UNITY_2022_1_OR_NEWER
set { input.selectIndex = value; }
#else
set { input.SelectRange(input.cursorIndex, value); }
#endif
}
public bool OnFocusSelectAll
{
#if UNITY_2022_1_OR_NEWER
get { return input.selectAllOnFocus || input.selectAllOnMouseUp; }
#else
get { return true; }
#endif
}
public bool EnableMobileSupport
{
get
{
// return false to use unity mobile keyboard support
return false;
}
}
public WrappedUIToolkit(WebGLUIToolkitTextField input)
{
this.input = input.TextField;
}
public Rect GetScreenCoordinates()
{
var textInput = input.Q("unity-text-input");
var rect = textInput.worldBound;
return new Rect(rect.x, Screen.height - (rect.y + rect.height), rect.width, rect.height);
}
public void ActivateInputField()
{
}
public void DeactivateInputField()
{
input.Blur();
}
public void Rebuild()
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a84ff74590e7172468127b13d7c934da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ef582d6b11c3602438e4a301923e45dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,127 @@
using System;
using AOT;
using System.Runtime.InteropServices; // for DllImport
using UnityEngine;
namespace WebGLSupport
{
static class WebGLWindowPlugin
{
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern void WebGLWindowInit();
[DllImport("__Internal")]
public static extern void WebGLWindowOnFocus(Action cb);
[DllImport("__Internal")]
public static extern void WebGLWindowOnBlur(Action cb);
[DllImport("__Internal")]
public static extern void WebGLWindowOnResize(Action cb);
[DllImport("__Internal")]
public static extern void WebGLWindowInjectFullscreen();
[DllImport("__Internal")]
public static extern string WebGLWindowGetCanvasName();
[DllImport("__Internal")]
public static extern void MakeFullscreen(string str);
[DllImport("__Internal")]
public static extern void ExitFullscreen();
[DllImport("__Internal")]
public static extern bool IsFullscreen();
#else
public static void WebGLWindowInit() { }
public static void WebGLWindowOnFocus(Action cb) { }
public static void WebGLWindowOnBlur(Action cb) { }
public static void WebGLWindowOnResize(Action cb) { }
public static void WebGLWindowInjectFullscreen() { }
public static string WebGLWindowGetCanvasName() { return ""; }
public static void MakeFullscreen(string str) { }
public static void ExitFullscreen() { }
public static bool IsFullscreen() { return false; }
#endif
}
public static class WebGLWindow
{
static WebGLWindow()
{
WebGLWindowPlugin.WebGLWindowInit();
}
public static bool Focus { get; private set; }
public static event Action OnFocusEvent = () => { };
public static event Action OnBlurEvent = () => { };
public static event Action OnResizeEvent = () => { };
static string ViewportContent;
static void Init()
{
Focus = true;
WebGLWindowPlugin.WebGLWindowOnFocus(OnWindowFocus);
WebGLWindowPlugin.WebGLWindowOnBlur(OnWindowBlur);
WebGLWindowPlugin.WebGLWindowOnResize(OnWindowResize);
WebGLWindowPlugin.WebGLWindowInjectFullscreen();
}
[MonoPInvokeCallback(typeof(Action))]
static void OnWindowFocus()
{
Focus = true;
OnFocusEvent();
}
[MonoPInvokeCallback(typeof(Action))]
static void OnWindowBlur()
{
Focus = false;
OnBlurEvent();
}
[MonoPInvokeCallback(typeof(Action))]
static void OnWindowResize()
{
OnResizeEvent();
}
[RuntimeInitializeOnLoadMethod]
static void RuntimeInitializeOnLoadMethod()
{
Init();
}
public static string GetCanvasName()
{
return WebGLWindowPlugin.WebGLWindowGetCanvasName();
}
public static void MakeFullscreen(string fullscreenElementName = null)
{
WebGLWindowPlugin.MakeFullscreen(fullscreenElementName ?? GetCanvasName());
}
public static void ExitFullscreen()
{
WebGLWindowPlugin.ExitFullscreen();
}
public static bool IsFullscreen()
{
return WebGLWindowPlugin.IsFullscreen();
}
public static void SwitchFullscreen()
{
if (IsFullscreen())
{
ExitFullscreen();
}
else
{
MakeFullscreen();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5fcbb4f34ed8e894896251ff74a4633d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,182 @@
var WebGLWindow = {
WebGLWindowInit : function() {
// use WebAssembly.Table : makeDynCall
// when enable. dynCall is undefined
if(typeof dynCall === "undefined")
{
// make Runtime.dynCall to undefined
Runtime = { dynCall : undefined }
}
else
{
// Remove the `Runtime` object from "v1.37.27: 12/24/2017"
// if Runtime not defined. create and add functon!!
if(typeof Runtime === "undefined") Runtime = { dynCall : dynCall }
}
},
WebGLWindowGetCanvasName: function() {
var elements = document.getElementsByTagName('canvas');
var returnStr = "";
if(elements.length >= 1)
{
returnStr = elements[0].parentNode.id;
// workaround : for WebGLTemplate:Minimal temp! body element not have id!
if(returnStr == '')
{
returnStr = elements[0].parentNode.id = 'WebGLWindow:Canvas:ParentNode';
}
}
var bufferSize = lengthBytesUTF8(returnStr) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(returnStr, buffer, bufferSize);
return buffer;
},
WebGLWindowOnFocus: function (cb) {
window.addEventListener('focus', function () {
(!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}();
});
},
WebGLWindowOnBlur: function (cb) {
window.addEventListener('blur', function () {
(!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}();
});
},
WebGLWindowOnResize: function(cb) {
window.addEventListener('resize', function () {
(!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}();
});
},
WebGLWindowInjectFullscreen : function () {
document.makeFullscreen = function (id, keepAspectRatio) {
// get fullscreen object
var getFullScreenObject = function () {
var doc = window.document;
var objFullScreen = doc.fullscreenElement || doc.mozFullScreenElement || doc.webkitFullscreenElement || doc.msFullscreenElement;
return (objFullScreen);
}
// handle fullscreen event
var eventFullScreen = function (callback) {
document.addEventListener("fullscreenchange", callback, false);
document.addEventListener("webkitfullscreenchange", callback, false);
document.addEventListener("mozfullscreenchange", callback, false);
document.addEventListener("MSFullscreenChange", callback, false);
}
var removeEventFullScreen = function (callback) {
document.removeEventListener("fullscreenchange", callback, false);
document.removeEventListener("webkitfullscreenchange", callback, false);
document.removeEventListener("mozfullscreenchange", callback, false);
document.removeEventListener("MSFullscreenChange", callback, false);
}
var div = document.createElement("div");
document.body.appendChild(div);
// save canvas size to originSize
var canvas = document.getElementsByTagName('canvas')[0];
var originSize =
{
width : canvas.style.width,
height : canvas.style.height,
};
var fullscreenRoot = document.getElementById(id);
// when build with minimal default template
// the fullscreenRoot is <body>
var isBody = fullscreenRoot.tagName.toLowerCase() == "body";
if(isBody)
{
// swip the id to div
div.id = fullscreenRoot.id;
fullscreenRoot.id = "";
// overwrite the fullscreen root
fullscreenRoot = canvas;
}
var beforeParent = fullscreenRoot.parentNode;
var beforeStyle = window.getComputedStyle(fullscreenRoot);
var beforeWidth = parseInt(beforeStyle.width);
var beforeHeight = parseInt(beforeStyle.height);
// to keep element index after fullscreen
var index = Array.from(beforeParent.children).findIndex(function (v) { return v == fullscreenRoot; });
div.appendChild(fullscreenRoot);
// recv fullscreen function
var fullscreenFunc = function () {
if (getFullScreenObject()) {
if (keepAspectRatio) {
var ratio = Math.min(window.screen.width / beforeWidth, window.screen.height / beforeHeight);
var width = Math.floor(beforeWidth * ratio);
var height = Math.floor(beforeHeight * ratio);
fullscreenRoot.style.width = width + 'px';
fullscreenRoot.style.height = height + 'px';
} else {
fullscreenRoot.style.width = window.screen.width + 'px';
fullscreenRoot.style.height = window.screen.height + 'px';
}
// make canvas size 100% to fix screen size
canvas.style.width = "100%";
canvas.style.height = "100%";
} else {
fullscreenRoot.style.width = beforeWidth + 'px';
fullscreenRoot.style.height = beforeHeight + 'px';
beforeParent.insertBefore(fullscreenRoot, Array.from(beforeParent.children)[index]);
if(isBody)
{
beforeParent.id = div.id;
}
div.parentNode.removeChild(div);
// set canvas size to origin size
canvas.style.width = originSize.width;
canvas.style.height = originSize.height;
// remove this function
removeEventFullScreen(fullscreenFunc);
}
}
// listener fullscreen event
eventFullScreen(fullscreenFunc);
if (div.mozRequestFullScreen) div.mozRequestFullScreen();
else if (div.webkitRequestFullScreen) div.webkitRequestFullScreen();
else if (div.msRequestFullscreen) div.msRequestFullscreen();
else if (div.requestFullscreen) div.requestFullscreen();
}
},
MakeFullscreen : function (str) {
document.makeFullscreen(UTF8ToString(str));
},
ExitFullscreen : function() {
// get fullscreen object
var doc = window.document;
var objFullScreen = doc.fullscreenElement || doc.mozFullScreenElement || doc.webkitFullscreenElement || doc.msFullscreenElement;
if (objFullScreen)
{
if (document.exitFullscreen) document.exitFullscreen();
else if (document.msExitFullscreen) document.msExitFullscreen();
else if (document.mozCancelFullScreen) document.mozCancelFullScreen();
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
}
},
IsFullscreen : function() {
// get fullscreen object
var doc = window.document;
var objFullScreen = doc.fullscreenElement || doc.mozFullScreenElement || doc.webkitFullscreenElement || doc.msFullscreenElement;
// check full screen elemenet is not null!
return objFullScreen != null;
},
}
mergeInto(LibraryManager.library, WebGLWindow);

View File

@ -0,0 +1,37 @@
fileFormatVersion: 2
guid: 5edef37b75c044e41a013a62fec2e1ff
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Facebook: WebGL
second:
enabled: 1
settings: {}
- first:
WebGL: WebGL
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant: