Unityで円を描く

# はじめに

　Unityで線をたくさん描画したい。Processingなら簡単なものが、Unityだと、かなり煩雑になるので、AIの力を借りながら、解決。LineRendererを配列にするのではなくて、prefabにして、並べる方法をAIに提案された。

メモ：
AIはかなり時短に貢献する。アタッチ済みのスクリプトのpublicの変数をプログラムで変更しても、instanceに反映されないときは、アタッチされたスクリプトの右端から、resetを選ぶと反映される。

# 環境
chatGPT 3.5
Unity 2021.3.17f1

# まずchatGPTに聞いてみた
Q:
unityで円を描くスクリプトを教えて
A:
Unityで円を描くスクリプトを書くためには、以下の手順を実行する必要があります。
新しいスクリプトを作成します。
スクリプト内に、円を描画するための関数を定義します。
関数内で、Unityの標準的な描画機能を使用して円を描画します。
以下は、円を描画するためのシンプルなスクリプトの例です。

```c#:Circle.cs
using UnityEngine;

public class Circle : MonoBehaviour
{
    public float radius = 1f;
    public int segments = 20;
 
    void Start()
    {
        LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        lineRenderer.widthMultiplier = 0.1f;
        lineRenderer.positionCount = segments + 1;
 
        float deltaTheta = (2f * Mathf.PI) / segments;
        float theta = 0f;
 
        for (int i = 0; i < segments + 1; i++)
        {
            float x = radius * Mathf.Cos(theta);
            float y = radius * Mathf.Sin(theta);
            Vector3 pos = new Vector3(x, y, 0f);
            lineRenderer.SetPosition(i, pos);
            theta += deltaTheta;
        }
    }
}
```
このスクリプトでは、円を描画するためのLineRendererコンポーネントを追加しています。radiusとsegmentsの値を変更することで、円のサイズや円周上の点の数を調整できます。
このスクリプトをアタッチしたオブジェクトをシーンに配置すると、円が描画されます。

# 結果
ちゃんと動いた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/32a2acc7-23a1-77a1-8687-21d3e302290a.png)


# 動的に変更できるようにする
　半径と分割数がせっかくpublicなので、実行しながら変更できるようにする。こういうのはUnityならでは。

```c#:Circle.cs
using UnityEngine;

public class Circle : MonoBehaviour
{
    public float radius = 1f;
    public int segments = 20;
    public float width = 0.1f;

    LineRenderer lineRenderer;

    void Start()
    {
        lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
    }

    void Update()
    {
        lineRenderer.widthMultiplier = width;
        lineRenderer.positionCount = segments + 1;

        float deltaTheta = (2f * Mathf.PI) / segments;
        float theta = 0f;

        for (int i = 0; i < segments + 1; i++)
        {
            float x = radius * Mathf.Cos(theta);
            float y = radius * Mathf.Sin(theta);
            Vector3 pos = new Vector3(x, y, 0f);
            lineRenderer.SetPosition(i, pos);
            theta += deltaTheta;
        }

    }

}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/5c879d73-49a6-ee6a-152b-fcd0f7f94ccf.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/bd5ada2c-2b52-98d3-63e1-490671e73a5a.png)

# １０個の円を並べて描いてください
と、chatGPTにお願いする。
lineRendererは一筆書きの使い方なので、複数のlineRendererが必要になる。lineRendererを配列で用意するのかなと思っていたが、AIの提案では、次のようにPrefabを使うように言われた。

※提案されたスクリプトは、未完成だったので、ローカル座標で描画するようにして、移動がAIの意図どうりになるように若干の修正したものを掲載します。

------

10個の円を並べて描くには、以下の手順を実行します。

1. 円のプレハブを作成します。
2. スクリプトを作成して、円のプレハブを複数生成して並べます。
以下は、円のプレハブを作成するスクリプトの例です。

```c#:Circle.cs
using UnityEngine;

