零基础开发小游戏语音开黑Demo

LogicAdventurer
• 阅读 607

与亲朋好友一起玩在线游戏,如果游戏中有实时语音能力可以拉进玩家之间的距离,添加更多乐趣。我们以经典的中国象棋为例,开发在线语音象棋。本文主要涉及如下几个点:

  1. 在线游戏的规则,本文以中国象棋为例。
  2. 借助Zego SDK的实时消息能力,实现在线游戏实时数据传输。
  3. 借助Zego SDK的语音能力,实现在线语音。

注意:虽然本文以中国象棋为例,但其他在线小游戏同样可以套用,只是游戏规则不一样而已。

最终效果如下:

零基础开发小游戏语音开黑Demo

1 中国象棋游戏规则

关于中国象棋的游戏规则,我这里做个简单的介绍。

  1. 车:只能走直线。
  2. 马:只能按字对角走,如果往对角方向的长边有棋子,则不能走。
  3. 象:只能按字对角走,且不能过河。如果字正中心有棋子,则不能走。
  4. 仕:只能在九宫对角线上走。
  5. 帅:只能在九宫里面走,需要注意,双方帅如果在同一条直线上中间必须有棋子,否则不允许在同一条直线。
  6. 跑:如果不吃子,则跟车一样的规则。如果吃子,则需要被吃的子与跑之间有一个棋子。
  7. 兵:没过河时只能前进。过河后,可以左右和前进,但不能后腿。

在玩家每一次下棋时,首先需要验证目标位置是否是有效位置,即是否符合游戏规则:

// 判断是否可以移动
public static boolean canMove(Chessboard chessboard, int fromX, int fromY, int toX, int toY) {
    //不能原地走
    if (fromX == toX && fromY == toY)
        return false;

    Chess chess = chessboard.board[fromY][fromX];
    // 首先,确保目标位置不是自己的子
    Chess[][] board = chessboard.board;
    if (board[toY][toX] != null && board[toY][toX].isRed() == chessboard.isRed) {
        return false;
    }

    switch (chess.type) {
        case RED_SHUAI:
        case BLACK_SHUAI:
            return canShuaiMove(chessboard, fromX, fromY, toX, toY);
        case RED_SHI:
        case BLACK_SHI:
            return canShiMove(chessboard, fromX, fromY, toX, toY);
        case RED_XIANG:
        case BLACK_XIANG:
            return canXiangMove(chessboard, fromX, fromY, toX, toY);
        case RED_MA:
        case BLACK_MA:
            return canMaMove(chessboard, fromX, fromY, toX, toY);
        case RED_CHE:
        case BLACK_CHE:
            return canCheMove(chessboard, fromX, fromY, toX, toY);
        case RED_PAO:
        case BLACK_PAO:
            return canPaoMove(chessboard, fromX, fromY, toX, toY);
        case RED_ZU:
        case BLACK_ZU:
            return canZuMove(chessboard, fromX, fromY, toX, toY);
    }

    return true;
}

如果是符合规则的行走,再直接将目标位置的棋子移除(必须先判断有棋子且是对方棋子才行)。游戏可以一直这样持续下去,直到有一方的被吃掉, 游戏结束。

2 实时游戏数据传输

实时传输游戏数据可以自己基于TCP去实现,但有如下几个缺点:

  1. 双方必须在同一个局域网,或者双方必须用有效的互联网ip地址。
  2. 得要精心维护消息数据发送与接收,代码量大且不方便维护。

我们可以借助Zego SDK中强大的实时消息能力实现实时棋盘同步,具体如何接入可以查看官方文档:
https://doc-zh.zego.im/article/3575。通过这篇官方文档,基本上可以完成Zego SDK的接入工作。

2.1 登录/登出房间

使用Zego SDK之前必须要先完成登录房间,因为不管是实时语音还是实时消息,都是以房间为单位的。假设读者已经按照官方文档教程创建好引擎对象mEngine。接下来是登录实现代码:

public boolean loginRoom(String userId, String userName, String roomId, String token) {

    ZegoUser user = new ZegoUser(userId, userName);
    ZegoRoomConfig config = new ZegoRoomConfig();
    config.token = token; // 请求开发者服务端获取
    config.isUserStatusNotify = true;
    mEngine.loginRoom(roomId, user, config, (int error, JSONObject extendedData) -> {
        // 登录房间结果,如果仅关注登录结果,关注此回调即可
    });
    Log.e(TAG, "登录房间:" + roomId);
    return true;
} 

登出操作比较简单mEngine.logoutRoom(roomId);指定房间ID即可。

2.2 发送实时消息

有了前面这些准备工作后,接下来是实现实时棋盘同步。封装一个发送消息函数:

