LoginSignup
2
2

More than 5 years have passed since last update.

第12話 ゴール(最終回)

Posted at

算数は必要だよ。実生活でも...

これで、太郎と次郎の冒険は一旦終わります。


先日、うちの小学生が算数のテストで目も当てられないような点数を持って帰ってきました。スマホでゲームしたり、Youtubeばかり見てたり、(スマホを与えるなってw)少しは勉学に励んでほしいものです。そこで、

俺「ほら、算数できれば、こんなゲームみたいなの作れるよ?少しは勉強しなさいよ」

小学生「え、なにこれ?わかった!すぐクリアするよ!」

いや、違...逆効果でした...orz


最後は、移動処理の実装です。基本的には入力デバイスからの情報で、進むのか、方向変えるのかを判断して、カメラを操作すればいいわけです。
とりあえず、前進を考えてみます。

poge.js
var moveForward = function() {
  deltaX = moveTransition[Player.direction].x;
  deltaY = moveTransition[Player.direction].y;
  var map = getMap(Player.x + deltaX, Player.y + deltaY);
  // 扉ならもう一歩進む
  var times = (map == "") ? 2 : 1;
  if (map == "") {
    return;
  }
  // 位置情報の更新
  Player.x += deltaX * times;
  Player.y += deltaY * times;
  // カメラの位置更新
  camera.position.x += deltaX * times * blockSize;
  camera.position.z += deltaY * times * blockSize;
  // 光源の位置更新
  light.position.copy( camera.position );
  light.position.x += deltaX;
  light.position.z += deltaY;
};

これを、update関数でコールします。

