ちんちろをUnityで作る

![スクリーンショット 2024-07-31 005309.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/c929b282-4d5a-0801-c476-afb691ec9d18.png)
![スクリーンショット 2024-07-31 112009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/ed7b625d-e980-9545-d121-425e2443280f.png)

# はじめに
過去に作った **ちんちろゲーム** を就活にいかせればと思い１から作り直した。
もともと未完であり、あらが目立つ出来だったので、完成された作品にしたいと思った。
個人で作成したが、ブログや記事を調べてみたり、サイトで質問するなど有識者に頼りまくった。エラーが出た際はChatGPTで解決した。

7月2日に一通り完成。それに伴い、制作過程を記録する
Assetのインポート方法やPrehab化する方法の解説などUnityに関する説明は省く

7月26日 unityroomに限定公開
下記のURLをクリックで遊べます！スマホは非対応です

https://unityroom.com/games/the-chinchiro-2004

**このゲームの目的、企画の意図**
・毎日ちんちろで遊んでいる弟に、楽しんでもらえるゲームが作りたい
・現実のちんちろに見劣りしないリアリティをだす
・大学で学んでいる数学やプログラミングの活用
・ユーザーに不快感を与えない出来栄えにする
**制作にあたって注意したこと**
・ライブラリの仕様を正しく理解し、スクリプトを書く
　要はなんとなくでコードを書かないようにする
・客観的に見る機会を設ける
・**見やすい 修正しやすい 簡潔で量が少ない**スクリプトを書く
# 制作概要
**環境**
Unity 2022.3.20f1

**使用言語**
C#

**期間**
5月後半から製作開始　ちまちま作って…
いったん7月2日で終了

**プレイ動画**

https://drive.google.com/file/d/13M4hnqUYETUTnDMuNhoAxsWRNkwQ3aro/view?usp=drive_link

## ゲームのシステムをsoloモードのみ説明
**やりたいこと**
・サイコロや皿、部屋を用意
・サイコロを3つ、皿の中に投げ入れたい
・出目を判定、役を表示

**サイコロ、皿など必要なobjectを用意**
AssetStore にてフリーのアセットを使用させてもらった

https://assetstore.unity.com/packages/3d/props/tools/dice-d6-game-ready-pbr-200151

https://assetstore.unity.com/packages/3d/props/interior/plates-bowls-mugs-pack-146682

AssetStoreから直接インポートするか、windowのPackageManagerからインポートする
ほったて小屋を作りそのなかに皿を置く
（皿とかサイコロが奈落に落っこちないようするため　Plate1枚敷くだけでも可）

皿に当たり判定を持たせるためMeshを追加

![スクリーンショット 2024-07-29 225940.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/9863582e-a331-1916-786b-ba8030695770.png)


サイコロにrightbodyをつける＆コード「 Dice Check A 」をアタッチ

![スクリーンショット 2024-07-29 225124.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/e3289eca-c805-7fc1-bafc-e18cc7d5e2f9.png)

``` DiceCheckA.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//サイコロにアタッチ
public class DiceCheckA : MonoBehaviour
{
private Transform diceTransform;
private int result;

    void Start()
{
    // ダイスのTransformを取得
    diceTransform = transform;
}

void Update()
{
    // 定期的に出目を計算
    CalculateResult();
}

private void CalculateResult()
{
    // ダイスの出目を計算
    result = GetNumber(diceTransform);
}

public int GetValue()
{
    // 上面の値を返す
    return result;
}

// 出目チェック
private int GetNumber(Transform diceTransform)
{
    int result = 0;

    // ダイスのローカル軸と世界の上向きベクトル(Vector3.up)の内積を計算
    float innerProductX = Vector3.Dot(diceTransform.right, Vector3.up);
    float innerProductY = Vector3.Dot(diceTransform.up, Vector3.up);
    float innerProductZ = Vector3.Dot(diceTransform.forward, Vector3.up);

    // 最も上向きに近い軸を判断し、それに対応するダイスの数字を決定
    if (Mathf.Abs(innerProductX) > Mathf.Abs(innerProductY) && Mathf.Abs(innerProductX) > Mathf.Abs(innerProductZ))
    {
        // X軸が一番近い
        if (innerProductX > 0f)
        {
            result = 4;
        }
        else
        {
            result = 3;
        }
    }
    else if (Mathf.Abs(innerProductY) > Mathf.Abs(innerProductX) && Mathf.Abs(innerProductY) > Mathf.Abs(innerProductZ))
    {
        // Y軸が一番近い
        if (innerProductY > 0f)
        {
            result = 2;
        }
        else
        {
            result = 5;
        }
    }
    else
    {
        // Z軸が一番近い
        if (innerProductZ > 0f)
        {
            result = 1;
        }
        else
        {
            result = 6;
        }
    }

    return result;
}

}

```

サイコロをprefab化したものを3つ用意

![スクリーンショット 2024-06-24 201703.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/476779d2-f06b-b238-e6e2-efec309418e9.png)

**オブジェクトは以下の通り**

![スクリーンショット 2024-07-29 224800.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/b101f8c9-3496-01e2-65b8-0429dfab15b6.png)

