まくろぐ

Undertale やってみた

更新:
作成:
/p/eu7djpv/img-001.jpg

インディーズゲームの世界で話題になった『Undertale』をやってみました。 斬新なシステムや、ストーリー、音楽などに定評があるゲームです。

プレイヤーによって好き嫌いがハッキリと分かれそうなゲームですが、ゲームとして非常に練りこまれていて、プレイしておいて損はないゲームだと思いました。 最初クリアしたときは、ただのパズル要素の入ったドット絵の同人 RPG みたいだなという感じでしたが、Web で情報を探してみると、下記のように特殊な進め方をする 2 回目以降のプレイからが本当のストーリーの始まりでした。

  • P ルート (True Pacifist Route) … 誰も殺さない
  • G ルート (Genocide Route) … 皆殺し

この両極端な進め方でもクリアできるようになっているところが面白く、そのプレイ方法がプレイヤーを感情移入させます。 そして登場キャラクターは、プレイヤーが何度も繰り返しプレイしていることを認識しています。 何度もクリアしなければいけないのは面倒だと思うかもしれませんが、2 回目以降は途中のパズルなどを飛ばせるようになっていて、サクサクと進められるよう工夫されています。

ただし、集団虐殺ルート(通称 G ルート)のラスボス的存在である骸骨姿の「サンズ (Sans)」は凶悪なほど強く、鬼畜ゲー と言われる理由がわかりました。 なんと、プレイヤーのコマンド入力エリアにまで攻撃を仕掛けてきて、ミリ秒単位の操作が要求されます。 ほんと寿命が縮まります。 何十回も挑戦してぎりぎりでクリアできましたが、もう少し歳を取ったら、このゲームはたぶんクリアできなくなると思います。 というかプレイ中に死ぬ と思います。 今のうちにクリアしておいてよかったです。

ちなみに、普通の RPG と同様に、最初にプレイヤーに名前を付けるのですが、この名前は実は、

関連記事

Next.js のダイナミックルーティング機能を利用する (getStaticPaths, getStaticProps, getServerSideProps)

更新:
作成:

ダイナミックルーティングとは

Next.js では、pages/books/[id].tsx のようなファイル名でページを作成すると、1 つのファイルで、

  • /books/001
  • /books/002
  • /books/003

のようなパス (URL) によるアクセスをハンドルできます。 これを ダイナミックルーティング (Dynamic Routes) 機能と呼びます。

ダイナミックルーティングの実装(SSG の場合)

