Pop技術ログ

ゲーム作りに関するアレコレ

こんにちは。皆様年末如何お過ごしでしょうか。
僕は土日フル出勤&毎日深夜3時過ぎに帰ってきて、10時に出社するという生活で、なんか最後の方は楽しかったです。
しかし個人開発やる時間がほぼ取れず、たまに取れた日曜午前休みとかで進めていた内容しかありません。

メモ書きですので、この記事にて一気に書きます。

内容は下記です。

■ゲームの雰囲気をトゥーン調にする
■カメラ指定した対象に追従させる
■キャラクターのステータス管理
■HPバーの作り方
■パーティクルの出し方
■オブジェクトの画面外判定
■その他結構ハマった事



■ゲームの雰囲気をトゥーン調にする
流行っていたり、個人的に好きなので、トゥーン調(アニメ調)の雰囲気のゲームが作りたかったのですが、リソースができないとか、アセット使おうにも種類が少なかったりして断念していました。

そこで、そういう雰囲気に最初から作られている物でなくても、マテリアルとシェーダーの設定で後からそういう雰囲気にできるのでは?と試してみました。

結果的にはデフォルトのトゥーンシェーダーと環境光の設定で割と簡単に行けると判断しました。
(今作ってるゲームはこの方法で行こうと思っています)

<具体的なやり方>
デフォルトのトゥーンシェーダーは
toon/basic 環境光が反映されないやつ
toon/lit 環境光反映される
の2パターンがすでに用意されているのですが、ポイントは下の方(toon/lit)を使って、それに加えて環境光をいじることです。

環境光の設定は
window >Lighting
 Environment Lighting>Source を Colorにして色を白系で良い感じに見える色に設定します
(昔のUnityだとEdit>Render Setting)

before

after(※地形のモデルは元からトゥーン調、メインキャラと敵キャラを見てほしい)

これでリアル系のアセットも雰囲気統一してトゥーン調にできて良いと個人的には思ってます。
(元からトゥーンな雰囲気のモデルが用意できればそれが一番いいんですが)



そろそろ年賀状書かなきゃいけないので以下駆け足で書きます。


■カメラ指定した対象に追従させる
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;

  4. public class CameraCtrl : MonoBehaviour {
  5.     //追従対象
  6.     public GameObject _target;
  7.     //カメラを滑らかに追従するか
  8.     public bool _is_smooth = false;
  9.     //カメラとターゲットの距離比率
  10.     public float _offset = 1.0f;

  11.     private Vector3 offset = Vector3.zero;

  12.     
  13.     void Start() {
  14.         if (!_target) {
  15.             Debug.Log("カメラの追従対象が設定されていません");
  16.         }

  17.         offset = transform.position - _target.transform.position;
  18.         offset *= _offset;
  19.     }

  20.     void LateUpdate() {
  21.         Vector3 newPosition = transform.position;
  22.         newPosition.x = _target.transform.position.x + offset.x;
  23.         newPosition.y = _target.transform.position.y + offset.y;
  24.         newPosition.z = _target.transform.position.z + offset.z;

  25.         if (_is_smooth) {
  26.             transform.position = Vector3.Lerp(transform.position, newPosition, 5.0f * Time.deltaTime);
  27.         } else {
  28.             transform.position = newPosition;
  29.         }

  30.     }
  31. }

offset値を入れることで、距離感の調整も可能



■キャラクターのステータス管理

ゲーム作りの基本的な手法なのですが、キャラクターのステータスを管理するキャラステクラスを作り、ステータスを持つオブジェクトにアタッチさせます。
これはなるべく汎用的に、敵と味方で分けたりはせずに共通で使えるように作ります。

  1. public class CharaStatus : MonoBehaviour {
  2.     public int MaxHP = 10;
  3.     public int HP = 10;
  4.     public float Speed = 1.0f;
  5.     public int Power = 4;

  6.     public void Damage(int _var) {
  7.         HP -= _var;
  8.         HP = HP < 0 ? 0 : HP;
  9.     }


  10.     public void Heal(int _var) {
  11.         HP += _var;
  12.         HP = MaxHP < HP ? MaxHP : HP;
  13.     }

  14.     public float GetHpPer() {
  15.         return (float)HP / (float)MaxHP;
  16.     }

  17. }

例えばこんな感じで持たせておくと、これをアタッチするだけでステータス系の管理ができるようになります。
ダメージ与えたかったらこれのDamage()を呼ぶだけみたいな。



■HPバーの作り方

GameObject > Canvas > Slider
という構成で作り、このGameObjectをキャラの子にしてやればOK

CanvasのインスペクタビューにあるCanvasコンポーネントのRenderModeをWorldSpaceに変更する
後はスケールを調整し、邪魔な〇を消したりする。

