我如何使用 Phaser3 进行任务?

How can I do a quest with Phaser3?

我在 Phaser3 中搜索但没有找到任何可做的任务。我想做一个类似(例子)的任务。

去和 'Jerry' 谈谈。

拿起一把剑交给'Jerry'。

当你完成对话后,它会打开一扇门或其他东西,但我需要知道如何检查他是否与 PNJ 对话以及如何简单地设置任务

我找到了 rexquestplugin,但我不知道如何使用它,也没有网站或其他网站谈论 RexQuest

我现在的代码,如果你需要了解游戏的一些信息:

class SceneStart extends Phaser.Scene {

  constructor() {
    super({key: 'sceneStart'})
  }

  //Chargement des images
  preload() {
    this.load.plugin('rexquestplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexquestplugin.min.js', true);

    this.load.image("player", "javascript/assets/player.png");
    this.load.image("run1", "javascript/assets/run1.png");
    this.load.image("run2", "javascript/assets/run2.png");
    this.load.image("playerLeftRun1", "javascript/assets/playerLeftRun1.png");
    this.load.image("playerLeftRun2", "javascript/assets/playerLeftRun2.png");
    this.load.image("door", "javascript/assets/doors.png");
    this.load.image("wall", "javascript/assets/walls.png");
    /** 
    this.load.image("fireStart1", "javascript/assets/fireStart1.png");
    this.load.image("fireStart2", "javascript/assets/fireStart2.png");
    this.load.image("fireStart3", "javascript/assets/fireStart3.png");
    */
  }

  create() {
    cursor = this.input.keyboard.createCursorKeys(); //touches des fleches
    platforms = this.physics.add.staticGroup();

    //Les animations 
    this.anims.create({
      key : "playerWalkUp",
      frames : [
        {key : "run1"},
        {key : "run2"}],
      frameRate : 7,
      repeat : 0
    })

    this.anims.create({
      key : "playerWalkLeft",
      frames : [
        {key : "playerLeftRun1"},
        {key : "playerLeftRun2"}],
      frameRate : 7,
      repeat : 0
    })

    /**
     * this.anims.create({
     * key : "fireMouvement",
     * frames : [
     * {key : "fireStart1"},
     * {key : "fireStart2"},
     * {key : "fireStart3"}],
     * framerate : 7,
     * repeat : -1
     * })
     */

    player = this.physics.add.sprite((w / 2), h, "player"); //joueur
    player.setScale(1, 1);
    player.body.setSize(30, 35);
    
    wall1 = this.add.sprite(200, 146, "wall");
    wall1.setScale(0.3);

    wall2 = this.add.sprite(200, 445, "wall");
    wall2.setFlip(false, true);
    wall2.setScale(0.3);

    wall3 = this.add.sprite((w - 200), 146, "wall");
    wall3.setScale(0.3);

    wall4 = this.add.sprite((w - 200), 445, "wall");
    wall4.setFlip(false, true);
    wall4.setScale(0.3);

    doorStart = this.physics.add.staticSprite((w / 2), 28, "door"); //Porte principale
    doorStart.setScale(0.3);
    doorStart.body.setSize(300, 55);
    doorStart.body.setOffset(-56, 472);
    doorStart.rotation += -20.42;

    platforms.add(wall1);
    platforms.add(wall2);
    platforms.add(wall3);
    platforms.add(wall4);
    this.physics.add.collider(platforms, player); //collision
    player.setCollideWorldBounds(true); //collision avec la bordure
    
    //Fonction de collision qui éxecute le code dedans quand la fonctions est appelé
    function collision() {
      this.scene.start("labyrintheStart");
    }
    this.physics.add.collider(player, doorStart, collision, undefined, this); 
  }

  update() {

    // Tous les mouvement sont controler par ce code
  
    if (cursor.left.isDown){
      player.setVelocityX(-200); //vitesse de deplacements
      player.anims.play("playerWalkLeft", true); //animations du personnage
      player.setFlip(false, false); //oriantation de l'image
    } else if (cursor.right.isDown){
      player.setVelocityX(200);
      player.anims.play("playerWalkLeft", true);
      player.setFlip(true, false);
    } else if (cursor.up.isDown){
      player.setVelocityY(-200);
      player.anims.play("playerWalkUp", true);
      player.setFlip(false, false);
    } else if (cursor.down.isDown){
      player.setVelocityY(200);
      player.anims.play("playerWalkUp", true);
      player.setFlip(false, true);
    } else {
      player.setVelocity(0);
      player.setTexture("player");
    }

    if ((cursor.left.isDown && cursor.up.isDown) || (cursor.left.isDown && cursor.right.isDown) || (cursor.left.isDown && cursor.down.isDown) || (cursor.right.isDown && cursor.up.isDown) || (cursor.right.isDown && cursor.down.isDown)){
      player.setVelocity(0);
      player.setTexture("player");
    }

  //--------
  }
}