静的ジェネレーション (SSG: Static Generation)、つまり Web サイトのビルド時にすべての HTML ファイルを生成してしまうには、あらかじめどのようなパラメーター(上記の例では id)でのアクセスが行われるかを把握した上で、各ページの内容を生成する必要があります。 これを実現するには、ページコンポーネントの実装ファイル (pages/*.tsx) で、次のような async 関数を実装して export します。

  • getStaticPaths 関数: URL のパラメーター部分(上記の例では id)で指定可能な値を返すように実装します。言い換えると、プリビルドすべきページの一覧情報を Next.js に教えてあげるための実装です。この関数は通常、Web サイトのビルド時にだけ実行されますが、開発サーバー (next dev) 使用時はアクセス毎に呼び出されることに注意してください。
  • getStaticProps 関数: 指定されたパラメーターに対応するデータを返すように実装します。この値がページコンポーネントに引数として渡されます。この関数は通常、Web サイトのビルド時にだけ実行されますが、getStaticPath の戻り値のプロパティ fallbacktrue、あるいは 'blocking' に設定した場合は、アクセス時に呼び出される可能性があります(後述のフォールバックの説明を参照)。

次の例では、/books/001/books/002/books/003 といった URL でアクセス可能な books/[id] ページを定義しています。

pages/books/[id].tsx
import Head from 'next/head'
import React from 'react'
import { GetStaticPaths, GetStaticProps } from 'next'

// パスの構成要素を表す型 (/books/[id].tsx なら id)
type Params = {
  id: string;
}

// ページコンポーネントに渡されるオブジェクトの型
type Book = {
  title: string;
  author: string;
}

// どのようなパラメーター(パス)でアクセスできるかを定義します。
export const getStaticPaths: GetStaticPaths<Params> = async () => {
  // /books/001、/books/002、/books/003 のページを事前生成するには、
  // 次のように paths プロパティの値を設定して返します。
  // 本来は id のリストを外部 API(getBookList など)で取得します。
  return {
    paths: [
      { params: { id: '001' } },
      { params: { id: '002' } },
      { params: { id: '003' } }
    ],
    fallback: false  // 上記以外の id が指定された場合に 404 ページを返す
  }
}

// context.params プロパティに URL のパラメーター部分の値を参照できるので、
// その値に応じたコンテンツを返すように実装します。
export const getStaticProps: GetStaticProps<Book> = async context => {
  // ファイル名が [id].tsx なので id パラメーターを取り出すことができる
  const { id } = context.params as Params

  // 本来はここで getBook(id) のように API を呼び出してデータを取得する
  const book = {
    title: `Title-${id}`,
    author: `Author-${id}`
  }

  // ページコンポーネントに渡す Book オブジェクトを props プロパティに設定する
  return { props: book }
}

// ページコンポーネントの実装
const BookPage: React.FC<Book> = (book) => {
  return <>
    <Head>
      <title>{book.title} の詳細ページ</title>
    </Head>
    <ul>
      <li>タイトル: {book.title}</li>
      <li>著者: {book.author}</li>
    </ul>
  </>
}
export default BookPage

上記の例では、getStaticPaths 関数の戻り値の fallback プロパティに false をセットしていますが、この値で次のように「フォールバック動作」を制御できます。

  • fallback: false … paths プロパティで指定したパラメーター以外でのアクセス時に、404 ページ (pages/404.tsx) を返します。
  • fallback: true … paths プロパティで指定したパラメーター以外でのアクセス時に、サーバーサイドで getStaticProps を呼び出して動的にページを生成します。それ以降のアクセスは、そのページを返します。この機能を利用する場合は、Next.js サーバーで Web サイトをホスティングしている必要があります(next export で HTML エクスポートした場合は動作しません)。
  • fallback: 'blocking'true を指定した場合とほぼ同じですが、サーバーサイドでの HTML 生成が終わってから初めて Web ブラウザにレスポンスが返されるところが異なります(なのでブロッキングという名前になっています)。true を指定した場合は、getStaticProps() の実行前にデータなし状態でブラウザ側にレスポンスが返されるため、Loading 表示を行うことができます。Loading 表示を実現するためには、ページの実装内で useRouter フックを利用して isFallback 情報を参照します(参考: Fallback pages - Next.js)。

ダイナミックルーティングの実装(SSR の場合)

すべてのページを Web サーバーへのアクセス時に動的に生成することを、サーバーサイドレンダリング (SSR: Server-side Rendering) と呼びます。 ダイナミックルーティング機能を SSR で使用するには、getStaticPaths 関数や getStaticProps 関数の代わりに次の関数を実装します。

  • getServerSideProps 関数: Web サーバー(Next.js サーバー)へのアクセス時に呼び出されます。context.params を参照すると URL で指定されたパラメーター (id) を取得できるので、それに対応するデータを返すように実装します。このデータはページコンポーネントの引数として渡されます。

実装方法は getStaticProps と同様です。

pages/books/[id].tsx(抜粋)
export const getServerSideProps: GetServerSideProps<Book> = async context => {
  const { id } = context.params as Params

  // 本来はここで getBook(id) のように API を呼び出してデータを取得する
  const book = {
    title: `Title-${id}`,
    author: `Author-${id}`
  }

  return { props: book }
}

この関数は Web サーバーへのアクセス時に呼び出されるため、必ず Web サイトを Next.js サーバーでホスティングする必要があります(next export で静的な HTML ファイルとしてエクスポートした場合は動作しません)。 Next.js アプリのホスティングサービスとしては、Vercel が有名です。

関連記事

Next.js のプリレンダリング機能を使用する (getStaticProps)

更新:
作成:

Pre-rendering とは

ブログの記事一覧ページなどを生成する場合、なんらかの API で取得した値をもとにページのコンテンツを生成する必要があります。 例えば、次のように取得した値を使ってページを生成することになります。

  • Web API で取得した値
  • データベースのクエリ結果
  • ローカルファイルの内容や、ファイルの一覧情報

Next.js には、Web サイトのビルド時や、Web サーバーへのアクセス時にこういった API を呼び出して、HTML コンテンツを生成する Pre-rendering 機能が備わっています。 Pre-rendering 機能は次の 2 種類があり、どちらか一方を使うこともできますし、両方を組み合わせて使うこともできます。

  • SSG: Static Generation(静的サイトジェネレーション): Web サイトのビルド時に HTML ファイルを生成します。Web サーバーは静的な HTML ファイルを返すだけでよいので、パフォーマンスが非常に高くなります。すべてのページを事前に列挙できるのであれば、できるだけこの SSG を使って静的に HTML 生成してしまうことが推奨されています。静的な HTML ファイルをホスト可能なサーバー(GitHub Pages など)があれば、Web サイトを公開できます。
  • SSR: Server-side Rendering(サーバーサイドレンダリング): クライアントが Web サーバーにアクセスしたときに、サーバーサイドで動的に HTML を生成します。この仕組みを使うと、日々増減するデータを扱いやすくなりますが、Web サーバーとして Next.js サーバーを稼働させておく必要があります。感覚的には、PHP サーバーなどが動作しているイメージに近いです。

ちなみに、純粋に React.js のみを使用した場合とはどう違うのでしょうか? React.js 自体には Pre-rendering 機能は備わっておらず、主に SPA (Single Page Application) を作成するライブラリとして使用されています。 React.js のみを使って上記の例のような記事一覧ページを生成する場合、Web ブラウザ上で JavaScript を実行してコンテンツを動的に生成する必要があります。 これを SSG や SSR と区別するために、CSR: Client-side rendering と呼びます。

  • CSR … Web ブラウザ上の JavaScript API のみ呼び出せる(React.js のみを使った場合)
  • SSG/SSR … Node.js 環境の JavaScript API を呼び出せる(Next.js を使った場合)

つまり、Next.js を使うと、より柔軟な方法でコンテンツを事前生成することが可能になります。

☝️ 静的サイトジェネレーターとの違いは? Hugo などの Static Site Generator(静的サイトジェネレーター)は、そのビルドコマンド自体が、上記の SSG (Static Generation) に対応する Pre-rendering 機能を提供しています。 Next.js を使うと、Hugo に組み込まれている Static Generation 部分(.md から .html への変換部分)を自由に実装できるのだと考えるとわかりやすいかもしれません。

Pre-rendering 機能を使用する

Pre-rendering 用の関数

Next.js が提供する Pre-rendering 機能のひとつである静的サイトジェネレーション (SSG: Static Generation) を使用すると、Web サイトのビルド時(next build 実行時)に、外部 API などを利用して HTML を事前生成することができます。 SSG 機能を利用するには、各ページコンポーネントの実装ファイルで、次のような名前の async 関数を実装して export します。

  • getStaticProps 関数: Web サイトのビルド時にこの関数が呼び出されるので、そのページのレンダリングに使用するデータを返すように実装します。戻り値として返したオブジェクトの props プロパティの値が、ページコンポーネントの引数(通称 props)として渡されます。
  • getStaticPaths 関数: ダイナミックルーティングを使用するとき、事前生成するページのリストを返すよう実装します。詳しくは、ダイナミックルーティングの記事 で説明します。

一方、完全にサーバーサイドで HTML を生成するサーバーサイドレンダリング (SSR: Server-side Rendering) の仕組みを使いたい場合は、上記の代わりに getServerSideProps 関数を実装します。 ちょっとややこしいですが、Next.js では、SSR も Pre-rendering の一種として分類されています(Pre-rendering じゃないのは CSR: Client-side Rendering のみです)。

  • getServerSideProps 関数: Web サイトへのアクセス時にこの関数が呼び出されるので、そのページのレンダリングに使用するデータを返すように実装します。詳しくは、ダイナミックルーティングの記事 で説明します。

Pre-rendering の実装例

例として、ローカルファイルや、データベースなどに格納された「本の一覧」を表示する books インデックスページを考えてみます。 こういった インデックスページ を Web サイトのビルド時に生成するには、getStaticProps 関数を実装して、戻り値の props プロパティで一覧情報を返すように実装します。 props プロパティの値はページコンポーネントの引数として渡されるため、その値を参照して「本の一覧」を表示することができます。 下記のサンプルコードでは、props プロパティの型を Props として定義しています。

pages/books/index.tsx
import Link from 'next/link'
import React from 'react'
import { GetStaticProps } from 'next'

type Book = {
  id: string;
  title: string;
}

// ページコンポーネントに渡されるデータ
type Props = {
  books: Book[];
}

// この関数がビルド時に呼び出され、戻り値の props の値がページコンポーネントに渡される
export const getStaticProps: GetStaticProps<Props> = async context => {
  // 本来は、ここで外部 API などを呼び出してデータを取得する
  const books = [
    { id: '001', title: 'Title-1' },
    { id: '002', title: 'Title-2' },
    { id: '003', title: 'Title-3' }
  ]

  // この props プロパティの値がページコンポーネントに渡される
  return { props: { books } }
}

// ページコンポーネントの実装
const BooksPage: React.FC<Props> = ({ books }) => (
  <>
    <h2>Book list</h2>
    <ul>
      {books.map(book => (
        <li key={book.id}>
          <Link href={`/books/${book.id}`}><a>{book.title}</a></Link>
        </li>
      ))}
    </ul>
  </>
)
export default BooksPage

getStaticProps 関数は Node.js 環境で実行されるため、ローカルファイルへのアクセスや、外部 API の呼び出しなどを自由に行うことができます。 つまり、データソースとして、ローカルの YAML ファイルや CSV ファイル、Web API で取得したデータなど、どのようなデータでも扱うことができます。

getStaticProps は、ページコンポーネントの実装ファイル (pages/**.tsx) の中でしか定義できないことに注意してください。

個別ページの Pre-rendering

上記の実装例では、「本の一覧」を表示するインデックスページを Pre-rendering する方法を示しました。 そこに表示された各アイテムには次のような URL のリンクが張られており、個々の本の詳細情報にジャンプできるようになっています。

  • /books/001
  • /books/002
  • /books/003

これらのページを、pages/books/001.tsxpages/books/002.tsx のようなファイルで、ひとつずつページコンポーネントとして実装していくのは大変です。 Next.js は、上記のような URL によるアクセスを 1 つのページコンポーネント (pages/books/[id].tsx) でハンドルする ダイナミックルーティング という仕組みを提供しています。

関連記事

メニュー

まくろぐ
サイトマップまくへのメッセージ