LoginSignup
37
25

More than 3 years have passed since last update.

UX設計からはじめるTEA入門

Last updated at Posted at 2019-08-12
1 / 35

2019/08/12
Elm関西 発表資料


自己紹介

  • arowM

    avatar

  • おしごと: 広義のUX設計

    • ヤギコンサルタント/ヤギアイドルさくらちゃんのプロデュース

      sakura

    • 気吹堂(出版) 代表

      • さくらちゃん写真集
    • ARoW(IT技術) 代表

      • 技術顧問
      • 技術補佐
      • 要件分析・UX設計
    • 株式会社UZUZ(人材紹介) CXO (ユーザー体験の責任者)

      • 組織制度の改善
      • マネジメント
      • 社員のモチベーションマネジメント
      • 業務フローの改善 (業務アプリの作成を含む)
      • 求職者向けアプリの設計・開発
      • 提携企業向けアプリの設計・開発
    • OSS活動


概要

Elmのアプリケーションフレームワーク The Elm Architecture (TEA) の入門として、すご〜くシンプルなゲームを作ってみましょう。
TEAはUX設計と親和性がめちゃんこ高いよ!

ただし、ちゃんとTEAを身につけるならElm guideを読みましょう!


シナリオ

「シナリオ」はUX設計をする上で最も大切なテクニックの1つです。
ユーザーの気持ちを妄想して、ユーザーの行動を未来予知して、事前に文章に列挙します。

「まずシナリオを書いてみましょう」と助言しても、「そんなの素人がやるやつじゃん」と、いきなり実装を初めてクソアプリを作る人たちがいっぱいでヤギさんは悲しいです。

いい製品とは、粘り強く根気強く試行錯誤を何度も重ねたもの
じゃあ、先に妄想の中で試行錯誤した方が短期間でいい製品ができるじゃん

では、実際に「さくらちゃん と じゃんけん」ゲームのシナリオを作っていきましょう!

完成版はこちら


シナリオ1 -- ヤギ思いのユーザー

  • このスタイルの文: アプリ側の表示
  • 地の文: ユーザーの思考・行動
  1. ヤギの写真とともに「じゃ〜んけ〜ん」と画面に表示され、「ぐー」「ちょき」「ぱー」のボタンが並んでいる
  2. 「じゃんけんゲームってことかな?」
  3. とりあえず「ぐー」を押してみた
  4. 「かち!」と表示され、ヤギが悔しがった顔をしながら「ヤギさんにも ようしゃが ないね!」と言ってくる
  5. 「そっか、ヤギさんだからチョキしか出せないのか。悪いことしちゃったな」
  6. 「もういっかい」のボタンがあることに気づく
  7. 「あ、もしかしてもう一回じゃんけんできる? 今度は負けてあげよう」
  8. 「もういっかい」のボタンをクリックする
  9. 最初の画面が再び表示される
  10. 「あ、やっぱりもういっかいできるみたいだ」
  11. 「ぱー」を押してみた
  12. 「まけ!」と表示され、ヤギが調子に乗った顔しながら「ほもさぴえんすって よわい いきものだね!」と言ってくる
  13. 「なんかうざいけど、でも喜んでくれてよかった。」

シナリオ2 -- 察しが悪いユーザー

UX設計は、「まさかそんなことするか?!」というところまで妄想するといいUXができる。

  1. ヤギの写真とともに「じゃ〜んけ〜ん」と画面に表示され、「ぐー」「ちょき」「ぱー」のボタンが並んでいる
  2. 「え、なによくわからん」
  3. とりあえずヤギの写真をクリックしてみた
  4. 荒ぶった感じのヤギが「やぎさんじゃなくて ぼたんをおしてね!」って言ってくる
  5. 「あ、ボタンか。なるほど」
  6. とりあえず「ぐー」を押してみた
  7. (以下略)

画面設計

シナリオをもとに、画面と状態を整理します。

基本画面構成

以下のエリアから構成されます。

  • ヤギ: ヤギの写真が表示される
  • メインメッセージ: ユーザーへのメインのメッセージを表示する
  • サブメッセージ: ユーザーへの補助的なメッセージを表示する
  • ボタンエリア: ボタンを表示する

