github地址:GitHub - Unity-Technologies/UnityRenderStreaming: Streaming server for Unity
目前我拉取的是 1.2.3这一分支,拉取到本地后,即可开始调试
用vscode打开WEBAPP这个文件夹,在该目录下执行终端命令
npm run build
npm run start
即可开启服务
同时终端窗口还会给出浏览器地址,打开该地址
要用2019版本的Unity报错才少,打开后,Unity告知PointerPhase类报错,换成TouchPhase即可,activetouches换成touches即可,这个时候就可以运行Unity了。运行Unity成功后即可看到Game窗口内容如下
经过代码查看,发现应该是InputSystem.QueueStateEvent这个方法的问题,这个方法调用后,并没有促发Unity的操作功能,所以此时无奈,只能自己些交互操作了。
-----------------------------------------------------------------------
这个时候也对两边的代码进行了改动,首先WEBAPP中的代码,首先把Mouse改成了MouseMove,同时新增了MouseClick的枚举:
const InputEvent = {
Keyboard: 0,
MouseMove: 1,
MouseWheel: 2,
Touch: 3,
ButtonClick: 4,
MouseClick: 5,
};
把SendMouse改成了SendMouseMove和SendMouseClick方法:
function sendMouseMove(e) {
const scale = _videoPlayer.videoScale;
const originX = _videoPlayer.videoOriginX;
const originY = _videoPlayer.videoOriginY;
if (x && y) {
const deltaX = (e.clientX - x) / scale;
const deltaY = (e.clientY - y) / scale;
let data = new DataView(new ArrayBuffer(9));
data.setUint8(0, InputEvent.MouseMove);
data.setFloat32(1, deltaX, true);
data.setFloat32(5, deltaY, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
x = e.clientX;
y = e.clientY;
}
function sendMouseClick(e) {
if (e.type == 'mouseup') {
let data = new DataView(new ArrayBuffer(3));
data.setUint8(0, InputEvent.MouseClick);
data.setInt16(1, 0, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
if (e.type == 'mousedown') {
let data = new DataView(new ArrayBuffer(3));
data.setUint8(0, InputEvent.MouseClick);
data.setInt16(1, 1, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
}
最后再修改调用的代码
playerElement.addEventListener('mousedown', sendMouseClick, false);
playerElement.addEventListener('mouseup', sendMouseClick, false);
playerElement.addEventListener('mousemove', sendMouseMove, false);
playerElement.addEventListener('wheel', sendMouseWheel, false);
此时Web端的调用已经修改完成
这个时候register-event的整个代码如下
const InputEvent = {
Keyboard: 0,
MouseMove: 1,
MouseWheel: 2,
Touch: 3,
ButtonClick: 4,
MouseClick: 5,
};
const KeyboardEventType = {
Up: 0,
Down: 1
}
const PointerPhase = {
None: 0,
Began: 1,
Moved: 2,
Ended: 3,
Canceled: 4,
Stationary: 5
}
const Keymap = {
"Space": 1,
"Enter": 2,
"Tab": 3,
"Backquote": 4,
"Quote": 5,
"Semicolon": 6,
"Comma": 7,
"Period": 8,
"Slash": 9,
"Backslash": 10,
"LeftBracket": 11,
"RightBracket": 12,
"Minus": 13,
"Equals": 14,
"KeyA": 15,
"KeyB": 16,
"KeyC": 17,
"KeyD": 18,
"KeyE": 19,
"KeyF": 20,
"KeyG": 21,
"KeyH": 22,
"KeyI": 23,
"KeyJ": 24,
"KeyK": 25,
"KeyL": 26,
"KeyM": 27,
"KeyN": 28,
"KeyO": 29,
"KeyP": 30,
"KeyQ": 31,
"KeyR": 32,
"KeyS": 33,
"KeyT": 34,
"KeyU": 35,
"KeyV": 36,
"KeyW": 37,
"KeyX": 38,
"KeyY": 39,
"KeyZ": 40,
"Digit1": 41,
"Digit2": 42,
"Digit3": 43,
"Digit4": 44,
"Digit5": 45,
"Digit6": 46,
"Digit7": 47,
"Digit8": 48,
"Digit9": 49,
"Digit0": 50,
"ShiftLeft": 51,
"ShiftRight": 52,
"AltLeft": 53,
"AltRight": 54,
// "AltGr": 54,
"ControlLeft": 55,
"ControlRight": 56,
"MetaLeft": 57,
"MetaRight": 58,
// "LeftWindows": 57,
// "RightWindows": 58,
// "LeftApple": 57,
// "RightApple": 58,
// "LeftCommand": 57,
// "RightCommand": 58,
"ContextMenu": 59,
"Escape": 60,
"ArrowLeft": 61,
"ArrowRight": 62,
"ArrowUp": 63,
"ArrowDown": 64,
"Backspace": 65,
"PageDown": 66,
"PageUp": 67,
"Home": 68,
"End": 69,
"Insert": 70,
"Delete": 71,
"CapsLock": 72,
"NumLock": 73,
"PrintScreen": 74,
"ScrollLock": 75,
"Pause": 76,
"NumpadEnter": 77,
"NumpadDivide": 78,
"NumpadMultiply": 79,
"NumpadPlus": 80,
"NumpadMinus": 81,
"NumpadPeriod": 82,
"NumpadEquals": 83,
"Numpad0": 84,
"Numpad1": 85,
"Numpad2": 86,
"Numpad3": 87,
"Numpad4": 88,
"Numpad5": 89,
"Numpad6": 90,
"Numpad7": 91,
"Numpad8": 92,
"Numpad9": 93,
"F1": 94,
"F2": 95,
"F3": 96,
"F4": 97,
"F5": 98,
"F6": 99,
"F7": 100,
"F8": 101,
"F9": 102,
"F10": 103,
"F11": 104,
"F12": 105,
// "OEM1": 106,
// "OEM2": 107,
// "OEM3": 108,
// "OEM4": 109,
// "OEM5": 110,
// "IMESelected": 111,
};
let isPlayMode = false;
export function registerKeyboardEvents(videoPlayer) {
const _videoPlayer = videoPlayer;
document.addEventListener('keyup', sendKeyUp, false);
document.addEventListener('keydown', sendKeyDown, false);
function sendKeyUp(e) {
sendKey(e, KeyboardEventType.Up);
}
function sendKeyDown(e) {
sendKey(e, KeyboardEventType.Down);
}
function sendKey(e, type) {
const key = Keymap[e.code];
const character = e.key.length === 1 ? e.key.charCodeAt(0) : 0;
console.log("key down " + key + ", repeat = " + e.repeat + ", character = " + character);
_videoPlayer && _videoPlayer.sendMsg(new Uint8Array([InputEvent.Keyboard, type, e.repeat, key, character]).buffer);
}
}
export function registerMouseEvents(videoPlayer, playerElement) {
const _videoPlayer = videoPlayer;
const _playerElement = playerElement;
const _document = document;
// Listen to mouse events
//playerElement.addEventListener('click', sendMouseClick, false);
playerElement.addEventListener('mousedown', sendMouseClick, false);
playerElement.addEventListener('mouseup', sendMouseClick, false);
playerElement.addEventListener('mousemove', sendMouseMove, false);
playerElement.addEventListener('wheel', sendMouseWheel, false);
// ios workaround for not allowing auto-play
// Listen to touch events based on "Touch Events Level1" TR.
//
// Touch event Level1 https://www.w3.org/TR/touch-events/
// Touch event Level2 https://w3c.github.io/touch-events/
//
playerElement.addEventListener('touchend', sendTouchEnd, false);
playerElement.addEventListener('touchstart', sendTouchStart, false);
playerElement.addEventListener('touchcancel', sendTouchCancel, false);
playerElement.addEventListener('touchmove', sendTouchMove, false);
function sendTouch(e, phase) {
const changedTouches = Array.from(e.changedTouches);
const touches = Array.from(e.touches);
const phrases = [];
for (let i = 0; i < changedTouches.length; i++) {
if (touches.find(function (t) {
return t.identifier === changedTouches[i].identifier
}) === undefined) {
touches.push(changedTouches[i]);
}
}
for (let i = 0; i < touches.length; i++) {
touches[i].identifier;
phrases[i] = changedTouches.find(
function (e) {
return e.identifier === touches[i].identifier
}) === undefined ? PointerPhase.Stationary : phase;
}
console.log("touch phase:" + phase + " length:" + changedTouches.length + " pageX" + changedTouches[0].pageX + ", pageX: " + changedTouches[0].pageY + ", force:" + changedTouches[0].force);
let data = new DataView(new ArrayBuffer(2 + 13 * touches.length));
data.setUint8(0, InputEvent.Touch);
data.setUint8(1, touches.length);
let byteOffset = 2;
for (let i = 0; i < touches.length; i++) {
const scale = _videoPlayer.videoScale;
const originX = _videoPlayer.videoOriginX;
const originY = _videoPlayer.videoOriginY;
const x = (touches[i].pageX - originX) / scale;
// According to Unity Coordinate system
// const y = (touches[i].pageX - originY) / scale;
const y = _videoPlayer.videoHeight - (touches[i].pageY - originY) / scale;
data.setInt32(byteOffset, touches[i].identifier, true);
byteOffset += 4;
data.setUint8(byteOffset, phrases[i]);
byteOffset += 1;
data.setInt16(byteOffset, x, true);
byteOffset += 2;
data.setInt16(byteOffset, y, true);
byteOffset += 2;
data.setFloat32(byteOffset, touches[i].force, true);
byteOffset += 4;
}
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
function sendTouchMove(e) {
sendTouch(e, PointerPhase.Moved);
e.preventDefault();
}
function sendTouchStart(e) {
sendTouch(e, PointerPhase.Began);
e.preventDefault();
}
function sendTouchEnd(e) {
sendTouch(e, PointerPhase.Ended);
e.preventDefault();
}
function sendTouchCancel(e) {
sendTouch(e, PointerPhase.Canceled);
e.preventDefault();
}
let x, y;
function sendMouseMove(e) {
const scale = _videoPlayer.videoScale;
const originX = _videoPlayer.videoOriginX;
const originY = _videoPlayer.videoOriginY;
if (x && y) {
const deltaX = (e.clientX - x) / scale;
const deltaY = (e.clientY - y) / scale;
let data = new DataView(new ArrayBuffer(9));
data.setUint8(0, InputEvent.MouseMove);
data.setFloat32(1, deltaX, true);
data.setFloat32(5, deltaY, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
x = e.clientX;
y = e.clientY;
}
function sendMouseClick(e) {
if (e.type == 'mouseup') {
let data = new DataView(new ArrayBuffer(3));
data.setUint8(0, InputEvent.MouseClick);
data.setInt16(1, 0, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
if (e.type == 'mousedown') {
let data = new DataView(new ArrayBuffer(3));
data.setUint8(0, InputEvent.MouseClick);
data.setInt16(1, 1, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
}
function sendMouseWheel(e) {
console.log("mouse wheel with delta " + e.wheelDelta);
let data = new DataView(new ArrayBuffer(9));
data.setUint8(0, InputEvent.MouseWheel);
data.setFloat32(1, e.deltaX, true);
data.setFloat32(5, e.deltaY, true);
_videoPlayer && _videoPlayer.sendMsg(data.buffer);
}
}
export function sendClickEvent(videoPlayer, elementId) {
let data = new DataView(new ArrayBuffer(3));
data.setUint8(0, InputEvent.ButtonClick);
data.setInt16(1, elementId, true);
videoPlayer && videoPlayer.sendMsg(data.buffer);
}
-----------------------------------------------------------------------------
再修改Unity的脚本
首先新建一个EventManger.cs的脚本来管理事件 代码如下
using System;
using System.Collections.Generic;
namespace EventManager
{
public delegate void ActionHandler(Message message);
public class Message
{
public float arg1;
public float arg2;
public string message;
public Message(int arg1, int arg2, string message)
{
this.arg1 = arg1;
this.arg2 = arg2;
this.message = message;
}
public Message(float arg1, float arg2, string message)
{
this.arg1 = arg1;
this.arg2 = arg2;
this.message = message;
}
public Message() { }
}
public class EventManager
{
#region Instance
private static EventManager instance;
public static EventManager GetInstance()
{
if (instance == null)
{
instance = new EventManager();
}
return instance;
}
private EventManager() { }
#endregion
private Dictionary actions = new Dictionary();
public void AddListener(string actionKey, ActionHandler action)
{
ActionHandler handler;
bool exist = actions.TryGetValue(actionKey, out handler);
if (exist)
{
//避免重复添加
Delegate[] delegates = handler.GetInvocationList();
if (Array.IndexOf(delegates, action) == -1)
{
handler += action;
actions[actionKey] = handler;
}
}
else
{
actions.Add(actionKey, action);
}
}
public void RemoveListener(string actionKey, ActionHandler action)
{
ActionHandler handler;
bool exist = actions.TryGetValue(actionKey, out handler);
if (exist)
{
handler -= action;
if (handler == null)
{
actions.Remove(actionKey);
}
else
{
actions[actionKey] = handler;
}
}
}
public bool BroadcastMessage(string actionKey, Message message)
{
ActionHandler handler;
bool exist = actions.TryGetValue(actionKey, out handler);
if (exist)
{
handler(message);
return true;
}
else
{
return false;
}
}
}
}
同步修改传来值类型的枚举值,在RemoteInput.cs里
enum EventType
{
Keyboard = 0,
MouseMove = 1,
MouseWheel = 2,
Touch = 3,
ButtonClick = 4,
MouseClick = 5,
}
把ProcessMouseEvent拆成PorcessMouseMove和ProcessMouseClick两个方法
static void ProcessMouseMoveEvent(float x, float y)
{
//var position = new Vector2Int(x, y);
//var delta = position - Mouse.current.position.ReadValue();
EventManager.EventManager.GetInstance().BroadcastMessage("mousemove", new EventManager.Message(x, y, "mouseclick"));
//InputSystem.QueueStateEvent(RemoteMouse, new MouseState { position = position, delta = delta, buttons = button });
}
static void ProcessMouseClickEvent(int state)
{
EventManager.EventManager.GetInstance().BroadcastMessage("mouseclick", new EventManager.Message(state, 0,"mouseclick"));
}
最后再改调用方法,注意 这里只改了Move方法和Click方法
public static void ProcessInput(byte[] bytes)
{
switch ((EventType)bytes[0])
{
case EventType.Keyboard:
var type = (KeyboardEventType)bytes[1];
var repeat = bytes[2] == 1;
var key = bytes[3];
var character = (char)bytes[4];
ProcessKeyEvent(type, repeat, key, character);
InputSystem.Update();
break;
case EventType.MouseMove:
var deltaX = BitConverter.ToSingle(bytes, 1);
var deltaY = BitConverter.ToSingle(bytes, 5);
//var button = bytes[5];
ProcessMouseMoveEvent(deltaX, deltaY);
InputSystem.Update();
break;
case EventType.MouseWheel:
var scrollX = BitConverter.ToSingle(bytes, 1);
var scrollY = BitConverter.ToSingle(bytes, 5);
Debug.Log(scrollX);
ProcessMouseWheelEvent(scrollX, scrollY);
InputSystem.Update();
break;
case EventType.Touch:
var length = bytes[1];
var index = 2;
var touches = new TouchState[length];
for (int i = 0; i < length; i++)
{
var identifier = BitConverter.ToInt32(bytes, index);
index += 4;
var phase = (TouchPhase)bytes[index];
index += 1;
var pageX = BitConverter.ToInt16(bytes, index);
index += 2;
var pageY = BitConverter.ToInt16(bytes, index);
index += 2;
var force = BitConverter.ToSingle(bytes, index);
index += 4;
touches[i] = new TouchState
{
touchId = identifier,
phase = phase,
position = new Vector2Int(pageX, pageY),
pressure = force
};
}
ProcessTouchMoveEvent(touches);
InputSystem.Update();
if (Touchscreen.current.touches.Count > length)
{
ChangeEndStateUnusedTouches(touches);
InputSystem.Update();
}
break;
case EventType.ButtonClick:
var elementId = BitConverter.ToInt16(bytes, 1);
ProcessButtonClickEvent(elementId);
break;
case EventType.MouseClick:
var mouseState = BitConverter.ToInt16(bytes, 1);
ProcessMouseClickEvent(mouseState);
InputSystem.Update();
break;
}
}
此时整个RemoteInput代码如下
using System;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.LowLevel;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;
namespace Unity.RenderStreaming
{
enum KeyboardEventType
{
KeyUp = 0,
KeyDown = 1,
}
enum EventType
{
Keyboard = 0,
MouseMove = 1,
MouseWheel = 2,
Touch = 3,
ButtonClick = 4,
MouseClick = 5,
}
public static class RemoteInput
{
public static Keyboard Keyboard { get; private set; }
public static Mouse RemoteMouse { get; private set; }
public static Touchscreen Touch { get; private set; }
public static Action ActionButtonClick;
static bool m_isInitialized = false;
static TDevice GetOrAddDevice() where TDevice : InputDevice
{
var device = InputSystem.GetDevice();
if(device != null)
{
return device;
}
return InputSystem.AddDevice();
}
public static void Initialize()
{
Keyboard = GetOrAddDevice();
RemoteMouse = InputSystem.AddDevice();
Touch = GetOrAddDevice();
m_isInitialized = true;
}
//---------------------------------------------------------------------------------------------------------------------
public static void Destroy()
{
InputSystem.RemoveDevice(RemoteMouse);
RemoteMouse = null;
m_isInitialized = false;
}
//---------------------------------------------------------------------------------------------------------------------
public static void ProcessInput(byte[] bytes)
{
switch ((EventType)bytes[0])
{
case EventType.Keyboard:
var type = (KeyboardEventType)bytes[1];
var repeat = bytes[2] == 1;
var key = bytes[3];
var character = (char)bytes[4];
ProcessKeyEvent(type, repeat, key, character);
InputSystem.Update();
break;
case EventType.MouseMove:
var deltaX = BitConverter.ToSingle(bytes, 1);
var deltaY = BitConverter.ToSingle(bytes, 5);
//var button = bytes[5];
ProcessMouseMoveEvent(deltaX, deltaY);
InputSystem.Update();
break;
case EventType.MouseWheel:
var scrollX = BitConverter.ToSingle(bytes, 1);
var scrollY = BitConverter.ToSingle(bytes, 5);
Debug.Log(scrollX);
ProcessMouseWheelEvent(scrollX, scrollY);
InputSystem.Update();
break;
case EventType.Touch:
var length = bytes[1];
var index = 2;
var touches = new TouchState[length];
for (int i = 0; i < length; i++)
{
var identifier = BitConverter.ToInt32(bytes, index);
index += 4;
var phase = (TouchPhase)bytes[index];
index += 1;
var pageX = BitConverter.ToInt16(bytes, index);
index += 2;
var pageY = BitConverter.ToInt16(bytes, index);
index += 2;
var force = BitConverter.ToSingle(bytes, index);
index += 4;
touches[i] = new TouchState
{
touchId = identifier,
phase = phase,
position = new Vector2Int(pageX, pageY),
pressure = force
};
}
ProcessTouchMoveEvent(touches);
InputSystem.Update();
if (Touchscreen.current.touches.Count > length)
{
ChangeEndStateUnusedTouches(touches);
InputSystem.Update();
}
break;
case EventType.ButtonClick:
var elementId = BitConverter.ToInt16(bytes, 1);
ProcessButtonClickEvent(elementId);
break;
case EventType.MouseClick:
var mouseState = BitConverter.ToInt16(bytes, 1);
ProcessMouseClickEvent(mouseState);
InputSystem.Update();
break;
}
}
public static void Reset()
{
if (!m_isInitialized)
return;
InputSystem.QueueStateEvent(RemoteMouse, new MouseState());
InputSystem.QueueStateEvent(Keyboard, new KeyboardState());
InputSystem.QueueStateEvent(Touch, new TouchState());
InputSystem.Update();
}
static void ProcessKeyEvent(KeyboardEventType state, bool repeat, byte keyCode, char character)
{
switch(state)
{
case KeyboardEventType.KeyDown:
if (!repeat)
{
InputSystem.QueueStateEvent(Keyboard, new KeyboardState((Key)keyCode));
}
if(character != 0)
{
InputSystem.QueueTextEvent(Keyboard, character);
}
break;
case KeyboardEventType.KeyUp:
InputSystem.QueueStateEvent(Keyboard, new KeyboardState());
break;
}
}
static void ProcessMouseMoveEvent(float x, float y)
{
//var position = new Vector2Int(x, y);
//var delta = position - Mouse.current.position.ReadValue();
EventManager.EventManager.GetInstance().BroadcastMessage("mousemove", new EventManager.Message(x, y, "mouseclick"));
//InputSystem.QueueStateEvent(RemoteMouse, new MouseState { position = position, delta = delta, buttons = button });
}
static void ProcessMouseClickEvent(int state)
{
EventManager.EventManager.GetInstance().BroadcastMessage("mouseclick", new EventManager.Message(state, 0,"mouseclick"));
}
static void ProcessMouseWheelEvent(float scrollX, float scrollY)
{
InputSystem.QueueStateEvent(RemoteMouse, new MouseState { scroll = new Vector2(scrollX, scrollY) });
}
static void ProcessTouchMoveEvent(TouchState[] touches)
{
for (var i = 0; i < touches.Length; i++)
{
InputSystem.QueueStateEvent(Touch, touches[i]);
}
}
static void ChangeEndStateUnusedTouches(TouchState[] touches)
{
for (var i = 0; i < Touchscreen.current.touches.Count; i++)
{
var touchId = Touchscreen.current.touches[i].touchId.ReadValue();
if (!Array.Exists(touches, v => v.touchId == touchId))
{
InputSystem.QueueStateEvent(Touch, new TouchState
{
touchId = touchId,
phase = TouchPhase.Ended
});
}
}
}
static void ProcessButtonClickEvent(int elementId)
{
if (ActionButtonClick != null)
{
ActionButtonClick(elementId);
}
}
}
}
最后我们再创建一个Player.cs脚本来接收监听事件,同时处理EventManger传来的事件,此处我们只做平移事件,旋转事件需要进一步的修改WEBAPP中的方法。此处Player脚本需要挂在在场景的一个Object下,同时还要把RemoteCamera的SimpleCameraController的对象传给它才能正常使用,代码如下,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityTemplateProjects;
public class Player : MonoBehaviour
{
// Start is called before the first frame update
int mouseState = 0;
public SimpleCameraController cameraController;
void Start()
{
EventManager.EventManager.GetInstance().AddListener("mouseclick", (e) =>
{
mouseState = (int)e.arg1;
});
EventManager.EventManager.GetInstance().AddListener("mousemove", (e) =>
{
if (mouseState == 1)
{
var x = e.arg1;
var y = e.arg2;
Vector3 translation = Vector3.right * x * 0.01f;
translation += Vector3.back * y * 0.01f;
translation *= Mathf.Pow(2.0f, 3.5f);
print(translation);
cameraController.m_TargetCameraState.Translate(translation);
}
});
}
}
目前就只做了平移功能,目的只是为了简单的跑起来看看云渲染的延迟
Unity云渲染Demo