Panda3D オープンワールドゲーム教材

【概要】Panda3Dゲームエンジンを使用した3D地形生成とキャラクター操作の学習教材である。Perlinノイズによる手続き的地形生成、GeoMipTerrainによるLOD制御、三人称カメラの実装方法を、動作するプログラムを通じて理解できる。

資料:[PDF], [パワーポイント]

【目次】

  1. プログラム利用ガイド
  2. 前準備
  3. プログラムコードの説明
  4. 実験・研究スキルの基礎:Windowsで学ぶ実験

プログラム利用ガイド

1. このプログラムの利用シーン

Panda3Dゲームエンジンを使用した3D地形生成とキャラクター操作の学習教材である。手続き的地形生成、LOD(詳細度)制御、三人称カメラの実装方法を、動作するプログラムを通じて理解できる。

2. 主な機能

3. 基本的な使い方

  1. 起動:

    ターミナルでpythonコマンドを実行する。地形生成には数秒かかる場合がある。

  2. 移動操作:

    WASDキーでプレイヤーを前後左右に移動させる。移動方向はカメラの向きを基準とする。

  3. カメラ操作:

    左マウスボタンを押しながらドラッグすると、カメラが回転する。マウスホイールで視点距離を調整できる。

  4. 終了:

    ESCキーを押すとプログラムが終了する。

4. 便利な機能

Python開発環境,ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent --accept-source-agreements --accept-package-agreements
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine --id Codeium.Windsurf -e --silent --accept-source-agreements --accept-package-agreements

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する:


pip install panda3d

プログラムコードの説明

1. 概要

このプログラムは、Panda3Dゲームエンジンを使用した三人称視点のオープンワールドゲーム教材である。実行時にPerlinノイズでハイトマップを動的に生成し、粒子ベースの侵食シミュレーションを適用した後、GeoMipTerrainで地形をレンダリングする。プレイヤーはWASDキーとマウス操作で地形上を移動できる。

2. 主要技術

Perlinノイズ

Ken Perlinが1985年に発表した手続き的ノイズ生成アルゴリズム[1]。格子点に割り当てた勾配ベクトルと、格子点から入力座標への方向ベクトルの内積を計算し、補間することで連続的なノイズ値を生成する。本プログラムでは、基本ノイズ、リッジノイズ(尾根状の形状を生成する変形ノイズ)、マスクノイズを組み合わせて複合地形を生成している。

GeoMipTerrain

Willem H. de Boerが2000年に発表したGeoMipMapping(Geometrical MipMapping)アルゴリズム[2]に基づくPanda3Dの地形レンダリングクラス。地形をブロック単位に分割し、視点からの距離に応じて各ブロックの詳細度(LOD: Level of Detail)を変化させる。視点移動時に全体を再生成する必要がなく、変更のあるブロックのみを更新する。

3. 技術的特徴

4. 実装の特色

教材としての利用を想定し、以下の機能を備える。

5. 参考文献

  1. Perlin, K. (1985). An Image Synthesizer. Proceedings of the 12th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH '85), 287-296. https://doi.org/10.1145/325334.325247
  2. de Boer, W. H. (2000). Fast Terrain Rendering Using Geometrical MipMapping. https://www.flipcode.com/archives/article_geomipmaps.pdf
  3. Mei, X., Decaudin, P., & Hu, B.-G. (2007). Fast Hydraulic Erosion Simulation and Visualization on GPU. Proceedings of the 15th Pacific Conference on Computer Graphics and Applications, 47-56. https://doi.org/10.1109/PG.2007.15

"""
Panda3D オープンワールドゲーム教材
- 実行時にPerlinノイズでハイトマップを動的生成
- GeoMipTerrainによるリアルな地形
- 三人称カメラ
- WASD + マウスによる簡易移動
"""

from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from panda3d.core import (
    GeoMipTerrain, PNMImage, Filename, TextureStage, Texture,
    Vec3, Point3, CardMaker, NodePath, DirectionalLight, AmbientLight,
    WindowProperties, LColor
)
import math
import sys
import random


