【再編集】Blender開発メモまとめ

※本記事は旧ブログの記事を再編集し、再度投稿したものです。
 一部内容に差異がある可能性がありますので、予めご了承ください。

ちょっとしたメモ

基本的なことからどうでもいいことまでメモしておきます!

・法線の再計算
面選択後、ツールシェルフからRecalcrateを選ぶかCtrl+Nでできます。
Unityへインポートした際にメッシュの表示がおかしくなる場合、法線を外側に再計算させるといい感じです。

・オブジェクトの中点の修正
Shift+SでCursor to Centerを選択し、カーソルを真ん中に移動させておきます。
オブジェクトを選択し、Shift+SのSelection to Cursorを選択します。
あとは3DView左下のObjectメニューから、Transform→Origin to 3D Cursorを選択。
Origin to 3D Cursorをする前に、ずれを手動で修正するといい感じです。

・RemoveDoubles
Editモード中に面選択後、Ctrl+Vのメニュー内にあります。
オブジェクトの面が重複しているかをチェックし、取り除いてくれるものです。
面選択の具合がおかしかったり、テクスチャが上手く貼れない場合に試してみるといいかも。

オブジェクトを結合した際の不自然な線もこれで修正できます。
エクスポートする前にも確認するとよさそうです。

・MarkSeam
Editモードで辺選択後、Ctrl+Eかツールシェルフ内にあります。
赤い線ができ、この状態でUV展開を行うことで、赤い線を切れ目とした展開図になります。
円形のオブジェクトにテクスチャで模様を貼ったりする際に便利です。

・Boneを左右対称に作る
AmatureオプションのX-Axis-Mirrorをオンにした状態で、Shiftを押しながらEで伸ばします。

モディファイア

・Mirror
オブジェクトを左右対称に作る際に使用します。
Editモードで片側のみを削除してから適用するといい感じです。

・Subdivision Surface
「頂点数を増やさずに面数を増やす」モディファイアです。
Cubeなどの単純なオブジェクトから形作る際に便利です。

shot2ss20150422233129491

オブジェクトの面自体は多くなっていますが、頂点数はCubeのままなので、エディットモードでの編集が楽になります。
Viewの値を変えることで面の細かさを変更することができます。
Iキーの面挿入やEキーのエルード、Ctrl+Rのループカット等を組み合わせれば、これだけでも多彩なモデリングができます。
上手く調節すると曲面も簡単に作成できたりします。

Applyすると頂点数が実際の面数に合わせて作られます。
適用後、smoothモディファイアを使えばかなり滑らかになります。

・Boolean
ブール演算を行う際に使用します。
オブジェクトを別のオブジェクトの形に合わせて切り抜くことができます。
単純ながらかなり効果的です。

Oprationを切り替えることで切り抜く以外の演算も行えます。
Objectには切り抜く部分となるオブジェクトを選択します。

アニメーション分割

長いアニメーションをUnityでフレーム区切りするのではなく、各アニメーションを独立させてインポートする方法です。
EditorTypeをDopeSheetに変更し、メニューバーのNewボタンを押して新規アニメーションを作成します。
アニメーション名を入力し終えたら横のFを忘れずに押します。

この状態でキーフレームを取得していくと、作成したアニメーションに対して設定されていきます。
次のアニメーションを作る際は「+ではなく×」を押します。(重要)
+は「現在のアニメーションを複製」みたいな挙動になるので、単に次のアクションを作る場合は不便です。
×を押すとアニメーションが消えるので、再度Newボタンを押して作成していきます。

Blender全般の操作ですが、×を押しただけでは消えないようになっています。
アニメーションも同様で、画面上は消えていますが、内部的には保存されています。
本当に消したい場合はShiftを押しながら×をクリックした後、Blenderを再起動すると削除されます。

ファイルパス修正

テクスチャとして貼られている画像ファイルをBlenderが見失った場合、テクスチャ部分がピンク色で表示されます。
「該当ファイルを移動・削除」「ドライブレターが変わった」などで起こりえます。

