はきだめ

プログラミングのこととか色々

3Dアクションゲーム開発記

今回の内容は昔作った3Dアクションゲームの開発記です。なんでこの記事を書こうと思ったのかというと、先日1年ぶりにunityに触ったときに3Dの知識が吹っ飛んでいて苦労したので、またすぐに忘れないように開発の記録をさらっと自分用にまとめようと思ったからです。

ちなみに昔作ったのはこちらのゲームです。
https://unityroom.com/games/monster-island

上記で作ったとか言っちゃってますが、6~7割ぐらい参考にした本の内容と一緒です(unity初心者なので勘弁してください…)。参考にした本というのはコチラです。

Unityゲーム開発 オンライン3Dアクションゲームの作り方

Unityゲーム開発 オンライン3Dアクションゲームの作り方

ゲーム内のキャラクターのモデルとかアニメーションとかサウンドとかも本を買ったときについてきたものです。自分で作ったのはフィールドだけですが、これはterrainで山作って草生やしてテクスチャ塗り塗りするだけなので結構簡単です。そんな感じで基本的に参考書に沿って作った上で少し改造しました。
ここから先は開発の流れについて書いていくつもりですが、ゲーム作る上で調べたこととか、重要そうなこととか、試行錯誤したところとかをメインにメモっていこうと思います。

3Dゲームを作る上で必要な知識

Tag(タグ)

gameobjectの管理をしやすくするために使う。「プレイヤー」「敵キャラ」のようにグループ分けするのに必須。

Layer(レイヤー)

複数のカメラを設置したゲームでカメラごとに映るものを分けたり、衝突判定を行う組み合わせを決めたりするときに使う。

ライトの種類
  • Direction Light…平行光源

  • Point Light…点光源。360度に光を発する。

  • Spotlight…スポットライト。円錐状に光を照らす。

  • Area Light…面光源

影の付け方

通常はNo Shadowsとなっているが、Hard Shadowsにすれば影を付けられる。場所はlightの設定と同じ場所。ただし、影は処理負荷が比較的高いらしい。

カメラのインスペクタビュー
  • Backgroud – 背景色を指定
  • Culling Mask – このカメラで描画するLayerにチェックを入れると、チェックしたLayerだけがカメラに映る。
Gameビュー

f:id:kurome-stdio:20170604131847p:plain

左からゲームの再生、一時停止、ステップ実行。ステップ実行をすると処理が1フレーム進む。

  • Stats(スタッツ)

f:id:kurome-stdio:20170705222612p:plain

3Dゲームなら30~60fpsは維持すべきラインらしい。
あとTris(ポリゴン)よりもVerts(頂点数)の方が重要(Vertsが多いと重い?)らしい。詳しくはここらへんの記事が参考になりそう。

d.hatena.ne.jp

プレハブ

オブジェクトの大元みたいなイメージ。プレハブを一つ作ることでその複製を簡単に作ることができ、またプレハブから複製されたインスタンスはプレハブの設定が変わったときにインスタンス(複製)の設定も同時に全て変えることが出来る。
作り方としてはPrefabというフォルダーにシーン内の配置されているオブジェクトをそのまま放り込むことで生成することが出来る。オブジェクトの設定をプレハブの設定に戻したいときはInspecterビューの上部にあるRevertボタンを押す。逆にシーンに配置したオブジェクトの設定をプレハブに反映させたい場合はApplyボタンを押す。

AnimationType

f:id:kurome-stdio:20170705234633p:plain

  • Humanoid…人用のアニメーション
  • Generic…人以外に用いるアニメーション。
  • None…アニメーションを使わないモデルデータ用
  • Legacy…Unity3.x以前のアニメーションシステムとの互換性のために残されている設定
Vector3の便利な関数
  • Vector3.Dot…ベクトルAとベクトルBの内積を返す。
  • Vector3.Lerp…ある点からある点の直線線形の割合を求める。
  • Vector3.normalized…ベクトルの正規化
  • Vector3.Distance…2点間の距離を返す

Lerpなどの補完系に関してはこれらの記事を参照したほうが良さそう

www.blueraja.com

gametukurikata.com

オイラー角とクォータニオン

オイラーオイラー角は3つの角度の値を X、Y、Z 軸に順に当てはめて回転を表す簡易な方法。
クォータニオンクォータニオン成分 (x, y, z, w) の4つを用いてオブジェクトを回転させる方法。

