見出し画像

#unity1week 「ふえる」の振り返り

どんなゲームを作ったか

 わたちこ -始末書修正ゲーム-という文書に湧き出る不適切な文字を削除するゲームを作成しました。

画像1

渾身のドット絵(イルカさん)

画像2

 ゲームを始めると、3秒おきに文書に赤文字が挿入され、始末書として不適切な内容に変わっていきます。

 ただし、デフォルトの時点で既に60回/3か月寝坊しているのでホームラン級にヤバい治国太郎くん。毎日がエブリデイやんけ。

※もちろん登場人物、組織はフィクションであり、実在する人物、団体に関連したものではありません。寝坊部であっても寝坊は許されません。


構想/アイデア

 クリックして何かを増やすゲーム、倍々になるゲームは容易に想像できましたが、他人とネタが被るのだけは絶対に嫌だと思ったので、真面目にふざける感じにしました。

 あと、前回の「密」のときは超突貫で作ったので、少なくとも前作よりボリュームを出そうと思いました。

結果的にカテゴライズに困るゲームができました。


結果

 遅刻したにも関わらず、温かい評価をたくさんいただけて大満足でした。

画像3

楽しさ:3.981pt…41位

絵作り:3.811pt…ランク外

サウンド:3.566pt…ランク外

操作性:3.189pt…ランク外

雰囲気:3.849pt…ランク外

斬新さ:4.396pt…6位


しかもランクインまで!

完全に予想外でした。プレイ&評価してくださったみなさまありがとうございます!!

画像4

嬉しいのでキャプチャして並べるなどした。

なんで参加したか

・ゲームを作りきる力を養うため

 過去にDXライブラリを触ったりして、エターナった作品も数知れずあるので、完成させるクセをつけたいと思っています。

・ゲーム制作者のフォロー/フォロワーを増やしたかったから

 色んな考え方に触れたいので、このお祭りに乗じてゲーム制作者の方たちをフォローさせていただきました。

こだわったところ

 無限にこだわることができるので、絞りました。

 ・イルカをWind〇wsのアイツに似せる

  若い人は知らないかもしれませんが、今は昔、Excelには魔物がおりました。~中略~ 消せなくてウザかったです。(今も生きているみたいです)

 ・イルカを強烈なキャラに仕立て上げる

画像5

ユーザーに怒りをぶつけてくるゲームはなかったんじゃないかと思います。

 ・ウザい文章になるよう心がける

  得意分野です。が、時間がなくてバリエーション少なめになりました。

目標と達成度

 目標は実現可能な範疇で掲げました。高すぎる理想は実現できなかった時、己に影を落とすので。

 ・作りきる …達成!!!!

 ・締切を守る …未達成

  初めて使うInputFieldで、ゲーム性を出すことに苦戦したため遅刻しました。あかんやん。

 ・誰とも被らない内容にする …達成!!!!

  ゲーム内容も遅刻の始末書だし、ゲーム名も自戒の意を込めて、

  "私は遅刻しました"を省略して"わたちこ"としました。(首からプラカードを下げて反省するイメージ)

 ・音を入れる …達成

  前作で実況者に「音がないと楽しみが半減する」とアドバイスを受けたので、必ず入れようと思ってました。

 ・ランキング機能をつける …達成

  u1wではもう必須といっても過言ではないと思います。naichiさんのおかげで実装もラクラクですよ。

 ・ツイート機能をつける …達成

  同上。ただし表現が難しい。

良かったこと

 ・イルカと文章がウケた

  ここで笑ってもらいたかったので、温かいコメントが多くて報われました!

 ・フォロワーが60人以上増えた

  しがないアカウントなのにありがとうございます。

納得いってないこと

 ・操作性が悪くなってしまった

  カーソルが飛ぶのを修正できなかった

  カメラとカーソルの連動がうまくいかなかった

 ・ツイート機能の内容が微妙

