leihuo-cloud-game-react v0.5.86
leihuo-cloud-game-react
云游戏 react 组件
Install
npm install --save leihuo-cloud-game-react
Usage
单个实例
单实例默认为全屏模式
import React, { Component } as React from 'react'
import CloudGame, { isSupport, getGameDataOf } from 'cloud-game-react'
export default class App extends Component {
constructor(props) {
super(props);
this.onStart = this.onStart.bind(this);
this.onShutdown = this.onShutdown.bind(this);
this.onStop = this.onStop.bind(this);
}
componentDidMount() {
// 平台是否支持云游戏
console.log(isSupport());
console.log(getGameDataOf('NSH'));
}
/**
* 开始游戏回调
* @public
*/
onStart(gameData) {
console.log("onStart", gameData);
}
/**
* 手动退出游戏回调
* @public
*/
onStop(gameData) {
console.log("onStop", gameData);
}
/**
* 调度退出游戏回调
* @public
*/
onShutdown(gameData) {
console.log("onShutdown", gameData);
}
render() {
return (
<CloudGame
logger={true}
scheduleENV="test"
gameId="NSH"
onStart={this.onStart}
onStop={this.onStop}
onShutdown={this.onShutdown}
/>
);
}
}
窗口模式
窗口模式,需要传入容器 dom 对应的 ref
import React, { Component } from "react";
import CloudGame, { isSupport, getGameDataOf } from "leihuo-cloud-game-react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
connected: false,
activeId: 0
};
// 容器ref
this.containerRef = React.createRef();
// 事件回调
this.onStart = this.onStart.bind(this);
this.onStop = this.onStop.bind(this);
this.onShutdown = this.onShutdown.bind(this);
}
componentDidMount() {
// 平台支持情况
isSupport();
// 获取游戏详细信息
getGameDataOf("NSH");
}
/**
* 开始游戏回调
* @public
*/
onStart(gameData) {
// console.log("onStart", gameData);
}
/**
* 手动退出游戏回调
* @public
*/
onStop(gameInstance) {
window.location.reload();
}
/**
* 调度退出游戏回调
* @public
*/
onShutdown(gameInstance) {
window.location.reload();
}
render() {
const gameVersionId = "2c49453b331a806544d0ec1ced6dc08f2703fe85";
return (
<div
className={`game-container ${
this.state.activeId === 0 ? "active" : ""
}`}
ref={this.containerRef}
>
<CloudGame
debug={false}
logger={false}
scheduleENV="dailyBuild"
urs="chenzelun@corp.netease.com~1574918819"
gameId="NSH"
ref={this.containerRef}
gameVersionId={gameVersionId}
withRecord={false}
onStart={this.onStart}
onStop={this.onStop}
onShutdown={this.onShutdown}
/>
</div>
);
}
}
同时打开多个实例
云游戏默认是全屏展示游戏,如果需要自定义游戏窗口大小,需要传入容器的 ref。
:warning: 关于建立 RTCPeerConnection 如果页面需要展示多个游戏实例,需要一个游戏连接完成之后(即 onConnectionStateChange 返回为状态为 connected),再进行下一个游戏的连接 ,请不要同时去连接多个游戏。因为浏览器在同时连接多个 WebRTC 时,会出现连接状态的返回状态错误的问题。可以按照下面的示例代码,进行多个游戏的连接。
:warning: 关于控制流 因为键鼠通常只有一套,所以默认的行为是:控制指令是同时发送给多个实例的,但大多的情况是控制指令只需要发给单个实例。通过动态的修改controllerEnable属性,来控制是否发送控制指令。详情可以查看下面的示例。
多实例实例代码
import React, { Component } from "react";
import CloudGame, { isSupport, getGameDataOf, resetKeys } from "leihuo-cloud-game-react";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
connected: false,
activeId: 0
};
// 容器ref
this.containerRef = React.createRef();
this.containerRef2 = React.createRef();
// 事件回调
this.onStart = this.onStart.bind(this);
this.onStop = this.onStop.bind(this);
this.onShutdown = this.onShutdown.bind(this);
this.onUpdateRtcStatus = this.onUpdateRtcStatus.bind(this);
this.onConnectionStateChange = this.onConnectionStateChange.bind(this);
}
componentDidMount() {
// 平台支持情况
isSupport();
// 获取游戏详细信息
getGameDataOf("NSH");
}
/**
* onDataChannel回调
* @public
*/
onDataChannel(channel) {
// console.log("onDataChannel", channel);
}
/**
* 开始游戏回调
* @public
*/
onStart(gameData) {
// console.log("onStart", gameData);
}
/**
* 手动退出游戏回调
* @public
*/
onStop(gameInstance) {
window.location.reload();
}
/**
* 调度退出游戏回调
* @public
*/
onShutdown(gameInstance) {
window.location.reload();
}
/**
* rtc状态更新回调
* @public
*/
onUpdateRtcStatus(status) {
// console.log(status);
}
/**
* webRTC peer connection 状态变化回调
* 当实例1的rtc连接建立之后,再连接第二个实例
* 也就是onConnectionStateChange返回的状态为connected之后,再进行其他的实例的连接
* 不然会出现一些控制流错误的问题
* @public
*/
onConnectionStateChange(state, data) {
if (state === "connected") {
this.setState({
connected: true
});
}
}
/**
* 修改active状态
* 根据activeId,动态修改controllerEnable属性
* 控制是否给示例控制信号
* @public
*/
onChangeActiveId(id) {
this.setState({ activeId: id });
}
render() {
const gameVersionId = "2c49453b331a806544d0ec1ced6dc08f2703fe85";
return (
<div
style={{
display: "flex",
flexDirection: "column"
}}
>
<div
className={`game-container ${
this.state.activeId === 0 ? "active" : ""
}`}
onMouseEnter={() => {
resetKeys()
this.onChangeActiveId(0)
}}
ref={this.containerRef}
>
<CloudGame
windowNo={0}
debug={false}
logger={false}
scheduleENV="dailyBuild"
urs="chenzelun@corp.netease.com~1574918819"
gameId="NSH"
ref={this.containerRef}
controllerEnable={this.state.activeId === 0}
startWithPointerLock={false}
gameVersionId={gameVersionId}
startWithFullScreen={false}
withRecord={false}
onScheduleChange={this.onScheduleChange}
onStart={this.onStart}
onStop={this.onStop}
onControllerChannel={this.onDataChannel}
onDataStreamChannel={this.onDataChannel}
onShutdown={this.onShutdown}
onUpdateRtcStatus={this.onUpdateRtcStatus}
onConnectionStateChange={this.onConnectionStateChange}
/>
</div>
<div
className={`game-container ${
this.state.activeId === 1 ? "active" : ""
}`}
ref={this.containerRef2}
onMouseEnter={() => {
resetKeys()
this.onChangeActiveId(1)
}}
>
{this.state.connected ? (
<CloudGame
windowNo={1}
debug={false}
logger={false}
scheduleENV="dailyBuild"
gameId="NSH"
ref={this.containerRef2}
controllerEnable={this.state.activeId === 1}
startWithPointerLock={false}
gameVersionId={gameVersionId}
startWithFullScreen={false}
withRecord={false}
onScheduleChange={this.onScheduleChange}
onStart={this.onStart}
onStop={this.onStop}
onControllerChannel={this.onDataChannel}
onDataStreamChannel={this.onDataChannel}
onShutdown={this.onShutdown}
onUpdateRtcStatus={this.onUpdateRtcStatus}
/>
) : null}
</div>
</div>
);
}
}
Props
prop | required | type | default | options | desc |
---|---|---|---|---|---|
ref | option | object | undefined | outside ref | 窗口模式所属外层容器的 ref,外层容器需要定义尺寸和 relative |
windowNo | option | number | undefined | number | 窗口模式需要唯一的窗口序号(1-n) |
logger | option | boolean/string[] | false | true/false/string[] | 日志,支持的配置有:session,schedule,rtc,keyboard, mouse, touch |
debug | option | boolean | false | true/false | 是否打印日志,主要包括控制日志 |
platform | option | object | 自动判断 | object | 平台环境,默认自动判断,也可强制指定 |
direction | option | string | 自动判断 | portrait/landscape | 移动端屏幕方向,默认自适应 |
gameId | required | string | NSH | NSH/L22/D10/L10/L12/ | 游戏 ID |
gameData | option | object | undefined | object | 游戏相关配置,一般使用默认配置,无需手动配置 |
serverIp | option | string | undefined | string | PC 游戏,服务器地址,默认手动选服 |
gameVersionId | option | string | undefined | string | 游戏版本 id,dailyBuild 环境徐配置此项 |
controllerEnable | option | boolean | true | true/false | 是否发送控制指令,比如键鼠操作 |
scheduleENV | option | string | test | test/dev/hw/dailyBuild/nw/ws_string | 调度环境,也可自定义指定 websocket 地址 |
serverENV | option | string | 自动判断 | online/dailyBuild/url_string | 默认根据环境自动判断,也可自定义指定 cdn 路径 |
directIp | option | string | undefined | undefined | 也可不使用调度,与实例地址直连,直连 IP 地址 |
withRecord | option | boolean | false | true/false | 是否开启录像(开发中) |
backPlay | option | boolean | true | true/false | 切到后台,是否仍继续游戏,或者暂停游戏 |
startWithFullScreen | option | boolean | false | true/false | 点击开始游戏是否全屏 |
withStartButton | option | boolean | true | true/false | 是否展示开始游戏按钮,不展示默认为静音状态,需要用户修改muted |
muted | option | boolean | false | true/false | 是否静音 |
holdTime | option | number | undefined | number | 一段时间内,用户没有操作,则自动释放实例,配置此时间 |
minBandWidth | option | number | undefined | number | 最小带宽要求,小于此值显示提示 |
extendArgs | option | string | undefined | jsonstring | 额外参数,传给调度 socket,需要是一个 jsonString |
sessionId | option | string | undefined | string | 调度 sessionId,主要是给自动化测试使用 |
menuType | option | string | undefined | button/default/disable | 菜单类型,button:界面按钮打开 default: ctrl+alt disable: 不使用 |
audioRecordEnable | option | boolean | false | true/false | 是否打开声音录制,传给游戏 |
onSchedule | option | (scheduleWs) => void | undefined | (scheduleWs) => void | 调度 webSocket 建立是回调,可以拿到调度 ws 句柄 |
onScheduleChange | option | (state) => void | undefined | (state) => void | 调度状态更新回调函数 |
onConnectionStateChange | option | (status) => void | undefined | (status) => void | webRTC connectionStateChange 回调 |
buriedPoints | option | string[] | [] | string[] | 埋点配置,埋点事件名称组成的数组 |
onDataChannel | option | (channelObject)=> void | undefined | (channelObject)=> void | dataChannel 回调,获取所有 dataChannel 状态和句柄 |
onControllerChannel | option | (channelObject)=> void | undefined | (channelObject)=> void | controllerChannel 回调,获取 controllerChannel 状态和句柄 |
onDataStreamChannel | option | (channelObject)=> void | undefined | (channelObject)=> void | dataStreamChannel 回调,获取 dataStreamChannel 状态和句柄 |
onBuried | option | (eventObject) => void | undefined | (eventObject) => void | 埋点事件回调,iframe 发送 postMessage |
onStart | option | (gameData) => void | undefined | (gameData) => void | 开始游戏回调 |
onStop | option | (instance) => void | undefined | (instance) => void | 手动关闭游戏回调 |
onShutdown | option | (instance) => void | undefined | (instance) => void | 调度退出游戏回调 |
onMouseDown | option | (point) => void | undefined | (point) => void | mouseDown 回调 |
onMouseMove | option | (point) => void | undefined | (point) => void | mouseMove 回调 |
onMouseUp | option | (point) => void | undefined | (point) => void | mouseUp 回调 |
onTouchStart | option | (point) => void | undefined | (point) => void | touchStart 回调 |
onTouchMove | option | (point) => void | undefined | (point) => void | touchMove 回调 |
onTouchEnd | option | (point) => void | undefined | (point) => void | touchEnd 回调 |
onVolumeChange | option | (number) => void | undefined | (number) => void | 修改音量回调 |
游戏事件埋点
目前只有 L12 支持埋点,具体的埋点事件有:
事件 | 描述 |
---|---|
get_in | 进入游戏选服界面(网页上开始记录倒计时) |
choose_role | 选择角色 |
choose_hair_color | 选择发色 |
enter_name | 输入玩家姓名 |
arrange_suite | 进入家园摆放家具 |
stay_time | 进入游戏之后到最后退出停留总时间 |
playing | 进入关卡,玩家正式开始打(全血) |
kill_one | 玩家杀掉 1 个小兵 |
kill_two | 玩家杀掉 2 个小兵 |
kill_three | 玩家杀掉 3 个小兵 |
kill_four | 玩家杀掉 4 个小兵 |
kill_five | 玩家杀掉 5 个小兵 |
health_70pct | 玩家血量掉至 70% |
health_50pct | 玩家血量掉至 50% |
health_30pct | 玩家血量掉至 30% |
health_10pct | 玩家血量掉至 10% |
kill_hantang | 玩家杀掉韩棠 |
win_los | 玩家胜利/失败 |
Methods
isSupport 是否支持云游戏
getGameDataOf 获取游戏详细信息
获取调度 host 地址 getScheduleHostOf
获取 RTC 信令交换代理地址 getRTCApiOf
多实例切换实例前清空键盘 resetKeys
使用:
import CloudGame, {
isSupport,
getGameDataOf,
getScheduleHostOf,
getRTCApiOf,
resetKeys
} from "cloud-game-react";
// 支持情况
isSupport();
// 获取游戏配置
getGameDataOf("NSH");
// 获取调度地址
getScheduleHostOf("test");
// 获取接口地址
getApiHostOf("test");
// 多实例清空键盘
resetKeys();
调度
接口文档:
https://confluence.leihuo.netease.com/pages/viewpage.action?pageId=24009410
主要流程:
键鼠协议
二报文格式进制格式统一为大端序, 偏移均为字节序 具体协议内容,请查看看键鼠协议
注意: 一些键是被接受端屏蔽的,比如 window 键或者一些系统快捷键。
手柄协议
二报文格式进制格式统一为大端序, 偏移均为字节序 具体协议内容,请查看手柄协议
注意: 传输前,需先发手柄链接信号。
手游触控协议
二报文格式进制格式统一为大端序, 偏移均为字节序 具体协议内容,请查看触控协议
注意: 目前暂最多支持 5 个触控点,触控点对应 id 固定为 0~4,一定注意不要使用系统返回的 touch id。
埋点流程
主要流程:
Contributing
仓库地址 https://git-wz.nie.netease.com/hzheyuan/cloudgame-component-of-react
node && npm node >= 8.0 npm >= 5.0
- 根目录 npm install
- 根目录 npm start start
- example 目录 npm install
- 如果使用 npm 执行:npm link leihuo-cloud-game-react 否则忽略此步骤
- example 目录 npm run start
- 跟新版本:npm run release
- changelog 为标准写法
注意: 请按照规程填写 commit!,详情 |
---|
issues
- 反馈收集的问题
- 游戏启动本地输入法进行输入(pc 与手游不同)
- 默认操作:打开链接,复制按钮,上传文件
- 可配置的快捷键,解决(系统,浏览器)快捷键与游戏快捷键冲突
- ice 打洞
- 手柄非游戏操作(移动网页元素,点击按钮等)
- 配套日志 sdk 和管理插件