前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【H5游戏】红包雨 实现详解

【H5游戏】红包雨 实现详解

作者头像
神仙朱
发布2021-11-30 13:55:02
2.6K1
发布2021-11-30 13:55:02
举报

之前总结了一个用pixi 实现的人物换装游戏,没看过的可以看 PIXI 实现人物换装 今天继续总结用 pixi 实现一个 红包雨 H5 游戏,可以来体验下

相信大家对这个游戏应该不陌生了,支付宝 QQ 啥的春节的时候都有这种游戏。

作为一个前端,做界面相关的实现肯定是我们应有的能力

学一些游戏的实现也可以帮助我们自己,自己开发多一些游戏,可以让朋友间互动,比如结婚的时候,要让我们的能力为我们的生活服务嘛哈哈

不废话了,下面开始讲解这个游戏的具体实现

1、游戏简介

2、游戏实现

3、代码仓库

游戏简介

游戏玩法很简单,去体验下就知道了

红包雨使用的游戏引擎依然是pixi,还不懂PIXI的可以看 PIXI 需求级入门

另外我们还使用了一个动画库来让属性变化动画更加丝滑,比如坐标位置的移动变化,透明度的变化,他就是

gsap

gsap 介绍他是

1、高性能js 动画工具库

2、超强浏览器兼容

3、支持多种实现方式(React、Vue、css、canvas,svg)

4、Chrome 推荐的动画库

5、行业动画标准

6、超千万网站使用

反正就是很牛逼的动画库

官方文档可以看

https://greensock.com/docs/

主要它可以配合pixi 完成动画,简直是不二选择,只要注册 pixi 插件就行了

代码语言:javascript
复制
import { PixiPlugin } from 'gsap/PixiPlugin.js';

gsap.registerPlugin(PixiPlugin);

虽然可能没用过,但是这次涉及的 gsap api 不多,就三个 from 、to、fromTo,都很简单

fromTo ,从某个状态开始,到某个状态结束,需要设置两个状态

代码语言:javascript
复制
// html
<div class="fromTo">1111</div>

//js
gsap.fromTo(".fromTo", { autoAlpha: 0, x: 0 }, { autoAlpha: 1, x: 100 });

from ,从某个状态开始,设置的是起始状态

代码语言:javascript
复制
<div class="from">2222</div>

gsap.from(".from", { opacity: 0, y: 100, duration: 1 });

to , 过渡到某个状态,设置的是结束状态

代码语言:javascript
复制
<div class="to">3333</div>

gsap.to(".to", { opacity: 0, x: 100, duration: 1 });

游戏实现

游戏实现分了 7个部分来详细说明,本文主要讲解主逻辑,边边角角不会太详细,详细的可以看具体仓库代码

1、总览

2、数据配置

3、数据通信

4、代码详解 - 红包

5、代码详解 - 倒计时

1总览

看下整个游戏的流程图 和 代码架构图

流程图

代码架构图

App

功能的入口,控制整个游戏的生命流程,包括其中 红包的定时生成,启动倒计时,监听倒计时结束后清空

代码语言:javascript
复制
class App{
  constructor(container) {
    super();
    this.app = new PIXI.Application({
      width: window.innerWidth,
      height: window.innerHeight - 195,
      antialias: true, // default: false 反锯齿
      autoDensity: true, // canvas视图的CSS尺寸是否应自动调整为屏幕尺寸。
      resolution:2, // default: 1 分辨率
      backgroundColor: 0x000000,
    });
    // 挂载到DOM 上
    container?.appendChild(this.app.view);
  }

  start(){ ... } // 开始游戏

  createRedPkg(){ ... } 
  onRedPkgClick(){ ... }

  createTimer(){ ... }
  onTimeEnd(){ ... }
}

RedPkg

负责红包具体的绘制细节,销毁,监听点击,具体看下面的讲解

代码语言:javascript
复制
class RedPackage {
   create(){ ... }
   destry(){ ... }
   onClick(){ ... }
}

DropBase

把元素坠落动画的功能抽离了出来,主要是这部分是通用能力,游戏中坠落的元素可以有很多种,比如我们的游戏就有 红包和 星星,所以把能力抽离出来,让他们去继承从而拥有这些能力

Timer 、Score

倒计时和 分数 功能比较简单,只是绘制的内容比较复杂

2数据配置

把游戏的一些基础配置数据都抽离出来,单独管理

代码语言:javascript
复制
import RedPkgImg from './img/redpkg.jpg';