オブジェクトを回転させるときにはx軸、y軸、z軸の3つをインスペクタ上でいじったりすると思いますが、実は内部ではクォータニオンに変換して回転させてます。深い話は一次変換とか行列の話になってしまいますが基本的にクォータニオンに関しては、知らなくてもゲームが作れるので問題ないと思います。この辺の話はこれらの動画がものすごく丁寧に解説しているのでそちらを参照にするのがいいと思います。特にクォータニオン完全マスターはスライドも分かりやすいし、数学の勉強にもなります。

【Unity道場 博多スペシャル 2017】クォータニオン完全マスター - YouTube

【MMD】物理演算の計算順序について【お詫びと訂正】 by すず その他/動画 - ニコニコ動画

Time.deltaTime
  • Time.deltaTime…前回のUpdate関数が呼び出されてからの経過時間が格納されている(単位は秒)
    例えば1秒間に1m動かしたいと思ったときに
function Update () {
    transform.position.x += 1;
}

って書いてしまうとUpdate関数が呼ばれる頻度はパソコンによって変化するので上手くいかないと思います。スペックが高いパソコンだと120フレームぐらい読み込まれますが、逆にしょぼいパソコンだと30フレームぐらいしか呼ばれないため1秒間に30〜120mぐらい動いてしまうという問題が起きます。そこで1秒間に動いて欲しい距離にTime.deltaTimeを掛けてやると、Time.deltaTimeの中身はUpdate関数が呼び出されてからの経過時間なのでどのパソコンでも1秒間に1m動かすことが可能になります。

function Update () {
    transform.position.x += 2 + Time.deltaTime;
}

この場合だと例えばUpdate関数が1秒間に60回呼び出されるとしたら(60/60)*2で2m進むという感じです。

キャラクターの移動

まず初めにやったのがキャラクターを移動させるプログラムを書くことですが、その前に地形(フィールド)を作る必要があります。こちらは適当に自作しました。

キャラクターを移動させるには

インポートしてきた3DモデルにCharacter Controllerを付けます。Character Controllerは人やモンスターなどのキャラクターを移動させるのに特化したコンポーネントのことで、使い方としてはMove関数に移動量をVector3で渡してあげるだけです。
一番シンプルな例(WASD移動の場合)

 public float speed;
    public const float gravity = 9.8f;
    public CharacterController controller;

    void Update () {
        float x = Input.GetAxis ("Horizontal");
        float z = Input.GetAxis ("Vertical");
        Vector3 v = new Vector3(x * speed * Time.deltaTime, -gravity * Time.deltaTime, z * speed * Time.deltaTime);
        controller.Move (v);
    }

上のコードではキャラクターを地面に設置させるために重力を加算してます。RigitBodyを付けてaddforceで動かすだけならこの処理は要りませんが、CharacterControllerを使って動かす場合はこの処理が必要です。重力を加える処理が必要になりますが、基本的にCharacterContorollerを使ったほうが簡単なので今回はこちらを使いました。

移動を滑らかにするには

上のコードだと移動が等速直線運動になってしまうためあまり自然な動きにはなりません。そこでゆっくり動き出して、ゆっくり停止させるために速度を補間する必要があります。例えば1/3秒で目的の速度に達するようにするには、

t = 1フレームの時間×3
ただし、t>1のときはt=1として、
移動速度ベクトル = (方向×移動速度)×t + 現在の移動速度ベクトル×(1 - t)

とすれば滑らかに移動させることが出来るようになります。というわけでさっきのコードを少し変えてスムーズに移動できるようにしました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public float speed;
    public const float gravity = 9.8f;
    Vector3 old_velocity = new Vector3(0,0,0);

    public CharacterController controller;

    void Start () {
    }

    void Update () {
        float x = Input.GetAxis ("Horizontal");
        float z = Input.GetAxis ("Vertical");
        Vector3 velocity = new Vector3(x * speed * Time.deltaTime, -gravity * Time.deltaTime, z * speed * Time.deltaTime);
        velocity = Vector3.Lerp(old_velocity, velocity, Mathf.Min(Time.deltaTime * 5.0f, 1.0f));
        controller.Move (velocity);
        old_velocity = velocity;
    }
}

動き始めてから0.2秒後に最大の速度になるようにしました(Time.deltaTime * 5.0fの部分)。さらにゆっくり減速させたい場合はこの値を小さめ、逆にクイックな反応にしたい場合は大きめな値に設定すればいいと思います。またMin関数で補間係数が1.0以上にならないように制限しています。

補間前

f:id:kurome-stdio:20170604163748g:plain

補間後

f:id:kurome-stdio:20170604164412g:plain

補間後のほうがより自然な動きになっていると思います。

さらにゆっくり減速させた場合(5.0f→1.0f)

f:id:kurome-stdio:20170604164622g:plain

