スマートデバイスプログラミング 第4回
Unityで開発してみよう(その3、表示編)

配布資料

課題

(1)画面にキャラクターを出して、アニメーションしつつ上下左右に動けるようにしてみましょう。
(2)(↑がうまくいったら)プレイヤーを追いかける敵を作ってみましょう
(3)(↑がうまくいったら)Prefabを使って、敵の数を増やしてみましょう。
(4)(余力があったら)ゲームをより改良するか、学んだ技術を使って何か面白いものを作ってみましょう。

作ったC#のプログラムを添付してください。
(プロジェクトを保存したフォルダのasset以下にあると思います。
コメント欄には、どこまでやったかと、(3)の逃げ回るゲームがうまく動いた人はハイスコア書いてください。

  1. 新規プロジェクトを作る(「2D」プロジェクトを選びましょう)
  2. Asset→Create→Folderを選んで、Asset内に新規フォルダを作成
  3. 名前が変えられるようになっているでresと名付けます。
  4. resをダブルクリックして開きます。
  5. DLしたサンプル画像ファイルを開いた場所(ProjectView内)にドラッグ&ドロップします。
  6. →画像がプロジェクトで使えるようになります。
  7. ※ここまでは先週と同じですが、先週より画像が1枚多いです。
  8. ※追加したCharacter.pngはキャラクターが16種類入っており、以後の操作で個別に使えるようにします。
  9. Project窓の「Character」を1回クリックして選択します
  10. Inspector窓の「Sprite Mode」の欄をSingleからMultipleに変更します。
  11. 4行下にある「Sprite Editor」のボタンをクリックします。
  12. →Sprite Editorが起動されます。
  13. エディタ内の左上にある「Slice」をクリックします
  14. →Sliceの設定windowが開きます
  15. TypeをGridに変更します
  16. PixelSizeのx,yの値を64から32にします。(画像内のキャラクターが32ドット感覚で並べてある為)
  17. 設定windowの一番下にある「Slice」ボタンを押します
  18. 画像が分割されます。
  19. (補足:空白で区切られた画像であれば、Typeを「Automatic」にしたままでも、画像がよしなに分割されます)
  20. (補足:分割された各画像をクリックすると、詳細設定の窓が開きます。切り抜く位置、中心点の位置、画像の名前などを変更できます)
  21. 分割できたら、窓の左上の「×」を押してSpriteEditorのwindowを閉じましょう
  22. 注意文が出て「Apply(変更を反映する)」するか「Revert(変更を破棄して戻る)」するか聞かれるので、Applyを選んでください。
  23. (補足:Editor内にもApplyボタンがあります。ボタンを押してから窓を閉じると、何も聞かれません)
  24. これで、16枚の画像が使えるようになっています。
  25. Characterのアイコンの隣にある右向き三角をクリックしてください。
  26. →Characterの中身が展開され、分割した各画像が表示されます。
  27. (補足:再度、三角(今度は左向きになってます)をクリックすると、閉じます)
  28. 開いた状態で、Character_0をScene窓にドラッグ&ドロップしてみましょう。
  29. 画像がシーン内に追加され、Hierarchy窓にもCharacter_0が追加されます。
  30. →分割した画像も、分割していない画像と同様に使用することができます。
  31. 次に、キャラクターにアニメーションを付けてみます。
  32. Window→Animationで、Animation編集Windowを開きます。
  33. AddPropertyを押します。
  34. ファイル名をNew Animation.animからChara_idle.animに変更して「保存」を押します。
  35. →Chara_idleが編集できるようになります。
  36. (選択肢が出ていたら)SpriteRenderer->Spriteを選択
  37. 0:00の真下にCharacter_0を、0.30の真下にCharacter_1を、1:00のましたに再度Character_0を、プロジェクト窓からドラッグ&ドロップして配置しましょう。
  38. idleのアニメーションが設定されました。
  39. (UnityのScene窓の上にある実行の△ボタンを押すと、キャラクターがアニメーションします)
  40. (確認できたら再度△を押して実行を停めましょう)
  41. Animation窓に戻って、Chara_idleをクリックして、Create New Clipを選択します。
  42. ファイル名をNew Animation.animからChara_move.animに変更して「保存」を押します。
  43. 0:00の真下にCharacter_1を、0.15の真下にCharacter_2を、0:30の真下にCharacter_1を、プロジェクト窓からドラッグ&ドロップして配置しましょう。
  44. moveのアニメーションが設定されました。
  45. Chara_moveをクリックして、再度Create New Clipを選択します。
  46. ファイル名をNew Animation.animからChara_damaged.animに変更して「保存」を押します。
  47. 0:00の真下にCharacter_3を、0:50の真下にCharacter_1を、1:00の真下にCharacter_3を、プロジェクト窓からドラッグ&ドロップして配置しましょう。
  48. damagedのアニメーションが設定されました。
  49. 3種類のアニメーションが作れたら、Animation窓は閉じましょう。
  50. resフォルダ内に、Character_0と書かれた、新しいアイコンが生成されています。
  51. Project窓に移り、Assetsをクリックして、resの中身が見えている状態からAssetsフォルダの中が見えている状態に変更しましょう。
  52. Assets→Create→C#Scriptを選んで、新規Scriptを作ります。名前はPlayerにしましょう。
  53. ダブルクリックして編集します。
  54. 
    //4行目を上書き(クラス名が変わっていたら上書き不要です
    public class Player : MonoBehaviour {
    
    //6行目(クラス始まってすぐ)に下記2行を追加
    Animator animator;
    float PlayerSpeed= 0.03f;
    
    //11行目(Startの中)に下記1行を追加
    animator = GetComponent <Animator> ();
    
    //16行目(Updateの中)に下記を追加
    
    //上下左右の移動
    if (Input.GetKey (KeyCode.RightArrow)) {
      transform.Translate (transform.right *PlayerSpeed);
    }
    if (Input.GetKey (KeyCode.LeftArrow)) {
      transform.Translate (transform.right *-PlayerSpeed);
    }
    if (Input.GetKey (KeyCode.UpArrow)) {
      transform.Translate (transform.up*PlayerSpeed);
    }
    if (Input.GetKey (KeyCode.DownArrow)) {
      transform.Translate (transform.up *-PlayerSpeed);
    }
    
    //上下左右を押した瞬間にアニメーション変更
    //左右を押した時は、scaleを使って画像を左右に反転させています
    if (Input.GetKeyDown (KeyCode.UpArrow)) {
      animator.Play ("Chara_move");
    }
    if (Input.GetKeyDown (KeyCode.DownArrow)) {
      animator.Play ("Chara_move");
    } 
    if (Input.GetKeyDown (KeyCode.RightArrow)) {
      animator.Play ("Chara_move");
      Vector3 s = transform.localScale;
      s.x = 1;
      transform.localScale = s;
    }
    if (Input.GetKeyDown (KeyCode.LeftArrow)) {
      animator.Play ("Chara_move");
      Vector3 s = transform.localScale;
      s.x = -1;
      transform.localScale = s;
    }
    
    //上下左右キーを離したらidleに戻る
    if (Input.GetKeyUp (KeyCode.UpArrow) ||
      Input.GetKeyUp (KeyCode.DownArrow) ||
      Input.GetKeyUp (KeyCode.RightArrow) ||
      Input.GetKeyUp (KeyCode.LeftArrow)) {
      animator.Play ("Chara_idle");
    }
    
    //スペースキーを押したら、damaged状態に
    if (Input.GetKeyDown(KeyCode.Space)) {
      animator.Play ("Chara_damaged");
    }
    
  55. 保存してUnityのWindowに戻ります。
  56. Hierarchy窓でCharacter_0を選択して、Player.C#をProject窓からInspector窓にドラッグ&ドロップ(AddComponent)します。
  57. 実行前に、カメラと座標の調整をします。
  58. Hierarchy窓でMain Cameraを選んで、Inspector窓でSizeを2にします。
  59. Hierarchy窓でCharacter_0を選んで、名前をPlayerに変更してあげます。
  60. Positionは(X:-1、Y:0)にします。
  61. プログラムを実行してみましょう。
  62. 上下左右でキャラが動いて、スペースでやられアニメが再生されたら成功です。
  1. 敵を作ってみましょう。
  2. Hierarchy窓でPlayerをCopy、ペーストします。
  3. 名前がPlayer 1になっているので、Enemyにrenameしましょう。
  4. Inspector窓で、Posionをx:0,y:0にしましょう。
  5. Assets→Create→C#Scriptを選んで新規scriptを作り、名前をEnemyにします。
  6. ダブルクリックしてソースを編集しましょう。
  7. 
    //4行目を上書き(クラス名が変わっていたら上書き不要です
    public class Enemy : MonoBehaviour {
    
    //6行目(クラス始まってすぐ)に下記2行を追加
    Animator animator;
    GameObject player;
    float EnemySpeed= 0.01f;
    
    //11行目(Startの中)に下記3行を追加
    animator = GetComponent <Animator> ();
    animator.Play ("Chara_move");
    player = GameObject.Find("Player");
    
    //17行目(Updateの中)に下記を追加
    float px, py, ex, ey;
    
    px = player.transform.localPosition.x;
    py = player.transform.localPosition.y;
    ex = transform.localPosition.x;
    ey = transform.localPosition.y;
    
    //playerの座標と自分の座標を比較して、より近づくように動く
    if (ex < px) {
      transform.Translate (transform.right * EnemySpeed);
      Vector3 s =transform.localScale;
      s.x = 1;
      transform.localScale = s;
    } else {
      transform.Translate (transform.right * -EnemySpeed);
      Vector3 s =transform.localScale;
      s.x = -1;
      transform.localScale = s;
    }
    
    if (ey < py) {
      transform.Translate (transform.up * EnemySpeed);
    } else {
      transform.Translate (transform.up * -EnemySpeed);
    }
    
  8. 作ったスクリプトをEnemyにAddComponentしましょう。
  9. EnemyについているPlayerのスクリプトは削除しましょう。Inspector窓でスクリプトの右側の歯車をクリックして「Remove Component」を選択すると削除できます。
  10. 実行してみましょう。敵がひたすら追いかけて来ます。
  11. あたり判定を付けましょう。
  12. PlayerにAddComponentでPhisics2D→Rigidbody2dとPhisics2D→Circle Collider2Dを追加します。
  13. Rigidbody2DのGravity Scaleを0にします。(重力の影響を消す)
  14. Enemyにも同様の処理を行います(Rigidbody2dとCircleColliderの追加、Gravityを0に)
  15. Player.csに、敵と当たった時の処理を追加しましょう。
  16. 
    //末尾から2行目、Classが終わる}の内側に追記
    void OnCollisionEnter2D(Collision2D collision){
      if (collision.gameObject.name == "Enemy") {
        animator.Play ("Chara_damaged");
      }
    }
    
  17. 敵と当たったらアニメーションが変わります。
  18. 次に、Itemを設置してみましょう。
  19. Character_4の画像をSceneViewにドラッグ&ドロップして、名前Itemに変更します。(ballの画像でも良いです)
  20. Inspector窓で、Posionをx:1,y:0にしましょう。
  21. 衝突判定など追加します。(PlayerやEnemyと同様、Rigidbody2dとCircleColliderの追加、Gravityを0に)
  22. Enemy.csに、アイテムと当たった時の処理を追加しましょう。
  23. 
    //末尾から2行目、Classが終わる}の内側に追記
    void OnCollisionEnter2D(Collision2D collision){
      if (collision.gameObject.name == "Item") {
        collision.gameObject.transform.position = new Vector3 ((Random.value - 0.5f) * 1.5f, (Random.value -0.5f) * 1.5f, 0);
        transform.position = new Vector3 ((Random.Range(0,2)*2-1)*2.0f, (Random.Range(0,2)*2-1)*2.0f, 0);
        EnemySpeed += 0.001f;
      }
    }
    
  24. 敵とアイテムを再配置しています。かつ、敵のスピードが少しだけ上がります
  25. 敵がアイテムを取ると敵が死ぬ→別の場所に生成、アイテムも別の場所に生成、というニュアンスです。
  26. 敵を誘導してアイテムに触れさせる遊びになっています。
  27. うまくいったら次の課題に進みましょう。
  1. 敵を複数出すために、プレハブと言う仕組みを使います。
  2. Assets→Create→Prefabを選んで、新しいPrefabを作成します。
  3. HierarchyViewのEnemyを、NewPrefabにドラッグ&ドロップしましょう。
  4. エネミーのアイコンがAsset窓に表示されます。名前をEnemyPrefabに変えておきましょう
  5. 画像と同様、Sceneにドラッグ&ドロップすることで、シーンに敵を追加できますが、プログラムから敵を生成させることもできるようになります。
  6. GameObject→CreateEmptyで空のゲームオブジェクトを生成
  7. Assets→Create→C#Scriptで新規スクリプトを生成
  8. 名前はGameManagerと名付け、ダブルクリックで編集します。
  9. 
    //3行目に追加
    using UnityEngine.UI;
    
    //class名が違っていたら上書き
    public class GameManager : MonoBehaviour {
    
    //class入った直後
    public Text t;
    int score=0;
    float time=30.0f;
    public GameObject EnemyPrefab;
    const int ENEMY_NUM = 3;
    GameObject[] enemy = new GameObject[ENEMY_NUM];
    
    //Start()の中に下記を足す(for分の<は半角に直すこと)
    for(int i=0;i < ENEMY_NUM;i++){
      enemy[i]= (GameObject) Instantiate (EnemyPrefab);
      Vector3 p= enemy[i].transform.position;
      p.x = (Random.Range (0,2)*2 -1 )*2;
      p.y = (Random.Range (0,2)*2 -1 )*2;
      enemy[i].transform.position = p;
    }
    
    //Update()の中に下記を足す
    time -= Time.deltaTime;
    if (time >= 0.0f) {
      t.text = string.Format ("score:{0} time:{1:f1}", score, time);
    } else {
      t.text = string.Format ("score:{0} finished!", score);
    }
    
    
    //ファイル末尾、下から2行目(classが終わる()の内側)に下記を足す
    public void AddScore(int s){
      if (time >= 0.0f) {
        score += s;
      }
    }
    
  10. 保存してgameObjecにAddComponentします。
  11. 変数欄のEnemyPrefabはAsset窓からドラッグ&ドロップして埋めます
  12. 変数欄のTは新規でUI→Textを作って埋めます。
  13. Textの座標をx:0,y:0にするのを忘れずに。
  14. Player.csに敵にあたった時にスコア減らす処理を足します。
  15. 
    // animation.Play(ダメージアニメ)の次の行に足します。
    GameObject go = GameObject.Find ("GameObject");
    if(go != null){
      GameManager gm = go.GetComponent<GameManager>();
      gm.AddScore(-30);
    }
    
  16. Enemy.csにアイテムにあたった時にスコア増やす処理を足します。
  17. 
    // EnemySpeed+=0.001fの次の行に足します。
    GameObject go = GameObject.Find ("GameObject");
    if(go != null){
      GameManager gm = go.GetComponent<GameManager>();
      gm.AddScore(10);
    }
    
  18. 保存したら実行してみましょう。うまく動けば課題終了です。
  19. Sceneに名前を付けて保存しましょう。EscapeGameなど。

補足

余力がある人の改良の案

・各オブジェクトのInspectorのパラメータを変更してみる
・スクリプトの内容を変えてみる
・画像の内容を変えてみる
など、色々やってみてください。
SA高橋君が書いてくれたUnityコラム。情報が追加されております。余力がある人もない人も、是非読んでみてください。

Tips

ProjectViewでGameManagerを選んで右クリック→ShowInExprolerで、エクスプローラが開きます(提出時に使ってください)
SceneViewにてオブジェクトを選んでいる際、Alt+右クリックしながらタッチパッドを左右にスライドすることで、表示範囲を拡縮できます。(レイアウトを調整する際使ってください)

Unityのマニュアルについて

 以下に一式あります。
 http://docs.unity3d.com/ja/current/Manual/
 使いこなしたい人は色々見てみると良いと思います。

よくある質問

スクリプトを更新したのに、InspectorViewの変数が増えない

プログラムが間違っていてビルドエラーが出ている可能性が高いです。

エディタ上でbuildしてみましょう。

{}の対応が間違っていないか、全角スペースが入っているかなど、確認してみましょう

ビルドエラーが出ていないのに、スクリプトをobjectに追加できない

classの大文字と小文字が違っている可能性があります。

エディタのbuildエラーが出ませんが、Unityでは読み込めない、という状況が確認されています。

保存して再度開いたらブロックやボールが消えた

unityの仕様(不具合)かなと思います。

BG(背景画像)の描画優先度を下げることで解決します。

BGのInspectorViewでOrder in Layerを-1にしてください

(テキストの手順にも追記しておきました)