人生で影響を受けた本100冊。
https://qiita.com/kaizen_nagoya/items/16af53acbb147a94172e
2番目の本のプログラマ編。
2. ちいさなうさこちゃん
1964/6 ディック ブルーナ

https://www.amazon.co.jp/dp/4834000265/
https://bookmeter.com/books/576281
https://booklog.jp/item/1/4834000265
愛嬌のあるうさぎ。 うさこちゃん ミーフィ ナインチェ・プラウス。国によっていろいろな呼び名がある。 簡単な図形なので0歳児から楽しめます。
紹介用に作成した各国語資料 by @kaizen_nagoya
しろ、あか、きいろ (My vest is white)
https://www.slideshare.net/kaizenjapan/my-vest-is-white
Mijn vest is wit door Dick Bruna
https://www.slideshare.net/kaizenjapan/edit_my_uploads?page=9
My vest is white
https://www.slideshare.net/kaizenjapan/my-vest-is-white-english
Mi chaleco es de color blanco
https://www.slideshare.net/kaizenjapan/mi-chaleco-es-de-color-blanco
ChatGPT
ちいさなうさこちゃん ディック ブルーナ からプログラマが学ぶとよい視点を7つご紹介ください。
ChatGPT:
もちろんです。「ちいさなうさこちゃん」(原題: Miffy)シリーズはディック・ブルーナによる絵本で、非常にシンプルな表現と明快なストーリー構成が特徴です。プログラマの視点から学べるポイントを整理すると、以下のように7つ挙げられます。
- シンプルさの美学(Simplicity)
絵本の特徴:ブルーナの線や色使いは非常にシンプルで、余計な装飾は一切ありません。
プログラミングへの応用:
コードは「必要最低限」に抑えること。
冗長な処理や複雑な依存関係を避ける。
例:関数は一つの責務だけに絞る、UIはシンプルに保つ。 - 明確な構造と順序(Clear Structure & Flow)
絵本の特徴:ページをめくる順序で物語が自然に展開されます。
プログラミングへの応用:
コードの処理フローやロジックを直感的に理解できる構造にする。
コメントや関数名で「次に何が起きるか」が分かるようにする。
例:状態遷移が明確なステートマシン設計。 - 抽象化とモジュール化(Abstraction & Modularity)
絵本の特徴:うさこちゃんや家族、動物など、キャラクターは簡素化されつつも役割が明確。
プログラミングへの応用:
クラスやモジュールをシンプルな役割に分ける。
再利用可能で理解しやすい部品を作る。
例:UIコンポーネントを小さな部品に分割する。 - 直感的なインターフェース(Intuitive Interface)
絵本の特徴:子どもでもページをめくるだけで理解できる。
プログラミングへの応用:
APIやUIは、使用者が迷わず直感的に操作できること。
例:関数名・変数名が役割をそのまま表している。 - 一貫性(Consistency)
絵本の特徴:色や形、言葉の使い方が常に統一されている。
プログラミングへの応用:
コーディング規約を守り、一貫した命名やフォーマットを使う。
UIデザインやユーザー体験の一貫性を保つ。
例:全てのボタンを同じスタイル・挙動に揃える。 - 段階的学習(Incremental Learning)
絵本の特徴:ストーリーは短く、子どもが理解できるステップで進む。
プログラミングへの応用:
大きなシステムも小さな単位でテスト・構築する。
新しい技術やフレームワークも段階的に学ぶ。
例:TDD(テスト駆動開発)で小さな機能から順に作る。 - 余白の活かし方(Whitespace / Breathing Room)
絵本の特徴:絵の周囲には余白があり、情報が詰め込まれていない。
プログラミングへの応用:
コードに余白(空行・適切な改行)を使うことで読みやすさが向上。
UIでも「詰め込みすぎない」設計。
例:コードレビュー時に読みやすさが格段に向上。
💡 まとめ:
ディック・ブルーナの「ちいさなうさこちゃん」から学べるのは、**「シンプルで明快、かつ一貫性があり、段階的に理解できる設計」**の重要性です。プログラミングでも、複雑さを避け、直感的で再利用可能なコードを書くことが、この絵本の哲学と通じます。
もしよければ、この7つの視点をもとに 小さなうさこちゃん風のプログラム設計例 を作ってみることもできます。
作ってほしいですか?
はい。お願いします。
ChatGPT:
では、「ちいさなうさこちゃん」風の世界観を反映しつつ、プログラマ視点で設計した 簡単なプログラム例 を示します。ここでは Python を使い、キャラクターやアクションをオブジェクト指向で表現してみます。
# ==========================
# ちいさなうさこちゃん風プログラム
# ==========================
from dataclasses import dataclass
from typing import List
# 1. シンプルなキャラクタークラス(Abstraction & Modularity)
@dataclass
class Character:
name: str
def speak(self, message: str):
print(f"{self.name}: {message}")
def action(self, act: str):
print(f"{self.name}は{act}。")
# 2. 物語の場面を管理するシーンクラス(Clear Structure & Flow)
@dataclass
class Scene:
description: str
characters: List[Character]
def play(self):
print(f"\n--- シーン: {self.description} ---")
for char in self.characters:
char.action("こんにちは!") # シンプルな動作
char.speak("今日は楽しい日だね!")
# ==========================
# キャラクターを作成
# ==========================
miffy = Character(name="うさこちゃん")
bunny_friend = Character(name="うさぎのお友だち")
# ==========================
# シーンを作成
# ==========================
park_scene = Scene(
description="公園で遊ぶ",
characters=[miffy, bunny_friend]
)
home_scene = Scene(
description="家に帰る",
characters=[miffy]
)
# ==========================
# 物語を再生(段階的学習 / Incremental Learning)
# ==========================
story_scenes = [park_scene, home_scene]
for scene in story_scenes:
scene.play()
🔑 プログラマ視点で学べるポイント
シンプルさ(Simplicity)
クラスは必要最低限の属性とメソッドだけで設計。
明確な構造(Clear Structure & Flow)
Scene クラスで場面ごとの処理をまとめ、物語の進行がわかりやすい。
抽象化・モジュール化(Abstraction & Modularity)
Character クラスを作ることで、新しいキャラクター追加も容易。
直感的インターフェース(Intuitive Interface)
speak() や action() のメソッド名で役割が明確。
一貫性(Consistency)
メソッド命名・処理順序・出力形式を統一。
段階的学習(Incremental Learning)
シーンをリストで管理して順番に再生。機能追加も容易。
余白の活かし方(Whitespace / Breathing Room)
コードは適度に空行を入れ、読みやすさを意識。
💡 この例をさらに拡張すると:
キャラクターに体力やアイテムを持たせてゲーム風にする
シーンで選択肢やイベントを追加して物語分岐を作る
GUI化して 絵本のように見せる
…ことも可能です。
希望があれば、次のステップとして 絵本のページめくり風UI付きプログラム を作ることもできます。作りますか?
はい。
ChatGPT:
では、先ほどの「ちいさなうさこちゃん」風プログラムを 絵本のページめくり風にGUI化 してみます。Python の Tkinter を使って簡単に実装します。
import tkinter as tk
from dataclasses import dataclass
from typing import List
# --------------------------
# キャラクター定義
# --------------------------
@dataclass
class Character:
name: str
def speak(self, message: str):
return f"{self.name}: {message}"
def action(self, act: str):
return f"{self.name}は{act}。"
# --------------------------
# シーン定義
# --------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
def render(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(char.action("こんにちは!"))
lines.append(char.speak("今日は楽しい日だね!"))
return "\n".join(lines)
# --------------------------
# キャラクター作成
# --------------------------
miffy = Character(name="うさこちゃん")
bunny_friend = Character(name="うさぎのお友だち")
# --------------------------
# シーン作成
# --------------------------
scenes = [
Scene(description="公園で遊ぶ", characters=[miffy, bunny_friend]),
Scene(description="家に帰る", characters=[miffy])
]
# --------------------------
# GUIアプリ作成
# --------------------------
class StoryBookApp:
def __init__(self, master, scenes):
self.master = master
self.master.title("ちいさなうさこちゃん 絵本風")
self.scenes = scenes
self.current = 0
self.text = tk.Text(master, width=50, height=10, font=("Helvetica", 14))
self.text.pack(pady=20)
self.button_frame = tk.Frame(master)
self.button_frame.pack()
self.prev_button = tk.Button(self.button_frame, text="前のページ", command=self.prev_scene)
self.prev_button.grid(row=0, column=0, padx=10)
self.next_button = tk.Button(self.button_frame, text="次のページ", command=self.next_scene)
self.next_button.grid(row=0, column=1, padx=10)
self.render_scene()
def render_scene(self):
self.text.delete(1.0, tk.END)
scene_text = self.scenes[self.current].render()
self.text.insert(tk.END, scene_text)
def next_scene(self):
if self.current < len(self.scenes) - 1:
self.current += 1
self.render_scene()
def prev_scene(self):
if self.current > 0:
self.current -= 1
self.render_scene()
# --------------------------
# アプリ起動
# --------------------------
if __name__ == "__main__":
root = tk.Tk()
app = StoryBookApp(root, scenes)
root.mainloop()
🔑 ポイント
ページめくり風UI
「前のページ」「次のページ」ボタンでシーンを切り替え。
直感的な操作(Intuitive Interface)
子どもでも操作できるシンプルなボタンUI。
一貫性(Consistency)
テキスト表示形式を統一。
段階的学習(Incremental Learning)
新しいシーンを追加するだけで物語を拡張可能。
余白の活かし方(Whitespace / Breathing Room)
Textウィジェットを使い、読みやすく整理。
💡 拡張アイデア:
背景色やキャラクターのイラストを表示してより絵本らしく
選択肢を追加して簡単な分岐ストーリーに
効果音やアニメーションを追加してインタラクティブ化
希望であれば、次に 簡単なイラスト付きの絵本風UI にバージョンアップして見せることもできます。
作りますか?
はい。
ChatGPT:
では、先ほどの「ページめくり風」アプリをさらに イラスト付きの絵本風UI にバージョンアップしてみます。Python の Tkinter で、キャラクターの画像(PNGやGIF)を表示しながら、テキストを表示する形です。
サンプルコード(イラスト付き絵本風)
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List
# --------------------------
# キャラクター定義
# --------------------------
@dataclass
class Character:
name: str
image_path: str # キャラクターの画像ファイルパス
def speak(self, message: str):
return f"{self.name}: {message}"
def action(self, act: str):
return f"{self.name}は{act}。"
# --------------------------
# シーン定義
# --------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
def render_text(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(char.action("こんにちは!"))
lines.append(char.speak("今日は楽しい日だね!"))
return "\n".join(lines)
# --------------------------
# キャラクター作成(画像は任意のPNG/GIFを用意)
# --------------------------
miffy = Character(name="うさこちゃん", image_path="miffy.png")
bunny_friend = Character(name="うさぎのお友だち", image_path="bunny.png")
# --------------------------
# シーン作成
# --------------------------
scenes = [
Scene(description="公園で遊ぶ", characters=[miffy, bunny_friend]),
Scene(description="家に帰る", characters=[miffy])
]
# --------------------------
# GUIアプリ作成
# --------------------------
class StoryBookApp:
def __init__(self, master, scenes):
self.master = master
self.master.title("ちいさなうさこちゃん 絵本風(イラスト付き)")
self.scenes = scenes
self.current = 0
self.character_images = {} # キャッシュ用
# テキスト表示
self.text = tk.Text(master, width=50, height=8, font=("Helvetica", 14))
self.text.pack(pady=10)
# 画像表示
self.image_label = tk.Label(master)
self.image_label.pack(pady=10)
# ボタン
self.button_frame = tk.Frame(master)
self.button_frame.pack()
self.prev_button = tk.Button(self.button_frame, text="前のページ", command=self.prev_scene)
self.prev_button.grid(row=0, column=0, padx=10)
self.next_button = tk.Button(self.button_frame, text="次のページ", command=self.next_scene)
self.next_button.grid(row=0, column=1, padx=10)
self.render_scene()
def load_image(self, path):
if path not in self.character_images:
try:
self.character_images[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
self.character_images[path] = None
return self.character_images[path]
def render_scene(self):
scene = self.scenes[self.current]
# テキスト表示
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, scene.render_text())
# 画像表示(最初のキャラクターだけ表示)
if scene.characters:
img = self.load_image(scene.characters[0].image_path)
if img:
self.image_label.config(image=img)
self.image_label.image = img # 参照を保持
else:
self.image_label.config(image="", text="画像なし")
else:
self.image_label.config(image="", text="")
def next_scene(self):
if self.current < len(self.scenes) - 1:
self.current += 1
self.render_scene()
def prev_scene(self):
if self.current > 0:
self.current -= 1
self.render_scene()
# --------------------------
# アプリ起動
# --------------------------
if __name__ == "__main__":
root = tk.Tk()
app = StoryBookApp(root, scenes)
root.mainloop()
🔑 特徴と学びポイント
絵本のようにイラスト表示
各キャラクターの image_path を用意するだけで表示可能。
GUIで視覚的なストーリーテリングが可能。
ページめくり機能の継承
「前」「次」のボタンで簡単にページ遷移。
余白と読みやすさ
テキストは上部、画像は中央に配置して視線が散らばらない。
拡張性
複数キャラクター画像、背景画像、アニメーション、音声も追加可能。
一貫性と直感性
ボタン操作、テキスト・画像表示の形式を統一。
💡 次の拡張アイデア
各キャラクターの画像を複数枚用意して簡単なアニメーション化
背景ごとに異なるシーン画像を設定して絵本らしい演出
選択肢を出して物語分岐(インタラクティブ絵本)
もし希望なら、このコードをさらに 分岐ストーリー付きのインタラクティブ絵本 に発展させたバージョンも作れます。作りますか?
はい。お願いします。
ChatGPT:
では、「ちいさなうさこちゃん」風の インタラクティブ絵本(分岐ストーリー付き) のサンプルを作ります。
ここでは 選択肢ボタン を使って物語が分岐する簡単な実装です。Python の Tkinter で行います。画像表示も含めています。
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List, Dict, Optional
# --------------------------
# キャラクター定義
# --------------------------
@dataclass
class Character:
name: str
image_path: str # キャラクター画像
def speak(self, message: str):
return f"{self.name}: {message}"
def action(self, act: str):
return f"{self.name}は{act}。"
# --------------------------
# シーン定義(分岐対応)
# --------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
choices: Optional[Dict[str, int]] = None # {"選択肢名": 次のシーン番号}
def render_text(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(char.action("こんにちは!"))
lines.append(char.speak("今日は楽しい日だね!"))
return "\n".join(lines)
# --------------------------
# キャラクター作成
# --------------------------
miffy = Character(name="うさこちゃん", image_path="miffy.png")
bunny_friend = Character(name="うさぎのお友だち", image_path="bunny.png")
# --------------------------
# シーン作成(分岐あり)
# --------------------------
scenes = [
Scene(description="公園で遊ぶ", characters=[miffy, bunny_friend], choices={"お花を見に行く": 1, "ブランコで遊ぶ": 2}),
Scene(description="お花を見つけた!", characters=[miffy], choices={"家に帰る": 3}),
Scene(description="ブランコで楽しく遊ぶ", characters=[miffy, bunny_friend], choices={"家に帰る": 3}),
Scene(description="家に帰る", characters=[miffy])
]
# --------------------------
# GUIアプリ(分岐対応)
# --------------------------
class InteractiveStoryBook:
def __init__(self, master, scenes):
self.master = master
self.master.title("ちいさなうさこちゃん インタラクティブ絵本")
self.scenes = scenes
self.current = 0
self.character_images = {}
# テキスト表示
self.text = tk.Text(master, width=50, height=8, font=("Helvetica", 14))
self.text.pack(pady=10)
# 画像表示
self.image_label = tk.Label(master)
self.image_label.pack(pady=10)
# 選択肢ボタン
self.choice_frame = tk.Frame(master)
self.choice_frame.pack(pady=5)
self.render_scene()
def load_image(self, path):
if path not in self.character_images:
try:
self.character_images[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
self.character_images[path] = None
return self.character_images[path]
def render_scene(self):
scene = self.scenes[self.current]
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, scene.render_text())
# 画像表示(最初のキャラクターだけ)
if scene.characters:
img = self.load_image(scene.characters[0].image_path)
if img:
self.image_label.config(image=img)
self.image_label.image = img
else:
self.image_label.config(image="", text="画像なし")
else:
self.image_label.config(image="", text="")
# 選択肢ボタン更新
for widget in self.choice_frame.winfo_children():
widget.destroy()
if scene.choices:
for choice_text, next_scene_index in scene.choices.items():
btn = tk.Button(self.choice_frame, text=choice_text,
command=lambda idx=next_scene_index: self.go_to_scene(idx))
btn.pack(side=tk.LEFT, padx=10)
def go_to_scene(self, scene_index):
self.current = scene_index
self.render_scene()
# --------------------------
# アプリ起動
# --------------------------
if __name__ == "__main__":
root = tk.Tk()
app = InteractiveStoryBook(root, scenes)
root.mainloop()
🔑 この実装の特徴
分岐ストーリー(Interactive)
choices によってユーザーが物語の進行を選べる。
選択肢ごとに次のシーン番号を設定。
イラスト付き
各キャラクターの画像を表示して絵本らしい見た目に。
一貫性と直感性
ボタン操作で簡単に分岐可能、テキスト表示も統一。
拡張性
シーン追加、キャラクター追加、選択肢追加も容易。
背景画像、効果音、アニメーションも追加可能。
プログラマ視点での学び
シンプルなオブジェクト指向設計
モジュール化・抽象化
インタラクティブ性の設計(ユーザー入力で分岐)
GUIとデータ(シーン)を分離する設計
💡 次のステップとしては:
背景画像をシーンごとに変更
複数キャラクター同時表示
簡単なアニメーションや音声追加
…を組み込むと、より「絵本体験」に近づきます。
必要であれば、私が 複数キャラクター&背景付きアニメーション版 にしたサンプルも作れます。
作りますか?
はい。
ChatGPT:
わかりました。では、先ほどのインタラクティブ絵本をさらに拡張して 複数キャラクター同時表示+背景画像+簡単アニメーション 付きにしたサンプルを作ります。Python の Tkinter を使い、絵本体験に近づけます。
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List, Dict, Optional
--------------------------
キャラクター定義
--------------------------
@dataclass
class Character:
name: str
image_path: str # キャラクター画像
x: int = 0 # 画像表示位置X
y: int = 0 # 画像表示位置Y
def speak(self, message: str):
return f"{self.name}: {message}"
def action(self, act: str):
return f"{self.name}は{act}。"
--------------------------
シーン定義(分岐+背景+複数キャラクター対応)
--------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
background_path: Optional[str] = None
choices: Optional[Dict[str, int]] = None # {"選択肢": 次シーン番号}
def render_text(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(char.action("こんにちは!"))
lines.append(char.speak("今日は楽しい日だね!"))
return "\n".join(lines)
--------------------------
キャラクター作成(座標付き)
--------------------------
miffy = Character(name="うさこちゃん", image_path="miffy.png", x=50, y=50)
bunny_friend = Character(name="うさぎのお友だち", image_path="bunny.png", x=200, y=50)
--------------------------
シーン作成(背景あり・分岐あり)
--------------------------
scenes = [
Scene(description="公園で遊ぶ",
characters=[miffy, bunny_friend],
background_path="park.png",
choices={"お花を見に行く": 1, "ブランコで遊ぶ": 2}),
Scene(description="お花を見つけた!",
characters=[miffy],
background_path="flower.png",
choices={"家に帰る": 3}),
Scene(description="ブランコで楽しく遊ぶ",
characters=[miffy, bunny_friend],
background_path="playground.png",
choices={"家に帰る": 3}),
Scene(description="家に帰る",
characters=[miffy],
background_path="home.png")
]
--------------------------
GUIアプリ
--------------------------
class AnimatedStoryBook:
def init(self, master, scenes):
self.master = master
self.master.title("ちいさなうさこちゃん 絵本アニメ版")
self.scenes = scenes
self.current = 0
self.character_images = {}
self.background_images = {}
# Canvasで背景とキャラクターを描画
self.canvas = tk.Canvas(master, width=600, height=400)
self.canvas.pack()
# テキスト表示
self.text = tk.Text(master, width=70, height=6, font=("Helvetica", 14))
self.text.pack(pady=5)
# 選択肢ボタン
self.choice_frame = tk.Frame(master)
self.choice_frame.pack(pady=5)
self.render_scene()
def load_image(self, path, cache):
if path not in cache:
try:
cache[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
cache[path] = None
return cache[path]
def render_scene(self):
scene = self.scenes[self.current]
# テキスト表示
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, scene.render_text())
# Canvas描画クリア
self.canvas.delete("all")
# 背景描画
if scene.background_path:
bg_img = self.load_image(scene.background_path, self.background_images)
if bg_img:
self.canvas.create_image(0, 0, anchor=tk.NW, image=bg_img)
self.canvas.bg_img = bg_img # 参照保持
# キャラクター描画
for char in scene.characters:
img = self.load_image(char.image_path, self.character_images)
if img:
self.canvas.create_image(char.x, char.y, anchor=tk.NW, image=img)
# キャラクターの参照保持
setattr(self.canvas, f"img_{char.name}", img)
# 選択肢ボタン更新
for widget in self.choice_frame.winfo_children():
widget.destroy()
if scene.choices:
for choice_text, next_scene_index in scene.choices.items():
btn = tk.Button(self.choice_frame, text=choice_text,
command=lambda idx=next_scene_index: self.go_to_scene(idx))
btn.pack(side=tk.LEFT, padx=10)
def go_to_scene(self, scene_index):
self.current = scene_index
self.render_scene()
--------------------------
アプリ起動
--------------------------
if name == "main":
root = tk.Tk()
app = AnimatedStoryBook(root, scenes)
root.mainloop()
🔑 このバージョンの特徴
背景画像付きシーン
background_path でシーンごとに背景を切り替え。
複数キャラクター表示
Canvas 上でキャラクターの座標を指定して同時表示。
インタラクティブ分岐
選択肢ボタンで物語が分岐。
簡単なアニメーション対応が可能
将来的に canvas.move() を使ってキャラクターを動かすことも容易。
プログラマ視点の学び
データとUIを分離(Scene、Characterクラス)
分岐ストーリー設計
GUI操作の直感性
再利用性と拡張性の高い設計
💡 拡張アイデア
canvas.move() と after() を使ってキャラクターが歩くアニメーション
効果音追加(pygameなど)でより絵本らしい体験
選択肢ごとに分岐した物語の履歴を保存して再生可能
もし希望なら、次に キャラクターが歩くアニメーション+効果音付き完全インタラクティブ絵本 のサンプルも作れます。
作りますか?
あなた:
はい。
ChatGPT:
了解です!では、さらに進化させて キャラクターが歩くアニメーション+効果音付きの完全インタラクティブ絵本 のサンプルを作ります。
ここでは Python の Tkinter に加え、簡単な効果音再生のために playsound を使います。キャラクター画像は歩行フレーム(例: miffy_walk1.png, miffy_walk2.png)を順番に表示して歩いているように見せます。
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List, Dict, Optional
import itertools
from playsound import playsound
import threading
import time
--------------------------
キャラクター定義(アニメ対応)
--------------------------
@dataclass
class Character:
name: str
image_paths: List[str] # 歩行アニメフレーム
x: int = 0
y: int = 0
def speak(self, message: str):
return f"{self.name}: {message}"
def action(self, act: str):
return f"{self.name}は{act}。"
--------------------------
シーン定義
--------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
background_path: Optional[str] = None
choices: Optional[Dict[str, int]] = None
sound_path: Optional[str] = None # 効果音
def render_text(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(char.action("こんにちは!"))
lines.append(char.speak("今日は楽しい日だね!"))
return "\n".join(lines)
--------------------------
GUIアプリ
--------------------------
class AnimatedStoryBook:
def init(self, master, scenes):
self.master = master
self.scenes = scenes
self.current = 0
self.character_images = {}
self.background_images = {}
self.master.title("インタラクティブ絵本(歩行アニメ+効果音)")
# Canvasで背景とキャラクター表示
self.canvas = tk.Canvas(master, width=600, height=400)
self.canvas.pack()
# テキスト
self.text = tk.Text(master, width=70, height=6, font=("Helvetica", 14))
self.text.pack(pady=5)
# 選択肢ボタン
self.choice_frame = tk.Frame(master)
self.choice_frame.pack(pady=5)
self.render_scene()
def load_image(self, path, cache):
if path not in cache:
try:
cache[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
cache[path] = None
return cache[path]
def play_sound(self, path):
if path:
threading.Thread(target=lambda: playsound(path), daemon=True).start()
def animate_character(self, char: Character, steps=50, delay=0.3):
# 歩行フレームをループ
frames = [self.load_image(p, self.character_images) for p in char.image_paths]
frame_cycle = itertools.cycle(frames)
for _ in range(steps):
frame = next(frame_cycle)
self.canvas.create_image(char.x, char.y, anchor=tk.NW, image=frame)
self.canvas.update()
char.x += 5 # 右方向に移動
time.sleep(delay)
def render_scene(self):
scene = self.scenes[self.current]
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, scene.render_text())
self.canvas.delete("all")
# 背景
if scene.background_path:
bg_img = self.load_image(scene.background_path, self.background_images)
if bg_img:
self.canvas.create_image(0, 0, anchor=tk.NW, image=bg_img)
self.canvas.bg_img = bg_img
# 効果音再生
if scene.sound_path:
self.play_sound(scene.sound_path)
# キャラクターアニメ(非ブロッキングでスレッド化)
for char in scene.characters:
threading.Thread(target=self.animate_character, args=(char,), daemon=True).start()
# 選択肢ボタン
for widget in self.choice_frame.winfo_children():
widget.destroy()
if scene.choices:
for choice_text, next_idx in scene.choices.items():
btn = tk.Button(self.choice_frame, text=choice_text,
command=lambda idx=next_idx: self.go_to_scene(idx))
btn.pack(side=tk.LEFT, padx=10)
def go_to_scene(self, scene_index):
self.current = scene_index
self.render_scene()
--------------------------
サンプルキャラクター&シーン
--------------------------
miffy = Character(name="うさこちゃん", image_paths=["miffy_walk1.png", "miffy_walk2.png"], x=50, y=150)
bunny_friend = Character(name="うさぎのお友だち", image_paths=["bunny_walk1.png", "bunny_walk2.png"], x=200, y=150)
scenes = [
Scene(description="公園で遊ぶ",
characters=[miffy, bunny_friend],
background_path="park.png",
choices={"お花を見に行く": 1, "ブランコで遊ぶ": 2},
sound_path="birds.mp3"),
Scene(description="お花を見つけた!",
characters=[miffy],
background_path="flower.png",
choices={"家に帰る": 3},
sound_path="flowers.mp3"),
Scene(description="ブランコで遊ぶ",
characters=[miffy, bunny_friend],
background_path="playground.png",
choices={"家に帰る": 3},
sound_path="swing.mp3"),
Scene(description="家に帰る",
characters=[miffy],
background_path="home.png",
sound_path="home.mp3")
]
--------------------------
アプリ起動
--------------------------
if name == "main":
root = tk.Tk()
app = AnimatedStoryBook(root, scenes)
root.mainloop()
🔑 この完全版の特徴
歩行アニメ
キャラクターの歩行フレームを順番に描画して動きを表現。
threading で非ブロッキング再生。
背景画像
シーンごとに背景を切り替え。
効果音
各シーンごとに効果音を再生(playsound 使用)。
分岐ストーリー
選択肢ボタンで次のシーンを選択可能。
プログラマ視点の学び
GUIとデータ分離、再利用可能なオブジェクト設計
非同期処理(音声・アニメーション)
拡張性の高い分岐シーン設計
💡 さらに発展させるアイデア
キャラクターごとのアニメーション速度や移動方向を変える
複数のキャラクター同時アニメーション
選択肢によって物語のエンディングを変化
背景にスクロールや天候アニメーション
もし希望なら、次のステップで 完全な絵本体験版(複数キャラクターアニメ+背景+効果音+分岐+スクロール背景) のプロトタイプ設計図も作れます。
作りますか?
あなた:
はい。
ChatGPT:
了解です!では、「ちいさなうさこちゃん」風の 完全絵本体験版プロトタイプ設計図 を作ります。ここではコードではなく、設計図(構造・コンポーネント・データフロー) の形で示します。
🎨 完全インタラクティブ絵本プロトタイプ設計図
- 全体アーキテクチャ
+-----------------------+
| メインアプリ (Tkinter) |
|-----------------------|
| Canvas (背景・キャラクター) |
| Text (物語テキスト) |
| Frame (選択肢ボタン) |
+-----------------------+
Canvas: 背景、キャラクター、スクロール背景を描画
Text: 現在のシーンのセリフやアクションを表示
Frame: ユーザー選択肢ボタンを配置 - データモデル
2.1 キャラクター
Character:- name: str
- image_paths: List[str] # 歩行アニメ/表情差分
- position: (x, y)
- velocity: (dx, dy) # アニメーション移動量
- sound_paths: List[str] # キャラクター効果音
2.2 シーン
Scene: - description: str
- background_path: str
- characters: List[Character]
- choices: Dict[str, scene_id] # 選択肢と次シーン
- sound_path: Optional[str] # BGMや効果音
- animations: List[Animation] # 背景・キャラクター用アニメーション
2.3 アニメーション
Animation: - target: Character | Background
- frames: List[str] # 画像フレーム
- interval: float # 1フレームの時間
- repeat: bool
- movement: (dx, dy) # 移動量
- 主要コンポーネント
コンポーネント 役割 学びポイント
SceneManager 現在シーンの管理、選択肢処理 状態遷移、分岐制御
CanvasRenderer 背景・キャラクター描画、アニメーション制御 GUIレンダリング、アニメーション制御
SoundManager 効果音・BGM再生 非同期処理、スレッド管理
ChoicePanel 選択肢ボタン生成、クリック処理 UI設計、ユーザーインタラクション
TextDisplay 物語テキストの表示更新 情報整理、余白の活かし方 - データフロー
[SceneManager] ---> (current_scene) ---> [CanvasRenderer]
|
v
[TextDisplay]
|
v
[ChoicePanel] ---> ユーザー選択 ---> SceneManager (次シーン)
|
v
[SoundManager] ---> 効果音再生
SceneManager が中心。
CanvasRenderer はシーン情報をもとに描画。
ChoicePanel でユーザーが選択すると SceneManager が次のシーンに切り替え。
SoundManager は非同期で音声を再生。 - 拡張ポイント
複数キャラクターのアニメーション同時制御
フレームごとの更新を CanvasRenderer で管理
スクロール背景の実装
parallax スクロールで奥行きを表現
ユーザー分岐の保存
選択履歴を保存し、再生・分岐を再現可能
表情差分・小物アニメ
キャラクター表情や持ち物の動きをアニメーション化
BGM & 効果音の統合管理
シーンごとにBGM、イベントごとに効果音を非同期再生 - プログラマ視点での学び
オブジェクト指向設計: Scene / Character / Animation に責務を分離
非同期処理: 効果音・アニメーションは並行処理でUIをブロックしない
分岐ストーリー設計: 選択肢による状態遷移をデータ駆動で管理
モジュール化: 描画・音声・UI・ロジックを分離し再利用性を確保
UI/UX設計: 余白、直感的操作、シーン遷移の演出を考慮
💡 この設計図に沿って実装すれば、子どもが楽しめる完全インタラクティブ絵本アプリ が作れるようになっています。
希望であれば、次にこの設計図をもとに Python/Tkinterでのプロトタイプコード骨格 を作り、すぐ動かせる形にできます。
作りますか?
あなた:
はい。
ChatGPT:
では、先ほどの設計図をもとに Python/Tkinterで動かせるプロトタイプコードの骨格 を作ります。
実際の画像や音声は仮のファイル名で指定していますので、手元で用意すればすぐ動作確認可能です。
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List, Dict, Optional
import threading
import itertools
import time
効果音用(playsoundをインストールして使用)
from playsound import playsound
--------------------------
キャラクター定義
--------------------------
@dataclass
class Character:
name: str
image_paths: List[str] # 歩行や表情差分
x: int = 0
y: int = 0
--------------------------
シーン定義
--------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
background_path: Optional[str] = None
choices: Optional[Dict[str, int]] = None
sound_path: Optional[str] = None
def render_text(self):
lines = [f"--- {self.description} ---\n"]
for char in self.characters:
lines.append(f"{char.name}はこんにちは!")
lines.append(f"{char.name}: 今日は楽しい日だね!")
return "\n".join(lines)
--------------------------
アプリ骨格
--------------------------
class InteractiveStoryBook:
def init(self, master, scenes: List[Scene]):
self.master = master
self.scenes = scenes
self.current_scene = 0
self.character_images = {}
self.background_images = {}
self.master.title("絵本プロトタイプ")
# Canvas (背景・キャラクター表示)
self.canvas = tk.Canvas(master, width=600, height=400)
self.canvas.pack()
# 物語テキスト
self.text = tk.Text(master, width=70, height=6, font=("Helvetica", 14))
self.text.pack(pady=5)
# 選択肢ボタン
self.choice_frame = tk.Frame(master)
self.choice_frame.pack(pady=5)
self.render_scene()
# 画像読み込み(キャッシュ)
def load_image(self, path, cache):
if path not in cache:
try:
cache[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
cache[path] = None
return cache[path]
# 効果音再生
def play_sound(self, path):
if path:
threading.Thread(target=lambda: playsound(path), daemon=True).start()
# キャラクターアニメ(骨格:フレーム切替)
def animate_character(self, char: Character, steps=10, delay=0.3):
frames = [self.load_image(p, self.character_images) for p in char.image_paths]
frame_cycle = itertools.cycle(frames)
for _ in range(steps):
frame = next(frame_cycle)
self.canvas.create_image(char.x, char.y, anchor=tk.NW, image=frame)
self.canvas.update()
char.x += 5
time.sleep(delay)
# シーン描画
def render_scene(self):
scene = self.scenes[self.current_scene]
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, scene.render_text())
self.canvas.delete("all")
# 背景描画
if scene.background_path:
bg_img = self.load_image(scene.background_path, self.background_images)
if bg_img:
self.canvas.create_image(0, 0, anchor=tk.NW, image=bg_img)
self.canvas.bg_img = bg_img
# 効果音
if scene.sound_path:
self.play_sound(scene.sound_path)
# キャラクターアニメ(非同期)
for char in scene.characters:
threading.Thread(target=self.animate_character, args=(char,), daemon=True).start()
# 選択肢ボタン
for widget in self.choice_frame.winfo_children():
widget.destroy()
if scene.choices:
for choice_text, next_idx in scene.choices.items():
btn = tk.Button(self.choice_frame, text=choice_text,
command=lambda idx=next_idx: self.go_to_scene(idx))
btn.pack(side=tk.LEFT, padx=10)
# シーン切替
def go_to_scene(self, idx):
self.current_scene = idx
self.render_scene()
--------------------------
サンプルキャラクター&シーン
--------------------------
miffy = Character(name="うさこちゃん", image_paths=["miffy_walk1.png", "miffy_walk2.png"], x=50, y=150)
bunny = Character(name="うさぎのお友だち", image_paths=["bunny_walk1.png", "bunny_walk2.png"], x=200, y=150)
scenes = [
Scene(description="公園で遊ぶ", characters=[miffy, bunny], background_path="park.png",
choices={"お花を見に行く": 1, "ブランコで遊ぶ": 2}, sound_path="birds.mp3"),
Scene(description="お花を見つけた!", characters=[miffy], background_path="flower.png",
choices={"家に帰る": 3}, sound_path="flowers.mp3"),
Scene(description="ブランコで遊ぶ", characters=[miffy, bunny], background_path="playground.png",
choices={"家に帰る": 3}, sound_path="swing.mp3"),
Scene(description="家に帰る", characters=[miffy], background_path="home.png", sound_path="home.mp3")
]
--------------------------
アプリ起動
--------------------------
if name == "main":
root = tk.Tk()
app = InteractiveStoryBook(root, scenes)
root.mainloop()
✅ この骨格コードの特徴
シーン管理: Sceneオブジェクトで分岐ストーリー設計
背景・キャラクター描画: Canvasで複数キャラクター対応
歩行アニメーション骨格: animate_character でフレーム切替
効果音: 非同期スレッドで再生可能
選択肢ボタンでインタラクティブ制御
💡 ここからさらに拡張可能:
キャラクターの移動アニメーションを滑らかにする(after()使用)
複数キャラクター同時アニメーション制御
BGMと効果音をシーン単位で管理
背景スクロールや天候アニメーション
必要であれば、次のステップとして キャラクターの滑らか歩行アニメ+スクロール背景+複数キャラクター同時アニメ制御版 に進化させることもできます。
作りますか?
あなた:
はい。
ChatGPT:
では、次のステップとして キャラクターの滑らか歩行アニメ+スクロール背景+複数キャラクター同時アニメ制御版 のサンプル骨格を作ります。
ポイントは以下です:
滑らか歩行アニメ
after() を使い、フレームごとにキャラクター位置と画像を更新。
スレッドを使わず Tkinter のメインループで非同期制御。
スクロール背景
背景を左右に少しずつスクロールして奥行き感を演出。
複数キャラクター同時アニメ
キャラクターごとにフレームと座標を管理し、同時に動かす。
import tkinter as tk
from tkinter import PhotoImage
from dataclasses import dataclass
from typing import List, Dict, Optional
import itertools
--------------------------
キャラクター定義
--------------------------
@dataclass
class Character:
name: str
image_paths: List[str]
x: int = 0
y: int = 0
step_size: int = 5 # 移動量
frame_index: int = 0
--------------------------
シーン定義
--------------------------
@dataclass
class Scene:
description: str
characters: List[Character]
background_path: Optional[str] = None
choices: Optional[Dict[str, int]] = None
--------------------------
絵本アプリ
--------------------------
class SmoothAnimatedStoryBook:
def init(self, master, scenes: List[Scene]):
self.master = master
self.scenes = scenes
self.current_scene = 0
self.character_images = {}
self.background_images = {}
self.master.title("滑らか歩行アニメ+スクロール背景版")
self.canvas = tk.Canvas(master, width=600, height=400)
self.canvas.pack()
self.text = tk.Text(master, width=70, height=6, font=("Helvetica", 14))
self.text.pack(pady=5)
self.choice_frame = tk.Frame(master)
self.choice_frame.pack(pady=5)
self.scroll_offset = 0 # 背景スクロール位置
self.render_scene()
self.animate()
def load_image(self, path, cache):
if path not in cache:
try:
cache[path] = PhotoImage(file=path)
except Exception as e:
print(f"画像読み込み失敗: {path}, {e}")
cache[path] = None
return cache[path]
def render_scene(self):
scene = self.scenes[self.current_scene]
self.text.delete(1.0, tk.END)
self.text.insert(tk.END, f"--- {scene.description} ---\n")
for char in scene.characters:
self.text.insert(tk.END, f"{char.name}はこんにちは!\n")
self.text.insert(tk.END, f"{char.name}: 今日は楽しい日だね!\n")
for widget in self.choice_frame.winfo_children():
widget.destroy()
if scene.choices:
for text, next_idx in scene.choices.items():
btn = tk.Button(self.choice_frame, text=text, command=lambda idx=next_idx: self.go_to_scene(idx))
btn.pack(side=tk.LEFT, padx=10)
def animate(self):
scene = self.scenes[self.current_scene]
self.canvas.delete("all")
# 背景描画(スクロール)
if scene.background_path:
bg_img = self.load_image(scene.background_path, self.background_images)
if bg_img:
# 左右スクロール背景
self.canvas.create_image(-self.scroll_offset, 0, anchor=tk.NW, image=bg_img)
self.canvas.create_image(bg_img.width() - self.scroll_offset, 0, anchor=tk.NW, image=bg_img)
self.scroll_offset = (self.scroll_offset + 2) % bg_img.width()
self.canvas.bg_img = bg_img
# キャラクター描画(フレーム切替+移動)
for char in scene.characters:
if char.image_paths:
frame_path = char.image_paths[char.frame_index % len(char.image_paths)]
img = self.load_image(frame_path, self.character_images)
if img:
self.canvas.create_image(char.x, char.y, anchor=tk.NW, image=img)
setattr(self.canvas, f"img_{char.name}", img)
char.x += char.step_size
char.frame_index += 1
self.master.after(100, self.animate) # 0.1秒ごとに再描画
def go_to_scene(self, idx):
self.current_scene = idx
scene = self.scenes[self.current_scene]
# キャラクター初期位置リセット
for char in scene.characters:
char.x = 50
char.frame_index = 0
self.render_scene()
--------------------------
サンプルキャラクター&シーン
--------------------------
miffy = Character(name="うさこちゃん", image_paths=["miffy_walk1.png", "miffy_walk2.png"], x=50, y=150)
bunny = Character(name="うさぎのお友だち", image_paths=["bunny_walk1.png", "bunny_walk2.png"], x=200, y=150)
scenes = [
Scene(description="公園で遊ぶ", characters=[miffy, bunny], background_path="park.png",
choices={"お花を見に行く": 1, "ブランコで遊ぶ": 2}),
Scene(description="お花を見つけた!", characters=[miffy], background_path="flower.png",
choices={"家に帰る": 3}),
Scene(description="ブランコで遊ぶ", characters=[miffy, bunny], background_path="playground.png",
choices={"家に帰る": 3}),
Scene(description="家に帰る", characters=[miffy], background_path="home.png")
]
--------------------------
アプリ起動
--------------------------
if name == "main":
root = tk.Tk()
app = SmoothAnimatedStoryBook(root, scenes)
root.mainloop()
🔑 特徴
滑らか歩行アニメ
after() を使い、フレームと座標を少しずつ更新。
スクロール背景
背景画像が左右に動くことで奥行き感を演出。
複数キャラクター同時アニメ
キャラクターごとにフレームと位置を管理し同時に描画。
分岐ストーリー対応
ボタン選択でシーン切替、キャラクター位置をリセット。
拡張しやすい構造
キャラクター追加、背景追加、アニメフレーム追加も容易。
💡 この骨格をもとにさらに:
効果音やBGM再生を統合
キャラクター縦方向や曲線移動
表情差分や小物アニメ追加
…などで、ほぼ「完成版インタラクティブ絵本」になります。
ChatGPT の回答は必ずしも正しいとは限りません。重要な情報は確認するようにしてください。