6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React Native ゲーム開発 Whack-A-Mole(もぐらたたき) 第一回

Last updated at Posted at 2020-04-15

Whack-A-Mole(もぐらたたき) 第一回

フロントエンジニアの、YAMATAKUです。前回、

React Nativeでゲームを作る(ための、はじめの一歩)
https://qiita.com/team-lot/items/3fe8d3f77535a7cae8b6

で、環境構築と、プロジェクト作成を完了している前提で、実装に入ります。

App.jsを整理

App.js
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, { Component } from "react";
import { 
    View,
    Text,
    StyleSheet,
    SafeAreaView,
} from "react-native";

export default class MainScreen extends Component {
     
  render() {
    return (
      <SafeAreaView style={styles.container}>
        <View>
          <Text style={styles.hello}>Hello, WhackAMole!</Text>
        </View> 
      </SafeAreaView>
    )
  }
 }

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  hello: {
    fontSize: 18,
    fontWeight: 'bold',
  }
})

とりあえず、こんな感じです。
スクリーンショット 2020-04-11 0.33.55.png

背景、UI追加

事前準備として、サンプルリソースから画像をコピーする必要があるので、以下GitHubより前プロジェクトをダウンロードしておきます。

前回作成したプロジェクト、WhackAMole/ 直下に、
上記でダウンロードしたディレクトリ /WhackAMole/assets をコピーします。

カスタムフォントを利用するので、./react-native.config.jsを作成して、以下を記述します。

react-native.config.js
module.exports = {
  project: {
    ios: {},
    android: {}, // grouped into "project"
  },
  assets: ["./assets/fonts/"], // stays the same
};

iOS/Androidフォルダに対して、以下コマンドでリソースを同期します。
各フォルダの必要箇所にアセットと設定が反映されます。

$ react-native link

共通変数ファイルとして、./Global.jsを作成します

.Global.js
import { Dimensions, Platform } from "react-native";

export default Constants = {
  MAX_WIDTH: Dimensions.get("screen").width,
  MAX_HEIGHT: Dimensions.get("screen").height,
  XR: Dimensions.get("screen").width / 650,
  YR: Dimensions.get("screen").height / 1024,
  FONT_LITITAONE_REGULAR: Platform.OS === 'ios' ? "LilitaOne" : "LilitaOne-Regular",
}

カスタムフォントのOS判別について、我流なので、うまい書き方があればご指摘ください(styleに'LilitaOne'で指定したら、Androidがフォントを読み込まなかったため、'LilitaOne-Regular'で静的に指定しています)

今回のゲーム開発において、メインとなるプラグインをインストールしておきます。

//スプライトシート
$ npm install --save rn-sprite-sheet

さて、実装にはいっていきます。
とりあえず、前述のコードは必要ないので削除し、以下のように実装していきます。

App.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React, { Component } from "react";
import { 
    View,
    Text,
    Image,
    StyleSheet,
    SafeAreaView,
    TouchableWithoutFeedback,
} from "react-native";
import Images from './assets/Images';
import Constants from './Constants'

const DEFAULT_TIME = 5;
const DEFAULT_STATE = {
  level: 1,
  score: 0,
  time: DEFAULT_TIME,
  cleared: false,
  paused: false,
  gameover: false,
  health: 100
}

export default class MainScreen extends Component {
  constructor(props){
    super(props);
    this.state = DEFAULT_STATE;
  }

