Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities

This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money

introduce

In the last issue, we talked about how to compress and optimize the font resources and picture resources of the game. In this issue, we will specifically realize the case of inuyasha’s broken iron teeth. As shown in the figure:

After the last operation, we have the font and image resources, this will learn how to use these resources in pixi. Js to complete the dialogue scenes of production, we will roughly from installation, infrastructure, resource loading, resource mapping, typewriter, switch the picture and other aspects to achieve it, will soon begin to cough up ~

Implement article

1. Install dependencies

We will use Vite to build this project.

yarn add -D vite
Copy the code

Then we need to install pixi.js again

yarn add pixi.js
Copy the code

2. Infrastructure

We will create a new index.html file and write the basic style in it. Of course, we will import js with module mode because we will use pixi.js which we just downloaded

<body>
    <div id="app"></div>
    <script type="module" src="./app.js"></script>
</body>
Copy the code
* {
    padding: 0;
    margin: 0;
}
html.body {
    width: 100%;
    height: 100vh;
    position: relative;
    overflow: hidden;
    background-color: # 000;
    display: flex;
    align-items: center;
    justify-content: center;
}

#app {
    width: 800px;
    height: 600px;
    background-color: white;
}
Copy the code

The main idea here is to size the #app of the main container and center it.

Next, we need to complete the basic structure of app.js, which is our main logic.

/*app.js*/
import * as PIXI from "pixi.js"
import data from "./data"