とりあえずの直し方として、メニューのFile→External_Data→FindMissingFilesを選びます。
ダイアログが表示されるので、テクスチャとなる画像ファイルが存在するフォルダを選択しましょう。
上手く認識すればピンク色の部分にテクスチャが貼り直されます。

画像ファイルの扱いを絶対パスから変更することで、間接的に防止できます。
以下の2つのどちらかで出来るようです。

・ファイル参照を相対パスにする
先程のExternal_Dataの中の「MakeAllPathsRelative」を選択します。
これは使用しているすべてのファイルの参照を相対パスに変換してくれます。
これならドライブレターが変わっても大丈夫です。

・Blenderファイルに画像ファイルを内包させる
こちらは画像ファイルを参照せずに、そのままデータとして入れてしまおうという話です。
External_Dataの中の「Pack into .blend file」を選択。
最も確実な方法ですが、画像ファイル分サイズが大きくなること、画像ファイルの修正が面倒になることに注意が必要です。

【再編集】Unity開発メモまとめ (その他)

※本記事は旧ブログの記事を再編集し、再度投稿したものです。
 一部内容に差異がある可能性がありますので、予めご了承ください。

AddForceのForceMode

RigidBody.AddForce()の第2引数として、力の加え方を指定することができます。
今のところ4種類あるようです。

・Force
対象のRigidbodyに継続的な力を加えます。
ForceModeを指定しない場合はこれになります。

・Impulse
対象のRigidbodyに瞬間的な力を加えます。
「オブジェクトに判定が当たったら~」などの場合に力を加える場合はこちらを指定します。
1回の呼び出しで吹き飛ばす際、Forceの場合はかなりの力を加えないと満足に吹き飛びませんが、こちらを指定すればそこそこ飛ぶようになります。

・Acceleration
対象のRigidbodyのMass(質量)を無視して、継続的な力を加えます。
オブジェクトの質量によらず一定量飛ばす場合などに使える・・・かも。

・VelocityChange
対象のRigidbodyのMassを無視して、瞬間的な力を加えます。
AccelerationやForceよりは使う機会がありそうです。

テクスチャの存在するマテリアルの取得

オブジェクトに複数設定されているマテリアルから、テクスチャが貼られているマテリアルのみ取得したいことがあります。
テクスチャを動的に切り替えたりする際は特にですね。
自分はキャラクターの顔オブジェクトをheadObjectに設定し、Start内に以下のスクリプトを書いて取得しています。

 
Renderer faceRenderer = headObject.GetComponent<Renderer>();

for (int i = 0; i > faceRenderer.materials.Length; i++ ) {
    if (faceRenderer.materials[i].GetTexture("_MainTex") != null) {
        faceMaterial = faceRenderer.materials[i];
        break;
    }
}

テクスチャが1つだけの場合は_MainTexに貼られることが多いので、GetTextureの返り値で判定しています。
breakを外せば複数のテクスチャ付きマテリアルが取得できます。
その場合はfaceMaterialに代入するのではなく、配列に足していく感じにするのがいいと思われます。

定数

定数だけをまとめた静的なクラスを作成します。
usingも関数も必要なく、GameObjectに付ける必要もありません。
定数が多くなってきた場合、内部クラスか別ファイルとしてクラスを分けて整理します。

 
public static class GameConstants {
    public const float DEFAULT_HP = 100;
    public const float DEFAULT_SP = 100;
}

参照する場合は「クラス名.定数名」でアクセスできます。
例の「DEFAULT_HP」にアクセスしたい場合、「GameConstants.DEFAULT_HP」といった形になります。
プロジェクト内のスクリプトであればどこからでも参照できます。

ログファイルの出力

適当に空のGameObjectを作り、以下のスクリプトを付けます。

 
using UnityEngine;
using System;
using System.IO;

public class LogManager : MonoBehaviour {

     private string output;
     private string stack;
     private string basePath;

     private FileStream fs;
     private StreamWriter sw;

     void Start() {
          // プロジェクトパスの取得
          basePath = System.IO.Directory.GetCurrentDirectory();

          // ログフォルダ生成
          System.IO.Directory.CreateDirectory(basePath + "/Logs");
     }

