1. サイトトップ
  2. ブログ
  3. Python
  4. PythonのテンプレートエンジンJinja2を使ってみる

PythonのテンプレートエンジンJinja2を使ってみる

こんにちは、情熱開発部プログラム3課の前田です!

前回のブログ投稿が約1年前だと思うと時間の流れがとても速く感じます。

4月で入社してから3年目になると思うとびっくりします。

今回は、ゲーム開発におけるPythonのテンプレートエンジン「Jinja2」の使いどころを、Unityでの利用例とあわせて紹介します。

※このブログで使用しているツールとバージョンは以下になります。
Python:3.14.3
Jinja2:3.1.6
Unity:6000.3.11f1
VisualStudio:2022

Jinja2とは

Jinja2(Jinja)は、プログラミング言語Python用のテンプレートエンジンです。

主に HTMLなどのテキストを動的に生成するために使われ、Webアプリ開発でよく利用されるようです。

テンプレートファイルに特定のパラメータを埋め込むことが可能で、外部からデータを入力して新しいファイルを生成できます。

ライセンスはBSDライセンスになります。

基本的な使い方

基本的な使い方の流れは以下のようになります。

  1. テンプレートファイルの作成
  2. Pythonでテンプレートファイルから新しいファイルを生成する

テンプレートファイルの作成

まず生成するためのもととなるテンプレートファイルを作成していきます。

Jinja2はテンプレートとなるファイルの拡張子の制限はありません。

テンプレートの基本構文

変数表示
{{ 変数名 }}
if文
{% if 条件 %}

{% else %}

{% endif %}
for文
{% for 要素 in リスト %}