くっつけたキャラと一緒に回転してしまうので、
GameObjectに付けたスクリプトで、常にカメラ側を向くようにする

  1.     void Update() {
  2.         //常にカメラを向くようにする
  3.         //(プレイヤーから見たカメラは若干ずらしてるから、upベクトルとしてカメラのupを指定してる)
  4.         transform.LookAt(camera.transform.position, -camera.transform.up);
  5.         //transform.LookAt(camera.transform.position, camera.transform.up);

  6.     }


  7.     public void SetHpPer(float _per) {
  8.         _slider.value = _per;
  9.     }
LookAtの第二引数にカメラのUpベクトルを指定しています。


■パーティクルの出し方
1つのパーティクルで構成されるParticleSystemであったら、
particle_explosion = particle_explosion_obj.GetComponent<ParticleSystem>();
単純にPlayとStopでいける。
何回も使うのはインスタンス1つ作って、
particle_explosion.Play();
particle_explosion.Stop();
でやるのが良さげ。

複数のパーティクルを内包した感じになっているのは、
パーティクルを梱包したGameObjectを生成してすぐ消す
GameObject explObj = Instantiate(particle_explosion_obj, _target.transform.position, _target.transform.rotation);
Destroy(explObj, 1.0f);

それか、先のPlay/Stopの方法を、子オブジェクト全てに対して行えばOK。
使用頻度によるかな。



■オブジェクトの画面外判定
画面外に出たら
  1. void OnBecameInvisible() {
  2. }
が呼ばれる

ただしRendererコンポネートをアタッチしておく必要がある



■その他結構ハマった事
Inspector の 名前の右にあるstatic にチェックが入ってると、rigitbodyとかcolliderとかアタッチしても効かない

メインキャラにIsKinematicは、マップのコリジョンとうまく共存できなくなるからやめた方が良い


なんと今回わたくし3連休を頂いております。
それでは皆さん、また来年もゲーム作りを楽しみましょう!


・オブジェクトにインパクトを与える方法
・アニメーターの進捗パーセントをとる方法
・当たり判定時の処理をする方法


こんばんは!今日も仕事でした。明日も仕事です。はい。
ゲーム作りなんでやってることは楽しいんですけど、ちょっと体の疲れが辛かったり、ゆっくり映画みてインプットする時間も欲しいんです…
と、言いつつ家でもゲーム作ってるんですが…


今日は少しゲームっぽくなってきた内容です。
敵やオブジェクトを吹き飛ばす所を作っていきます。

深夜のテンションでお送りします。
朝読んでる人いたらゴメンね☆(ゝω・)vキャピ

イメージとしては、
① 「触れたものを全て吹き飛ばすスクリプト」を作って、
② 武器とか手とかにくっつける
③ ①を常時ではなく、攻撃時のみ触れたら吹き飛ぶようにする
という感じです。


まず②の方からやっていきます。番号の意味とは…

まず自身の当たり判定用のオブジェクトを作ります。
空のオブジェクトです。
武器とかあればそれでも良いけど、ゴーレムはパンチだから。。。
ゴーレムパンチ、良い響き。

キャラ自体で直接攻撃当たりとるのはちょっと制御しにくそうだから、新しく作ってつけておこうって感じです。


②コライダーを付けた空のオブジェクトを手に付ける

20171202_1

こいつ右パンチするんで、右腕にくっつけておきます。
空のオブジェクトにはコリジョンをアタッチしておきます。
20171202_2

20171202_3



続いてこのコリジョンにつけるスクリプトを作ります。
①の奴です。

吹き飛ばす方向を定める為に自身のインスタンスと、床などの除外するタグ名を記載するpublicメンバを用意しておきます。

あ、その前に、
コライダーをつけておくと、何かとぶつかった時に
void OnCollisionEnter(Collision collision)が呼ばれます。
ぶつかっている間呼ばれ続ける
void OnCollisionStay(Collision collision)なんかもあります。
他にもあります。

で、コライダーをつけておくと、そのコライダー同士干渉しあうけど、
コライダーの設定の所にある、[is Trigger]にチェックを入れておくとお互い押し合わないようになるけれど、メソッドだけ呼ばれるので、そこで自分の好きな処理を入れることができます。
その場合呼ばれるのはvoid OnTriggerEnter(Collider _collider) です。

要するにis Trigger入れて使う場合とそうでない場合で呼ばれるメソッド名が違うので、勘違いして眠いのにイライラしないでね!という事です。僕のことです。


そろそろ疲れてきたね。
けど、これを見てくれてる美少女の為に頑張るね。


①触れた物を吹き飛ばすスクリプトを書く