こちらはさらにゆっくり減速させた場合です。ここまでやると滑っているように見えます。
本だとクリックした場所にキャラクターを動かすようにしていますが、普通にWASDで動かせるようにしたかったのでここは弄りました。あとクリックした場所にキャラクターを動かすとなるとクリックした場所の座標をワールド座標に変換するという処理が必要でなおかつベクトルの計算も結構複雑であまり自分が理解していないということもあってWASD移動にしました。

カメラの移動

次にカメラがキャラクターを追随するようにします。ここは本のスクリプトをそのまま使いました。

考え方としては
カメラの位置=追随対象の位置+ずらし位置
として、Lookatで追跡対象の方向を向きながら追跡対象が動いたときにカメラも一緒に移動させるって感じです。

ここでついでにカメラの向きを正面にしてキャラクターが移動出来るようにしました。コードは以下のようになりました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMove : MonoBehaviour {
    public CharacterController controller;
    public float speed;
    public const float gravity = 9.8f;

    Vector3 move_direction; 
    Vector3 old_velocity = new Vector3(0,0,0);

        Transform cam_trans; 

    void Update () {

        //// カメラの方向を正面にして動かせるようにする
        move_direction = (cam_trans.transform.right * Input.GetAxis("Horizontal")) + (cam_trans.transform.forward * Input.GetAxis("Vertical"));
        move_direction *= speed * Time.deltaTime;

        //重力
        move_direction = move_direction  + new Vector3(0, -gravity * Time.deltaTime, 0);

        //Lerpで滑らかに移動させる
        move_direction = Vector3.Lerp(old_velocity, move_direction, Mathf.Min(Time.deltaTime * 5.0f, 1.0f));

        //移動
        controller.Move (move_direction);
        old_velocity = move_direction;
    }
}

アニメーション

次にキャラクターにアニメションを付けていきます。アニメーションに関してはデータさえあればあまりプログラミングせずに実装することが出来るのでunityは本当に凄いです。

敵のアニメーター
f:id:kurome-stdio:20170705234637p:plain

具体的にはAnimatorというものを作ってその中に移動のアニメーションとか攻撃のアニメーションとかを放り込みます。矢印はアニメーションの繊維条件です。ここで注意しておく必要があるのはちゃんとモデルデータとAnimetionTypeが一致しているかどうかです。ここで人のようなキャラクターモデルにAnimationTypeがGenericのアニメーションを結びつけて再生すると人ならざる動きをするので注意が必要です。当時はここを見落としてだいぶ時間をくった記憶があります。他にもすぐにアニメーションを遷移させる場合にはHas exit timeのチェックを外す必要があります。Has exit timeにチェックしたままだと遷移条件を満たしていても、再生しているアニメーションが再生し終わるまで遷移しません。
このゲームではキャラクターの移動速度が秒速0.4以上になったときに走るアニメーションに遷移するようにしています。

f:id:kurome-stdio:20170706215914p:plain

最初はWASDキーまたは矢印キーが押されたときに走るアニメーションに遷移するようにしていましたが、それだとキーを離した瞬間に止まったときのアニメーションになってしまい少し不自然だったのでここは本に書いてあるように前回のUpdateからの移動量を計算してspeedパラーメータに値を渡して遷移するかしないかを区別しています。

Vector3 delta_position = transform.position - prePosition;
animator.SetFloat("Speed", delta_position.magnitude / Time.deltaTime);

攻撃の実装

次に行ったのがアクションゲームのメインとなる攻撃の実装です。オブジェクトとオブジェクトの接触の感知には基本的にはコライダーを使います。具体的には攻撃側(武器など)と攻撃を受けるキャラクター(胴体部分など)にコライダーを取り付けて、接触したときにライフを減らす関数を呼び出すということをします。また接触したときに、物理的な作用をさせたくない場合にはIs Triggerにチェックする必要があります。またコライダーを取り付けたオブジェクトを移動させるときはRigitbodyコンポーネントをつける必要があります。ここでRigitbodyを付けないと物理エンジンがオブジェクトが移動しないものと認識するため衝突計算がおかしくなります。またRigitbodyは処理が重いのでむやみにつけないほうがいいみたいです。衝突判定だけを行いたい場合はRigitbodyのIs Kinematicにもチェックを行う必要があります。つまり物理的な処理をさせずにオブジェクトの衝突判定をするには

攻撃側にコライダーを取り付ける
↓
攻撃を受ける側にコライダーを取り付ける
↓
コライダーのIs Triggerにチェック
↓
RigitbodyのIs Kinematicにチェック