public class Circle : MonoBehaviour
{
    public float radius = 1f;
    public int segments = 20;
    public float width = 0.1f;

    LineRenderer lineRenderer;

    void Start()
    {
        lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        lineRenderer.loop = true;
        lineRenderer.useWorldSpace = false; // local space
    }

    void Update()
    {
        lineRenderer.widthMultiplier = width;
        lineRenderer.positionCount = segments + 1;

        float deltaTheta = (2f * Mathf.PI) / segments;
        float theta = 0f;

        for (int i = 0; i < segments + 1; i++)
        {
            float x = radius * Mathf.Cos(theta);
            float y = radius * Mathf.Sin(theta);
            Vector3 pos = new Vector3(x, y, 0f);
            lineRenderer.SetPosition(i, pos);
            theta += deltaTheta;
        }
    }
}
```

このスクリプトをアタッチしたオブジェクトをプレハブとして保存します。
次に、以下のスクリプトを作成して、円のプレハブを複数生成して並べます。

```c#:CircleArray.cs
using UnityEngine;

public class CircleArray : MonoBehaviour
{
    public GameObject circlePrefab;  // 円のプレハブ
    public int circleCount = 10;     // 並べる円の数
    public float circleSpacing = 2f; // 円同士の間隔

    void Start()
    {
        for (int i = 0; i < circleCount; i++)
        {
            // 円の生成と位置の設定
            Vector3 pos = new Vector3(i * circleSpacing, 0f, 0f);
            GameObject circle = Instantiate(circlePrefab, pos, Quaternion.identity);
            circle.transform.parent = transform;
        }
    }
}
```
　このスクリプトでは、circlePrefabに先程作成した円のプレハブを設定し、circleCountで指定した数だけ、円を生成して並べます。円同士の間隔はcircleSpacingで指定します。
　このスクリプトをアタッチしたオブジェクトをシーンに配置すると、10個の円が並んで描画されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/3d94a2ed-c248-d589-88c2-52811ac1cf33.png)

空のprefabを作れることを知った。

# 背景を黒くしたい
Unityで背景を黒くするには、カメラの背景色を黒に設定します。

カメラを選択し、Inspectorウィンドウを開きます。
「Clear Flags」を「Solid Color」に変更します。
「Background」のカラーピッカーを開き、黒に設定します。
以下は、カメラの背景色を黒に設定するスクリーンショットです。

Unity背景色を黒に設定する（※ここには、なにか表示されるべきものが、表示できてない？）

これで、シーンを再生すると、背景が黒くなります。

-----
AIは会話の流れを汲み取りながら、ちゃんとUnityの背景色の話であることを理解している。

# 半径１の円周上に沿って、半径１の円を１００個並べて描いてください。

半径1の円周上に沿って、半径1の円を100個並べるには、以下の手順を実行します。

1. スクリプトを作成して、円のプレハブを生成します。
2. スクリプトで、半径1の円周上の位置を計算して、円を並べます。
以下は、円を描画するスクリプトの例です。

（※微妙に修正しています）

```c#:Circle.cs
using UnityEngine;

public class Circle : MonoBehaviour
{
    public float radius = 1f;
    public int segments = 20;
    public float width = 0.1f;

    LineRenderer lineRenderer;

    void Start()
    {
        lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        lineRenderer.loop = true;
        lineRenderer.useWorldSpace = false; // local space
    }

    void Update()
    {
        lineRenderer.widthMultiplier = width;
        lineRenderer.positionCount = segments + 1;

        float deltaTheta = (2f * Mathf.PI) / segments;
        float theta = 0f;

        for (int i = 0; i < segments + 1; i++)
        {
            float x = radius * Mathf.Cos(theta);
            float y = radius * Mathf.Sin(theta);
            Vector3 pos = new Vector3(x, y, 0f);
            lineRenderer.SetPosition(i, pos);
            theta += deltaTheta;
        }
    }
}

