【JS球球大作战项目实战】+在线体验

2024-03-31 1180阅读

温馨提示:这篇文章已超过397天没有更新,请注意相关的内容是否还可用!

【JS球球大作战项目实战】+在线体验



个人名片:

【JS球球大作战项目实战】+在线体验


🐼作者简介:一名大三在校生,喜欢AI编程🎋

🐻‍❄️个人主页🥇:落798.

🐼个人WeChat:hmmwx53

🕊️系列专栏:🖼️

  • 零基础学Java——小白入门必备🔥
  • 重识C语言——复习回顾🔥
  • 计算机网络体系———深度详讲
  • HCIP数通工程师-刷题与实战🔥🔥🔥
  • 微信小程序开发——实战开发🔥
  • HarmonyOS 4.0 应用开发实战——实战开发🔥🔥🔥
  • Redis快速入门到精通——实战开发🔥🔥🔥
  • RabbitMQ快速入门🔥

    🐓每日一句:🍭我很忙,但我要忙的有意义!

    欢迎评论 💬点赞👍🏻 收藏 📂加关注+



    文章目录

    • BallBattle
      • 简介
      • 背景设定
      • 玩法
      • 主要功能
        • 匹配对战
        • 属性同步与保存
          • 房间属性
          • 玩家属性
          • 自定义事件
          • 其他功能
            • 消息处理控制
            • 移动同步
            • 项目结构
            • 欢迎添加微信,加入我的核心小队,请备注来意

              BallBattle

              简介

              《球球大作战》是一款由巨人网络Superpop&Lollipop工作室自主研发,并且免费(不包括道具)的手机网络游戏。2015年5月27日由巨人网络在中国大陆发行。

              【JS球球大作战项目实战】+在线体验

              游戏以玩家间的实时互动PK为设计宗旨,通过简单的规则将玩家操作直接转化为游戏策略,体验智谋碰撞的战斗乐趣。在这个球球的世界里,每个人都化身为一颗独特的球球,大球吃小球,努力生存下来就是唯一的目标。

              线上体验

              背景设定

              在宇宙深处一片遍布着荆棘之花的神秘星云中,生活着一群名叫“波拉哩”(译名“球球”)的奇特生物。他们外表萌萌,却有着勇敢的心。他们是天生的战斗种族,为战斗而生,为战斗而亡。

              传说中,这群波拉哩的共同祖先是一只叫“塔坦”的超级波拉哩,塔坦的职责就是守护宇宙瑰宝“荆棘之花”,它拥有强大的能量,会分出分身,变化万物,唯一的弱点就是贪吃。

              一天,塔坦终于禁不住诱惑,偷食了“荆棘之花”,结果身体爆裂,成为了数以亿计的小波拉哩。从此,波拉哩的族群就受到了贪食的诅咒,只能在这片星云中无休止的战斗,如果停止战斗,生命便会流失,消亡在茫茫的星空之中。在漫漫的历史长河里,只有最强大的波拉哩才能冲过这片黑暗星云,打破命运的枷锁,去寻找那传说中的光明与和平。

              为了那甜蜜的希望,波拉哩们战斗着。他们必须奋力奔跑,让自己变大变大再变大,才能对抗比自己更强的存在。哪怕经历无数失败也必须重新凝聚力量,直到成为最强壮的那个。

              【JS球球大作战项目实战】+在线体验

              玩法

              输入房间 ID,加入房间(如果没有此房间,则创建)。

              用户 ID 随机生成。

              使用 ⬆️⬇️⬅️➡️ 或 WSAD 来控制小球移动,吃掉场景中的食物(三角形,方形,六边形)则会增长体重(并减少速度);遇到其他球(玩家),碰撞之后,体重较大者获胜,较小者将会死亡并重生。

              右侧面板显示当前房间的玩家体重排行榜。

              主要功能

              匹配对战

              最基础的房间 ID 匹配。

              更多关于房间匹配文档

              属性同步与保存

              这个 demo 使用的是 Master Client 机制,但由于 Master Client 可能存在掉线等异常情况,所以需要将房间和玩家的部分数据保存至 Room Properties 和 Player Properties。

              更多关于属性同步文档

              房间属性
              • 房间用时
              • 战场的食物列表
              • 食物最大 ID

                食物:

                /**
                 * 食物
                 */
                cc.Class({
                  extends: cc.Component,
                  properties: {
                    id: 0,
                    type: 0
                  },
                  getProperties() {
                    const id = this.id;
                    const type = this.type;
                    const { x, y } = this.node.position;
                    return {
                      id,
                      type,
                      x,
                      y
                      // 可能还会有能量值
                    };
                  }
                });
                
                玩家属性
                • 位置
                • 体重
                • 速度

                  球:

                  const Constants = require("Constants");
                  const Food = require("./Food");
                  /**
                   * 球
                   */
                  cc.Class({
                    extends: cc.Component,
                    properties: {
                      nameLabel: {
                        type: cc.Label,
                        default: null
                      },
                      infoLabel: {
                        type: cc.Label,
                        default: null
                      }
                    },
                    init(player) {
                      this.player = player;
                    },
                    eat() {
                      // 计算尺寸
                      const { weight } = this.player.customProperties;
                      const scale = Math.sqrt(weight) / Constants.BORN_SIZE;
                      this.node.scale = cc.v2(scale, scale);
                    },
                    reborn() {
                      // 计算尺寸
                      const { weight, pos } = this.player.customProperties;
                      const scale = Math.sqrt(weight) / Constants.BORN_SIZE;
                      this.node.scale = cc.v2(scale, scale);
                      // 位置
                      const { x, y } = pos;
                      this.node.position = cc.v2(x, y);
                    },
                    getId() {
                      return this.player.actorId;
                    },
                    getSpeed() {
                      const { speed } = this.player.customProperties;
                      return speed;
                    },
                    getWeight() {
                      const collider = this.node.getComponent(cc.CircleCollider);
                      const { radius } = collider;
                      const { scaleX, scaleY } = this.node;
                      return Constants.PI * Math.pow(radius, 2) * scaleX * scaleY;
                    },
                    // LIFE-CYCLE CALLBACKS:
                    start() {
                      this.nameLabel.string = this.player.userId;
                    },
                    update(dt) {
                      const { x, y } = this.node;
                      this.infoLabel.string = `(${parseInt(x)}, ${parseInt(y)})`;
                    },
                    // 碰撞
                    onCollisionEnter(other, self) {
                      const { group: otherGroup } = other.node;
                      if (otherGroup === Constants.FOOD_GROUP) {
                        this._onCollideFood(other, self);
                      } else if (otherGroup === Constants.BALL_GROUP) {
                        this._onCollideBall(other, self);
                      }
                    },
                    _onCollideFood(other, self) {
                      // 球碰食物,客户端模拟
                      const { node: foodNode } = other;
                      const { x, y } = self.node.position;
                      cc.log(`collide food: (${x}, ${y})`);
                      const food = foodNode.getComponent(Food);
                      foodNode.active = false;
                      // 交由 Master 处理
                      const event = new cc.Event.EventCustom(
                        Constants.BALL_AND_FOOD_COLLISION_EVENT,
                        true
                      );
                      event.detail = {
                        ball: this,
                        food
                      };
                      this.node.dispatchEvent(event);
                    },
                    _onCollideBall(other, self) {
                      const { node: b1Node } = other;
                      const { node: b2Node } = self;
                      const event = new cc.Event.EventCustom(
                        Constants.BALL_AND_BALL_COLLISION_EVENT,
                        true
                      );
                      event.detail = {
                        b1Node,
                        b2Node
                      };
                      this.node.dispatchEvent(event);
                    }
                  });
                  

                  自定义事件

                  • 玩家出生:对于当前玩家,执行战场初始化逻辑;对于其他玩家,执行增加玩家逻辑。
                  • 吃食物:客户端移除内存中的食物节点,同步玩家体重。
                  • 杀死玩家:用于同步节点间碰撞事件。
                  • 玩家重生:用于重新初始化玩家数据。
                  • 生成食物:同步房间内的食物数据。
                  • 玩家离开:用于移除场景和 UI 对应节点。
                  • 游戏结束:用于返回主菜单场景。

                    其他功能

                    消息处理控制

                    由于从主场景加载到战斗场景,存在异步的资源加载过程,所以需要暂停 / 恢复消息队列的处理。流程如下:

                    • 加入房间
                    • 暂停消息处理
                    • 加载战斗场景
                    • 初始化战场
                    • 恢复消息队列。
                      移动同步

                      移动同步实现思路是玩家在运动状态改变时,将当前运动状态同步给其他客户端,其他客户端对玩家行为进行模拟。而在运动过程中,并不同步移动数据。

                      运动状态包括:

                      • 位置
                      • 移动方向
                      • 时间戳

                        模拟步骤:

                        • 在收到运动状态改变时,根据运动改变时的位置,方向,以及当前时间戳与运动改变时的时间戳的差值,计算出当前应该所在的位置 p0
                        • 玩家节点当前实际所在位置 p1,p0 - p1(向量减法),即为校正后的运动路径
                        • 对路径进行模拟,直至下次运动状态改变

                          球控制器,当前客户端需要添加组件,由用户输入直接移动,并触发移动同步

                          const Ball = require("Ball");
                          const Constants = require("../Constants");
                          const LeanCloud = require("../LeanCloud");
                          const { getClient } = LeanCloud;
                          /**
                           * 球控制器,当前客户端需要添加组件,由用户输入直接移动,并触发移动同步
                           */
                          cc.Class({
                            extends: cc.Component,
                            properties: {},
                            // LIFE-CYCLE CALLBACKS:
                            onLoad() {
                              cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this._onKeyDown, this);
                              cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this._onKeyUp, this);
                              this._ball = this.node.getComponent(Ball);
                              this._direction = cc.Vec2.ZERO;
                            },
                            onDestroy() {
                              cc.systemEvent.off(
                                cc.SystemEvent.EventType.KEY_DOWN,
                                this._onKeyDown,
                                this
                              );
                              cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this._onKeyUp, this);
                            },
                            start() {
                              this._cameraNode = cc.find("Canvas/Main Camera");
                            },
                            update(dt) {
                              const speed = this._ball.getSpeed();
                              const delta = this._direction.normalize().mul(speed * dt);
                              const position = this.node.position.add(delta);
                              const { x, y } = position;
                              const { LEFT, RIGHT, TOP, BOTTOM } = Constants;
                              const newPosition = cc.v2(
                                Math.min(Math.max(x, LEFT), RIGHT),
                                Math.min(Math.max(y, BOTTOM), TOP)
                              );
                              this.node.position = newPosition;
                              // 设置摄像机跟随
                              this._cameraNode.position = this.node.position;
                            },
                            _onKeyDown(event) {
                              this.running = true;
                              let dir = this._direction.clone();
                              switch (event.keyCode) {
                                case cc.macro.KEY.a:
                                case cc.macro.KEY.left:
                                  dir.x = -1;
                                  break;
                                case cc.macro.KEY.d:
                                case cc.macro.KEY.right:
                                  dir.x = 1;
                                  break;
                                case cc.macro.KEY.w:
                                case cc.macro.KEY.up:
                                  dir.y = 1;
                                  break;
                                case cc.macro.KEY.s:
                                case cc.macro.KEY.down:
                                  dir.y = -1;
                                  break;
                                default:
                                  break;
                              }
                              this._synchMove(dir.normalize());
                            },
                            _onKeyUp(event) {
                              let dir = this._direction.clone();
                              switch (event.keyCode) {
                                case cc.macro.KEY.a:
                                case cc.macro.KEY.left:
                                case cc.macro.KEY.d:
                                case cc.macro.KEY.right:
                                  dir.x = 0;
                                  break;
                                case cc.macro.KEY.w:
                                case cc.macro.KEY.up:
                                case cc.macro.KEY.s:
                                case cc.macro.KEY.down:
                                  dir.y = 0;
                                  break;
                                default:
                                  break;
                              }
                              this._synchMove(dir.normalize());
                            },
                            _synchMove(dir) {
                              if (dir.fuzzyEquals(this._direction, 0.01)) {
                                return;
                              }
                              this._direction = dir;
                              const { x, y } = this.node.position;
                              const { x: dx, y: dy } = this._direction;
                              const client = getClient();
                              client.player.setCustomProperties({
                                move: { p: { x, y }, d: { x: dx, y: dy }, t: Date.now() }
                              });
                            }
                          });
                          

                          项目结构

                          ├── Animation 动画目录
                          ├── Prefabs 预制目录,主要存放球,食物预制体
                          ├── Scene 场景目录,主菜单场景,战斗场景
                          ├── Script 脚本目录
                          │   ├── Battle 战斗相关脚本目录
                          │   │    ├── Ball.js 球节点控制脚本
                          │   │    ├── BallController.js 玩家控制球脚本,生成移动数据同步给其他客户端
                          │   │    ├── BallSimulator.js 玩家运动模拟脚本,根据玩家运动数据,模拟运动行为
                          │   │    ├── Battle.js 战场节点总控制器,用于接收并解析战斗中的自定义事件,驱动场景节点及 UI 节点变化
                          │   │    ├── BattleHelper.js 战场工具脚本
                          │   │    ├── Food.js 食物节点控制脚本
                          │   │    ├── Master.js 游戏逻辑脚本,用于区分 Master 客户端与普通客户端,Master 组件用于生成房间数据及逻辑判断,只有 Master 的客户端才拥有这个组件,包括最初的房间的创建者和切换后的新房主。
                          │   │    ├── PlayerInfoItem.js 玩家信息 UI 节点控制脚本
                          │   │    └── UI.js UI 控制脚本
                          │   ├── Menu主菜单相关脚本目录
                          │   │    └── Menu.js 主菜单脚本
                          │   ├── Constants.js 游戏中用到的常量
                          │   └── LeanCloud.js 全局存放 LeanCloud SDK 对象的脚本
                          ├── Texture 素材资源目录
                          └── play.js LeanCloud 实时对战服务 SDK
                          

                          添加wx/微信公众回复球球大作战获取完整代码

                          在线体验链接


                          欢迎评论 💬点赞👍🏻 收藏 📂加关注+



                          【JS球球大作战项目实战】+在线体验

                          欢迎添加微信,加入我的核心小队,请备注来意

                          👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]