妥協したところ

 初期構想では、天使と悪魔とイルカが邪魔してくるイメージでした。

  ・天使:たまにワードを消してくれるが、余計なお世話で文字が増えることがある

  ・悪魔:欲望に素直なワードを入れてくる(「めんどくせェ」など)

  ・イルカ:頓珍漢なワードを入れてくる。シンプルに邪魔。


 始末書の文章をそのままTwitterに晒し上げる

  文字数足りなくなるし良い代替案がないからボツ


 文字を放置し続けると書類から文字がはみだす

  1分間放置しても表示がおおきくずれないことだけ確認してそっ閉じした

今後の目標

 スマホからプレイできるようにする

  ゲーム制作者だけでなく、一般人からもプレイしやすい形式にする。

  今は大多数がスマホでTwitterをやってると思うので、興味をもった方がそのままリンク先のゲームをできたら楽だと思います。

始末書オブジェクトのソースコード

流れに乗って載せます。

可読性がすんごい悪かったので、未使用の変数と関数、デバッグコードはさすがに消しました。

あとちょこちょこ捕捉コメントを入れました。

Updateから辿れば、大体読めると思います。

using Random = UnityEngine.Random;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class Disclaimer : MonoBehaviour
{
   [SerializeField] GameObject honbun; //本文オブジェクト
   [SerializeField] GameObject inputField;
   TMP_InputField inputFieldObj;
   string inputText;   //一時保管用
   const float popTime = 3.0f; //文字が湧く間隔
   [SerializeField] float moveY;   //始末書の移動
   [SerializeField] TextMeshProUGUI coverText; //InputFieldと全く同じ位置に表示させるテキスト
   public int ShimatsushoLength {get;private set;} //文字数
   public int DeleteWord {get;private set;}    //スコア
   [SerializeField] GameObject gmObj;  //GameManager
   GameManager gm; //GameManager
   bool isOnce, isClickOnce; //1回だけ実行したいフラグ
   private bool keyIsBlock = false; //キー入力ブロックフラグ
   private DateTime pressedKeyTime; //前回キー入力された時間
   private TimeSpan elapsedTime; //キー入力されてからの経過時間
   private TimeSpan blockTime = new TimeSpan(0, 0, 0, 0, 180); //ブロックする時間 180ms
   [SerializeField] private AudioSource audioSource;
   [SerializeField] private AudioClip popWordSE;   //文字が増える時の効果音
   [SerializeField] private AudioClip cursorSE;   //カーソル移動時の効果音
   [SerializeField] private AudioClip deleteSE;   //文字削除時の効果音
   Coroutine _delayMethod; //コルーチンが動いているかチェック用

   //元々の文字
   List<string> wordList =	new List<string>(){
       "2年","8月","17日",
		"徳 三","部長",
		"太郎",
		"           ","始末書",
		"私は","何度も","注意を","かかわらず",
       "3ヶ月","60回","始業時間","への",
		"会社","職場","みなさま","対して",
       "ご迷惑","おかけ","お詫び","上げ",
		"業務","きたす",
       "後輩","反省",
		"自己","管理","回復","すべく",
       "誠意","邁進","いく","ございます",
		"以上",
       "邁進","始末書"};

   //置き換える文字(最初使ってたけどcolorListに替わったので御役御免)
   List<string> devilWordList = new List<string>(){
       "2億万光年","88888月","17日って何曜日?",
		"徳 ☆ミ三","部長~ん",
		"太郎様",
		"      ドンナ(^o^≡^o^)キモチ     ","お前を始末する書",
		"私とあなたは","何度でも何度でも何度でも立ち上がり呼ぶよ","注意と拷問を","かかかかかわらず",
       "3000ヶ月","60000回","始☆業☆時☆間","へのへのもへじ",
		"壮大なる会社","偉大なる職場","無関係なみなさま","対戦して",
       "すっごい迷惑","おかかふりかけ","お詫び寂び","↑↑上げ↑↑",
		"業務用スーパー","ゆびきたす",
       "存在しない後輩","反&省",
		"自己再生を習得して","体力管理","と体力を回復","すべっく",
       "誠意天上天下唯我独尊","ガンガン邁進","ガンガンいく","ござそうろういります?",
		"以上でも以下でもない",
       "ガンガンズイズイグングン邁進","始業前に遅刻した者の末路を示す書"};

   //置き換える色付き文字ああああ
   List<string> colorList = new List<string>(){
       "2<color=red>億万光</color>年","8<color=red>8888</color>月","17日<color=red>って何曜日?</color>",
		"徳 <color=red>☆ミ</color>三","部長<color=red>~ん</color>",
		"太郎<color=red>様</color>",
		"      <color=red>ドンナ(^o^≡^o^)キモチ</color>     ","<color=red>お前を</color>始末<color=red>する</color>書",
		"私<color=red>とあなた</color>は","何度<color=red>で</color>も<color=red>何度でも何度でも立ち上がり呼ぶよ</color>","注意<color=red>と拷問</color>を","<color=red>かかか</color>かかわらず",
       "3<color=red>000</color>ヶ月","60<color=red>000</color>回","始<color=red>☆</color>業<color=red>☆</color>時<color=red>☆</color>間","への<color=red>へのもへじ</color>",
		"<color=red>壮大なる</color>会社","<color=red>偉大なる</color>職場","<color=red>無関係な</color>みなさま","対<color=red>戦</color>して",
       "<color=red>すっごい</color>迷惑","おか<color=red>かふりか</color>け","お詫び<color=red>寂び</color>","<color=red>↑↑</color>上げ<color=red>↑↑</color>",
		"業務<color=red>用スーパー</color>","<color=red>ゆび</color>きたす",
       "<color=red>存在しない</color>後輩","反<color=red>&</color>省",
		"自己<color=red>再生を習得して</color>","<color=red>体力</color>管理","<color=red>と体力を</color>回復","すべ<color=red>っ</color>く",
       "誠意<color=red>天上天下唯我独尊</color>","<color=red>ガンガン</color>邁進","<color=red>ガンガン</color>いく","ござ<color=red>そうろう</color>い<color=red>り</color>ます<color=red>?</color>",
		"以上<color=red>でも以下でもない</color>",
       "<color=red>ガンガンズイズイグングン</color>邁進","始<color=red>業前に遅刻した者の</color>末<color=red>路を示す</color>書"};

   // Start is called before the first frame update
   void Start()
   {
       _delayMethod = null;
       isClickOnce = false;
       isOnce = false;
       gm = gmObj.GetComponent<GameManager>();
       keyIsBlock = false;
       DeleteWord = 0;
       inputFieldObj = inputField.GetComponent<TMP_InputField>();
       inputText = inputFieldObj.text;
       InitBody();
       inputFieldObj.ActivateInputField();
       honbun.SetActive(false);
   }

   //ゲーム中に文書外にフォーカスが当たったら呼ばれる
   public void ActiveInputField()
   {
       if(gm.GameState != 3)
       {
           inputFieldObj.ActivateInputField();
       }
   }

   void Update()
   {
       //タイトル
       if(gm.GameState == 0)
       {
       }
       //ゲーム中
       else if(gm.GameState == 1)
       {
           if(isOnce == false)
           {
               isOnce = true;
               honbun.SetActive(true);
               inputFieldObj.readOnly = false;
               InvokeRepeating("ReplaceWord", 0, popTime);
           }
           //入力してよいキー
           if (
               (Input.GetKeyDown(KeyCode.Backspace)) ||
               (Input.GetKeyDown(KeyCode.Delete)) ||
               (Input.GetKeyDown(KeyCode.UpArrow)) ||
               (Input.GetKeyDown(KeyCode.DownArrow)) ||
               (Input.GetKeyDown(KeyCode.RightArrow)) ||
               (Input.GetKeyDown(KeyCode.LeftArrow))
           )
           {
               audioSource.PlayOneShot( cursorSE, 0.7f);
           }
           //入力すると1文字目に戻されるキー
           if ((Input.GetKey(KeyCode.RightShift)) ||
               (Input.GetKey(KeyCode.LeftShift)) ||
               (Input.GetKey(KeyCode.RightControl)) ||
               (Input.GetKey(KeyCode.LeftControl)) ||
               (Input.GetKey(KeyCode.Home)) ||
               (Input.GetKey(KeyCode.End)) ||
               (Input.GetKey(KeyCode.Return)) ||
               (Input.GetKey(KeyCode.PageUp)) ||
               (Input.GetKey(KeyCode.PageDown)) ||
               (Input.GetMouseButton(0)) ||
               (Input.GetMouseButton(1)) ||
               (Input.GetMouseButton(2)) ||
               (Input.GetAxis ("Mouse ScrollWheel") != 0 )
           )
           {
               inputFieldObj.MoveTextStart(false);
               transform.localPosition = new Vector3(0.2f,-5.14f,0);
               ForbidInput(0.50f);
           }
           //その他文字キー(入力制御したかったけどなんか効かないので考えるのを止めた)
           if(
           (Input.GetKey(KeyCode.Alpha0)) || (Input.GetKey(KeyCode.Alpha1)) ||
           (Input.GetKey(KeyCode.Alpha2)) || (Input.GetKey(KeyCode.Alpha3)) ||
           (Input.GetKey(KeyCode.Alpha4)) || (Input.GetKey(KeyCode.Alpha5)) ||
           (Input.GetKey(KeyCode.Alpha6)) || (Input.GetKey(KeyCode.Alpha7)) ||
           (Input.GetKey(KeyCode.Alpha8)) || (Input.GetKey(KeyCode.Alpha9)) ||
           (Input.GetKey(KeyCode.Keypad0)) || (Input.GetKey(KeyCode.Keypad1)) ||
           (Input.GetKey(KeyCode.Keypad2)) || (Input.GetKey(KeyCode.Keypad3)) ||
           (Input.GetKey(KeyCode.Keypad4)) || (Input.GetKey(KeyCode.Keypad5)) ||
           (Input.GetKey(KeyCode.Keypad6)) || (Input.GetKey(KeyCode.Keypad7)) ||
           (Input.GetKey(KeyCode.Keypad8)) || (Input.GetKey(KeyCode.Keypad9))
           )
           {
               inputFieldObj.MoveTextStart(false);
               transform.localPosition = new Vector3(0.2f,-5.14f,0);
               ForbidInput(0.50f);
           }
           //入力してから一定時間経過するまでは入力できないようにする(上下キー用)
           if (keyIsBlock)
           {
               elapsedTime = DateTime.Now - pressedKeyTime;
               if (elapsedTime> blockTime)
               {
                   keyIsBlock = false;
               }
           }
           MoveSheet();    //シート移動
           ScoreCalc();    //スコア計算
           UpdateBody();    //文章同期
       }
       //リザルト
       else
       {
           CancelInvoke("ReplaceWord");
           inputFieldObj.readOnly = true;
       }
   }

   //カーソル押しっぱなしの連続入力間隔を調整する
   private void ForbidInput(float time)
   {
       inputFieldObj.readOnly = true;
       if(_delayMethod != null) StopCoroutine(_delayMethod);
       _delayMethod = StartCoroutine(DelayMethod(time, () =>
       {
           inputFieldObj.readOnly = false;
       }));
   }

   //指定時間待ってから実行してくれる良いヤツ
   private IEnumerator DelayMethod(float waitTime, Action action)
   {
       yield return new WaitForSeconds(waitTime);
       action();
   }

   //シートの上下位置変更
   private void MoveSheet()
   {
       if(!keyIsBlock)
       {
           float tempX = 0;
           float tempY = 0;
           float nowY = transform.localPosition.y;
           //上
           if  (Input.GetKey(KeyCode.UpArrow) && nowY > -6.00f)
           {
               keyIsBlock = true;
               pressedKeyTime = DateTime.Now;
               tempY += moveY;
               audioSource.PlayOneShot( cursorSE, 0.7f);
           }
           //下
           else if (Input.GetKey(KeyCode.DownArrow) && nowY < 4.50f)
           {
               keyIsBlock = true;
               pressedKeyTime = DateTime.Now;
               tempY -= moveY;
               audioSource.PlayOneShot( cursorSE, 0.7f);
           }
           //Backspace
           if  (Input.GetKey(KeyCode.Backspace))
           {
               keyIsBlock = true;
               pressedKeyTime = DateTime.Now;
               audioSource.PlayOneShot( cursorSE, 0.7f);
           }
           //Delete(中身がBackspaceと一緒)
           if  (Input.GetKey(KeyCode.Delete))
           {
               keyIsBlock = true;
               pressedKeyTime = DateTime.Now;
               audioSource.PlayOneShot( cursorSE, 0.7f);
           }
           transform.localPosition += new Vector3(0,tempY,0);
       }
   }

   //文字を置き換える
   private void ReplaceWord()
   {
       string a = inputFieldObj.text;
       int i = Random.Range(0,wordList.Count - 1);
       string test = a.Replace(wordList[i],colorList[i]);
       //置き換わったら音を鳴らす
       if (inputFieldObj.text == test)
       {
       }
       else
       {
           inputFieldObj.text = test;
           audioSource.PlayOneShot( popWordSE, 1.0f);
       }
       inputFieldObj.text = a.Replace(wordList[i],colorList[i]);
       //カレットの位置調整用(うまく機能していない)
       if (inputFieldObj.caretPosition < a.IndexOf(wordList[i]))
       {
       }
       else
       {
           //置換されたら
           if(a.IndexOf(wordList[i]) != -1)
           {
               inputFieldObj.caretPosition += devilWordList[i].Length - wordList[i].Length;
           }
       }
   }

   private void InitBody()
   {
   string txt = $"令和2年8月17日\n" +
                $"早起事業部\n" +
                $"御徳 三文 部長\n" +
                $"           寝坊部夜更課 治国 太郎\n" +
                $"         始末書" +
                $"\n" +
                $"私は何度も注意を受けたにもかかわらず、" +
                $"令和2年1月から4月に至るまでの3ヶ月間、" +
                $"60回に及ぶ始業時間への遅刻を繰り返し、" +
                $"会社ならびに職場のみなさまに対して" +
                $"ご迷惑をおかけしたことをお詫び申し上げます。\n" +
                $"\n" +
                $"こうした遅刻により業務に支障をきたすばかりでなく、" +
                $"職場の士気を低下させ、" +
                $"後輩への模範ともならず、" +
                $"深く反省しております。\n" +
                $"\n" +
                $"今後は自己管理を徹底し、" +
                $"信頼回復すべく誠心誠意邁進していく所存でございます。\n" +
                $"\n" +
                $"以上。";
       inputFieldObj.text = txt;
       ShimatsushoLength = txt.Length;
   }

   //InputFieldとCoverTextを同期させる
   public void UpdateBody()
   {
       coverText.text = inputFieldObj.text;
       inputText = coverText.text;
       inputText = inputText.Replace("<color=red>","");
       inputText = inputText.Replace("</color>","");
       ShimatsushoLength = inputText.Length;
   }

   //スコア計算
   private void ScoreCalc()
   {
       // <color=red></color>の数だけスコアになる
       string replaceWord = "<color=red></color>";
       int replaceNum = CountString(inputFieldObj.text,replaceWord);
       if(replaceNum > 0)
       {
           DeleteWord += replaceNum/19;
           inputFieldObj.text = inputFieldObj.text.Replace(replaceWord,"");
           coverText.text = inputFieldObj.text.Replace(replaceWord,"");
           audioSource.PlayOneShot( deleteSE, 1.0f);
       }
   }

   // 消した文字数カウント
   private int CountString(string s, string c) {
       return s.Length - s.Replace(c, "").Length;
   }
}

ギミックのキモは、文字列置換を使っています。

さらに、置換前の文字列は、置換後にも必ず残っています。

ですので、ゲーム中に黒字を消しすぎると、変換元の文字列がなくなってしまい、必然的に得点源がなくなってしまいます。


InputFieldオブジェクトはカレットのみ見えるように設定し、文字列は透明にすることで、誰しも使い慣れているいつものカレットの動きを実現しています。

透明なInputFieldとピッタリ重ねるようにTextオブジェクトを配置し、InputFieldの内容を同期させることで表示させています。


ご意見、ご感想、ソースの改善点などいただけるととても嬉しいです。

ここまで読んでいただき、ありがとうございました!



生ハムとグミが買えると嬉しいです。嘘ですアセットに使います。