```

このスクリプトをアタッチしたオブジェクトをプレハブとして保存します。

次に、以下のスクリプトを作成して、円を並べます。

```c#:CircleArray.cs
using UnityEngine;

public class CircleArray : MonoBehaviour
{
    public GameObject circlePrefab;  // 円のプレハブ
    public int circleCount = 100;    // 並べる円の数
 
    void Start()
    {
        for (int i = 0; i < circleCount; i++)
        {
            // 円の生成と位置の設定
            float theta = (2f * Mathf.PI / (float)circleCount) * i;
            Vector3 pos = new Vector3(Mathf.Cos(theta), Mathf.Sin(theta), 0f) * 1f;
            GameObject circle = Instantiate(circlePrefab, pos, Quaternion.identity);
            circle.transform.parent = transform;
        }
    }
}
```
このスクリプトでは、円の数をcircleCountで指定し、円周上の位置を計算して、円を生成して並べます。円周上の位置は、半径1の円の場合、x座標がcos(theta)、y座標がsin(theta)となります。

このスクリプトをアタッチしたオブジェクトをシーンに配置すると、半径1の円周上に沿って、半径1の円が100個並んで描画されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/a02eb8d6-5ca1-2810-1613-eeb5e54db59e.png)

# よくある曼荼羅アートに使える下絵（例）
空のオブジェクトを２つ作って、それぞれに、スクリプトをアタッチして、実行後にパラメータ調整

```c#:LineArt.cs
using UnityEngine;

public class LineArt : MonoBehaviour
{
    public float radius = 5f;
    public int segments = 72;
    public float dTH = 145;
    public float width = 0.03f;

    LineRenderer lineRenderer;

    void Start()
    {
        lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        lineRenderer.loop = false;
        lineRenderer.useWorldSpace = false; // local space
    }

