LiMMの溶解記録

脳を溶かすようなゲームを。

おっぱい揺らそう

みんな大好き私も大好きな『乳揺れ』を簡単に。

乳揺れしたい。

2Dゲームを作っていて、もっと楽に乳揺れが出来たらなぁと思いませんか?

私は思います。あと私の知り合いも全員思っているし、あなたの知り合いも全員思っています。

知らないのはあなただけ。

例えば移動から停止の時にプルルンとなったりしたら嬉しいなぁとか。

 

そういうのはコンポーネントとして作っちゃえば、なんかいろいろ使えますよね。

ということで作りました。

正直クソみたいなコードなのでそのまま下の方に貼っておきます。

その前にどんな感じになるかだけ見てください。

 

なかなかパインパインしていませんか?

特に上昇する時にちょっと細くなるところとか、そこからまた形が戻るところとか、こう、揉みたくなりませんか?

私はなります。あと私の知り合いも全員揉みたくなってるし、あなたの知り合いも全員なっています。

あなたもなります。

副産物

 使い方次第ではこういうロボット系にも使えそうですね(ハナホジ)

 

ではお待ちかねのソースコードですが、リファクタリングとかパラメータの洗い出しなおしとか全くやってないコードなので、あなたがやってください。

私の知り合いも全員やってるし、あなたの知り合いも全員やっています。

やってないのはあなただけ。

そんなのを晒すなんて、例えてみれば私室のゴミ箱の中身を友達に見せるようなものです。私の勇気を末代まで讃えてください。え?もう末代?

 

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

public class SpriteSway : MonoBehaviour {
[SerializeField]
Vector2 m_minScale = Vector2.zero;

[SerializeField]
Vector2 m_maxScale = Vector2.one;

[SerializeField]
float m_minAngle = -10.0f;

[SerializeField]
float m_maxAngle = 10.0f;

[SerializeField]
Vector2 m_minTransrate = Vector2.zero;

[SerializeField]
Vector2 m_maxTransrate = Vector2.zero;

const float m_maxPowerLength = 1.0f;

Vector3 m_defaultScale = Vector3.zero;
Vector3 m_defautlRotation = Vector3.zero;
Vector3 m_defaultPosition = Vector3.zero;

Vector3 m_power = Vector3.zero;
Vector3 m_powerWork = Vector3.zero;
Vector3 m_prevPosition = Vector3.zero;
float m_sinValue = 0.0f;

enum State {
STATE_IDLE,
STATE_MOVE,
STATE_SWAY,
}
State m_state = State.STATE_IDLE;

public void AddImpulse(Vector3 power)
{

}

// Use this for initialization
void Start () {
m_prevPosition = transform.position;
m_defaultScale = transform.localScale;
m_defautlRotation = transform.eulerAngles;
m_defaultPosition = transform.localPosition;
m_minTransrate += (Vector2)m_defaultPosition;
m_maxTransrate += (Vector2)m_defaultPosition;
}

// Update is called once per frame
void Update () {
switch (m_state) {
case State.STATE_IDLE: {
if (IsMove()) {
m_state = State.STATE_MOVE;
}
break;
}

case State.STATE_MOVE: {
if (UpdatePower()) {
m_state = State.STATE_SWAY;
m_powerWork = m_power;
m_sinValue = Mathf.PI;
}
break;
}

case State.STATE_SWAY: {
if (ReducePower()) {
m_state = State.STATE_IDLE;
}
Sway();

if (IsMove()) {
m_state = State.STATE_MOVE;
}
break;
}
}
UpdateShape();
m_prevPosition = transform.position;
Debug.DrawLine(transform.position, transform.position + m_power, Color.red);
}

bool UpdatePower()
{
Vector3 diff = m_prevPosition - transform.position;
if (diff.magnitude < 0.001f) {
return true;
}
AddPower(diff * 2.0f);
return false;
}

void AddPower(Vector3 power)
{
m_power += power;
float length = Mathf.Min(m_power.magnitude, m_maxPowerLength);
m_power = m_power.normalized * length;
}

bool ReducePower()
{
m_powerWork *= 0.98f;
if (m_powerWork.magnitude < 0.001f) {
m_powerWork = Vector3.zero;
return true;
}
return false;
}

void Sway()
{
m_sinValue += 2.0f * Mathf.PI * 2.0f * Time.deltaTime;
m_power = m_powerWork * Mathf.Sin(m_sinValue);
}

bool IsMove()
{
Vector3 diff = m_prevPosition - transform.position;
if (diff.magnitude > 0.001f) {
return true;
}
return false;
}

void UpdateShape()
{
// 回転.
{
Vector3 rotation = transform.eulerAngles;
float power = m_power.x;
float ratio = Mathf.Abs(power) / m_maxPowerLength;
if (power > 0.0f) {
rotation.z = m_defautlRotation.z + (m_maxAngle - m_defautlRotation.z) * ratio;
} else if (power < 0.0f) {
rotation.z = m_defautlRotation.z - (m_defautlRotation.z - m_minAngle) * ratio;
}
transform.eulerAngles = rotation;
}

// スケール.
{
Vector3 scale = transform.localScale;
float power = m_power.y * -1.0f;
float ratio = Mathf.Abs(power) / m_maxPowerLength;
if (power > 0.0f) {
scale.y = m_defaultScale.y + ((m_maxScale.y - m_defaultScale.y) * ratio);
scale.x = m_defaultScale.x - ((m_defaultScale.x - m_minScale.x) * ratio);
} else if (power < 0.0f) {
scale.y = m_defaultScale.y - ((m_defaultScale.y - m_minScale.y) * ratio);
scale.x = m_defaultScale.x + ((m_maxScale.x - m_defaultScale.x) * ratio);
}
transform.localScale = scale;
}

// 位置.
{
Vector3 pos = transform.localPosition;
{
float ratioX = Mathf.Abs(m_power.x) / m_maxPowerLength;
if (m_power.x < 0.0f) {
pos.x = m_defaultPosition.x - ((m_defaultPosition.x - m_minTransrate.x) * ratioX);
} else if (m_power.x > 0.0f) {
pos.x = m_defaultPosition.x + ((m_maxTransrate.x - m_defaultPosition.x) * ratioX);
}
}

{
float ratioY = Mathf.Abs(m_power.y) / m_maxPowerLength;
if (m_power.y < 0.0f) {
pos.y = m_defaultPosition.y - ((m_defaultPosition.y - m_minTransrate.y) * ratioY);
} else if (m_power.y > 0.0f) {
pos.y = m_defaultPosition.y + ((m_maxTransrate.y - m_defaultPosition.y) * ratioY);
}
Debug.Log("ratioY : " + ratioY);
Debug.Log("m_power.y : " + m_power.y);
Debug.Log("m_defaultPosition.y : " + m_defaultPosition.y);
}
transform.localPosition = pos;
}
}
}

 

