【Godot4.x】タワーディフェンスのサンプルプロジェクト

Godot Engine でタワーディフェンスを作ったので、プロジェクトを公開します。

目次

タワーディフェンスのサンプルプロジェクト

プロジェクトファイル

プロジェクトファイルはGitHubにアップロードしています。

ライセンスについて

プロジェクトファイルは自由に改変が可能です。
再配布も「無改造のものをそのまま販売する」といったことでなければ、自由に使っていただいて問題ありません(例えば、画像を差し替えたり、ステージクリアタイプのゲームに変更すれば、配布したり販売してOK)。

 

ただ “assets/tiles/pipo_map001.png” については、ぴぽや様よりお借りしている画像ですので、ぴぽや様の利用規約に従う必要があります。

遊び方

このゲームは、通常の弾のみ発射するタワー、ホーミング弾を発射するタワー、レーザーを発射するタワーの「3つ」のタワーを駆使して、より高いWave数を目指すタワーディフェンスです。

操作はマウスのみとなります。

  • 左クリック:各種メニューの選択
  • 右クリック:タワーの購入メニューやアップグレードメニューを表示する

開発全般について

1つのマップをエンドレスでプレイする構成で、過去に作ったタワーディフェンスを参考に作りました。

Wave数に応じて敵のパラメータが増加することで、それに対抗するためにタワーを強化する必要があります。プレイヤーができることは、「タワーの購入と配置」「タワーのアップグレード」の2つだけとなります。

開発期間

開発期間は、2023.5.5 から 2023.5.13 まで。平日はあまり作業できなかったので、実質5日ほどです。

実装手順

開発ログを見るとおおよそ以下の順番で実装したようです。

  1. タイル情報の作成
  2. 敵が経路に沿って動く処理の実装
  3. タワーオブジェクトの作成
  4. タワーが敵に向かってショットを撃つ処理を実装
  5. タワーの購入メニューを実装
  6. タワーのパラメータ処理を実装
  7. Wave数に応じた敵の出現頻度やHP、移動速度の実装
  8. ホーミングミサイル、レーザーの実装
  9. 各種タワーを選んで購入できるようにした
  10. アップグレードメニューの実装
  11. Wave開始演出やヘルプテキストの実装
  12. ヒットエフェクトや消滅エフェクトの実装
  13. ゲームオーバーの実装
  14. ゲームバランスの調整。ダメージ演出(Vignette)の実装
  15. BGM/SEの実装

それと実際に作り始める前に、AIチャットで「タワーディフェンスの作り方」を聞いてから本制作に入りました。そのあたりの情報はこのページの最後にまとめています。

自己ベストのスコア

作者の最高記録は Wave 86 でした。

プロジェクト構成

モジュール構成

ゲームの動作に関連するモジュールの一覧は以下のとおりです。

モジュール一覧
TowerDefense
 +-- Main.tscn/.gd: メインシーン
 +-- src
      +-- common
      |    +-- Common.tscn/.gd: 共通モジュール
      |    +-- Ease.tscn/.gd: イージング関数定義
      |    +-- Game.gd: ゲームパラメータの定義
      |    +-- Main.gd: マップ関連のユーティリティ
      |
      +-- effects
      |    +-- Particle.tscn/.gd: エフェクト
      |    +-- ParticleAscii.tscn/.gd: 文字エフェクト
      |    +-- VignetteFilter.tscn: ダメージエフェクト
      |    +-- VignetteFilter.gdshader: Vignetteシェーダー
      |
      +-- enemy
      |    +-- Enemy.tscn/.gd: 敵
      |    +-- EnemySpawnMgr.gd: 敵の生成管理
      |
      +-- menu
      |    +-- MenuBuy.tscn/.gd: タワー購入メニュー
      |    +-- MenuCommon.gd: メニュー共通
      |    +-- MenuUpgrade.tscn/.gd: アップグレードメニュー
      |
      +-- shot
      |    +-- Shot.tscn/.gd: 通常砲台のショット
      |    +-- ShotHorming.tscn/.gd: ホーミングミサイル
      |    +-- ShotLaser.tscn/.gd: レーザー
      |
      +-- Tower.tscn/.gd: タワー
      +-- WaveStart.tscn/.gd: Wave開始演出

リソースの説明

画像データやフォントデータ、サウンドデータの一覧は以下のとおりです。