export const GAME_CONFIG = {
  assets: {
    redPackageImg: RedPkgImg, // 红包图片
  },
  animations: {
    redPackageFrequency: 300, // 红包生成频率
    countdownTotal: 5000, // 游戏时长
    redPackageDuration: 4000, // 红包坠落时长
    redPackageEaseOut: 200, // 红包消失动画时长
  },
  // 奖品列表
  lotteryList: ['久旱逢甘霖', '金榜题名时', '洞房花烛夜', '他乡遇故知'],
}

3数据通信

App 作为入口,负责元素的控制,包括红包生成,倒计时、分数的绘制等等,而具体的细节则交给对应的类去完成

比如 App 只负责调用 RedPkg 的create 方法,具体怎么 create 由 RedPkg 去负责

App 想要知道 什么时候 create 完毕,就需要 RedPkg 去通知App,所以这里就用了 eventemitter3 这个库去实现监听通信

比如这样

代码语言:javascript
复制
class App {
    start(){
        while(true){
           this.createRedPkg()
        }
    }
    createRedPkg(){
        const rpg = new RedPkg()
        rpg.create()
        rpg.on("createEnd",()=>{
            console.log("绘制结束")
        })
    }
}
class RedPkg extends eventemitter3{
    create(){
        .....
        this.emit("createEnd")
    }
}

4代码详解 - 红包

红包生成逻辑

绘制也没什么复杂的,不过我们需要随机设定他坠落的起始位置,毕竟不能所有红包都从一个位置下来把

代码语言:javascript
复制
class RedPkg {

  element = null

  create() {
    this.element = PIXI.Sprite.from("红包url..");
    this.element.width = 60;
    this.element.height = 90;
    this.element.anchor.set(0.5);

    this.setInitPos()

    // 监听点击事件
    this.element.interactive = true;
    this.element.on('pointerdown', (e) => {
      this.onClick(e);
    });

    // 添加进容器
    this.app.stage.addChild(this.element);
  }
  setInitPos(){ ... }
}

随机位置从 设定好的三个方向中 随机抽取

主要就是通过 Math.random *3 ,这样就得到 1-3 之前的随机数字

代码语言:javascript
复制
class RedPkg {
   initPositions = [
    [window.innerWidth * 0.25, 0],
    [window.innerWidth * 0.5, 0],
    [window.innerWidth * 0.75, 0],
  ];

  setInitPos(){
    // 设置随机初始坐标
    const [x, y] = this.getRandomPosition();
    this.element.x = x;
    this.element.y = y;
  }

  getRandomPosition() {
    const randomNum = Math.random() * this.initPositions.length
    return this.initPositions[ Math.floor(randomNum)];
  }
}

除了设置初始的位置,我们还会设置一个随机初始的 角度,代码是一样的

而 红包什么时候开始生成 ,是由App 控制的

当调用游戏开始的时候,App会使用一个 setInterval 去循环生成 红包

代码语言:javascript
复制
class App {
  start(){
    this.redPkgTimer = setInterval(() => {
      this.createRedPkg();
    },250); 
  }
  createRedPkg(){
    const redPkg = new RedPkg()
    redPkg.create()
  }
}

红包掉落逻辑

这里的内容主要是红包坠落的动画,观察这个动画,一个是从上至下的坠落动画,一个是左右摇晃的动画,毕竟是模拟雨嘛,并不是直上直下的

这里就用了前面说的动画库 gasp,控制的动画是 红包元素 的 y 坐标 和 x 坐标 变化

先看最基础的坠落动画,使用 gasp.to 设置 结束状态的坐标是 【屏幕高度+50】

然后 gasp 就可以控制 红包元素 从起始位置一直移动到结束位置,从而达到 坠落的效果

代码语言:javascript
复制
import gsap from 'gsap/all';

class  DropBase {
  element = null
  drop() {
    this.animateY();
    this.animateX();
  }
  animateY() {
    gsap.to(this.element, {
      y: window.innerHeight + 50,
      duration: 5000, // 坠落的时间,前面抽离出来的配置
      ease: 'none',
      onComplete: () => {
        this.app.stage?.removeChild?.(this.element);
      },
    });
  }
}

class RedPkg extends DropBase{ ... }

class App {
  createRedPkg(){
    const redPkg = new RedPkg()
    redPkg.create()
    redPkg.drop() // 手动控制坠落
  }
}

下面来看下 X 坐标的动画,其实就是横向的偏移动画

这里的逻辑主要是几点