初期状態

  • [画面]
    • ヤギ: ノーマルやぎ
    • メインメッセージ: 「じゃ〜んけ〜ん」
    • サブメッセージ: 「すきな ぼたんを おしてね!」
    • ボタンエリア:
      • 「ぐー」
      • 「ちょき」
      • 「ぱー」
  • [ユーザー] 「なにこれ、どれかボタン押せばいいの?」
    • [イベント1] ぐーを押した
    • [イベント2] ちょきを押した
    • [イベント3] ぱーを押した
    • [イベント4] ヤギを押した

イベント1

  • [画面]
    • ヤギ: 悔しそうなやぎ
    • メインメッセージ: 「かち!」
    • サブメッセージ: 「ヤギさんにも ようしゃが ないね!」
    • ボタンエリア
      • 「もういっかい」
  • [ユーザー]
    • [イベント5] 「もういっかい」を押した

イベント2

  • [画面]
    • ヤギ: 絶妙な表情のヤギ
    • メインメッセージ: 「あいこ!」
    • サブメッセージ: 「きが あうね!」
    • ボタンエリア
      • 「もういっかい」
  • [ユーザー]
    • [イベント5] 「もういっかい」を押した

イベント3

  • [画面]
    • ヤギ: 嬉しいヤギ
    • メインメッセージ: 「まけ!」
    • サブメッセージ: 「ほもさぴえんすって よわい いきものだね!」
    • ボタンエリア
      • 「もういっかい」
  • [ユーザー]
    • [イベント5] 「もういっかい」を押した

イベント4

  • [画面]
    • ヤギ: 荒ぶるやぎ
    • メインメッセージ: 「じゃ〜んけ〜ん」
    • サブメッセージ: 「やぎさんじゃなくて ぼたんをおしてね!」
    • ボタンエリア:
      • 「ぐー」
      • 「ちょき」
      • 「ぱー」
  • [ユーザー] 「なにこれ、どれかボタン押せばいいの?」
    • [イベント1] ぐーを押した
    • [イベント2] ちょきを押した
    • [イベント3] ぱーを押した

イベント5

初期状態と同じ

実装する

TEAは画面設計と親和性が高いので簡単に実装できます。

ポイントとなる概念は4つ:

  • モデル
  • 初期状態
  • メッセージ/アップデート
  • ビュー

モデル

アプリケーションの状態を、Elmの値で表現してみましょう。
これをTEAでは「モデル」と言います。

基本的には「画面設計」で列挙した各イベント後の状態をカスタム型で表現するだけです。

-- あとで拡張できるようにレコード型にしておくと便利!
type alias Model =
    { phase : Phase
    }


type Phase
    -- 初期状態/イベント5
    = Start
    -- イベント1
    | AfterWin
    -- イベント2
    | AfterDraw
    -- イベント3
    | AfterLose
    -- イベント4
    | AfterClickingGoat

初期状態

初期状態のモデルを定義しましょう。

init : Model
init =
    { phase = Start
    }

そのまんま


メッセージ/アップデート

イベントハンドリングのことです。

シナリオのイベントに名前をつけて、それぞれどのようにモデルを更新するか定義しましょう。

-- メッセージ (イベント)
type Msg
    = PressGuu
    | PressChoki
    | PressPaa
    | ClickGoat
    | PressAgain


-- モデルの更新 (イベントが起きた時にどのようにモデルを更新するか)
update : Model -> Msg -> Model
update model msg =
    case msg of
        PressGuu ->
            { phase = AfterWin
            }
        PressChoki ->
        (中略)
        PressAgain ->
            { phase = Start
            }

ビュー

モデルをもとに実際の画面をつくってみましょう!

elm/htmlライブラリを使います。

import Html exposing (..)
import Html.Attributes exposing (..)


