AkiIroブログ

初心者なりにUnityとVRまわりのことについて書いていきます。

【C/C++】scanfでループしたので標準入力から数値を受け取ることについてまとめる

scanfでループする

以下のコードは不正な入力(例えばabc)を与えると無限ループします.

/*
    scanfに不正入力をするとループするテスト
*/
#include <stdio.h>
#include <stdlib.h>

int main(void){
    int x = 0;
    while(true){
        printf("Please input x\n");
        scanf("%d", &x);
        printf("x = %d\n", x);
        if(x == -1) break;
    }
    return EXIT_SUCCESS;
}

この原因はscanfが予期しないデータの入力によって,
バッファのデータをそのまま残して動作を終了していることにあります.

scanfは予期しない入力があると無限ループに陥る(C学習中) - 虎塚

標準入出力関数(1)

変換指定文字列で、期待していなかったデータを入力すると、 バッファのデータをそのまま残し、動作を終了してしまいます。

fflush(stdin);
scanf("%*c");

このあたりも試してバッファのクリアを狙ってみましたが,思うような動作には至りませんでした.

fgetsのあとatoiする

// 独自関数fgetiを実装する
// ほぼ問題ないがatoiが範囲外の処理ができない
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#define N 512

int fgeti(int*, FILE*);

int main()
{
    int x;
    while(true)
    {
        printf("Please input a number\n");
        if (fgeti(&x, stdin) == EOF)
        {
            continue;
        }
        printf("x = %d\n", x);
    }
}

int fgeti(int* p, FILE *fp)
{
    char buf[N];
    if (fgets(buf, N, fp) == NULL)
    {
        return EOF;
    }
    *p = atoi(buf);
    if(*p == 0 && (strcmp(buf, "0\n")!=0))
    {
        return EOF;
    }
    return 1;
}

こうすることで大抵の処理には耐えますが,範囲(INT_MIN〜INT_MAX)外と
123abcのような頭に数字のついた入力に耐えません.
文字列から数値への変換 - forest book
なお,atoiは変換できない文字列の際に0を返すことにも注意です.
【C言語】atoi関数|ato関数群(atoi, atol, atoll, atof)完全解説 | MaryCore

fgetsのあとstrtolする

atoiよりエラーに強いstrtolを使って
strtoiという関数を定義しました.

strtolは第二引数のendptrに変換出来ない文字列を格納します.
C言語関数辞典 - strtol

// 独自関数fgetiを実装する
// atoiからstrtoi(自作)に変更
// これでどんな入力にも対応できる...はず
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#define N 512

int fgeti(int*, FILE*);
int strtoi(const char*);

int main()
{
    int x;
    while(true)
    {
        printf("Please input a number\n");
        if (fgeti(&x, stdin) == EOF)
        {
            continue;
        }
        printf("x = %d\n", x);
    }
}

int fgeti(int* p, FILE *fp)
{
    char buf[N];
    if (fgets(buf, N, fp) == NULL)
    {
        return EOF;
    }
    *p = strtoi(buf);
    if(*p == 0 && (strcmp(buf, "0\n")!=0))
    {
        return EOF;
    }
    return 1;
}

int strtoi(const char* str)
{
    char* endptr;
    long lstr = strtol(str, &endptr, 10);
    if (*endptr != '\0' || lstr < INT_MIN || INT_MAX < lstr)
    {
        return 0;
    }
    return (int)lstr;
}

少なくともscanfで直接受け取るよりは堅牢かと思います.

【Unity】CloudBuildをつかって作ったiOSアプリ(IPA)をコマンドラインでiTunesConnectにアップする.

ITunesConnectにアップロードする

p12ファイルとProvisioning Profileの作成は終わっていて,CloudBuildは動いているものとします.
f:id:AkiIro:20180121022605p:plain
こんな感じですね.

こういう状況になっていない人は,こちらの記事様がわかりやすいかと思います. macdays.hatenablog.com blog.naichilab.com

今回はCloudBuildからダウンロードできるIPAをiTunesConnectにアップロードする方法です.
CloudBuildを行うとIPAファイルをダウンロードできますが,直接iTunesConnectにアップロードする仕組みはないようです.

altool

altoolというものを使います.これはXcodeに付属しています.
簡単に使えるようにPATHにいれてしまうことをおすすめします.
パス名の途中にスペースがあるのでご注意ください.

export PATH=$PATH:/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/

ターミナルを開き直すか,以下のコマンドで更新します.

source ~/.bash_profile

これでaltoolが簡単に使えるようになりました.

アップロード