という流れになります。衝突判定を行って接触を感知したときに関数が呼ばれるようになったのであとはそれぞれのコライダーがある部分にスクリプトを埋め込んでスクリプト側で処理していきます。

攻撃側

//接触すると呼ばれる
void OnTriggerEnter(Collider other){
    //攻撃を受ける側に情報(攻撃力)を渡す
    other.SendMessage("Damage",GetAttackInfo());
}

攻撃を受ける側

void Damage(AttackArea.AttackInfo attackInfo){
    //渡された攻撃力などの情報をそのまま親のオブジェクトに渡す(Damage関数を呼ぶ)
    transform.parent.SendMessage ("Damage",attackInfo);
}

親のゲームオブジェクト

//ライフから攻撃力分の値を引く
void Damage(AttackArea.AttackInfo attackInfo){
    status.HP -= attackInfo.attackPower;
}

本では攻撃を受ける側のスクリプトではtransform.rootで参照していますが、下の写真のようにEnemyという空のフォルダを作ってそこに敵のプレハブを放り込んでヒエラルキーが見やすくなるように変更したときにtransform.rootだとEnemyというフォルダが参照されてエラーになるということがありました。なのでtransform.parent(コライダーの親が欲しいオブジェクト)でDamege関数を記述しているオブジェクトを参照することが出来ました。

階層
f:id:kurome-stdio:20170706230056p:plain

敵のAIの実装

最後にAIの実装です。敵のアルゴリスムは以下のようになっています。

待機
↓
徘徊
↓(もし半径5m以内にプレイヤーが居たら)
追跡開始
↓(もし半径2m以内にプレイヤが居たら)
攻撃
↓
待機

半径5m以内にプレイヤーが居るかどうかの判定はコライダーの接触判定を使います。さらにプレイヤーやどうかの判断はtagで識別してプレイヤーだった場合はenemyCtrlスクリプトのSetAttackTarget関数を呼び出して変数に位置情報を保持させます。

void OnTriggerStay( Collider other ){
    // Playerタグをターゲットにする
    if( other.tag == "Player" ){
        //プレイヤーの位置情報を渡す。
        enemyCtrl.SetAttackTarget( other.transform ); 
    }
}

追跡と待機は主に乱数で決めています。

Vector2 randomValue = Random.insideUnitCircle * 5;
Vector3 destinationPosition = transform.position + new Vector3(randomValue.x, 0.0f, randomValue.y)

Random.insideUnitCircle関数で半径1の円の中の点をランダムに選んでもらい、5倍して現在居る位置にrandomValueの値を足します。ここで高さは要らないので切り捨てます。

waitTime = Random.Range(2.0f, 4.0f);

待機時間もRandom.Range関数を用いて(2秒から4秒)バラバラになるようにしています。追跡に関しては本では目的地とオブジェクトのベクトルの差を求めて正規化し方向だけを取り出してから速さを掛けてCharacter.Moveで動かすということをしていますがナビゲーション機能を使うともっと簡単に追跡するプログラムを掛けそうなのでいずれナビゲーション機能を使ってここの部分を書き換えたいと思います。

敵キャラをUnityのナビゲーション機能を使って移動させる | Unityを使った3Dゲームの作り方(かめくめ)

付け足したところ

ここまででアクションゲームの骨組みは大体完成です。エフェクトと音の鳴らし方などは他にもいろんなサイトでやり方が載っているので省略します。大きく弄ったのはプレイヤーの移動と地形とかなのであんまり追加した要素はないのですが、覚えている限りの試行錯誤したところやボツになった案などをメモっておこうと思います。

ライフバーの表示

まず初めに敵の上部にライフバーを設置したことです。

ライフバー
f:id:kurome-stdio:20170707231811p:plain

といってもこちらのサイトを参考にしたので1から自分で考えたわけではないです…。

Unityで敵キャラクターのHPを頭上に表示するUIを作成 | Unityを使った3Dゲームの作り方(かめくめ)

こちらのサイトではJavaScriptで書かれていますが、それをC#に変換して自分のゲームでも動くように書き換えたらこんな感じになりました。

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class SetHp : MonoBehaviour {

    private UnityEngine.UI.Slider slider;
    private CharacterStatus status;
    private int count;
    private int countNow;

    void Start () {
        status = GetComponentInParent<CharacterStatus> ();
        slider = GetComponent<UnityEngine.UI.Slider>();
        slider.value = slider.maxValue;
        count = status.MaxHP;
    }
    
    void Update () {
        countNow = status.HP;

        if(countNow != count) {
            slider.value = (countNow * slider.maxValue) / status.MaxHP;
            count = countNow;
        }
  }
}