コード張ります。
相変わらずうまい張り方わかりません。
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;

  4. //触れたものすべて吹き飛ばすやばいスクリプト
  5. public class AttackForce : MonoBehaviour {
  6.     public GameObject _player;
  7.     public string _earthTag;         //除外するタグの名前
  8.     public float _forceHeight;       //吹き飛ばす高さ調整値
  9.     public float _forcePower;        //吹き飛ばす強さ調整値

  10.     //何かに触れたときに呼ばれる
  11.     void OnTriggerEnter(Collider _collider) {
  12.         //除外対象のタグがついたゲームオブジェクトだったら何もしない
  13.         if (_earthTag == _collider.tag) {
  14.             return;
  15.         }
  16.         //自分の体は除外
  17.         if (_player && _player == _collider.gameObject) {
  18.             return;
  19.         }

  20.         //攻撃状態じゃなかったら何もしない
  21.         PlayerCtrl player = _player.GetComponent<PlayerCtrl>();
  22.         if (PlayerCtrl.STATE_PLAYER.PANCH != player.GetAnimState()) {
  23.         
  24.         //ぶつかった相手からRigitBodyを取り出す
  25.         Rigidbody otherRigitbody = _collider.GetComponent<Rigidbody>();
  26.         if (!otherRigitbody) {
  27.             return;
  28.         }

  29.         //吹き飛ばす方向を求める(プレイヤーから触れたものの方向)
  30.         Vector3 toVec = GetAngleVec(_player, _collider.gameObject);

  31.         //Y方向を足す
  32.         toVec = toVec + new Vector3(0, _forceHeight, 0);

  33.         //ふきとべええ
  34.         otherRigitbody.AddForce(toVec * _forcePower, ForceMode.Impulse);
  35.     }


  36.     Vector3 GetAngleVec(GameObject _from, GameObject _to) {
  37.         //高さの概念を入れないベクトルを作る
  38.         Vector3 fromVec = new Vector3(_from.transform.position.x, 0, _from.transform.position.z);
  39.         Vector3 toVec = new Vector3(_to.transform.position.x, 0, _to.transform.position.z);

  40.         return Vector3.Normalize(toVec - fromVec);
  41.     }
  42. }

計算方法間違ってたらごめんね、おじちゃん15連勤目なんだ…
(この言い訳のために働いていたんだ!やったぞ!)

何をやってるかというと、自機から相手への角度を求めて、publicで指定しておいた上方向の調整値を加味して、さらにこれまた指定しておいた吹き飛ばし強さで吹き飛ばすって感じ。

なぜ、コライダーをつけたオブジェクトから敵への角度ではなく、プレイヤー自身から敵への角度なのかってのは、、わ、わかりますよね。
なんかもうこのブログを読んでくれる人のテンションがわからない。
ツイッターの人たち技術レベル高いからその人達には全く役に立たない内容だろうし。
はあつら。明日も仕事つら。


あ、で、なんだっけ。
ほら、コライダーから敵への角度だと、触れるタイミングによって思わぬ方向になっちゃったりするからだね。


①のスクリプトを②のArmColにつける

で、ここまでで一回動かしてみると


ん?なんか地面がぐらぐら揺れたり、思った方向に吹き飛ばなかったり、なんかおかしいぞ…

な、なんと冒頭で説明した [is Trigger]にチェックを入れていなかったのだ!な、なにいいい!!
だから冒頭にあんなに説明したのかー!エー!!

という事で、チェックいれなかったので、純粋にコライダー同士の押し合いでこんな動きになっています。意外と飛ぶね。


で、きちんとチェックを入れたのがこっち

さっきと違って、地面と手のぶつかり合いで地面が揺れたり、あらぬ方向に飛ぶのは少なくなった。
しかしまだ、手に当たったゴミ場が吹き飛んだりしてる。
原因は、


そ、そろそろ読んでくれてる人も疲れてきたよね…
もうゴールしよっか…


なにい!続きが読みたいってぇー!
そかそか、ここまで読んでくれた君はすでに僕のファンだね!
ありがとう。ありがとう。君みたいなカワイ子ちゃんの為に最後まで書くよ!うふふ



で、原因は、攻撃状態中ずっと判定してて、攻撃状態が今、
腕を振りかぶって、下ろして、までずっとなのね。
ようは長いんですね。


だから、攻撃モーション中の、どこからどこまでは、吹き飛び判定をするっていう風に絞らなきゃいけないのです。
あ、知ってるって、あ、はいすみません。もうすぐおわりますんで…

モーションの進み具合をとるには、
       AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
       stateInfo.normalizedTime
これを使います。0.0f ~ 1.0fで取れます。
なので、攻撃モーションの0.6(60%) ~ 0.8(80%)の時だけ吹き飛ばし適用!みたいな事ができます。

これを利用して下の様なメソッド作りました。
  1.     //攻撃アニメ状態の取得
  2.     public STATE_ATTACK GetAttackAnimState(TYPE_ATTACK _attackType) {

  3.         //そもそも攻撃状態じゃなければ何もしない
  4.         if (STATE_PLAYER.ATTACK != GetAnimState()) {
  5.             return STATE_ATTACK.NONE;
  6.         }

  7.         AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);

  8.         //パンチ
  9.         if (TYPE_ATTACK.PUNCH == _attackType) {
  10.             if (stateInfo.normalizedTime < 0.3f) {
  11.                 return STATE_ATTACK.PRE;

  12.             } else if (stateInfo.normalizedTime <= 0.6f) {
  13.                 return STATE_ATTACK.DONE;

  14.             } else {
  15.                 return STATE_ATTACK.AFTER;
  16.             }
  17.         }

  18.         return STATE_ATTACK.NONE;

  19.     }