altool --upload-app -f NAME.ipa -u MAIL@ADDRESS -p PASS

大文字のところは適宜変えてください.
なお,Windowsの方はApplication Loaderというものを使う必要があるようです.(一ヶ月だけ無料です.)

ここで,PASSの部分ですが,App用パスワードが必要です.
設定していない方はこちらを参考に用意してください. support.apple.com

以上になります.

参考

blog.kishikawakatsumi.com

【Unity】キャラクターをポイントクラウド化するスクリプト

Meshtopologyを変更する

実際はMeshtopologyを変更するだけです.
こんなことができます.
f:id:AkiIro:20171118232721p:plain
youtu.be

つまり可愛くはなりません.

使い方

f:id:AkiIro:20171118231351p:plain
アタッチしてスクリプトの上で右クリックしてGo.
以上です.
終わったらRemoveComponentしてください.

ソースコード

Modify MeshTopology with one button.

解説みたいなもの

既存のモデルのメッシュをコピーしてから,トポロジを変更します.
コピーしないと既存のモデルのメッシュが壊れます.壊しました.
元のモデルのとこにメッシュのコピーが作られます.
点の大きさはまた別の処理が要ります.
このあたりです.

notargs様 wordpress.notargs.com

参考

Unity Forum
https://forum.unity.com/threads/how-do-i-duplicate-a-mesh-asset.35639/

【Unity】uGUIのImageに空間フィルタをかけるシェーダー

uGUIのImageに対するフィルタ処理をGPUでやれます.ご自由にお使いください.

フィルタ処理

f:id:AkiIro:20171115033633p:plain ソーベルフィルタの例
マテリアルのプロパティを変えれば色々な3x3の空間フィルタがかけられます.

ソースコード

Unity ShaderLab "Custom/UI/Filtered" uGUIのImageに空間 ...

インスペクタ

f:id:AkiIro:20171115033906p:plain Cutoutを0以外にすれば明るさが低い部分を切り捨てられます.

シェーダーだけで完結するためにマトリクスを作る部分で無駄があります.
ご了承ください.

【Unity】AudioListerを録音してwavにする

録音する

Unity内で鳴っている音をそのまま録音します.
ソースコードはこちら.

using System;
using System.IO;
using System.Text;
using UnityEngine;

[RequireComponent(typeof(AudioListener))]
public class AudioListener2Wav : MonoBehaviour {
    private int m_outputRate = 44100;
    private bool m_isRecording = false;
    private FileStream m_stream;
    readonly private string m_fileName = "recTest.wav";
    readonly private int m_headerSize = 44;
    float[] wave = new float[1024];

    void Update ()
    {
        if (!Input.GetKeyDown(KeyCode.R)) return;
        if (m_isRecording == false)
        {
            Debug.Log("rec started",this);
            m_isRecording = true;
            startWriting(m_fileName);
        }
        else {
            m_isRecording = false;
            writeHeader();
            Debug.Log("rec stop", this);
        }
    }

    private void OnAudioFilterRead(float[] data, int channels)
    {
        if (!m_isRecording) return;
        convertAndWrite(data);
    }

    /// <summary>
    /// ストリームを0埋めして初期化
    /// </summary>
    /// <param name="name">ファイルの名前</param>
    private void startWriting(string name)
    {
        m_stream = new FileStream(name, FileMode.Create);
        var emptyByte = new byte();
        for (int i = 0; i < m_headerSize; i++) {
            m_stream.WriteByte(emptyByte);
        } 
    }

    /// <summary>
    /// 変換してストリームに書き込む
    /// </summary>
    /// <param name="dataSource">書き込むデータ</param>
    private void convertAndWrite(float[] dataSource)
    {
        Int16[] intData = new Int16[dataSource.Length];
        var bytesData = new byte[dataSource.Length*2];
        int rescaleFactor = 32767;
        for (int i = 0; i < dataSource.Length; i++) {
            intData[i] = (short) (dataSource[i] * rescaleFactor);
            var byteArr = new byte[2];
            byteArr = BitConverter.GetBytes(intData[i]);
            byteArr.CopyTo(bytesData, i * 2);
        }
        m_stream.Write(bytesData, 0, bytesData.Length);
    }