view : Model -> Html Msg
view model =
    {-
        <div>
            <img src="./img/goat/normal.jpg">
            ...
        </div>
    -}
    div
        []
        [ img
            [ src <|
                case model.phase of
                    Start ->
                        "./img/goat/normal.jpg"

                    (後略)
            ]
            []
        -- メインメッセージ
        , h1
            []
            [ text <|
                case model.phase of
                    Start ->
                        "じゃ〜んけ〜ん"

                    AfterWin ->
                        "かち!"

                    (中略)

                    AfterClickingGoat ->
                        "じゃ〜んけ〜ん"
            ]
        -- サブメッセージ
        , h2
            []
            [ text <|
                case model.phase of
                    Start ->
                        "すきな ぼたんを おしてね!"

                    (後略)
            ]
        -- ボタンエリア
        , if model.phase == Start || model.phase == AfterClickingGoat then
            div
                []
                {-
                    <button type="button">ぐー</button>
                -}
                [ button
                    [ type_ "button"
                    ]
                    [ text "ぐー"
                    ]
                , button
                    [ type_ "button"
                    ]
                    [ text "ちょき"
                    ]
                , button
                    [ type_ "button"
                    ]
                    [ text "ぱー"
                    ]
                ]

          else
            div
                []
                [ button
                    [ type_ "button"
                    ]
                    [ text "もういっかい"
                    ]
                ]
        ]

ビューをイベントと紐付ける

これだけでは、ユーザーのどんな操作がどのイベント(Msg)を引き起こすのかがわかりません。
Html.Events の関数を使ってイベントの紐付けを行います。

import Html.Events as Events

div
    []
    [ button
        [ type_ "button"
        -- ボタンを押した時のイベントを紐付ける
        , Events.onClick PressGuu
        ]
        [ text "ぐー"
        ]
    , ...
    , ...
    ]
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events as Events


view : Model -> Html Msg
view model =
    div
        []
        [ img
            [ src <|
                case model.phase of
                    Start ->
                        "./img/goat/normal.jpg"

                    AfterWin ->
                        "./img/goat/loser.jpg"

                    AfterDraw ->
                        "./img/goat/draw.jpg"

                    AfterLose ->
                        "./img/goat/winner.jpg"

                    AfterClickingGoat ->
                        "./img/goat/wild.jpg"
            , if model.phase == Start then
                Events.onClick ClickGoat

              else
                -- 何も属性がないときのテクニック
                class ""
            ]
            []
        , h1
            []
            [ text <|
                case model.phase of
                    Start ->
                        "じゃ〜んけ〜ん"

                    AfterWin ->
                        "かち!"

                    AfterDraw ->
                        "あいこ!"

                    AfterLose ->
                        "まけ!"

                    AfterClickingGoat ->
                        "じゃ〜んけ〜ん"
            ]
        , h2
            []
            [ text <|
                case model.phase of
                    Start ->
                        "すきな ぼたんを おしてね!"

                    AfterWin ->
                        "ヤギさんにも ようしゃが ないね!"

                    AfterDraw ->
                        "きが あうね!"

                    AfterLose ->
                        "ほもさぴえんすって よわい いきものだね!"

                    AfterClickingGoat ->
                        "やぎさんじゃなくて ぼたんをおしてね!"
            ]
        , if model.phase == Start || model.phase == AfterClickingGoat then
            div
                []
                [ button
                    [ type_ "button"
                    , Events.onClick PressGuu
                    ]
                    [ text "ぐー"
                    ]
                , button
                    [ type_ "button"
                    , Events.onClick PressChoki
                    ]
                    [ text "ちょき"
                    ]
                , button
                    [ type_ "button"
                    , Events.onClick PressPaa
                    ]
                    [ text "ぱー"
                    ]
                ]

          else
            div
                []
                [ button
                    [ type_ "button"
                    , Events.onClick PressAgain
                    ]
                    [ text "もういっかい"
                    ]
                ]
        ]

しあげ

elm/browserライブラリを使います。

import Browser

main =
  Browser.sandbox { init = init, update = update, view = view }

これをくわえるだけ!

完成したソース


リファクタリング

Elmは強い静的型とかしこいコンパイラによって守られているので、リファクタリングしてもデグレしにくいぞ!