リソース一覧
TowerDefense
 +-- default_bus_layout.tres: オーディオレイアウトの設定
 +-- assets
      +-- default_theme.tres: デフォルトのthemeファイル
      +-- health_bar.tres: 体力ゲージのthemeファイル
      +-- fonts
      |    +-- Xolonium-Regular.ttf: オープンフォントライセンスのフォント
      |    +-- mplus-1m-regular.otf: M+フォント
      |    +-- bmp
      |         +-- bmpfont.fnt: ビットマップフォント
      |         +-- bmpfont.png: ビットマップフォント画像
      |         +-- bmpfont.tres: ビットマップフォントリソース
      |
      +-- images
      |    +-- cross.png: 配置(ビルド)不可の×画像
      |    +-- cursor.png: カーソル
      |    +-- enemy.png: 敵画像
      |    +-- enemy_mask: 敵ヒット時の赤点滅画像
      |    +-- heart.png: ハート画像
      |    +-- laser.png: レーザー画像
      |    +-- missile.png: ホーミングミサイル
      |    +-- particle.png: エフェクト(丸)
      |    +-- ring.png: エフェクト(リング)
      |    +-- shot.png: 通常ショット
      |    +-- tower.png: 通常タワー
      |    +-- tower_horming.png: ホーミングタワー
      |    +-- tower_laser.png: レーザータワー
      |
      +-- sound
      |    +-- bgm
      |    |    +-- bgm01_bpm132.mp3: BGM1
      |    |    +-- bgm01_intro_bpm132.mp3: BGM1のイントロ
      |    |    +-- bgm02_bpm132.mp3: BGM2
      |    |    +-- bgm03_bpm132.mp3: BGM3
      |    |    +-- bgm04_bpm132.mp3: BGM4
      |    |    +-- bgm05_bpm132.mp3: BGM5
      |    |
      |    +-- se
      |         +-- break.wav: 敵が拠点にダメージを与えたときのSE
      |         +-- build.wav: タワー建設時のSE
      |         +-- destroy.wav: 敵破壊SE
      |         +-- hit.wav: 敵ダメージSE
      |         +-- laser.wav: レーザー発射SE
      |         +-- start.wav: Wave開始時
      |         +-- upgrade.wav: アップグレードSE
      |
      +-- tiles
           +-- pipo_map001.png: ぴぽや様よりお借りしているタイル画像
           +-- tile_set.tres: タイルセットリソース

タイルマップ

タイルマップレイヤー

タイルマップのレイヤーは「Ground(地面)」「Terrain(地形)」の2つのレイヤーから構成されています。

カスタムデータについて

タワーを建設するときに、「敵が通過する道」や「」などタワーを配置できない場所があります。

これはタイルセットに “cant_build” というカスタムデータを設定することで対応しています。

カスタムデータに “cant_build” フラグが設定されているかどうかは、Map.gdcant_build_position() で判定しています。

## 砲台の設置できない場所かどうか.
func cant_build_position(pos:Vector2i) -> bool:
	for layer in eTileLayer.values():
		var data = _tilemap.get_cell_tile_data(layer, pos)
		if data == null:
			continue
		if data.get_custom_data("cant_build"):
			return true # 配置できない場所.
	
	# 配置可能.	
	return false

その他、タイルマップに関する詳しい情報は以下のページにまとめています。

【Godot4.x】タイルマップの基本的な使い方

敵の制御について

敵は src/Enemy.gd で実装しています。

経路に沿った移動の実装

Path2D と PathFollow2D を使って敵の移動経路をあらかじめ作り、それに合わせて移動させています。

Path2D と PathFollow2D の使い方について、詳しくは以下のページにまとめています。

【Godot】Path2D/PathFollow2Dを使って決まった経路で動かす方法

Path2Dに沿って移動するためには、その子として「PathFollow2D」として登録し、さらにひ孫として「Enemy」を登録します。

そのためノードの階層として以下のようになります。

他のオブジェクトは CanvasLayer に直接ぶら下がっていますが、Enemyだけ Path2D / PathFollow2D にぶらさがる特殊なデータ構造となっています。

そのため、Enemy.gd の setup() では引数として渡された Path2D の子として PathFollow2D を登録し、その下に Enemy自身を登録する…という流れになります。