基本的に下の写真みたいなcanvasを作って上のスクリプトをsliderに貼れば動くんじゃなかと思います。

f:id:kurome-stdio:20170707233636p:plain

ただこのままだと、ライフバーは常にカメラの方向いているわけではないので横からみると何も見えないという状況に陥りました。なので下記の記述をすることでライフバーをカメラの方向に向かせることが出来ました。

transform.rotation = Camera.main.transform.rotation;
CharacterController同士が重なるバグの修正(?)

こちらは新たな機能を追加した訳ではありませんが、ステージ内の少し高いところから敵のキャラクターに向かってキャラクターを移動させるとそのまま敵のキャラクターの上に乗っかることが出来てしまうというバグが起きました。具体的には以下の記事のような現象です。

【Unity Tips】 CharacterControllerでコリジョンを滑り落ちよう: Karasuのアプリ奮闘記

恐らくステージに坂道が多かったのが原因だと思います。対応策としてはキャラクターの下向きにRayを飛ばし少しでも傾斜があったら滑り落とすという処理を加えました。ただどのように滑り落とせばいいか分からなかったため、とりあえずx軸方向とz軸方向にそれぞれ0.5ずつずらすことにしました。多分もっといい方法があると思いますが、スクリプトは以下のようになりました。

using UnityEngine;
using System.Collections;

public class slide : MonoBehaviour {


    private PlayerMove Move;
    private Vector3 slide_direction;

    public float slide_magnitude = 0.5f;

    const float gravity = 9.8f;
    bool isSliding = false;

    RaycastHit slideHit;
    CharacterController chara;

    void Start () {
        Move = GetComponent<PlayerMove>();
        chara = GetComponent<CharacterController>(); 
    }

    void Update () {
        //下に敵が居て傾斜が0度以上だったら
        if (Physics.Raycast(transform.position, Vector3.down, out slideHit, LayerMask.NameToLayer("Defaul"))) {
            if (Vector3.Angle(slideHit.normal, Vector3.up) > 0){
                isSliding = true;
            }else{
                isSliding = false;
            }
        }

        if(isSliding){
            Vector3 hitNormal = slideHit.normal;
            slide_direction.x = hitNormal.x * slide_magnitude;
            slide_direction.y -= gravity * Time.deltaTime;
            slide_direction.z = hitNormal.z * slide_magnitude;
            chara.Move(slide_direction); 
        }
    
    }
}

確かに上のソースコードで滑り落ちるようになりましたが、それでも理由は分かりませんがたまに乗っかってしまうことがありました。なので強硬策としてCharacterControllerを縦に引き伸ばすということをしました。結果としてCharacterControllerが普通にプレイする上では重なることはなくなりましたがここはもう少し改善する必要があります。

マップの実装(ボツ)

これはフィールドが広くて分かりづらいという意見があったので、マップを実装したときの写真です。

f:id:kurome-stdio:20170708001922p:plain

実装にあたって以下のサイトを参考にしました。

Unityのゲームでキャラクターの動きを追うレーダーカメラを作成する | Unityを使った3Dゲームの作り方(かめくめ)

上のサイトを見れば分かるようにマップといっても二つ目のカメラでステージの上からの景色をそのまま表示させているだけです。そのため木が生い茂っている場所に移動するとプレイヤーの位置が木に隠れて見えない画面の右上にあるのが邪魔といった問題があってボツにしました。マップを実装するとしたらメニュー画面を開いたときだけマップを画面上にだすとか、フィールドのミニマップを描くなどをする必要があると思います。当時は間に合いませんでした。

まとめ

以上がゲームを作るまでの流れです。攻撃の部分と敵のAIの部分は本のスクリプトをそのまま使いましたがAI部分はもうちょっと改善できそうな気がします。ちなみにゲームを制作期間は大体2ヶ月ぐらいです。せっかくなのでunityインターハイにこの作品を提出しましたが予選で敗退してしまいました…。大会の審査員からはこんなフィードバックを頂きました。

  • 敵の狼が強い。市販の3Dアクションゲームを見ると複数の敵が同時に近づいてくることはあっても、同時に襲ってくることはない(片方は間合いの外でじわじわ歩き回ったり)ので、そうした動きを研究してみてほしい。

  • アクションの駆け引きがもっとほしい。

てっきり予選通過したかどうかの連絡しかこないと思っていたのでフィードバックが貰えたのは結構嬉しかったです。Unityroomの方でもコメントがついたりしててとても励みになりました。
以上が、去年作った3Dアクションゲームの開発記です。