関数を分けてみよう

ビューが長すぎて読めなぁあい!
別の関数に分けてみましょう。

view : Model -> Html Msg
view model =
    div
        []
        [ img
            [ src <| goatSrc model.phase
            , if model.phase == Start then
                Events.onClick ClickGoat

              else
                -- 何も属性がないときのテクニック
                class ""
            ]
            []
        , h1
            []
            [ text <| mainMessage model.phase
            ]
        , h2
            []
            [ text <| subMessage model.phase
            ]
        , buttons model.phase
        ]

めちゃんこ短い!見やすい!

別の場所に定義するだけ。

{-| ヤギ画像
-}
goatSrc : Phase -> String
goatSrc phase =
    case phase of
        Start ->
            "./img/goat/normal.jpg"

        AfterWin ->
            "./img/goat/loser.jpg"

        AfterDraw ->
            "./img/goat/draw.jpg"

        AfterLose ->
            "./img/goat/winner.jpg"

        AfterClickingGoat ->
            "./img/goat/wild.jpg"


{-| ボタンエリア
-}
buttons : Phase -> Html Msg
buttons phase =
    case phase of
        Start ->
            guuChokiParrButtons

        AfterClickingGoat ->
            guuChokiParrButtons

        _ ->
            againButtons


guuChokiParrButtons : Html Msg
guuChokiParrButtons =
    div
        []
        [ button
            [ type_ "button"
            , Events.onClick PressGuu
            ]
            [ text "ぐー"
            ]
        , button
            [ type_ "button"
            , Events.onClick PressChoki
            ]
            [ text "ちょき"
            ]
        , button
            [ type_ "button"
            , Events.onClick PressPaa
            ]
            [ text "ぱー"
            ]
        ]

シナリオの改善

失敗シナリオ

  1. ヤギの写真とともに「じゃ〜んけ〜ん」と画面に表示され、「ぐー」「ちょき」「ぱー」のボタンが並んでいる
  2. 「じゃんけんゲームってことかな?」
  3. とりあえず「ぐー」を押してみた
  4. 「かち!」と表示され、ヤギが悔しがった顔をしながら「ヤギさんにも ようしゃが ないね!」と言ってくる
  5. 「ん?? ヤギが負けて悔しがってるっぽいのに『かち!』っておかしくない?バグ?」

誰の視点で「かち!」と言っているかわかりにくいのが問題。
「あなたの かち!」に変えるだけでもいいけど、せっかくなのでユーザーの名前を聞いてみよう!


シナリオex1

  1. 「あなたの名前を教えてください」という案内とともに入力欄が表示されている
  2. 「なにこれ、個人情報とられそうで怖い |> _ <|」
  3. よく見たら「結果の表示に使います」と書いてある
  4. 「なんだ、じゃあ仮名でもいいのか」
  5. 「ウラジーミル・ヤギスキィ」と入力して「決定」ボタンを押した
  6. ヤギの写真とともに「じゃ〜んけ〜ん」と画面に表示され、「ぐー」「ちょき」「ぱー」のボタンが並んでいる
  7. 「じゃんけんゲームってことかな?」
  8. とりあえず「ぐー」を押してみた
  9. 「ウラジーミル・ヤギスキィさんの かち!」と表示され、ヤギが悔しがった顔をしながら「ヤギさんにも ようしゃが ないね!」と言ってくる
  10. (後略)

画面設計(改)

基本画面構成

設定画面

  • メインメッセージ: ユーザーへのメインのメッセージを表示する
  • サブメッセージ: ユーザーへの補助的なメッセージを表示する
  • 入力欄: 入力ボックスや送信ボタンを表示する

ゲーム画面

  • ヤギ: ヤギの写真が表示される
  • メインメッセージ: ユーザーへのメインのメッセージを表示する
  • サブメッセージ: ユーザーへの補助的なメッセージを表示する
  • ボタンエリア: ボタンを表示する

