Cocos Creator开发踩坑记录 用Cocos Creator开发游戏有一段时间了,从UI交互到平台适配,踩了不少坑。记录一下实战经验和解决方案。
UI交互开发 可拖动按钮 游戏里经常需要能拖动的按钮,但又不想触发点击。实现思路:拖动时禁用按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const { ccclass, property } = cc._decorator ;@ccclass export default class DraggableButton extends cc.Component { onLoad ( ) { const button = this .node .getComponent (cc.Button ); if (button) { this .node .on (cc.Node .EventType .TOUCH_MOVE , () => { button.interactable = false ; }); this .node .on (cc.Node .EventType .TOUCH_END , () => { button.interactable = true ; }); this .node .on (cc.Node .EventType .TOUCH_CANCEL , () => { button.interactable = true ; }); } } onDestroy ( ) { this .node .off (cc.Node .EventType .TOUCH_MOVE ); this .node .off (cc.Node .EventType .TOUCH_END ); this .node .off (cc.Node .EventType .TOUCH_CANCEL ); } }
事件
触发时机
处理
TOUCH_MOVE
触摸移动
禁用按钮
TOUCH_END
触摸结束
恢复按钮
TOUCH_CANCEL
触摸取消
恢复按钮
事件冒泡与穿透 1 2 3 4 5 6 7 8 this .node ._touchListener .setSwallowTouches (false );this .node .on (cc.Node .EventType .TOUCH_END , (event ) => { console .log ("Node clicked" ); }, this );
事件阶段:CAPTURE(捕获)→ TARGET(目标)→ BUBBLE(冒泡)
自定义鼠标样式 PC端游戏可以自定义鼠标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const { ccclass, property } = cc._decorator ;@ccclass export default class CustomCursor extends cc.Component { @property (cc.Prefab ) cursorPrefab : cc.Prefab = null ; private cursor : cc.Node = null ; onLoad ( ) { cc.game .canvas .style .cursor = 'none' ; this .cursor = cc.instantiate (this .cursorPrefab ); this .cursor .parent = this .node ; this .node .on (cc.Node .EventType .MOUSE_MOVE , this .onMouseMove , this ); } onMouseMove (event: cc.Event.EventMouse ) { const pos = event.getLocation (); this .cursor .position = cc.v2 ( pos.x - cc.winSize .width / 2 , pos.y - cc.winSize .height / 2 ); } onDestroy ( ) { cc.game .canvas .style .cursor = '' ; this .node .off (cc.Node .EventType .MOUSE_MOVE , this .onMouseMove , this ); } }
要点:
cc.game.canvas.style.cursor = 'none'隐藏默认光标
预制体做鼠标精灵
屏幕坐标转世界坐标
组件销毁时恢复光标
动画系统 stopAllActions的坑 stopAllActions()只停当前节点的Action,子节点的不会停:
1 2 3 4 5 6 7 this .node .stopAllActions ();this .node .children .forEach (child => { child.stopAllActions (); });
Tween动画 循环动画:
1 2 3 4 5 6 7 cc.tween (this .node ) .repeatForever ( cc.tween () .by (0.3 , { position : cc.v2 (0 , -30 ) }) .by (0.3 , { position : cc.v2 (0 , 30 ) }) ) .start ();
组合动画:
1 2 3 4 5 6 7 8 cc.tween (this .node ) .to (1 , { position : cc.v2 (100 , 100 ), scale : 1.5 }) .delay (0.5 ) .to (1 , { position : cc.v2 (0 , 0 ), scale : 1 }) .call (() => { console .log ("Animation complete" ); }) .start ();
方法
说明
示例
to
绝对值变化
.to(1, { x: 100 })
by
相对值变化
.by(1, { x: 100 })
delay
延迟
.delay(0.5)
call
回调
.call(callback)
repeat
重复N次
.repeat(3, tween)
repeatForever
无限循环
.repeatForever(tween)
网络通信 WebSocket证书问题 Android平台用WSS需要配置证书,Web和iOS不需要。
Web/iOS直接用:
1 this .wss = new WebSocket ("wss://your-server.com" );
Android需要证书:
1 2 3 4 5 6 7 8 9 10 11 12 13 if (cc.sys .isNative && cc.sys .os === cc.sys .OS .ANDROID ) { let cacert = cc.url .raw ('resources/Files/cacert.pem' ); if (cc.loader .md5Pipe ) { cacert = cc.loader .md5Pipe .transformURL (cacert); } this .wss = new WebSocket (url, null , cacert); } else { this .wss = new WebSocket (url); }
配置步骤:
curl官网下载 cacert.pem
放 resources/Files/cacert.pem
代码里用上面的方式创建WebSocket
WebSocket状态管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class WebSocketManager { private ws : WebSocket = null ; private url : string = "" ; connect (url: string ) { this .url = url; if (cc.sys .isNative && cc.sys .OS .ANDROID ) { this .connectWithCert (url); } else { this .ws = new WebSocket (url); this .bindEvents (); } } private bindEvents ( ) { this .ws .onopen = (event ) => { console .log ("WebSocket connected" ); }; this .ws .onmessage = (event ) => { console .log ("Received:" , event.data ); }; this .ws .onerror = (event ) => { console .error ("WebSocket error:" , event); }; this .ws .onclose = (event ) => { console .log ("WebSocket closed" ); this .reconnect (); }; } private reconnect ( ) { setTimeout (() => { this .connect (this .url ); }, 3000 ); } }
调试工具 网页端预览调试神器,能实时看节点树、改属性。
功能:
实时显示场景节点树
同步更改节点属性
输出节点、组件引用到控制台
标记UI节点位置
独立显示FPS等调试信息
安装:
下载:https://github.com/potato47/ccc-devtools
解压 preview-template.zip 到项目根目录
刷新浏览器,F12打开开发者工具
Cocos标签页查看节点树
使用:
1 2 3 4 5 6 7 8 9 10 11 const node = cc.find ("Canvas/Player" );console .log (node);const sprite = node.getComponent (cc.Sprite );console .log (sprite);node.x = 100 ; node.color = cc.Color .RED ;
VSCode配置 过滤.meta文件:
全局配置(settings.json):
1 2 3 4 5 { "files.exclude" : { "**/*.meta" : true } }
项目配置(.vscode/settings.json):
1 2 3 4 5 6 7 { "files.exclude" : { "**/*.meta" : true , "**/build" : true , "**/temp" : true } }
Android平台开发 NDK版本选择 不同NDK版本兼容性不一样:
NDK版本
状态
21.1.6352462
正常(推荐)
26.1.10909125
报错:implicit conversion错误
遇到error: implicit conversion from 'int' to 'float'就降级到NDK 21.x。
头条渠道权限问题 头条渠道有个坑:授权同意之前不能获取敏感权限,否则会检测为违规。
问题场景:
1 2 3 行为阶段:授权前行为 行为名称:注册传感器监听器 检测屏幕旋转方向的权限
解决:
修改Cocos2dxActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override protected void onResume () { Log.d(TAG, "onResume()" ); paused = false ; super .onResume(); if (gainAudioFocus) { Cocos2dxAudioFocusManager.registerAudioFocusListener(this ); } Utils.hideVirtualButton(); resumeIfHasFocus(); }
SDK初始化放在同意协议后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 SDK.init(appActivity, new Callback () { @Override public <T> void onResult (int type, boolean isOk, T result) { if (type == Callback.TYPE_INIT) { appActivity.runOnGLThread(new Runnable () { @Override public void run () { Cocos2dxJavascriptJavaBridge.evalString( "window.GameJsb.sdkInitCallback(" + isOk + ")" ); } }); } } });
Android剪贴板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public String copyTextFromClipboard () { String result = "" ; try { if (Looper.myLooper() == null ) { Looper.prepare(); } ClipboardManager clipboard = (ClipboardManager) this .getSystemService(Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); int count = clipData.getItemCount(); for (int i = 0 ; i < count; i++) { ClipData.Item item = clipData.getItemAt(i); CharSequence text = item.coerceToText(this ); result += text.toString(); } Toast.makeText(getApplicationContext(), "成功获取剪贴板内容:" + result, Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); } return result; }
UI线程调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static String getClipboardText () { FutureTask<String> future = new FutureTask <>(new Callable <String>() { @Override public String call () throws Exception { return appActivity.copyTextFromClipboard(); } }); appActivity.runOnUiThread(future); try { return future.get(); } catch (Exception e) { Log.e("Clipboard" , "Error" , e); return "" ; } }
平台发布 Google Play关联风险 2022年后注册的开发者账号,如果因为关联被封,基本是这些原因:
类别
风险等级
IP地址、身份信息
高
卡号、手机号
高
Google Analytics、Facebook、AppsFlyer
中
包名、类名、资源文件
高
文案、图片、协议文本
中
IP清洁度验证 :干净的IP注册时不会出现手机号验证界面。
Pixel设备调试 开启开发者模式:
设置 > 系统 > 关于手机
连续点击版本号10次
返回 > 开发者选项 > 开启USB调试
WiFi网络受限 Pixel连WiFi经常有感叹号,Captive Portal检测Google服务器的问题。
解决:
1 2 3 4 5 6 7 8 9 10 11 adb shell settings delete global captive_portal_https_url adb shell settings delete global captive_portal_http_url adb shell settings put global captive_portal_http_url \ http://captive.v2ex.co/generate_204 adb shell settings put global captive_portal_https_url \ https://captive.v2ex.co/generate_204
传输文件
USB线连接手机和电脑
下拉通知栏,点击USB选项
选”传输文件(MTP)”模式
电脑上打开”我的电脑”
双击Pixel设备访问文件
TLS时间同步 Pixel要求时间误差1分钟内,否则TLS连接失败。
1 2 adb shell settings put global auto_time 1 adb shell settings put global auto_time_zone 1
热更新 视频资源热更新问题 Cocos Creator 2.4.10原生平台,热更新后的MP4可能无法播放。
原因:Cocos2dxVideoView.java里数据源设置方式问题。
解决:修改cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxVideoView.java
1 2 3 4 5 mRetriever.setDataSource(mVideoUri.toString(), new HashMap <String, String>()); mRetriever.setDataSource(mCocos2dxActivity.getContext(), mVideoUri);
第三方平台接入 微信小游戏 服务端口配置 :
报错”工具的服务端口已关闭”:
微信开发者工具 > 设置 > 安全设置
开启服务端口
获取OpenID :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 window ["wx" ].login ({ success : (res ) => { if (res.code ) { console .log ("Code:" , res.code ); requestOpenId (res.code ); } else { console .log ("登录失败:" , res.errMsg ); } } }); async function requestOpenId (code ) { const response = await fetch ( `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID} &secret=${SECRET} &js_code=${code} &grant_type=authorization_code` ); const data = await response.json (); console .log ("OpenID:" , data.openid ); }
获取用户信息 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const button = wx.createUserInfoButton ({ type : 'text' , text : '获取用户信息' , style : { left : 10 , top : 76 , width : 200 , height : 40 , lineHeight : 40 , backgroundColor : '#ff0000' , color : '#ffffff' , textAlign : 'center' , fontSize : 16 , borderRadius : 4 } }); button.onTap ((res ) => { if (res.errMsg .indexOf (':ok' ) > -1 ) { console .log ("用户信息:" , res.userInfo ); } });
域名配置 :
小程序只能和配置的域名通信:
https(wx.request, wx.uploadFile, wx.downloadFile)
wss(wx.connectSocket)
开发环境可以跳过校验:开发者工具 > 设置 > 项目设置 > 不校验请求域名
H5游戏平台 CrazyGames :
1 2 3 4 5 6 7 CrazyGames .SDK .init ();CrazyGames .SDK .ad .requestAd ("midgame" , { adStarted : () => console .log ("Ad started" ), adFinished : () => console .log ("Ad finished" ), adError : () => console .log ("Ad error" ) });
Poki :
1 2 3 4 5 6 PokiSDK .gameLoadingStart ();PokiSDK .gameLoadingFinished ();PokiSDK .commercialBreak ().then (() => { console .log ("Commercial break complete" ); });
Cocos Creator整体来说挺好用的,跨平台发布省很多事。但坑也不少:
WebSocket证书Android和iOS要分开处理
NDK版本要选稳定的,新版经常有坑
头条渠道的权限检测很严格
Google Play关联检测越来越严
热更新后视频播放要注意
建议多看官方文档,遇到问题先查github issue。