インデントが無いのは引用で貼り付けたらスペースが全部消えてしまったためであり、普段からこんなモノを書いている訳じゃあないです。許してください。あなたの知り合いも全員許していますので。

 

以上です。

よろしくお願い致します。

Unityで.pngを読み込むと変な色補間が入るのですが!?

ドットゲー作ってますか?

Unityやってますか? ドットのゲームとか作りますか? こんなこと起きませんか?

「やった! いい感じに打てたぞ!」

f:id:littlemeltmachine:20180723231322p:plain

pngで出力して、Unityにインポートして、SpriteEditorを開いて……ん!?」

f:id:littlemeltmachine:20180723232732p:plain

せっかく可愛く打ったドットがめちゃくちゃになってる!? Unity許さん。俺はUEで行く!

どうか待って頂きたい。これはちゃんと修正できます。

間違った解決策

まず私がやっていた、大いに間違った解決方法です。

それは単純に、元の画像のサイズを大きくするというものでした。

例えば64x64のドット絵であれば、拡大して128x128で出力するという感じです。

一見するとちゃんと出ているように見えますが、よく見るとちょっとだけ絵がつぶれています。

f:id:littlemeltmachine:20180723234306p:plain

しかしこれは、不具合の理由を考えない愚行の行きつく先にある、間違った解決方法です。

こんなのは解決とは言えません。やめようね!

ということでちょっと調べてみました。

原因はデータの圧縮

Unityにデータを取り込む時、自動で圧縮してくれています。

しかしこの画像を軽くするための処理が、今回はドットを潰してしまっていました。

もちろん、これはデータサイズを抑えてくれる役割があるので基本的に従うといいと思います。しかし1ピクセル1ピクセルが大事なドット絵では一切圧縮して欲しくありませんよね。

じゃあそうなると解決策は一つしかありません。

 

圧縮をしないようにしようね

しかし自動で圧縮されているものをどうしたらコントロールできるのでしょうか。

ユニティ・テクノロジー社に殴り込みをかける?

それは最後の手段です。今回は、非常に簡単に対応できますのでやめましょう。

答えは画像データのInspectorにあります。

f:id:littlemeltmachine:20180723235511p:plain

赤線部分『Compression』(訳:圧縮)

ここをいじればいいワケです。

現在Normal Qualityとなっていますが、これをNoneに変えます。

すると……

f:id:littlemeltmachine:20180724000158p:plain