    void Update()
    {
        lineRenderer.widthMultiplier = width;
        lineRenderer.positionCount = segments + 1;

        for (int i = 0; i < segments + 1; i++)
        {
            float x = radius * Mathf.Cos(i* dTH * Mathf.Deg2Rad);
            float y = radius * Mathf.Sin(i* dTH * Mathf.Deg2Rad);
            Vector3 pos = new Vector3(x, y, 0f);
            lineRenderer.SetPosition(i, pos);
        }
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510257/29606cca-41fb-5003-5280-8cdb8a5d4ec3.png)


# スクリーンショットを保存したい
Unityでスクリーンショットを保存するには、以下の手順を実行します。

1. スクリプトを作成して、スクリーンショットを撮影します。
2. スクリプトで、スクリーンショットを保存します。
以下は、スクリーンショットを撮影して保存するスクリプトの例です。
```c#:Screenshot.cs
using UnityEngine;
using System.IO;

public class Screenshot : MonoBehaviour
{
    public string fileName = "screenshot.png";  // 保存するファイル名
 
    public void CaptureScreenshot()
    {
        // スクリーンショットを撮影
        string filePath = Path.Combine(Application.persistentDataPath, fileName);
        ScreenCapture.CaptureScreenshot(filePath);
    }
}
```
このスクリプトをアタッチしたオブジェクトをシーンに配置します。

スクリーンショットを撮影するには、スクリプトのCaptureScreenshot()メソッドを呼び出します。このメソッドは、スクリーンショットを撮影して、指定したファイル名で保存します。ファイル名は、Application.persistentDataPathで指定したパスに保存されます。

以下は、スクリーンショットを撮影して保存するコード例です。


```c#:Example.cs
public class Example : MonoBehaviour
{
    public Screenshot screenshotScript;
 
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            screenshotScript.CaptureScreenshot();
            Debug.Log("Screenshot saved.");
        }
    }
}
```
このスクリプトでは、スペースキーを押すとスクリーンショットを撮影して保存します。保存されたファイル名は、screenshot.pngです。保存されたファイル名を確認するには、以下のコードを使用します。

```c#:
string filePath = Path.Combine(Application.persistentDataPath, "screenshot.png");
Debug.Log("Screenshot saved at: " + filePath);
```
これで、スクリーンショットを撮影して保存することができます。

# スクリーンショットをpdfで保存したい
Unityでは、直接PDF形式でスクリーンショットを保存することはできません。しかし、PDFファイルに変換することができるライブラリを使用することができます。以下の手順に従って、スクリーンショットを撮影し、PDFファイルに変換して保存するスクリプトを作成できます。

1. iTextSharpライブラリをインストールします。
iTextSharpは、PDFファイルを生成するためのC#用のオープンソースライブラリです。NuGetパッケージマネージャーからiTextSharpをインストールします。

2. スクリーンショットを撮影し、画像ファイルとして保存します。
スクリーンショットを撮影して、画像ファイルとして保存するには、次のようなコードを使用します。
```c#:
public void CaptureScreenshot()
{
    string fileName = "screenshot.png";
    string filePath = Path.Combine(Application.persistentDataPath, fileName);
    ScreenCapture.CaptureScreenshot(filePath);
}
```
このコードは、スクリーンショットを撮影し、指定したファイル名で画像ファイルを保存します。ファイルパスは、アプリケーションの永続的なデータパスに保存されます。

3. 画像ファイルをPDFファイルに変換して保存します。
画像ファイルをPDFファイルに変換するには、iTextSharpライブラリを使用します。以下のコードを使用して、画像ファイルをPDFファイルに変換して保存することができます。

```c#:CircleArray.cs
public void ConvertToPdf(string imagePath, string pdfPath)
{
    Document pdfDoc = new Document(PageSize.A4, 10f, 10f, 10f, 0f);
    PdfWriter.GetInstance(pdfDoc, new FileStream(pdfPath, FileMode.Create));
    pdfDoc.Open();

    iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(imagePath);
    pdfDoc.Add(image);

    pdfDoc.Close();
}
```

このコードは、画像ファイルをPDFファイルに変換して保存します。変換されたPDFファイルは、指定されたファイルパスに保存されます。

以上の手順を組み合わせると、スクリーンショットを撮影し、PDFファイルに変換して保存することができます。以下は、完全なスクリプトの例です。

```c#:Screenshot.cs
using UnityEngine;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

public class Screenshot : MonoBehaviour
{
    public string screenshotName = "screenshot.png";  // スクリーンショットのファイル名
    public string pdfName = "screenshot.pdf";  // PDFファイルのファイル名

    public void CaptureScreenshot()
    {
        // スクリーンショットを撮影し、画像ファイルとして保存する
        string filePath = Path.Combine(Application.persistentDataPath, screenshotName);
        ScreenCapture.CaptureScreenshot(filePath);
    }

    public void ConvertToPdf()
    {
        // 画像ファイルをPDFファイルに変換して保存する
        string imagePath = Path.Combine(Application.persistentDataPath, screenshotName);
        string pdfPath = Path.Combine(Application.persistentDataPath, pdfName);
    
        Document pdfDoc = new Document(PageSize.A4, 10f, 10f, 10f, 0f);
        PdfWriter.GetInstance(pdfDoc, new FileStream(pdfPath, FileMode.Create));
        pdfDoc.Open();
    
        iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(imagePath);
        pdfDoc.Add(image);
    
        pdfDoc.Close();
    }
    
    public void SaveScreenshotAsPdf()
    {
        // スクリーンショットを撮影し、PDFファイルに変換して保存する
        CaptureScreenshot();
        ConvertToPdf();
    }
}

```

この例では、CaptureScreenshot()関数でスクリーンショットを撮影し、ConvertToPdf()関数で画像ファイルをPDFファイルに変換して保存しています。SaveScreenshotAsPdf()関数を呼び出すことで、スクリーンショットを撮影し、PDFファイルに変換して保存することができます。

注意点として、iTextSharpライブラリの使用には、ライセンス契約が必要となる場合があります。使用前にライセンスの確認を行ってください。