攻撃モーション中の、どの位のやつなのか取ります。
STATE_ATTACKは単なるenumです。

この場合は、STATE_ATTACK.DONEが返ってきた場合だけ当たり判定をするように、

  1.         //攻撃状態じゃなかったら何もしない
  2.         PlayerCtrl player = _player.GetComponent<PlayerCtrl>();
  3.         if (PlayerCtrl.STATE_PLAYER.PANCH != player.GetAnimState()) {
ここの部分を

  1.         //攻撃状態じゃなかったら何もしない
  2.         PlayerCtrl player = _player.GetComponent<PlayerCtrl>();
  3.         if (PlayerCtrl.STATE_ATTACK.DONE != player.GetAttackAnimState(PlayerCtrl.TYPE_ATTACK.PUNCH)) {
  4.             return;
  5.         }
こうして、その他スピードとか重さとか諸々調整するとー


こうなりました。
わー! ぱちぱちー!
良かったらまた呼んでくれよな!



なんかごめん


























こんにちは。
やっぱりメモしながらだと開発進捗自体は遅くなるんですが、後で見返して分かるように、もう少しこのスタイルで続けて行こうと思います。


今日はコライダーとかの話です。
Unityをはじめてすぐ出てくるような内容で、よく玉と地面で説明してるの見かけますね。

Unityではそれぞれ目的に応じたコンポーネントをアタッチしてやるだけです。
このへん、中身を作らなくてもGUI上だけで完結できるなんてゲームエンジン様様ですよねーという奴が一番感じられる所なんじゃないでしょうか。

会社の研修で最初にこの当たりを自前で作るのがあったんですが、泣きそうになりながらやっていました。

簡単に説明すると

オブジェクト同士が当たったかどうかを判定、制御したい
 =>Collider
 
オブジェクトに重力や加速度を適用したい
 =>
Rigitbody

をアタッチしてやればよいという感じです。
だから、よくサンプルとかでColliderをつけ忘れてると、当たり判定できずに玉が床を貫通してアーレーみたいになるんですね。

間違ってても知りません。僕は雰囲気でUnityしてるんだ!


では具体的に設定していきます。

①モデルの準備
20171129_1
はい、バギーです。
アセットストアで$1くらいで色々セットで売ってたので買いました。
アセットストア強力過ぎて怖いです。


②ColliderとRigidbodyをアタッチ
20171129_2


Colliderはいくつかありまして、Box ColliderとかCupsule ColliderとかMesh Colliderとかあります。
面数が多くなるほど当たり判定をする回数が増えるので、単純に処理負荷がかかりますので、ここぞという時以外はBoxやCupsuleにしておいた方がいいと思います。

という事で、バギー君はBoxで行きます。


③Colliderの大きさ調整
20171129_202
この当たりの値を調整します。
XとかYとかの所をドラッグすると簡単に値を調整できます。
皆知ってるよね。



20171129_201
モデルの方は、選択状態で、[F]を押してフォーカスを合わせてから、Alt + ドラッグで、フォーカスを中心に回転できます。
これは最近知った。知らないとかなり辛かった…


そんな感じでいい感じにバギー君を包むように設定します。



20171129_4
こっちも同じようにします。
こっちは人型なので、Cupsule Colliderにしました。



④どすこい
20171129_5
無事2つのオブジェクトを当てると、お互い干渉しあいました。
けど、ロボ倒れた。


⑤キャラを倒れないようにする
20171129_6


このFreeze RotationのXとZにチェックを入れます。
言葉の通り、チェック入れた軸がフリーズします。
つまり、Y軸での回転以外しなくなります。

無事転ばなくなりました。


今日の内容は以上です。


ところで、あれ、Pop土曜日なのにブログ更新してるぞ?休みなのか?
と思った人、残念でした予約投稿です^o^

それではまた…


↑このページのトップヘ