# PathFollow2Dが親となる.
var _parent:PathFollow2D = null

...

## セットアップ.
func setup(path2d:Path2D) -> void:
	_parent = PathFollow2D.new()
	_parent.loop = false # ループしない.
	_parent.rotates = false # 回転しない.
	_parent.add_child(self) # 自身を登録する.
	
	# Path2Dに登録する.
	path2d.add_child(_parent)

少し特殊なデータ構造となりますが、経路探索を自作するよりは簡単と考え、Godotの機能で移動させることにしました。

そして、PathFollow2D.progress の値を変化させることで、ピクセル単位での移動量が設定できます。.

## 手動更新 (_process()は使わない).
func update_manual(delta:float) -> void:
	_cnt += 1
	
	# 移動処理 ("progress" はピクセル単位での移動量を指定できる)
	_parent.progress += MOVE_SPEED * _speed * delta

といった感じで実装したのですが、今回 Path2D / PathFollow2D をガッツリ使ったことで様々な問題点とその解決法がわかったので、それを以下にまとめていきます。

PathFollow2D で動かすオブジェクトの position は (0, 0) となる

タワーが敵を狙い撃ちするとき、敵の座標が正しく取れずに少し悩みました。というのも、PathFollow2D で動かしたノードの position がなぜか (0, 0) となるためです。

ただこれはよく考えるとわかるのですが、動いているのは PathFollow2D であって、その子ノードではないためです。ではどうやって Enemy の正しい座標を取得するのかというと、”global_position” を使用します。

例えば以下は Common.gd に定義している、指定の位置から一番近い敵のインスタンスを取得する関数です。

## @param pos 基準の位置.
## @param range 射的距離.
func search_nearest_enemy(pos:Vector2, range:float) -> Enemy:
	var enemies = get_enemies()
	var dist = 99999
	var ret:Enemy = null
	for enemy in enemies:
		var dist2 = enemy.global_position.distance_to(pos) # ※ global_position で座標を取る
		dist2 -= enemy.get_size() # 半径を考慮する.
		if dist2 < dist:
			# より近い.
			dist = dist2
			if dist <= range:
				# 射程範囲内.
				ret = enemy
	return ret

距離を計算するところで “global_position” プロパティで敵の座標を取得しています。このプロパティは親からの相対的な座標移動を含めた最終的な座標を取得するものとなります。

PathFollow2D の回転を許可すると UI も回転してしまう

もう1つ悩んだのが、敵の体力ゲージです。

敵の体力はこのように下の部分に表示しているのですが、PathFollow2D の回転を許可すると曲がり角で「体力ゲージも一緒に回転」してしまいます

ノード階層が以下のようになっているので、”Enemy” を回転すれば子である体力ゲージも一緒に回転するのは当たり前だよなぁ…と気が付きました。

ただ、調べたところ子の回転をロックする(回転させない)方法はなさそうだったので、PathFollow2D の回転を無効にして、親(PathFollow2D)の「現在の座標から前回からの座標を引いて求めた移動ベクトルに対応する回転値」を敵スプライトのみ反映させる…という対処方法をしました。

## 回転処理.
func _update_rotate():
	var pos = _parent.position # 現在の座標.
	var d = pos - _prev_pos # 前回の座標からの差分で移動ベクトルを求める.
	var angle = lerp_angle(_spr.rotation, d.angle(), 0.1*Common.game_speed) # Vector2.angle() で回転値が求まる.
	_spr.rotation = angle # 回転値を反映.
	_mask.rotation = angle # 回転値を反映.
	_prev_pos = pos # 前回の座標として保存.

シーン・オブジェクト関連

ゲームオブジェクトの更新

ゲームオブジェクトの更新は基本的に Main.gd から行うようにしました。

これは、オブジェクトの更新に _process() / _physics_process() は使わないようにして、update_manual() という独自の更新関数を呼び出すようにしている、ということです。