poge.js
function update() {
  if (upKey.isDown || game.touchControl.cursors.up) {
    moveForward();
    console.log('UP');
  }
  if (leftKey.isDown || game.touchControl.cursors.left) {

このままで良いといえばいいかもしれませんが、なんかこう、ググッと歩いてる雰囲気がほしいじゃないですか!そこで、Tweenという機能でアニメーションさせてみます。
Tweenでは、初期値と最終的な値と時間をセットすると、初期値から最終値までセットした時間をかけて変化させてくれます。どんな変化をするのかというと、関数を自作もできますがプリセットがあるので、それを使ってみます。
ここにプリセットがあります。どんな動きなのかは、Tween.jsのグラフが参考になるでしょうか...

先ほどのmoveForward関数のカメラの位置更新と光源の位置更新の部分が以下のようになります。

poge.js
  // カメラと光源の位置更新
  var tween = game.add.tween(camera.position);
  tween.to({
    x : camera.position.x + deltaX * times * blockSize,
    z : camera.position.z + deltaY * times * blockSize
  }, 1000, Phaser.Easing.Quartic.In);
  tween.onUpdateCallback(function(){
    light.position.copy( camera.position );
    light.position.x += deltaX;
    light.position.z += deltaY;
  }, this);
  tween.start();

1000msかけて座標が変化します。光源の位置はコールバック関数で変化させています。

同様に向きの変更は、y軸で回転させればいいわけです。


≪後記≫
まぁ、息子に算数を勉強してほしい一心で、javascriptもThreeとかPhaserも使い始めて1週間ほどなので、稚拙な感じでアレですが、最後にデモとソースコードを貼っておきます。


デモ
final.png

poge-final.js
//*--- Const
// 方角
var direction = {
  north : 0,
  west  : 1,
  south : 2,
  east  : 3
};
// 迷路表示サイズ
var mazeScreen = {
  width : 16 * 30,
  height : 9 * 30
};
// カメラ用パラメータ
var cameraParams = {
  fov : 95,
  aspect : 1.8,
  near : 1,
  far : 50
};
// 光源用パラメータ
var lightParams = {
  ambient : 0x404040, // 環境光
  point   : 0xffffff, // 点光源
};
// 移動変移係数
var moveTransition = [
  { x :  0, y : -1 },
  { x : -1, y :  0 },
  { x :  0, y :  1 },
  { x :  1, y :  0 }
];
// Cubeの一辺長
var blockSize = 10;
var textureFile = {
  wall    : "img/wall.png",
  door    : "img/door.png",
  ceiling : "img/ceiling.png",
  floor   : "img/floor.png",
};
var joypadImage = {
  compass  : { text : 'compass',       file : "img/compass_rose.png" },
  touchSeg : { text : 'touch_segment', file : "img/touch_segment.png" },
  touch    : { text : "touch",         file : "img/touch.png" },
}
// 方角に対する回転角度
var rotationAngle = [
  0,
  Math.PI/2,
  Math.PI,
  -Math.PI/2
];
// 地図
var mapData = {
  ceiling : [
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■"
  ],
  wall : [
    "■■■■■■■■■■",
    "■   ■    ■",
    "■   ■□■■ ■",
    "■■□■■  ■ ■",
    "■■ ■■  ■ ■",
    "■■ ■■■□■ ■",
    "■      □ ■",
    "■■■■■■■■■■"
  ],
  floor : [
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■■■■■■■■■■",
    "■ ■■■■■■■■",
    "■■■■■■■■■■"
  ]
};

// Variable
// プレイヤー情報
var Player = {
  x : 6,
  y : 1,
  direction : direction.west,
};

// Three.js
var camera;
var scene;
var renderer;
var mesh;
var light;
// Phaser.js
var baseTexture;
var texture;
var textureFlame;
var upKey;
var leftKey;
var rightKey;
// ワーク
var deltaX;
var deltaY;
var isTween;

var game = new Phaser.Game(
  mazeScreen.width, mazeScreen.height, Phaser.AUTO, 'game',
  { preload: preload, create: create, update: update, render: render }
);

var calcPos = function(x){
  return x * blockSize;
};

var getMap = function(x, y){
  return mapData.wall[y].charAt(x);
};

function preload() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(
    cameraParams.fov, cameraParams.aspect, cameraParams.near, cameraParams.far
  );
  camera.position.set( calcPos(Player.x), 0, calcPos(Player.y) );
  camera.rotation.y = rotationAngle[Player.direction];
  light = new THREE.PointLight( lightParams.point, 5, 10, 1 );
  light.position.copy( camera.position );
  light.position.z -= 1;
  scene.add( light );
  var ambient = new THREE.AmbientLight(lightParams.ambient);
  scene.add(ambient);
  var loader = new THREE.TextureLoader();
  var texture = {
    door   :loader.load(textureFile.door),
    wall   :loader.load(textureFile.wall),
    ceiling:loader.load(textureFile.ceiling),
    floor  :loader.load(textureFile.floor)
  };
  var material = {
    door   : new THREE.MeshPhongMaterial({map:texture.door, bumpMap:texture.door, bumpScale: 0.08}),
    wall   : new THREE.MeshPhongMaterial({map:texture.wall, bumpMap:texture.wall, bumpScale: 0.05}),
    ceiling: new THREE.MeshPhongMaterial({map:texture.ceiling, bumpMap:texture.ceiling,bumpScale: 0.09}),
    floor  : new THREE.MeshPhongMaterial({map:texture.floor, bumpMap:texture.floor, bumpScale: 0.09})
  };
  var geometry = new THREE.CubeGeometry(blockSize, blockSize, blockSize);
  for (y = 0; y < mapData.wall.length; y++) {
    for (x = 0; x < mapData.wall[y].length; x++) {
      if (mapData.wall[y].charAt(x) == "") {
        var cube = new THREE.Mesh(geometry, material.wall);
        cube.position.set(blockSize * x, 0, blockSize * y);
        scene.add(cube);
      }
      if (mapData.wall[y].charAt(x) == "") {
        var cube = new THREE.Mesh(geometry, material.door);
        cube.position.set(blockSize * x, 0, blockSize * y);
        scene.add(cube);
      }
      if (mapData.ceiling[y].charAt(x) == "") {
        var cube = new THREE.Mesh(geometry, material.ceiling);
        cube.position.set(blockSize * x, blockSize, blockSize * y);
        scene.add(cube);
      }
      if (mapData.floor[y].charAt(x) == "") {
        var cube = new THREE.Mesh(geometry, material.floor);
        cube.position.set(blockSize * x, -blockSize, blockSize * y);
        scene.add(cube);
      }
    }
  }
  renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setSize( mazeScreen.width, mazeScreen.height );
  var canvas = renderer.domElement;
  baseTexture = new PIXI.BaseTexture(canvas);
  texture = new PIXI.Texture(baseTexture);
  textureFrame = new Phaser.Frame(
    0, 0, 0, mazeScreen.width, mazeScreen.height,
  'debug', game.rnd.uuid());
  var sprite = game.add.sprite(0, 0, texture, textureFrame);
  game.load.image(joypadImage.compass.text, 'img/compass_rose.png');
  game.load.image('touch_segment', 'img/touch_segment.png');
  game.load.image('touch', 'img/touch.png');
}

function create() {
  game.touchControl = game.plugins.add(Phaser.Plugin.TouchControl);
  game.touchControl.inputEnable();
  upKey = game.input.keyboard.addKey(Phaser.Keyboard.UP);
  leftKey = game.input.keyboard.addKey(Phaser.Keyboard.LEFT);
  rightKey = game.input.keyboard.addKey(Phaser.Keyboard.RIGHT);
  game.input.keyboard.addKeyCapture([
    Phaser.Keyboard.UP, Phaser.Keyboard.LEFT, Phaser.Keyboard.RIGHT
  ]);
  isTween = false;
}

function update() {
  if (upKey.isDown || game.touchControl.cursors.up) {
    moveForward();
    //console.log('UP');
  }
  if (leftKey.isDown || game.touchControl.cursors.left) {
    _turn(Math.PI / 2, 1);
    //console.log('LEFT');
  }
  if (rightKey.isDown || game.touchControl.cursors.right) {
    _turn(-Math.PI / 2, -1);
    //console.log('RIGHT');
  }
}

function render() {
  renderer.render(scene, camera);
  game.renderer.updateTexture(baseTexture);
}

var moveForward = function() {
  if (isTween) {
    return;
  }
  isTween = true;
  deltaX = moveTransition[Player.direction].x;
  deltaY = moveTransition[Player.direction].y;
  var map = getMap(Player.x + deltaX, Player.y + deltaY);
  var times = (map == "") ? 2 : 1;
  if (map == "") {
    isTween = false;
    oops();
    return;
  }
  Player.x += deltaX * times;
  Player.y += deltaY * times;
  var tween = game.add.tween(camera.position);
  tween.to({
    x : camera.position.x + deltaX * times * blockSize,
    z : camera.position.z + deltaY * times * blockSize
  }, 1000, Phaser.Easing.Quartic.In);
  tween.onUpdateCallback(function(){
    light.position.copy( camera.position );
    light.position.x += deltaX;
    light.position.z += deltaY;
  }, this);
  tween.onComplete.add(function(){
    isTween = false;
  });
  tween.start();
};

var _turn = function(rotation, direction) {
  if (isTween) {
    return;
  }
  isTween = true;
  var tween = game.add.tween(camera.rotation);
  tween.to( { y: camera.rotation.y + rotation }, 1000, Phaser.Easing.Cubic.InOut );
  tween.onUpdateCallback(function(){
    light.rotation.y = camera.rotation.y;
  }, this);
  tween.onComplete.add(function() {
    Player.direction += direction;
    if (Player.direction > 3) Player.direction = 0;
    if (Player.direction < 0) Player.direction = 3;
    isTween = false;
  });
  tween.start();
};

var oops = function(){
  if (isTween) {
    return;
  }
  isTween = true;
  deltaX = moveTransition[Player.direction].x;
  deltaY = moveTransition[Player.direction].y;
  var tween = game.add.tween(camera.position);
  tween.to({
    x : camera.position.x + deltaX,
    z : camera.position.z + deltaY
  }, 1000, Phaser.Easing.Cubic.InOut, false, 0, 0, true);
  tween.onUpdateCallback(function(){
    light.position.copy( camera.position );
    light.position.x += deltaX;
    light.position.z += deltaY;
  }, this);
  tween.onComplete.add(function(){
    isTween = false;
  });
  tween.start();
};

var turnLeft = function(){
  _turn(Math.PI / 2, 1);
};

var turnRight = function(){
  _turn(-Math.PI / 2, -1);
};
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2