Java 多玩家 libgdx 教程 【已翻译100%】

我们如何去做?

  • 在 libgdx的主页修改libgdx样本“superjumper".
  • 使用 AppWarp Cloud将它转化为2个玩家的实时游戏.
  • 本游戏将匹配玩家并且用户需要到达城堡来赢得游戏的胜利.
    • 用户将获得其他用户成绩的实时反馈以增加了游戏的刺激性。

Eclipse 项目设置

接下来,您需要从这个git repository下载libgdx游戏样本(superjumper) 项目。.

在Eclipse中打开下载的superjumper解决方案。你将看到项目如下:

为了创建多玩家,我使用了 AppWarp Java SDK (1.5 as of now)

关于libgdx的依赖
正如 libgdx的官网上提到的,这个示例依赖于libgbx。如果你试图运行libgbx网站上的superjumper示例程序,你会得到关于gdx,gdx-backend-lwjgl, gdx-jnigen, gdx-openal的错误。你需要将这些工程设置为你的应用(superjumper)的依赖库工程来解决这些错误。

但我已经将这些库包含在了superjumper git仓库中的libs文件夹下。观看这个视频或阅读这个教程来了解更多关于libgdx工程的安装设置。

取得你的AppWarp application keys

如果你要与AppWarp云服务集成,你需要从ShepHertz开发者面板AppHq取得你的application keys.这些key能够在ShepHertz云服务中识别的你应用空间,而且AppWarp云需要用它们隔离不同应用间的消息。

在AppWarp网站上按步骤注册(免费)并取得你的application keys.

现在打开superjumper示例工程中的 WarpController.java 文件并在其中添加这些值。例如:

public static String AppKey = "14a611b4b3075972be364a7270d9b69a5d2b24898ac483e32d4dc72b2df039ef";
public static String SecretKey = "55216a9a165b08d93f9390435c9be4739888d971a17170591979e5837f618059";

运行多用户sample
既然你已经准备好了, 我们可以实际运行并观察这个游戏了。因为这个游戏有单人或多人游戏的选项,为了玩多人游戏你需要在2个模拟器/设备上同时运行它。

当你按了multiplayer按扭, 这个游戏会连接AppWarp并加入一个游戏房间。一旦进入这个游戏房间, 这个客户端在游戏开始前会一直等待第二个玩家加入该房间。

现在你需要在第二个模拟器/设备上做同样的操作,AppWarp的匹配API会将第二个玩家连接到相同的游戏房间,然后游戏开始。玩家需要到达城堡来完成这个游戏。同时用户会发现他们的对手在实时地运动。这个游戏内实时通信正是AppWarp的强大之处。

游戏会在这三个条件下完成

用户离开:当一个玩家离开游戏时,另一个玩家被判定为胜利者。因为他的对手已经离开的游戏。

闯关成功:到达城堡的玩家成为胜利者

游戏结束:如果玩家碰到了松鼠或者玩家掉了下来那另一个玩家会成为胜利者。

怎样与AppWarp集成
开始游戏

首先你需要用你的应用密钥初始化Warpclient单例(WarpController.java).

WarpClient.initialize(apiKey, secretKey);
接下你需要连接到AppWarp云端并且加入一个游戏房间(WarpController.java)

注意: AppWarp SDK 提供通过异步API提供它的功能。这意味着你只需简单的将请求监听器添加到WarpClient实例中区接受响应和通知即可。

这个文件 (WarpController.java) 有我们这步所需的所有代码。它创建连接请求,房间请求,区域请求(如果有必要创建一个房间)。因此我们添加相关监听器到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();
    }
}

设置好listener后我们可以继续创建连接。用户需要传入一个惟一用户名(username)以连接到AppWarp云。在示例中我只是使用了一个随机字符串(你也可以从用户或像facebook的第三方服务处取得来惟一标识用户)。随机字符串是在MainMenuScreen.java文件中生成的。

WarpClient.connectWithUserName(userName);

连接的结果会交给以下回调函数。

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();
    }
}

如果连接成功,我们会试着加入一个房间。我们也可以选择初始化UDP(稍后的游戏玩家会用到)。为了加入房间,我们使用JoinRoomInRange方法并传入参数(1,1),它会请求服务器将客户端加入只有一个用户的房间。如果失败我们会新建并加入一个容纳两个玩家的房间。

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();
    }
}

一旦加入某个房间(不管是现在还是创建新房间之后),客户端需要订阅这个房间来接收房间的通知(这在游戏中是必须的)。这里详细解释了这些概念。订阅之后我们要调用getLiveRoomInfo来检查房间是否有两个玩家了,如果是我们就开始游戏,否则就等待其他玩家加入这个房间。