Unity本1冊目まとめ

今回は自分が初めて買ったUnity本を以前読んだところまでもう一度読み直し、自分用に大事だと思ったところをまとめました。ちなみにこの本です。

chapter1 プログラミングの準備をしよう!

unityの使い方
monodevelopmentの使い方

主に環境構築などの話です。サークルのゲームジャムに参加したときは圧倒的にvisual studioを使ってる人が多かったですね…。
ちなみにscriptを開いたときにデフォルトで開かれるエディタは変更することが出来ます。
Unity→Preferenceを選択して

f:id:kurome-stdio:20170506202849p:plain

External Toolsを選択して、右上のやつを変更すればデフォルトのエディタを変更することが出来ます。

chapter2 C#を使おう!

C#には沢山の値の型があるみたいです。

  • 整数
    – short…符号付き16ビット整数
    – int…符号付き32ビット整数
    – long…符号付き64ビット整数

  • 実数
    – float…32ビット幅の値
    – double…64ビット幅の値

他にもいろいろあったけどこの辺ぐらいしか使わないだろう思うので省略。

Unity内ではでは普通に値を書くと

123…int型
123.0…double型
123.0f…float型

と判定されるみたいです。floatにするときはfをつけよう。他にも定番のchar型とかstring型があります。

ちなみにプログラムのソースコードにおいて使用される、数値や文字列を直接に記述した定数のことをリテラルって言うらしい。

画面内にテキストを表示させるプログラム

GameObject→UI→Textを選択し、textオブジェクトを生成。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;

public class HelloWorld : MonoBehaviour {
    public Text text;
    void Start () {
        text.text = "Hello World";
    }
    }
}

f:id:kurome-stdio:20170506215922p:plain

そして先程作ったtextオブジェクトこのスクリプトを貼り付けてtextって書かれた変数にインスペクタ上からさっき作ったtextオブジェクトを貼ると画面にHello Worldが表示されるって流れです。

public Text textについて

textという名のTextオブジェクトを宣言し、先程この変数に画面に表示されているtextオブジェクトをアタッチしたので、このtextという変数が、シーンに配置したTextコンポーネントの部品そのもののように扱える様になっているという感じだと思います。ちゃんとオブジェクト指向を理解してないから上手く説明できない…。
text.textの意味としては前にあるテキストがTextコンポーネントが入っている変数。後にあるtextがTextに保管されているインスタンスのプロパティでTextに表示されているテキストを表しているものです。

Textコンポーネント…Unityのテキストに関する機能が集まった部品

オブジェクト…独立して扱うことの出来るプログラムのかたまり。メソッドと値(フィールド)を持ったもの。ゲームはシーン、モデル、証明、カメラといったオブジェクトの集まりで出来ている。

クラス…オブジェクトの設計図。

継承について
例)class クラス名 : 継承するクラス名 {}
実はどのクラスもMonoBehabiourを継承していることになっている。 MonoBehavioourは「Unityのプログラムに関連付けて動くクラスのために必要な機能をまとめたもの」

C#インスタンスの作り方
  • クラス名 変数名;
  • 変数 = new クラス名();
    下のように宣言と初期化を同時に行うことも出来る。
  • クラス名 変数 = new クラス名();
文字+数値

C#だと文字列+数値は数値が文字列に変換されて連結されます。(確かJavaScriptもそうだった)

String str;
str = "200" + 9;

という文があったら
strの中身は"2009"です。

C#の配列の宣言

タイプ名 “"変数名;
宣言と初期化は下記の通り
タイプ名 ”
“ = new タイプ名 [保管する値の数]
タイプ名 ”[]“= { 値1, 値2}

string [] arr = {"Hello", "welcome", "Bye"} 
列挙型

いくつか用意されている値だけを使える特別な型。型そのものを自分で作れる。
列挙型のタイプを作成する

enum 型名 {値1, 値2, ・・・・・・}

本に書いてある列挙型よりも分かりやすく説明しているサイトがあったのでこっちを参考にしたほうが良さそう
あとenumはメソッドの中には書けず、予め用意しておく必要があるのでvoid startの中には書けません!

連載:C#入門 第16回 列挙型の活用(1/4) - @IT

Chapter3 ゲームオブジェクトを操作しよう!

Vector3について

Vector3とは横、縦、奥行きを表したベクトルのこと

transform.Translate と transform.positionの違い

下記はどちらもオブジェクトを動かくスクリプトですが
transform.Translateは変化量を渡します。

Vector3 p = new Vector3(0,0,0.1f);
transform.Translate(p);

対してtransform.positionは位置を渡します。