ドットが綺麗に出ました~!

となるわけです。

え? 知ってた?

 

現場からは以上です。

2Dゲーム空間に文字を出したい[その1]

痒いところに手が届かない

Unityのデバッグ機能は使っていますか?

Debug.Log()でコンソールに文字を出せます。

Debug.Line()でSceneビューに線が引けます。

ところでSceneビューやGameビューにデバッグ文字出したいですよね?

しかしそんな機能はありません。なので作りましょう。

[Unity 2017.3.0f3] 

完成予想図

f:id:littlemeltmachine:20180711225324p:plain

意外と簡単

調べたところ、3D空間上に文字を出せるものがありました。

このコンポーネントの名は『TextMesh』

空のオブジェクトにコンポーネントをアドしたところ、いい感じに出せました。

やった~

f:id:littlemeltmachine:20180711231311p:plain

が・・・駄目・・・! 罠っ・・・!!

f:id:littlemeltmachine:20180711231505p:plain

おわかりいただけるだろうか。

そう、スプライトより手前に描いてくれていません。

どうやらOrderInLayer=0で表示されているようです。というより、これは内部的には完全にスプライトですね。スプライトをカメラに合わせて変形させて、3D空間に置けますよ~というものみたいです。

スプライトなので、くっつけてるオブジェクトを他の3Dオブジェクト(球体)より奥にもっていっても文字が描画されます。

逆に言うと他の2D表示物をOrderInLayer=0より奥、つまりマイナスにすれば出るんじゃないかと思うのですが、そんなことやってられませんよね。

 

つまり2DゲームにおいてはTextMeshはスプライトと干渉してしまい、実力を発揮できないということですね。 ペッ!

2Dで出そう

ということでUIのTextで出すことにしました。

UI/TextをあらかじめPrefabにして……

ざっくりこんな感じのクラスを作って……(ちなみにinstanceIDを保持しているのは、Update()内でDrawTextを呼ばれる想定だからです)

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

public class Debug2DTextManager : MonoBehaviour {
    [SerializeField]
    GameObject m_canvas = null;

    [SerializeField]
    string m_prefabPath = "";

    class TextData {
        public GameObject textObject = null;
        public int instanceID = -1;
    }
    List<TextData> m_texts = new List<TextData>();

    public void DrawText(string text, Vector3 position, int instanceID)
    {
        // 3D座標をスクリーン座標に変換.
        position = Camera.main.WorldToScreenPoint(position);
        var data = m_texts.Find(a => a.instanceID == instanceID);
        if (data != null) {
                // 既存のテキストの情報を書き変える.
                data.textObject.transform.position = position;
                data.textObject.GetComponent<Text>().text = text;
        } else {
                // 新しいテキストオブジェクトを作る.
                TextData addData = new TextData();
                addData.textObject = (GameObject)Instantiate(Resources.Load(m_prefabPath), position, Quaternion.identity, m_canvas.transform);
                addData.textObject.GetComponent<Text>().text = text;
                addData.instanceID = instanceID;
                m_texts.Add(addData);
        }
    }
}

 うん、いい感じ!

f:id:littlemeltmachine:20180712002608p:plain

しかし当然、これはGameビューでしか見れません。デバッグで使いたいので、どちらかというとGameビューよりもSceneビューで見たい!

どうやって解決しようかなぁ。

現在模索中です。

[その2]へ続く。

2Dゲーム制作中、Sceneビューでカメラを寄せると表示が消える!?

あれ? 表示が消える!?