{%endfor%}
コメント
{# コメント #}

例として簡単な自己紹介用のテンプレートファイルを作ってみました。

{%- if is_am -%}
おはようございます。
{%- else -%}
こんにちは。
{%- endif %}
はじめまして、私は{{ name }}と申します。
現在{{ company }}で{{ work }}をしています。

{#- 複数表示可能 #}
趣味は
{%- for hobby in hobbies -%}
{{ hobby }} {% if not loop.last %},{% endif %}
{%- endfor -%}
です。
よろしくお願いいたします。

Pythonでテンプレートファイルから新しいファイルを生成する

テンプレートファイルの作成できたので、PythonでJinja2を使いファイルを生成してみます。

以下がコードになります。

from jinja2 import Environment, FileSystemLoader

# テンプレートファイルがあるディレクトリを取得
env = Environment(loader=FileSystemLoader('templates'))

# テンプレートファイルの読み込み
tpl = env.get_template('self_introduction.txt')

# テンプレートに埋め込むデータを用意
data = {
    'is_am': False,
    'name': '前田',
    'company': 'ロジカルビート',
    'work':'ゲーム開発',
    'hobbies':['ゲーム','映画観賞','散歩']
}

# テンプレートファイルに埋め込む
output = tpl.render(data)

# テキストファイルに保存
with open('output.txt', 'w', newline ='\n', encoding='utf-8') as f:
    f.write(output)

テンプレートに埋め込むデータ(入力データ)は、コード上に直接用意しています。

ポイントはEnvironmentクラスのget_template関数でテンプレートファイルを読み込み、Templateクラスのrender関数でデータを埋め込みます。

実行すると以下のファイルを生成することができました。

こんにちは。
はじめまして、私は前田と申します。
現在ロジカルビートでゲーム開発をしています。
趣味はゲーム ,映画観賞 ,散歩 です。
よろしくお願いいたします。

ゲーム開発で使える場面

Jinja2がゲーム開発で使える場面は主にソースコード自動生成で使えます。

ステートマシンやUIの制御コードはインターフェースや継承元のクラスを用意することが多く、構造が決まっているかつ繰り返し同じ関数を作成することが多くなりがちです。

このようなクラスや関数を自動生成する仕組みを作っておけば、作業効率が上がるのと同時にヒューマンエラーも起こりにくくなります。

C#ではジェネリッククラスを作れますが、セーブデータやマスターデータなどの構造は似ているがデータ定義が異なってくるクラスなどは、リフレクションを頻繁に使いパフォーマンスが落ちる可能性があるため、ソースコードの自動生成を行った方が良い場合もあります。

今回はJinja2とUnityを使い、ステートマシンの一部ソースコードの自動生成をやってみます。

自動生成できる箇所について考える

簡易的なステートマシンのコードを用意しました。

public class StateMachine
{
    public IState CurrentState { get; private set; }

    public void ChangeState(IState nextState)
    {
        CurrentState?.Exit();
        CurrentState = nextState;
        CurrentState.Enter();
    }

    public void Update(float deltaTime)
    {
        CurrentState.Update(deltaTime);
    }
}
public interface IState
{
    public void Enter() { }

    public void Update(float deltaTime) { }

    public void Exit() { }
}
using UnityEngine;

public class IdleState : IState
{
    public void Enter()
    {
        Debug.Log("IdleStateのEnter()実行!");
    }

    public void Update(float deltaTime)
    {
        Debug.Log("IdleStateのUpdate()実行!");
    }

    public void Exit()
    {
        Debug.Log("IdleStateのExit()実行!");
    }
}
using UnityEngine;

public class MoveState : IState
{
    public void Enter()
    {
        Debug.Log("MoveStateのEnter()実行!");
    }

    public void Update(float deltaTime)
    {
        Debug.Log("MoveStateのUpdate()実行!");
    }

    public void Exit()
    {
        Debug.Log("MoveStateのExit()実行!");
    }
}

IdleStateやMoveStateはインターフェース(IStateクラス)を実装したクラスはステート(状態)が増えるたびに、毎回新しく実装する必要があります。

クラスの構造自体は変わらないため、一部コードを自動生成できそうです。

IStateを実装したクラスを自動生成するツールを作成する

以下のフローで実装してきます。

  1. テンプレートファイルの作成
  2. PythonからC#スクリプト自動生成
  3. UnityからPython実行

テンプレートファイルの作成

IStateを実装したクラスはクラス名のみ違うので、クラス名を外部から入力できればよさそうです。

作成するテンプレートファイルは以下のようになります。

using UnityEngine;

public class {{ class_name }} : IState
{
    public void Enter()
    {
        Debug.Log("{{ class_name }}のEnter()実行!");
    }

    public void Update(float deltaTime)
    {
        Debug.Log("{{ class_name }}のUpdate()実行!");
    }

    public void Exit()
    {
        Debug.Log("{{ class_name }}のExit()実行!");
    }
}

PythonからC#スクリプト自動生成

次にC#スクリプトを生成するコードをPythonで実装していきます。

Unityからクラス名を受け取れるように引数を実装し、動的に変更できるようにします。

生成されるスクリプト名はファイル名と同じにしています。

import os
from pathlib import Path
import argparse
from jinja2 import Environment, FileSystemLoader

# Unityからクラス名を取得
parser = argparse.ArgumentParser()
parser.add_argument("--class_name", default = "DefaultClass")
args = parser.parse_args()

# テンプレートファイルが置かれているディレクトリを指定
base_dir = os.path.dirname(os.path.abspath(__file__))
template_dir = os.path.join(base_dir, "templates")
file_loader = FileSystemLoader(template_dir)
env = Environment(loader = file_loader)

# テンプレートファイルの読み込み
template = env.get_template('class.txt')

# テンプレートに埋め込むデータを用意
data = {'class_name': args.class_name}

# テンプレートファイルに埋め込む
output = template.render(data)

parent_dir = Path(__file__).resolve().parent.parent
output_file_dir = os.path.join(parent_dir, "Scripts")
output_file = os.path.join(output_file_dir, args.class_name +".cs")

# C#スクリプト生成
with open(output_file, 'w', encoding='utf-8') as f:
    f.write(output)

Unityエディタ拡張からPython実行

最後にUnityからPythonを実行できるように実装していきます。

エディタ拡張でクラス名を入力できるようにして、クラスを生成できるようにします。

以下コードになります。

using UnityEngine;
using UnityEditor;
using System.Diagnostics;

public class GeneratorWindow : EditorWindow
{
    private string arg = "";

    [MenuItem("Tools/IState Generator")]
    public static void ShowWindow()
    {
        GetWindow<GeneratorWindow>("IState Generator");
    }

    void OnGUI()
    {
        GUILayout.Label("クラス名入力", EditorStyles.boldLabel);
        arg = EditorGUILayout.TextField("クラス名", arg);

        if (GUILayout.Button("コード生成開始"))
        {
            if (string.IsNullOrWhiteSpace(arg))
            {
                UnityEngine.Debug.LogError("class_name が未指定です");
                return;
            }

            Run(arg);
        }
    }

    void Run(string arg)
    {
        var fileName = "python.exe"; // フルパスを指定してください
        var scriptPath = $"{Application.dataPath}//Tool//generator.py";
        
        var info  = new ProcessStartInfo();
        info.FileName = fileName;
        info.Arguments = $"\"{scriptPath}\" --class_name \"{arg}\"";

        var process = Process.Start(info);
        process.WaitForExit();
    }
}

入力文字が空の場合は生成されるスクリプトファイルがコンパイルエラーを起こすので、実行させないようにしています。

またProcessStartInfoのfileNameには、環境に合わせてpython.exeのフルパスを指定してください。

Tools/IState Generatorという名前のエディタ拡張ができているので、クラス名を入力し、コード生成開始を実行すると入力したクラス名のC#スクリプトが生成されていたら成功です。

最後に各ファイルのディレクトリは以下のようになっています。

  • Aseets
    • Scripts
      • GeneratorWindow.cs
      • StateMachine.cs
      • IState.cs
    • Tool
      • generator.py
      • template
        • class.txt

使ってみた感想

今回はJinja2ライブラリを使い、ソースコード自動生成を紹介いたしました。

テンプレートファイルを直感的に作成できるところや、Pythonでのテンプレートの読み込み、埋め込みコードがシンプルで使いやすかったです。

C#にはSourceGeneratorがありますが、簡単なソースコードの自動生成を行う場合は、学習コストも少なく直感的に使えるJinja2を使ってみるのもありかもしれません。

ぜひJinja2ライブラリを使ってみてください。

参考

【Unity】デザインパターン State プレイヤー管理編

デザインパターンとSOLIDでコードをレベルアップ

jinja2についてまとめてみました。

Jinja2超わかりやすいガイド – Spovisor(スポバイザー)ブログ


【免責事項】

本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。