zvalue += 0.1f;
Vector3 p = new Vector3(0,0,zvalse)
transform.position += p;
回転させたい場合は
transform.Rotate
Vector3 v = new Vector3(0.1f,0,0)
transform.Rotate += v;
大きさはを変えたい場合は
ltransform.localScale
Vector3 v = new Vector3(0.1f,0,0)
transform.localScale += v;
Input系

Input.GetKey(KeyCode.UpArrow)…矢印キーの上
Input.GetKey(KeyCode.A)…Aキー

よく使うやつ Input.GetAxis(“Horizontal”);
Input.GetAxis(“Vertical”);

x = Input.GetAxis("Horizontal");
y = Input.GetAxis("Vertical");
Vector3 v = new Vector3(x, y, 0);
transform.Translate(v);

GetAxis(“Horizontal”)はゲームパッドまたはキーボードの←→キーによって-1~1の値を返す。

Input.mousePosition

マウスポインタの位置を示します。

Vector3 pos = Input.mousePosition;
pos.z = 3.0f;
Vector3 newpos = <camera>.ScreenToWorldPoint(pos);

※ただしmousePositionで得られた値は画面上の位置であり、Rayでオブジェクトの当たり判定を行う場合は絶対座標に変換する必要がある。

Rayについて

Rayはある地点から、無限に伸びる見えない直線のこと。本よりも分かりやすい使い方をしているサイトがあったからそのまま引用します。

void RayTest()
    {
        //Rayの作成       ↓Rayを飛ばす原点   ↓Rayを飛ばす方向
        Ray ray = new Ray (transform.position, new Vector3 (0, -1, 0));

        //Rayが当たったオブジェクトの情報を入れる箱
        RaycastHit hit;

        //Rayの飛ばせる距離
        int distance = 10;

        //もしRayにオブジェクトが衝突したら
        //                  ↓Ray  ↓Rayが当たったオブジェクト ↓距離
        if (Physics.Raycast(ray,out hit,distance))
        {
              //Rayが当たったオブジェクトのtagがPlayerだったら
                if (hit.collider.tag == "Player")
                Debug.Log ("RayがPlayerに当たった");
        }
    }

引用元
Rayを飛ばして当たったオブジェクトの情報を得る方法 - Qiita

Rigitbodyを埋め込んだオブジェクトに力を加える
transform.GetComponent<Rigidbody>().AddForce(0, 0, 1);

※Rigitbodyなどのコンポーネントは元からあったわけでなく、「後から追加したコンポーネント」です。こうしたコンポーネントスクリプトで制御する場合にはGetComponentする必要があります。

GetComponent<クラス>():
名前によるゲームオブジェクトの取得
GameObject 変数 = GameObject.Find("変数");

※find系は重くなる原因となるため極力使わない
findの代替案
public変数、もしくはSerializeFieldアトリビュートをつけた変数を定義し、オブジェクトを予めunityのInspecterからアタッチしておく。
インスペクタ上でオブジェクトを貼り付けられることも知らなかった….。どうでもいいですけど

public Camera camera;

って宣言すると

f:id:kurome-stdio:20170506231311p:plain

カメラのオブジェクトしか貼れないけど

public GameObject camera;

って宣言すると

f:id:kurome-stdio:20170506231336p:plain

カメラ以外のオブジェクトも以外のゲームオブジェクトも貼れるんですね。どっちがいいんだろ….。
ここまでの知識でビリヤードみたいな物を作ることが出来るみたいです。ってことで3章の最後にあるビリヤード作りもやりました

f:id:kurome-stdio:20170506231836p:plain

f:id:kurome-stdio:20170506231850p:plain

カメラはRotateAroundというメソッドを使ってオブジェクトの周りを動くようになっています。
これはある地点を中心にして、決まっただけ離れた場所を回転させるメソッドです。

transform.RotateAround(中心位置, 距離, 回転角度)

本ではガッツリ三角関数を使ってカメラの位置を移動させてますね。(カメラ制御難しい…)

終わりに

この本には4章と5章と6章があるのですが、自分が読んだのは4章の途中までです。というのも訳あって去年の8月の終わりまでに3Dアクションゲームを作る必要があったため途中で3Dアクションゲームに特化した本に切り替えてしまったからです。残りの方をサラッと読んだ感じアニメーションクリップの話とかコライダーの話とかあって重要そうな感じがするので今度ちゃんと読もうと思っていたのですがまだ読んでません…。あと自分が大切だと思ったところをまとめたのですが結構な量になってしまいました。まとめるのに思った以上に時間がかかったので最初のところとかもっと適当で良かったかも。