WarpClient.getLiveRoomInfo(roomId);
public void onGetLiveRoomInfo(String[] liveUsers){
    if(liveUsers!=null){
        if(liveUsers.length==2){
            startGame();
        }else{
            waitForOtherUser();
        }
        }else{
            warpClient.disconnect();
            handleError();
    }
}

开始游戏

进行游戏的代码在MultiplayerGameScreen.java文件中。如果用户进入了这个界面,那就意味着有两个用户在这个房间中且游戏开始了。玩家玩这个游戏,并且他也要更新其他玩家的状态。其他玩家在你的界面上显示成绿色小怪物。

随着玩家在界面上移动以完成游戏关卡,需要绘制它的移动轨迹,也要将位置更新发送给远程玩家。参见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);
    }
}

消息通过我们在这个示例中所写的工具方法来发送。WarpClient允许客户端将字节数组广播给它所在的房间。可以使用TCP(默认)或UDP来发送。记住我们已经在成功连接到云服务后的第一个界面中初始化了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
    }
  }

发送给房间的消息是通过onUpdatePeersReceived的回调方法提供的。在这个回调中我们要解析这个消息并识别发送者,消息类型和与此消息绑定的数据。我们根据这些消息做相应的处理。

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
}

游戏结束

当游戏结束后我们只需要更新房间的属性。其他玩家收到通知后需要根据此消息更新他们的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

当两个远程玩家同玩游戏时,他们有可能会同时结束游戏,而这会引起资源竞争。这种情况最好交由服务器解决,所以我们使用了lockProperties API。所以当游戏结束时用户向服务器发送一个lockProperties请求将结果属性锁定。一旦这个结果被某个用户锁定,服务器会放弃处理后续对同一个属性的lockProperties请求。点击此处以了解更多此AppWarp仲裁方式。

随着游戏的结束,其他用户得到通知,StartMultiplayerScreen.java根据以下代码将游戏结束的原因显示到界面上。

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);
}

我们也要离开并取消订阅此房间,并且取消监听器;如果游戏不在运行状态我们也要删除房间。由于在这个游戏中我们使用的是AppWarp 动态房间,在使用完后最好立即删除(尽管空动态房间在60分钟后都会被自动删除)。

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();
}

用户可以在这里点击并返回MainMenuScreen,然后我们可以重新进行这个过程。但这次我们只需要找到一个房间就可以开始了(因为我们已经连接到了服务器)。

总结

这篇文章中我们看到如何用AppWarp开发多人游戏。 我们在一个现成的libgdx超级跳跃例子基础上用 AppWarp Cloud 特性进行拓展。我们同样看到客户端怎样连接到AppWarp上,怎样加入游戏房间。继承概念不受libgdx的影响,并且可以应有与其他任何Java程序中。

使用Robovm发布到iOS

你可以使用 Robovm 来将超级跳跃游戏发布到iOS上. 下面是几步是任何其它项目中都需要做的。另外你需要做如下改变。

  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>

  1. 使用如下代码从背景中改变屏幕。

    Gdx.app.postRunnable(new Runnable() {
    @Override
    public void run () {
        game.setScreen(new MultiplayerGameScreen(game, StartMultiplayerScreen.this));
    }
    });
    
这里另有要求, 我们得到如下错误,因为AppWarp的回调不在UI线程中。
    Exception in thread "MessageDispatchThread" java.lang.IllegalArgumentException:     Error compiling shader
3. 超级跳跃中声音不可用了,这是因为iOS中的声音是使用RoboVm。

时间: 2024-11-03 21:50:59

Java 多玩家 libgdx 教程 【已翻译100%】的相关文章

Scala 比 Java 还快? 【已翻译100%】

通常Scala被认为比Java要慢,特别是用于函数式编程时.本文会解释为什么这个被广泛接受的假设是错误的. 数据验证 编程中一个常见的问题是数据验证.即我们要确保所有得到的数据处于正确的结构中.我们需要从安全的,编译器验证的数据中找到不安全的外部输入.在一个典型的WEB应用中,你需要验证每个请求.很明显这会影响你的应用的性能.在本文中我将会比较处理这个问题的两种极不相同的解决方案.Java的Bean验证API和来自play的统一验证API.后者是一种更为函数式的方法,它具有不变性和类型安全的特性

创建你自己的 Java 注解类 【已翻译100%】