class PerlinNoise:
    """純粋Python実装のPerlinノイズ"""

    def __init__(self, seed=0):
        random.seed(seed)
        self.perm = list(range(256))
        random.shuffle(self.perm)
        self.perm += self.perm  # 512要素に拡張

    def fade(self, t):
        return t * t * t * (t * (t * 6 - 15) + 10)

    def lerp(self, a, b, t):
        return a + t * (b - a)

    def grad(self, hash_val, x, y):
        h = hash_val & 3
        if h == 0:
            return x + y
        elif h == 1:
            return -x + y
        elif h == 2:
            return x - y
        else:
            return -x - y

    def noise2d(self, x, y):
        """2Dパーリンノイズ(-1〜1の範囲)"""
        xi = int(x) & 255
        yi = int(y) & 255
        xf = x - int(x)
        yf = y - int(y)

        u = self.fade(xf)
        v = self.fade(yf)

        aa = self.perm[self.perm[xi] + yi]
        ab = self.perm[self.perm[xi] + yi + 1]
        ba = self.perm[self.perm[xi + 1] + yi]
        bb = self.perm[self.perm[xi + 1] + yi + 1]

        x1 = self.lerp(self.grad(aa, xf, yf), self.grad(ba, xf - 1, yf), u)
        x2 = self.lerp(self.grad(ab, xf, yf - 1), self.grad(bb, xf - 1, yf - 1), u)

        return self.lerp(x1, x2, v)

    def octave_noise(self, x, y, octaves=6, persistence=0.5):
        """オクターブノイズ(複数周波数の合成)"""
        total = 0
        frequency = 1
        amplitude = 1
        max_value = 0

        for _ in range(octaves):
            total += self.noise2d(x * frequency, y * frequency) * amplitude
            max_value += amplitude
            amplitude *= persistence
            frequency *= 2

        return total / max_value

    def ridge_noise(self, x, y, octaves=6, persistence=0.5):
        """リッジノイズ(尾根・谷を生成する変形ノイズ)"""
        total = 0
        frequency = 1
        amplitude = 1
        max_value = 0

        for _ in range(octaves):
            # 絶対値を取り、反転することで尾根状の形状を生成
            n = self.noise2d(x * frequency, y * frequency)
            n = 1.0 - abs(n)  # 尾根の形状
            n = n * n  # より鋭い尾根に
            total += n * amplitude
            max_value += amplitude
            amplitude *= persistence
            frequency *= 2

        return total / max_value