**GameObject（空のObject）に以下のコードをアタッチ**

![スクリーンショット 2024-07-29 225903.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819264/e6c6c5e6-6ea6-7c92-da7d-7c3695dd6110.png)

``` ChinchiroManager.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using TMPro;

public class ChinchiroManager : MonoBehaviour
{
    public TextMeshProUGUI resultText;

    public void EvaluateDice(int[] diceValues)
    {
        // サイコロの目を表示
        string diceString = string.Join(", ", diceValues);
        resultText.text = "サイコロの出目: " + diceString + "\n";

        // 役の判定
        string yaku = GetHand(diceValues);

        // 役の表示
        resultText.text += "役: " + yaku;
    }

    public string GetHand(int[] values) // メソッドをpublicに変更
    {
        if (values.Distinct().Count() == 1) // 全ての目が同じ
        {
            if (values[0] == 1) return "ピンゾロ";
            else return "アラシ";
        }
        else if (values.Contains(4) && values.Contains(5) && values.Contains(6))
        {
            return "シゴロ";
        }
        else if (values.Distinct().Count() == 2) // 2つの目が同じ
        {
            int remainingValue = values.GroupBy(v => v).Where(g => g.Count() == 1).First().Key;
            return remainingValue.ToString();
        }
        else if (values.OrderBy(v => v).SequenceEqual(new int[] { 1, 2, 3 }))
        {
            return "ヒフミ";
        }
        else if (values.Any(v => v == 0)) // サイコロがボウルからはみ出ている場合（0がはみ出た目を示す）
        {
            return "ションベン";
        }
        else
        {
            return "役なし";
        }
    }
}

```
``` DiceManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiceManager : MonoBehaviour
{
    public GameObject[] prefabs; // サイコロのPrefabを格納する配列
    public DiceCheckA dice1; // DiceCheckAをアタッチしたサイコロ1
    public DiceCheckA dice2; // DiceCheckAをアタッチしたサイコロ2
    public DiceCheckA dice3; // DiceCheckAをアタッチしたサイコロ3
    public ChinchiroManager chinchiro;
    private int[] rotateX = new int[3];
    private int[] rotateY = new int[3];
    private int[] rotateZ = new int[3];
    private Vector3[] positions = new Vector3[3]; // サイコロの初期位置を格納する配列
    private int[] diceValues = new int[3];

    private bool canThrowOrDeleteDice = true; // サイコロを振るか削除できるかどうかのフラグ
    private List<GameObject> currentDice = new List<GameObject>(); // 現在のサイコロを管理するリスト

    void Start()
    {
        // サイコロの初期位置を設定
        positions[0] = new Vector3(5, 5, 0);
        positions[1] = new Vector3(4.5f, 5, 0);
        positions[2] = new Vector3(4, 5, 0);

        if (dice1 == null || dice2 == null || dice3 == null)
        {
            Debug.LogError("DiceCheckAオブジェクトがアサインされていません");
            return;
        }

        if (chinchiro == null)
        {
            Debug.LogError("ChinchiroManagerオブジェクトがアサインされていません");
            return;
        }
    }

    void Update()
    {
        if (canThrowOrDeleteDice && Input.GetMouseButtonUp(0))
        {
            ThrowAllDice();

            // 一定時間待ってから結果を評価
            StartCoroutine(EvaluateDiceAfterDelay());
        }
    }

    public void ThrowAllDice()
    {
        ClearCurrentDice(); // 現在のサイコロを削除

        for (int i = 0; i < 3; i++)
        {
            // ランダムな回転値を生成
            rotateX[i] = Random.Range(0, 360);
            rotateY[i] = Random.Range(0, 360);
            rotateZ[i] = Random.Range(0, 360);

            // サイコロを生成して投げる
            GameObject dice = Instantiate(prefabs[i], positions[i], Quaternion.identity);
            Rigidbody rb = dice.GetComponent<Rigidbody>();
            rb.AddForce(-transform.right * 250);
            dice.transform.Rotate(rotateX[i], rotateY[i], rotateZ[i]);

            // サイコロにDiceCheckAをアタッチ
            if (i == 0) dice1 = dice.GetComponent<DiceCheckA>();
            if (i == 1) dice2 = dice.GetComponent<DiceCheckA>();
            if (i == 2) dice3 = dice.GetComponent<DiceCheckA>();

            currentDice.Add(dice); // リストにサイコロを追加
        }

        canThrowOrDeleteDice = false; // サイコロを振ったのでフラグをfalseにする
        StartCoroutine(ResetThrowOrDeleteCooldown()); // クールダウンを開始する
    }

    private void ClearCurrentDice()
    {
        foreach (GameObject dice in currentDice)
        {
            Destroy(dice);
        }
        currentDice.Clear();
    }

    private IEnumerator EvaluateDiceAfterDelay()
    {
        // 少し待つ（例えば2秒）
        yield return new WaitForSeconds(2.5f);

        // サイコロの結果を評価
        EvaluateDice();
    }

    private void EvaluateDice()
    {
        diceValues[0] = dice1.GetValue();
        diceValues[1] = dice2.GetValue();
        diceValues[2] = dice3.GetValue();

        chinchiro.EvaluateDice(diceValues); // ChinchiroManagerと連携して役を判別
    }

    private IEnumerator ResetThrowOrDeleteCooldown()
    {
        yield return new WaitForSeconds(3f); // 3秒待つ
        canThrowOrDeleteDice = true; // サイコロを再び振ったり削除できるようにする
    }
}

```