     void OnEnable() {
          Application.logMessageReceived += HandleLog;
     }

     void OnDisable() {
          Application.logMessageReceived -= HandleLog;
     }

     void HandleLog(string output, string stack, LogType type) {
          try {
               DateTime date = System.DateTime.Today;

               // ログファイル生成
               FileStream fs = new FileStream(basePath + "/Logs/" + date.ToString("yyyyMMdd") + ".log", FileMode.Append);
              
               // ログファイルへ出力
               sw = new StreamWriter(fs);
               sw.WriteLine(output);
               sw.WriteLine(stack);

          } catch (FileNotFoundException e) {
               Debug.Log("File Not Found: " + e.ToString());
          } catch (UnityException e) {
               Debug.Log(e.ToString());
          } finally {
               sw.Close();
               fs.Close();
          }
     }
} 

「Application.logMessageReceived」はログメッセージが出力された際に呼び出されるイベントです。
「Application.RegisterLogCallback」は古い形式のようです。
Unity5から変わっているようなので注意します。

Start内でログファイルを格納するフォルダを生成します。
System.IO.Directory.GetCurrentDirectory()でProjectのフォルダまでのパスが取得できます。
その直下にCreateDirectory()でフォルダを生成します。

ログ出力時にHandleLog()が呼ばれるので、この中でファイルを生成します。
System.DateTime.TodayのToString()でフォーマットを指定します。

FileStreamインスタンス生成時にファイルがない場合、自動的にファイルが生成されます。
デフォルトでは上書きされてしまうので、FileModeをAppendにしておきます。
あとはFileStreamを使ってStreamWriterを作り、WriteLine()でログファイルに出力します。
finally句でCloseするのも忘れないようにしましょう。

【再編集】Unity開発メモまとめ「Clothコンポーネント」

※本記事は旧ブログの記事を再編集し、再度投稿したものです。
 一部内容に差異がある可能性がありますので、予めご了承ください。

Unity5から、それまでのCloth系コンポーネントがClothのみの1本に統合されたようです。
なので「Interactive Cloth」や「Skinned Cloth」はなくなりました。

EditConstraint

メッシュの頂点に対し、どの程度まで動かすかを設定します。
専用のUIが用意されており、方法は範囲選択or頂点クリックの2パターンがあります。

そのままでは超絶見にくいので、シーンビューのレンダリングをWireFrameに変更します。
またはVisualizationの「Manipulate Backface」にチェックを入れると頂点が透けて見えるようになります。

shot2ss20150531221157814

MaxDistanceにチェックを入れ、「動かさず固定する部分」が赤色になるようにします。
逆に動かす部分は緑色にするわけですが、赤色の頂点さえ付けておけば緑色にしなくてもシミュレートしてくれます。

「動かす頂点だけど、緑だと動きすぎる」という場合は、赤と緑の中間の色に設定します。
いきなり中間に設定しようとすると、入力した値が最大値になる(=緑色になる)ため上手く設定できません。
先に動かす箇所を緑色にして最大値を設定し、その後で0~最大値の中間を指定します。

パラメータメモ

・StretchingStiffness
布の伸縮率を設定します。
リファレンスには「頂点数を減らす程硬くなる」と書いてあります。

・BendingStiffness
布の曲げ剛性を設定する・・・らしいです。
頂点数によって変わるのはStretchingStiffnessと同じようです。

・UseTethers
過剰な伸縮を行わないようにする制約を適用するそうです。
OFFだと頂点が1つだけ吹っ飛んでしまったりするので、基本ONで問題なさそう。

・Damping
布がどの程度湿っているかを0~1の間で設定します。
1にするとかなり鈍くなります。

・friction
キャラクターと布との間に生じる摩擦だそうです。
キャラクターを回転させた際に布が引っかかってしまう場合、この値を下げるとスルっと外れてくれます。

・Acceleration系
UnityのWindZoneはTerrainに配置した草木やパーティクルにしか反応しません。
外部的な力を加えたい場合、「Extrernal Acceleration」と「Random Acceleration」の値を変更します。