class LabyrintheStart extends Phaser.Scene {

  constructor() {
   super({key: 'labyrintheStart'});
  }
  preload() {
    this.load.plugin('rexquestplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexquestplugin.min.js', true);

    this.load.image("player", "javascript/assets/player.png");
    this.load.image("run1", "javascript/assets/run1.png");
    this.load.image("run2", "javascript/assets/run2.png");
    this.load.image("playerLeftRun1", "javascript/assets/playerLeftRun1.png");
    this.load.image("playerLeftRun2", "javascript/assets/playerLeftRun2.png");
    this.load.image("wall", "javascript/assets/walls.png"); 
    this.load.image("door", "javascript/assets/doors.png");
  }

  create() {
    cursor = this.input.keyboard.createCursorKeys();
    platforms = this.physics.add.staticGroup();


    this.anims.create({
      key : "playerWalkUp",
      frames : [
        {key : "run1"},
        {key : "run2"}],
      frameRate : 7,
      repeat : 0
    })
    
    this.anims.create({
      key : "playerWalkLeft",
      frames : [
        {key : "playerLeftRun1"},
        {key : "playerLeftRun2"}],
      frameRate : 7,
      repeat : 0
    })

    player = this.physics.add.sprite(34, h, "player");
    player.setScale(1, 1);
    player.body.setSize(30, 35);
        
    wall1 = this.add.sprite(9, h - 126, "wall");
    wall1.setScale(0.05);

    doorDroite = this.physics.add.staticSprite(w - 35, (h / 2) - 20, "door"); 
    doorDroite.setSize(18, 80);
    doorDroite.setScale(0.08);

    platforms.add(wall1);
    this.physics.add.collider(platforms, player);
    player.setCollideWorldBounds(true);

    function collisionDroite() {
      this.scene.start("labyrintheDeux");
    }
    
    this.physics.add.collider(player, doorDroite, collisionDroite, undefined, this);
  }

  update() {

      if (cursor.left.isDown){
        player.setVelocityX(-200);
        player.anims.play("playerWalkLeft", true);
        player.setFlip(false, false);
      } else if (cursor.right.isDown){
        player.setVelocityX(200);
        player.anims.play("playerWalkLeft", true);
        player.setFlip(true, false);
      } else if (cursor.up.isDown){
        player.setVelocityY(-200);
        player.anims.play("playerWalkUp", true);
        player.setFlip(false, false);
      } else if (cursor.down.isDown){
        player.setVelocityY(200);
        player.anims.play("playerWalkUp", true);
        player.setFlip(false, true);
      } else {
        player.setVelocity(0);
        player.setTexture("player");
      }
  
      if ((cursor.left.isDown && cursor.up.isDown) || (cursor.left.isDown && cursor.right.isDown) || (cursor.left.isDown && cursor.down.isDown) || (cursor.right.isDown && cursor.up.isDown) || (cursor.right.isDown && cursor.down.isDown)){
        player.setVelocity(0);
        player.setTexture("player");
      }
  }
}
/**
class LabyrintheDeux extends Phaser.Scene {

  constructor() {
    super({key: "labyrintheDeux"});
  }

  preload() {
    this.load.plugin('rexquestplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexquestplugin.min.js', true);

    this.load.image("player", "javascript/assets/player.png");
    this.load.image("run1", "javascript/assets/run1.png");
    this.load.image("run2", "javascript/assets/run2.png");
    this.load.image("playerLeftRun1", "javascript/assets/playerLeftRun1.png");
    this.load.image("playerLeftRun2", "javascript/assets/playerLeftRun2.png");
    this.load.image("wall", "javascript/assets/wall.png"); 
    this.load.image("doorStart", "javascript/assets/door.png");
  }

  create() {
    cursor = this.input.keyboard.createCursorKeys();
    platforms = this.physics.add.staticGroup();

    this.anims.create({
      key : "playerWalkUp",
      frames : [
        {key : "run1"},
        {key : "run2"}],
      frameRate : 7,
      repeat : 0
    })

    this.anims.create({
      key : "playerWalkLeft",
      frames : [
        {key : "playerLeftRun1"},
        {key : "playerLeftRun2"}],
      frameRate : 7,
      repeat : 0
    })

    player = this.physics.add.sprite((w - w) + 70, h, "player");
    player.setScale(1, 1);
    player.body.setSize(30, 35);
    
    wall = this.add.sprite((w - w) + 6, h - 126, "wall");

    door = this.physics.add.staticSprite((w / 2) - 20, 30, "door");
    door.rotation += 20.42;
    door.setSize(50, 10);

    platforms.add(wall);
    this.physics.add.collider(platforms, player);
    player.setCollideWorldBounds(true);
    
    function collision() {
      this.scene.start("");
    }
    
    this.physics.add.collider(player, door, collision, undefined, this);
  }

  update() {
  
      if (cursor.left.isDown){
        player.setVelocityX(-200);
        player.anims.play("playerWalkLeft", true);
        player.setFlip(false, false);
      } else if (cursor.right.isDown){
        player.setVelocityX(200);
        player.anims.play("playerWalkLeft", true);
        player.setFlip(true, false);
      } else if (cursor.up.isDown){
        player.setVelocityY(-200);
        player.anims.play("playerWalkUp", true);
        player.setFlip(false, false);
      } else if (cursor.down.isDown){
        player.setVelocityY(200);
        player.anims.play("playerWalkUp", true);
        player.setFlip(false, true);
      } else {
        player.setVelocity(0);
        player.setTexture("player");
      }
  
      if ((cursor.left.isDown && cursor.up.isDown) || (cursor.left.isDown && cursor.right.isDown) || (cursor.left.isDown && cursor.down.isDown) || (cursor.right.isDown && cursor.up.isDown) || (cursor.right.isDown && cursor.down.isDown)){
        player.setVelocity(0);
        player.setTexture("player");
      }
  }

}
*/

