Java 多玩家 libgdx 教程
我們?nèi)绾稳プ?
-
在 libgdx的主頁(yè)修改libgdx樣本“superjumper".
-
使用 AppWarp Cloud將它轉(zhuǎn)化為2個(gè)玩家的實(shí)時(shí)游戲.
-
本游戲?qū)⑵ヅ渫婕也⑶矣脩粜枰竭_(dá)城堡來(lái)贏得游戲的勝利.
-
用戶將獲得其他用戶成績(jī)的實(shí)時(shí)反饋以增加了游戲的刺激性。
Eclipse 項(xiàng)目設(shè)置
接下來(lái),您需要從這個(gè)git repository下載libgdx游戲樣本(superjumper) 項(xiàng)目。.
在Eclipse中打開(kāi)下載的superjumper解決方案。你將看到項(xiàng)目如下:
為了創(chuàng)建多玩家,我使用了 AppWarp Java SDK (1.5 as of now)
關(guān)于libgdx的依賴
正如 libgdx的官網(wǎng)上提到的,這個(gè)示例依賴于libgbx。如果你試圖運(yùn)行l(wèi)ibgbx網(wǎng)站上的superjumper示例程序,你會(huì)得到關(guān)于gdx,gdx-backend-lwjgl, gdx-jnigen, gdx-openal的錯(cuò)誤。你需要將這些工程設(shè)置為你的應(yīng)用(superjumper)的依賴庫(kù)工程來(lái)解決這些錯(cuò)誤。
但我已經(jīng)將這些庫(kù)包含在了superjumper git倉(cāng)庫(kù)中的libs文件夾下。觀看這個(gè)視頻或閱讀這個(gè)教程來(lái)了解更多關(guān)于libgdx工程的安裝設(shè)置。
取得你的AppWarp application keys
如果你要與AppWarp云服務(wù)集成,你需要從ShepHertz開(kāi)發(fā)者面板AppHq取得你的application keys.這些key能夠在ShepHertz云服務(wù)中識(shí)別的你應(yīng)用空間,而且AppWarp云需要用它們隔離不同應(yīng)用間的消息。
在AppWarp網(wǎng)站上按步驟注冊(cè)(免費(fèi))并取得你的application keys.
現(xiàn)在打開(kāi)superjumper示例工程中的 WarpController.java 文件并在其中添加這些值。例如:
- public static String AppKey = "14a611b4b3075972be364a7270d9b69a5d2b24898ac483e32d4dc72b2df039ef";
- public static String SecretKey = "55216a9a165b08d93f9390435c9be4739888d971a17170591979e5837f618059";
運(yùn)行多用戶sample
既然你已經(jīng)準(zhǔn)備好了, 我們可以實(shí)際運(yùn)行并觀察這個(gè)游戲了。因?yàn)檫@個(gè)游戲有單人或多人游戲的選項(xiàng),為了玩多人游戲你需要在2個(gè)模擬器/設(shè)備上同時(shí)運(yùn)行它。
當(dāng)你按了multiplayer按扭, 這個(gè)游戲會(huì)連接AppWarp并加入一個(gè)游戲房間。一旦進(jìn)入這個(gè)游戲房間, 這個(gè)客戶端在游戲開(kāi)始前會(huì)一直等待第二個(gè)玩家加入該房間。
現(xiàn)在你需要在第二個(gè)模擬器/設(shè)備上做同樣的操作,AppWarp的匹配API會(huì)將第二個(gè)玩家連接到相同的游戲房間,然后游戲開(kāi)始。玩家需要到達(dá)城堡來(lái)完成這個(gè)游戲。同時(shí)用戶會(huì)發(fā)現(xiàn)他們的對(duì)手在實(shí)時(shí)地運(yùn)動(dòng)。這個(gè)游戲內(nèi)實(shí)時(shí)通信正是AppWarp的強(qiáng)大之處。
游戲會(huì)在這三個(gè)條件下完成
-
用戶離開(kāi):當(dāng)一個(gè)玩家離開(kāi)游戲時(shí),另一個(gè)玩家被判定為勝利者。因?yàn)樗膶?duì)手已經(jīng)離開(kāi)的游戲。
-
闖關(guān)成功:到達(dá)城堡的玩家成為勝利者
-
游戲結(jié)束:如果玩家碰到了松鼠或者玩家掉了下來(lái)那另一個(gè)玩家會(huì)成為勝利者。
#p#
怎樣與AppWarp集成
開(kāi)始游戲
首先你需要用你的應(yīng)用密鑰初始化Warpclient單例(WarpController.java).
- WarpClient.initialize(apiKey, secretKey);
接下你需要連接到AppWarp云端并且加入一個(gè)游戲房間(WarpController.java)
注意: AppWarp SDK 提供通過(guò)異步API提供它的功能。這意味著你只需簡(jiǎn)單的將請(qǐng)求監(jiān)聽(tīng)器添加到WarpClient實(shí)例中區(qū)接受響應(yīng)和通知即可。
這個(gè)文件 (WarpController.java) 有我們這步所需的所有代碼。它創(chuàng)建連接請(qǐng)求,房間請(qǐng)求,區(qū)域請(qǐng)求(如果有必要?jiǎng)?chuàng)建一個(gè)房間)。因此我們添加相關(guān)監(jiān)聽(tīng)器到OnStart()方法中。
- public WarpController()
- {
- initAppwarp();
- warpClient.addConnectionRequestListener(new ConnectionListener(this));
- warpClient.addChatRequestListener(new ChatListener(this));
- warpClient.addZoneRequestListener(new ZoneListener(this));
- warpClient.addRoomRequestListener(new RoomListener(this));
- warpClient.addNotificationListener(new NotificationListener(this));
- }
- private void initAppwarp(){
- try {
- WarpClient.initialize(apiKey, secretKey);
- warpClient = WarpClient.getInstance();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
設(shè)置好listener后我們可以繼續(xù)創(chuàng)建連接。用戶需要傳入一個(gè)惟一用戶名(username)以連接到AppWarp云。在示例中我只是使用了一個(gè)隨機(jī)字符串(你也可以從用戶或像facebook的第三方服務(wù)處取得來(lái)惟一標(biāo)識(shí)用戶)。隨機(jī)字符串是在MainMenuScreen.java文件中生成的。
- WarpClient.connectWithUserName(userName);
連接的結(jié)果會(huì)交給以下回調(diào)函數(shù)。
- public void onConnectDone(ConnectEvent e) {
- if(e.getResult()==WarpResponseResultCode.SUCCESS){
- callBack.onConnectDone(true);
- }else{
- callBack.onConnectDone(false);
- }
- }
- public void onConnectDone(boolean status){
- if(status){
- warpClient.initUDP();
- warpClient.joinRoomInRange(1, 1, false);
- }else{
- isConnected = false;
- handleError();
- }
- }
如果連接成功,我們會(huì)試著加入一個(gè)房間。我們也可以選擇初始化UDP(稍后的游戲玩家會(huì)用到)。為了加入房間,我們使用JoinRoomInRange方法并傳入?yún)?shù)(1,1),它會(huì)請(qǐng)求服務(wù)器將客戶端加入只有一個(gè)用戶的房間。如果失敗我們會(huì)新建并加入一個(gè)容納兩個(gè)玩家的房間。
- public void onJoinRoomDone(RoomEvent event){
- if(event.getResult()==WarpResponseResultCode.SUCCESS){// success case
- this.roomId = event.getData().getId();
- warpClient.subscribeRoom(roomId);
- }else if(event.getResult()==WarpResponseResultCode.RESOURCE_NOT_FOUND){// no such room found
- HashMap<string, object=""> data = new HashMap<string, object="">();
- data.put("result", "");
- warpClient.createRoom("superjumper", "shephertz", 2, data);
- }else{
- warpClient.disconnect();
- handleError();
- }
- }
一旦加入某個(gè)房間(不管是現(xiàn)在還是創(chuàng)建新房間之后),客戶端需要訂閱這個(gè)房間來(lái)接收房間的通知(這在游戲中是必須的)。這里詳細(xì)解釋了這些概念。訂閱之后我們要調(diào)用getLiveRoomInfo來(lái)檢查房間是否有兩個(gè)玩家了,如果是我們就開(kāi)始游戲,否則就等待其他玩家加入這個(gè)房間。
- WarpClient.getLiveRoomInfo(roomId);
- public void onGetLiveRoomInfo(String[] liveUsers){
- if(liveUsers!=null){
- if(liveUsers.length==2){
- startGame();
- }else{
- waitForOtherUser();
- }
- }else{
- warpClient.disconnect();
- handleError();
- }
- }
開(kāi)始游戲
進(jìn)行游戲的代碼在MultiplayerGameScreen.java文件中。如果用戶進(jìn)入了這個(gè)界面,那就意味著有兩個(gè)用戶在這個(gè)房間中且游戲開(kāi)始了。玩家玩這個(gè)游戲,并且他也要更新其他玩家的狀態(tài)。其他玩家在你的界面上顯示成綠色小怪物。
隨著玩家在界面上移動(dòng)以完成游戲關(guān)卡,需要繪制它的移動(dòng)軌跡,也要將位置更新發(fā)送給遠(yuǎn)程玩家。參見(jiàn)WorldRenderer.java(multiplayer)
- private void renderBob () {
- {
- ...
- ...
- if (side < 0){
- batch.draw(keyFrame, world.local_bob.position.x + 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
- sendLocation(world.local_bob.position.x + 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
- }else{
- batch.draw(keyFrame, world.local_bob.position.x - 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
- sendLocation(world.local_bob.position.x - 0.5f, world.local_bob.position.y - 0.5f, side * 1, 1);
- }
- }
消息通過(guò)我們?cè)谶@個(gè)示例中所寫(xiě)的工具方法來(lái)發(fā)送。WarpClient允許客戶端將字節(jié)數(shù)組廣播給它所在的房間??梢允褂肨CP(默認(rèn))或UDP來(lái)發(fā)送。記住我們已經(jīng)在成功連接到云服務(wù)后的***個(gè)界面中初始化了UDP。
- private void sendLocation(float x, float y, float width, float height){
- try {
- JSONObject data = new JSONObject();
- data.put("x", x);
- data.put("y", y);
- data.put("width", width);
- data.put("height", height);
- WarpController.getInstance().sendGameUpdate(data.toString());
- } catch (Exception e) {
- // exception in sendLocation
- }
- }
發(fā)送給房間的消息是通過(guò)onUpdatePeersReceived的回調(diào)方法提供的。在這個(gè)回調(diào)中我們要解析這個(gè)消息并識(shí)別發(fā)送者,消息類型和與此消息綁定的數(shù)據(jù)。我們根據(jù)這些消息做相應(yīng)的處理。
- public void onUpdatePeersReceived(UpdateEvent event) {
- callBack.onGameUpdateReceived(new String(event.getUpdate()));
- }
- try {
- JSONObject data = new JSONObject(message);
- float x = (float)data.getDouble("x");
- float y = (float)data.getDouble("y");
- float width = (float)data.getDouble("width");
- float height = (float)data.getDouble("height");
- renderer.updateEnemyLocation(x, y, width, height);
- } catch (Exception e) {
- // exception
- }
#p#
游戲結(jié)束
當(dāng)游戲結(jié)束后我們只需要更新房間的屬性。其他玩家收到通知后需要根據(jù)此消息更新他們的UI。
- public void updateResult(int code, String msg){
- if(isConnected){
- STATE = COMPLETED;
- HashMap<string, object=""> properties = new HashMap<string, object="">();
- properties.put("result", code);
- warpClient.lockProperties(properties);
- }
- }
lockProperties
當(dāng)兩個(gè)遠(yuǎn)程玩家同玩游戲時(shí),他們有可能會(huì)同時(shí)結(jié)束游戲,而這會(huì)引起資源競(jìng)爭(zhēng)。這種情況***交由服務(wù)器解決,所以我們使用了lockProperties API。所以當(dāng)游戲結(jié)束時(shí)用戶向服務(wù)器發(fā)送一個(gè)lockProperties請(qǐng)求將結(jié)果屬性鎖定。一旦這個(gè)結(jié)果被某個(gè)用戶鎖定,服務(wù)器會(huì)放棄處理后續(xù)對(duì)同一個(gè)屬性的lockProperties請(qǐng)求。點(diǎn)擊此處以了解更多此AppWarp仲裁方式。
隨著游戲的結(jié)束,其他用戶得到通知,StartMultiplayerScreen.java根據(jù)以下代碼將游戲結(jié)束的原因顯示到界面上。
- public void onGameFinished (int code) {
- if(code==WarpController.GAME_WIN){
- this.msg = game_loose;
- }else if(code==WarpController.GAME_LOOSE){
- this.msg = game_win;
- }else if(code==WarpController.ENEMY_LEFT){
- this.msg = enemy_left;
- }
- update();
- game.setScreen(this);
- }
我們也要離開(kāi)并取消訂閱此房間,并且取消監(jiān)聽(tīng)器;如果游戲不在運(yùn)行狀態(tài)我們也要?jiǎng)h除房間。由于在這個(gè)游戲中我們使用的是AppWarp 動(dòng)態(tài)房間,在使用完后***立即刪除(盡管空動(dòng)態(tài)房間在60分鐘后都會(huì)被自動(dòng)刪除)。
- public void handleLeave(){
- if(isConnected){
- warpClient.unsubscribeRoom(roomId);
- warpClient.leaveRoom(roomId);
- if(STATE!=STARTED){
- warpClient.deleteRoom(roomId);
- }
- warpClient.disconnect();
- }
- }
- private void disconnect(){
- warpClient.removeConnectionRequestListener(new ConnectionListener(this));
- warpClient.removeChatRequestListener(new ChatListener(this));
- warpClient.removeZoneRequestListener(new ZoneListener(this));
- warpClient.removeRoomRequestListener(new RoomListener(this));
- warpClient.removeNotificationListener(new NotificationListener(this));
- warpClient.disconnect();
- }
用戶可以在這里點(diǎn)擊并返回MainMenuScreen,然后我們可以重新進(jìn)行這個(gè)過(guò)程。但這次我們只需要找到一個(gè)房間就可以開(kāi)始了(因?yàn)槲覀円呀?jīng)連接到了服務(wù)器)。
總結(jié)
這篇文章中我們看到如何用AppWarp開(kāi)發(fā)多人游戲。 我們?cè)谝粋€(gè)現(xiàn)成的libgdx超級(jí)跳躍例子基礎(chǔ)上用 AppWarp Cloud 特性進(jìn)行拓展。我們同樣看到客戶端怎樣連接到AppWarp上,怎樣加入游戲房間。繼承概念不受libgdx的影響,并且可以應(yīng)有與其他任何Java程序中。
使用Robovm發(fā)布到iOS
你可以使用 Robovm 來(lái)將超級(jí)跳躍游戲發(fā)布到iOS上. 下面是幾步是任何其它項(xiàng)目中都需要做的。另外你需要做如下改變。
1. 將這些屬性添加到 robovm.xml
- <forcelinkclasses>
- <pattern>org.apache.harmony.xnet.provider.jsse.OpenSSLProvider</pattern>
- <pattern>org.apache.harmony.security.provider.cert.DRLCertFactory</pattern>
- <pattern>com.android.org.bouncycastle.jce.provider.BouncyCastleProvider</pattern>
- <pattern>org.apache.harmony.security.provider.crypto.CryptoProvider</pattern>
- <pattern>org.apache.harmony.xnet.provider.jsse.JSSEProvider</pattern>
- <pattern>com.android.org.bouncycastle.jce.provider.JCEMac$SHA1</pattern>
- </forcelinkclasses>
2. 使用如下代碼從背景中改變屏幕。
- Gdx.app.postRunnable(new Runnable() {
- @Override
- public void run () {
- game.setScreen(new MultiplayerGameScreen(game, StartMultiplayerScreen.this));
- }
- });
這里另有要求, 我們得到如下錯(cuò)誤,因?yàn)锳ppWarp的回調(diào)不在UI線程中。
Exception in thread "MessageDispatchThread" java.lang.IllegalArgumentException: Error compiling shader
3. 超級(jí)跳躍中聲音不可用了,這是因?yàn)閕OS中的聲音是使用RoboVm。
英文原文: Java Multiplayer libgdx Tutorial
譯文鏈接:http://www.oschina.net/translate/java-multiplayer-libgdx-tutorial