1、设置横向偏移的幅度值,比如这里设置的是 左右最大偏移25

2、偏移是从一端到另一端,所以使用 gasp.fromTo 设定起始状态 和 结束状态

3、为了防止元素偏移到屏幕之后,计算出偏移值之后会 进行比较

先简单看下代码

代码语言:javascript
复制
class  DropBase {
  animateX() {
    const initX = this.element.x
    // 起始位置是 位置左偏移X 25
    const from = {
      x: Math.max(0, initX - 25),
    };
    // 结束位置是 位置右偏移X 25
    const to = {
      x: Math.min(initX + 25, window.innerWidth),
    };
    // gasp 配置
    const option = {
      duration: 2,
      repeat: -1,
      yoyo: true, // 
      ease: 'power1.inOut',
    };
    // 左右随机一下,以免都是同一个方向的偏移
    if (Math.random() > 0.5) {
      gsap.fromTo(this.element, from, {
        ...to,
        ...option,
      });
    } else {
      gsap.fromTo(this.element, to, {
        ...from,
        ...option,
      });
    }
  }
}

上面代码需要解释两个地方

1、配置中出现的 repeat 和 yoyo

可想而知,左右横移动画应该是重复的,而不是执行一次就结束了,所以这里设置 repeat = -1,表示为无限循环

yoyo 类似于 css 中的 animation-direction:alternate

作用是 动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放。

就像这样循环往复的效果

不然每完成一次动画都从头开始

2、偏移方向随机

为了防止所有红包 都往一个方向偏移,所以这里会随机处理一下,有的往左,有的往右

也就是调换一下 from 和 to

红包点击逻辑

红包点击之后需要做几个事情

1、红包移除+消失动画

2、分数+1

在 监听到 红包元素点击的时候,就需要通知到总控室 App,控制分数+1

代码语言:javascript
复制
class RedPkg {
  element = null
  create() {
    this.element = PIXI.Sprite.from("红包url..");
    // 监听点击事件
    this.element.interactive = true;
    this.element.on('pointerdown', (e) => {
      this.onClick(e);
    });
  }
  onClick(){
      this.emit("redPkgClick",()=>{
        // 红包消失动画...
      })
  }
}
class App {
  createRedPkg(){
    const redPkg = new RedPkg()
    redPackage.on('redPkgClick', this.onRedPackageClick);
  }
  onRedPackageClick(){ ... 分数+1 逻辑 }
}

另外点击之后还有一个红包消失的动画,这部分内容主要是 ,不复杂,但是挺麻烦的,不过不属于主体逻辑,所以不放在这里说,具体可以看仓库代码

5代码详解 - 倒计时

倒计时内容主要有两部分

1、绘制的逻辑

2、通信逻辑

其中绘制的内容也不会细说,主要看仓库代码,这里讲讲 倒计时 和 App 的关系

倒计时内部,主要就是使用 setInterval 完成时间计算,然后在倒计时结束的时候触发 timeEnd 事件

代码语言:javascript
复制
class Timer extends EventEmitter {

   remainTime = 5000 // 剩余时长

   duration=1000 // 单位时长

   start() {
    this.draw();

    const timer = setInterval(() => {
      this.remainTime -= this.duration;
      if (this.remainTime <= 0) {
        clearInterval(timer);
        this.emit('timeEnd');
      }
    }, this.duration);
  }

  // 绘制倒计时
  draw(){...}
}

App 则是负责初始化倒计时,监听倒计时事件,在倒计时结束的时候,完成收尾动作

代码语言:javascript
复制
class App {
  start(){
    this.createTimer()
    this.redPkgTimer = setInterval(() => {
      this.createRedPkg();
    },250);
  }

  createTimer(){
    this.timer = new Timer();
    this.timer.start();
    this.timer.on('timeEnd', () => {
      // ....移除所有元素,销毁 pixi 容器
      this.destroy()
    });
  }
}

倒计时结束的收尾动作,主要是

1、销毁红包生成定时器

2、销毁所有 pixi 元素

3、销毁 pixi 容器

代码语言:javascript
复制
class App{

  destroy() {
    clearInterval(this.redPkgTimer as number);
    this.redPkgTimer = null;

    while (this.app?.stage.children[0]) {
      this.app.stage.removeChild(this.app.stage.children[0]);
    }

    this.app?.destroy(true);
    this.app?.renderer?.destroy(true);
  }
}

代码仓库

https://gitee.com/hoholove/study-code-snippet/tree/master/PIXI/HongBaoYu

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神仙朱 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档