class OpenWorldGame(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # ウィンドウ設定
        self.set_window_title("Open World - Panda3D教材")

        # 地形パラメータ
        self.terrain_size = 513  # 2^n + 1 である必要がある(257→513で面積約4倍)
        self.terrain_scale = 2.5  # スケールも拡大(合計で面積約10倍)
        self.height_scale = 80.0  # 高低差も拡大

        # プレイヤー設定
        self.player_pos = Vec3(640, 640, 0)  # マップ中央付近
        self.player_heading = 0
        self.move_speed = 50.0  # 広いマップ用に速度アップ

        # カメラ設定
        self.camera_distance = 30.0  # 広いマップ用に距離拡大
        self.camera_height = 15.0
        self.camera_heading = 0  # カメラの水平角度
        self.camera_pitch = 30   # カメラの垂直角度
        self.mouse_dragging = False
        self.last_mouse_x = 0
        self.last_mouse_y = 0

        # 移動状態
        self.key_map = {
            "forward": False, "backward": False,
            "left": False, "right": False
        }

        # 初期化
        self.setup_terrain()
        self.setup_player()
        self.setup_camera()
        self.setup_lighting()
        self.setup_controls()
        self.setup_sky()

        # メインループ
        self.taskMgr.add(self.update, "update")

    def set_window_title(self, title):
        """ウィンドウタイトル設定"""
        props = WindowProperties()
        props.setTitle(title)
        self.win.requestProperties(props)

    def generate_heightmap(self):
        """複合ノイズ+侵食シミュレーションでハイトマップを動的生成"""
        size = self.terrain_size

        # 複数シードのPerlinノイズ
        perlin_base = PerlinNoise(seed=42)      # 基本地形
        perlin_ridge = PerlinNoise(seed=123)    # 山脈用
        perlin_detail = PerlinNoise(seed=789)   # 細部用
        perlin_mask = PerlinNoise(seed=456)     # 平地/山地マスク用

        # 高さデータを2次元配列で保持(侵食処理用)
        heightdata = [[0.0] * size for _ in range(size)]

        # ステップ1: 複合ノイズ生成
        for y in range(size):
            for x in range(size):
                # 大規模な地形マスク(平地と山岳地帯を分ける)
                mask = perlin_mask.octave_noise(x * 0.005, y * 0.005, octaves=3, persistence=0.5)
                mask = (mask + 1) / 2  # 0〜1に正規化
                mask = pow(mask, 0.8)  # コントラスト調整

                # 基本地形(なだらかな起伏)
                base = perlin_base.octave_noise(x * 0.015, y * 0.015, octaves=6, persistence=0.5)
                base = (base + 1) / 2

                # リッジノイズ(山脈・尾根)
                ridge = perlin_ridge.ridge_noise(x * 0.012, y * 0.012, octaves=5, persistence=0.45)

                # 細部ノイズ(小さな起伏)
                detail = perlin_detail.octave_noise(x * 0.05, y * 0.05, octaves=3, persistence=0.4)
                detail = (detail + 1) / 2

                # マスクに基づいて合成
                # mask < 0.4: 平地優勢
                # mask > 0.6: 山岳優勢
                if mask < 0.35:
                    # 平地エリア(低くて平坦)
                    combined = base * 0.3 + detail * 0.1
                    combined = pow(combined, 2.0)  # より平坦に
                elif mask < 0.55:
                    # 丘陵エリア(中程度の起伏)
                    t = (mask - 0.35) / 0.2  # 0〜1の遷移
                    flat_part = base * 0.3 + detail * 0.1
                    hill_part = base * 0.5 + ridge * 0.3 + detail * 0.1
                    combined = flat_part * (1 - t) + hill_part * t
                else:
                    # 山岳エリア(高くて急峻)
                    combined = base * 0.3 + ridge * 0.5 + detail * 0.15
                    combined = pow(combined, 1.2)  # より急峻に
                    combined = combined * 0.6 + 0.4  # 最低高度を上げる

                heightdata[y][x] = combined

        # ステップ2: 簡易侵食シミュレーション(粒子ベース)
        heightdata = self.apply_erosion(heightdata, size, iterations=5000)

        # 正規化して画像に変換
        heightdata = self.normalize_heightdata(heightdata, size)

        img = PNMImage(size, size)
        for y in range(size):
            for x in range(size):
                h = heightdata[y][x]
                img.setXel(x, y, h, h, h)

        return img

    def normalize_heightdata(self, heightdata, size):
        """高さデータを0〜1に正規化"""
        min_h = min(min(row) for row in heightdata)
        max_h = max(max(row) for row in heightdata)
        range_h = max_h - min_h if max_h > min_h else 1.0

        for y in range(size):
            for x in range(size):
                heightdata[y][x] = (heightdata[y][x] - min_h) / range_h

        return heightdata

    def apply_erosion(self, heightdata, size, iterations=5000):
        """簡易粒子ベース侵食シミュレーション"""
        erosion_rate, deposition_rate, evaporation, min_slope = 0.3, 0.3, 0.02, 0.01

        for _ in range(iterations):
            x, y = random.uniform(1, size - 2), random.uniform(1, size - 2)
            sediment, water = 0.0, 1.0

            for _ in range(50):  # 雨粒が流れ落ちる(最大50ステップ)
                ix, iy = int(x), int(y)
                if ix < 1 or ix >= size - 1 or iy < 1 or iy >= size - 1:
                    break

                h = heightdata[iy][ix]
                neighbors = [(ix-1, iy, heightdata[iy][ix-1]), (ix+1, iy, heightdata[iy][ix+1]),
                             (ix, iy-1, heightdata[iy-1][ix]), (ix, iy+1, heightdata[iy+1][ix])]
                lowest = min(neighbors, key=lambda n: n[2])
                slope = h - lowest[2]

                if slope < min_slope:
                    heightdata[iy][ix] += sediment * deposition_rate
                    break

                capacity = slope * water
                if sediment > capacity:
                    deposit = (sediment - capacity) * deposition_rate
                    heightdata[iy][ix] += deposit
                    sediment -= deposit
                else:
                    erode = min((capacity - sediment) * erosion_rate, h * 0.1)
                    heightdata[iy][ix] -= erode
                    sediment += erode

                x, y = float(lowest[0]), float(lowest[1])
                water *= (1 - evaporation)
                if water < 0.01:
                    break

        return heightdata

    def setup_terrain(self):
        """地形の生成と設定"""
        # ハイトマップ生成
        heightmap = self.generate_heightmap()

        # GeoMipTerrain作成
        self.terrain = GeoMipTerrain("terrain")
        self.terrain.setHeightfield(heightmap)

        # 地形設定
        self.terrain.setBlockSize(32)
        self.terrain.setNear(40)
        self.terrain.setFar(200)
        self.terrain.setFocalPoint(self.camera)

        # 地形生成
        self.terrain.generate()

        # シーンに追加
        self.terrain_root = self.terrain.getRoot()
        self.terrain_root.reparentTo(self.render)
        self.terrain_root.setScale(self.terrain_scale, self.terrain_scale, self.height_scale)

        # テクスチャ生成(高さに応じた色分け)
        self.apply_terrain_texture(heightmap)

        # 定期的にLOD更新
        self.taskMgr.add(self.update_terrain, "update_terrain")

    def apply_terrain_texture(self, heightmap):
        """高さに応じた地形テクスチャを生成"""
        tex_img = PNMImage(self.terrain_size, self.terrain_size)

        for y in range(self.terrain_size):
            for x in range(self.terrain_size):
                height = heightmap.getGray(x, y)

                # 高さに応じて色を変える
                if height < 0.3:
                    # 低地: 緑(草原)
                    r, g, b = 0.2, 0.6, 0.2
                elif height < 0.5:
                    # 中腹: 黄緑
                    r, g, b = 0.4, 0.5, 0.2
                elif height < 0.7:
                    # 高地: 茶色(岩)
                    r, g, b = 0.5, 0.4, 0.3
                else:
                    # 山頂: 白(雪)
                    r, g, b = 0.9, 0.9, 0.95

                tex_img.setXel(x, y, r, g, b)

        tex = Texture()
        tex.load(tex_img)
        self.terrain_root.setTexture(tex)

    def update_terrain(self, task):
        """地形LODの更新"""
        self.terrain.update()
        return Task.cont

    def setup_player(self):
        """プレイヤー(シンプルな球体)の作成"""
        # プレイヤーノード
        self.player = self.render.attachNewNode("player")

        # 可視化用の球体
        self.player_model = self.loader.loadModel("models/misc/sphere")
        self.player_model.setScale(1.0)
        self.player_model.setColor(1, 0.5, 0, 1)  # オレンジ
        self.player_model.reparentTo(self.player)

        # 初期位置設定
        start_height = self.get_terrain_height(self.player_pos.x, self.player_pos.y)
        self.player_pos.z = start_height + 2.0
        self.player.setPos(self.player_pos)

    def setup_camera(self):
        """三人称追従カメラの設定"""
        # デフォルトマウス操作を無効化
        self.disableMouse()

    def setup_lighting(self):
        """ライティング設定(環境光+指向性光源+シャドウ)"""
        # 環境光の設定(全方向から均一に照らす光)
        ambient = AmbientLight('ambient')
        ambient.setColor(LColor(0.3, 0.3, 0.35, 1))  # やや暗めの環境光
        ambient_np = self.render.attachNewNode(ambient)
        self.render.setLight(ambient_np)

        # 指向性光源(太陽光のような平行光)
        sun = DirectionalLight('sun')
        sun.setColor(LColor(0.9, 0.85, 0.7, 1))  # 暖色系の太陽光

        # シャドウマッピングの設定
        sun.setShadowCaster(True, 2048, 2048)  # 影を有効化(2048x2048解像度)
        sun.getLens().setFilmSize(200, 200)     # 影の範囲(広いマップ用)
        sun.getLens().setNearFar(1, 400)        # 影の描画範囲

        sun_np = self.render.attachNewNode(sun)
        sun_np.setHpr(45, -60, 0)  # 光の方向を設定
        sun_np.setPos(640, 640, 150)  # 光源位置(シャドウ計算用)
        self.render.setLight(sun_np)

        # シャドウを有効化
        self.render.setShaderAuto()

        self.sun_np = sun_np  # 後で参照できるよう保持

    def setup_sky(self):
        """空の背景色設定"""
        self.setBackgroundColor(0.5, 0.7, 1.0, 1)  # 水色

    def setup_controls(self):
        """キー・マウス操作の設定"""
        # キーバインド
        self.accept("w", self.set_key, ["forward", True])
        self.accept("w-up", self.set_key, ["forward", False])
        self.accept("s", self.set_key, ["backward", True])
        self.accept("s-up", self.set_key, ["backward", False])
        self.accept("a", self.set_key, ["left", True])
        self.accept("a-up", self.set_key, ["left", False])
        self.accept("d", self.set_key, ["right", True])
        self.accept("d-up", self.set_key, ["right", False])

        # マウスドラッグでカメラ回転
        self.accept("mouse1", self.start_drag)
        self.accept("mouse1-up", self.stop_drag)

        # マウスホイールでズーム
        self.accept("wheel_up", self.zoom_in)
        self.accept("wheel_down", self.zoom_out)

        # 終了
        self.accept("escape", sys.exit)

    def start_drag(self):
        """マウスドラッグ開始"""
        self.mouse_dragging = True
        if self.mouseWatcherNode.hasMouse():
            self.last_mouse_x = self.mouseWatcherNode.getMouseX()
            self.last_mouse_y = self.mouseWatcherNode.getMouseY()

    def stop_drag(self):
        """マウスドラッグ終了"""
        self.mouse_dragging = False

    def zoom_in(self):
        """ズームイン"""
        self.camera_distance = max(10, self.camera_distance - 3)

    def zoom_out(self):
        """ズームアウト"""
        self.camera_distance = min(100, self.camera_distance + 3)

    def set_key(self, key, value):
        """キー状態の更新"""
        self.key_map[key] = value

    def get_terrain_height(self, x, y):
        """指定座標の地形高さを取得"""
        # 地形座標に変換
        tx = x / self.terrain_scale
        ty = y / self.terrain_scale

        # 範囲チェック
        if 0 <= tx < self.terrain_size - 1 and 0 <= ty < self.terrain_size - 1:
            elevation = self.terrain.getElevation(tx, ty)
            return elevation * self.height_scale
        return 0

    def update(self, task):
        """メインループ"""
        dt = globalClock.getDt()

        # マウスドラッグでカメラ回転
        self.handle_mouse_drag()

        # キー入力で移動
        self.handle_movement(dt)

        # カメラをプレイヤーに追従
        self.update_camera()

        return Task.cont

    def handle_mouse_drag(self):
        """マウスドラッグ処理"""
        if self.mouse_dragging and self.mouseWatcherNode.hasMouse():
            mx = self.mouseWatcherNode.getMouseX()
            my = self.mouseWatcherNode.getMouseY()

            # マウス移動量を計算
            dx = mx - self.last_mouse_x
            dy = my - self.last_mouse_y

            # カメラ角度を更新
            self.camera_heading -= dx * 100
            self.camera_pitch += dy * 50

            # ピッチ制限(0〜80度)
            self.camera_pitch = max(5, min(80, self.camera_pitch))

            self.last_mouse_x = mx
            self.last_mouse_y = my

    def handle_movement(self, dt):
        """移動処理(カメラの向きを基準に移動)"""
        # 移動ベクトル計算
        move_vec = Vec3(0, 0, 0)

        if self.key_map["forward"]:
            move_vec.y += 1
        if self.key_map["backward"]:
            move_vec.y -= 1
        if self.key_map["left"]:
            move_vec.x -= 1
        if self.key_map["right"]:
            move_vec.x += 1

        if move_vec.length() > 0:
            move_vec.normalize()
            move_vec *= self.move_speed * dt

            # カメラの向きに応じて移動方向を回転
            heading_rad = math.radians(self.camera_heading)
            cos_h = math.cos(heading_rad)
            sin_h = math.sin(heading_rad)

            new_x = move_vec.x * cos_h - move_vec.y * sin_h
            new_y = move_vec.x * sin_h + move_vec.y * cos_h

            self.player_pos.x += new_x
            self.player_pos.y += new_y

            # プレイヤーの向きをカメラの向きに合わせる
            self.player_heading = self.camera_heading

        # 地形に沿って高さを調整
        terrain_height = self.get_terrain_height(self.player_pos.x, self.player_pos.y)
        self.player_pos.z = terrain_height + 2.0

        # プレイヤー位置更新
        self.player.setPos(self.player_pos)
        self.player.setH(self.player_heading)

    def update_camera(self):
        """カメラをプレイヤーに追従させる"""
        # 球面座標でカメラ位置を計算
        heading_rad = math.radians(self.camera_heading)
        pitch_rad = math.radians(self.camera_pitch)

        # カメラ位置(プレイヤーを中心とした球面上)
        cam_x = self.player_pos.x - self.camera_distance * math.sin(heading_rad) * math.cos(pitch_rad)
        cam_y = self.player_pos.y - self.camera_distance * math.cos(heading_rad) * math.cos(pitch_rad)
        cam_z = self.player_pos.z + self.camera_distance * math.sin(pitch_rad)

        # カメラ位置設定
        self.camera.setPos(cam_x, cam_y, cam_z)

        # プレイヤーを見る
        self.camera.lookAt(self.player_pos + Vec3(0, 0, 2))


# メイン実行
if __name__ == "__main__":
    print("=== Open World Game ===")
    print("操作方法:")
    print("  WASD: プレイヤー移動")
    print("  左ドラッグ: カメラ回転")
    print("  ホイール: ズームイン/アウト")
    print("  ESC: 終了")
    print("地形を生成中...")

    game = OpenWorldGame()
    game.run()

実験・研究スキルの基礎:Windowsで学ぶ実験

1. 実験・研究のスキル構成要素

実験や研究を行うには、以下の5つの構成要素を理解する必要がある。

1.1 実験用データ

このプログラムではパラメータ値が実験用データである。地形生成に関わる数値(ノイズのシード、スケール、侵食パラメータなど)を変更することで、異なる結果を得る。

1.2 実験計画

何を明らかにするために実験を行うのかを定める。

計画例:

1.3 プログラム

実験を実施するためのツールである。このプログラムはPanda3DのGeoMipTerrainクラスとPerlinノイズアルゴリズムを使用している。

1.4 プログラムの機能

このプログラムは複数のパラメータで地形生成を制御する。

主要な入力パラメータ:

出力情報:

パラメータ変更の方法:

1.5 検証(結果の確認と考察)

プログラムの実行結果を観察し、パラメータの影響を考察する。

基本認識:

観察のポイント:

2. 間違いの原因と対処方法

2.1 プログラムのミス(人為的エラー)

プログラムがエラーで停止する

ウィンドウが表示されない

地形生成に時間がかかる

2.2 期待と異なる結果が出る場合

地形が平坦すぎる

地形が険しすぎる

侵食の効果が見えない

フレームレートが低下する

3. 実験レポートのサンプル

侵食シミュレーションが地形の自然さに与える影響

実験目的:

侵食シミュレーションの反復回数が地形の自然さに与える影響を確認し、処理時間とのバランスが取れる設定を見つける。

実験計画:

他のパラメータを固定し、侵食シミュレーションのiterations値を変化させて地形を比較する。

実験方法:

プログラムのiterations値を変更して実行し、以下の基準で評価する。

実験結果:

iterations 処理時間 谷の形成 尾根の鮮明さ 総合評価
xxxx x秒 x x x
xxxx x秒 x x x
xxxx x秒 x x x
xxxx x秒 x x x

考察:

結論:

(例文)本実験においては、iterations=xxxxが処理時間と地形の自然さのバランスが取れた設定であった。即時生成が必要な場合はxxxxを、品質を優先する場合はxxxxが適切である。用途に応じてパラメータを調整する必要性が確認できた。