## 更新 > ゲームオブジェクト.
func _update_objs(delta:float) -> void:
	
	_selected_tower = null
	var tower_cnt = _tower_layer.get_child_count()
	# タワー更新.
	for tower in _tower_layer.get_children():
		tower.update_manual(delta) # ※手動更新.
		# 選択状態も更新しておく.
		var mouse = Map.get_grid_mouse_pos()
		var pos:Vector2i = Map.world_to_grid(tower.position)
		tower.selected = (mouse == pos)
		if tower.selected:
			_selected_tower = tower
	
	if is_instance_valid(_selected_tower):
		_change_help(eHelp.UPGRADE)
	elif Common.wave == 0:
		if tower_cnt > 0:
			_change_help(eHelp.NEXT_WAVE)
	else:
		_change_help(eHelp.NONE)
	
	# 敵の更新.
	_update_enemy(delta)  # ※手動更新.
	# ショットの更新.
	for shot in _shot_layer.get_children():
		shot.update_manual(delta)  # ※手動更新.

このように実装することで、ポーズ画面(オブジェクトをすべて停止する)では _update_objs() を呼び出さないようにしたり、倍速再生する場合には “delta” の値に倍率を掛ける、といった実装でオブジェクトの制御がやりやすくなるためです。

一応、Godot Engine にはポーズ機能が用意されていたり、フレームレートを上げることで倍速再生を実装できます。

【Godot】ゲームを一時停止するポップアップウィンドウの実装方法 【Godot】スロー再生する方法

ただ個人的にはスクリプトでフレームレートの制御を細かく指定できたほうが良いと考えているため、独自の実装に置き換えています。

タワーの実装について

タワーは src/Tower.gd で実装しています。基本的な実装はシンプルで、get_range() で取得した射程範囲内に敵が存在しているかどうかを判定して、敵がいたら _shot() で各ショットを発射しています。

タワーのステータス表示と射程範囲表示

少し特殊な実装がステータス表示と射程範囲の表示です。

カーソルを合わせるとステータスが左下から拡大して表示されます。

これは UIを構成するノードに親となる Node2D を挟み込み、このノードのスケール値を変化させることで実現しています。

そして射程範囲は _draw() で実装しました。draw_circle() が塗りつぶしの円で、draw_arc() を使うと円弧を描くことができます。

## 描画.
func _draw() -> void:
	_help.visible = false
	if selected:
		# ヘルプポップアップの表示.
		_help.visible = true
		var rate = 1.0 - Ease.expo_in(_selected_timer / SELECTED_TIMER)
		_help.scale = Vector2.ONE * rate
		# 射程範囲の描画.
		var range = get_range() * rate
		var color = Color.AQUA
		color.a = 0.3
		# 塗りつぶし円の描画.
		draw_circle(Vector2.ZERO, range, color)
		var color2 = Color.WHITE
		# 円をいくつか描画.
		for ofs in [0, 0.1, 0.2]:
			var rate2 = fmod((_timer + ofs) * 0.5, 1.0)
			color2.a = 1.0 - rate2
			draw_arc(Vector2.ZERO, range*Ease.expo_out(rate2), 0, 2*PI, 32, color2)

ショットの実装について

タワーが発射するショットは src/shot 以下のモジュールで実装しています。

src/shot
      +-- shot
           +-- Shot.tscn/.gd: 通常砲台のショット
           +-- ShotHorming.tscn/.gd: ホーミングミサイル
           +-- ShotLaser.tscn/.gd: レーザー

ホーミングの実装について

ホーミングの回転する角度は Common.diff_angle() で角度差を取得しています。

func _update_horming(delta:float, target:Enemy) -> void:
	if delta <= 0:
		# 時間停止しているときは動かない.
		return
		
	var d = target.global_position - position
	var diff = Common.diff_angle(_deg, rad_to_deg(atan2(-d.y, d.x)))
	var rate = min(1.0, 0.1 * Common.game_speed)
	_deg += diff * rate
	set_velocity(_deg, _speed)

ホーミングを実装する方法は Godot を始めたときからこの方法で実装していて、たとえばホーミングレーザーの実装サンプルは以下のページに記載しています。

【Godot】ホーミングレーザーの実装サンプル

ただ最近気がついたのですが、lerp_angle() という関数で角度差を計算する方法もあるようです。

なお、この方法で指定する角度の単位は radian であることに注意です。

また私の回転の扱いは基本「反時計周り (+90 が上)」で制御していますが、Godot Engine は「時計回り (+90が下)」になっているので、そのあたりもゲームエンジンに合わせた実装にするべきなのかもしれません…。

レーザーの実装について

レーザーは左端の中央を原点として、画面サイズより少し大きいくらいまで拡大する…という実装となっています。

