Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

ReactでRPGマップ作成

年末年始のお休みに入り、少し時間が出来ました。
何かReactでアプリを作りたいな?🤔と思い、ちょっとしたRPG作成してみようかな?と思い手を動かしてみました!!

基本的にjavascriptでのRPGアプリ作成についてはこちらのサイトに掲載されていたので、流用させていただきましたが、
React固有の部分がありましたのでこちらを中心に載せておこうと思います!!
https://original-game.com/introduction-to-javascript-character-move-on-map/



■環境

Ubuntu16.04(VirtualBox上)

■完成形

今回作成したアプリはこんな感じになりました。
f:id:Elsammit:20201228221825g:plain

※画像等も
 https://original-game.com/introduction-to-javascript-character-move-on-map/
 より一部利用させていただいております。

■マップ表示

マップ表示のためのコードはこちらになります。

var map = [
    [0, 0, 1, 0, 1, 0, 0, 0 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0],
    [0, 1, 0, 0, 0, 1, 1, 1 ,0 ,1 ,0 ,1 ,1 ,0 ,1 ,1 ,1 ,0 ,1 ,0],
    [0, 0, 1, 1, 0, 0, 0, 1 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,0],
    [1, 0, 1, 0, 1, 1, 0, 0 ,0 ,1 ,1 ,1 ,1 ,1 ,0 ,0 ,1 ,0 ,1 ,0],
    [0, 0, 0, 0, 0, 1, 1, 1 ,0 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,0 ,1 ,1 ,0],
    [0, 1, 1, 1, 0, 0, 0, 0 ,0 ,1 ,0 ,1 ,1 ,1 ,0 ,1 ,0 ,0 ,0 ,0],
    [0, 1, 1, 1, 0, 1, 1, 1 ,1 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,1 ,1 ,1 ,0],
    [0, 0, 0, 1, 0, 0, 0, 0 ,1 ,0 ,0 ,1 ,0 ,1 ,1 ,0 ,0 ,0 ,1 ,0],
    [1, 1, 0, 1, 1, 1, 1, 1 ,1 ,0 ,1 ,1 ,0 ,0 ,1 ,1 ,1 ,0 ,1 ,1],
    [1, 0, 0, 0, 0, 0, 1, 1 ,0 ,0 ,0 ,0 ,1 ,0 ,1 ,1 ,0 ,0 ,1 ,0],
    [1, 0, 1, 1, 1, 0, 0, 0 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,1 ,1 ,0 ,0],
    [1, 0, 1, 0, 1, 1, 1, 0 ,1 ,0 ,1 ,1 ,0 ,1 ,1 ,0 ,0 ,0 ,0 ,1],
    [0, 0, 1, 0, 0, 1, 0, 0 ,1 ,0 ,0 ,1 ,0 ,1 ,0 ,1 ,1 ,1 ,0 ,0],
    [0, 1, 1, 1, 0, 1, 0, 1 ,0 ,0 ,1 ,1 ,0 ,1 ,0 ,1 ,1 ,0 ,1 ,0],
    [0, 0, 0, 1, 0, 1, 0, 0 ,1 ,0 ,1 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0],
    [1, 1, 0, 1, 0, 1, 0, 1 ,1 ,0 ,0 ,1 ,0 ,1 ,1 ,0 ,1 ,1 ,1 ,0],
    [0, 0, 0, 1, 0, 1, 1, 1 ,1 ,1 ,0 ,1 ,0 ,1 ,1 ,0 ,0 ,0 ,1 ,0],
    [0, 1, 1, 1, 0, 1, 0, 0 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,1 ,1 ,0 ,1 ,1],
    [0, 1, 0, 0, 0, 1, 0, 1 ,1 ,1 ,0 ,0 ,1 ,1 ,0 ,1 ,0 ,0 ,0 ,0],
    [0, 0, 0, 1, 0, 0, 0, 1 ,1 ,1 ,1 ,0 ,0 ,0 ,1 ,1 ,1 ,1 ,1 ,0]
];

export default class Snow extends Component  {

    componentDidMount(){
        this.intervalId = setInterval(()=>{
            this.DoCanvas();
        }, 200);
    }

    DoCanvas = () =>{
        var img = new Image();
        img.src = mapImg;

        var usrimg = new Image();
        usrimg.src = UserImg;

        var goalimg = new Image();
        goalimg.src = GoalImg;

        var canvas = document.getElementById('canvas');
        
        canvas.width=640;
        canvas.height=640;
        var ctx = canvas.getContext('2d');

        for (var y = 0; y<map.length;y++){
            for(var x=0; x<map.length;x++){
                if(map[y][x] === 0){
                    ctx.drawImage(img,0,0,32,32,32*x,32*y,32,32);
                }else{
                    ctx.drawImage(img,32,0,32,32,32*x,32*y,32,32);
                }
            }
        }
        ctx.drawImage(usrimg,0,0,32,32,0,0,32,32);
    }

    render () {
        return(
            <div>
                <canvas id="canvas"></canvas>
            </div>
        )
    }
}

少し長いコードですね。。。
ただ、やっていることは単純で、
画面表示時に実行されるcomponentDidMount関数を用いて、

    componentDidMount(){
        this.intervalId = setInterval(()=>{
            this.DoCanvas();
        }, 200);
    }

