プログラミング初心者がphina.jsでゲームを作る⑤

phina.js
在宅さん
在宅さん

今回は、ゲームっぽくプレイヤーを表示させて移動させよう。

今回は、ゲームの主役となるプレイヤーを表示させて移動させてみます。

マップの作成

まず、プレイヤーが移動しやすいように簡易的なマップを作成しました。
といっても線を描画させただけです。

var SCREEN_HEIGHT;
var SCREEN_WIDTH;
var height_span = 50;
var width_span = 20;
var length = 50;
var max_index=12;
var bottom_line=650;
phina.define("MainScene", {
  superClass: 'DisplayScene',
  init: function() {
    this.superInit({
      backgroundColor:'hsl(200, 80%, 64%)',
    });
    SCREEN_WIDTH=this.gridX.width;
    SCREEN_HEIGHT=this.gridY.width;
    field().addChildTo(this).setPosition(this.gridX.center(), this.gridY.center());
  },
});
phina.define("field", {
  superClass: 'PlainElement',
  init: function() {
    this.superInit();
    this.canvas.setSize(SCREEN_WIDTH,SCREEN_HEIGHT);
    this.canvas.context.strokeStyle = 'black';
    this.canvas.context.lineWidth = 2;
    for ( let i = 0; i < max_index+1; i++){
      this.canvas.drawLine(width_span+(length*i), height_span,width_span+(length*i),bottom_line);
      this.canvas.drawLine(width_span,height_span+(length*i),SCREEN_WIDTH-width_span,height_span+(length*i));
    }
 },
});

こんな感じで作成できます。
囲碁版みたいですが、とりあえず簡易的なものが出来ました。
プレイヤーは、このマスの中を移動させます。移動させるときはindexの値で、次のマス目の位置を計算をするクラス(calx,caly)を作っておきます。これを作っておけば、indexの値を増減させるだけで移動させることができます。

calx:function(index_x){
var x = (width_span+length/2)+length(index_x);
return x; 
}, 
caly:function(index_y){ 
var y = (height_span+length/2)+length(index_y);
return y;
},

プレイヤーの配置

次はプレイヤーを配置したいと思います。
画像は、ぴぽや倉庫様の画像をお借りいたしました。
ぴぽや倉庫様:https://pipoya.net/sozai/assets/charachip/character-chip-1/


ダウンロードしたキャラチップの中の、「pipo-charachip001.png」をコピーします。

次に作成しているゲームのフォルダに「sozai」フォルダを作成し、先ほどコピーした「pipo-charachip001.png」を「sozai」フォルダにコピーします。そして、「pipo-charachip001.png」を「player.png」に名前変更します。

次にこの素材をphinaで読み込みます。
まずASSETSに画像情報を書きます。今回は歩くアニメーション付きの画像を配置したいのでアニメーション情報を記載します。

var ASSETS = {
  image: {
    'player': './sozai/player.png',
  },
  spritesheet: {
    "player_ss":{
      "frame": {//画像情報
        "width": 32,
        "height": 32,
        "cols": 3,
        "rows": 4,
      },
      "animations" : {
        "walk": {
          "frames": [0,1,2],
          "next": "walk",
          "frequency": 6,
        },
      }
    }
  },
};
・・・略・・・
phina.main(function() {
  // アプリケーション生成
  var app = GameApp({
        assets: ASSETS,
        scene:DefaultManager(),
  });
  // アプリケーション実行
  app.run();
});

これで読み込むコードが記載できました。今度は、読み込んだ画像を実際に配置させてみたいと思います。画像を使いたいSceneで、Sptiteクラスを継承したplayerクラスを作成します。index_xとindex_yは初期位置を指定するために入れております。

