初试pixijs,实现猫和老鼠小游戏(上)

274 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

一、前言

接触gsap后,对pixijs也产生了兴趣,Pixi是一个非常快的2D sprite渲染引擎,Pixi可以自由的使用,同时能与其它框架无缝集成。话不多说,来试试看吧。

注意: 1、本文使用ts,部分代码存在不兼容情况; 2、本文所使用的pixi版本为6.5.5。

二、过程

1、下载

本文是在vite构建的vue3项目里,所以需要以下命令

npm i pixi.js pixi-tweener

局部引入

import * as  PIXI from 'pixi.js'

2、初始化一个application对象

我们需要创建一个特殊Pixi容器对象,先按官网的来试一试

let app = new PIXI.Application({
  width: 400,         // default: 800 宽度
  height: 400,        // default: 600 高度
  antialias: true,    // default: false 反锯齿
  transparent: false, // default: false 透明度
  resolution: 1       // default: 1 分辨率
})
//Add the canvas that Pixi automatically created for you to the HTML document
document.body.appendChild(app.view)

生成的的stage先直接挂载到body上,效果如下

1664972955432.png

ok能达到预期效果,那下面我们正式开始制作我们的场景吧

3、正式开始

(1)准备好元素标签和样式

id为pixijsjs的是我们挂载canvas的元素,基础布局样式如下

    <style scoped>
    .pixilabel {
      position: absolute;
      top: 20%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    .pixibox {
      width: 400px;
      height: 400px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    </style>

(2)生成Pixi容器对象,并往其中的stage(舞台)添加sprite(精灵)

生成的pixi容器对象这步骤跟上述一样,添加到页面标签后,调用加载器

  app.loader
  .add([
    "tom.jpeg",
    "jerry.jpeg"
  ])
  .load(setup)

此处我们使用add函数加载了两张图片,图片位置是在public下

1664976418338.png

在加载完毕之后,会调用load里的setup函数

function setup() {
  console.log("setup")
  let cat = new PIXI.Sprite(app.loader.resources["tom.jpeg"].texture)
  //  调整汤姆的大小和位置
  //Change the sprite's position
  cat.x = 300
  cat.y = 200
  // 用cat.scale.set(0.5, 0.5)也可以,缩放大小为原图的一半
  //Change the sprite's size
  cat.width = 80
  cat.height = 120
  app.stage.addChild(cat)
  let mouse = new PIXI.Sprite(app.loader.resources["jerry.jpeg"].texture)
  //  调整杰瑞的大小和位置
  //Change the sprite's position
  mouse.x = 40
  mouse.y = 260
  //Change the sprite's size
  // 杰瑞应该比汤姆小只
  mouse.width = 40
  mouse.height = 60
  app.stage.addChild(mouse)
}

到这里我们的tom和jerry就生成啦。

1664976795550.png

(3)游戏循环机制game loop和requestAnimationFrame

game loop每秒执行60次,requestAnimationFrame跟屏幕刷新率挂钩,多少HZ就一秒调用几次,一般都是60次。

本文使用ticker构建gameloop,添加完sprite精灵之后,补上以下语句

app.ticker.add(delta => gameLoop(delta))

gameloop函数

function gameLoop(delta: number){
  console.log(delta)
  //Move the cat 1 pixel 
  cat.x -= 1
}

下面是requestAnimationFrame的写法,可供参考

   // app.ticker.add(delta => gameLoop(delta))
   //换成下面的
    gameLoop()

gameloop函数

   function gameLoop(delta: number){
      console.log(delta)
      requestAnimationFrame(gameLoop)
      //Move the cat 1 pixel 
      cat.x -= 1
    }
    

效果如下:

移动.gif

(4)设置动画状态

官网是推荐的构建游戏循环步骤是,说是像这样构建戏循环,将会更容易去处理游戏场景和关卡的切换。

1、设置游戏的状态state
2、开启游戏循环
3、在游戏循环函数里更新状态

代码如下:

let state: Function  // 创建状态变量

然后在加载完精灵图之后,

  state = play // 挂载到状态函数
  app.ticker.add(delta => gameLoop(delta)) // 开启游戏循环

gameloop和play函数如下

function gameLoop(delta: number){
  // console.log(delta, cat)
  //更新状态
  state(delta)
}
function play(delta: number) {
  console.log(delta)
  //每次向左移动1px
  cat.x -= 1
}
  

效果跟之前一样,虽然不知道这样有什么好处,但官网都这样推荐,应该是有它的道理的。

(5)碰撞检测

当我们的汤姆碰到杰瑞,那应该就结束这次动画

// 写了个检测函数hitTestRectangle,官网有直接copy,检测两个精灵是否有重合的部分
hitTestRectangle(spriteOne, spriteTwo)

需要改动的代码不多

let count = 0 // 初始化变量

修改play函数,同时增加hitTestRectangle函数

function play(delta: number) {
  console.log(delta)
  //Move the cat 1 pixel to the right each frame
  if (hitTestRectangle(cat, mouse) && count === 0) {
    //There's a collision
    cat.x = 130
    cat.tint = 0xff0054
    count++
  } else if (!hitTestRectangle(cat, mouse) && count === 0) {
    //There's no collision
    cat.x -= 1
    cat.tint = 0xccff99
  }
}
function hitTestRectangle(r1: PIXI.Sprite, r2: PIXI.Sprite) {

    //Define the variables we'll need to calculate
    let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;

    //hit will determine whether there's a collision
    hit = false;

    //Find the center points of each sprite
    r1.centerX = r1.x + r1.width / 2;
    r1.centerY = r1.y + r1.height / 2;
    r2.centerX = r2.x + r2.width / 2;
    r2.centerY = r2.y + r2.height / 2;

    //Find the half-widths and half-heights of each sprite
    r1.halfWidth = r1.width / 2;
    r1.halfHeight = r1.height / 2;
    r2.halfWidth = r2.width / 2;
    r2.halfHeight = r2.height / 2
    //Calculate the distance vector between the sprites
    vx = r1.centerX - r2.centerX
    vy = r1.centerY - r2.centerY

    //Figure out the combined half-widths and half-heights
    combinedHalfWidths = r1.halfWidth + r2.halfWidth
    combinedHalfHeights = r1.halfHeight + r2.halfHeight

    //Check for a collision on the x axis
    if (Math.abs(vx) < combinedHalfWidths) {

      //A collision might be occurring. Check for a collision on the y axis
      if (Math.abs(vy) < combinedHalfHeights) {

        //There's definitely a collision happening
        hit = true
      } else {

        //There's no collision on the y axis
        hit = false
      }
    } else {

      //There's no collision on the x axis
      hit = false
    }

    //`hit` will be either `true` or `false`
    return hit
}

效果就是汤姆气的脸色发青去抓杰瑞,结果碰到之前后退100px,同时怒气使得汤姆变红(物理)。

碰撞检测.gif

三、小结

今天试了几个基础的函数,实现的效果也比较简单,大家伙看个乐呵哈,明天再带大家领略一下pixi的其他函数。

ps:我是地霊殿__三無,一起学习吧

微信图片_20221001074313.jpg