・World~Scale系
キャラクター本体の移動が布に与える影響を設定します。
高いと移動に対してアグレッシブに影響を受けるので、自分は0か低い数値に設定しています。

・Collider系
Sphere ColliderとCapsel Colliderを設定できます。
ここで設定したコライダーを貫通しなくなります。
主な用途は体に合わせてコライダーを設定し、めり込まないようにすることでしょうか。

shot2ss20150604131213760

逆を言うと上の2種類のコライダーしか設定できません。
自分のキャラのように単純なモデリングならいいのですが、リアルな人間などの場合は何個もコライダーを用意する必要がありそうです。

上手くいかない場合

Edit Constraintsで設定してもなびかない場合

Skinned Mesh Rendererの「RootBone」が設定されていると上手くいきません。
特にBlenderで親子関係を設定してアニメーション等を作成していると見落としやすいです。
RootBoneを解除(None)すればOKです。

メッシュの形状にもよりますが、明らかに不自然なレベルで動かないので分かりやすい現象ではあります。
また、RootBoneが設定されているとEdit Constraintsを設定していなくても落下しません。

メッシュが不必要に伸びてしまう場合

上とは逆に、Clothが効きすぎてびろびろに伸びてしまう場合は、Clothを付けているモデル自体のScaleを調節するといい感じになります。
小さくしすぎると逆に効かなくなってしまうので適度にサイズを下げましょう。
Scaleというよりは、Boundsの値がそれに応じて変化するため・・・でしょうか。

【再編集】Unity開発メモまとめ 「オブジェクトの透明処理」

※本記事は旧ブログの記事を再編集し、再度投稿したものです。
 一部内容に差異がある可能性がありますので、予めご了承ください。

オブジェクトのフェードアウト

iTween.FadeTo()を使うことで、透明度と掛ける時間を設定してフェードさせることができます。
「lifeが0になったら透明にする」という処理を行う場合、該当キャラクターのUpdate()内に以下のように記述します。

protected void Update() {
   if(life <= 0){
      iTween.FadeTo(gameObject, iTween.Hash(
         "alpha", 0, 
           "time", 0.5f,
       "oncomplete", "Destroy",
       "oncompletetarget", gameObject
         ));
   }
}

private void Destroy() {
    Destroy(gameObject);
}

「oncomplete」と「oncompletetarget」を指定しておくと、iTween後に関数を実行させることができます。
上の例ではDestroy()を呼び出し削除処理をしています。

シェーダー修正

レンダラーモードをTransparentにすると描画順がおかしくなるので、シェーダーを修正します。
公式からビルドインシェーダーをダウンロードできるので、これをカスタマイズします。
http://japan.unity3d.com/unity/download/archive

各Pass内のZWriteの部分をすべてOnに書き替えましょう。
被らないようにソース冒頭のシェーダー名を「Standard_Custom」等に変えておきます。

両面シェーダー

せっかくなので両面シェーダーのメモも載せておきます。
ソース内に2箇所あるSubShaderのLODの下に「Cull off」を追加します。