これにより、レーザー自身を回転させることで、発射方向を制御することができる実装となっています。

そして消えるときにはY軸方向への縮小をすることでレーザーが収束する動きを実現しています。

ちなみに、Area2Dの当たり判定である Area Enteredシグナルは接触を開始した瞬間のみの判定となるので、連続ヒットダメージの実装となっていません。

なので、レーザーの連続ヒット (衝突時間が長いほどダメージが入る) を実現するには、当たり判定を少し工夫する必要があります(例えば、area_enteredでヒットフラグを立て、area_exitedでヒットフラグを下げるなど。ヒットフラグが立っているときだけダメージ処理をする)。

Mainシーンの制御

ゲームシーンは Main.gd ですべて処理しています。

状態と操作モードの管理

状態は “eState” という enum で定義しています。

  • eState.STANDBY: Wave開始前の準備状態
  • eState.MAIN: Wave開始後(メインゲーム)
  • eState.GAMEOVER: ゲームオーバー

もしステージクリアタイプのゲームデザインに変更する場合は、ゲームクリア状態を追加することになると思います。

状態と別で操作モード “eMode” を定義しています。

  • eMode.FREE: 自由操作
  • eMode.BUY: 購入メニューの表示
  • eMode.BUILD: タワーの配置モード
  • eMode.UPGRADE: アップグレードメニューの表示

ゲームオブジェクト更新(_update_objs)の呼び出しが必要な操作モードは eMode.FREE だけでそれ以外は一時停止状態として、操作を行えるようにしています。

ただこれらの処理は eState.GAMEOVER のときには呼び出されないようになっています。もしこのゲームを改造して eState に別の状態を追加する場合は、このあたりの制御も気をつける必要がありそうです。

メニューのオブジェクト階層

今回のゲームで使用するメニューは「タワーの購入」「タワーのアップグレード」の2つとなっていて、対応するモジュールは以下の通りです

メニューのオブジェクト階層
MenuCommon.gd: メニュー共通
 +-- MenuBuy.tscn/gd: 購入メニュー
 +-- MenuUpgrade.tscn/gd: アップグレードメニュー

前述したとおり、オブジェクトの更新は Main.gd の _udate_objs() を呼び出すかどうかで制御できるようにしたので、メニュー画面はメニューシーンのインスタンスを生成して、closed() で閉じたかどうかを判定するだけのシンプルな実装になっています。
そしてメニューの共通シーンとして MenuCommon.gd を定義しています。背景の暗転や選択項目の結果、閉じたかどうかの判定は MenuCommmon.gd で共通化しています。

ダメージ演出 (Vignette効果)

ダメージ演出として Vignette を表示するようにしました。Vignette とは画面の周りを暗くするようにして、レトロな雰囲気や視野を狭くすることからくる不安感などを表現するレンズ効果ですね。

Vignette画像はPhotoshopなどで作っても良いのですが、シェーダーの勉強も兼ねてシェーダーで実装してみました。
ただ、一から作る方法はわからなかったので、GodotShadersからお借りしています。

ちなみにこちらのシェーダーコードは Godot4 ではエラーとなっていたのでそこだけ手直ししました。どうも SCREEN_TEXTURE キーワードがGodot4では使えなくなって、hint_screen_textureを使う必要があるようです。

ゲームバランス

Game.gdにゲーム関連のパラメーターをすべて定義しています。もしゲームバランスを調整したい場合には、このあたりのパラメータを変化させて遊んで見るのが良いかもしれません。

Tower

パラメータ

タワーには以下の3つのパラメータがあります。

  • 射程範囲 (Range)
  • 攻撃威力 (Power)
  • 発射間隔 (Firerate)

コスト

  • 製造コスト (Build)
  • 各種パラメータのアップグレードコスト (Range/Power/Firerate)

Enemy

Wave数が上昇するごとに敵のパラメータが上昇(変化)します。

  • HP
  • 所持金 (Money)
  • 出現間隔 (Interval)
  • そのWaveでの出現数 (Spawn Number)
  • 移動速度

BGM

ループ区間の設定

ループ区間ですが、なぜか FLStudio で出力した BGM はループにスキマが生まれてしまうようです(もしかしたら使用したループ素材の問題かもしれません)。

そこで、手動でループ区間を設定しました。設定方法は以下のページにまとめています。