おまけ

本とは全く関係ないです

Unityのコンソールを色付け
Debug.Log ("<color=yellow>黄色のログ</color>");
Debug.Log ("<color=#0033ff>カラーコードのログ</color>");
macC#コンパイル、実行

monoが入っていなかったら

brew install mono
$ mcs hello.cs
$ mono hello.exe

参考文献

enum (C# リファレンス) | Microsoft Docs

クラス - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

Unity開発で気をつけておきたい7つの事 -2015年版- - 渋谷ほととぎす通信

MacでC#をコンパイル、実行 | nkmk log

個人的なUnityはまりポイント

Unityをやってて自分がよくハマるところをまとめました。

シーンを遷移すると画面が暗くなる ver5.6

window→Lighting→Settings f:id:kurome-stdio:20170504230449p:plain Auto Generateのチェックを外して
f:id:kurome-stdio:20170504230630p:plain Generate Lightingをクリック
※いじるのは遷移先のシーンのLightingの設定です。
何がムカつくってversionが変わるたびにこのここらへんの名称とLightingの設定の場所が変わるんだよね。原因としてはautoだとシーンを遷移したときにライトが間に合わないから予め焚いておく必要があるみたいな?(すいません、よく分からないので知ってい人が居たら教えてください!)

オブジェクトが壁をすり抜ける

transform.Translateとかtransform.positionとかで座標を指定して動かすとすり抜けます。解決策としてはRigitbodyとColliderを付けてRigitbody.AddForceとかで動かす。

※Rigitbodyでもすり抜ける場合

移動してくる物体が速すぎると衝突判定をする関数に引っかからずにすり抜けてしまうらしい。
解決策

  • RigidbodyにあるCollision Detectionを"Continuous"にする。

  • Edit > Project Settings > Time から、Fixed Timestepの値を小さくする(全体的に重くなるので注意)

  • Mathf.Clampとかでオブジェクトが動く幅を制限する。

transform.positionに直接値を代入できない

Cannot modify a value type return value of ‘UnityEngine.Transform.position’.
Consider storing the value in a temporary variable. 直接transform.positionに値を代入できません。一時的な変数に代入してからtransform.positionに入れてくださいみたいなことをいってるんだと思います。自分の場合はさっきの例でスクリプトでオブジェクトを動く幅を制限したときにtransform.positionに直接Mathf.Clampで制限した値をぶち込んだらエラーになったので

z = Input.GetAxis("Horizontal");
y = Input.GetAxis("Vertical");
Vector3 v = new Vector3(0, y * speed, z * speed);
transform.Translate (v);
transform.position.y = Mathf.Clamp (transform.position.y, 2.0f, 18.2f);
transform.position.z = Mathf.Clamp (transform.position.z, -13.0f, 13.4f);

から

z = Input.GetAxis("Horizontal");
y = Input.GetAxis("Vertical");
Vector3 v = new Vector3(0, y * speed, z * speed);
Vector3 Position = transform.position;
transform.Translate (v);
Position.y = Mathf.Clamp (transform.position.y, 2.0f, 18.2f);
Position.z = Mathf.Clamp (transform.position.z, -13.0f, 13.4f);
transform.position = Position;

という風に一時的にVector3に入れてからtransform.positionに入れたらエラーが無くなりました。
ここらへん触ってないとすぐ忘れちゃうんだよね…

参考文献

Unityまとめ

Unity C# エラーはまりポイント - Qiita

ブログを創設するにあたって

先日約1年ぶりにunityを触ったときの話です。 自分は現在大学1年生であり、とあるゲームを作るサークルに入会しました。 近いうちにunityを触ることになるだろうと思い、久しぶりにunityの復習がてら3Dのキャラクターをインポートし、適当に走らせてみようと思いましたが…

transform.translate とtransform.positionの違いってなんだっけ???
キャラクターにアニメーションをつけるのってどうやんだっけ???
当たり判定、攻撃の処理を実装するコードが全く思い出せないよ!!!

というわけで3Dゲームに欠かせない知識の大部分を忘れてしまい、今必死に復習をしている訳です。 しかもメモをあまりとっていなかったため、同じところでまた躓く…。 そういう訳でこのブログでは備忘録的な意味を持たせたいのです。
まあプログラミングの知識を貯める場所が欲しかったのもそうですが、最近書きたい欲溜まり溜まってしまいtwitterでは語りきれない部分をぶちまける場所が欲しいと思ったのも理由の一つです。
というわけでこれからプログラミングをしていて学んだとか躓いたところ、たまに日常のことなどをつらつら書き連ねていこうと思っています。