    /// <summary>
    /// ヘッダを書く
    /// 詳しくはwavのフォーマットを確認
    /// http://www.graffiti.jp/pc/p030506a.htm
    /// </summary>
    private void writeHeader()
    {
        m_stream.Seek(0, SeekOrigin.Begin);
        
        Byte[] riff = Encoding.UTF8.GetBytes("RIFF");
        m_stream.Write(riff, 0, 4);
        Byte[] chunkSize = BitConverter.GetBytes(m_stream.Length - 8);
        m_stream.Write(chunkSize, 0, 4);
        Byte[] wave = Encoding.UTF8.GetBytes("WAVE");
        m_stream.Write(wave, 0, 4);
        Byte[] fmt = Encoding.UTF8.GetBytes("fmt ");
        m_stream.Write(fmt, 0, 4);
        Byte[] subChunk1 = BitConverter.GetBytes(16);
        m_stream.Write(subChunk1, 0, 4);

        UInt16 one = 1;
        UInt16 two = 2;
        Byte[] audioFormat = BitConverter.GetBytes(one);
        m_stream.Write(audioFormat, 0, 2);
        Byte[] numChannels = BitConverter.GetBytes(two);
        m_stream.Write(numChannels, 0, 2);

        Byte[] sampleRate = BitConverter.GetBytes(m_outputRate);
        m_stream.Write(sampleRate, 0, 4);

        Byte[] byteRate = BitConverter.GetBytes(m_outputRate * 4);
        // sampleRate * bytesPerSample*number of channels, here 44100*2*2
        m_stream.Write(byteRate, 0, 4);

        UInt16 four = 4;
        Byte[] blockAlign = BitConverter.GetBytes(four);
        m_stream.Write(blockAlign, 0, 2);

        UInt16 sixteen = 16;
        Byte[] bitPerSample = BitConverter.GetBytes(sixteen);
        m_stream.Write(bitPerSample, 0, 2);

        Byte[] dataString = Encoding.UTF8.GetBytes("data");
        m_stream.Write(dataString, 0, 4);

        Byte[] subChunk2 = BitConverter.GetBytes(m_stream.Length - m_headerSize);
        m_stream.Write(subChunk2, 0, 4);

        m_stream.Close();
    }
}

同じGameObjectにAudioListenerがいないと
OnAudioFilterReadがコールされません.(ハマりました)

使い方

  1. AudioListerのついたGameObjectにアタッチ
  2. Rボタンを叩く(録画開始)
  3. 再度Rボタンを叩く(録画終了)

参考

参考にした元のソースはこちらにあります. ただしこちらはUnityScriptです.
https://forum.unity3d.com/threads/writing-audiolistener-getoutputdata-to-wav-problem.119295/

【Unity】DrawMesh初歩と動的Mesh生成

DrawMesh

APIはこれです.

docs.unity3d.com

これを使うことでアクティブなGameObjectを存在させずにMeshを描くことができます.
つまりその分軽量なわけです.

大抵は膨大な数(1,000,000とか)のパーティクルを扱う際に活躍する技術のようですが, 今回は初歩的に一つのMeshを描きます.
Meshは事前に用意せずに動的に生成してみます.
動的なMeshの生成に関してはこちらの記事がわかりやすいです.

www.shibuya24.info

コード

using UnityEngine;

public class DynamicMeshUsingDrawMesh : MonoBehaviour {
    [SerializeField]
    Material m_mat;
    Mesh m_mesh;

    void Start () {
        // 動的Mesh生成
        m_mesh = new Mesh();
        m_mesh.vertices = new Vector3[] {
            new Vector3 (-0.5f, -0.5f),
            new Vector3 (0.5f, -0.5f),
            new Vector3 (0.5f, 0.5f),
            new Vector3 (-0.5f, 0.5f),
        };
        m_mesh.uv = new Vector2[]
        {
            new Vector2(0,0),
            new Vector2(1f,0),
            new Vector2(1f,1f),
            new Vector2(0,1f),
        };
        m_mesh.triangles = new int[] {
            0, 1, 2,
            0, 2, 3,
        };
        m_mesh.RecalculateNormals();
        m_mesh.RecalculateBounds();
    }
    private void Update()
    {
        Graphics.DrawMesh(m_mesh, Vector3.zero, Quaternion.identity, m_mat, 0); 
    }
}

マテリアルはなんでもいいですが,今回は適当に赤いマテリアルをつけます. f:id:AkiIro:20170705001231p:plain

実行結果

f:id:AkiIro:20170705001008p:plain
動的にMeshを生成して描画できました.

【Slide】ゲームエンジンアーキテクチャの5章ざっくりまとめ

先日,ゲームエンジンアーキテクチャをみんなで読む機会があり,5章についてまとめましたので需要は不明ですがこちらに掲示します.
5章エンジンサポートシステムを担当しました.
Unityをはじめとして強力なエンジンが無料で使える時代に,ゲームエンジンを組む意味を確認する一助として.