あああああああああああああ(ブリブリブリブリ

極限脱糞全裸大発狂!!!!!!!!

 

みなさん、こんな経験はないだろうか。

このゲーム2Dで作ってるけど、3Dで見たらどうかしら?

f:id:littlemeltmachine:20180710223820g:plain

おほーっ! いつもと違う感じ。たーのしー!

ふぅ……さて、2Dに戻すか。

f:id:littlemeltmachine:20180710224303g:plain

あれ?????? 寄……寄れない????

あああああああああああああ(ブリブリブリブリ

極限脱糞全裸大発狂!!!!!!!!(回収)

直し方は簡単

Hierarchyビューの任意のオブジェクトをダブルクリック(カメラ注視)するだけ。

f:id:littlemeltmachine:20180711001923g:plain

また寄れるようになりました!!

え? 知ってた?

はい。

以上です。

音声を文章にしよう[その2]

やることがない

先日の[その1]で紹介したこの記事ですが、

qiita.com

音声認識までには罠があったのですが、それ以降は罠はありませんでした。

ボイロにテキストを転送する部分は記事のままで動きます。

やったね。

おわり。

 

 

 

でもやっぱリアルタイム変換したいなぁ。

音声を文章にしよう[その1]

ゆかりさんになりたい

友人とボイチャしてる時にふと、音声認識して文章にしてボイロに読ませたら、それってもう私がボイロになることなのでは????と思い、なってみることにしました。

どうせやってる人いるだろうなぁと思ったらQiitaにいい感じにまとめられてるかたがいたので、私もやることにしました。

そんな中ちょっと躓くことがあったので記事にします。

私、ゆかりさんになります。

qiita.com

DictationRecognizerを使おう

Qiitaの記事の通り、Unityで実現可能っぽいです。正確にはUnityからWindowsの機能を呼び出しているみたいなので、残念ながらMacはNG。(そしてWinsows10じゃなくてもNG……)

DictationRecognizerを使います。リファレンスを見たら全てわかるので、どうぞ。

docs.unity3d.com

当然の権利のように出るエラー

第一のエラー

まず出たエラーがこれ。

INTERNAL_CALL_Create is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead

なんじゃこりゃ。コンストラクタじゃなくてAwake()かStart()の中で初期化しろ?

その時のコードが以下。

~~~

public class VoiceToText : MonoBehaviour  {

    DictationRecognizer m_recognizer = new DictationRecognizer();

    void Start () {
        m_recognizer.InitialSilenceTimeoutSeconds = 10.0f;

~~~

これの DictationRecognizer m_recognizer = new DictationRecognizer();がいけなかった。

ので、newをStart()に移動。

~~~

public class VoiceToText : MonoBehaviour {

    DictationRecognizer m_recognizer = null;
    void Start () {
        m_recognizer = new DictationRecognizer();
        m_recognizer.InitialSilenceTimeoutSeconds = 10.0f;

~~~

これでOK。

第二のエラー

無事にビルドエラーも取れ実行。しかし即エラー発生。

ERROR: Dictation support is not enabled on this device (see 'Get to know me' in Settings > Privacy > Speech, inking, & typing) [Operation has failed with error 0x80045509: (null)]

このデバイスのDictationのサポートが有効になっていません。は????

これは、音声認識Windows側の機能を使っていて、その機能がオンになっていないといったエラーでした。

音声認識 - UWP app developer | Microsoft Docs

Web サービスの制約を使用するには、[設定] -> [プライバシー] -> [音声認識、手描き入力、入力の設定] で [自分を知ってもらう] オプションをオンにして、[設定] で音声入力とディクテーションのサポートを有効にする必要があります。

なるほどなぁ。

ここでおもむろにDictationRecognizerのリファレンスを見てみましょう。

こんな一文があります。

Dictation recognizer is currently functional only on Windows 10, and requires that dictation is permitted in the user's Speech privacy policy (Settings->Privacy->Speech, inking & typing). If dictation is not enabled, DictationRecognizer will fail on Start. Developers can handle this failure in an app-specific way by providing a DictationError delegate and testing for SPERR_SPEECH_PRIVACY_POLICY_NOT_ACCEPTED (0x80045509).

 ここだけなぜか日本語翻訳されておらずつい見逃してしまったのですが、音声認識の機能をONにしろよ的なことがバッチリ書いてありました。

更に、DictationRecognizerはWindows10しか対応していないとのこと。

以下は、肝心の設定の変更方法です。

f:id:littlemeltmachine:20180708014527j:plain

f:id:littlemeltmachine:20180708014642j:plain

さーてこれでよし。

いざ実行。

とりあえず文章化はOK

ここまでやって、得た文字列をUIのTextにセットするようにします。

そして実行し、ヘッドセットに話しかけると……

f:id:littlemeltmachine:20180708015006j:plain

と、このようにしゃべった内容が表示されるようになりました。

感じた問題は、

  1. 滑舌がカスだとめちゃくちゃな当て字みたいになる
  2. しゃべり終わりを検知して音声認識するので、文章化されるまでタイムラグがある。

といったところ。

どちらもボイロにはつらそうです。というのもめちゃくちゃな漢字にされて誤字が発生してもボイロはそのまま読み上げますし、なんなら漢字の読みが変わったり、話終わらない限り読み上げられないのでチャットには不向きかもです。

でもまぁ、とりあえずは! これでOK。

[その2]では、ボイロとの連携部分をやってみようと思います。

では。

ブログ、始めました。

人生初ブログ

何事にも初めてはあります。

ライト兄弟が有人動力飛行を成功させたのは12月17日でした。

人類が初めて月に降り立ったのは7月20日でした。

 

だから何ですか。

私は7月5日にブログを始めます。

 

ゲーム制作の進捗や、嵌ったこと、いいアセット……何かあったら記事を書きます。

 

よろしくお願い致します。