  render() {
    let healthBarWidth = (Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60 - Constants.XR * 6) * this.state.health / 100;
    return (
      <View style={styles.container}>
        <Image style={styles.backgroundImage} resizeMode="stretch" source={Images.background} />
        <View style={styles.topPanel}>
          <SafeAreaView >
            <View style={styles.statsContainer}>
              <View style={styles.stats}>
                <View style={styles.levelContainer}>
                  <Text style={styles.levelTitle}>Level</Text>
                  <Text style={styles.levelNumber}>{this.state.level}</Text>
                </View>
              </View>
              <View style={styles.stats}>
                <View style={styles.timeBar}>
                  <Text style={styles.timeNumber}>{this.state.time}</Text>
                </View>
                <Image style={styles.timeIcon} resizeMode="stretch" source={Images.timeIcon} />
              </View>
              <View style={styles.stats}>
                <View style={styles.scoreBar} >
                  <Text style={styles.scoreNumber}>{this.state.score}</Text>
                </View>
                <Image style={styles.scoreIcon} resizeMode="stretch" source={Images.scoreIcon} />
              </View>
              <View style={styles.stats}>
                <TouchableWithoutFeedback onPress={this.pause}>
                  <View style={styles.pauseButton}>
                    <Image style={styles.pauseButtonIcon} resizeMode="stretch" source={Images.pauseIcon} />
                  </View>
                </TouchableWithoutFeedback>
              </View>
            </View>
            <View style={styles.healthBarContainer}>
              <View style={styles.healthBar}>
                <View style={[styles.healthBarInner, { width: healthBarWidth}]} />
              </View>
              <Image style={styles.healthIcon} resizeMode="stretch" source={Images.healthIcon} />
            </View>
          </SafeAreaView>
        </View>
      </View> 
    )
  }
 }

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',   //縦並びに
  },
  backgroundImage: {
    width: Constants.MAX_WIDTH,
    height: Constants.MAX_HEIGHT,
    position: 'absolute'
  },
  topPanel: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    height: Constants.YR * 250,
    flexDirection: 'column'
  },
  statsContainer: {
    width: Constants.MAX_WIDTH,
    height: Constants.YR * 120,
    flexDirection: 'row'
  },
  stats: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  levelContainer: {
    width: Constants.YR * 80,
    height: Constants.YR * 80,
    backgroundColor: '#ff1a1a',
    borderColor: 'white',
    borderWidth: 3,
    borderRadius: 10,
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center'
  },
  levelTitle: {
    fontSize: Constants.YR * 25,
    color: 'white',
    fontFamily: Constants.FONT_LITITAONE_REGULAR
  },
  levelNumber: {
    fontSize: Constants.YR * 21,
    color: 'white',
    fontFamily: Constants.FONT_LITITAONE_REGULAR
  },
  timeIcon: {
    position: 'absolute',
    left: 0,
    width: Constants.YR * 40,
    height: Constants.YR * 40
  },
  timeBar: {
    height: Constants.YR * 25,
    position: 'absolute',
    left: 20,
    right: 5,
    backgroundColor: 'white',
    borderRadius: 13,
    justifyContent: 'center',
    alignItems: 'center'
  },
  timeNumber: {
    fontSize: Constants.YR * 22,
    color: 'black',
    fontFamily: Constants.FONT_LITITAONE_REGULAR
  },
  scoreIcon: {
    position: 'absolute',
    left: 0,
    width: Constants.YR * 40,
    height: Constants.YR * 40
  },
  scoreBar: {
    height: Constants.YR * 25,
    position: 'absolute',
    left: 20,
    right: 5,
    backgroundColor: 'white',
    borderRadius: 13,
    justifyContent: 'center',
    alignItems: 'center'
  },
  scoreNumber: {
    fontSize: Constants.YR * 21,
    color: 'black',
    fontFamily: Constants.FONT_LITITAONE_REGULAR
  },
  pauseButton: {
    width: Constants.YR * 50,
    height: Constants.YR * 50,
    backgroundColor: 'blue',
    borderColor: 'white',
    borderWidth: 3,
    borderRadius: 10,
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center'
  },
  pauseButtonIcon: {
    width: Constants.YR * 25,
    height: Constants.YR * 25
  },
  healthBarContainer: {
    height: Constants.YR * 40,
    width: Constants.MAX_WIDTH - Constants.XR * 120,
    marginLeft: Constants.XR * 60,
  },
  healthIcon: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: Constants.YR * 40,
    height: Constants.YR * 40
  },
  healthBar: {
    height: Constants.YR * 20,
    width: Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60,
    marginLeft: Constants.XR * 40,
    marginTop: Constants.YR * 10,
    backgroundColor: 'white',
    borderRadius: Constants.YR * 10
  },
  healthBarInner: {
    position: 'absolute',
    backgroundColor: '#ff1a1a',
    left: Constants.XR * 3,
    top: Constants.XR * 3,
    bottom: Constants.YR * 3,
    borderRadius: Constants.YR * 8
  }
})

とりあえず、ベースを実装しました。

次回は、「もぐら」と「もぐら穴」を実装します。

6
6
2

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
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?