・49~53行目付近
SubShader
{
Tags { “RenderType”=”Opaque” “PerformanceChecks”=”False” }
LOD 300
Cull off //この行

・208~212行目付近
SubShader
{
Tags { “RenderType”=”Opaque” “PerformanceChecks”=”False” }
LOD 150
Cull off //この行

カメラとプレイヤー間のオブジェクトの半透明化

カメラとプレイヤーの間に障害物が入ってしまい、プレイヤーを見失うことはよくあることです。
そんな時に障害物を半透明化し、プレイヤーが見えるようにします。

void Update () {
     float distance = Vector3.Distance(mainCamera.transform.position, player.transform.position);
     Ray ray = new Ray(mainCamera.transform.position, mainCamera.transform.forward * distance);
 
     if (Physics.Raycast(ray)) {
          RaycastHit[] hits = Physics.RaycastAll(ray);
 
          for (int i = 0; i < hits.Length; i++) {
               if (hits[i].collider.tag == "BreakObjects") {
                   iTween.FadeTo(hits[i].collider.gameObject, iTween.Hash(
                      "alpha", 0,
                      "time", 0.5f,
                      "oncomplete", "AlphaReset",
                      "oncompletetarget", hits[i].collider.gameObject
                   )); 
               }
          }
     }
}

予めオブジェクトにタグを付けておきます。
ここでは破壊可能なオブジェクトを指定し、if文でタグを評価します。

まず飛ばすRayのインスタンスを生成します。
上記の通りカメラ向きに飛ばせばいいので、[カメラの位置],[カメラの向き]を引数にセットします。
Rayを飛ばす距離は適当でも問題なさそうですが、一応カメラとプレイヤー間の距離を測り、それをforwardに掛けています。

生成したRayをRaycastで飛ばし、カメラとプレイヤーの間にオブジェクトが存在するか判定します。
存在する場合はRaycastAllで間のオブジェクトを全て取得し、特定のタグを持つオブジェクトのみを半透明化します。

「プレイヤーが隠れているか判定」のスクリプトでRay上のオブジェクトの配列を取得しています。
Raycast型なので、.collider.gameObjectでゲームオブジェクトを呼び出し、iTweenを実行します。

そのままでは半透明になりっぱなしなので、オブジェクトにスクリプトを付与します。
iTweenのoncompleteで透明度をリセットする関数を呼び出します。

using UnityEngine;
using System.Collections;
 
public class StageObject : MonoBehaviour {
 
     protected void AlphaReset() {
          iTween.FadeTo(gameObject, iTween.Hash(
               "alpha", 1f,
               "time", 0.5f
               ));
     }
}

【再編集】Unity開発メモまとめ 「攻撃処理系」

※本記事は旧ブログの記事を再編集し、再度投稿したものです。
 一部内容に差異がある可能性がありますので、予めご了承ください。

[2016/04/10]
記事内容を加筆・修正しました。

攻撃判定の作成

攻撃判定はアクションゲームにおいて重要なものです。
ちょっと考えるだけでも、「判定の大きさ」「持続時間」「攻撃力」といった要素があります。

Unity的には判定の大きさはCollider系コンポーネントで扱います。
大雑把な判定でよければBoxCollider、よりリアルに当たったか判定したい場合はMeshColliderなんかが使えそうですね。
処理的にはSphereColliderが一番高速らしいです。
判定として扱うので、isTriggerにチェックを入れます。

後々使うRigidBodyコンポーネントも付けておき、isKinematicにチェックを入れておきます。
これにチェックがないと何かの拍子に吹っ飛んでしまったります。

攻撃力や持続時間をUnity側のコンポーネントに任せるのは難しいので、攻撃判定用にスクリプトを1つ作ってしまいます。
以下は攻撃判定用の「PlayerHit」クラスです。

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Rigidbody))]
public class PlayerHit : MonoBehaviour {

    public float power, time;
    public GameObject damageEffect;
    public Player pc;

    protected override void Start () {
        if (time != 0) {
            StartCoroutine(DestroyHit());
        }
    }

    protected virtual IEnumerator DestroyHit(){
        yield return new WaitForSeconds(time);
        Destroy(gameObject);
    }

    protected virtual void OnTriggerEnter(Collider c){
        if (TagUtility.getParentTagName(character.getGameObject().tag) == "Player") {
            if (TagUtility.getParentTagName(c.gameObject) == "Enemy") {
                c.GetComponent<Enemy>().damage(power);
                Instantiate(damageEffect, transform.position, Quaternion.identity);
            }
        }
    }
}

フィールドに攻撃判定に必要なパラメータを設定しておきます。
実際にぶつかった時の処理はOnTriggerEnter()で行います。
OnTriggerEnterの引数(Collider型)から、タグが「Enemy」のオブジェクトに当たった場合のみ処理を行います。
Enemyクラスにdamage()を実装しておき、GetComponent()して呼び出します。
damage()内でEnemyクラスのパラメータ(体力など)を変化させることで、敵キャラクターにダメージを与える処理を作ることができます。