により、200msで毎にDoCanvas関数をコール。
DoCanvas関数はマップ作成用の関数であり、
canvas上に、背景マップや主人公表示、ゴール位置を

        for (var y = 0; y<map.length;y++){
            for(var x=0; x<map.length;x++){
                if(map[y][x] === 0){
                    ctx.drawImage(img,0,0,32,32,32*x,32*y,32,32);
                }else{
                    ctx.drawImage(img,32,0,32,32,32*x,32*y,32,32);
                }
            }
        }
        ctx.drawImage(usrimg,0,0,32,32,0,0,32,32);

により表示させているのみになります。

■主人公を移動させる

次に主人公を移動させるためのコードです。
抜粋するとこちらのコードになります。

    componentDidMount(){
        document.addEventListener(
            "keydown",
            this.handleKeyDown,
        );
        this.intervalId = setInterval(()=>{
            this.DoCanvas();
        }, 200);
    }

    componentWillUnmount() {
        document.removeEventListener(
          "keydown",
          this.handleKeyDown,
        );
     }
   
     handleKeyDown = (e: KeyboardEvent) => {
       switch (e.key) {
         case "Left": // IE/Edge specific value
         case "ArrowLeft":
           this.keyboardLeft();
           break;
         case "Right": // IE/Edge specific value
         case "ArrowRight":
            this.keyboardRight();
           break;
         case "Up": // IE/Edge specific value
         case "ArrowUp":
              this.keyboardUp();

            break;
         case "Down": // IE/Edge specific value
         case "ArrowDown":
              this.keyboardDown();
            break;
        default:
            break;
       }
     };
   
     keyboardRight = () => {
       var {MoveX} = this.state;
       var {MoveY} = this.state;
       if(map.length-1 > MoveX){
           if(map[MoveY][MoveX+1] === 0){
                MoveX += 1;
           }
       }
       this.setState({MoveX:MoveX})
     };

     keyboardLeft = () => {
        var {MoveX} = this.state;
        var {MoveY} = this.state;
        if(0 < MoveX){
            if(map[MoveY][MoveX-1] === 0){
                MoveX -= 1;
            }
        }
        this.setState({MoveX:MoveX})
      };

      keyboardUp = () => {
        var {MoveX} = this.state;
        var {MoveY} = this.state;
        if(0 < MoveY){
            if(map[MoveY-1][MoveX] === 0){
                MoveY -= 1;
            }
        }
        this.setState({MoveY:MoveY})
      };
 
      keyboardDown = () => {
         var {MoveX} = this.state;
         var {MoveY} = this.state;
         if((map.length-1 > MoveY)){
             if(map[MoveY+1][MoveX] === 0){
                MoveY += 1;
             }
        }
         this.setState({MoveY:MoveY})
       };

    DoCanvas = () =>{
        var img = new Image();
        img.src = mapImg;

        var usrimg = new Image();
        usrimg.src = UserImg;

        var goalimg = new Image();
        goalimg.src = GoalImg;

        const {MoveX} = this.state;
        const {MoveY} = this.state;

        var canvas = document.getElementById('canvas');
        
        canvas.width=640;
        canvas.height=640;
        var ctx = canvas.getContext('2d');

        for (var y = 0; y<map.length;y++){
            for(var x=0; x<map.length;x++){
                if(map[y][x] === 0){
                    ctx.drawImage(img,0,0,32,32,32*x,32*y,32,32);
                }else{
                    ctx.drawImage(img,32,0,32,32,32*x,32*y,32,32);
                }
            }
        }

        ctx.drawImage(goalimg,0,0,32,32,(map.length-1)*32,(map.length-1)*32,32,32);

        ctx.drawImage(usrimg,0,0,32,32,MoveX*32,MoveY*32,32,32);
    }

こちらも少し長いですね😅
重要な部分としては、キーイベントの登録とイベント取得時の各種処理の実行です。
まず、

document.addEventListener(
            "keydown",
            this.handleKeyDown,
        );

にてキーイベントを登録し、handleKeyDownにて押下されたキーを判定し、
各種矢印ボタン押下時の関数をコールさせております。

     handleKeyDown = (e: KeyboardEvent) => {
       switch (e.key) {
         case "Left": // IE/Edge specific value
         case "ArrowLeft":
           this.keyboardLeft();
           break;
         case "Right": // IE/Edge specific value
         case "ArrowRight":
            this.keyboardRight();
           break;
         case "Up": // IE/Edge specific value
         case "ArrowUp":
              this.keyboardUp();

            break;
         case "Down": // IE/Edge specific value
         case "ArrowDown":
              this.keyboardDown();
            break;
        default:
            break;
       }
     };

各種矢印ボタン押下時のcase文に実行したい処理を記載すれば、OKです。
今回は、主人公を動かしたいので、XY座標を加算・減算しております。

■まとめ

今回のRPGゲームのソースコードはこちらに格納しておりますので、全体を確認したい方はこちらをご参照ください。
https://github.com/Elsammit/RPGGame

マップ移動まで作成できたので、次はバトル画面の作成ですね!!
年末年始中に作成してみたいと思います。