public void sendMsg(String roomId, ArrayList<ZegoUser> userList, Msg msg) {

    String msgPack = msg.toString();
    // 发送自定义信令,`toUserList` 中指定的用户才可以通过 onIMSendCustomCommandResult 收到此信令
    // 若 `toUserList` 参数传 `null` 则 SDK 将发送该信令给房间内所有用户
    mEngine.sendCustomCommand(roomId, msgPack, userList, new IZegoIMSendCustomCommandCallback() {
        /**
          * 发送用户自定义消息结果回调处理
          */
        @Override
        public void onIMSendCustomCommandResult(int errorCode) {
            //发送消息结果成功或失败的处理
            Log.e(TAG, "消息发送结束,回调:" + errorCode);
        }
    });

}

其中,roomId表示房间号,userList表示接收人列表,msg是我们自定义的一个实体类。创建一个表示实时棋盘界面的实体类:

public class MsgBoard extends Msg {
    public boolean isRedPlaying; //接下来是否是红方下棋
    public byte[][] board; //当前棋局面
    public int fromX;
    public int fromY;
    public int toX;
    public int toY;

    public MsgBoard(int msgType, String fromUserId, boolean isRedPlaying, byte[][] board, int fromX, int fromY, int toX, int toY) {
        super(msgType, fromUserId);
        this.board = board;
        this.isRedPlaying = isRedPlaying;
        this.fromX = fromX;
        this.fromY = fromY;
        this.toX = toX;
        this.toY = toY;
    }
}

每个玩家下完棋后,将当前棋子位置发出去(如果有服务器,为了安全,这个工作最好让服务器去做)。这样,可以实现不局限于2个游戏玩家,如果有多个观众,任何观众随时上线即可观看对战。

3 接入实时语音

第2小节完成了Zego SDK的接入,接下来完成实时语音功能。实时语音实现过程可以看官方文档https://doc-zh.zego.im/article/7636。简单来说,
最重要的是2步:

  1. 推语音流
  2. 拉语音流
注意:一切操作都必须先登录房间成功后再做,否则会失败。

3.1推流

实时语音推流代码如下:

public void pushStream(String streamId) {
    //不管有没有推流,先停止推流
    mEngine.stopPublishingStream();
    mEngine.startPublishingStream(streamId);
    Log.e(TAG, "已推流:" + streamId); 
}

这里的streamId建议RoomID_UserID_后缀形式,以确保唯一性,避免串流

3.2 拉流

顾名思义,拉流是拉取对方的实时语音流。那如何知道对方的streamID呢?可以监听如下回调函数:

 public void onRoomStreamUpdate(String roomID, 
                                ZegoUpdateType updateType,
                                ArrayList<ZegoStream> streamList,
                                JSONObject extendedData) ;

房间里面一旦有新的流推送或有流停止推送都会触发这个回调函数。我们根据updateType参数来判断是新增还是删除:

if(updateType == ZegoUpdateType.ADD){
   //表示有新增流
} else if (updateType == ZegoUpdateType.DELETE) {
   //表示有流停止推送
}

一旦判断有新增流,那么可以直接拉取对方流,拉流和停止拉流代码如下:

public void pullStream(String streamId) {
    mEngine.startPlayingStream(streamId);
}

public void stopPullStream(String streamId) {
    mEngine.stopPlayingStream(streamId);
}

4 总结

实现了在线语音中国象棋对战游戏,其他任何游戏均可直接套用本文的实时同步和实时语音能力。我将这两部份能力封装起来,读者可以直接下载源码复用。

源码如下:
https://github.com/KaleTom/On...

近期有开发规划的开发者可上即构官网查看,恰逢即构七周年全线音视频产品1折的优惠,适合有预算要求的中小型企业和个人开发工作室。
笔者为大家争取到小福利:联系商务获取RTC产品优惠,发送“RTC七周年福利"有惊喜!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Stella981 Stella981
4年前
Python+Selenium自动化篇
本篇文字主要学习selenium定位页面元素的集中方法,以百度首页为例子。0.元素定位方法主要有:id定位:find\_element\_by\_id('')name定位:find\_element\_by\_name('')class定位:find\_element\_by\_class\_name(''
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
4年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
dkll dkll
4个月前
运营版游戏陪玩平台源码/tt语音聊天/声优服务/陪玩系统源码开黑/约玩源码小程序公众号APP三端
一、语音聊天功能语音聊天功能是游戏陪玩平台中不可或缺的一部分。它允许用户和陪玩师傅在游戏过程中进行实时语音交流,提升游戏体验。开发语音聊天功能时,需要考虑音频数据的压缩、实时音视频技术的应用以及音频效果的实现。这些技术的优化和提升可以为用户提供高质量的语音