【Godot4.x】サウンドのループがキレイにつながらないときの対処方法

BGM素材について

最近は曲作りもあまりできていないので、サンプル素材の組み合わせで作りました。使用したループ素材は以下のものとなります。

Unity Samples – Techno Bundle 4
(なんかずっと80%OFFセールしているので、この価格が定価なのかもしれません…)

手っ取り早くBGMを作るのであれば、こういったサンプル素材を使うとクオリティが高いものができて良いですね。

ちなみに DAW は FLStudio を使っています。

商品の情報は 1つ前のバージョンですが、FLStudio は Lifetime Free Updates という「生涯無料アップデート可能」という メジャーな DAW の中でも唯一無二のライセンス形式を提供しているのでおすすめです(なんと、支払いは初回だけ)。日本ではあまり使われていないのか日本語解説情報が少ないですが、Godot Engine で英語に慣れることを余儀なくされているユーザーの方々であれば全然、苦ではないはず。

今回はサンプル素材だけで曲を作るというズルをしてしまいましたが、最近曲作り熱が上がってきたため、FLStudio専用のMIDIキーボード+MIDIコントローラーである “FLkey 37” を衝動買いしました。

紹介動画を見ての衝動買いでしたが、実際に2週間ほど使ってみたところ、Sequenceモードでのドラムの打ち込みや、FLEXのプリセットがボタン1つで切り替えられたりと、かなり快適に曲作りができますね。あとしっかりした作りなのでピアノマーベルでのピアノ練習用キーボードとしても使っています。

それと、気になって作曲に関する情報をいくつか調べた結果をまとめたりしたので、曲作りをやってみたい方は以下のページが参考になるかもしれません。

作曲を始めるときのメモ 作曲を始めるときのメモ

(余談終わり)

テンポ同期によるシームレスなBGM切り替えの実装

前回作った「Stomping Shooter」でも実装しましたが、今回も曲の拍の切り替わりでBGMを切り替える、という実装をしています。

【Godot】Stomping Shooterで使った技術と得られた知見まとめ

Main.gd の _update_bgm() がその実装部分です。

## サウンドの更新.
func _update_bgm():
	if _now_bgm == _next_bgm:
		return # 変更不要.
		
	var _can_change = true
	if _bgm.playing:
		var pos = _bgm.get_playback_position()
		var stream:AudioStreamMP3 = _bgm.stream
		var measure = stream.get_length() / stream.beat_count
		measure *= 4 # 4小節単位で切り替える.
		var d = fmod(pos, measure)
		if 0.1 < d:
			_can_change = false
	if _can_change:
		# BGM変更.
		_now_bgm = _next_bgm
		_bgm.stream = load(_now_bgm)
		_bgm.play()

measure” が その曲の1拍に対応する時間の値です。

前回との違いは、AudioStreamMP3 にビート数 を設定することで、BPMを直接指定する必要がなくなったことですね。

ちなみにビート数はこの値です。

これで BPM を指定しなくても 1拍に対する秒を計算できるようになります。

拡張の手引

今回のタワーディフェンスをもし機能拡張するのであれば以下のアイデアが考えられます。

  • ステージクリア制の導入:一定Wave数を耐えられればステージクリア
  • バフユニットの実装:バフ効果として、範囲内にいる味方のタワーの性能を向上するなど
  • デバフユニットの実装:敵にスリップダメージを与える、移動速度を低下させるなど

AIに聞いてみたタワーディフェンスの作り方

久々にタワーディフェンスを作ろうとして、何をすべきか曖昧だったので、AIに聞いてから作り始めました。AIチャットを使って得られた情報を以下にまとめておきます。

タワーディフェンスの作り方

タワーディフェンスの作り方は、ゲームエンジンやプログラミング言語によって異なりますが、一般的には以下のような手順があります

  • ゲームの設計と仕様を決める
  • ゲームの画面やオブジェクトを作成する
  • ゲームのロジックやアルゴリズムを実装する
  • ゲームのテストやデバッグを行う
  • ゲームの調整や改善を行う

この中で、特に重要なのはゲームのロジックやアルゴリズムです。タワーディフェンスでは、敵や味方のユニットの移動や索敵、戦闘などを制御する必要があります。これらを実現するためには、ユニットの一元管理やAIの基本を理解する必要があります