初期状態

  • [画面] 設定画面
    • メインメッセージ: 「あなたの名前を教えてください」
    • サブメッセージ: 「結果の表示に使います」
    • 入力欄:
      • 名前入力欄
      • 「決定」ボタン
  • [ユーザー] 「仮名にしよ」
    • [イベントex1] 名前(= name)を入力してフォーカスを外した

イベントex1

  • [画面] 設定画面
    • メインメッセージ: 「あなたの名前を教えてください」
    • サブメッセージ: 「結果の表示に使います」
    • 入力欄:
      • 名前入力欄
      • 「決定」ボタン
  • [ユーザー] 「これでよし!」
    • [イベントex2] 「決定」ボタンを押した

イベントex2

  • [画面] ゲーム画面
    • ヤギ: ノーマルやぎ
    • メインメッセージ: 「じゃ〜んけ〜ん」
    • サブメッセージ: 「すきな ぼたんを おしてね!」
    • ボタンエリア:
      • 「ぐー」
      • 「ちょき」
      • 「ぱー」
  • [ユーザー] 「なにこれ、どれかボタン押せばいいの?」
    • [イベント1] ぐーを押した
    • [イベント2] ちょきを押した
    • [イベント3] ぱーを押した
    • [イベント4] ヤギを押した

イベント1'

  • [画面]
    • ヤギ: 悔しそうなやぎ
    • メインメッセージ: 「nameさんの かち!」
    • サブメッセージ: 「ヤギさんにも ようしゃが ないね!」
    • ボタンエリア
      • 「もういっかい」
  • [ユーザー] 「ああ、ヤギさんだからチョキしかだせないのか」
  • [ユーザー] 「今度は負けてあげよう」
    • [イベント5] 「もういっかい」を押した

実装する(改)

モデル

フェーズを変更し、変数nameも保持できるようにしましょう!

-- あとで拡張できるようにレコード型にしておいてよかった!
type alias Model =
    { phase : Phase
    , name : String
    }


type Phase
    -- 初期状態 / イベントex1
    = Setting
    -- イベント5 / イベントex2
    | Start
    -- イベント1'
    | AfterWin
    -- イベント2'
    | AfterDraw
    -- イベント3'
    | AfterLose
    -- イベント4
    | AfterClickingGoat

初期状態

init : Model
init =
    { phase = Setting
    , name = ""
    }

メッセージ/アップデート

イベントが2つ追加されます。

type Msg
    = PressGuu
    | PressChoki
    | PressPaa
    | ClickGoat
    | PressAgain
    -- イベントex1
    | ChangeName String
    -- イベントex2
    | SubmitName


update : Msg -> Model -> Model
update msg model =
    case msg of
        -- モデルの関係ないフィールド(`name`)はそのままにしておきたいので
        -- レコードの更新構文が便利
        PressGuu ->
            { model
                | phase = AfterWin
            }

        PressChoki ->
            { model
                | phase = AfterDraw
            }

        PressPaa ->
            { model
                | phase = AfterLose
            }

        ClickGoat ->
            { model
                | phase = AfterClickingGoat
            }

        PressAgain ->
            { model
                | phase = Start
            }

        ChangeName str ->
            { model
                | name = str
            }

        SubmitName ->
            { model
                | phase = Start
            }

ビュー

view : Model -> Html Msg
view model =
    case model.phase of
        Setting ->
            settingView model

        _ ->
            gameView model

入力欄のイベントには elm-community/html-extraHtml.Events.Extra.onChange が便利。

import Html.Events.Extra as Events


settingView : Model -> Html Msg
settingView model =
    div
        []
        [ h1
            []
            [ text "あなたの名前を教えてください"
            ]
        , h2
            []
            [ text "結果の表示に使います"
            ]
        , input
            [ type_ "text"
            , value model.name
            , Events.onChange ChangeName
            ]
            []
        , button
            [ type_ "button"
            , Events.onClick SubmitName
            ]
            [ text "決定" ]
        ]

完成したソース
完成版


さらに学ぶには

公式ガイドの日本語訳 がとっても便利!


さくらちゃんにご飯をあげる
さくらちゃんをもっと見る
さくらちゃんグッズを見る
他の記事を見る
さくらちゃん

37
25
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
25