#### コードの概要
**DicecheckA**
サイコロの出目を計算するためのコードです。サイコロのTransformを取得し、毎フレームでその出目を計算します。CalculateResultメソッドでサイコロの現在の出目を取得し、GetValueメソッドでその値を返します。GetNumberメソッドでは、サイコロの各軸（X, Y, Z）と上向きベクトルの内積を計算し、どの面が上を向いているかを判定します。その結果に基づいて、対応する数字（1～6）を出目として返します。

**DiceManager**
このコードは、サイコロを管理し、振る機能を提供します。DiceManagerクラスは、3つのサイコロのPrefabを保持し、サイコロを投げたり、結果を評価するためのメソッドを持ちます。Startメソッドでサイコロの初期位置を設定し、必要なコンポーネントがアサインされているか確認します。Updateメソッドでは、マウスのクリックを検出してサイコロを投げるThrowAllDiceメソッドを呼び出し、EvaluateDiceAfterDelayコルーチンで一定時間後に結果を評価します。ThrowAllDiceメソッドはサイコロをランダムに回転させ、生成されたサイコロに力を加えて投げます。結果はEvaluateDiceメソッドで評価され、ChinchiroManagerに渡されます。

**ChinchiroManager**
このコードは、Unityでサイコロを使ったゲームにおける役の判定と結果表示を担当するスクリプトです。ChinchiroManagerクラスは、EvaluateDiceメソッドを通じて、サイコロの出目を受け取り、その結果を画面に表示します。DiceManagerクラスと連携し、DiceCheckAクラスが計算したサイコロの出目を評価し、役を判定します。DiceManagerがサイコロを振り、DiceCheckAが出目を計算し、その結果をChinchiroManagerに渡して役を判定し表示することで、全体のゲームフローが完結します。

***
**その他のオブジェクトは内装やUIになります**
・Main Camera
**位置**X2.25 Y4.5 Z1.8 **回転**X45 Y230 に初期位置から変更

・Point Light
**位置**Y10 **タイプ** ポイント **範囲** 80 に初期位置から変更

・house（ほったて小屋）
ただPlateを６枚つなげた立方体の小屋
Plate1枚だけでも良かったが、雰囲気出すため室内にしたかった

・player
消し忘れ　これは不要

・Canvas
Result 用の Text と pause 用の button （どちらもTMP）を配置

・furniture
テレビと書道用の額縁を部屋の中に配置
ゲームに関係ないどころか画面にうつりやしない
一応テレビは映像も流せます ←がしたくて作りました

## アップデート（実装済）
**タイトル画面、ポーズ画面の実装**
ゲームには必要不可欠といっても過言ではない。
タイトル画面はマウスで適当に書いてしまいました…
モード選択のボタンだけでなく、操作説明も記載しました。

ポーズ画面は開くと時間が止まり、ボタンでタイトル画面に戻れる
シンプルかつ最低限のでき。

**Comとの対戦モードの実装**
一番苦戦しました。Comが思い通りに動いてくれない。
制作に詰まることがあっても、開発の手を止め
考える時間を作ることで解決しましたがこれに関しては数日悩みました。

コードの書き方を見直すいい機会となった。コードを複数管理する難易度は高い。
ある程度機能を集約させて、コードの数を増やさないようにするといい
のだろうか？　バグったりしたときの修正が面倒になるのでは....悩ましい

**オリジナルモードの実装**
Com対戦モードの制作中にできた失敗作をオリジナルモードとして活用。
現実では実現が難しいことをやれるメリットを生かすことができたと思う。

## アップデート（実装予定）
**ストーリーを加える**
背景があるとギャンブルに熱がこもる。勝った負けたで感情が動くことで
ゲームとしての面白さが増すと思う。

**報酬要素の追加**
条件を満たしたらサイコロの色を変えられるようにするとか。ストーリークリアで皿の色が変わるとか。

モード問わず ピンゾロ が出たら派手な演出を行うとか。

**賭け要素の追加**
おそらく最難関なのではと思っている。
サイコロを振る前にお金を賭ける。もちろんComにもランダムにかけてもらう。
勝敗に応じてお金を分配する。（注）ちんちろのルールでは役に応じて倍率が変わる。

**ゲーム性の拡張**
例えば面が456しかないサイコロをアイテムとして追加したとする。
賭けの要素と相まって、勝負の選択肢が広がる。
親と子の実装は考えていない。

# 参考
https://chatgpt.com/

制作過程で拝見させていただいたブログや記事↓

https://futabazemi.net/unity/dice_throw

https://qiita.com/shinonome8090/items/cbf45cdd997233dc308f