var config = {
  type: Phaser.AUTO,
  width: window.innerWidth - 20,
  height: window.innerHeight - 100,
  backgroundColor: "#FFFFFF", //#FFFFFF
  physics: {
    default : "arcade",
    arcade : {
      debug : false,
    }
  },
  scene: [SceneStart, LabyrintheStart /**, LabyrintheDeux*/]
};

let game = new Phaser.Game(config);

var h = window.innerHeight;
var w = window.innerWidth;

var platforms;
var cursor;
var player;

var doorGauche;
var doorDroite;
var doorUp;
var doorStart;

var wall1;
var wall2;
var wall3;
var wall4;


// A rajouter plus tard
/**
 * 
    function collisionUp() {
      this.scene.start("labyrintheTrois");
    }
 * 
 * this.physics.add.collider(player, doorGauche, collisionUp, undefined, this); 
 * 
 * doorUp = this.physics.add.staticSprite((w / 2) - 20, 8, "door");
 * doorUp.setScale(0.08);
 * doorUp.setSize(80, 18);
 * doorUp.setOffset(55, 491);
 * doorUp.rotation += -20.42;
*/

我从未真正使用过 rexquestplugin,但听起来很有趣,所以我检查了一下。

Here the Documentation(不详细,但可能有助于清理下面的代码)
The Demo 文档站点上帮助我更好地理解了用法,但不是很清楚。

所以我写了一个小的演示应用程序,来回答这个问题“我将如何使用插件解决你的问题?”
(btw.: 你可以执行下面的代码片段)