ゲームのロジックやアルゴリズムについて

タワーディフェンスのロジックやアルゴリズムとは、ゲームのルールや挙動を決めるプログラムのことです。タワーディフェンスでは、以下のようなロジックやアルゴリズムが必要です

  • マップデータの作成と読み込み:ゲームの舞台となるマップを作成し、ゲームエンジンで読み込む方法を実装します。マップには敵の移動経路やタワーの設置場所などの情報が含まれます1
  • 敵の生成と移動:敵を一定の間隔で生成し、マップ上の経路に沿って移動させる方法を実装します。敵には種類や能力などの属性があります。
  • タワーの設置と攻撃:タワーをマップ上の任意の場所に設置し、敵に攻撃する方法を実装します。タワーには種類や能力などの属性があります。
  • 敵とタワーの戦闘:敵とタワーが互いにダメージを与えたり、状態異常を発生させたりする方法を実装します。戦闘には確率や計算式などが関わります。
  • ゲームの進行と終了:敵が全滅したり、タワーが破壊されたりするとゲームが終了する方法を実装します。ゲームにはスコアやレベルなどの要素があります

タワーの設置を面白くするアイデア

タワーの設置を面白くするアイデアは、ゲームの目的や難易度によって異なりますが、一般的には以下のようなものがあります。

  • タワーの設置場所を制限する:タワーを設置できる場所を限定することで、戦略的な配置を考えさせることができます。例えば、通路の外や特定の地形だけに設置できるようにするなどです。
  • タワーの設置コストを設定する:タワーを設置するためには一定のコストが必要になるようにすることで、資源の管理を考えさせることができます。例えば、コインやエネルギーなどを消費するようにするなどです 。
  • タワーの設置効果を変化させる:タワーを設置するときに、その場所や周囲の状況に応じてタワーの効果が変化するようにすることで、バリエーションを増やすことができます。例えば、高い場所や水辺などに設置すると攻撃力や射程が変わるようにするなどです 。

敵の生成や移動を面白くするアイデア

タワーの性能のバリエーションとは、タワーの種類や能力を多様化することで、ゲームの戦略や楽しさを増やすことです。タワーの性能のバリエーションは、以下のような要素で表現できます。

  • 攻撃力:タワーが敵に与えるダメージの量です。攻撃力が高いタワーは敵を素早く倒すことができますが、コストや射程などの制限があることが多いです。
  • 射程:タワーが攻撃できる範囲です。射程が広いタワーは敵を遠くから攻撃することができますが、攻撃力や速度などの制限があることが多いです。
  • 速度:タワーが攻撃する間隔です。速度が速いタワーは敵に連続して攻撃することができますが、攻撃力や射程などの制限があることが多いです。
  • 特殊効果:タワーが敵に与える状態異常や補助効果です。特殊効果を持つタワーは敵の動きや能力を妨害したり、自分のタワーや領地を強化したりすることができますが、コストや条件などの制限があることが多いです。

敵の性能のバリエーションについて教えてください

敵の性能のバリエーションとは、敵の種類や能力を多様化することで、ゲームの難易度や楽しさを増やすことです。敵の性能のバリエーションは、以下のような要素で表現できます。

  • 体力:敵が受けるダメージに耐える量です。体力が高い敵はタワーの攻撃に強くなりますが、速度や特殊効果などの制限があることが多いです。
  • 速度:敵が移動する速さです。速度が速い敵はタワーの攻撃を避けやすくなりますが、体力や攻撃力などの制限があることが多いです。
  • 攻撃力:敵がタワーや領地に与えるダメージの量です。攻撃力が高い敵はタワーや領地を素早く破壊することができますが、体力や速度などの制限があることが多いです。
  • 特殊効果:敵が持つ状態異常や補助効果です。特殊効果を持つ敵はタワーの攻撃や効果に抵抗したり、自分の能力を強化したりすることができますが、コストや条件などの制限があることが多いです。

タワーディフェンスにおけるウェーブについて