・・・ということを踏まえ、攻撃判定用のオブジェクトを作ります。
シーン上にCubeを追加し、上記のスクリプトを設定します。
実際の攻撃判定は見えるものではないので、MeshRendererのチェックは外して非アクティブにしておくか、コンポーネントを削除します。

攻撃する度に生成・削除されるものなので、プレハブ化しておきましょう。
攻撃毎に異なる攻撃力・範囲を設定したい場合、プレハブを複製(Ctrl+D)して複数個作っておきます。

攻撃判定の生成

↑で作った攻撃判定を実際し生成し、判定として機能するようにします。
が、単に生成するだけではキャラクターの動きについてきてくれません。

例えば剣に攻撃判定を持たせ、振りに合わせて追従させたい場合、Armatureとしてキャラクターの剣にboneを持たせ、そのboneの子オブジェクトに設定する必要があります。
キャラクター本体のMeshの子オブジェクトにしても追従してくれないので注意します。

以下は攻撃判定を生成するattackInstantiate()です。
Playerクラス(プレイヤーキャラに付けているスクリプト)内のメソッドとして実装します。

 
protected void attackInstantiate(GameObject hitObject, GameObject hitOffset) {
    GameObject hit;

    hit = Instantiate(hitObject, hitOffset.transform.position, transform.rotation) as GameObject;
    hit.transform.parent = hitOffset.transform;
    hit.GetComponent<PlayerHit>().pc = this;
}

Playerクラスに以下のフィールドを追加しておきます。

・hitObject
先程作った攻撃判定のプレハブを設定します。

・hitOffset
攻撃判定を生成する位置で、剣に設定したboneの下に存在する空のGameObjectです。
boneだけでは攻撃判定の位置を調整しにくいので、この空オブジェクトの位置に生成させることで微調整できるようにします。

shot2ss20160408204748536

上はマリンパの剣攻撃のhitOffsetです。
(見やすいようにCubeをレンダリングしています)
剣の中腹部分に空オブジェクトがくるようにし、ここから攻撃判定を生成させています。

Instantiate()の返り値で生成したもの(=攻撃判定)をGameObject型にキャストして取得し、Transform.parentで親オブジェクトをhitOffsetに設定します。
これで位置や回転情報がhitOffset(の親のbone)から継承され、剣の振りにあわせて動いてくれます。

攻撃判定のスクリプトにフィールドを持たせ、Player自身の参照を渡しておくと便利です。
「攻撃が当たったらゲージが増える」などの制御も、pcという変数を通してPlayerクラスのフィールド/メソッドを呼び出すことで実現できます。
その場合、PlayerHitクラスにフィールドとして増加量を追加・設定しておくと、攻撃判定ごとに増加量を設定できたりします。

あとは実際にattackInstantiate()を呼ぶだけです。
シンプルに行くのであれば、PlayerクラスのUpdate内で「攻撃ボタンが押下されたか」を判定し、それに合わせて生成するのが簡単です。
合わせて攻撃モーションに切り替えるのを忘れないようにします。

if (Input.GetButtonDown("Attack1")) {
    animator = this.GetComponent<Animator>();
    animator.Play("Attack1");
    attackInstantiate(hitObject[0], hitOffset[0]);
}

hitObjectとOffsetは配列で複数指定できます。
「通常攻撃の1段目」と「3段目」で威力が異なったりする場合、インスペクターから対応する攻撃判定を設定し、添え字を切り替えてあげます。
その場合、animatorから「どのモーションを実行しているか」を取得し分岐させましょう。

今はanimatorのGetComponent()をif文の中でやっていますが、本来はStart()あたりでやって参照を取っておくほうが良いと思われます。
animator.Play()の引数はAnimatorControllerのステート名に合わせましょう。

AnimatorControllerを使っているのであれば、StateMachineBehaviourのEnter系を使って生成させると、アニメーションと攻撃判定生成のタイミングが合って確実です。
長くなるのでここでは割愛させて頂きます。

20160408_2

剣を振ったときに攻撃判定が付いてくるのが分かります。
(見やすいように攻撃判定をレンダリングしています)

