<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>アリッシアの朝</title>
	<atom:link href="https://alicia-ing.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://alicia-ing.com</link>
	<description></description>
	<lastBuildDate>Sun, 18 Oct 2026 12:33:09 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://alicia-ing.com/wp-content/uploads/2023/05/cropped-alicia-32x32.webp</url>
	<title>アリッシアの朝</title>
	<link>https://alicia-ing.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>【Flutter】Gemini APIの使い方｜AIに質問するアプリを作成</title>
		<link>https://alicia-ing.com/programming/flutter/gemini-api-application/</link>
					<comments>https://alicia-ing.com/programming/flutter/gemini-api-application/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sun, 18 Oct 2026 12:31:55 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=9239</guid>

					<description><![CDATA[アプリに AI を組み込みたいとき、手軽に試せるのが Google の Gemini API です。文章を送ると、AI が考えた返事が文章で返ってきます。 Flutter からは、パッケージを1つ追加するだけで Gemi [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">入力欄に書いた質問（マイクで話した質問も）を Gemini に送り、AI の返事を画面に表示するアプリを作れます。</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/10/complete-4.webp" alt="" class="wp-image-9241" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/complete-4.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-4-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-4-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-4-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-4-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-4-640x360.webp 640w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">アプリに AI を組み込みたいとき、手軽に試せるのが Google の <strong>Gemini API</strong> です。文章を送ると、AI が考えた返事が文章で返ってきます。</p>



<p class="wp-block-paragraph">Flutter からは、パッケージを1つ追加するだけで Gemini を呼び出せます。ただし、<strong>混雑して返事が来ないことがある</strong>、<strong>返事に記号がそのまま出る</strong>など、動かしてみて初めて気づく落とし穴もあります。</p>



<p class="wp-block-paragraph">本記事では、質問を送って返事を表示するまでの流れを解説します。また、混雑して返事が来ないときに、自動でやり直す仕組みも入れます。使っているパッケージが非推奨になった点と、これから始める人向けの選び方も紹介します。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Flutter のアプリから Gemini（AI）を呼び出したい人</li>



<li>Gemini API に質問を送って、返事を画面に出すまでの流れを知りたい人</li>



<li>返事が来ない（混雑する）ときの対策を知りたい人</li>



<li>APIキーの扱いで気をつけることを知りたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="9239" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">Gemini API（google_generative_ai）</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">APIキーの取得</a></li><li class="rtoc-item"><a href="#rtoc-3">パッケージの追加と.envの設定</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">google_generative_aiは非推奨｜これから始めるならFirebase AI Logic</a></li><li class="rtoc-item"><a href="#rtoc-5">gemini-3.6-flashとflash-liteの使い分け</a></li><li class="rtoc-item"><a href="#rtoc-6">Gemini APIに質問し返答する</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-7">ソースコード</a></li><li class="rtoc-item"><a href="#rtoc-8">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-9">エラーと解決策・対策</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-10">503 UNAVAILABLE（混雑）で返事が来ない</a></li><li class="rtoc-item"><a href="#rtoc-11">返事に「##」や「**」がそのまま出る</a></li><li class="rtoc-item"><a href="#rtoc-12">「GEMINI_API_KEY が未設定です」と出る</a></li><li class="rtoc-item"><a href="#rtoc-13">WebではAPIキーが見えてしまう</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-14">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">Gemini API（google_generative_ai）</h2>



<figure class="wp-block-image size-full"><img decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request.webp" alt="" class="wp-image-9249" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-api-key-request-240x135.webp 240w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">Gemini API はGoogle の AI「Gemini」を使うための窓口で、<strong>質問の文章を送るとGemini が考えた返事が文章で返ってきます</strong>。</p>



<p class="wp-block-paragraph">そのため、Flitterに入れることでAIを用いたアプリが作成可能になります。</p>



<p class="wp-block-paragraph">Flutter からは、<strong>google_generative_ai</strong> というパッケージで呼び出せます。本記事のコードは、このパッケージの 0.4.7 で作っています。</p>



<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-2 sbs-stn sbp-l sbis-cb cf block-box not-nested-style cocoon-block-balloon"><div class="speech-person"><figure class="speech-icon"><img decoding="async" src="https://alicia-ing.com/wp-content/uploads/2023/05/cropped-alicia.webp" alt="筆者" class="speech-icon-image"/></figure><div class="speech-name">筆者</div></div><div class="speech-balloon">
<p class="wp-block-paragraph">Gemini を選んだのは、<strong>無料範囲があるから</strong>です。ChatGPT や Claude の API は、試すだけでも使った分の料金がかかるため、まずは Geminiで連携を作りました。</p>
</div></div>



<p class="wp-block-paragraph">一方で、Geminiも上限を超えると従量課金になり、モデルによってChatGPTの方が安くなることがあります。</p>



<h3 id="rtoc-2"  class="wp-block-heading">APIキーの取得</h3>



<p class="wp-block-paragraph">Gemini API を使うには <strong>APIキー</strong>（アプリが自分のものだと Google に伝えるための合言葉）が必要です。</p>



<ol class="wp-block-list">
<li>Google AI Studio を開き、左下の鍵のアイコン（<strong>Get API key</strong>）を押す</li>



<li>「API キー」のページで、右上の <strong>API キーを作成</strong> を押す</li>



<li>作ったキーをコピーしておく</li>
</ol>



<figure class="wp-block-image size-full"><img decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key.webp" alt="" class="wp-image-9243" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-get-api-key-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">作ったキーは、この一覧に並びます。「請求階層」が<strong>無料枠</strong>になっていれば、料金はかからない範囲で使えます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key.webp" alt="" class="wp-image-9244" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/ai-studio-create-api-key-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-2 sbs-stn sbp-l sbis-cb cf block-box not-nested-style cocoon-block-balloon"><div class="speech-person"><figure class="speech-icon"><img decoding="async" src="https://alicia-ing.com/wp-content/uploads/2023/05/cropped-alicia.webp" alt="筆者" class="speech-icon-image"/></figure><div class="speech-name">筆者</div></div><div class="speech-balloon">
<p class="wp-block-paragraph">無料枠でも、基本的なテストで使う程度なら、制限に当たることはほとんどありません。安心してFlutterと連携してください。</p>
</div></div>



<p class="wp-block-paragraph">APIキーは<strong>パスワードと同じように扱います</strong>。GitHub などに上げないようにしてください。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://ai.google.dev/gemini-api/docs/api-key">Using Gemini API keys</a></p>



<h3 id="rtoc-3"  class="wp-block-heading">パッケージの追加と.envの設定</h3>



<p class="wp-block-paragraph"><strong>pubspec.yaml に2つのパッケージを追加</strong>します。google_generative_ai が Gemini を呼ぶパッケージ、flutter_dotenv が APIキーを別ファイルから読むパッケージです。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>dependencies:
  google_generative_ai: ^0.4.7
  flutter_dotenv: ^6.0.1

flutter:
  assets:
    - .env</code></pre></div>



<p class="wp-block-paragraph">プロジェクトの直下（pubspec.yaml と同じ場所）に .env というファイルを作り、your_api_key_here の部分を、コピーしたキーに書き換えます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-plain"><code>GEMINI_API_KEY=api_key</code></pre></div>



<p class="wp-block-paragraph">アプリの起動時に、この .env を読み込みます。main.dart の main の最初に、次の2行を入れます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: &#39;.env&#39;);</code></pre></div>



<p class="wp-block-paragraph">キーをコードに直接書かず .env に分けておくと、<strong>コードだけを人に見せたり公開したりしても、キーが一緒に出ていきません</strong>。.env は .gitignore に入れておきましょう。ただし、Web で動かすときには別の注意があります（「エラーと解決策・対策」で説明します）。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://pub.dev/packages/google_generative_ai">google_generative_ai</a></p>



<h2 id="rtoc-4"  class="wp-block-heading">google_generative_aiは非推奨｜これから始めるならFirebase AI Logic</h2>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">google_generative_ai は、公式のパッケージページで<strong>非推奨（Deprecated）</strong>と表示されています。今後、機能の追加や変更はされない予定です。</p>
</div>



<p class="wp-block-paragraph">本記事のコードは、まず手元で Gemini を試すために google_generative_ai で作りました。いまも動きますが、<strong>これから作り始めるアプリ、特に人に公開するアプリでは、後継の Firebase AI Logic（firebase_ai パッケージ）を使う</strong>のがおすすめです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>項目</th><th>google_generative_ai</th><th>firebase_ai（Firebase AI Logic）</th></tr></thead><tbody><tr><td>状態</td><td>非推奨。今後は変更されない</td><td>開発が続いている</td></tr><tr><td>準備</td><td>APIキーだけ</td><td>Firebase のプロジェクトが必要</td></tr><tr><td>アプリを守る仕組み</td><td>なし</td><td>App Check（正規のアプリからの呼び出しかを確かめる仕組み）を強く推奨</td></tr><tr><td>呼び出し方</td><td>GenerativeModel(&#8230;) を作り、generateContent</td><td>FirebaseAI.googleAI().generativeModel(&#8230;) を作り、generateContent</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">呼び出し方はよく似ていて、<strong>モデルを作って generateContent に質問を渡す</strong>流れは同じです。本記事の解説は、そのまま firebase_ai でも役に立ちます。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://firebase.google.com/docs/ai-logic/get-started?platform=flutter">Get started with the Gemini API using the Firebase AI Logic SDKs</a></p>



<h2 id="rtoc-5"  class="wp-block-heading">gemini-3.6-flashとflash-liteの使い分け</h2>



<p class="wp-block-paragraph">Gemini には、性能と速さの違う<strong>モデル</strong>（AI の種類）がいくつかあります。どのモデルを使うかは、コードの中でモデル名の文字列を書いて選びます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>モデル名</th><th>特徴</th><th>本記事での使い方</th></tr></thead><tbody><tr><td>gemini-3.6-flash</td><td>速さと性能のバランスがよい</td><td>普段はこちらで返事を作る</td></tr><tr><td>gemini-3.5-flash-lite</td><td>一番速く、費用も安い</td><td>混雑して flash が返事をしないときの予備</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">本記事では、<strong>普段は flash を使い、混雑しているときだけ flash-lite に切り替えます</strong>。返事の質を保ちつつ、返事が来ないまま待たされることを減らすためです。</p>



<p class="wp-block-paragraph">モデルは新しいものが次々に出ます。公式のモデル一覧には、gemini-3.6-flash より新しい世代の flash も載っています。<strong>モデル名は文字列1か所なので、新しいモデルに替えるときはそこを書き換えるだけ</strong>です。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://ai.google.dev/gemini-api/docs/models">Gemini models</a></p>



<h2 id="rtoc-6"  class="wp-block-heading">Gemini APIに質問し返答する</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1408" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/gemini-service_response.webp" alt="" class="wp-image-9250" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/gemini-service_response.webp 1408w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-service_response-300x164.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-service_response-766x418.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/gemini-service_response-1024x559.webp 1024w" sizes="(max-width: 1408px) 100vw, 1408px" /></figure>



<p class="wp-block-paragraph">ここまでの内容をもとに、次の動きをする画面を作ります。</p>



<ul class="wp-block-list">
<li>入力欄に質問を書く（マイクで話して入れてもよい）</li>



<li>「Geminiに聞く」を押すと、質問を Gemini に送る</li>



<li>返事を待つ間は、ボタンにくるくるを出して押せなくする</li>



<li>返事が届いたら、入力欄の下に表示する（長い返事はスクロールできる）</li>



<li>混雑していたら、少し待ってやり直し、それでもだめなら予備のモデルで返事を作る</li>
</ul>



<p class="wp-block-paragraph">マイクで話した内容を入力欄に入れる部分は、speech_to_text で作っています。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>speech_to_text で音声入力を作る方法を解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/flutter/speech-to-text-voiceinput/" title="【Flutter】speech_to_textで音声入力し文字起こし" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="320" height="180" src="https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-voiceinput-320x180.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-voiceinput-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-voiceinput-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-voiceinput-640x360.webp 640w" sizes="(max-width: 320px) 100vw, 320px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Flutter】speech_to_textで音声入力し文字起こし</div><div class="blogcard-snippet internal-blogcard-snippet">Flutterのspeech_to_textで音声入力を作る方法を解説。話した内容をリアルタイムで文字にし、認識が止まっても自動で聞き取り直すコードと、マイク許可・エラー対処を紹介します。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.10.11</div></div></div></div></a>
</div>



<h3 id="rtoc-7"  class="wp-block-heading">ソースコード</h3>



<ul class="wp-block-list">
<li>gemini_service.dart</li>
</ul>



<p class="wp-block-paragraph">Gemini とのやり取りをまとめたクラスです。lib/services/ に置きます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>class GeminiService {
  GeminiService() {
    // .env から APIキーを読む
    final apiKey = dotenv.env[&#39;GEMINI_API_KEY&#39;];
    if (apiKey != null && apiKey.isNotEmpty && apiKey != &#39;your_api_key_here&#39;) {
      _model = GenerativeModel(model: &#39;gemini-3.6-flash&#39;, apiKey: apiKey);
      // 混雑しているときに使う予備のモデル
      _fallbackModel = GenerativeModel(model: &#39;gemini-3.5-flash-lite&#39;, apiKey: apiKey);
    }
  }

  GenerativeModel? _model;
  GenerativeModel? _fallbackModel;

  bool get isConfigured =&gt; _model != null;

  // 質問を送り、返事の文章を返す
  Future&lt;String&gt; ask(String prompt) async {
    final model = _model;
    final fallbackModel = _fallbackModel;
    if (model == null || fallbackModel == null) {
      throw StateError(&#39;GEMINI_API_KEY が未設定です。.env に自分のAPIキーを設定してください。&#39;);
    }

    final content = [Content.text(prompt)];
    try {
      return _textOf(await _withRetryOnOverload(() =&gt; model.generateContent(content)));
    } on GenerativeAIException catch (e) {
      if (!_isOverloaded(e)) rethrow;
      // やり直しても混雑していたら、予備のモデルで返事を作る
      return _textOf(await fallbackModel.generateContent(content));
    }
  }

  // 混雑していたら2秒待って、1回だけやり直す
  Future&lt;GenerateContentResponse&gt; _withRetryOnOverload(
    Future&lt;GenerateContentResponse&gt; Function() call,
  ) async {
    try {
      return await call();
    } on GenerativeAIException catch (e) {
      if (!_isOverloaded(e)) rethrow;
      await Future&lt;void&gt;.delayed(const Duration(seconds: 2));
      return call();
    }
  }

  // エラーの文に 503 や UNAVAILABLE があれば「混雑」とみなす
  static bool _isOverloaded(GenerativeAIException e) =&gt;
      e.message.contains(&#39;503&#39;) || e.message.contains(&#39;UNAVAILABLE&#39;);

  static String _textOf(GenerateContentResponse response) =&gt;
      response.text ?? &#39;(応答がありませんでした)&#39;;
}</code></pre></div>



<ul class="wp-block-list">
<li>speech_input_page.dart</li>
</ul>



<p class="wp-block-paragraph">画面の State に持たせる部分です。「Geminiに聞く」を押すと _askGemini が呼ばれます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>final GeminiService _gemini = GeminiService();
bool _isAsking = false; // 返事を待っている間は true
String _answer = &#39;&#39;; // Gemini の返事

// 入力欄の文章を Gemini に送り、返事を表示する
Future&lt;void&gt; _askGemini() async {
  final prompt = _textController.text.trim();
  if (prompt.isEmpty) return;

  // 先に _isAsking を true にしてボタンを押せなくする（返事待ちの間の二重送信を防ぐ）
  setState(() {
    _isAsking = true;
    _statusMessage = &#39;&#39;;
    _isError = false;
    _answer = &#39;&#39;;
  });

  // 聞き取り中なら、マイクを止めてから送る
  if (_isListening) {
    await _speech.stop();
    if (!mounted) return;
    setState(() =&gt; _isListening = false);
  }

  try {
    final answer = await _gemini.ask(prompt);
    if (!mounted) return; // 待っている間に画面が閉じられていたら何もしない
    setState(() =&gt; _answer = answer);
  } catch (e) {
    if (!mounted) return;
    setState(() {
      _statusMessage = &#39;Gemini呼び出しエラー: $e&#39;;
      _isError = true;
    });
  } finally {
    // 成功しても失敗しても、最後に必ずボタンを押せる状態に戻す
    if (mounted) setState(() =&gt; _isAsking = false);
  }
}</code></pre></div>



<p class="wp-block-paragraph">build の中に並べる部品です。送信ボタンと、返事の表示です。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>// Gemini に送るボタン（返事待ちの間は押せず、くるくるを出す）
FilledButton.icon(
  onPressed: _isAsking ? null : _askGemini,
  icon: _isAsking
      ? const SizedBox(
          width: 16,
          height: 16,
          child: CircularProgressIndicator(strokeWidth: 2),
        )
      : const Icon(Icons.send),
  label: const Text(&#39;Geminiに聞く&#39;),
),

// Gemini の返事（長い返事はスクロールできる）
if (_answer.isNotEmpty)
  Expanded(
    child: SingleChildScrollView(
      // 右下のマイクボタンに、返事の最後が隠れないように余白を空ける
      padding: const EdgeInsets.only(bottom: 80),
      child: SelectableText(_answer), // 選択してコピーできる
    ),
  ),</code></pre></div>



<h3 id="rtoc-8"  class="wp-block-heading">解説</h3>



<p class="wp-block-paragraph">Gemini とのやり取りは <strong>GeminiService にまとめ</strong>、画面は「いつ送るか」と「何を表示するか」だけを受け持ちます。画面からは _gemini.ask(質問) を呼ぶだけなので、あとで firebase_ai などに替えるときも、<strong>直すのは GeminiService だけ</strong>で済みます。</p>



<p class="wp-block-paragraph">画面が持つ状態は次の2つです（音声入力やエラー表示の状態は省略しています）。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>変数</th><th>意味</th></tr></thead><tbody><tr><td>_isAsking</td><td>いま返事を待っているか（ボタンを押せなくする・くるくるを出すのに使う）</td></tr><tr><td>_answer</td><td>Gemini から届いた返事</td></tr></tbody></table></div></figure>



<h4 class="wp-block-heading">GeminiService：APIキーを読んでモデルを作る</h4>



<p class="wp-block-paragraph">最初に .env から APIキーを読み、<strong>GenerativeModel にモデル名とキーを渡してモデルを作ります</strong>。普段使う flash と、予備の flash-lite の2つを作っておきます。</p>



<p class="wp-block-paragraph">キーが読めなかったときはモデルを作らず、isConfigured が false になります。.envに書き換え忘れのときも、キーがないものとして扱います。</p>



<h4 class="wp-block-heading">ask：質問を送って返事を受け取る</h4>



<p class="wp-block-paragraph">Content.text(質問) で質問を包み、<strong>generateContent に渡すと、Gemini の返事が返ってきます</strong>。返事の文章は response.text に入っています。返事が空のときは null になるので、(応答がありませんでした) と表示します。</p>



<p class="wp-block-paragraph">generateContent は返事が届くまで時間がかかるので、await で待ちます。待っている間も、画面は固まらずに動き続けます。</p>



<h4 class="wp-block-heading">_withRetryOnOverload：混雑したら待ってやり直す</h4>



<p class="wp-block-paragraph">Gemini が混雑していると、<strong>返事の代わりに「503」「UNAVAILABLE」を含むエラーが返る</strong>ことがあります。混雑は少し待つと解消することが多いので、<strong>2秒待ってから1回だけやり直します</strong>。</p>



<p class="wp-block-paragraph">それでも混雑していたら、ask の側で<strong>予備の flash-lite に切り替えて返事を作ります</strong>。混雑以外のエラー（キーが間違っているなど）は、待っても直らないので、やり直さずにそのまま画面へ伝えます（rethrow）。</p>



<h4 class="wp-block-heading">_askGemini：二重送信を防いで送る</h4>



<p class="wp-block-paragraph">_askGemini は、次の順に動きます。</p>



<ol class="wp-block-list">
<li>入力欄が空なら何もしない</li>



<li><strong>先に _isAsking を true にする</strong>。ボタンの onPressed が null になり、押せなくなる</li>



<li>聞き取り中なら、マイクを止めてから送る（話している途中の文字が混ざらないようにする）</li>



<li>_gemini.ask で返事を待ち、届いたら _answer に入れて表示する</li>



<li>失敗したら、エラーの文を赤字で表示する</li>



<li><strong>finally で、成功しても失敗しても _isAsking を false に戻す</strong></li>
</ol>



<p class="wp-block-paragraph">2 を先にしておかないと、返事を待っている間にボタンを何度も押せてしまい、同じ質問が何回も送られます。</p>



<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-2 sbs-stn sbp-l sbis-cb cf block-box not-nested-style cocoon-block-balloon"><div class="speech-person"><figure class="speech-icon"><img decoding="async" src="https://alicia-ing.com/wp-content/uploads/2023/05/cropped-alicia.webp" alt="筆者" class="speech-icon-image"/></figure><div class="speech-name">筆者</div></div><div class="speech-balloon">
<p class="wp-block-paragraph">送信中に何回も押すと、Gemini の返事でエラーが起きるのではと考えて、送信中はボタンを押せなくしました。</p>
</div></div>



<p class="wp-block-paragraph">mounted は「この画面がまだ表示されているか」です。返事を待っている間に画面が閉じられていたら、setState を呼ばないようにしています（閉じた画面を更新しようとするとエラーになるため）。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>ボタンを押せなくする書き方を解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/flutter/button-onpress/" title="【Flutter】ボタンの種類と使い方｜押せなくする・色を変える" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="320" height="180" src="https://alicia-ing.com/wp-content/uploads/2026/10/button-onpress-320x180.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/button-onpress-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/button-onpress-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/button-onpress-640x360.webp 640w" sizes="(max-width: 320px) 100vw, 320px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Flutter】ボタンの種類と使い方｜押せなくする・色を変える</div><div class="blogcard-snippet internal-blogcard-snippet">Flutterのボタンの種類（Filled・Elevated・Outlined・Text・Icon）の違いと使い分けを解説。onPressedにnullを渡して押せなくする方法と、状態で色が変わるマイクボタンの作り方も紹介します。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.10.14</div></div></div></div></a>
</div>



<h4 class="wp-block-heading">返事の表示：スクロールとコピー</h4>



<p class="wp-block-paragraph">返事は長くなりやすいので、SingleChildScrollView に入れて<strong>スクロールできる</strong>ようにします。SelectableText を使うと、<strong>返事を選択してコピー</strong>できます。</p>



<h2 id="rtoc-9"  class="wp-block-heading">エラーと解決策・対策</h2>



<p class="wp-block-paragraph">以下では、想定しうるエラーの解決方法を紹介します。APIキーを第三者に公開するのは非常に危険ですので、取り扱いには十分に注意してください。</p>



<h3 id="rtoc-10"  class="wp-block-heading">503 UNAVAILABLE（混雑）で返事が来ない</h3>



<p class="wp-block-paragraph">Gemini 側が混雑していると、503（UNAVAILABLE）のエラーが返ります。こちらのコードの誤りではないので、<strong>時間をおいてやり直せば返事が来る</strong>ことが多いです。</p>



<div class="wp-block-cocoon-blocks-balloon-ex-box-1 speech-wrap sb-id-2 sbs-stn sbp-l sbis-cb cf block-box not-nested-style cocoon-block-balloon"><div class="speech-person"><figure class="speech-icon"><img decoding="async" src="https://alicia-ing.com/wp-content/uploads/2023/05/cropped-alicia.webp" alt="筆者" class="speech-icon-image"/></figure><div class="speech-name">筆者</div></div><div class="speech-balloon">
<p class="wp-block-paragraph">実際に混雑に当たったときは、ボタンを押し直してもエラーが表示されるだけでした。そこで、同じモデルで粘るより、別のモデルに切り替えて返事をもらう方がよいと考えました。</p>
</div></div>



<p class="wp-block-paragraph">公式のトラブルシューティングでは、<strong>少し待ってからやり直し、やり直すたびに待つ時間を延ばす</strong>方法がすすめられています。</p>



<p class="wp-block-paragraph">本記事のコードは、この考え方を簡単にして「2秒待って1回やり直す」にしています。さらに、<strong>やり直してもだめなときは予備のモデルに切り替える</strong>のは、待たされる時間を減らすための工夫です（公式の手順にはありません）。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://ai.google.dev/gemini-api/docs/troubleshooting">Troubleshooting guide</a></p>



<h3 id="rtoc-11"  class="wp-block-heading">返事に「##」や「**」がそのまま出る</h3>



<p class="wp-block-paragraph">完成イメージのように、返事に「##」や「**」がそのまま出ることがあります。<strong>Gemini はマークダウン</strong>（見出しや太字を記号で表す書き方）<strong>で返事を書く</strong>ことが多く、SelectableText は記号をそのまま文字として表示するためです。</p>



<p class="wp-block-paragraph">直し方は2通りあります。</p>



<ul class="wp-block-list">
<li><strong>表示側で整える：</strong> マークダウンを見た目に変えて表示するパッケージを使う。よく紹介される flutter_markdown は開発が終了しているので、後継として案内されている <strong>flutter_markdown_plus</strong> を使う</li>



<li><strong>記号を使わせない：</strong> 質問と一緒に「マークダウンや記号を使わず、普通の文章で答えて」と指示する</li>
</ul>



<p class="wp-block-paragraph">返事を読み上げたり吹き出しに出したりする場合は、<strong>記号を使わせない方法が向いています</strong>。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://pub.dev/packages/flutter_markdown_plus">flutter_markdown_plus</a></p>



<h3 id="rtoc-12"  class="wp-block-heading">「GEMINI_API_KEY が未設定です」と出る</h3>



<p class="wp-block-paragraph"><strong>APIキーが読めていない</strong>状態です。次を確認してください。</p>



<ul class="wp-block-list">
<li>.env がプロジェクトの直下（pubspec.yaml と同じ場所）にあるか</li>



<li>pubspec.yaml の flutter: の assets: に .env を書いたか（書き忘れると、アプリに .env が入らない）</li>



<li>.env の書き方が GEMINI_API_KEY=キー になっているか（your_api_key_here のままになっていないか）</li>
</ul>



<p class="wp-block-paragraph">pubspec.yaml を直したあとは、ホットリロードではなく、<strong>アプリを止めてから起動し直して</strong>ください。</p>



<h3 id="rtoc-13"  class="wp-block-heading">WebではAPIキーが見えてしまう</h3>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">.env を assets に入れると、<strong>Web で公開したときに、.env のファイルがそのまま配信されます</strong>。誰でも中の APIキーを読めてしまうので、手元で試すだけにして、build/web を公開しないでください。</p>
</div>



<p class="wp-block-paragraph">Web に限らず、スマホのアプリに入れたキーも、取り出そうと思えば取り出せます。公式も、<strong>Web やスマホのアプリにキーを直接入れないこと</strong>、本番ではサーバーを間に置いて呼び出すことをすすめています。</p>



<p class="wp-block-paragraph">人に使ってもらうアプリでは、APIキーをアプリに入れない作りにします。<strong>Firebase AI Logic（firebase_ai）に移す</strong>か、<strong>自分のサーバーを間に置いて Gemini を呼ぶ</strong>形にしましょう。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://ai.google.dev/gemini-api/docs/api-key">Using Gemini API keys</a></p>



<h2 id="rtoc-14"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">Flutter から Gemini API を使う流れは、<strong>モデルを作り、generateContent に質問を渡して、response.text で返事を受け取る</strong>、の3つです。</p>



<ul class="wp-block-list">
<li>Gemini とのやり取りは GeminiService にまとめ、画面は送るタイミングと表示だけを受け持つ</li>



<li>返事を待つ間はボタンを押せなくし、finally で必ず元に戻す</li>



<li>混雑（503）は待ってやり直す。だめなら軽いモデルに切り替える</li>



<li>返事のマークダウン記号は、表示側で整えるか、使わないように指示する</li>



<li>google_generative_ai は非推奨。公開するアプリは Firebase AI Logic かサーバー経由にして、APIキーをアプリに入れない</li>
</ul>



<p class="wp-block-paragraph">まずは手元で質問を送って返事が返ってくるところまで動かし、AI に何を任せたいかを試してみてください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/flutter/gemini-api-application/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Flutter】ボタンの種類と使い方｜押せなくする・色を変える</title>
		<link>https://alicia-ing.com/programming/flutter/button-onpress/</link>
					<comments>https://alicia-ing.com/programming/flutter/button-onpress/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Tue, 13 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=9197</guid>

					<description><![CDATA[アプリには、必ずと言っていいほどボタンがあります。Flutter にはボタンの種類がいくつもあり、「どれを使えばいいのか」「押せなくするにはどうするのか」と迷いやすい部品です。 本記事では、よく使う5種類のボタンを並べて [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">Flutter の5種類のボタンの違いを動かして確かめ、状態で色とアイコンが変わるマイクボタン（FloatingActionButton）と、押せなくする切り替えを作れます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/10/complete-3.webp" alt="" class="wp-image-9223" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/complete-3.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-3-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-3-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-3-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-3-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-3-640x360.webp 640w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">アプリには、必ずと言っていいほどボタンがあります。Flutter にはボタンの種類がいくつもあり、「<strong>どれを使えばいいのか</strong>」「<strong>押せなくするにはどうするのか</strong>」と迷いやすい部品です。</p>



<p class="wp-block-paragraph">本記事では、よく使う<strong>5種類のボタン</strong>を並べて押し比べたあと、音声入力の開始と停止に使うマイクボタンを作ります。<strong>聞き取り中は赤くなり、スイッチで押せなく</strong>もできます。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Flutter のボタンの種類と使い分けを知りたい人</li>



<li>押したときの処理（onPressed）の書き方を知りたい人</li>



<li>状態によってボタンの色やアイコンを変えたい人</li>



<li>ボタンを押せなくする方法を知りたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="9197" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">Flutterのボタンの種類</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">押せなくする：onPressed に null</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-3">FloatingActionButton</a></li><li class="rtoc-item"><a href="#rtoc-4">ソースコード</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-5">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-6">実装</a></li><li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">Flutterのボタンの種類</h2>



<p class="wp-block-paragraph">よく使うボタンは、次の5つです。（FilledButton・ElevatedButton・OutlinedButton・TextButton・IconButton）どれも、押したときの処理を <strong>onPressed に渡す</strong>と動きます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>ボタン</th><th>見た目</th><th>使いどころ</th></tr></thead><tbody><tr><td>FilledButton</td><td>色で塗りつぶされている</td><td>その<strong>画面でいちばん大事な操作</strong></td></tr><tr><td>ElevatedButton</td><td>影があり、浮いて見える</td><td>目立たせたい操作。背景と区別したいとき</td></tr><tr><td>OutlinedButton</td><td>枠線だけ</td><td>補助的な操作</td></tr><tr><td>TextButton</td><td>文字だけ</td><td>控えめな操作</td></tr><tr><td>IconButton</td><td>アイコンだけ</td><td>ツールバーなど、場所を取りたくないとき</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">使い分けの目安は、「<strong>目立たせたい操作ほど、塗りつぶしや影のあるボタンにする</strong>」ことです。1つの画面に塗りつぶしのボタンを並べすぎると、どれが大事か分からなくなります。</p>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">古い記事に出てくる RaisedButton、FlatButton などは、いまは使われていません。新しく書くときは、上の5つを使ってください。</p>
</div>



<h3 id="rtoc-2"  class="wp-block-heading">押せなくする：onPressed に null</h3>



<p class="wp-block-paragraph">どのボタンも、<strong>onPressed に null を渡す</strong>と、押せなくなります。公式の説明でも、onPressed が null のボタンは無効になります。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>ElevatedButton(
  onPressed: isReady ? () { /* 処理 */ } : null,
  child: const Text(&#39;送信&#39;),
)</code></pre></div>



<p class="wp-block-paragraph">条件によって、処理の関数と null を切り替える書き方です。</p>



<h2 id="rtoc-3"  class="wp-block-heading">FloatingActionButton</h2>



<p class="wp-block-paragraph">FloatingActionButton は、画面の上に浮いて見える丸いボタンです。画面でいちばん大事な操作（作る、共有する、録音を始めるなど）に使います。公式の説明では、1つの画面に置くのは<strong>多くても1つ</strong>にするよう案内されています。</p>



<p class="wp-block-paragraph">Scaffold の floatingActionButton に渡すと、通常は画面の右下に置かれます。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>画面の枠組みを作る Scaffold について解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/flutter/scaffold/" title="【Flutter】画面を構成するScaffoldウィジェット一覧" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2023/11/scaffold-1-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2023/11/scaffold-1-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2023/11/scaffold-1-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2023/11/scaffold-1-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Flutter】画面を構成するScaffoldウィジェット一覧</div><div class="blogcard-snippet internal-blogcard-snippet">Scaffoldはアプリの構造やレイアウトを提供するウィジェット（Widget）で、Flutterでは必要不可欠な要素です。ウィジット一覧と使い方を解説しています。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2025.08.11</div></div></div></div></a>
</div>



<p class="wp-block-paragraph">主なプロパティは次のとおりです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>プロパティ</th><th>役割</th></tr></thead><tbody><tr><td>onPressed</td><td>押したときの処理。null だと押せなくなる</td></tr><tr><td>child</td><td>ボタンの中身。ふつうはアイコン</td></tr><tr><td>tooltip</td><td>長押ししたときに出る、ボタンの説明</td></tr><tr><td>backgroundColor</td><td>ボタンの色</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://api.flutter.dev/flutter/material/FloatingActionButton-class.html">FloatingActionButton</a>（Flutter公式）</p>



<p class="wp-block-paragraph">この記事では、次の画面を作ります。</p>



<ul class="wp-block-list">
<li>右下のマイクボタンを押すと、聞き取り中（赤）と停止中を切り替わる。アイコンと説明文も変わる</li>



<li>「マイクボタン許可」スイッチをオフにすると、マイクボタンを押せなくなる</li>



<li>5種類のボタンを押すと、押した回数が増える</li>
</ul>



<h2 id="rtoc-4"  class="wp-block-heading">ソースコード</h2>



<ul class="wp-block-list">
<li>mic_button.dart</li>
</ul>



<p class="wp-block-paragraph">マイクボタンの部品です。状態で色とアイコンを変えます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

/// 音声入力の開始／停止ボタン。聞き取り中は赤くなる。
/// onPressed が null のときは押せない。
class MicButton extends StatelessWidget {
  const MicButton({
    super.key,
    required this.isListening,
    required this.onPressed,
  });

  final bool isListening;
  final VoidCallback? onPressed;

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: onPressed,
      tooltip: isListening ? &#39;停止&#39; : &#39;音声入力開始&#39;,
      backgroundColor: isListening ? Colors.red : null,
      child: Icon(isListening ? Icons.mic : Icons.mic_none),
    );
  }
}</code></pre></div>



<ul class="wp-block-list">
<li>button_page.dart</li>
</ul>



<p class="wp-block-paragraph">画面です。状態と、5種類のボタンを持ちます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;../widgets/mic_button.dart&#39;;

class ButtonPage extends StatefulWidget {
  const ButtonPage({super.key, required this.title});

  final String title;

  @override
  State&lt;ButtonPage&gt; createState() =&gt; _ButtonPageState();
}

class _ButtonPageState extends State&lt;ButtonPage&gt; {
  bool _isListening = false;
  bool _enabled = true;
  int _tapCount = 0;

  void _toggleListening() =&gt; setState(() =&gt; _isListening = !_isListening);

  void _countUp() =&gt; setState(() =&gt; _tapCount++);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(_isListening ? &#39;聞き取り中(デモ)&#39; : &#39;停止中&#39;),
            SwitchListTile(
              title: const Text(&#39;マイクボタン許可&#39;),
              value: _enabled,
              onChanged: (value) =&gt; setState(() =&gt; _enabled = value),
            ),
            const Divider(),
            Text(&#39;ボタンの種類（押した回数: $_tapCount）&#39;),
            const SizedBox(height: 12),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              children: [
                FilledButton(onPressed: _countUp, child: const Text(&#39;Filled&#39;)),
                ElevatedButton(onPressed: _countUp, child: const Text(&#39;Elevated&#39;)),
                OutlinedButton(onPressed: _countUp, child: const Text(&#39;Outlined&#39;)),
                TextButton(onPressed: _countUp, child: const Text(&#39;Text&#39;)),
                IconButton(
                  onPressed: _countUp,
                  tooltip: &#39;Icon&#39;,
                  icon: const Icon(Icons.thumb_up),
                ),
              ],
            ),
          ],
        ),
      ),
      floatingActionButton: MicButton(
        isListening: _isListening,
        onPressed: _enabled ? _toggleListening : null,
      ),
    );
  }
}</code></pre></div>



<ul class="wp-block-list">
<li>main.dart</li>
</ul>



<p class="wp-block-paragraph">動かすための最小の入口です。mic_button.dart は lib/widgets/、button_page.dart は lib/pages/ に置きます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;pages/button_page.dart&#39;;

void main() {
  runApp(
    const MaterialApp(home: ButtonPage(title: &#39;ボタン&#39;)),
  );
}</code></pre></div>



<h3 id="rtoc-5"  class="wp-block-heading">解説</h3>



<p class="wp-block-paragraph">画面（ButtonPage）が、「聞き取り中か」「マイクボタンを押せるか」「押した回数」の3つの状態を持ちます。マイクボタンの見た目は、MicButton が受け取った値だけで決まります。</p>



<h4 class="wp-block-heading">MicButton：状態で色とアイコンを変える</h4>



<p class="wp-block-paragraph"><strong>isListening が true のときだけ赤く</strong>し、アイコンとツールチップも切り替えています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>backgroundColor: isListening ? Colors.red : null,
child: Icon(isListening ? Icons.mic : Icons.mic_none),</code></pre></div>



<p class="wp-block-paragraph">条件が true か false かで値を選ぶ書き方です。backgroundColor に null を渡すと、テーマで決まっている標準の色になります。</p>



<p class="wp-block-paragraph">色だけでなくアイコンも変えているので、色の違いが見分けにくい人にも、状態が伝わります。</p>



<h4 class="wp-block-heading">onPressed に null を渡す</h4>



<p class="wp-block-paragraph">MicButton の onPressed は、VoidCallback? という型で受け取ります。? は、null も受け取れるという意味です。</p>



<p class="wp-block-paragraph">画面側では、スイッチの状態で、処理と null を切り替えます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>onPressed: _enabled ? _toggleListening : null,</code></pre></div>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">公式の説明では、FloatingActionButton の onPressed を null にすると押せなくなりますが、<strong>見た目で押せないことが分かりにくい</strong>ので、おすすめしないとされています。押せなくするときは、<strong>あわせて backgroundColor を変えて</strong>、押せないことが見た目で分かるようにしてください。例は次のとおりです。</p>
</div>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>backgroundColor: onPressed == null
    ? Colors.grey
    : (isListening ? Colors.red : null),</code></pre></div>



<h4 class="wp-block-heading">部品に切り出す理由</h4>



<p class="wp-block-paragraph">マイクボタンを MicButton として別のクラスに切り出すと、<strong>見た目の決まりごと（赤くする、アイコンを変える）を1か所にまとめられます</strong>。画面側は、状態を渡すだけで済みます。同じボタンを別の画面で使うときにも、そのまま使えます。</p>



<h4 class="wp-block-heading">_countUp：5種類のボタンに同じ処理を渡す</h4>



<p class="wp-block-paragraph">5種類のボタンには、すべて同じ _countUp を渡しています。押した回数が増えることで、どのボタンも同じように onPressed で動くことが確かめられます。</p>



<h2 id="rtoc-6"  class="wp-block-heading">実装</h2>



<ol class="wp-block-list">
<li>Flutter のプロジェクトを作る（追加するパッケージはありません）</li>



<li>2つのファイルを、上の場所に置く</li>



<li>main.dart を書き換えて、実行する</li>
</ol>



<p class="wp-block-paragraph">マイクボタンを押すと赤くなり、もう一度押すと元に戻ります。スイッチをオフにすると、マイクボタンを押せなくなります。</p>



<h2 id="rtoc-7"  class="wp-block-heading">まとめ</h2>



<ul class="wp-block-list">
<li>ボタンは FilledButton・ElevatedButton・OutlinedButton・TextButton・IconButton の5種類を使い分ける</li>



<li>押したときの処理は onPressed に渡し、null を渡すと押せなくなる</li>



<li>FloatingActionButton は、画面でいちばん大事な操作に1つだけ置く</li>



<li>押せなくするときは、見た目も変えて、押せないと分かるようにする</li>



<li>状態で変わる見た目は、部品に切り出して1か所にまとめる</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/flutter/button-onpress/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Flutter】ドロップダウンの作り方｜選択肢を作り選べなくする</title>
		<link>https://alicia-ing.com/programming/flutter/dropdownbuttonformfield/</link>
					<comments>https://alicia-ing.com/programming/flutter/dropdownbuttonformfield/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Mon, 12 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=9195</guid>

					<description><![CDATA[アプリで言語や国を選んでもらうとき、一覧から1つを選ぶドロップダウン（プルダウン）がよく使われます。 Flutter でドロップダウンを作る部品はいくつかあります。 本記事では、フォームの仕組みにも組み込める Dropd [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">言語などの選択肢から1つを選べるドロップダウンを作り、選んだ値の取り出しと、スイッチで選べなくする切り替えまでを DropdownButtonFormField で実装できます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/10/complete-2.webp" alt="" class="wp-image-9216" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/complete-2.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-2-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-2-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-2-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-2-640x360.webp 640w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-2-320x180.webp 320w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">アプリで言語や国を選んでもらうとき、一覧から1つを選ぶドロップダウン（プルダウン）がよく使われます。</p>



<p class="wp-block-paragraph">Flutter でドロップダウンを作る部品はいくつかあります。</p>



<p class="wp-block-paragraph">本記事では、フォームの仕組みにも組み込める <strong>DropdownButtonFormField</strong> を使います。<strong>画面に出す文字と、プログラムが受け取る値を分ける方法</strong>、最初に選んでおく値の決め方、選べなくする方法を、動くコードで説明します。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Flutter でドロップダウン（プルダウン）を作りたい人</li>



<li>選択肢をリストから作る方法を知りたい人</li>



<li>画面に出す名前と、プログラムが使う値を分けたい人</li>



<li>ドロップダウンを、条件によって選べなくしたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="9195" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">DropdownButtonFormField（ドロップダウン）</a></li><li class="rtoc-item"><a href="#rtoc-2">ソースコード</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-3">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">実装</a></li><li class="rtoc-item"><a href="#rtoc-5">エラーと解決策・対策</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">There should be exactly one item with [DropdownButton]&#8217;s value</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">DropdownButtonFormField（ドロップダウン）</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field.webp" alt="" class="wp-image-9213" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/dropdown-button-form-field-320x180.webp 320w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">DropdownButtonFormField は、<strong>押すと選択肢の一覧が開き、1つを選べる部品</strong>です。公式の説明では、ドロップダウンの部品である DropdownButton を、フォームの入力欄として使えるように包んだものです。複数の入力欄をまとめて検証したいときに使う Form の中に置けますが、Form がなくても使えます。</p>



<p class="wp-block-paragraph">この記事で使うプロパティは3つです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>プロパティ</th><th>役割</th></tr></thead><tbody><tr><td>items</td><td>選択肢の一覧（DropdownMenuItem のリスト）。必須</td></tr><tr><td>initialValue</td><td>最初に選んでおく値</td></tr><tr><td>onChanged</td><td>選び直したときに呼ばれる関数</td></tr></tbody></table></div></figure>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">古い記事では、最初に選んでおく値に value を使っています。公式では <strong>value は非推奨</strong>になっており、<strong>initialValue を使う</strong>よう案内されています（v3.33.0-1.0.pre の後に非推奨）。新しく書くときは initialValue を使ってください。</p>
</div>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://api.flutter.dev/flutter/material/DropdownButtonFormField-class.html">DropdownButtonFormField</a></p>



<p class="wp-block-paragraph">本記事では、次の動きをする画面を作ります。</p>



<ul class="wp-block-list">
<li>言語の一覧から1つを選べる</li>



<li>選んだ言語の名前と ID を、ドロップダウンの下に表示する</li>



<li>「変更を許可する」スイッチをオフにすると、ドロップダウンが押せなくなる</li>
</ul>



<h2 id="rtoc-2"  class="wp-block-heading">ソースコード</h2>



<ul class="wp-block-list">
<li>language.dart</li>
</ul>



<p class="wp-block-paragraph">選択肢1つ分のクラスと、一覧のデータです。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>/// 選択肢1つ分。id は選んだときに渡る値、name は画面に出る文字。
class Language {
  const Language(this.id, this.name);

  final String id;
  final String name;
}

const languages = [
  Language(&#39;ja-JP&#39;, &#39;日本語 (日本)&#39;),
  Language(&#39;en-US&#39;, &#39;English (United States)&#39;),
  Language(&#39;fr-FR&#39;, &#39;Français (France)&#39;),
];</code></pre></div>



<ul class="wp-block-list">
<li>language_dropdown.dart</li>
</ul>



<p class="wp-block-paragraph">ドロップダウン部分の部品です。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;../models/language.dart&#39;;

class LanguageDropdown extends StatelessWidget {
  const LanguageDropdown({
    super.key,
    required this.languages,
    required this.selectedId,
    required this.enabled,
    required this.onChanged,
  });

  final List&lt;Language&gt; languages;
  final String? selectedId;
  final bool enabled;
  final ValueChanged&lt;String?&gt; onChanged;

  @override
  Widget build(BuildContext context) {
    return DropdownButtonFormField&lt;String&gt;(
      initialValue: selectedId,
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        labelText: &#39;言語&#39;,
      ),
      items: [
        for (final language in languages)
          DropdownMenuItem(value: language.id, child: Text(language.name)),
      ],
      // onChanged が null のとき、ドロップダウンは押せなくなる。
      onChanged: enabled ? onChanged : null,
    );
  }
}</code></pre></div>



<ul class="wp-block-list">
<li>dropdown_page.dart</li>
</ul>



<p class="wp-block-paragraph">画面です。選んだ値と、変更できるかどうかの状態を持ちます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;../models/language.dart&#39;;
import &#39;../widgets/language_dropdown.dart&#39;;

class DropdownPage extends StatefulWidget {
  const DropdownPage({super.key, required this.title});

  final String title;

  @override
  State&lt;DropdownPage&gt; createState() =&gt; _DropdownPageState();
}

class _DropdownPageState extends State&lt;DropdownPage&gt; {
  String? _selectedId = languages.first.id;
  bool _enabled = true;

  String get _selectedName =&gt;
      languages.firstWhere((l) =&gt; l.id == _selectedId).name;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            LanguageDropdown(
              languages: languages,
              selectedId: _selectedId,
              enabled: _enabled,
              onChanged: (id) =&gt; setState(() =&gt; _selectedId = id),
            ),
            const SizedBox(height: 12),
            Text(&#39;選んだ言語: $_selectedName（ID: $_selectedId）&#39;),
            const SizedBox(height: 12),
            SwitchListTile(
              title: const Text(&#39;変更を許可する&#39;),
              value: _enabled,
              onChanged: (value) =&gt; setState(() =&gt; _enabled = value),
            ),
          ],
        ),
      ),
    );
  }
}</code></pre></div>



<ul class="wp-block-list">
<li>main.dart</li>
</ul>



<p class="wp-block-paragraph">動かすための最小の入口です。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;pages/dropdown_page.dart&#39;;

void main() {
  runApp(
    const MaterialApp(home: DropdownPage(title: &#39;ドロップダウン&#39;)),
  );
}</code></pre></div>



<h3 id="rtoc-3"  class="wp-block-heading">解説</h3>



<p class="wp-block-paragraph">選択肢のデータ（language.dart）、ドロップダウンの部品（language_dropdown.dart）、画面（dropdown_page.dart）を分けています。ドロップダウンの部品は、選択肢の一覧、いま選んでいる値、選べるかどうか、選び直したときの処理を、外から受け取るだけです。選んだ値は画面側が持ちます。</p>



<h4 class="wp-block-heading">Language：選ぶ値と画面の文字を分ける</h4>



<p class="wp-block-paragraph">選択肢を、ただの文字列のリストにすることもできます。この記事では、<strong>id（プログラムが使う値）と name（画面に出す文字）</strong>の2つを持つクラスにしました。</p>



<p class="wp-block-paragraph">分けておくと、画面の<strong>表示を変えても、プログラムが受け取る値が変わりません</strong>。たとえば表示を「日本語 (日本)」から「Japanese」に変えても、受け取る値は ja-JP のままです。</p>



<h4 class="wp-block-heading">items：リストから選択肢を作る</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>items: [
  for (final language in languages)
    DropdownMenuItem(value: language.id, child: Text(language.name)),
],</code></pre></div>



<p class="wp-block-paragraph"><strong>リストの中に for を書く</strong>と、一覧のデータから選択肢を作れます。DropdownMenuItem の value が、選んだときに受け取る値、child が画面に出る文字です。一覧に項目を足すだけで、選択肢も増えます。</p>



<h4 class="wp-block-heading">initialValue：最初に選んでおく値</h4>



<p class="wp-block-paragraph">initialValue に、最初に選んでおく値を渡します。ここに渡す値は、<strong>items の value のどれか1つと一致している必要</strong>があります。</p>



<p class="wp-block-paragraph">initialValue は、最初の1回に使う値です。選び直したあとの値は、onChanged で受け取って画面側で持ちます。</p>



<h4 class="wp-block-heading">onChanged：選び直したときの処理と、選べなくする方法</h4>



<p class="wp-block-paragraph">onChanged には、選び直したときに呼ばれる関数を渡します。引数が、選ばれた value です。この画面では setState で _selectedId を更新し、下の表示を変えています。</p>



<p class="wp-block-paragraph">ドロップダウンを選べなくしたいときは、<strong>onChanged に null を渡します</strong>。公式の説明では、onChanged が null のとき、ドロップダウンは無効になります。この画面では、スイッチの状態 enabled が false のときに null を渡しています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>onChanged: enabled ? onChanged : null,</code></pre></div>



<p class="wp-block-paragraph">無効のときに出す案内文は、disabledHint で指定できます。</p>



<h2 id="rtoc-4"  class="wp-block-heading">実装</h2>



<ol class="wp-block-list">
<li>Flutter のプロジェクトを作る（追加するパッケージはありません）</li>



<li>3つのファイルを、上の場所に置く</li>



<li>main.dart を書き換えて、実行する</li>
</ol>



<p class="wp-block-paragraph">ドロップダウンを押して言語を選ぶと、下の表示が変わります。スイッチをオフにすると、ドロップダウンを押せなくなります。</p>



<h2 id="rtoc-5"  class="wp-block-heading">エラーと解決策・対策</h2>



<p class="wp-block-paragraph">以下では、公式の説明で示されているエラーと対処を紹介します。実際に起きたエラーではなく、起こりやすい点をまとめたものです。</p>



<h3 id="rtoc-6"  class="wp-block-heading">There should be exactly one item with [DropdownButton]&#8217;s value</h3>



<p class="wp-block-paragraph">initialValue に、items の value に存在しない値を渡すと、このメッセージで止まります。公式の説明では、一致する項目がないとき、または2つ以上あるときに出ます。</p>



<ul class="wp-block-list">
<li>initialValue の値が、items の value のどれかと一致しているか確認する</li>



<li>同じ value の項目が2つ以上ないか確認する</li>



<li>最初は何も選ばない状態にしたいときは、initialValue を null にする</li>
</ul>



<h2 id="rtoc-7"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">DropdownButtonFormField は、items、initialValue、onChanged の3つを渡せば動きます。</p>



<ul class="wp-block-list">
<li>選択肢は、プログラムが使う値と画面に出す文字を分けたクラスで作る</li>



<li>選択肢のリストは、リスト内の for で作る</li>



<li>最初に選んでおく値は initialValue（古い value は非推奨）</li>



<li>onChanged に null を渡すと、ドロップダウンを選べなくできる</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/flutter/dropdownbuttonformfield/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Flutter】TextFieldの使い方｜文字の取得・書き換え</title>
		<link>https://alicia-ing.com/programming/flutter/textfield-input/</link>
					<comments>https://alicia-ing.com/programming/flutter/textfield-input/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sun, 11 Oct 2026 22:00:00 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=9191</guid>

					<description><![CDATA[アプリの入力欄は、手で打った文字を読むだけでなく、プログラムから文字を入れたり、中身が変わるたびに何かを表示したりしたい場面がよくあります。 Flutter では、入力欄の部品 TextField に、中身を操作するため [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">手で文字を打てて、ボタンでコードから文字を入れたり全部消したりでき、文字数もリアルタイムで表示される入力欄を、TextField と TextEditingController で作れます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/10/complete-1.webp" alt="" class="wp-image-9205" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/complete-1.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-1-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-1-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-1-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-1-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-1-640x360.webp 640w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">アプリの入力欄は、手で打った文字を読むだけでなく、プログラムから文字を入れたり、中身が変わるたびに何かを表示したりしたい場面がよくあります。</p>



<p class="wp-block-paragraph">Flutter では、入力欄の部品 <strong>TextField</strong> に、中身を操作するための <strong>TextEditingController</strong> を組み合わせると、どちらもできます。</p>



<p class="wp-block-paragraph">本記事では、サンプル文を入れる、全部消す、文字数を表示する、という3つの動きを持つ入力欄を作りながら、TextField と TextEditingController の使い方を説明します。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Flutter で入力欄（テキストボックス）を作りたい人</li>



<li>入力された文字を、コードから読み取りたい人</li>



<li>ボタンなどで、コードから入力欄の文字を書き換えたい人</li>



<li>TextEditingController を、なぜ後片付けしなければいけないのか知りたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="9191" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">TextField（テキスト入力欄）</a></li><li class="rtoc-item"><a href="#rtoc-2">TextEditingController（入力欄の中身を操作する）</a></li><li class="rtoc-item"><a href="#rtoc-3">ソースコード</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-4">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-5">エラーと解決策・対策</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">文字を入れたのに、カーソルが思った場所にない</a></li><li class="rtoc-item"><a href="#rtoc-7">入力のたびに、変な動きや無限ループになる</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-8">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">TextField（テキスト入力欄）</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box.webp" alt="" class="wp-image-9206" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/textfield-input-box-240x135.webp 240w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">TextField は、<strong>文字を入力するための入力欄の部品</strong>です。最小の書き方は次のとおりです。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>const TextField()</code></pre></div>



<p class="wp-block-paragraph">見た目や動きは、プロパティで変えます。この記事で使うものは次の3つです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>プロパティ</th><th>役割</th></tr></thead><tbody><tr><td>controller</td><td>入力欄の中身を操作する TextEditingController を渡す</td></tr><tr><td>maxLines</td><td>表示する行数。2以上にすると複数行の入力欄になる</td></tr><tr><td>decoration</td><td>枠線や、何も入力されていないときの案内文（hintText）を決める</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://api.flutter.dev/flutter/material/TextField-class.html">TextField</a>（Flutter公式）</p>



<h2 id="rtoc-2"  class="wp-block-heading">TextEditingController（入力欄の中身を操作する）</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller.webp" alt="" class="wp-image-9207" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/text-editing-controller-320x180.webp 320w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">TextEditingController は、<strong>入力欄の中身を読んだり書いたりするための部品</strong>です。TextField の <strong>controller に渡す</strong>と、2つの部品がつながります。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>final TextEditingController _controller = TextEditingController();

TextField(controller: _controller)</code></pre></div>



<p class="wp-block-paragraph">つなげたあとは、次のことができます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>やりたいこと</th><th>書き方</th></tr></thead><tbody><tr><td>いまの文字を読む</td><td>_controller.text</td></tr><tr><td>文字を書き換える</td><td>_controller.text = &#8216;新しい文字&#8217;</td></tr><tr><td>全部消す</td><td>_controller.clear()</td></tr><tr><td>中身が変わったことを知る</td><td>_controller.addListener(&#8230;)、またはウィジェットで監視する</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph"><strong>入力された文字を取る方法</strong>は、TextEditingController 以外にもあります。それぞれ、呼ばれるタイミングが違います。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>方法</th><th>タイミング</th></tr></thead><tbody><tr><td>onChanged</td><td>1文字入力するたびに呼ばれる</td></tr><tr><td>onSubmitted</td><td>入力を終えたとき（キーボードの確定ボタンなど）に呼ばれる</td></tr><tr><td>TextEditingController</td><td>好きなタイミングで text を読める。コードから書き換えもできる</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">読むだけなら onChanged や onSubmitted でも足ります。この記事のように、コードから文字を入れたり、文字数を出し続けたりするなら、<strong>TextEditingController が向いています</strong>。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://api.flutter.dev/flutter/widgets/TextEditingController-class.html">Flutter 公式 API</a></p>



<p class="wp-block-paragraph">この記事では、次の動きをする入力欄を作ります。</p>



<ul class="wp-block-list">
<li>手で文字を入力できる（複数行）</li>



<li>入力欄の下に、いまの文字数が出る</li>



<li>「サンプルを入れる」ボタンで、コードから文章が入る。カーソルは文字の最後に置く</li>



<li>「全部消す」ボタンで、入力欄が空になる</li>
</ul>



<h2 id="rtoc-3"  class="wp-block-heading">ソースコード</h2>



<ul class="wp-block-list">
<li>text_input_page.dart</li>
</ul>



<p class="wp-block-paragraph">入力欄、文字数の表示、2つのボタンを持つ画面です。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

class TextInputPage extends StatefulWidget {
  const TextInputPage({super.key, required this.title});

  final String title;

  @override
  State&lt;TextInputPage&gt; createState() =&gt; _TextInputPageState();
}

class _TextInputPageState extends State&lt;TextInputPage&gt; {
  static const _sampleText = &#39;Hello, nice to meet you.&#39;;

  final TextEditingController _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  /// コードから文字を入れ、カーソルを文字の最後に置く。
  void _insertSample() {
    _controller.text = _sampleText;
    _controller.selection = TextSelection.collapsed(offset: _sampleText.length);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _controller,
              maxLines: 6,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                hintText: &#39;直接入力してください&#39;,
              ),
            ),
            const SizedBox(height: 12),
            // 中身が変わるたびに、文字数を作り直す。
            ListenableBuilder(
              listenable: _controller,
              builder: (context, _) =&gt; Text(&#39;${_controller.text.length} 文字&#39;),
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                FilledButton(
                  onPressed: _insertSample,
                  child: const Text(&#39;サンプルを入れる&#39;),
                ),
                const SizedBox(width: 12),
                OutlinedButton(
                  onPressed: _controller.clear,
                  child: const Text(&#39;全部消す&#39;),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}</code></pre></div>



<ul class="wp-block-list">
<li>main.dart</li>
</ul>



<p class="wp-block-paragraph">動かすための最小の入口です。text_input_page.dart は lib/pages/ に置きます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;pages/text_input_page.dart&#39;;

void main() {
  runApp(
    const MaterialApp(home: TextInputPage(title: &#39;テキスト入力&#39;)),
  );
}</code></pre></div>



<h3 id="rtoc-4"  class="wp-block-heading">解説</h3>



<p class="wp-block-paragraph">画面は StatefulWidget で作っています。TextEditingController は、画面が表示されている間ずっと同じものを使い続けるので、State のフィールドとして1つだけ作ります。</p>



<p class="wp-block-paragraph">使っている部品は、入力欄の TextField、文字数を出す ListenableBuilder、操作する2つのボタンです。</p>



<h4 class="wp-block-heading">ListenableBuilder：文字数を更新し続ける</h4>



<p class="wp-block-paragraph">文字数の Text は ListenableBuilder で包んでいます。listenable に TextEditingController を渡すと、中身が変わるたびに builder の中が作り直されます。</p>



<p class="wp-block-paragraph">TextEditingController は、中身が変わると監視している側に知らせる仕組みを持っています（変化を知らせる部品の仲間として作られています）。ListenableBuilder は、公式の説明では、監視している対象が変わったときに、その部分の画面を作り直す部品で、builder は変化の知らせが来るたびに呼ばれます。ここでは、その知らせを受け取って、文字数の表示だけを更新します。</p>



<p class="wp-block-paragraph">onChanged の中で setState を呼んでも文字数は出せます。ただ、その場合は画面全体が作り直されます。ListenableBuilder なら、<strong>作り直す範囲を文字数の部分だけに絞れます</strong>。</p>



<h4 class="wp-block-heading">_insertSample：コードから文字を入れる</h4>



<p class="wp-block-paragraph">_controller.text = _sampleText; で、入力欄の文字が置き換わります。</p>



<p class="wp-block-paragraph">ただし、公式の説明では、text を書き換えると、<strong>選択範囲や変換中の範囲が消えます</strong>。文字を入れたあと、カーソルをどこに置くかも決まらなくなります。</p>



<p class="wp-block-paragraph">そこで、次の行で<strong>カーソルを文字の最後に置いています</strong>。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>_controller.selection = TextSelection.collapsed(offset: _sampleText.length);</code></pre></div>



<p class="wp-block-paragraph">TextSelection.collapsed は、範囲を選ばず、1か所にカーソルを置く指定です。offset に文字数を渡すと、文字の最後になります。</p>



<p class="wp-block-paragraph">文字とカーソルを同時に決めたいときは、公式の説明のとおり、value に TextEditingValue をまとめて入れる方法もあります。</p>



<h4 class="wp-block-heading">clear：全部消す</h4>



<p class="wp-block-paragraph">_controller.clear は、入力欄を空にする関数です。ボタンの onPressed に、そのまま渡せます。</p>



<h4 class="wp-block-heading">dispose：後片付け</h4>



<p class="wp-block-paragraph">TextEditingController は、使い終わったら dispose で後片付けします。公式の説明にも、不要になったら <strong>dispose する</strong>、と書かれています。画面が閉じるときに呼ばれる State の dispose の中で、_controller.dispose() を呼びます。</p>



<h2 id="rtoc-5"  class="wp-block-heading">エラーと解決策・対策</h2>



<p class="wp-block-paragraph">以下では、公式の説明で注意されている、つまずきやすい点と対処を紹介します。実際に起きたエラーではなく、起こりやすい点をまとめたものです。</p>



<h3 id="rtoc-6"  class="wp-block-heading">文字を入れたのに、カーソルが思った場所にない</h3>



<p class="wp-block-paragraph">_controller.text だけを書き換えた場合、選択範囲が消えるため、カーソルの位置が思った場所にならないことがあります。上のコードのように selection も指定するか、value にまとめて入れてください。</p>



<h3 id="rtoc-7"  class="wp-block-heading">入力のたびに、変な動きや無限ループになる</h3>



<p class="wp-block-paragraph">addListener の中で、同じ TextEditingController の text を書き換えると、書き換えがまた知らせとして届くため、<strong>無限に繰り返すおそれ</strong>があります。公式の説明でも、この点が注意されています。入力内容を整えたいときは、入力時に文字を変換する TextInputFormatter を使ってください。</p>



<h2 id="rtoc-8"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">TextField は入力欄の部品、TextEditingController はその中身を操作する部品です。</p>



<ul class="wp-block-list">
<li>controller に渡すと、text で読み書きできる</li>



<li>text を書き換えると選択範囲が消えるので、カーソルの位置は selection で決める</li>



<li>中身の変化に合わせて表示を変えるなら、ListenableBuilder で作り直す範囲を絞れる</li>



<li>使い終わったら dispose で後片付けする</li>



<li></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/flutter/textfield-input/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Flutter】speech_to_textで音声入力し文字起こし</title>
		<link>https://alicia-ing.com/programming/flutter/speech-to-text-voiceinput/</link>
					<comments>https://alicia-ing.com/programming/flutter/speech-to-text-voiceinput/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sat, 10 Oct 2026 14:47:43 +0000</pubDate>
				<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=9185</guid>

					<description><![CDATA[Flutterで英会話アプリや音声メモアプリを作るとき話した言葉を文字に入力が第一歩です。 Flutterではspeech_to_textというパッケージを使うと、スマホやブラウザに入っている音声認識の機能を、短いコード [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph"> 話しかけた内容がそのまま画面に文字で出続け、音声認識が止まっても自動で聞き取り直す音声入力画面を、speech_to_text で作れます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/10/complete.webp" alt="" class="wp-image-9199" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/complete.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/complete-640x360.webp 640w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">Flutterで英会話アプリや音声メモアプリを作るとき話した言葉を文字に入力が第一歩です。</p>



<p class="wp-block-paragraph">Flutterでは<strong>speech_to_text</strong>というパッケージを使うと、スマホやブラウザに入っている音声認識の機能を、短いコードで呼び出せます。ただし、そのままだと無音が続いたときなどに認識が止まってしまいます。</p>



<p class="wp-block-paragraph">本記事では、話している途中の文字を表示しながら、止まったら自動で聞き取り直す画面を作ります。パッケージの呼び出しは別のクラスに分け、画面側のコードをすっきりさせます。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Flutter で音声入力を作りたい人</li>



<li>speech_to_text の基本的な流れ（初期化、聞き取り開始、結果の受け取り）を知りたい人</li>



<li>話している途中の文字を、画面にリアルタイムで表示したい人</li>



<li>音声認識が途中で止まる問題への対処を知りたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="9185" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">speech_to_text（音声認識パッケージ）</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">インストール</a></li><li class="rtoc-item"><a href="#rtoc-3">事前設定（マイクの許可）</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">話した内容を文字にする音声入力画面</a></li><li class="rtoc-item"><a href="#rtoc-5">ソースコード</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">解説</a></li><li class="rtoc-item"><a href="#rtoc-7">各関数の機能</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-8">実装</a></li><li class="rtoc-item"><a href="#rtoc-9">エラーと解決策・対策</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-10">「この端末では音声認識を利用できません」と出る</a></li><li class="rtoc-item"><a href="#rtoc-11">「音声認識エラー」に permission と出る</a></li><li class="rtoc-item"><a href="#rtoc-12">言語が思った言語にならない</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-13">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">speech_to_text（音声認識パッケージ）</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package.webp" alt="" class="wp-image-9200" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/speech-to-text-package-240x135.webp 240w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">speech_to_text は、端末に入っている音声認識の機能を Flutter から使うためのパッケージです。Android、iOS、Web に対応しています。</p>



<p class="wp-block-paragraph">注意したいのは、このパッケージが想定している使い方です。公式の説明では、想定しているのはコマンドや短いフレーズで、長い会話を途切れなく文字にしたり、ずっと聞き続けたりする使い方は想定外とされています。</p>



<p class="wp-block-paragraph">そのため、今回のように「開いている間ずっと聞き取る」画面を作るときは、止まったら聞き取り直す仕組みを自分で用意します。</p>



<h3 id="rtoc-2"  class="wp-block-heading">インストール</h3>



<p class="wp-block-paragraph"><strong>pubspec.yaml の dependencies に追加</strong>します。この記事のコードは ^7.4.0 で作っています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>dependencies:
  speech_to_text: ^7.4.0
</code></pre></div>



<h3 id="rtoc-3"  class="wp-block-heading">事前設定（マイクの許可）</h3>



<p class="wp-block-paragraph">マイクを使うため、プラットフォームごとに設定が必要です。設定の内容は公式ページの記載にもとづきます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>プラットフォーム</th><th>設定</th></tr></thead><tbody><tr><td>Android</td><td>AndroidManifest.xml に RECORD_AUDIO と INTERNET などの権限を追加する。ターゲット SDK が 30 以上なら、android.speech.RecognitionService を指定した queries も追加する</td></tr><tr><td>iOS</td><td>Info.plist に NSSpeechRecognitionUsageDescription と NSMicrophoneUsageDescription を追加する</td></tr><tr><td>Web</td><td>設定は不要。ただし対応しているブラウザは一部のみ</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://pub.dev/packages/speech_to_text">speech_to_text 公式ページ</a></p>



<h2 id="rtoc-4"  class="wp-block-heading">話した内容を文字にする音声入力画面</h2>



<p class="wp-block-paragraph">ここまでの内容をもとに、次の動きをする画面を作ります。</p>



<ul class="wp-block-list">
<li>画面を開くと、すぐ聞き取りを始める</li>



<li>話している途中の文字も、確定した文字も、画面に出し続ける</li>



<li>右上のマイクのアイコンが、聞き取り中は赤、停止中は灰色になる</li>



<li>認識が止まったら、少し待って自動で聞き取り直す</li>
</ul>



<h2 id="rtoc-5"  class="wp-block-heading">ソースコード</h2>



<ul class="wp-block-list">
<li>speech_service.dart</li>
</ul>



<p class="wp-block-paragraph">speech_to_text を包むクラス。パッケージの細かい呼び出しをここに集める</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:speech_to_text/speech_to_text.dart&#39; as stt;

class SpeechService {
  final stt.SpeechToText _speech = stt.SpeechToText();

  Future&lt;bool&gt; initialize({
    required void Function(bool isListening) onStatusChange,
    required void Function(String message) onError,
  }) {
    return _speech.initialize(
      onStatus: (status) =&gt; onStatusChange(status == &#39;listening&#39;),
      onError: (error) =&gt; onError(error.errorMsg),
    );
  }

  /// 話した内容を、認識できた分だけ onResult に渡し続ける。
  /// 一区切り話し終えると isFinal が true になる。
  Future&lt;void&gt; listen({
    required void Function(String recognizedWords, bool isFinal) onResult,
    String? localeId,
  }) {
    return _speech.listen(
      onResult: (result) =&gt; onResult(result.recognizedWords, result.finalResult),
      listenOptions: stt.SpeechListenOptions(
        localeId: localeId,
        partialResults: true,
      ),
    );
  }

  Future&lt;void&gt; stop() =&gt; _speech.stop();
}
</code></pre></div>



<ul class="wp-block-list">
<li>voice_input_page.dart</li>
</ul>



<p class="wp-block-paragraph">画面。表示する文字と状態だけを持つ</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;dart:async&#39;;
import &#39;package:flutter/material.dart&#39;;
import &#39;../services/speech_service.dart&#39;;

class VoiceInputPage extends StatefulWidget {
  const VoiceInputPage({super.key, required this.title});

  final String title;

  @override
  State&lt;VoiceInputPage&gt; createState() =&gt; _VoiceInputPageState();
}

class _VoiceInputPageState extends State&lt;VoiceInputPage&gt; {
  final SpeechService _speech = SpeechService();

  bool _speechEnabled = false;
  bool _isListening = false;
  Timer? _restartTimer;

  String _committedText = &#39;&#39;; // 話し終えて確定した分
  String _partialText = &#39;&#39;; // いま話している途中の分
  String _statusMessage = &#39;&#39;;

  @override
  void initState() {
    super.initState();
    _initSpeech();
  }

  @override
  void dispose() {
    _restartTimer?.cancel();
    _speech.stop();
    super.dispose();
  }

  Future&lt;void&gt; _initSpeech() async {
    final available = await _speech.initialize(
      onStatusChange: _handleStatus,
      onError: _handleError,
    );
    if (!mounted) return;
    setState(() {
      _speechEnabled = available;
      if (!available) _statusMessage = &#39;この端末では音声認識を利用できません&#39;;
    });
    if (available) _listen();
  }

  Future&lt;void&gt; _listen() async {
    if (!mounted || !_speechEnabled || _isListening) return;
    await _speech.listen(onResult: _handleResult);
  }

  void _handleResult(String words, bool isFinal) {
    setState(() {
      if (isFinal) {
        _committedText = &#39;$_committedText $words&#39;.trim();
        _partialText = &#39;&#39;;
      } else {
        _partialText = words;
      }
    });
  }

  // 認識が止まったら、少し待って聞き取り直す。
  void _handleStatus(bool listening) {
    if (!mounted) return;
    setState(() =&gt; _isListening = listening);
    if (!listening && _speechEnabled) {
      _restartTimer?.cancel();
      _restartTimer = Timer(const Duration(milliseconds: 300), _listen);
    }
  }

  void _handleError(String message) {
    if (!mounted) return;
    setState(() {
      _isListening = false;
      _statusMessage = &#39;音声認識エラー: $message&#39;;
      // マイクの許可がないときは、やり直しても同じなので止める。
      if (message.contains(&#39;permission&#39;)) _speechEnabled = false;
    });
  }

  String get _displayText =&gt; &#39;$_committedText $_partialText&#39;.trim();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Icon(
              _isListening ? Icons.mic : Icons.mic_off,
              color: _isListening ? Colors.red : Colors.grey,
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Expanded(
              child: SingleChildScrollView(
                child: Text(
                  _displayText.isEmpty ? &#39;話しかけてください&#39; : _displayText,
                  style: Theme.of(context).textTheme.headlineSmall,
                ),
              ),
            ),
            Text(_statusMessage),
          ],
        ),
      ),
    );
  }
}
</code></pre></div>



<ul class="wp-block-list">
<li>main.dart</li>
</ul>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-dart" data-lang="Dart"><code>import &#39;package:flutter/material.dart&#39;;

import &#39;pages/voice_input_page.dart&#39;;

void main() {
  runApp(
    const MaterialApp(home: VoiceInputPage(title: &#39;音声入力&#39;)),
  );
}
</code></pre></div>



<h3 id="rtoc-6"  class="wp-block-heading">解説</h3>



<p class="wp-block-paragraph">音声認識の呼び出しはSpeechServiceに集め、画面のVoiceInputPageは「いま何が表示されているか」だけを持ちます。こうしておくと、あとで別のパッケージに替えるときも、直すのは SpeechService だけで済みます。</p>



<p class="wp-block-paragraph">画面が持つ状態は次の4つです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>変数</th><th>意味</th></tr></thead><tbody><tr><td>_speechEnabled</td><td>この端末で音声認識が使えるか</td></tr><tr><td>_isListening</td><td>いま聞き取り中か（アイコンの色に使う）</td></tr><tr><td>_committedText</td><td>話し終えて確定した文字</td></tr><tr><td>_partialText</td><td>いま話している途中の文字</td></tr></tbody></table></div></figure>



<h3 id="rtoc-7"  class="wp-block-heading">各関数の機能</h3>



<h4 class="wp-block-heading">initialize：音声認識を使える状態にする</h4>



<p class="wp-block-paragraph">initializeは、<strong>音声認識を使える状態にする最初の処理</strong>です。使えるときは true、使えないときは false が返ります。公式の説明では、initializeは一度だけ呼ぶ想定で、2回目以降の呼び出しは無視されます。そのため、状態の変化とエラーを受け取る関数は、最初の呼び出しで渡します。</p>



<p class="wp-block-paragraph">SpeechService.initialize では、受け取った状態が listening のときだけ true として画面に伝えます。</p>



<h4 class="wp-block-heading">listen：聞き取りを始める</h4>



<p class="wp-block-paragraph"><strong>listen で聞き取り</strong>を始めます。ポイントは partialResults: true です。これを指定すると、話し終わるのを待たず、話している途中の文字が結果に何度も届きます。話しながら文字が増えていく動きは、この設定のおかげです。</p>



<p class="wp-block-paragraph"><strong>localeIdを省略すると、端末の標準の言語で聞き取り</strong>ます。言語を指定したいときは、locales() で取れる言語の一覧にある ID を渡します。</p>



<h4 class="wp-block-heading">_handleResult：途中の文字と確定した文字を分ける</h4>



<p class="wp-block-paragraph">結果は isFinal で2つに分かれます。</p>



<ul class="wp-block-list">
<li>isFinal が false：話している途中。_partialText を上書きする</li>



<li>isFinal が true：一区切り話し終えた。_committedText の後ろに足し、_partialText を空にする</li>
</ul>



<p class="wp-block-paragraph">画面には、2つをつなげた _displayText を出します。</p>



<h4 class="wp-block-heading">_handleStatus：止まったら聞き取り直す</h4>



<p class="wp-block-paragraph"><strong>音声認識は、無音が続くなどで自分から止まります。</strong>公式の説明のとおり、ずっと聞き続ける使い方は想定されていないためです。</p>



<p class="wp-block-paragraph">そこで、状態が「聞き取り中」から変わったら、Timer で 300 ミリ秒待ってから _listen を呼び直します。止まった直後には呼び直さず、少し間を置いてから再開しています。</p>



<h4 class="wp-block-heading">_handleError：許可がないときは止める</h4>



<p class="wp-block-paragraph">エラーの文字にpermission が含まれていたら、マイクの許可がありません。何度やり直しても同じ結果になるので_speechEnabled を false にして再開を止めます。</p>



<h4 class="wp-block-heading">dispose：後片付け</h4>



<p class="wp-block-paragraph">画面を閉じるときに、再開用のTimerを止め、聞き取りも止めます。</p>



<h2 id="rtoc-8"  class="wp-block-heading">実装</h2>



<ol class="wp-block-list">
<li>Flutter のプロジェクトを作り、speech_to_text を pubspec.yaml に追加して flutter pub get を実行する</li>



<li>先ほどの「事前設定」を済ませる</li>



<li>上のコードを lib/ に置き、実行する</li>



<li>初回はマイクの許可を求められるので、許可する</li>
</ol>



<p class="wp-block-paragraph">画面を開くと聞き取りが始まり、アイコンが赤になります。話しかけると、途中の文字が出て、話し終えると確定します。</p>



<h2 id="rtoc-9"  class="wp-block-heading">エラーと解決策・対策</h2>



<p class="wp-block-paragraph">以下では、想定しうるエラーの解決方法を紹介します。</p>



<h3 id="rtoc-10"  class="wp-block-heading">「この端末では音声認識を利用できません」と出る</h3>



<p class="wp-block-paragraph"><strong>initialize が false を返した状態</strong>です。音声認識が使えない環境で起きます。公式の説明では、ビルドはできても音声認識に対応していないプラットフォームがあり、その場合は initialize が false を返します。</p>



<p class="wp-block-paragraph">先に述べた事前設定が済んでいるか、対応しているブラウザや端末かを確認してください。</p>



<h3 id="rtoc-11"  class="wp-block-heading">「音声認識エラー」に permission と出る</h3>



<p class="wp-block-paragraph"><strong>マイクの許可がありません。</strong>端末やブラウザの設定で、アプリにマイクの使用を許可してください。このコードでは、許可がないと分かった時点で聞き取りのやり直しを止めます。</p>



<h3 id="rtoc-12"  class="wp-block-heading">言語が思った言語にならない</h3>



<p class="wp-block-paragraph">localeId を省略すると、端末の標準の言語で聞き取ります。ほかの言語で話したいときは、<strong>locales() で取れる言語の ID を listen の localeId</strong> に渡してください。</p>



<h2 id="rtoc-13"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">speech_to_text の流れは、initialize で準備し、listen で聞き取り、結果を受け取る、の3つです。</p>



<ul class="wp-block-list">
<li>partialResults: true で、話している途中の文字を出せる</li>



<li>isFinal で、途中の文字と確定した文字を分けて持つ</li>



<li>音声認識は自分から止まるので、状態の変化を見て聞き取り直す</li>



<li>パッケージの呼び出しは SpeechService に分け、画面は状態だけを持つ</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/flutter/speech-to-text-voiceinput/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【NordVPN】買い方から解約・返金まで｜実際に契約した感想</title>
		<link>https://alicia-ing.com/life/best-buy/nordvpn-contract/</link>
					<comments>https://alicia-ing.com/life/best-buy/nordvpn-contract/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sun, 04 Oct 2026 15:39:34 +0000</pubDate>
				<category><![CDATA[買ってよかったもの]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=8993</guid>

					<description><![CDATA[海外でTVerやU-NEXTなど動画コンテンツを楽しみたい。そこでVPNを契約したいけど、こんなことで迷っていませんか？ この記事ではそうした疑問に答えるために、契約からiPhoneで接続するまでの手順と、自動更新の止め [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">海外でTVerやU-NEXTなど動画コンテンツを楽しみたい。<br>そこでVPNを契約したいけど、こんなことで迷っていませんか？</p>



<ul class="wp-block-list">
<li>契約の手順が分からず、どのようにすればよいか分からない</li>



<li>契約したあとにやめたくなったとき、きちんと解約できるのか不安</li>



<li>「30日間返金保証」があると聞くけれど、条件がよく分からない</li>



<li>iPhoneで使うには、何をすればいいのか知りたい</li>
</ul>



<p class="wp-block-paragraph">この記事ではそうした疑問に答えるために、<strong>契約からiPhoneで接続するまでの手順</strong>と、<strong>自動更新の止め方</strong>、<strong>返金の条件</strong>を、実際の画面でまとめます。</p>



<p class="wp-block-paragraph">筆者は、飛行機搭乗の数十分前から契約を始めましたが離陸前までに契約できました。スマホから短い時間で簡単ですので是非契約をしてみて下さい。</p>



<div class="wp-block-cocoon-blocks-button-wrap-1 aligncenter btn-wrap btn-wrap-block button-block btn-wrap-m btn-wrap-circle btn-wrap-shine has-background has-light-green-background-color"><a rel="nofollow" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+66OZ6">NordVPNを見てみる</a>
<img loading="lazy" decoding="async" border="0" width="1" height="1" src="https://www13.a8.net/0.gif?a8mat=4B88ST+198YR6+3YFI+66OZ6" alt=""></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="8993" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">NordVPNを契約した理由</a></li><li class="rtoc-item"><a href="#rtoc-2">契約前に確認すること</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-3">契約手順（ステップごと）</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">解約・自動更新停止を止める方法</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-5">公式サイト経由で契約した場合</a></li><li class="rtoc-item"><a href="#rtoc-6">30日間返金保証を使うには</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">NordVPNを契約した理由</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson.webp" alt="" class="wp-image-8999" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-reson-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">私がVPNを契約したきっかけはドイツへの留学です。</p>



<p class="wp-block-paragraph">ドイツへは研究のために留学をしましたが、そこで研究をする前に一度研究室へのあいさつ回りでドイツへ渡航しました。</p>



<p class="wp-block-paragraph">しかし現地ではU-NEXTやTVerを始め日本の動画サービスが使えませんでした。YouTube、Twitter、Instagramは見られても、それ以外のアプリは見られず、不便に感じました。</p>



<p class="wp-block-paragraph">そこで<strong>ドイツでの接続が安定して接続できる</strong>情報を知り、2回目の渡航では<a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>を契約しました。</p>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">日本の動画配信サービスを海外から見られるかどうかは、各サービスの利用規約や配信地域の決まりによって変わります。利用する前に、各サービスの規約を確認してください。</p>
</div>



<h2 id="rtoc-2"  class="wp-block-heading">契約前に確認すること</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways.webp" alt="" class="wp-image-9000" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-ways-320x180.webp 320w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph">契約の前に、次の3つを用意しておくとスムーズです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">項目</th><th class="has-text-align-left" data-align="left">内容</th></tr></thead><tbody><tr><td>メールアドレス</td><td>アカウント作成とログインに使います</td></tr><tr><td>支払い方法</td><td>契約時に選びます（使える支払い方法は公式サイトで確認してください）</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">&nbsp;どこで契約したかによって、あとで解約や返金をする場所が変わります。公式サイトで契約すれば手続きは<a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>側で行えますが、Apple（App Store）などを経由して契約すると、解約や返金は購入元で行う必要があります。</p>



<p class="wp-block-paragraph">詳しくは後半の「解約・返金」で説明します。</p>



<p class="wp-block-paragraph">私は、あとで気に入らなかったときに30日以内に返金してもらえるよう、アプリを入れる前にブラウザで「NordVPN」と検索して、<strong>公式サイトから直接契約</strong>しました。App Store経由だと、返金の手続きが<a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>ではなくAppleになるためです。</p>



<p class="wp-block-paragraph">スマホで契約する人は、先にアプリを入れず、公式サイトから契約することをおすすめします。</p>



<h3 id="rtoc-3"  class="wp-block-heading">契約手順（ステップごと）</h3>



<p class="wp-block-paragraph"><a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>を契約する方法を各ステップごとに説明をします。</p>



<div class="wp-block-cocoon-blocks-timeline timeline-box cf block-box not-nested-style cocoon-block-timeline"><div class="timeline-title">スマホでアクセスする方法</div><ul class="timeline">
<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step1</div><div class="timeline-item-content cf"><div class="timeline-item-title">ページにアクセスしプランを選ぶ</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step2</div><div class="timeline-item-content cf"><div class="timeline-item-title">アカウント作成</div><div class="timeline-item-snippet"></div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step3</div><div class="timeline-item-content cf"><div class="timeline-item-title">支払い方法を選ぶ</div><div class="timeline-item-snippet"></div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step4</div><div class="timeline-item-content cf"><div class="timeline-item-title">アプリを入れてログイン</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step5</div><div class="timeline-item-content cf"><div class="timeline-item-title">VPN接続</div><div class="timeline-item-snippet"></div></div></li>
</ul></div>



<h4 class="wp-block-heading">プランを選ぶ</h4>



<p class="wp-block-paragraph">公式サイトの料金ページを開き、使いたい期間のプランを選びます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/plan.webp" alt="" class="wp-image-9004" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/plan.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/plan-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">上の画面は、2026年9月に開いたときの料金ページです。<br>「<strong>1か月プラン</strong>」を選ぶと、ベーシックプランが<strong>月額2,280円</strong>と表示されていました。<br>画面には「<strong>30日間返金保証</strong>」の表示もあります。</p>



<p class="wp-block-paragraph"><strong>私は1か月プランを契約</strong>しました。ドイツには1年もいないため、滞在期間に合わせて月額のプランにしました。</p>



<p class="wp-block-paragraph"><strong>プランは1か月・1年・2年</strong>があり、<strong>長期になるほど割引</strong>が大きくなります。公式サイトで見ると、ベーシックプランの2年プランは月額580円（最初の27か月分。3か月延長つき）でした。ドイツに長く滞在する人は、長期プランのほうがお得だと思います。</p>



<p class="wp-block-paragraph">ただし、これは最初の期間の価格で、更新後は料金が変わる点には注意してください。</p>



<h4 class="wp-block-heading">アカウントを作成する</h4>



<p class="wp-block-paragraph">プランを選んだら、メールアドレスを入力してアカウントを作成します。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/account.webp" alt="" class="wp-image-9005" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/account.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/account-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/account-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/account-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/account-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/account-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/account-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/account-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">このメールアドレスは、ログインや契約内容の確認メールの受け取りに使われます。</p>



<h4 class="wp-block-heading">支払い方法を選ぶ</h4>



<p class="wp-block-paragraph">次に支払い方法を選んで支払い情報を入力します。確定ボタンを押すと契約が完了します。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/payment.webp" alt="" class="wp-image-9006" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/payment.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/payment-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">&nbsp;支払い方法は<strong>クレジットカード</strong>にしました。返金保証が使えなくなるのが不安で、クレジットカードを選びました。</p>



<p class="wp-block-paragraph">ただ、公式の料金ページやヘルプを確認したところ、返金の条件は「初回購入から30日以内」で、支払い方法によって返金できるかが変わるという記載は見当たりませんでした。</p>



<p class="wp-block-paragraph">支払い方法は、<strong>クレジットカードのほか、PayPalやGoogle Pay、Apple Pay</strong>などが案内されています。入力で迷う場面はありませんでした。</p>



<h4 class="wp-block-heading">アプリを入れてログインする</h4>



<p class="wp-block-paragraph">契約が終わったら、アプリストアで「NordVPN」を検索してインストールします。WindowsやMacでもアプリがあるのでインストールしてください。完了後、先ほど作ったアカウントでログインします。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1013" src="https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect.webp" alt="" class="wp-image-9007" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect-300x159.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect-766x405.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect-1024x541.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/Before_connect-1536x812.webp 1536w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">この時点ではまだVPNにつながっていないため「保護されていません」と表示されています。VPNの接続は簡単で、<strong>「すべてのロケーションで検索」で日本を選択</strong>するか、<strong>最近の履歴</strong>から選んでください。</p>



<p class="wp-block-paragraph">ホーム画面の<strong>青いVPNで接続を保護ボタン</strong>を押すと、VPNに接続されます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1027" src="https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN.webp" alt="" class="wp-image-9008" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN-300x161.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN-767x411.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN-1024x549.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/connectVPN-1536x823.webp 1536w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">&nbsp;契約したあとすぐに飛行機に乗ったため事前に接続を試すことはできませんでした。それでも、日本のサーバーへの接続は<strong>1回のタップ</strong>でできてとても楽でした。</p>



<p class="wp-block-paragraph">インストールとログインも、ほかのサービスと同じ感覚でできます。パソコンの場合も、アプリ（ファイル）を入れて実行すれば接続できます。私はiPhoneとノートパソコンで、おもに動画を見るときに使っています。</p>



<p class="wp-block-paragraph">他にも外出先Wifiにつなぐとき海外では見ている内容が筒抜けで個人情報が抜き取られてしまうといった問題が起きやすいです。（日本でも野良Wifiにつなぐと被害に遭います。）</p>



<p class="wp-block-paragraph">そこで、<a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>を契約しておくと安全に接続ができます。</p>



<h2 id="rtoc-4"  class="wp-block-heading">解約・自動更新停止を止める方法</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="768" src="https://alicia-ing.com/wp-content/uploads/2026/10/contact-day.webp" alt="" class="wp-image-9001" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/contact-day.webp 1376w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-day-300x167.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-day-767x428.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-day-1024x572.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-day-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-day-320x180.webp 320w" sizes="(max-width: 1376px) 100vw, 1376px" /></figure>



<p class="wp-block-paragraph"><a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>は、期間が終わると自動的に更新される仕組みです。使わなくなったら更新される前に<strong>自動更新を止める</strong>必要があります。</p>



<p class="wp-block-paragraph">自動更新を止めてもお金が戻るわけではありません。自動更新を止めるとすでに払った期間の終わりまではそのまま使えてその後に更新されなくなります。</p>



<p class="wp-block-paragraph">お金を戻してもらうには、次の「返金」の手続きが別に必要です。</p>



<h3 id="rtoc-5"  class="wp-block-heading">公式サイト経由で契約した場合</h3>



<p class="wp-block-paragraph"><a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>の公式ヘルプによると、手順は次のとおりです。パソコンでもiPhoneでも、アプリではなくブラウザ（iPhoneならSafariなど）でNordアカウントにログインして行います。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="1078" src="https://alicia-ing.com/wp-content/uploads/2026/10/contact.webp" alt="" class="wp-image-9009" srcset="https://alicia-ing.com/wp-content/uploads/2026/10/contact.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-766x431.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/10/contact-640x360.webp 640w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<ol class="wp-block-list">
<li>パソコンやスマホのブラウザで、Nordアカウント（my.nordaccount.com）にログインします。</li>



<li>「請求」（billing）のタブを開きます。</li>



<li>自動更新の横にある「キャンセル」を押します。</li>



<li>「自動更新をキャンセル」を選びます。</li>



<li>自動更新の表示が「オフ」に変わります。</li>
</ol>



<h3 id="rtoc-6"  class="wp-block-heading">30日間返金保証を使うには</h3>



<p class="wp-block-paragraph"><a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>には、新しく契約したときに使える<strong>30日間の返金保証</strong>があります。公式ヘルプによる条件は次のとおりです。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">項目</th><th class="has-text-align-left" data-align="left">内容</th></tr></thead><tbody><tr><td>対象</td><td>最初の契約（新規契約）のみ</td></tr><tr><td>期間</td><td>契約から30日以内</td></tr><tr><td>対象外（英語版ヘルプの記載）</td><td>支払いから30日を過ぎた場合／最初の支払いについてすでに返金済みの場合／更新後の支払い</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">&nbsp;日本語版のヘルプには、返金できない理由として「月間サブスクリプションを利用している」という項目もあります。英語版のヘルプには、この項目はありません。一方、料金ページには「すべてのプランには30日間の返金保証が付いています」とあり、1か月プランにも「30日間返金保証」と表示されていました。</p>



<p class="wp-block-paragraph"><strong>1か月プランで返金を考えている人は、申請する前にライブチャットで対象になるか確認する</strong>ことをおすすめします。</p>



<h2 id="rtoc-7"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph"><a rel="nofollow sponsored" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+674EQ">NordVPN</a>は、公式サイトでプランを選び、アカウントを作って支払いをすれば契約できます。契約後はアプリを入れてログインし、「VPNで接続を保護」を押すだけで接続できます。</p>



<p class="wp-block-paragraph">返金を望む場合は契約前に以下の内容を押さえておきたいです。</p>



<ul class="wp-block-list">
<li>自動更新を止めても、返金はされない</li>



<li>返金保証が使えるのは、新規契約から30日以内</li>



<li>契約した場所（公式サイトかApp Storeか）で、解約・返金の手続きをする場所が変わる</li>
</ul>



<div class="wp-block-cocoon-blocks-button-wrap-1 aligncenter btn-wrap btn-wrap-block button-block btn-wrap-m btn-wrap-circle btn-wrap-shine has-background has-light-green-background-color"><a rel="nofollow" target="_self" href="https://px.a8.net/svt/ejp?a8mat=4B88ST+198YR6+3YFI+66OZ6">NordVPNを見てみる</a>
<img loading="lazy" decoding="async" border="0" width="1" height="1" src="https://www13.a8.net/0.gif?a8mat=4B88ST+198YR6+3YFI+66OZ6" alt=""></div>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/life/best-buy/nordvpn-contract/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Unity】矢印キーで2Dプレイヤーを1マスずつ移動</title>
		<link>https://alicia-ing.com/programming/unity/2dplayermove/</link>
					<comments>https://alicia-ing.com/programming/unity/2dplayermove/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sun, 27 Sep 2026 04:43:53 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[タイルマップ]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=8971</guid>

					<description><![CDATA[昔のRPGのように、キャラクターがマス目にそってカクカクと動く移動するゲームは3Dゲームよりも制作の負担もなく現在もレトロゲームや同人ゲームとして人気が強いです。 Tilemapでマップを作ったあと、「プレイヤーを1マス [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">矢印キーで2Dプレイヤーをタイル1マスずつ動かし、長押しでの連続移動と、進む方向に合わせた向きの画像の切り替えができるようになります。</p>



<figure class="wp-block-image size-full"><img decoding="async" src="https://alicia-ing.com/wp-content/uploads/2026/09/complete-1.webp" alt=""/></figure>
</div></div>



<p class="wp-block-paragraph">昔のRPGのように、<strong>キャラクターがマス目にそってカクカクと動く移動するゲーム</strong>は3Dゲームよりも制作の負担もなく現在もレトロゲームや同人ゲームとして人気が強いです。</p>



<p class="wp-block-paragraph">Tilemapでマップを作ったあと、「プレイヤーを1マスずつ動かすにはどうすればいいの？」とつまずく人も多いはずです。</p>



<p class="wp-block-paragraph">本記事では、2dキャラクターの実装として、Input.GetKeyを使ったキー入力から1マスずつの移動・向きの切り替えまでを解説します。キーを押しっぱなしにしたときの連続移動や、2つのキーを同時に押したときの優先順位など、実際に遊ぶと気になる動きにも対応しています。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>3Dのキャラクター移動を解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/3dplayermove/" title="【Unity】Rigidbody+CapsuleColliderを用いたプレイヤー移動" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="320" height="180" src="https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-320x180.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-640x360.webp 640w" sizes="(max-width: 320px) 100vw, 320px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】Rigidbody+CapsuleColliderを用いたプレイヤー移動</div><div class="blogcard-snippet internal-blogcard-snippet">UnityでCharacterControllerからRigidbody+CapsuleColliderへ移動処理を移行する方法を解説。カメラ相対移動、FixedUpdateでの速度制御、移行の判断基準まで紹介します。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.07.13</div></div></div></div></a>
</div>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>矢印キーで2Dキャラクターを1マスずつ動かしたい人</li>



<li>Tilemapで作ったマップの上をプレイヤーに歩かせたい人</li>



<li>unity キー 入力（GetKey・GetKeyDown）の違いと使い分けを知りたい人</li>



<li>キーを押しっぱなしにしたときに、ちょうど良い速さで連続移動させたい人</li>



<li>進む方向に合わせてキャラクターの画像を上下左右に切り替えたい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="8971" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">Input.GetKey（キー入力）</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">GetKey・GetKeyDown・GetKeyUpの違い</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-3">event Action（C#のイベント）で処理をつなぐ</a></li><li class="rtoc-item"><a href="#rtoc-4">SpriteRenderer.spriteで画像を差し替える</a></li><li class="rtoc-item"><a href="#rtoc-5">矢印キーで1マスずつ動く2Dプレイヤー移動</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">ソースコード</a></li><li class="rtoc-item"><a href="#rtoc-7">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-8">実装</a></li><li class="rtoc-item"><a href="#rtoc-9">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">Input.GetKey（キー入力）</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey.webp" alt="" class="wp-image-8981" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/input-getkey-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">Input.GetKeyは、<strong>キーボードのキーが押されているかどうか</strong>を調べる関数です。矢印キーでキャラクターを動かす、スペースキーでジャンプする、Escキーでメニューを開くなど、<strong>キーボードで操作するゲーム</strong>のほとんどで使われます。</p>



<p class="wp-block-paragraph">使い方は、調べたいキーをKeyCodeで指定するだけです。矢印キーはそれぞれ次のKeyCodeに対応しています。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>キー</th><th>Keycode</th></tr></thead><tbody><tr><td>↑　上</td><td>KeyCode.UpArrow</td></tr><tr><td>↓　下</td><td>KeyCode.DownArrow</td></tr><tr><td>→　右</td><td>KeyCode.RightArrow</td></tr><tr><td>←　左</td><td>KeyCode.LeftArrow</td></tr></tbody></table></div></figure>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>if (Input.GetKey(KeyCode.RightArrow))
{
    // →キーが押されている間、毎フレーム実行される
}</code></pre></div>



<p class="wp-block-paragraph">キー入力の処理は、毎フレーム呼ばれるUpdate()の中に書きます。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://docs.unity3d.com/ja/current/ScriptReference/Input.GetKey.html">Input.GetKey &#8211; Unity スクリプトリファレンス</a></p>



<h3 id="rtoc-2"  class="wp-block-heading">GetKey・GetKeyDown・GetKeyUpの違い</h3>



<p class="wp-block-paragraph">キー入力を調べる関数は3種類あり、trueを返すタイミングが違います。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">関数</th><th class="has-text-align-left" data-align="left">trueになるタイミング</th><th class="has-text-align-left" data-align="left">主な用途</th></tr></thead><tbody><tr><td>Input.GetKey</td><td>キーを押している間ずっと（毎フレーム）</td><td>押しっぱなしで動き続ける移動・ダッシュ</td></tr><tr><td>Input.GetKeyDown</td><td>キーを押した瞬間の1フレームだけ</td><td>ジャンプ・決定・1回だけの移動</td></tr><tr><td>Input.GetKeyUp</td><td>キーを離した瞬間の1フレームだけ</td><td>ため攻撃の発射・長押しの終了</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">1マスずつ動かしたい場合、GetKeyだけを使うと毎フレーム1マス進んでしまい、一瞬で画面の端まで飛んでいってしまいます。反対にGetKeyDownだけを使うと、押すたびに1マスしか進まず、長い距離を歩くときにキーを何度も叩くことになります。</p>



<p class="wp-block-paragraph">そこで本記事のコードでは、2つを組み合わせています。</p>



<ul class="wp-block-list">
<li><strong>GetKeyDown：</strong> どのキーが「新しく押されたか」を調べる</li>



<li><strong>GetKey：</strong> そのキーが「まだ押され続けているか」を調べ、一定の間隔で連続移動する</li>
</ul>



<h2 id="rtoc-3"  class="wp-block-heading">event Action（C#のイベント）で処理をつなぐ</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/event-action.webp" alt="" class="wp-image-8982" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/event-action.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/event-action-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">eventは、<strong>「何かが起きたこと」を他のスクリプトに知らせるためのC#の仕組み</strong>です。Actionと組み合わせると、知らせるときに値（移動方向など）も一緒に渡せます。</p>



<p class="wp-block-paragraph">例えば「キーが押された」ことを入力のスクリプトが知らせ、それを受け取った移動のスクリプトがプレイヤーを動かす、という使い方をします。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>// 知らせる側
public event Action&lt;Vector3&gt; MoveRequested;   // Vector3（方向）を一緒に渡すイベント
MoveRequested?.Invoke(Vector3.up);            // 登録されている処理をまとめて呼ぶ

// 受け取る側
input.MoveRequested += OnMoveRequested;       // 登録
input.MoveRequested -= OnMoveRequested;       // 解除</code></pre></div>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">書き方</th><th class="has-text-align-left" data-align="left">意味</th></tr></thead><tbody><tr><td>event Action</td><td>値を渡さないイベント</td></tr><tr><td>event Action&lt;T></td><td>値を1つ渡すイベント</td></tr><tr><td>event Action&lt;T1, T2></td><td>値を2つ渡すイベント</td></tr><tr><td>?.Invoke(&#8230;)</td><td>登録している処理があれば呼び出す（誰も登録していなければ何もしない）</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">イベントを使うと、入力・移動・画像の切り替えを別々のスクリプトに分けられます。それぞれのスクリプトはお互いの中身を知らなくてよいため、後から「NPCにも同じ移動を使いたい」「入力をゲームパッドに変えたい」となったときに、一部だけを差し替えられます。</p>



<p class="wp-block-paragraph">なお、Unityには<strong>Inspectorから登録できるUnityEvent</strong>もあります。本記事ではコードの中だけで完結させるため、C#のeventを使っています。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/events/">イベント &#8211; C# プログラミング ガイド（Microsoft Learn）</a></p>



<h2 id="rtoc-4"  class="wp-block-heading">SpriteRenderer.spriteで画像を差し替える</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer.webp" alt="" class="wp-image-8983" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/spriteRenderer-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">SpriteRendererは、<strong>2Dの画像（スプライト）を画面に表示するコンポーネント</strong>です。<br>spriteプロパティに別のスプライトを代入すると、表示している画像をその場で差し替えられます。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>playerRenderer.sprite = spriteUp;   // 上向きの画像に切り替える</code></pre></div>



<p class="wp-block-paragraph">キャラクターの向きを変える方法には、ほかにAnimatorでアニメーションを切り替える方法もあります。</p>



<p class="wp-block-paragraph"><strong>歩きのアニメーションまで作り込みたい場合はAnimatorが向いています</strong>が、上下左右の4枚の画像を切り替えるだけなら、spriteを差し替える方法の方が設定が少なく済みます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">方法</th><th class="has-text-align-left" data-align="left">向いている場面</th></tr></thead><tbody><tr><td>SpriteRenderer.spriteの差し替え</td><td>上下左右の静止画を切り替えるだけのとき（本記事）</td></tr><tr><td>Animator（Blend Tree）</td><td>方向ごとに歩きのアニメーションを付けたいとき</td></tr></tbody></table></div></figure>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>オブジェクトが表示されないエラーについて紹介しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/render-camera/" title="【Unity】2D/3Dオブジェクトが表示されない原因と対処方法" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2024/06/Screen-Space-Camera-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2024/06/Screen-Space-Camera-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2024/06/Screen-Space-Camera-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2024/06/Screen-Space-Camera-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】2D/3Dオブジェクトが表示されない原因と対処方法</div><div class="blogcard-snippet internal-blogcard-snippet">2D/3Dオブジェクトが表示されない原因は、カメラワークの不具合が考えられます。「Screen Space - Camera」にして、MainCameraをアタッチすると正しく表示されます。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2024.11.10</div></div></div></div></a>
</div>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://docs.unity3d.com/ja/current/ScriptReference/SpriteRenderer-sprite.html">SpriteRenderer.sprite &#8211; Unity スクリプトリファレンス</a></p>



<h2 id="rtoc-5"  class="wp-block-heading">矢印キーで1マスずつ動く2Dプレイヤー移動</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/keymove.webp" alt="" class="wp-image-8984" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/keymove.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/keymove-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">ここまでに説明したInput.GetKey・event Action・SpriteRenderer.spriteを組み合わせて、Tilemapの上を1マスずつ歩く2Dプレイヤーを作ります。実装する動きは次のとおりです。</p>



<ul class="wp-block-list">
<li><strong>1マスずつ移動：</strong> 矢印キーを押すと、上下左右に1マス分だけ移動する</li>



<li><strong>長押しで連続移動：</strong> キーを押しっぱなしにすると、少し待ってから一定の間隔で進み続ける</li>



<li><strong>同時押しの優先：</strong> 2つのキーを押したときは、後から押したキーの方向へ進む（斜めには進まない）</li>



<li><strong>向きの切り替え：</strong> 進む方向に合わせて、キャラクターの画像を上下左右に切り替える</li>



<li><strong>止まったときの表示：</strong> キーを離したら「待機画像」か「最後に向いた方向」のどちらかを表示する</li>
</ul>



<p class="wp-block-paragraph">処理は役割ごとに5つのスクリプトに分けています。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">スクリプト</th><th class="has-text-align-left" data-align="left">役割</th></tr></thead><tbody><tr><td>PlayerInput.cs</td><td>矢印キーの入力から動く方向を決める</td></tr><tr><td>GridMover.cs</td><td>対象を1マス動かし「動こうとした」「動いた」と知らせる</td></tr><tr><td>PlayerDirectionSprite.cs</td><td>向きに合わせて画像を差し替える</td></tr><tr><td>PlayerSpawner.cs</td><td>ゲーム開始時の位置と向きを決める</td></tr><tr><td>PlayerController.cs</td><td>上の4つをイベントでつなぐ</td></tr></tbody></table></div></figure>



<h3 id="rtoc-6"  class="wp-block-heading">ソースコード</h3>



<h4 class="wp-block-heading">PlayerInput.cs</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using System;
using System.Collections.Generic;
using UnityEngine;

/// &lt;summary&gt;
/// 矢印キーから移動の要求
/// &lt;/summary&gt;
public class PlayerInput : MonoBehaviour
{
    [SerializeField] private float repeatDelay = 0.2f;     
    [SerializeField] private float repeatInterval = 0.1f;  
    private float repeatTimer;      

    public event Action&lt;Vector3&gt; MoveRequested; 
    public event Action Released;              
    //入力キーと方向を対応させる
    private static readonly (KeyCode key, Vector3 direction)[] bindings =
    {
        (KeyCode.UpArrow, Vector3.up),
        (KeyCode.DownArrow, Vector3.down),
        (KeyCode.RightArrow, Vector3.right),
        (KeyCode.LeftArrow, Vector3.left),
    };

    // 押された順に並べたbindingsの添字。最後が現在有効なキー
    private readonly List&lt;int&gt; heldKeys = new List&lt;int&gt;();
    private const int None = -1;  // キーを押していない
    private int CurrentIndex =&gt; heldKeys.Count &gt; 0 ? heldKeys[heldKeys.Count - 1] : None;

    private void Update()
    {
        int previousIndex = CurrentIndex;

        heldKeys.RemoveAll(i =&gt; !Input.GetKey(bindings[i].key));
        for (int i = 0; i &lt; bindings.Length; i++)
        {
            if (Input.GetKeyDown(bindings[i].key) && !heldKeys.Contains(i))
                heldKeys.Add(i);
        }
        
        int currentIndex = CurrentIndex;
        if (currentIndex == None)
        {
            if (previousIndex != None) Released?.Invoke();
            return;
        }

        if (currentIndex != previousIndex)
        {
            repeatTimer = repeatDelay;
            MoveRequested?.Invoke(bindings[currentIndex].direction);
            return;
        }

        repeatTimer -= Time.deltaTime;
        if (repeatTimer &lt;= 0f)
        {
            repeatTimer = repeatInterval;
            MoveRequested?.Invoke(bindings[currentIndex].direction);
        }
    }
}</code></pre></div>



<h4 class="wp-block-heading">GridMover.cs</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using System;
using UnityEngine;

/// &lt;summary&gt;
/// 対象を1マスずつ動かす。向き・移動の通知を行う
/// &lt;/summary&gt;
public class GridMover : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private float gridSize = 1f;

    public event Action&lt;Vector3&gt; MoveAttempted;
    public event Action&lt;Vector3, Vector3&gt; Moved;

    //移動処理
    public bool TryMove(Vector3 direction)
    {
        if (direction == Vector3.zero || target == null) return false;

        MoveAttempted?.Invoke(direction);

        Vector3 nextPos = target.position + direction * gridSize; //移動先

        target.position = nextPos;  //位置更新
        Moved?.Invoke(nextPos, direction);
        return true;
    }
}
</code></pre></div>



<h4 class="wp-block-heading">PlayerDirectionSprite.cs</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;

/// &lt;summary&gt;
/// プレイヤーの進行で画像差し替え
/// &lt;/summary&gt;
public class PlayerDirectionSprite : MonoBehaviour
{
    //止まっているときの状態
    private enum IdleFacingMode
    {
        IdleSprite, //待機
        LastDirection //最後に向いている方向
    }

    [SerializeField] private SpriteRenderer playerRenderer;
    [SerializeField] private Sprite spriteIdle;
    [SerializeField] private Sprite spriteUp;
    [SerializeField] private Sprite spriteDown;
    [SerializeField] private Sprite spriteLeft;
    [SerializeField] private Sprite spriteRight;
    [SerializeField] private IdleFacingMode idleFacingMode = IdleFacingMode.LastDirection;

    private Vector3 lastFacingDirection = Vector3.zero;

    //指定した方向を向く
    public void Face(Vector3 direction)
    {
        if (direction == Vector3.zero) return; 

        lastFacingDirection = direction;
        SetSprite(GetSpriteForDirection(direction));
    }

    //止まったときの画像にする
    public void ShowIdle()
    {
        bool hasLastFacing = lastFacingDirection != Vector3.zero;
        bool keepLastFacing = idleFacingMode == IdleFacingMode.LastDirection && hasLastFacing;

        SetSprite(keepLastFacing ? GetSpriteForDirection(lastFacingDirection) : null);
    }

    //画像の差し替え
    private void SetSprite(Sprite sprite)
    {
        if (playerRenderer == null) return;

        Sprite nextSprite = sprite ?? spriteIdle;
        if (nextSprite != null && playerRenderer.sprite != nextSprite)
            playerRenderer.sprite = nextSprite;
    }

    //対応する画像を呼び出す
    private Sprite GetSpriteForDirection(Vector3 direction)
    {
        if (direction == Vector3.up && spriteUp != null) return spriteUp;
        if (direction == Vector3.down && spriteDown != null) return spriteDown;
        if (direction == Vector3.left && spriteLeft != null) return spriteLeft;
        if (direction == Vector3.right && spriteRight != null) return spriteRight;
    
        return null;
    }
}
</code></pre></div>



<h4 class="wp-block-heading">PlayerSpawner.cs</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;

/// &lt;summary&gt;
/// シーン開始時のプレイヤーの位置と向きを決める
/// &lt;/summary&gt;
public class PlayerSpawner : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 startPosition = new Vector3(0.5f, 0.5f, 0f); 

    // 開始位置に置き、開始時の向きを startFacing に入れる
    public void Spawn(out Vector3 startFacing)
    {
        if (target == null) target = transform;

        target.position = startPosition;
        startFacing = Vector3.zero;
    }
}
</code></pre></div>



<p class="wp-block-paragraph"></p>



<h4 class="wp-block-heading">PlayerController.cs</h4>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;

/// &lt;summary&gt;
/// プレイヤーの各コンポーネントの接続
/// 入力 → 移動 → 向きの画像
/// &lt;/summary&gt;
public class PlayerController : MonoBehaviour
{
    [SerializeField] private PlayerInput input;
    [SerializeField] private GridMover mover;
    [SerializeField] private PlayerSpawner spawner;
    [SerializeField] private PlayerDirectionSprite directionSprite; // 向きスプライト制御

    // イベントの登録/解除
    private void OnEnable()
    {
        if (input != null)
        {
            input.MoveRequested += OnMoveRequested; 
            input.Released += OnReleased;
        }
        if (mover != null)
        {
            mover.MoveAttempted += OnMoveAttempted;
        }
    }

    private void OnDisable()
    {
        if (input != null)
        {
            input.MoveRequested -= OnMoveRequested;
            input.Released -= OnReleased;
        }
        if (mover != null)
        {
            mover.MoveAttempted -= OnMoveAttempted;
        }
    }

    private void Start()
    {
        Vector3 startFacing = Vector3.zero;
        if (spawner != null) spawner.Spawn(out startFacing);

        if (directionSprite == null) return;
        if (startFacing == Vector3.zero) directionSprite.ShowIdle();
        else directionSprite.Face(startFacing);
    }

    ///イベントを受け取る
    private void OnMoveRequested(Vector3 direction) =&gt; mover?.TryMove(direction);
    private void OnReleased() =&gt; directionSprite?.ShowIdle();
    private void OnMoveAttempted(Vector3 direction) =&gt; directionSprite?.Face(direction);
}
</code></pre></div>



<h3 id="rtoc-7"  class="wp-block-heading">解説</h3>



<ul class="wp-block-list">
<li><strong>名前空間（モジュール）</strong></li>
</ul>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">名前空間</th><th class="has-text-align-left" data-align="left">使っている理由</th></tr></thead><tbody><tr><td>UnityEngine</td><td>MonoBehaviour・Input・Vector3・SpriteRendererなど、Unityの基本機能を使うため</td></tr><tr><td>System</td><td>イベントの型Actionを使うため（PlayerInput・GridMover）</td></tr><tr><td>System.Collections.Generic</td><td>押しているキーを順番に覚えるList&lt;int>を使うため（PlayerInput）</td></tr></tbody></table></div></figure>



<ul class="wp-block-list">
<li><strong><strong>イベント</strong></strong></li>
</ul>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">スクリプト</th><th class="has-text-align-left" data-align="left">イベント</th><th class="has-text-align-left" data-align="left">知らせる内容</th></tr></thead><tbody><tr><td>PlayerInput</td><td>MoveRequested</td><td>「この方向に動きたい」（方向）</td></tr><tr><td>PlayerInput</td><td>Released</td><td>「矢印キーがすべて離された」</td></tr><tr><td>GridMover</td><td>MoveAttempted</td><td>「この方向に動こうとした」（方向）</td></tr><tr><td>GridMover</td><td>Moved</td><td>「動いた」（移動後の位置・方向）</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">処理の流れをまとめると、次のようになります。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-plain"><code>[PlayerInput] 矢印キーを押す → MoveRequested
      ↓
[PlayerController] OnMoveRequested → GridMover.TryMove()
      ↓
[GridMover] MoveAttempted → 1マス移動 → Moved
      ↓
[PlayerController] OnMoveAttempted → PlayerDirectionSprite.Face()（画像を切り替え）

[PlayerInput] キーを離す → Released → PlayerDirectionSprite.ShowIdle()</code></pre></div>



<h4 class="wp-block-heading">キー長押しで連続移動する（repeatDelay・repeatInterval）</h4>



<p class="wp-block-paragraph">PlayerInputのUpdate()では、キーを押したときと、押し続けているときで処理を分けています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>// 有効なキーが変わった：即移動
if (currentIndex != previousIndex)
{
    repeatTimer = repeatDelay;
    MoveRequested?.Invoke(bindings[currentIndex].direction);
    return;
}

//同じキーを押し続ける
repeatTimer -= Time.deltaTime;
if (repeatTimer &lt;= 0f)
{
    repeatTimer = repeatInterval;
    MoveRequested?.Invoke(bindings[currentIndex].direction);
}
</code></pre></div>



<p class="wp-block-paragraph">キーを押した瞬間はすぐに1マス進み、タイマーにrepeatDelay（0.2秒）をセットします。そのまま押し続けると、タイマーが0になったところで次の1マスに進み、以降はrepeatInterval（0.1秒）ごとに進みます。</p>



<p class="wp-block-paragraph">パソコンでキーを押しっぱなしにしたとき、1文字目のあとに少し間が空いてから文字が連続で入力されるのと同じ仕組みです。最初に少し待つことで、<strong>「1マスだけ動きたかったのに2マス進んでしまった」という誤操作を防げます。</strong></p>



<p class="wp-block-paragraph">Time.deltaTime（前のフレームからの経過時間）を引いているため、フレームレートが変わっても同じ速さで進みます。<strong>歩く速さを変えたいときは、InspectorでRepeat Intervalの値を調整</strong>してください。値を小さくするほど速く歩きます。</p>



<h4 class="wp-block-heading">キー同時押しの優先度</h4>



<p class="wp-block-paragraph">2つの矢印キーを同時に押したときにどちらへ進むかは、heldKeysリストで決めています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>//リスト更新（離されたキーを外し、新しく押されたキーを末尾に追加）
heldKeys.RemoveAll(i =&gt; !Input.GetKey(bindings[i].key));
for (int i = 0; i &lt; bindings.Length; i++)
{
    if (Input.GetKeyDown(bindings[i].key) && !heldKeys.Contains(i))
        heldKeys.Add(i);
}</code></pre></div>



<ol class="wp-block-list">
<li>RemoveAllで、もう押されていないキーをリストから外す</li>



<li>GetKeyDownで新しく押されたキーを見つけ、リストの<strong>末尾</strong>に追加する</li>
</ol>



<p class="wp-block-paragraph">リストの末尾が「一番最後に押したキー」になり、CurrentIndexはこの末尾のキーを返します。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>private int CurrentIndex =&gt; heldKeys.Count &gt; 0 ? heldKeys[heldKeys.Count - 1] : None;</code></pre></div>



<p class="wp-block-paragraph">例えば、→キーを押しながら↑キーを押すと↑方向へ進みます。そのあと↑キーだけを離すと、リストには→キーが残っているため、すぐに→方向へ戻って進みます。</p>



<p class="wp-block-paragraph">入力をif文で上から順に調べる書き方だと、「↑と→を同時に押すと、いつも↑が勝つ」「同時に押すと斜めに進む」といった動きになりがちです。押した順番を覚えておくことで、プレイヤーの操作に素直に反応する移動になります。</p>



<h2 id="rtoc-8"  class="wp-block-heading">実装</h2>



<p class="wp-block-paragraph">5つのスクリプトは、プレイヤーのオブジェクトと、画像を表示するオブジェクトにアタッチします。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="967" src="https://alicia-ing.com/wp-content/uploads/2026/09/attach-2.webp" alt="" class="wp-image-8990" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/attach-2.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-2-300x151.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-2-767x387.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-2-1024x517.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-2-1536x775.webp 1536w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">各スクリプトは[RequireComponent]を使っていないため、必要なコンポーネントは自動で追加されません。SpriteRendererの追加と、PlayerControllerの参照の設定は、忘れずに手動で行ってください。</p>



<h2 id="rtoc-9"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">本記事では、Tilemapの上で2Dプレイヤーを矢印キーで1マスずつ動かす方法として、次の内容を解説しました。</p>



<ul class="wp-block-list">
<li>Input.GetKeyとGetKeyDownを組み合わせて、押した瞬間の移動と長押しの連続移動を作る</li>



<li>押したキーの順番をListで覚えて、同時押しでは後から押したキーを優先する</li>



<li>SpriteRenderer.spriteを差し替えて、進む方向に合わせて画像を切り替える</li>
</ul>



<p class="wp-block-paragraph">スクリプトを役割ごとに分けているため、ここから機能を足していくのも簡単です。次のステップとして、GridMoverに壁の判定を加えて「壁の方を向くだけで進めない」動きにしたり、Movedイベントを使ってマップの端で別のシーンへ切り替えたりすると、<strong>RPGのマップ移動らしく</strong>なります。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/unity/2dplayermove/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Unity】Input Systemでゲームパッド対応</title>
		<link>https://alicia-ing.com/programming/unity/gamepad-player/</link>
					<comments>https://alicia-ing.com/programming/unity/gamepad-player/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sat, 26 Sep 2026 12:01:56 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=8956</guid>

					<description><![CDATA[Unityでプレイヤー操作実装する際に、キーボード操作だけでなく「ゲームパッド（コントローラー）にも対応したい」という場面が出てきます。 特にアクションゲームやプラットフォーマーでは、スティックでの移動とボタンでのジャン [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">Input SystemのInputActionReferenceを使って、ゲームパッドでキャラクターの移動とジャンプを操作できるようになります。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="960" height="540" src="https://alicia-ing.com/wp-content/uploads/2026/09/complete.webp" alt="" class="wp-image-8967" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/complete.webp 960w, https://alicia-ing.com/wp-content/uploads/2026/09/complete-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/complete-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/complete-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/complete-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/complete-640x360.webp 640w" sizes="(max-width: 960px) 100vw, 960px" /></figure>
</div></div>



<p class="wp-block-paragraph">Unityでプレイヤー操作実装する際に、キーボード操作だけでなく「ゲームパッド（コントローラー）にも対応したい」という場面が出てきます。</p>



<p class="wp-block-paragraph">特にアクションゲームやプラットフォーマーでは、スティックでの移動とボタンでのジャンプはほぼ必須の操作です。</p>



<p class="wp-block-paragraph">本記事では、Unityの新しいInput SystemとInputActionReferenceを使い、移動・ジャンプという最小構成でゲームパッド対応を実装する方法を分かりやすく解説します。</p>



<p class="wp-block-paragraph">Gamepad.currentを直接参照する方法やPlayerInputコンポーネントに依存する方法とは違う、疎結合で使い回しやすい設計です。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>マウス操作の導入を紹介しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/mouse-input/" title="【Unity】マウス操作をゲームに導入する画面クリックで処理" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2022/12/mouse-input-1-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2022/12/mouse-input-1-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2022/12/mouse-input-1-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2022/12/mouse-input-1-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】マウス操作をゲームに導入する画面クリックで処理</div><div class="blogcard-snippet internal-blogcard-snippet">UnityでCSVのテキストをマウスクリックで１つずつ進めるには、if文でメソッド「Input.GetMouseButtonDown(0)」を使用します。ノベルゲームは画面のクリック回数に応じてテキストを表示するシステムに近づきます。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2025.08.11</div></div></div></div></a>
</div>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Unityでゲームパッド（コントローラー）対応を実装したい人</li>



<li>新しいUnity Input Systemの基本的な使い方を知りたい人</li>



<li>InputActionReferenceを使った疎結合な入力処理の設計を学びたい人</li>



<li>Gamepad.currentを直接参照するコードから卒業したい人</li>



<li>ジャンプ入力の判定でバグが起きて困っている人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="8956" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">Input System</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">Input Systemパッケージの導入</a></li><li class="rtoc-item"><a href="#rtoc-3">Input Actionsアセットとゲームパッドのバインディング設定</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">InputActionReference</a></li><li class="rtoc-item"><a href="#rtoc-5">InputActionReferenceで実装する移動・ジャンプ入力</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">ソースコード</a></li><li class="rtoc-item"><a href="#rtoc-7">解説</a></li><li class="rtoc-item"><a href="#rtoc-8">InputActionReferenceでアクションを参照</a></li><li class="rtoc-item"><a href="#rtoc-9">移動入力をVector2で取得する</a></li><li class="rtoc-item"><a href="#rtoc-10">ジャンプ入力を1フレームだけ検知する（WasPressedThisFrame）</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-11">実演</a></li><li class="rtoc-item"><a href="#rtoc-12">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">Input System</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/Input-System.webp" alt="" class="wp-image-8960" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/Input-System.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-System-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">Input Systemは、キーボード・マウス・ゲームパッドなどの入力をまとめて扱える、Unityの新しい入力の仕組みです。</p>



<p class="wp-block-paragraph">XboxコントローラーやPlayStationコントローラー（DualSense）などのゲームパッドに対応したいときや、同じ操作をキーボードとゲームパッドの両方で行えるようにしたいときに使います。</p>



<p class="wp-block-paragraph">従来の<strong>Input.GetAxis()</strong>などを使う古い入力システム（Input Manager）でもある程度のゲームパッド対応は可能でしたが、デバイスごとの差異を吸収しづらく、<strong>コードが複雑になりがち</strong>でした。</p>



<p class="wp-block-paragraph">Input Systemでは、実際の入力デバイスと、ゲーム内の意味（「移動する」「ジャンプする」）を<strong>Input Actionという単位</strong>で分けられます。そのため、デバイスが変わってもコード側を変更せずに済みます。</p>



<p class="wp-block-paragraph">Input Systemを使うには、パッケージの導入と、入力を定義するInput Actionsアセットの作成が必要です。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://docs.unity3d.com/ja/current/Manual/com.unity.inputsystem.html">Input System &#8211; Unity マニュアル</a></p>



<h3 id="rtoc-2"  class="wp-block-heading">Input Systemパッケージの導入</h3>



<p class="wp-block-paragraph">Unity Input Systemは標準では有効になっていないため、<strong>Package Managerからインストール</strong>する必要があります。</p>



<ol class="wp-block-list">
<li>メニューから&nbsp;Window &gt; Package Manager&nbsp;を開きます</li>



<li>検索欄で「Input System」を検索し、Install&nbsp;をクリックします</li>



<li>インストール後、「新しい入力システムに切り替えますか？」という確認ダイアログが表示されるので&nbsp;Yes&nbsp;を選択します（Unityの再起動が必要です）</li>
</ol>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="980" src="https://alicia-ing.com/wp-content/uploads/2026/09/packageManager.webp" alt="" class="wp-image-8961" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/packageManager.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/09/packageManager-300x153.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/packageManager-767x392.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/09/packageManager-1024x523.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/packageManager-1536x785.webp 1536w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p class="wp-block-paragraph">Yesを選ぶと、Edit &gt; Project Settings &gt; Player &gt; Active Input Handling が Input System Package (New) に切り替わります。従来のInput Managerを使うコードが他にある場合は、動作しなくなる点に注意してください。両方を併用したい場合は Both を選択します。</p>
</div>



<h3 id="rtoc-3"  class="wp-block-heading">Input Actionsアセットとゲームパッドのバインディング設定</h3>



<p class="wp-block-paragraph">次に、実際の入力を定義するInput Actionsアセットを作成します。</p>



<ol class="wp-block-list">
<li>Projectウィンドウで右クリックし、Create &gt; Input Actions を選択してアセットを作成します</li>



<li>作成したアセットをダブルクリックしてエディタを開きます</li>



<li>Action Mapsに「Player」などの名前でマップを作成します</li>
</ol>



<p class="wp-block-paragraph">各アクションの+ボタンからバインディングを追加し、Listen機能を使って実際にゲームパッドを操作するとバインディング候補が自動検出されるので、それを選択すると設定が楽になります。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1360" height="562" src="https://alicia-ing.com/wp-content/uploads/2026/09/Input-Actions.webp" alt="" class="wp-image-8962" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/Input-Actions.webp 1360w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-Actions-300x124.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-Actions-767x317.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/09/Input-Actions-1024x423.webp 1024w" sizes="(max-width: 1360px) 100vw, 1360px" /></figure>



<p class="wp-block-paragraph">設定が終わったら忘れずにSave Assetをクリックしてください。</p>



<h2 id="rtoc-4"  class="wp-block-heading">InputActionReference</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference.webp" alt="" class="wp-image-8963" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/InputActionReference-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">InputActionReferenceは、Input Actionsアセットの中にある特定のInput Actionを、Inspectorから参照するための型です。スクリプトにアクション名を文字列で書かずに、「どのアクションを使うか」をInspectorで選べます。</p>



<figure class="wp-block-table is-style-regular"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>方法</th><th>特徴</th></tr></thead><tbody><tr><td>Gamepad.currentを直接参照</td><td>コード量は少ないが、デバイスの差し替えや複数デバイス対応がしづらい</td></tr><tr><td>PlayerInputコンポーネント</td><td>イベント駆動で扱いやすいが、コンポーネントへの依存度が高くなる</td></tr><tr><td>InputActionReference</td><td>Inspector上でアクションを差し替え可能。専用の入力ハンドラークラスに集約でき、疎結合な設計にしやすい</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">InputActionReferenceは、同じ入力ハンドラークラスを別のキャラクターやプレハブで使い回したり、テスト用に別のInput Actionsアセットへ差し替えたりしやすくなります。</p>



<p class="wp-block-paragraph">参考：<a target="_self" href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.14/api/UnityEngine.InputSystem.InputActionReference.html">InputActionReference &#8211; Input System ドキュメント</a></p>



<h2 id="rtoc-5"  class="wp-block-heading">InputActionReferenceで実装する移動・ジャンプ入力</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller.webp" alt="" class="wp-image-8964" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/gamepad_Controller-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">本記事では、InputActionReferenceを使って、ゲームパッドの移動（左スティック）とジャンプ（ボタン）を取得する入力ハンドラーを実装します。</p>



<p class="wp-block-paragraph">入力の取得だけを専用クラス（PlayerInputHandler）に切り出し、実際の移動処理・ジャンプ処理からはMoveInput・JumpWasPressedThisFrameという2つのプロパティを参照するだけで済むようにすることで、<strong>キーボードやマウスなど他の入力方式にも差し替えやすい設計</strong>を目指します。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>実装にはこちらのゲームパッドを使用しました。手ごろで普段のゲームでも使うことができます。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/life/best-buy/f310r/" title="F310r-Windows11で使えるゲームパッド実機レビュー" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2023/08/logi-f310r-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2023/08/logi-f310r-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2023/08/logi-f310r-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2023/08/logi-f310r-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">F310r-Windows11で使えるゲームパッド実機レビュー</div><div class="blogcard-snippet internal-blogcard-snippet">Logicool F310ゲームパッドの実機レビュー。Windows 11対応の設定方法やドライバ情報を解説。スペックや操作手順を紹介。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2025.03.28</div></div></div></div></a>
</div>


		<div class="pochipp-box"
			data-id="8031"
			data-img="l"
			data-lyt-pc="dflt"
			data-lyt-mb="vrtcl"
			data-btn-style="dflt"
			data-btn-radius="off"
			data-sale-effect="flash"
			 data-cvkey="ffbaab34"		>
							<div class="pochipp-box__image">
					<a rel="nofollow" target="_self" href="https://www.amazon.co.jp/dp/B00CDG799E?tag=b0420e-22">
						<img loading="lazy" decoding="async" src="https://m.media-amazon.com/images/I/519-CQdsB+L._SL400_.jpg" alt="" width="120" height="120" />					</a>
				</div>
						<div class="pochipp-box__body">
				<div class="pochipp-box__title">
					<a rel="nofollow" target="_self" href="https://www.amazon.co.jp/dp/B00CDG799E?tag=b0420e-22">
						Logicool G ゲームパッド F310r ゲーム コントローラー 有線 USB 接続 FF14 steam PC PS5 PS4 windows F310 国内正規品 【 ファイナルファンタジー XIV 推奨モデル 】					</a>
				</div>

				
									<div class="pochipp-box__price">
						¥2,527						<span>（2026/09/26 19:01時点 | Amazon調べ）</span>
					</div>
				
							</div>
				<div class="pochipp-box__btns"
		data-maxclmn-pc="fit"
		data-maxclmn-mb="1"
	>
					<div class="pochipp-box__btnwrap -amazon">
								<a rel="nofollow" target="_self" href="https://www.amazon.co.jp/dp/B00CDG799E?tag=b0420e-22" class="pochipp-box__btn">
					<span>
						Amazon					</span>
									</a>
			</div>
							<div class="pochipp-box__btnwrap -rakuten">
								<a rel="nofollow" target="_self" href="https://af.moshimo.com/af/c/click?a_id=4016319&#038;p_id=54&#038;pc_id=54&#038;pl_id=616&#038;url=https%3A%2F%2Fsearch.rakuten.co.jp%2Fsearch%2Fmall%2FF310r" class="pochipp-box__btn">
					<span>
						楽天市場					</span>
					<img loading="lazy" decoding="async" src="https://i.moshimo.com/af/i/impression?a_id=4016319&#038;p_id=54&#038;pc_id=54&#038;pl_id=616" width="1" height="1" style="border:none;">				</a>
			</div>
										<div class="pochipp-box__btnwrap -yahoo -long-text">
								<a rel="nofollow" target="_self" href="https://shopping.yahoo.co.jp/search?p=F310r" class="pochipp-box__btn">
					<span>
						Yahooショッピング					</span>
									</a>
			</div>
									</div>
								<div class="pochipp-box__logo">
					<img loading="lazy" decoding="async" src="https://alicia-ing.com/wp-content/plugins/pochipp/assets/img/pochipp-logo-t1.png" alt="" width="32" height="32">
					<span>ポチップ</span>
				</div>
					</div>
	


<h3 id="rtoc-6"  class="wp-block-heading">ソースコード</h3>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerInputHandler : MonoBehaviour
{
    [SerializeField] private InputActionReference moveAction;
    [SerializeField] private InputActionReference jumpAction;

    public Vector2 MoveInput { get; private set; }
    public bool JumpWasPressedThisFrame { get; private set; }

    private void OnEnable()
    {
        moveAction.action.Enable();
        jumpAction.action.Enable();
    }

    private void OnDisable()
    {
        moveAction.action.Disable();
        jumpAction.action.Disable();
    }

    private void Update()
    {
        MoveInput = moveAction.action.ReadValue&lt;Vector2&gt;();
        JumpWasPressedThisFrame = jumpAction.action.WasPressedThisFrame();
    }
}
</code></pre></div>



<h3 id="rtoc-7"  class="wp-block-heading">解説</h3>



<ul class="wp-block-list">
<li><strong>名前空間</strong></li>
</ul>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><tbody><tr><td>UnityEngine</td><td>MonoBehaviour・Vector2など、Unityの基本機能を使うため</td></tr><tr><td>UnityEngine.InputSystem</td><td>InputActionReferenceなど、Input Systemの機能を使うため（パッケージの導入が必要）</td></tr></tbody></table></div></figure>



<ul class="wp-block-list">
<li><strong><strong>フィールド・プロパティ</strong></strong></li>
</ul>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><tbody><tr><td>moveAction</td><td>移動に使うMoveアクションの参照。Inspectorで設定する</td></tr><tr><td>jumpAction</td><td>ジャンプに使うJumpアクションの参照。Inspectorで設定する</td></tr><tr><td>MoveInput</td><td>スティックの傾き（-1〜1のVector2）。ほかのスクリプトから読み取る</td></tr><tr><td>JumpWasPressedThisFrame</td><td>このフレームでジャンプボタンが押されたか。ほかのスクリプトから読み取る</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">MoveInput・JumpWasPressedThisFrameは{ get; private set; }としているため、ほかのスクリプトからは読み取りだけができ、書き換えはできません。</p>



<h3 id="rtoc-8"  class="wp-block-heading">InputActionReferenceでアクションを参照</h3>



<p class="wp-block-paragraph">InputActionReferenceは、Input Actionsアセット内の特定のInput ActionをInspector上から参照できる型です。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>[SerializeField] private InputActionReference moveAction;
[SerializeField] private InputActionReference jumpAction;</code></pre></div>



<p class="wp-block-paragraph">コード内に直接アクション名を文字列で書く必要がなく、Inspectorから見た目でどのアクションを使っているか確認できます。</p>



<p class="wp-block-paragraph">Gamepad.currentを直接参照する実装と違って、キーボード用のInput ActionsアセットとゲームパッドMap用のアセットを差し替えるだけで入力元を切り替えられます。</p>



<p class="wp-block-paragraph">同じPlayerInputHandlerを別のプレハブで使い回す際にも、参照先のアクションを変えるだけで対応できる点が疎結合設計のメリットです。</p>



<h3 id="rtoc-9"  class="wp-block-heading">移動入力をVector2で取得する</h3>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>MoveInput = moveAction.action.ReadValue&lt;Vector2&gt;();</code></pre></div>



<p class="wp-block-paragraph">MoveアクションはAction TypeをValue（Vector2）に設定しているため、ReadValue()でスティックの<strong>傾き具合をVector2として取得</strong>できます。x軸が左右、y軸が上下の入力値（-1〜1の範囲）です。</p>



<p class="wp-block-paragraph">取得したMoveInputは、別のスクリプト（プレイヤーの移動処理を担うクラスなど）からplayerInputHandler.MoveInputとして参照し、transform.TranslateやRigidbody.linearVelocityなどに渡してキャラクターを動かすといった使い方をします。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>Rigidbodyで移動させる処理はこちらで解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/3dplayermove/" title="【Unity】Rigidbody+CapsuleColliderを用いたプレイヤー移動" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="320" height="180" src="https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-320x180.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/07/3dplayermove-640x360.webp 640w" sizes="(max-width: 320px) 100vw, 320px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】Rigidbody+CapsuleColliderを用いたプレイヤー移動</div><div class="blogcard-snippet internal-blogcard-snippet">UnityでCharacterControllerからRigidbody+CapsuleColliderへ移動処理を移行する方法を解説。カメラ相対移動、FixedUpdateでの速度制御、移行の判断基準まで紹介します。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.07.13</div></div></div></div></a>
</div>



<h3 id="rtoc-10"  class="wp-block-heading">ジャンプ入力を1フレームだけ検知する（WasPressedThisFrame）</h3>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>JumpWasPressedThisFrame = jumpAction.action.WasPressedThisFrame();</code></pre></div>



<p class="wp-block-paragraph">WasPressedThisFrame()は、そのボタンが「このフレームで押された瞬間」かどうかを返すメソッドです。</p>



<p class="wp-block-paragraph">ジャンプのように「押した瞬間に1回だけ処理を実行したい」動作には、このWasPressedThisFrame()を使うのが適切です。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>キャラクターのジャンプ処理はこちらで解説しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/player-jump/" title="【Unity】キャラクターをジャンプさせる自然な動きを実装" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="320" height="180" src="https://alicia-ing.com/wp-content/uploads/2026/09/player-jump-320x180.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/player-jump-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/player-jump-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/player-jump-640x360.webp 640w" sizes="(max-width: 320px) 100vw, 320px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】キャラクターをジャンプさせる自然な動きを実装</div><div class="blogcard-snippet internal-blogcard-snippet">本記事を読むと以下の実行ができますRigidbodyを使ったキャラクターのジャンプ実装と、SphereCastによる正確...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.09.20</div></div></div></div></a>
</div>



<h4 class="wp-block-heading">isPressedにしない理由</h4>



<p class="wp-block-paragraph">InputActionにはisPressed（現在押されているかどうか）とWasPressedThisFrame()（このフレームで押された瞬間かどうか）という似た名前のプロパティ・メソッドがあります。</p>



<p class="wp-block-paragraph">ジャンプ処理をisPressedで判定してしまうと、<strong>ボタンを押し続けている間、毎フレーム「押されている」と判定</strong>され続けてしまいます。その結果、Update()が呼ばれるたびにジャンプ処理が実行され、意図せず連続ジャンプしてしまうバグにつながります。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>// 悪い例：ボタンを押している間ずっとtrueになる
if (jumpAction.action.IsPressed())
{
    Jump(); // 毎フレーム呼ばれてしまう
}

// 良い例：押した瞬間の1フレームだけtrueになる
if (jumpAction.action.WasPressedThisFrame())
{
    Jump(); // 1回だけ呼ばれる
}</code></pre></div>



<p class="wp-block-paragraph">ジャンプやポーズメニューの開閉など、「1回の入力につき1回だけ処理したい」動作では、必ずWasPressedThisFrame()を使うようにしましょう。</p>



<p class="wp-block-paragraph">逆に、ダッシュのように「押している間だけ継続する」動作にはisPressedが向いています。用途によって使い分けることが重要です。</p>



<h2 id="rtoc-11"  class="wp-block-heading">実演</h2>



<p class="wp-block-paragraph">空のGameObject、またはプレイヤーオブジェクトにアタッチします。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1917" height="966" src="https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1.webp" alt="" class="wp-image-8965" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1.webp 1917w, https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1-300x151.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1-768x387.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1-1024x516.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/Attach-1-1536x774.webp 1536w" sizes="(max-width: 1917px) 100vw, 1917px" /></figure>



<p class="wp-block-paragraph">設定が完了すると、他のスクリプトからplayerInputHandler.MoveInput・playerInputHandler.JumpWasPressedThisFrameを参照するだけで、ゲームパッドからの入力を取得できるようになります。</p>



<h2 id="rtoc-12"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">本記事では、Unity Input SystemのInputActionReferenceを使い、ゲームパッドでの移動とジャンプを実装する方法を解説しました。</p>



<ul class="wp-block-list">
<li>InputActionReferenceをInspectorで参照することで、入力元を差し替えやすい疎結合な設計にできる</li>



<li>移動はReadValue()、ジャンプはWasPressedThisFrame()で取得する</li>



<li>ジャンプなど「1回だけ処理したい」入力はisPressedではなくWasPressedThisFrame()を使う</li>
</ul>



<p class="wp-block-paragraph">今回紹介したPlayerInputHandlerは移動とジャンプの最小構成ですが、同じ考え方でダッシュ・攻撃・ポーズメニューの開閉など他のアクションにも応用できます。</p>



<p class="wp-block-paragraph">まずはこの最小構成で「ゲームパッドで動くもの」を作り、そこから少しずつアクションを増やしていくと理解が深まるのでおすすめです。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/unity/gamepad-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Unity】キャラクターをジャンプさせる自然な動きを実装</title>
		<link>https://alicia-ing.com/programming/unity/player-jump/</link>
					<comments>https://alicia-ing.com/programming/unity/player-jump/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 06:10:09 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=8940</guid>

					<description><![CDATA[Unityでキャラクターをジャンプさせることはゲーム開発での基礎ですが、「ボタンを押しても反応しない」「空中で何度もジャンプできてしまう」といった問題によくぶつかります。 これは多くの場合、接地判定（キャラクターが地面に [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">Rigidbodyを使ったキャラクターのジャンプ実装と、SphereCastによる正確な接地判定を組み合わせて、二段ジャンプや無限ジャンプを起こさない自然なジャンプ動作</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1280" height="720" src="https://alicia-ing.com/wp-content/uploads/2026/09/complete.gif" alt="" class="wp-image-8946"/></figure>
</div></div>



<p class="wp-block-paragraph">Unityでキャラクターをジャンプさせることはゲーム開発での基礎ですが、「ボタンを押しても反応しない」「空中で何度もジャンプできてしまう」といった問題によくぶつかります。</p>



<p class="wp-block-paragraph">これは多くの場合、接地判定（キャラクターが地面に着いているかどうかの判定）が不十分なことが原因です。</p>



<p class="wp-block-paragraph">本記事では、RigidbodyとPhysics.SphereCastを使い、CapsuleColliderの実際のサイズに連動した接地判定と、目標到達高さから逆算したジャンプ初速の計算方法をあわせて解説します。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Unityでキャラクターのジャンプ処理を実装したい人</li>



<li>AddForceやvelocity直接指定でジャンプを作ったが、二段ジャンプ・無限ジャンプが直らず困っている人</li>



<li>タグ判定やisGroundedだけの簡易な接地判定から、より正確な判定方法にステップアップしたい人</li>



<li>ジャンプの高さを感覚ではなく数値（目標到達高さ）から計算して設定したい人</li>



<li>UpdateとFixedUpdateの役割分担を理解して、物理挙動が絡む処理を正しく設計したい人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="8940" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">ジャンプ実装に必要な要素（SphereCast）とは？</a></li><li class="rtoc-item"><a href="#rtoc-2">SphereCastを使ったジャンプ処理</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-3">ソースコード</a></li><li class="rtoc-item"><a href="#rtoc-4">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-5">実演</a></li><li class="rtoc-item"><a href="#rtoc-6">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">ジャンプ実装に必要な要素（SphereCast）とは？</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="940" src="https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump.webp" alt="" class="wp-image-8947" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-767x431.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/sphereCast_jump-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">Unityでジャンプ実装を検索すると、「unity rigidbody ジャンプ」「unity 接地判定」といったキーワードで多くの記事がヒットしますが、実装方式は大きく2つの系統に分かれます。</p>



<ol class="wp-block-list">
<li><strong>ジャンプそのものの実装</strong>（AddForceやvelocityでY方向に力・速度を加える）</li>



<li><strong>接地判定の実装</strong>（タグ判定・Raycast・CheckSphere・SphereCast・isGroundedなど）</li>
</ol>



<p class="wp-block-paragraph">この2つは別々に解説されがちですが、実際に「ちゃんと動くジャンプ」を作るには両方を組み合わせる必要があります。</p>



<p class="wp-block-paragraph">特に接地判定が甘いと、空中にいるのにジャンプ入力を受け付けてしまい、二段ジャンプ・無限ジャンプというよくあるバグにつながります。</p>



<p class="wp-block-paragraph">本記事で採用するPhysics.SphereCastは、球（Sphere）を指定方向に飛ばして衝突を検出するUnityの物理判定です。</p>



<p class="wp-block-paragraph">単純なRaycast（線を1本飛ばすだけ）と違い、球という「太さ」を持つ判定なので、地面のわずかな段差や傾斜でも安定して接地を検出できます。</p>



<h2 id="rtoc-2"  class="wp-block-heading">SphereCastを使ったジャンプ処理</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1672" height="940" src="https://alicia-ing.com/wp-content/uploads/2026/09/jump.webp" alt="" class="wp-image-8948" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/jump.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-767x431.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/09/jump-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">今回のジャンプ実装は、以下の4ステップで構成されています。</p>



<div class="wp-block-cocoon-blocks-timeline timeline-box cf block-box not-nested-style cocoon-block-timeline"><div class="timeline-title">ジャンプ処理の実装</div><ul class="timeline">
<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step1</div><div class="timeline-item-content cf"><div class="timeline-item-title">入力で、ジャンプボタンが押されたかどうかをUpdateで検知する</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step2</div><div class="timeline-item-content cf"><div class="timeline-item-title">SphereCastを使い接地判定を行う</div><div class="timeline-item-snippet"></div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step3</div><div class="timeline-item-content cf"><div class="timeline-item-title">地面以外のコライダーやトリガーを誤検知しないようにする<br></div><div class="timeline-item-snippet"></div></div></li>
</ul></div>



<h3 id="rtoc-3"  class="wp-block-heading">ソースコード</h3>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;

[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider), typeof(PlayerInputHandler))]
public class PlayerJump : MonoBehaviour
{
    [SerializeField] private float jumpHeight = 1.5f;
    [SerializeField] private LayerMask groundMask = ~0;

    private Rigidbody rb;
    private CapsuleCollider capsule;
    private PlayerInputHandler input;
    private bool jumpRequested;

    private void Awake()
    {
        rb = GetComponent&lt;Rigidbody&gt;();
        capsule = GetComponent&lt;CapsuleCollider&gt;();
        input = GetComponent&lt;PlayerInputHandler&gt;();
    }

    private void Update()
    {
        if (input.JumpWasPressedThisFrame)
        {
            jumpRequested = true;
        }
    }

    private void FixedUpdate()
    {
        Vector3 velocity = rb.linearVelocity;

        if (jumpRequested && IsGrounded())
        {
            velocity.y = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);
        }
        jumpRequested = false;

        rb.linearVelocity = velocity;
    }

    private bool IsGrounded()
    {
        Vector3 origin = transform.TransformPoint(capsule.center);
        float castRadius = capsule.radius * 0.9f;
        float castDistance = capsule.height * 0.5f + 0.15f;
        return Physics.SphereCast(origin, castRadius, Vector3.down, out _, castDistance, groundMask, QueryTriggerInteraction.Ignore);
    }
}
</code></pre></div>



<h3 id="rtoc-4"  class="wp-block-heading">解説</h3>



<ul class="wp-block-list">
<li><strong>入力の検知とFixedUpdateでの物理処理を分離する設計</strong></li>
</ul>



<p class="wp-block-paragraph">ジャンプボタンの入力検知をUpdate、実際の速度変更をFixedUpdateに分けています。</p>



<ul class="wp-block-list">
<li>Updateはフレームごとに呼ばれる（フレームレートに依存する）ため、入力の取りこぼしを防ぐのに向いています。</li>



<li>FixedUpdateは一定の時間間隔で呼ばれる（物理エンジンの更新タイミングに同期する）ため、Rigidbodyを操作する処理はここで行うのが基本です。</li>
</ul>



<p class="wp-block-paragraph">Update内で直接Rigidbodyの速度を書き換えてしまうと、フレームレートによって物理挙動が不安定になることがあります。そこで、UpdateではjumpRequestedというフラグを立てるだけにとどめ、実際の速度変更はFixedUpdateでまとめて行うように設計しています。</p>



<p class="wp-block-paragraph">FixedUpdateの先頭でjumpRequestedを判定した後、必ずfalseにリセットしている点もポイントです。これにより、1回のボタン入力に対してジャンプが1回だけ発生するようになります。</p>



<ul class="wp-block-list">
<li><strong>SphereCastでCapsuleColliderに連動した接地判定を行う</strong></li>
</ul>



<p class="wp-block-paragraph">IsGrounded()メソッドが接地判定の中心部分です。単純に固定の半径・距離を指定するのではなく、CapsuleColliderのプロパティから動的に計算しています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>Vector3 origin = transform.TransformPoint(capsule.center);
float castRadius = capsule.radius * 0.9f;
float castDistance = capsule.height * 0.5f + 0.15f;</code></pre></div>



<ul class="wp-block-list">
<li>origin：capsule.center（ローカル座標）をTransformPointでワールド座標に変換し、キャラクターの中心からキャストを開始します。</li>



<li>castRadius：コライダーの半径そのままだと壁際でわずかに引っかかることがあるため、0.9fを掛けて少し小さめに設定しています。</li>



<li>castDistance：カプセルの半分の高さ（中心から底面まで）に、地面との間に余裕を持たせる0.15fを加えています。</li>
</ul>



<p class="wp-block-paragraph">この値をキャラクターのサイズ（CapsuleCollider）から計算する設計にしておくことで、キャラクターモデルを差し替えてCapsuleColliderのサイズが変わっても、接地判定のコードを書き直す必要がありません。</p>



<ul class="wp-block-list">
<li><strong>ジャンプの初速を目標到達高さから逆算する</strong></li>
</ul>



<p class="wp-block-paragraph">ジャンプの高さを「なんとなくの数値」で調整すると、意図した高さにならず何度も微調整することになります。本実装では、到達させたい高さ（jumpHeight）から必要な初速を逆算しています。</p>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>velocity.y = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);</code></pre></div>



<p class="wp-block-paragraph">これは物理の等加速度運動の公式 v0 = √(2gH) をそのままコードにしたものです。Physics.gravity.yは通常マイナスの値（デフォルトは-9.81）なので、符号を合わせるために-2fを掛けています。</p>



<ul class="wp-block-list">
<li>H：到達させたい高さ（jumpHeight、インスペクターで調整可能）</li>



<li>g：重力加速度（Physics.gravity.yの絶対値）</li>



<li>v0：地面を離れる瞬間に必要な初速（Y方向の速度）</li>
</ul>



<p class="wp-block-paragraph">インスペクター上でjumpHeightの値を1.5から2.0に変えるだけで、「ちょうど2m飛び上がるジャンプ」を数値ベースで作れるようになります。感覚での調整に頼らず、狙った高さを確実に再現できるのがこの計算式の利点です。</p>



<ul class="wp-block-list">
<li>レイヤーマスクとQueryTriggerInteraction.Ignoreで誤検知を防ぐ</li>
</ul>



<p class="wp-block-paragraph">SphereCastはデフォルトのままだと、地面以外のコライダー（アイテムの当たり判定やエフェクト用のトリガーなど）にも反応してしまうことがあります。これを防ぐために2つの仕組みを併用しています。</p>



<ul class="wp-block-list">
<li>groundMask（レイヤーマスク）： SphereCastの判定対象を、あらかじめ設定した「地面」レイヤーのみに絞り込みます。プレイヤー自身や敵、アイテムなどのレイヤーを対象外にできます。</li>



<li>QueryTriggerInteraction.Ignore： IsTriggerが有効なコライダー（当たり判定はあるが物理的にすり抜けるコライダー）を接地判定の対象から除外します。これを指定しないと、トリガーに触れただけで「地面に着地した」と誤判定されることがあります。</li>
</ul>



<p class="wp-block-paragraph">この2つを組み合わせることで、地面用のコライダーだけを確実に検出できるようになります。レイヤーマスクの活用に触れていない競合記事も多いため、意図しない誤検知を防ぎたい場合は必ず設定しておきましょう。</p>



<h2 id="rtoc-5"  class="wp-block-heading">実演</h2>



<p class="wp-block-paragraph">Rigidbody・CapsuleCollider・PlayerInputHandlerを設定済みのプレイヤーオブジェクトにアタッチします（RequireComponentにより、この3つが未設定のオブジェクトにはアタッチできません）。</p>



<div class="wp-block-cocoon-blocks-timeline timeline-box cf block-box not-nested-style cocoon-block-timeline"><div class="timeline-title">アタッチの流れ</div><ul class="timeline">
<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP1</div><div class="timeline-item-content cf"><div class="timeline-item-title">プレイヤーオブジェクトにRigidbody・CapsuleColliderを追加</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP2</div><div class="timeline-item-content cf"><div class="timeline-item-title">同じオブジェクトにスクリプトをアタッチする</div><div class="timeline-item-snippet"></div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP3</div><div class="timeline-item-content cf"><div class="timeline-item-title">InspectorでJump Height（到達させたい高さ）を設定する</div><div class="timeline-item-snippet"></div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">Step 4</div><div class="timeline-item-content cf"><div class="timeline-item-title">InspectorでGround Maskに地面用のレイヤーを指定する</div><div class="timeline-item-snippet"></div></div></li>
</ul></div>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2560" height="1299" src="https://alicia-ing.com/wp-content/uploads/2026/09/attach-scaled.webp" alt="" class="wp-image-8944" srcset="https://alicia-ing.com/wp-content/uploads/2026/09/attach-scaled.webp 2560w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-300x152.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-766x389.webp 766w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-1024x520.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-1536x780.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/09/attach-2048x1039.webp 2048w" sizes="(max-width: 2560px) 100vw, 2560px" /></figure>



<h2 id="rtoc-6"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">本記事では、Rigidbodyを使ったUnityのジャンプ実装を、以下の4つのポイントに分けて解説しました。</p>



<ul class="wp-block-list">
<li>入力検知（Update）と物理処理（FixedUpdate）を分離する設計</li>



<li>CapsuleColliderの実サイズに連動したSphereCast接地判定</li>



<li>目標到達高さから初速を逆算するv0 = √(2gH)の計算式</li>



<li>レイヤーマスクとQueryTriggerInteraction.Ignoreによる誤検知防止</li>
</ul>



<p class="wp-block-paragraph"><br>これらを組み合わせることで、二段ジャンプ・無限ジャンプといった典型的なバグを防ぎつつ、狙った高さに正確に跳ぶジャンプ処理を作れます。</p>



<p class="wp-block-paragraph">まずは本記事のコードをそのまま自分のプロジェクトに組み込み、jumpHeightやgroundMaskの値を自分のキャラクターに合わせて調整するところから始めてみてください。</p>



<p class="wp-block-paragraph">接地判定の考え方は壁ジャンプや二段ジャンプなど、他のアクションを追加する際の土台にもなります。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/unity/player-jump/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【Unity】三人称視点を滑らかに追従処理｜Lerp（位置）とSlerp（回転）</title>
		<link>https://alicia-ing.com/programming/unity/third-person-view-player/</link>
					<comments>https://alicia-ing.com/programming/unity/third-person-view-player/#respond</comments>
		
		<dc:creator><![CDATA[アリッシア]]></dc:creator>
		<pubDate>Thu, 16 Jul 2026 09:46:20 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<guid isPermaLink="false">https://alicia-ing.com/?p=8909</guid>

					<description><![CDATA[3人称視点（TPS/TPP）のゲームを作っていると、カメラをプレイヤーにそのまま追従させただけではカクついたり、キャラクターの動きに機械的に張り付いてしまったりして、なめらかさに欠ける映像になってしまうことがあります。  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box not-nested-style cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-flag"><span class="label-box-label-text block-box-label-text box-label-text">本記事を読むと以下の実行ができます</span></div><div class="label-box-content block-box-content box-content">
<p class="wp-block-paragraph">プレイヤーの後ろから滑らかに追従する、カクつきのない三人称カメラの実装</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1280" height="720" src="https://alicia-ing.com/wp-content/uploads/2026/07/complete-1.gif" alt="" class="wp-image-8927"/></figure>
</div></div>



<p class="wp-block-paragraph">3人称視点（TPS/TPP）のゲームを作っていると、カメラをプレイヤーにそのまま追従させただけではカクついたり、キャラクターの動きに機械的に張り付いてしまったりして、なめらかさに欠ける映像になってしまうことがあります。 </p>



<p class="wp-block-paragraph">本記事では、位置はVector3.Lerp、回転はQuaternion.Slerpという2つの異なる補間関数を組み合わせて、自然な三人称カメラの追従処理を実装する方法を解説します。</p>



<p class="wp-block-paragraph"> followSpeedやrotationSpeedなどのパラメータ調整方法や、カメラの動きが不自然になったときのチェックポイントまで、実際に手を動かしながら理解できる内容になっています。</p>



<div class="wp-block-cocoon-blocks-label-box-1 label-box block-box cocoon-block-label-box"><div class="label-box-label block-box-label box-label fab-check"><span class="label-box-label-text block-box-label-text box-label-text">本記事は次の人におすすめ</span></div><div class="label-box-content block-box-content box-content">
<ul class="wp-block-list">
<li>Unityで三人称視点（TPS/TPP）のカメラを実装したい人</li>



<li>Unityのカメラ追従を実装したがカクつく・不自然な動きになって困っている人</li>



<li>Vector3.LerpとQuaternion.Slerpの違いや使い分けを知りたい人</li>



<li>followSpeedやrotationSpeedなどのパラメータをどう調整すればいいか分からない人</li>
</ul>
</div></div>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame3 preset1 animation-none rtoc_open default" data-id="8909" data-theme="Cocoon Child">
			<div id="rtoc-mokuji-title" class=" rtoc_left">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>目次</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">三人称カメラの追従処理とは？</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">Vector3.LerpとQuaternion.Slerpで実装する三人称カメラ</a></li><li class="rtoc-item"><a href="#rtoc-3">ソースコード</a></li><li class="rtoc-item"><a href="#rtoc-4">解説</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-5">実装</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-6">パラメータ調整ガイド</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">三人称カメラの追従処理とは？</h2>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking.webp" alt="" class="wp-image-8930" srcset="https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/07/camera-tracking-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">三人称カメラ（TPS/TPPカメラ）とは、プレイヤーが操作するキャラクターの背後や斜め上から映すカメラのことです。アクションゲームやオープンワールドゲームなど、多くの3Dゲームで採用されている視点です。</p>



<p class="wp-block-paragraph">三人称カメラの追従処理を作るとき、意識すべきポイントは大きく分けて2つあります。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>要素</th><th>内容</th></tr></thead><tbody><tr><td>位置追従</td><td>カメラのキャラクターを一定距離後ろに保ちつづける処理</td></tr><tr><td>回転の追従</td><td>カメラ向きをcharacter（注視点）へ向け続ける処理</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">transform.positionをそのままキャラクターの位置に代入するだけでは、カメラが一瞬でキャラクターの真後ろにワープしたような、機械的で不自然な映像になってしまいます。</p>



<p class="wp-block-paragraph">そこで、位置と回転それぞれに<strong>補間（インターポレーション）</strong>をかけることで、カメラがワンテンポ遅れてついてくるような、滑らかな追従感を演出します。</p>



<p class="wp-block-paragraph">このとき重要なのが、位置の補間にはVector3.Lerp、回転の補間にはQuaternion.Slerpという異なる関数を使うという点です。</p>



<p class="wp-block-paragraph">位置は直線的な移動なので線形補間（Lerp）で十分ですが、回転は「角度」を扱うため、球面線形補間（Slerp）を使わないと不自然な回転になってしまいます。</p>



<p class="wp-block-paragraph">この違いについてはコードを解説するセクションで詳しく説明します。</p>



<h3 id="rtoc-2"  class="wp-block-heading">Vector3.LerpとQuaternion.Slerpで実装する三人称カメラ</h3>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="1672" height="941" src="https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp.webp" alt="" class="wp-image-8931" srcset="https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp.webp 1672w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-300x169.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-768x432.webp 768w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-1024x576.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-1536x864.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-240x135.webp 240w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-320x180.webp 320w, https://alicia-ing.com/wp-content/uploads/2026/07/lerp-slerp-640x360.webp 640w" sizes="(max-width: 1672px) 100vw, 1672px" /></figure>



<p class="wp-block-paragraph">三人称カメラの追従処理は、以下の3ステップで実装できます。</p>



<div class="wp-block-cocoon-blocks-timeline timeline-box cf block-box not-nested-style cocoon-block-timeline"><div class="timeline-title">実装までの流れ</div><ul class="timeline">
<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step1</div><div class="timeline-item-content cf"><div class="timeline-item-title">オブジェクトの配置</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph">カメラをキャラクターの子オブジェクトにせず、独立したオブジェクトとして配置する</p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step2</div><div class="timeline-item-content cf"><div class="timeline-item-title">カメラ追従</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph">LateUpdate内で、キャラクター位置＋オフセットへVector3.Lerpでカメラを追従させる</p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">step3</div><div class="timeline-item-content cf"><div class="timeline-item-title">回転によるカメラ向き合わせる</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph">LateUpdate内で、注視点をQuaternion.LookRotationで計算し、Quaternion.Slerpでカメラの向きを滑らかに合わせる</p>
</div></div></li>
</ul></div>



<p class="wp-block-paragraph">カメラをキャラクターの子オブジェクトにしてしまうと、キャラクターの回転がそのままカメラの回転に伝わってしまい、独立した滑らかな動きを付けられなくなります。</p>



<p class="wp-block-paragraph">そのため、カメラはシーン上の独立したオブジェクトとして配置し、スクリプトで位置・回転を毎フレーム計算する構成にします。</p>



<h3 id="rtoc-3"  class="wp-block-heading">ソースコード</h3>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>using UnityEngine;

public class CameraFollow : MonoBehaviour
{
    [SerializeField] private Transform target;
    [SerializeField] private Vector3 offset = new Vector3(0f, 3.2f, -7f);
    [SerializeField] private float followSpeed = 5f;
    [SerializeField] private float rotationSpeed = 5f;
    [SerializeField] private float lookHeight = 1.5f;

    private void LateUpdate()
    {
        if (target == null)
        {
            return;
        }
        Vector3 desiredPosition = target.position + offset;
        transform.position = Vector3.Lerp(transform.position, desiredPosition, followSpeed * Time.deltaTime);

        Vector3 lookPoint = target.position + Vector3.up * lookHeight;
        Quaternion desiredRotation = Quaternion.LookRotation(lookPoint - transform.position);
        transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, rotationSpeed * Time.deltaTime);
    }
}</code></pre></div>



<h3 id="rtoc-4"  class="wp-block-heading">解説</h3>



<ul class="wp-block-list">
<li><strong>位置の追従処理（Vector3.Lerp）</strong></li>
</ul>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>Vector3 desiredPosition = target.position + offset;
transform.position = Vector3.Lerp(transform.position, desiredPosition, followSpeed * Time.deltaTime);</code></pre></div>



<p class="wp-block-paragraph">まずtarget.position + offsetで、「カメラが本来いるべき理想の位置（desiredPosition）」を計算します。</p>



<p class="wp-block-paragraph">offsetは(0, 3.2, -7)のように、キャラクターから見て「上に3.2、後ろに7」といった相対位置を指定するベクトルです。</p>



<p class="wp-block-paragraph">次にVector3.Lerp(現在位置, 理想位置, 割合)で、現在のカメラ位置から理想位置へ少しずつ近づけます。第3引数のfollowSpeed * Time.deltaTimeが「1フレームでどれだけ理想位置に近づくか」の割合です。</p>



<p class="wp-block-paragraph">Time.deltaTimeを掛けることで、フレームレートが変わっても同じ速度で追従するようになります。</p>



<p class="wp-block-paragraph">Vector3.Lerpは2点間を直線的に補間する関数なので、位置の移動には自然にマッチします。<br>これが、いきなりtransform.position = desiredPositionと代入せず、Lerpを使う理由です。</p>



<ul class="wp-block-list">
<li><strong>注視点の回転処理（Quaternion.Slerp）</strong></li>
</ul>



<div class="hcb_wrap"><pre class="prism undefined-numbers lang-csharp" data-lang="C#"><code>Vector3 lookPoint = target.position + Vector3.up * lookHeight;
Quaternion desiredRotation = Quaternion.LookRotation(lookPoint - transform.position);
transform.rotation = Quaternion.Slerp(transform.rotation, desiredRotation, rotationSpeed * Time.deltaTime);</code></pre></div>



<p class="wp-block-paragraph">回転の処理も基本的な流れは位置と同じで、「理想の回転（desiredRotation）」を計算してから、現在の回転を少しずつ近づけます。</p>



<p class="wp-block-paragraph">ただし、回転を補間する際はVector3.Lerpではなく<strong>Quaternion.Slerp（球面線形補間）</strong>を使う点に注意してください。</p>



<p class="wp-block-paragraph">回転は「角度」という性質上、直線的な補間（Lerp）をそのまま当てはめると、回転速度にムラが出たり、経由地点でおかしな姿勢を取ったりすることがあります。</p>



<p class="wp-block-paragraph">Quaternion.Slerpは球面上を一定の角速度で補間するため、回転が常に自然な速さで進みます。位置はLerp、回転はSlerpという使い分けは、Unityのカメラ制御において基本となる考え方です。</p>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>Vector3を使った補間・イージングの考え方を紹介しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/unity/easing/" title="【Unity】Vectorを使ったイージング、アニメーション動作" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2023/03/vector-easing-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2023/03/vector-easing-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2023/03/vector-easing-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2023/03/vector-easing-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】Vectorを使ったイージング、アニメーション動作</div><div class="blogcard-snippet internal-blogcard-snippet">Vector2を使ったスクリプトでアニメーションを作成します。RectTransformで座標を決めて、線形補間「Leap」を実行します。本記事では、マウスのポインターの位置でアニメーションを稼働させるソースコードを公開・解説しています。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2025.08.11</div></div></div></div></a>
</div>



<ul class="wp-block-list">
<li><strong>LookRotationで注視点を計算する仕組み</strong></li>
</ul>



<p class="wp-block-paragraph">Quaternion.LookRotation(方向ベクトル)は、指定した方向を向く回転（Quaternion）を返す関数です。</p>



<p class="wp-block-paragraph">ここでは、lookPoint &#8211; transform.position（カメラから注視点への方向ベクトル）を渡すことで、「カメラから見て、注視点の方向を向いた回転」を計算しています。</p>



<p class="wp-block-paragraph">lookPointはtarget.position + Vector3.up * lookHeight、つまりキャラクターの位置から少し上にずらした点です。</p>



<p class="wp-block-paragraph">キャラクターの足元をそのまま注視点にすると、カメラが常に下向きになり圧迫感のある画になってしまうため、lookHeightで見上げるような角度にならないよう調整しています。</p>



<ul class="wp-block-list">
<li><strong>LateUpdateを使う理由</strong></li>
</ul>



<p class="wp-block-paragraph">このスクリプトでは、UpdateではなくLateUpdateを使っています。これは三人称カメラの実装において非常に重要なポイントです。</p>



<p class="wp-block-paragraph">Unityでは、シーン内の複数のUpdateがどの順番で実行されるかは保証されていません。もしキャラクターの移動処理と、カメラの追従処理が両方ともUpdateに書かれていた場合、カメラのUpdateがキャラクターのUpdateより先に実行されてしまうことがあります。</p>



<p class="wp-block-paragraph">それにより、カメラは「1フレーム前のキャラクター位置」を基準に追従することになり、映像がカクつく原因になります。</p>



<p class="wp-block-paragraph">LateUpdateは、シーン内のすべてのUpdateが終わった後に必ず呼ばれることが保証されています。そのため、キャラクターの移動処理が確実に終わった後の最新位置を使ってカメラを更新でき、カクつきのない滑らかな追従が実現できます。</p>



<p class="wp-block-paragraph">カメラ制御では「移動処理はUpdate、カメラ追従はLateUpdate」という役割分担を徹底しましょう。</p>



<h2 id="rtoc-5"  class="wp-block-heading">実装</h2>



<p class="wp-block-paragraph">シーン上に独立して配置したカメラオブジェクトにアタッチします。</p>



<div class="wp-block-cocoon-blocks-timeline timeline-box cf block-box not-nested-style cocoon-block-timeline"><div class="timeline-title"></div><ul class="timeline">
<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP1</div><div class="timeline-item-content cf"><div class="timeline-item-title">カメラをキャラクターの子オブジェクトにせず、シーン上の独立したオブジェクトとして配置する</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP2</div><div class="timeline-item-content cf"><div class="timeline-item-title">カメラオブジェクトにスクリプトをアタッチする</div><div class="timeline-item-snippet">
<p class="wp-block-paragraph"></p>
</div></div></li>



<li class="wp-block-cocoon-blocks-timeline-item timeline-item cf"><div class="timeline-item-label">STEP3</div><div class="timeline-item-content cf"><div class="timeline-item-title">InspectorのTargetに、追従したいキャラクターのTransformをドラッグ＆ドロップで設定する</div><div class="timeline-item-snippet"></div></div></li>
</ul></div>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2560" height="1265" src="https://alicia-ing.com/wp-content/uploads/2026/07/attach-scaled.webp" alt="" class="wp-image-8942" srcset="https://alicia-ing.com/wp-content/uploads/2026/07/attach-scaled.webp 2560w, https://alicia-ing.com/wp-content/uploads/2026/07/attach-300x148.webp 300w, https://alicia-ing.com/wp-content/uploads/2026/07/attach-767x379.webp 767w, https://alicia-ing.com/wp-content/uploads/2026/07/attach-1024x506.webp 1024w, https://alicia-ing.com/wp-content/uploads/2026/07/attach-1536x759.webp 1536w, https://alicia-ing.com/wp-content/uploads/2026/07/attach-2048x1012.webp 2048w" sizes="(max-width: 2560px) 100vw, 2560px" /></figure>



<p class="wp-block-paragraph">offset・followSpeed・rotationSpeed・lookHeightはデフォルト値のままでも動作しますが、キャラクターのモデルサイズやゲームのジャンルに応じて調整が必要になることがほとんどです。</p>



<p class="wp-block-paragraph">値の目安は次の「パラメータ調整ガイド」を参照してください。</p>



<h3 id="rtoc-6"  class="wp-block-heading">パラメータ調整ガイド</h3>



<p class="wp-block-paragraph">スクリプトには4つの調整用パラメータがあります。それぞれの値を変えたときに、どのような体感の変化があるかをまとめました。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>パラメータ</th><th>役割</th><th>値を大きくすると</th><th>値を小さくすると</th></tr></thead><tbody><tr><td>followSpeed</td><td>位置の追従の速さ</td><td>キャラクターにピッタリ張り付く</td><td>ワンテンポ遅れてついてくる（重厚感）</td></tr><tr><td>rotationSpeed</td><td>回転の追従の速さ</td><td>向きの切り替えが機敏になる</td><td>ゆったりと視線が追いつく</td></tr><tr><td>lookHeight</td><td>注視点の高さ</td><td>見下ろすような画角になる</td><td>水平に近い、圧迫感のある画角になる</td></tr><tr><td>offset</td><td>カメラの距離・画角</td><td>引きの画（広い視界）</td><td>寄りの画（迫力のある視界）</td></tr></tbody></table></div></figure>



<div class="wp-block-cocoon-blocks-micro-text micro-text micro-copy micro-top"><span class="micro-text-content micro-content"><span class="micro-text-icon micro-icon fab-info-circle"></span>キリのいいパラメータ値に吸着させる機能を紹介しています。</span></div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-together">

<a target="_self" href="https://alicia-ing.com/programming/slidersnap/" title="【Unity】スライダーを特定の位置にスナップ（吸着）させる方法" class="blogcard-wrap internal-blogcard-wrap a-wrap cf"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img loading="lazy" decoding="async" width="160" height="90" src="https://alicia-ing.com/wp-content/uploads/2026/06/slidersnap-160x90.webp" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://alicia-ing.com/wp-content/uploads/2026/06/slidersnap-160x90.webp 160w, https://alicia-ing.com/wp-content/uploads/2026/06/slidersnap-120x68.webp 120w, https://alicia-ing.com/wp-content/uploads/2026/06/slidersnap-320x180.webp 320w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">【Unity】スライダーを特定の位置にスナップ（吸着）させる方法</div><div class="blogcard-snippet internal-blogcard-snippet">UnityのSliderに任意の位置へのスナップ（吸着）機能を実装する方法を解説。IPointerUpHandlerとonValueChangedを組み合わせ、snapPointsとsnapThresholdで吸着位置・距離を自由に設定できます。</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://alicia-ing.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">alicia-ing.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.06.18</div></div></div></div></a>
</div>



<h4 class="wp-block-heading">followSpeed（追従の重さ）</h4>



<p class="wp-block-paragraph">followSpeedは、カメラがキャラクターの理想位置にどれだけ速く近づくかを決める値です。値が大きいほど追従が速く（キャラクターに張り付くような動き）、値が小さいほど追従が遅く（ワンテンポ遅れてついてくる、重厚感のある動き）なります。</p>



<p class="wp-block-paragraph">目安として、アクション性の高いゲームではfollowSpeed = 8〜10程度、じっくり見せたい探索系のゲームではfollowSpeed = 3〜5程度から調整を始めるとよいでしょう。</p>



<p class="wp-block-paragraph">極端に小さくしすぎると、キャラクターが素早く動いたときにカメラが置いていかれ、画面外にキャラクターが消えてしまうことがあるので注意してください。</p>



<h4 class="wp-block-heading">rotationSpeed（回転の追従感）</h4>



<p class="wp-block-paragraph">rotationSpeedは、カメラの向きが理想の回転にどれだけ速く追いつくかを決める値です。この値がfollowSpeedと極端に違うと、後述する「不自然な動き」の原因になりやすいため、基本的には<strong>followSpeedと近い値</strong>からスタートするのがおすすめです。</p>



<h4 class="wp-block-heading">lookHeight（注視点の高さ）</h4>



<p class="wp-block-paragraph">lookHeightは、カメラがキャラクターのどの高さを見つめるかを決める値です。キャラクターの頭上あたり（1.5前後）に設定すると自然な見下ろし角度になり、値を大きくしすぎるとカメラが必要以上に見下ろす形になります。</p>



<p class="wp-block-paragraph">逆に0に近づけると、キャラクターの足元付近を注視点にすることになり、水平に近い圧迫感のある画角になります。</p>



<h4 class="wp-block-heading">offset（カメラ距離・画角）</h4>



<p class="wp-block-paragraph">offsetはキャラクターから見たカメラの相対位置で、(左右, 上下, 前後)のベクトルで指定します。Y成分を大きくするとカメラの高さが上がり、Z成分（マイナス方向）を大きくするとキャラクターとの距離が離れて引きの画になります。</p>



<p class="wp-block-paragraph">壁際でカメラがめり込まないようにする「衝突回避」は本記事のスコープ外としていますが、offsetの距離を調整するだけでも、狭い通路でのめり込みはある程度軽減できます。</p>



<h2 id="rtoc-7"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">本記事では、Unityで三人称カメラの追従処理を実装する方法を解説しました。</p>



<ul class="wp-block-list">
<li>位置の追従にはVector3.Lerp、回転の追従にはQuaternion.Slerpを使う（直線補間と球面補間の使い分け）</li>



<li>Quaternion.LookRotationで注視点への方向を計算し、lookHeightで見上げ角度を調整する</li>



<li>カメラの更新処理はUpdateではなくLateUpdateに書き、実行順序によるカクつきを防ぐ</li>



<li>followSpeedとrotationSpeedは近い値に揃えることで、首振りのような不自然な動きを防げる</li>
</ul>



<p class="wp-block-paragraph">まずは本記事のパラメータの目安値から始めて、実際にゲームを動かしながらfollowSpeed・rotationSpeed・lookHeight・offsetの4つを少しずつ調整し、自分のゲームに合った「気持ちいい」カメラワークを探ってみてください。</p>



<p class="wp-block-paragraph">壁のめり込み対策やCinemachineの活用など、さらに踏み込んだカメラ制御は次のステップとして挑戦してみるとよいでしょう。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://alicia-ing.com/programming/unity/third-person-view-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
