PixiJs-Container 容器

2,693 阅读5分钟

Container 容器

什么是container

见名知意, 它就是一个容器,如果你写过html,我们可以看下这个例子,从html中去类比container的概念以及它的好处

<div class="container">
    <span class="title"></span>
    <p class="content">2222</p>
</div>

官方的描述如下

The Container class provides a simple display object that does what its name implies - collect a set of child objects together. But beyond grouping objects, containers have a few uses that you should be aware of.

大概意思就是,把一些子元素组合起来,但是又远远不止是组合他们。 看上面的html,用div.container把span 和p 包起来又什么好处?样式,移动,形变等好控制。

看下官方是如何介绍container的作用

Almost every type of display object is also derived from Container - even Sprites! This means that in many cases you can create a parent-child hierarchy with the objects you want to render.

However, it's a good idea not to do this. Standalone Container objects are very cheap to render, and having a proper hierarchy of Container objects, each containing one or more renderable objects, provides flexibility in rendering order. It also future-proofs your code, as when you need to add an additional object to a branch of the tree, your animation logic doesn't need to change - just drop the new object into the proper Container, and your logic moves the Container with no changes to your code.

So that's the primary use for Containers - as groups of renderable objects in a hierarchy.

大概意思,整体控制层级,以及渲染的高性能

一个contain demo讲解


import * as PIXI from 'pixi.js'

class App extends PIXI.Application {
    bunnyTexture = PIXI.Texture.from('assets/bunny.png')
    mainContainer: PIXI.Container = null
    constructor() {
        super({
            width: 800,
            height: 600,
            backgroundColor: 0xfc5531, //fc5531 0x1099bb
            backgroundAlpha: 0.5,
        })
        this.bootStrap()
        this.renderComponent()
        this.startCustomTicker()
    }
    bootStrap() {
        document.body.append(this.view)
        this.mainContainer = new PIXI.Container()
        this.stage.addChild(this.mainContainer)
    }
    renderComponent() {
        for (let i = 0; i < 25; i++) {
            let sprite = new PIXI.Sprite(this.bunnyTexture)
            sprite.anchor.set(0.5)
            sprite.position.x = (i % 5) * 40
            sprite.position.y = Math.floor((i / 5)) * 40
            this.mainContainer.addChild(sprite)
        }
        this.mainContainer.position.x = this.screen.width / 2
        this.mainContainer.position.y = this.screen.height / 2
        this.mainContainer.pivot.x = this.mainContainer.width / 2;
        this.mainContainer.pivot.y = this.mainContainer.height / 2
    }
    startCustomTicker() {
        this.ticker.add((delta) => {
            this.mainContainer.rotation -= 0.01 * delta;
        })
    }
}
(window as any).debugApp = new App()
  • new PIXI.Container 创建一个Container的实例,并且把它添加到舞台中去
  • 本项目中,我们创建了25个sprite 实例,并且把它添加到上面创建的container中了
  • 最后添加了ticker(可以粗略把它理解为前端中的requestAnimationFrame封装),每一帧执行的去改变container 的rotation

mask实现

继续按照前文html来类比

<div class="container">
    <span class="title"></span>
    <p class="content">2222</p>
</div>
// css
<style>
.container {
    width: 200px;
    height:200px;
    overflow:hidden;// auto
}
</style>

如果p和span 的高度和宽度综合超出200 * 200 那么多余的内容就会不展示。mask 的作用比较类似,就是遮罩层,超出边界的内容不会被绘制出来,接着前面的demo,我们来实现下mask的demo

Mask Container demo

  • 新建一个Container 为mainContainer
  • 新建一个mask,mask可以是一个图形组件绘制的图形(100 * 200的矩形),也可以是一个sprite,这里先不展开
  • 把container的mask属性设置为mask
  • 创建一个文本组件,追加到container,让text 文本多一些超出mask的限制
import * as PIXI from 'pixi.js'
class App extends PIXI.Application {
    bunnyTexture = PIXI.Texture.from('assets/bunny.png')
    mainContainer: PIXI.Container = null
    maskContainer: PIXI.Container = null
    text: PIXI.Text = null
    constructor() {
        super({
        width: 800,
        height: 600,
        backgroundColor: 0xfc5531, //fc5531 0x1099bb
        backgroundAlpha: 0.5,
        })
        this.bootStrap()
    }
    bootStrap() {
        document.body.append(this.view)
        this.mainContainer = new PIXI.Container()
        this.stage.addChild(this.mainContainer)
        this.stage.addChild(this.getMask())
        this.renderComponent()
        this.startCustomTicker()
        // PIXI.utils.sayHello('a')
    }