phina.define("player", {
  superClass: 'Sprite',
  init: function(index_x,index_y) {
  this.superInit('player', PLAYER_SIZE_X, PLAYER_SIZE_Y);
  var ani = FrameAnimation('player_ss');
  ani.attachTo(this).gotoAndPlay('walk');

最後に配置したプレイヤーを、動かしてみます。今回はキーボードの矢印方向に1マスずつ進むプログラムしようと思います。playerクラスのupdateにキーボードの入力を受けたら1マス進む処理をさせます。同時押しをさせない処理を考えてたら長いコードとなってしまいました。もう少し簡略化できるかもしれないです。

  update:function(app){
    var key = app.keyboard;
    var self = this;
    if(this.flagg==0){
      if (key.getKeyDown('left')&& key.getKeyAngle()==180) {
        this.flagg=1;
        this.leftflagg=1;
        self.lef();
      }
      if (key.getKeyDown('right')&& key.getKeyAngle()==0) {
        this.flagg=1;
        this.rightflagg=1;
        self.rig();
      }
      if (key.getKeyDown('up')&& key.getKeyAngle()==90) {
        this.flagg=1;
        this.upflagg=1;
        self.up();
      }
      if (key.getKeyDown('down')&& key.getKeyAngle()==270) {
        this.flagg=1;
        this.downflagg=1;
        self.down();
      }
    }
    if(this.flagg==1){
      if (key.getKeyUp('left')&&this.leftflagg==1) {
        this.flagg=0;
        this.leftflagg=0;
      }
      if (key.getKeyUp('right')&&this.rightflagg==1) {
        this.flagg=0;
        this.rightflagg=0;
       }
      if (key.getKeyUp('up')&&this.upflagg==1) {
        this.flagg=0;
        this.upflagg=0;
      }
      if (key.getKeyUp('down')&&this.downflagg==1) {
        this.flagg=0;
        this.downflagg=0;
      }
    }
    if(this.x>this.calx(max_index-1)){
      this.x=this.calx(max_index-1);
      this.index_x=max_index-1;
    }
    if(this.x<this.calx(0)){
      this.x=this.calx(0);
      this.index_x=0;
    }
    if(this.y>this.caly(max_index-1)){
      this.y=this.caly(max_index-1);
      this.index_y=max_index-1;
    }
    if(this.y<this.caly(0)){
      this.y=this.caly(0);
      this.index_y=0;
    }
  },
  up:function(){
    this.index_y-=1;
    this.y=this.caly(this.index_y);
  },
  down:function(){
    this.index_y+=1;
    this.y=this.caly(this.index_y);
  },
  lef:function(){
    this.index_x-=1;
    this.x=this.calx(this.index_x);
  },
  rig:function(){
    this.index_x+=1;
    this.x=this.calx(this.index_x);
  },
  calx:function(index_x){
  var x = (width_span+length/2)+length(index_x);
  return x; 
  }, 
  caly:function(index_y){ 
  var y = (height_span+length/2)+length(index_y);
  return y;
  },

これでキーボードの方向キーでプレイヤーが移動できるようになりました。

以下全コードです。

// phina.js をグローバル領域に展開
phina.globalize();
var ASSETS = {
  image: {
    'player': './sozai/player.png',
  },
  spritesheet: {
    "player_ss":{
      "frame": {//画像情報
        "width": 32,
        "height": 32,
        "cols": 3,
        "rows": 4,
      },
      "animations" : {
        "walk": {
          "frames": [0,1,2],
          "next": "walk",
          "frequency": 6,
        },
      }
    }
  },
};

var PLAYER_SIZE_X     = 20;
var PLAYER_SIZE_Y    = 32;
var SPEED =10;
var SCREEN_HEIGHT;
var SCREEN_WIDTH;
var height_span = 50;
var width_span = 20;
var length = 50;
var max_index=12;

phina.define("DefaultManager", {
  superClass: "ManagerScene",
  init: function() {
    this.superInit({
      scenes:[
          {
            className: 'TitleScene',
            label: 'title',
            nextLabel: 'main',
          },
          {
            className: 'MainScene',
            label: 'main',
            nextLabel: 'pause',
          },
          {
            className: 'ResultScene',
            label: 'result',
            nextLabel: 'title',
          },
      ],
    });
  },
});

phina.define("CountManager", {
  superClass: "ManagerScene",
  init: function() {
    this.superInit({
      startLabel:'Easy',
      scenes: [
        {
          className: 'EasyScene',
          label: 'Easy',
          nextLabel: 'pause',
        },
        {
          className: 'PauseScene',
          label: 'pause',
        },
      ]
    });
  },
  onfinish:function(){
  this.exit();
  }
});
phina.define('TitleScene', {
  superClass: 'DisplayScene',
 init: function(params) {
   this.superInit(params);

   params = ({}).$safe(params, TitleScene.defaults);
   this.backgroundColor = params.backgroundColor;

   this.fromJSON({
     children: {
       titleLabel: {
         className: 'phina.display.Label',
         arguments: {
           text: 'プログラミング初心者の勉強道',
           fill: params.fontColor,
           stroke: false,
           fontSize: 32,
         },
         x: this.gridX.center(),
         y: this.gridY.span(4),
       }
     }
   });

   if (params.exitType === 'touch') {
     this.fromJSON({
       children: {
         touchLabel: {
           className: 'phina.display.Label',
           arguments: {
             text: "TOUCH START",
             fill: params.fontColor,
             stroke: false,
             fontSize: 32,
           },
           x: this.gridX.center(),
           y: this.gridY.span(12),
         },
       },
     });


   }
 },
onpointend:function(){
  this.exit();
},
 _static: {
   defaults: {
     title: 'phina.js games',
     message: '',
     width: 640,
     height: 960,
     fontColor: 'black',
     backgroundColor: 'hsl(200, 80%, 64%)',
     backgroundImage: '',
     exitType: 'touch',
   },
 },
});
phina.define('MainScene', {
  superClass: 'DisplayScene',
  init: function() {
    this.superInit();
    this.backgroundColor = '#444';
    this.label = Label('どちらか押してね').addChildTo(this);
    this.label.x = this.gridX.center();
    this.label.y = this.gridY.center();
    this.label.fill = 'white';
    var main_scene=this;

    var button_count = Button({
          x: main_scene.gridX.center(-4),
          y: main_scene.gridY.span(12),
          width: 150,
          height: 100,
          text: "ゲーム起動",
          fontSize: 18,
          fontColor: 'black',
    }).addChildTo(this)
    .onpush=function(){
          main_scene.app.replaceScene(CountManager());
    };

    var button_finish = Button({
            x: main_scene.gridX.center(4),
            y: main_scene.gridY.span(12),
            width: 150,
            height: 100,
            text: "終わり",
            fontSize: 23,
            fontColor: 'black',
    }).addChildTo(this)
    .onpush=function(){
            main_scene.exit();
    };
  },
});

phina.define("EasyScene", {
  superClass: 'DisplayScene',
  init: function() {
    this.superInit({
      backgroundColor:'hsl(200, 80%, 64%)',
    });
    player(5,11).addChildTo(this);
    SCREEN_WIDTH=this.gridX.width;
    SCREEN_HEIGHT=this.gridY.width;
    field().addChildTo(this).setPosition(this.gridX.center(), this.gridY.center());
  },
});

phina.define("player", {
  superClass: 'Sprite',
  init: function(index_x,index_y) {
    this.superInit('player', PLAYER_SIZE_X, PLAYER_SIZE_Y);
    var ani = FrameAnimation('player_ss');
    ani.attachTo(this).gotoAndPlay('walk');
    this.index_x = index_x;
    this.index_y = index_y;
    this.flagg=0;
    this.leftflagg=0;
    this.rightflagg=0;
    this.upflagg=0;
    this.downflagg=0;
    this.x=this.calx(this.index_x);
    this.y=this.caly(this.index_y);
  },
  update:function(app){
    var key = app.keyboard;
    var self = this;
    if(this.flagg==0){
      if (key.getKeyDown('left')&& key.getKeyAngle()==180) {
        this.flagg=1;
        this.leftflagg=1;
        self.lef();
      }
      if (key.getKeyDown('right')&& key.getKeyAngle()==0) {
        this.flagg=1;
        this.rightflagg=1;
        self.rig();
      }
      if (key.getKeyDown('up')&& key.getKeyAngle()==90) {
        this.flagg=1;
        this.upflagg=1;
        self.up();
      }
      if (key.getKeyDown('down')&& key.getKeyAngle()==270) {
        this.flagg=1;
        this.downflagg=1;
        self.down();
      }
    }
    if(this.flagg==1){
      if (key.getKeyUp('left')&&this.leftflagg==1) {
        this.flagg=0;
        this.leftflagg=0;
      }
      if (key.getKeyUp('right')&&this.rightflagg==1) {
        this.flagg=0;
        this.rightflagg=0;
       }
      if (key.getKeyUp('up')&&this.upflagg==1) {
        this.flagg=0;
        this.upflagg=0;
      }
      if (key.getKeyUp('down')&&this.downflagg==1) {
        this.flagg=0;
        this.downflagg=0;
      }
    }
    if(this.x>this.calx(max_index-1)){
      this.x=this.calx(max_index-1);
      this.index_x=max_index-1;
    }
    if(this.x<this.calx(0)){
      this.x=this.calx(0);
      this.index_x=0;
    }
    if(this.y>this.caly(max_index-1)){
      this.y=this.caly(max_index-1);
      this.index_y=max_index-1;
    }
    if(this.y<this.caly(0)){
      this.y=this.caly(0);
      this.index_y=0;
    }
  },
  up:function(){
    this.index_y-=1;
    this.y=this.caly(this.index_y);
  },
  down:function(){
    this.index_y+=1;
    this.y=this.caly(this.index_y);
  },
  lef:function(){
    this.index_x-=1;
    this.x=this.calx(this.index_x);
  },
  rig:function(){
    this.index_x+=1;
    this.x=this.calx(this.index_x);
  },
  calx:function(index_x){
    var x = 45+50*(index_x);
    return x;
  },
  caly:function(index_y){
    var y = 75+50*(index_y);
    return y;
  },
});

phina.define("field", {
  superClass: 'PlainElement',
  init: function() {
    this.superInit();
    this.canvas.setSize(SCREEN_WIDTH,SCREEN_HEIGHT);
    this.canvas.context.strokeStyle = 'black';
    this.canvas.context.lineWidth = 2;
    for ( let i = 0; i < max_index+1; i++){
      this.canvas.drawLine(width_span+(length*i), height_span,width_span+(length*i),700-50);
      this.canvas.drawLine(width_span,height_span+(length*i),SCREEN_WIDTH-width_span,length+(length*i));
    }

 },
});

phina.main(function() {
  // アプリケーション生成
  var app = GameApp({
        assets: ASSETS,
        scene:DefaultManager(),
  });
  // アプリケーション実行
  app.run();
});
Forked: 2020/05/27 21:18:06 - Runstant | Runstant
思いたったらすぐ開発. プログラミングに革命を...

※runstantでは、画像をphinaのキャラのtomopikoさんに入れ替えております。

まとめ

今回はマップの作成、画像の読み込み、画像の呼び出し、キャラの移動などを行いました。方向キーの同時押しの回避や、移動する距離の計算など考えて作る場面があり、とても作りがいがありました。
しかしまだ移動が瞬間移動になってしまっています。
次回は、移動を滑らかに歩くようにしたり、障害物を配置したりできたらいいなと思います。

タイトルとURLをコピーしました