如果你已经在使用Java编程,并且也使用了任何像Spring和Hibernate这样的流行框架,那么你应该对注解的使用非常地熟悉.使用一个现有框架工作的时候,通常使用它的注解就够了.但是,你是不是也有时候有要创建属于你自己的注解的需求呢? 不久之前,我找到了一个自己创建一个注解的理由,那是一个涉及验证存储在多种数据库中的常用数据的项目. 场景描述 该业务有多种数据库都存储着相同的数据,它们有各自不同的保持数据更新的方法. 该业务曾计划把所有这些数据都整合到一个主数据库中,以减轻涉及到多种数据源所

Docker, Java EE 7, 和 Maven with WebLogic 12.1.3 【已翻译100%】

WebLogic 12.1.3已经发布,并且对于JavaEE7的APIs在数据库支持web应用开发上也是最重要的支持.以下是在发行版本中支持的一些标准: Java Persistence API 2.1 (implemented by EclipseLink) JAX-RS 2.0 (implemented by Jersey) JSON-P 1.0 (implemented by GlassFish subproject jsonp) WebSockets 1.0 (implemented b

Java 8 Lambda 表达式示例 【已翻译100%】

自从我听说Java8将要支持Lambda表达式(或称闭包),我便开始狂热的想要将这些体面的简洁的功能元素应用到我的代码中来.大多开发者普遍的使用匿名内部类来开发事件处理器,比较器,thread/runnable实现等等,一些没有必要的辅助代码将逻辑复杂化,即便一些非常简单的代码也变的复杂不堪.Java8现在加入了Lambda表达式作为语法的一部分将会极大地解决这一类似问题. 它使得开发者可以封装一个单独的行为单元并且传递给其他代码.他更像是一个匿名类(带有一个方法的可推断类型)的语法糖和一个更少

Spring Java 配置之 Session 超时 【已翻译100%】

我们生活在一个美好的时代,在这个时代你可以使用基于java的配置来开发一个Spring应用程序. 再也没有多余的XML代码了,只有纯正的java代码. 本文中我想要讨论一下关于Spring应用程序中的session管理这里流行主题. 更确切的目的是我将会说说java配置风格会话超时配置. 而在我之前的一篇 博文 中, 我已经谈到了如何去管理一个会话的生命周期. 但是那一种方案需要使用web.xml文件,而在基于java的配置中是不需要的. 因为其作用是操作一个扩展了 AbstractAnnota

从 C++ 到 Objective-C 的快速指南 【已翻译100%】

**简介 ** 当我开始为iOS写代码的时候,我意识到,作为一个C++开发者,我必须花费更多的时间来弄清楚Objective-C中怪异的东西.这就是一个帮助C++专家的快速指南,能够使他们快速的掌握Apple的iOS语言. 请注意这绝不是一个完整的指南,但是它让你避免了阅读100页的手册.除此之外,我知道你喜欢我的写作风格. 背景 需要C++的技能,我会比较C++和Objective-C的东西.此外,COM编程也是有用的,因为Objective-C有类似于IUnkown的东西,因此基础的COM编

从 Objective-C 到 Swift —— Swift 糖果 【已翻译100%】

Swift带来很多确实很棒的特性,使得很难再回到Objective-C.主要的特性是安全性,不过这也被看成是一种额外副作用. 带类型接口的强型别 Swift有强型别,这意味着除非你要求,不然Swift不会为你做类型之间的转换.所以,例如你无法把Int型赋给Double型.你不得不首先转换类型: let i: Int = 42 let d: Double = Double(i) 或者你必须给Double类扩展一个方法用来转换Int型: extension Double { func __conve

AngularJS —— 使用 ngResource、RESTful APIs 和 Spring MVC 框架提交数据 【已翻译100%】

本文为开发者呈现了一些概念和相关的示例代码,介绍了用ngResource($resource)服务POST方式提交数据到和服务器端SpringMVC环境下的RESTFul APIs.示例代码可以在如下页面找到:http://hello-angularjs.appspot.com/angularjs-restful-apis-post-method-code-example.相对于使用$http服务,我更喜欢这种方法的主要理由是ngResource允许你使用抽象方式(例如$resource类),你

编写更好代码的 6 个提示 【已翻译100%】

每周我都可以用四种不同的语言编写至少几百行代码.我也可以同其他与我一同工作的开发者协作进行代码的编辑和审查. 简单来说,有许多代码在到处放着,当它们没有被组织管理起来,但 更重要的是当它们没有写好时,事情就会变得有点复杂起来.让我们来看一看几种能提升我们的代码整体质量的不同方法. 1. 开始构建模块 保持代码一致,可重用且有组织的一个最好方式就是将功能成组的放在一起.例如,别把你所有的js代码都扔到一个main.js文件中,而是要尝试基于功能将它们分组放在分开的文件里面, 然后在你达成你的构建步