from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(327, 303)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 0, 0, 1, 1)
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.gridLayout.addWidget(self.pushButton, 0, 1, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem1, 0, 2, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 327, 23))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.pushButton.clicked.connect(MainWindow.open_dialog)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "多线程弹窗"))
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(369, 128)
self.gridLayout = QtWidgets.QGridLayout(Dialog)
self.gridLayout.setObjectName("gridLayout")
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
self.progressBar = QtWidgets.QProgressBar(Dialog)
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 0, 0, 1, 1)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
class DialogWindow(QDialog, Ui_Dialog):
stop_thread = pyqtSignal() # 定义关闭子线程的信号
def __init__(self, parent=None):
super(DialogWindow, self).__init__(parent)
self.setupUi(self)
def update_progressbar(self, p_int):
self.progressBar.setValue(p_int)
def closeEvent(self, event):
self.stop_thread.emit()
pass
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.count = 0
def open_dialog(self):
dialog = DialogWindow(self)
dialog.show()
self.thread = RunThread(self.count)
self.count += 1
self.thread.update_pb.connect(dialog.update_progressbar)
dialog.stop_thread.connect(self.thread.terminate)
self.thread.start()
class RunThread(QThread):
update_pb = pyqtSignal(int)
def __init__(self, count):
super().__init__()
self.count = count
def run(self):
for i in range(1, 101):
print('thread_%s' % self.count, i, QThread().currentThreadId())
self.update_pb.emit(i)
time.sleep(1)
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
跨平台的服务与解决方案
拥抱开源
领先的机器学习与数据分析
和 Google 资源体系全面整合
Google 正在和游戏行业内的诸多开发商和平台伙伴们一起,为游戏开发者们打造开放与便利的工具与服务。
更快捷地打造多人游戏体验
多人游戏体验中的一个核心问题,就是玩家匹配 (matchmaking),而这个简单词汇中包含的技术难题却多到难以计数。而且不同类型的游戏,其玩家匹配原则甚至会完全不同。Google 和 Unity 联手打造的开源多人游戏框架 Open Match 让开发者们得以轻松创建多人游戏体验。从设计之初,这个框架就旨在满足开放性 (基于 Kubernetes)、灵活性 (匹配原则可基于游戏高度定制) 以及可扩展性。
另一个多人游戏体验的核心问题,就是快速建立服务器以及根据玩家规模灵活扩容。Agones 基于开源的应用程序容器编排系统 Kubernetes,从而让开发者们得以快速打造自己游戏的专属服务器,并且根据需要完成快速扩容。这个开源项目由 Google Cloud 和育碧以及其他游戏工作室联手打造,丰富的第一手开发经验确保了 Agones 得以覆盖相当多样化的多人游戏开发场景。
△ 由 multiplay 带来的案例: Apex Legends 的大热远远超过开发者的预期,但 Google Cloud 带来的高扩展性让扩容变得轻松而可靠
"在 Apex Legends 发布的时候,一个很关键的问题就是我们需要能迅速扩充服务器从而容纳高速涌入的玩家,而且我们需要那些服务器是高度可靠的。"—— Jon Shiring, Lead Programmer, Respawn
玩家数据挖掘
考虑到游戏的规模和玩家数量的快速升级,如何高效地解读和分析玩家数据已经成为一个开发者早晚要面对的问题。Google Cloud 的数据中心以及其上的各种服务 (包括 BigQuery、Cloud AI 等) 正好可以用来帮助游戏开发者们存储、检索和分析玩家数据,更可以便利地基于海量数据打造属于自己游戏的机器学习模型,从而更好地解读与预测玩家行动 (如优化广告物料与投放渠道,以及预测玩家的 Lifetime Value 等)。
在玩家体验中引入人工智能
在游戏中使用 Google Cloud 的场合绝不会仅限于营销层面,比如 DeNA 就在自己大热的黑白棋游戏 Gyakuten Othellonia (逆转黑白棋) 中引入 Google Cloud ML/AI 为玩家们带来更畅快的对战体验,包括在对局前智能化地推荐该局的对战角色阵容,以及为初学者玩家提供智能化的对局练习。这一切得以成立,也仰赖于使用 BigQuery 等服务来快速存储和检索数以 10 亿局的对战记录数据。
△ 逆转黑白棋在传统的黑白棋上引入了丰富的角色数值系统,让盘面的复杂度大幅上升,这也正好是机器学习大显身手的舞台
通过精细追踪与迭代提升游戏体验
想在游戏业务增长和体验打磨上发力的话,Firebase 则为开发者们提供了强大的助力。包括更智能的玩家细分以及使用 Firebase Predictions 和 Firebase 的机器学习细分工具来改善游戏运行的稳定性以及数据表现。
篇幅所限,本文带您回顾了这次 GDC 上 Google 发布内容的重点信息。我们希望这次 GDC 上的内容能让中国的开发者们描绘出属于自己作品的成功蓝图。如果您在游戏开发与发行上有任何疑问或者建议,欢迎在评论区和我们分享。
---------------------
<receiver android:name="xxx.push.HWPushReceiver"
android:permission="xxx.permission.PROCESS_PUSH_MSG">
<intent-filter>
<!-- 必须,用于接收token -->
<action android:name="com.huawei.android.push.intent.REGISTRATION"/>
<!-- 必须, 用于接收透传消息 -->
<action android:name="com.huawei.android.push.intent.RECEIVE"/>
<!-- 必须, 用于接收通知栏消息点击事件 此事件不需要开发者处理,只需注册就可以-->
<action android:name="com.huawei.intent.action.PUSH_DELAY_NOTIFY"/>
</intent-filter>
</receiver>
<receiver android:name="xxx.push.HWNotifyReceiver">
<intent-filter>
<!-- 用于点击通知栏或通知栏上的按钮后触发onEvent回调 -->
<action android:name="com.huawei.android.push.intent.CLICK"/>
<!-- 查看push通道是否连接, 不查看则不需要 -->
<action android:name="com.huawei.intent.action.PUSH_STATE"/>
</intent-filter>
</receiver>//WXSDKEngine的init方法已经被弃用,weex的初始化需要使用下面这个initialize方法
public static void initialize(Application application,InitConfig config){
synchronized (mLock) {
//如果已经初始化过那么直接return
if (mIsInit) {
return;
}
long start = System.currentTimeMillis();
WXEnvironment.sSDKInitStart = start;
if(WXEnvironment.isApkDebugable()){
WXEnvironment.sLogLevel = LogLevel.DEBUG;
}else{
if(WXEnvironment.sApplication != null){
WXEnvironment.sLogLevel = LogLevel.WARN;
}else {
WXLogUtils.e(TAG,"WXEnvironment.sApplication is " + WXEnvironment.sApplication);
}
}
doInitInternal(application,config);
WXEnvironment.sSDKInitInvokeTime = System.currentTimeMillis()-start;
WXLogUtils.renderPerformanceLog("SDKInitInvokeTime", WXEnvironment.sSDKInitInvokeTime);
//监测weex表现的的一个检测器的初始化
WXPerformance.init();
mIsInit = true;
}
}private static void doInitInternal(final Application application,final InitConfig config){
//这里给WXEnvironment中的application赋值,方便后续使用
WXEnvironment.sApplication = application;
if(application == null){//application为空的话报错
WXLogUtils.e(TAG, " doInitInternal application is null");
WXExceptionUtils.commitCriticalExceptionRT(null,
WXErrorCode.WX_KEY_EXCEPTION_SDK_INIT,
"doInitInternal",
WXErrorCode.WX_KEY_EXCEPTION_SDK_INIT.getErrorMsg() + "WXEnvironment sApplication is null",
null);
}
//weex环境初始化标识设置为false
WXEnvironment.JsFrameworkInit = false;
//通过单例模式获取一个WXBridgeManager的唯一实例,使用WXBridgeManager开启一个安全的线程初始化weex框架。
//开启安全线程的细节暂时不深究,这里主要看weex框架的初始化流程
WXBridgeManager.getInstance().post(new Runnable() {
@Override
public void run() {
//开始初始化时间
long start = System.currentTimeMillis();
//获取WXSDKManager实例
WXSDKManager sm = WXSDKManager.getInstance();
//使用sm监听weex框架初始化,我们可以通过WXSDKManager.registerStatisticsListener()
//注册一个监听器
sm.onSDKEngineInitialize();
//设置weex框架配置项
if(config != null ) {
sm.setInitConfig(config);
}
//初始化so文件加载器,后两个参数分别为初始化时的加载器和监听器。
WXSoInstallMgrSdk.init(application,
sm.getIWXSoLoaderAdapter(),
sm.getWXStatisticsListener());
//加载so库文件,如不设置自定义的IWXSoLoaderAdapter,那么采用系统默认方式加载
boolean isSoInitSuccess = WXSoInstallMgrSdk.initSo(V8_SO_NAME, 1, config!=null?config.getUtAdapter():null);
if (!isSoInitSuccess) {//加载失败报错
WXExceptionUtils.commitCriticalExceptionRT(null,
WXErrorCode.WX_KEY_EXCEPTION_SDK_INIT,
"doInitInternal",
WXErrorCode.WX_KEY_EXCEPTION_SDK_INIT.getErrorMsg() + "isSoInit false",
null);
return;
}
//使用WXSDKManager初始化jsFramwork,就是讲weexSdk中assets下的js文件拷贝到缓存目录中,使用so的native方法执行
sm.initScriptsFramework(config!=null?config.getFramework():null);
//计算初始化所用时间
WXEnvironment.sSDKInitExecuteTime = System.currentTimeMillis() - start;
WXLogUtils.renderPerformanceLog("SDKInitExecuteTime", WXEnvironment.sSDKInitExecuteTime);
}
});
//注册weexSDK内置的一系列Component和Module
register();
}
public static synchronized boolean registerComponent(final String type, final IFComponentHolder holder, final Map<String, Object> componentInfo) throws WXException {
if (holder == null || TextUtils.isEmpty(type)) {
return false;
}
//execute task in js thread to make sure register order is same as the order invoke register method.
//在js线程执行这个注册组件的任务,以确保我们注册组件的顺序与调用注册方法的顺序一致。
WXBridgeManager.getInstance()
.post(new Runnable() {
@Override
public void run() {
try {
Map<String, Object> registerInfo = componentInfo;
if (registerInfo == null){
registerInfo = new HashMap<>();
}
//设置注册信息
registerInfo.put("type",type);
//holder是之前让大家留意的SimpleComponentHolder类
registerInfo.put("methods",holder.getMethods());
//生成原生组件映射
registerNativeComponent(type, holder);
//将组件注册信息传递到jsFramwork,生成js中解析vue中原生component是所需的Json映射
registerJSComponent(registerInfo);
//sComponentInfos是js解析需要使用的类,包含所有组件的注册信息
sComponentInfos.add(registerInfo);
} catch (WXException e) {
WXLogUtils.e("register component error:", e);
}
}
});
return true;
}