遠距離攻撃の生成

判定生成の仕組みは同じですが、生成後の判定をhitOffsetから切り離し、弾として独立させる必要があります。
また、進む方向と速度を定義し、移動させる必要があります。

 using UnityEngine;
using System.Collections;

public class BulletHit : PlayerHit {

    public float bulletSpeed;

    protected Vector3 forward;
    private Rigidbody rb;

    protected override void Start(){
        base.Start();

        forward = hitManager.transform.forward;

        rb = this.GetComponent<Rigidbody>();
    }

    protected override void Update(){
        base.Update();

        rb.velocity = forward * bulletSpeed;
    }

    protected override void OnTriggerEnter(Collider c){
        base.OnTriggerEnter(c);

        if(TagUtility.getParentTagName(c.gameObject) == "Enemy"){
            Destroy(gameObject);
        }
    }
}

フィールドとして「弾速度」を表すbulletSpeedを追加します。
単に前に打ち出すだけであれば、transform.forwardで前方方向のベクトルが取得できるので、これにbulletSpeedを掛けてあげればOKです。
あとはRigidBodyのvelocityに代入すればそのベクトルへ進みます。

判定生成時に通常の攻撃判定のようにhitOffsetの子オブジェクトに設定してしまうと、発射後もプレイヤーの動きに連動してしまうため、parentの設定はしないようにします。

ホーミング弾

遠距離攻撃の応用で、敵を自動的に追尾する弾です。
弾発射前にSphereCastを行い、前方に敵がいたらその敵に対してホーミングするようにします。
SphereCast時に余計なもの(敵以外)にぶつからないようにするためには、以下の2通りの方法があります。

・レイヤーを「Ignore Raycast」にする
レイヤーをIgnore Raycastに設定すると、そのオブジェクトはレイキャストを無視するようになります。
これをTerrainなどに適用すれば、床に当たってしまうことはなくなります。

しかし、ホーミングさせたいのは敵だけです。
だからといって、他のプレイヤーや破壊可能なものにもIgnore Raycastを設定するのはよろしくないです。
敵側もレイキャストを行っているのでなおさらですね。

・新規レイヤーを作り、そのレイヤーとしか衝突しないようにする
例えば Enemy というレイヤーを作り、そのレイヤーに設定したオブジェクトとしか衝突しないようにします。
敵だけをキャストしたいなら、この方法が一番かと思います。

敵のプレハブのレイヤーをEnemyに設定し、Spherecastの第5引数でレイヤーマスクを設定します。
弾の攻撃判定のStart内でSphereCastを行います。

protected override void Start(){
    base.Start();

    angle = angle / 100f;

    if(Physics.SphereCast(transform.position, 10f, 
                            forward, out hit, 100f, 1<<8)){
        if(hit.collider.tag == "Enemy"){
            target = hit.collider.transform;
        }
    }
}

敵の方へ誘導する処理はQuaternion.Slerp()を使います。
第1引数の角度と第2引数の角度との差を、指定秒数分だけ補完した結果を返す関数です。
弾の攻撃判定のUpdate内に処理を追加します。

protected override void Update(){
    base.Update();

    if(target != null){
        transform.rotation = Quaternion.Slerp(
                         transform.rotation, 
                         Quaternion.LookRotation(
                           target.position - transform.position),
                         angle);
        rigidbody.velocity = transform.forward * bulletSpeed;
    } else {
        rigidbody.velocity = forward * bulletSpeed;
    }
}

敵が存在したらQuaternion.Slerp()を使い、徐々に向き直すことでホーミングを行います。
angleに渡す値でホーミングの度合いを調節できます。
基本的に0~1の間で設定し、ゲームや弾の性質に合わせて調整するのがよさそうです。

拡散弾

ホーミング弾と同じく遠距離攻撃の応用です。
弾1つ1つに攻撃判定を持たせるため、各々をInstantiateする形にします。
拡散弾専用の「DiffusionBullet」クラスを作り、攻撃判定となる弾に予めスクリプトを設定しておきます。

 
using UnityEngine;
using System.Collections;
 