    getMask() {
        // Create a graphics object to define our mask
        let mask = new PIXI.Graphics();
        // Add the rectangular area to show
        mask.beginFill(0x1099bb);
        mask.drawRect(0, 0, 100, 200);
        mask.endFill();
        let maskContainer = new PIXI.Container()
        maskContainer.mask = mask
        this.maskContainer = maskContainer
        return maskContainer
    }
    renderSpriteCom() {
        for (let i = 0; i < 25; i++) {
            let sprite = new PIXI.Sprite(this.bunnyTexture)
            sprite.anchor.set(0.5)
            sprite.position.x = (i % 5) * 40
            sprite.position.y = Math.floor((i / 5)) * 40
            console.log('add Before => ',sprite.width, sprite.height)
            this.mainContainer.addChild(sprite)
            console.log('add After => ',sprite.width, sprite.height)
        }
        this.mainContainer.position.x = this.screen.width / 2
        this.mainContainer.position.y = this.screen.height / 2
        this.mainContainer.pivot.x = this.mainContainer.width / 2;
        this.mainContainer.pivot.y = this.mainContainer.height / 2
    }
    renderTextCom() {
        let text = new PIXI.Text(
        'This text will scroll up and be masked, so you can see how masking works. Lorem ipsum and all that.\n\n' +

        'You can put anything in the container and it will be masked!',
        {
        fontSize: 24,
        fill: 0x1010ff,
        wordWrap: true,
        wordWrapWidth: 180
        }
        );
        text.x = 10;
        this.text = text
        this.maskContainer.addChild(text);
    }

    renderComponent() {
        this.renderSpriteCom()
        this.renderTextCom()
    }

    startCustomTicker() {
        this.ticker.add((delta) => {
            this.mainContainer.rotation -= 0.01 * delta;
        })
        let elapsed = 0.0;
        this.ticker.add((delta) => {
            elapsed += delta;
            this.text.y = 10 + -100.0 + Math.cos(elapsed / 50.0) * 100.0;
        });
    }
}
(window as any).debugApp = new App()

知识点讲解

//设置sprite中心点
sprite.anchor.set(0.5)
//设置container 位置
this.mainContainer.position.x = this.screen.width / 2
this.mainContainer.position.y = this.screen.height / 2
this.mainContainer.pivot.x = this.mainContainer.width / 2;
this.mainContainer.pivot.y = this.mainContainer.height / 2

上面代码中用到了anchor.set 和pivot.x /pivot.y,接下来我们来详细讲一下

anchor

我们先来创建一个宠物精灵,然后把它定位屏幕中心点,然后给他设置旋转不同的角度

this.petSprite.position.x = this.screen.width / 2
this.petSprite.position.y = this.screen.height / 2
this.petSprite.rotation = 0.5

改变rotation的不同的值,然后用代码输出旋转之后宠物精灵的postion ,x,y值,你会发现都没有变化,这是因为宠物精灵的渲染都是围绕它的左上角(x,y)作为旋转中心,下面画张图会更直观一些.这个(x,y)点叫做锚点(anchor)

加上下面一行代码试试

this.petSprite.anchor.set(0.5)

通过对比上面两张图应该比较直观清晰的看到区别了吧,两张图的宠物精灵代码设置的position中的xy 都没有变化,但是位置却变化了,第二张图,宠物精灵是居中的,第一张不是哈。 anchor.xanchor.y,cnchor.set()是快捷方法从0到1。点本身的位置不会改变,只会改变纹理的位置

pivot

代码做如下变更,这里我们的精灵的尺寸是102 * 88

代码-1

this.petSprite.pivot.set(102,88)
this.petSprite.position.x = 102
this.petSprite.position.y = 88

你会发现现在宠物精灵在左上角显示, 如果去掉后面两行代码,你会发现,只是展示了1/4

代码-2

this.petSprite.pivot.set(102,88)
//this.petSprite.position.x = 102
//this.petSprite.position.y = 88

紧接着咱们对在做一次变更,把anchor 也引入进来,你会发现效果和代码-2是一样的

代码-3

this.petSprite.pivot.set(102,88)
this.petSprite.position.x = 102
this.petSprite.position.y = 88
this.petSprite.anchor.set(0.5)

为什么呢?前面说过ancho 不改定位点本身位置,只改变纹理位置, 继续在看一种情况,我们设置anchor 0.5 之后和代码-1 效果也是一样的

代码-4

this.petSprite.anchor.set(0.5)

还记得上面说过么,anchor是纹理的百分比和点之间对应的关系,为了好理解你可以这么理解,anchor设置的值就是纹理对应的位置和精灵左上角的对应关系,pivot的改变其实transform的形变,这个你看display Object源码里anchor 和pivot 实现就是不一样的