タワーディフェンスにおけるウェーブとは、敵が生成される回数や順序のことです。ウェーブは、ゲームの進行や難易度を調整する重要な要素です。ウェーブの設計には、以下のようなポイントがあります。

  • ウェーブの数を決める:ウェーブの数は、ゲームの長さや目標を決める要素です。ウェーブの数が多いほど、ゲームは長くなりますが、飽きやすくなる可能性もあります。ウェーブの数が少ないほど、ゲームは短くなりますが、難しくなる可能性もあります。
  • ウェーブの間隔を決める:ウェーブの間隔は、ゲームのペースや緊張感を決める要素です。ウェーブの間隔が短いほど、ゲームは速くなりますが、ストレスや混乱を感じやすくなる可能性もあります。ウェーブの間隔が長いほど、ゲームは遅くなりますが、退屈や不満を感じやすくなる可能性もあります。
  • ウェーブの内容を決める:ウェーブの内容は、ゲームのバリエーションや難易度を決める要素です。ウェーブの内容には、敵の種類や数、生成パターンや移動経路などが含まれます。ウェーブの内容を変化させることで、ゲームにサプライズや挑戦を与えることができます。

タワーディフェンスの名作について

タワーディフェンスの名作として、以下のゲームがおすすめです。

  • キャッスルストーム:自分の城を守りながら、敵の城を攻撃するタワーディフェンスです。城の設計や兵士の編成、魔法や投石などのアクション要素が楽しめます。
  • Infinitode 2 – Infinite Tower Defense:シンプルな見た目で奥が深いタワーディフェンスです。無限に広がるマップや多彩なタワー、カスタマイズやリサイクルなどのやりこみ要素が魅力です。
  • メゾン・ド・魔王:アパート経営とタワーディフェンスを組み合わせた異色作です。魔王となって人間に対抗するために、アパートに住むモンスターを育てたり、防衛施設を設置したりします。

これらのゲームは、Steamで遊ぶことができます。

タワーディフェンスである “プラント vs ゾンビ” が面白い理由を教えてください

プラント vs ゾンビとは、EAが開発したタワーディフェンスのゲームシリーズです。プラント vs ゾンビが面白い理由は、以下のような点が挙げられます。

  • キャラクターの個性:プラント vs ゾンビでは、様々な種類や能力を持つ植物とゾンビが登場します。植物はゾンビを撃退するための攻撃や防御、特殊効果などを持ち、ゾンビは植物を食べるための移動や攻撃、特殊効果などを持ちます。キャラクターのデザインやアニメーションもユニークで魅力的です。
  • ゲームモードの多彩さ:プラント vs ゾンビでは、様々なシチュエーションやルールでゲームを楽しむことができます。アドベンチャーモードでは、昼夜や霧、水中などの環境でゾンビと戦います。他にも、ミニゲームやパズル、サバイバルなどのモードがあります。また、シリーズ作品では、時代や世界を旅するモードや、カードバトルのモードなどもあります。
  • 戦略性と遊び心:プラント vs ゾンビでは、植物とゾンビの組み合わせや配置によって、ゲームの難易度や楽しさが変わります。自分の好みや目的に合わせて、植物を選んだり、アップグレードしたりすることができます。また、ゲームにはユーモアやサプライズが満載で、笑いや驚きを与えてくれます。

プラント vs ゾンビの戦略性の奥深さ

プラント vs ゾンビの戦略性の奥深さについて、以下のような点が挙げられます。

  • 植物やゾンビの種類と特徴:プラント vs ゾンビには、攻撃や防御、補助などの役割を持つ様々な植物と、武器や能力、行動パターンなどの特徴を持つ様々なゾンビが登場します。これらの植物やゾンビを組み合わせて、自分の戦略を考えることができます。
  • 資源の管理と選択:プラント vs ゾンビでは、植物を配置するために必要な太陽を資源として管理する必要があります。太陽は一定時間ごとに落ちてくるほか、太陽を生成する植物を使うこともできます。しかし、太陽は有限であり、植物によっては多くの太陽を消費します。また、一度に使える植物の種類も限られています。そのため、どの植物をどこに配置するか、どの植物を優先的に使うかなどの選択が重要になります。
  • 状況に応じた対応:プラント vs ゾンビでは、昼夜や場所によってゲームの状況が変化します。例えば、夜は太陽が少なくなり、霧や屋根などの障害物が出現したり、水辺やプールなどの特殊な地形が登場したりします。また、ゾンビもレベルごとに強さや種類が変わります。そのため、状況に応じて植物の種類や配置を変えたり、新しい植物やアイテムを使ったりすることが求められます。