public class DiffusionBullet : MonoBehaviour {
 
     [Range(0f, 1f)]
     public float diffusionAngle;
     public float bulletSpeed;
     
     private Vector3 forward;
     private Rigidbody rb;
 
     protected void Start () {
          rb = this.GetComponent<Rigidbody>();
 
          float angle_x = Random.Range(-diffusionAngle, diffusionAngle);
          float angle_y = Random.Range(-diffusionAngle, diffusionAngle);
          float angle_z = Random.Range(-diffusionAngle, diffusionAngle);
 
          forward = pc.transform.forward + new Vector3(angle_x, angle_y, angle_z);
          this.transform.parent = null;
     }
     
     protected void Update () {
          rb.velocity = forward * bulletSpeed;
     }
}

Start内でforwardを設定し、進む方向をVector3型で定義します。
固定値だと全ての弾が同じ方向になってしまうので、Vector3の各軸をランダムで設定し、forwardと足し合わせます。
diffusionAngleの値を大きくすると拡散角度が広がる仕組みです。

オブジェクト自体を回転させても進行方向は変わらないことに注意が必要です。
forwardは発射時のプレイヤーの方向を参照しており、且つStart時に1回だけ定義しているためです。
発射後も方向を変えたい場合はUpdate内でforwardを変更する必要があります。

[Range]属性はフィールドの最小値~最大値の値を制御します。
インスペクター上にはスライダーが表示されるようになります。
簡単でかつ便利なので、入力する範囲が決まっているフィールドには付けてみましょう。

ヒットストップ処理

攻撃がヒットした際にコルーチンを実行します。
一瞬だけAnimatorのスピードを遅くすることでヒットストップを実現します。
Playerクラスに以下の関数を持たせ、攻撃判定からコルーチンとして呼び出します。

public IEnumerator attackHitStop(float time){
    animator.speed = 0.1f;
    yield return new WaitForSeconds(time);
    animator.speed = 1.0f;
}

実際にやってみるとこんな感じになります。
gifアニメーションだとあまり伝わらないかも・・・。

20160408_1

ちなみにTimeManagerのTimeScaleはゲーム全体の速度なので、ヒットストップの処理には適しません。

攻撃時の軸補正処理

3Dアクションということで、多少なりとも敵のいる方向に自動で振り向いて欲しかったので考えてみました。
攻撃時に「Physics.SphereCast」を使い、敵の位置情報を取得します。
攻撃対象に振り向く処理はQuaternion.Slerp()を使用します。
Transform.LookAt()だと一瞬で振り向いてしまうので不自然になります。

PlayerクラスのUpdate()内に処理を追加します。
ステートのタグがAttackであれば敵の方に振り向くようにします。
予めコントローラから攻撃系ステートにタグを振っておきましょう。

 
if(animator){
    stateInfo = animator.GetCurrentAnimatorStateInfo(0);
 
    if (isAttack && SystemConstants.ATTACK_AUTO_ROTATE) {
        Debug.DrawRay(
            transform.position,
            transform.forward * 10f,
            new Color(255, 255, 255),
            1.5f
        );
        if (Physics.SphereCast(
            transform.position, 10f, transform.forward, out hit, 0.1f, 1 << 8)) {
            if (hit.collider.tag == "Enemy") {
                autoRotateTarget = hit.collider.transform;
            }
        }
        if (autoRotateTarget != null) {
            Vector3 to = Quaternion.LookRotation(
                autoRotateTarget.position - transform.position
            ).eulerAngles;
            to.x = 0;
            to.z = 0;
            transform.rotation = Quaternion.Slerp(
                transform.rotation,
                Quaternion.Euler(to),
                SystemConstants.ATTACK_AUTO_ROTATE_ANGLE
            );
        }
    } else {
        autoRotateTarget = null;  
    }
}

ATTACK_AUTO_ROTATEは単なる定数で、Bool型で機能の有効/無効をコントロールしています。
Debug.DrawRay()を使うとレイキャストが視覚化できるようになり、方向や距離が把握できるようになります。