class Application {
  constructor() {
    this.app = null;                   / / PIXI. Application examples
    this.stage = null;                 // Game stage
    this.talkStage = null;             // Dialogue scene container
    this.w = 800;                      // Width of the scene
    this.h = 600;                      // Height of the scene
    this.stageIndex = 0;               // The current subscript of the scene
    this.stageTextures = {};           // Scene resources
    this.charTimer = [];             // Set of typewriters timer
    this.init();
  }
  init() {
    / / initialization
    let el = document.getElementById("app")
    this.app = new PIXI.Application({
      resizeTo: el,
      backgroundColor: 0xFFFFFFF
    });
    this.w = this.app.view.width;
    this.h = this.app.view.height;
    this.stage = this.app.stage;
    this.talkStage = new PIXI.Container();
    this.stage.addChild(this.talkStage);
    el.appendChild(this.app.view);
    this.load();
    this.bindEvent();
  }
  bindEvent() {
    // Bind events
  }
  changeStageIndex() {
    // Switch scenes
  }
  render() {
    / / rendering
    this.drawBgImg();
    this.drawTalk();
  }
  load() {
    / / load
  }
  drawBgImg() {
    // Draw the background image
  }
  drawTalk() {
    // Draw dialogue
    this.drawContent();
  }
  drawContent() {
     // Draw dialogue}}window.onload = new Application();
Copy the code

Here are the main things we do in master logic:

  • Pixi.js and the script data data.js used were introduced. Pixi.js will be my engine throughout. And data is the dialog script data that we wrote ourselves. As shown in the picture below, each scene in our dialogue and the characters and content of the dialogue are written here:

  • Second, we perform the initialization event to instantiate pixi.application, where in order to directly fill the #app container, we can use the resizeTo(EL) method to directly fill it, instead of setting it by width and height. Finally, don’t forget to fill the canvas into the #app container with el. AppendChild (app.view).

  • App. stage is the stage of the game where the content to be rendered can be placed, but we expect each scene to have a stage for easy control, so the talkStage instantiated by Pixi. Container is added to the main scene app.stage as the main Container for the dialogue scene. The content and pictures of all subsequent dialogue scenes will be appended to the talkStage.

3. Load resources

We have finished the preparation of resources before. In load, we can use Promise+ Loader to directly save the resource data we use after the initial loading, and then we can render.

init(){
    // ...
    this.load().then(res= > {
      this.stageTextures = res.resources.stage.textures;
      this.render();
    })
    // ...
}
load() {
    return new Promise((resolve, reject) = > {
        this.app.loader
            .add('hk'.'font/hk/font.fnt')
            .add("stage"."assets/stage.json") .load(resolve); })}Copy the code

4. Resource drawing

drawBgImg() {
    const {stageIndex, stageTextures, talkStage} = this;
    let _img = data[stageIndex].img;
    if(! _img)return;
    let img = stageTextures[_img + ".png"];
    let bgImg = new PIXI.Sprite(img);
    bgImg.anchor.set(0.0);
    bgImg.position.set(0, -5)
    bgImg.scale.set(1.23)
    talkStage.addChild(bgImg);
}
drawTalk() {
    const {stageIndex, stageTextures, stage} = this;
    let _talk = data[stageIndex] ? data[stageIndex].talk : [];
    if(! _talk || ! _talk.length)return;
    this.drawContent(_talk);
}
drawContent(res) {
    const contentContainer = new PIXI.Container();
    contentContainer.position.set(30.this.h - 210);
    contentContainer.width = this.w - 120
    this.talkStage.addChild(contentContainer);

    const box = new PIXI.Graphics();
    box.beginFill(0x000000);
    box.drawRect(0.0.this.w - 60.180);
    box.endFill();
    box.alpha = 0.61;
    contentContainer.addChild(box);

    const nameText = new PIXI.BitmapText(res[0].name + ":", {
        fontName: 'Hua Kang Girl font'.fontSize: 32.tint: 0xFFFFFF.letterSpacing: 5.textHeight: 32.align: "left"}); nameText.position.set(30.15)
    contentContainer.addChild(nameText);
    const contentText = new PIXI.BitmapText(res[0].content, {
        fontName: 'Hua Kang Girl font'.fontSize: 32.tint: 0xFFFFFF.letterSpacing: 3.textHeight: 120.textWidth: this.w - 120.align: "left".maxWidth: this.w - 120
    });
    contentText.position.set(30.55)
    this.talkStage.addChild(contentText);
    contentContainer.addChild(contentText);
}
Copy the code

Seemingly a lot, most of them are tedious repetitive business, and positioning, nesting. Just remember two things:

  • Image with pixi. Sprite into the corresponding texture data to achieve rendering
  • BitmapText is drawn with pixi.bitmaptext passing in the text and font to be drawn and some font properties

This completes the first scene interface

5. Typewriter effects

If we play A Lot of Japanese RPGS, we’ll find that a lot of dialogue is done individually with typewriter effects, which is very immersive. We’ll implement it in just a few lines.

drawContent(res) {
    // ...
    const contentText = new PIXI.BitmapText("", {
      fontName: 'Hua Kang Girl font'.fontSize: 32.tint: 0xFFFFFF.letterSpacing: 3.textHeight: 120.textWidth: this.w - 120.align: "left".maxWidth: this.w - 120
    });
    Array.from(res[0].content).forEach((char, i) = >{(function(i) {
        let timer = setTimeout(() = > {
          contentText.text += char;
        }, 200 * (i + 1))
        this.charTimer.push(timer)
      }.bind(this))(i)
    })
    // ...
}
Copy the code

We’re going to modify the drawContent method so that the content is just an empty string, and then we’re going to iterate over the content, adding one word every 200 milliseconds to create a typewriter effect. And we also need to save the timer for each word. If we switch to the next one before finishing the word, we will stop the timer and delete it.

render() {
    if (this.charTimer.length) {
        this.charTimer.forEach(timer= >{
            clearTimeout(timer)
        })
        this.charTimer.length = 0;
    };
    this.talkStage.children.forEach((c, i) = > {
        this.talkStage.removeChildAt(i)
    })
    this.drawBgImg();
    this.drawTalk();
}
Copy the code

We will delete and empty the chartimers one by one, and we will also make sure that when we switch scenes we will empty the elements of the original scene.

6. Switch screens

Finally, the final step, we first bind the click event, then click the screen will be cut to the next image and text.

bindEvent() {
    this.talkStage.interactive = true;
    this.talkStage.buttonMode = true;
    this.talkStage.on("pointerdown".this.changeStageIndex.bind(this));
}
changeStageIndex() {
    if (this.stageIndex >= data.length - 1) return;
    this.stageIndex += 1;
    this.render();
}
Copy the code

We use the PointerDown click event to bind the method of switching scenes, and then add one to the stageIndex index if the current dialog data is not executed, and render the new screen. It’s as simple as that, and if it goes beyond that, we’re giving it a break here, which could actually jump into other logic, such as combat, mini-games, other dialogue, etc., depending on the specific business that the plan produces.

conclusion

This time we completely implemented dialogue scene animations for a simple game, and we could build on that to enrich the experience and functionality, but that’s the core of it. Although it is only a dialogue scene animation, we can realize that the loading and rendering of resources, the change of state, and the interaction of events can not escape a game. On this basis, we can also do animation, such as the typewriter just a few lines of code can enhance the experience of game substitution. Why should we give play to our thinking and create a story of our own