// This is the Quest csv File as Array (just for the demo)
    // load the csv from a file, if it is bigger
    var questString = [
        ['type', 'key', 'next', 'end'],    // Line 1: HEADER
        ['q', 'Go talk to Jerry', '', ''], // Line 2: Quest1 Part1
        ['', '', 'Get Sword', ''],         // Line 3:   First Option for Quest1 Part1 (there could be several option per Quest)
        ['q', 'Get Sword', '', ''],        // Line 4: Quest1 Part2
        ['', '', 'Give it to Jerry', ''],  // Line 5:   First Option for Quest1 Part2
        ['q', 'Give it to Jerry', '', ''], // Line 6: Quest1 Part3
        ['','', 'DONE', '', ''],           // Line 7:   First Option for Quest1 Part3
        ['q', 'DONE', '', '1'],            // Line 8: QUEST End
    ].map(x => x.join(',')).join('\n');


    // This is the DemoScene
    // Just very simple way to define a Scene, without Class (just for the demo)
    var DemoScene = {
        preload() {
            // Load Plugin
            this.load.plugin('rexquestplugin', 'https://raw.githubusercontent.com/rexrainbow/phaser3-rex-notes/master/dist/rexquestplugin.min.js', true);
        },
        extend: {
            // Collision Callback for Jerry
            handleMeeting(player, jerry){
                if(player._currentQuest ){
                    // Get all options (in this case only one)
                    console.info(player._currentQuest.currentQuest)
                    let options = player._currentQuest.currentQuest.options;
                    if(options[0].next == 'Get Sword'){
                        this.sword.visible = true;
                        player._currentQuest.manager.getNextQuestion(options[0].next);
                    } else if(options[0].next == 'DONE'){
                        this.door.fillColor = 0x00ff00;
                        player._currentQuest.manager.getNextQuestion(options[0].next);
                    }
                }
            },
            // Collision Callback for Sword
            handleSword(player, sword){
                if(player._currentQuest ){
                    sword.destroy();
                    // Get all options (in this case only one)
                    let options = player._currentQuest.currentQuest.options;
                    if(options[0].next == 'Give it to Jerry'){
                        sword.visible = true;
                        player._currentQuest.manager.getNextQuestion(options[0].next);
                    }
                }
            },
            // Collision Check Callback for Sword
            checkSwordStatus(player, sword){
                // if the sword is visible collide
                return sword.visible;
            },
            // Collision Check Callback for door
            checkDoorStatus(player, door){
                // if the door is red collide
                return door.fillColor == 0xff0000;
            }
        },
        create() {
            // Player
            this.player = this.add.circle(30, 30, 10, 0xffffff).setOrigin(.5);
            this.cursor = this.input.keyboard.createCursorKeys();
            this.physics.add.existing(this.player);
            this.player.body.setCollideWorldBounds(true);
            this.player.body.setCircle(10);
            this.player.setDepth(2);

            // Jerry
            this.jerry = this.add.circle(160, 30, 10, 0x0000ff).setOrigin(.5);
            this.physics.add.existing(this.jerry, true);
            this.jerry.body.setCircle(10);

            // Sword
            this.sword = this.add.isotriangle(50, 100, 20, 40, false, 0xffe31f, 0xf2a022, 0xf8d80b).setOrigin(.5);
            this.physics.add.existing(this.sword, true);
            this.sword.visible = false;

            // Door
            this.door = this.add.rectangle(190, 0, 20, 200, 0xff0000).setOrigin(0);
            this.physics.add.existing(this.door, true);

            // colliders
            this.physics.add.collider(this.player, this.jerry, this.handleMeeting, undefined, this);
            this.physics.add.collider(this.player, this.sword, this.handleSword, this.checkSwordStatus, this);
            this.physics.add.collider(this.player, this.door, undefined, this.checkDoorStatus, this );

            // Quest List
            this.print = this.add.text(380, 180, '', { fontSize: '12px', align: 'right' }).setOrigin(1);

            // QUEST SETUP
            this.plugins.get('rexquestplugin').add({
                questions: questString,
                quest: true
            })
            // EVENT executes on new Quest/Question
            .on('quest', function (currentQuest, manager, quest) {
                // QUEST has ended
                if (currentQuest.end === 1) {
                    manager.setData('endAt', currentQuest.key);
                    manager.emit('complete', manager, quest);
                } else {
                    // NEXT Step in the Quest
                    if(this.player._currentQuest){ 
                        this.print.text = this.print.text + 'done\n';
                    }
                    this.print.text += `${currentQuest.key}...`;
                    this.player._currentQuest = { currentQuest, manager}
                }
            }, this)
            // Is emited from `on('quest', ... )`
            .on('complete', function (manager, quest) {
                delete this.player._currentQuest;
                this.print.text = this.print.text + 'done\n';
                this.print.text += `\nDoor is unlocked!`;
            }, this)
            // Get First Quest
            .getNextQuestion();
        },
        update() {
            // only player movement
            let body = this.player.body;
            if (this.cursor.left.isDown) {
                body.setVelocityX(-200); 
            } else if (this.cursor.right.isDown) {
                body.setVelocityX(200);
            } else if (this.cursor.up.isDown) {
                body.setVelocityY(-200);
            } else if (this.cursor.down.isDown) {
                body.setVelocityY(200);
            } else {
                body.setVelocity(0);
            }
        }
    }

    var config = {
        type: Phaser.AUTO,
        width: 400,
        height: 200,
        scene: [DemoScene],
        physics: {
            default : "arcade"
        }
    };

    var game = new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"></script>

播放器 - 使用光标键移动
blue: 是 Jerry
白:是玩家
黄色:是剑
red/gree: closed/open门

One part that was not really clear to me, was the csv for the quest, here is my interpretation, of the columns:

  • 'type' column
    • if set to 'q' it is a quest/question
    • if empty '' it is a option
  • 'key' column
    • if is set it is the name of the quest/question
    • if empty '' it is a option
  • 'next' column
    • if it is set this is the name of the next quest/question
    • if empty '' it is a quest
  • 'end' column
    • if is is set it ends the quest/question (in this example it has to be set to 1)
    • if empty '', it is not the last quest/question

更新:
根据您正在制作的任务/游戏的大小,框架 ink https://www.inklestudios.com/ink/ 可能是一个选项。它很容易编写,有令人难以置信的文档,但内容非常广泛。