見出し画像

ChatGPT でゲーム作ってみた

EIKI`

 前置き省略。ChatGPT がゲーム作ってくれるならもうプログラムしなくていいじゃん!ヤッター!
 *以後、Q:入力文 A:AI からの回答です。長いので飛ばし飛ばし読んでください。

セットアップ~移動まで

Q. Unity で 2D アクションゲームを作りたいと思います。空のプロジェクトをセットアップしましたが、何を配置すれば良いですか?

A. まず、2D キャラクター用のスプライトを配置します。次に、キャラクターを動かすためのスクリプトを作成し、キャラクターにアタッチします。さらに、背景や障害物などのスプライトを配置し、それらを動かすためのスクリプトを作成します。また、ゲームのルールやポイントなどを表示する UI エレメントも配置します。最後に、ゲームのシーンやゲームオーバーやクリアなどの状態を切り替えるためのスクリプトを作成します。

つくりました。素材は全部アセットストアです。

Q. プレイヤーのスプライトを作成しました。プレイヤーにアタッチする移動のためのスクリプトを教えてください。

A. 下記のようなスクリプトを作成して、プレイヤーにアタッチすることができます。

// 移動速度
public float speed = 5f;

// 移動量
private Vector2 moveAmount;

// Rigidbody2D コンポーネント
private Rigidbody2D rb;

// 初期化
void Start() {
  rb = GetComponent<Rigidbody2D>();
}

// 毎フレーム呼び出される関数
void Update() {
  // 入力値から移動量を算出
  float moveHorizontal = Input.GetAxis("Horizontal");
  float moveVertical = Input.GetAxis("Vertical");
  moveAmount = new Vector2(moveHorizontal, moveVertical);

  // 移動量から移動
  rb.MovePosition(rb.position + moveAmount * speed * Time.deltaTime);
}

このスクリプトは、毎フレームでキー入力から移動量を算出し、Rigidbody2D コンポーネントを使用してプレイヤーを移動させます。speed パラメーターで移動速度を調整することができます。

A. このスクリプトは、毎フレームでキー入力から移動量を算出し、Rigidbody2D コンポーネントを使用してプレイヤーを移動させます。speed パラメーターで移動速度を調整することができます。

結果

 動いた!
 ※そのままだとずっこけたので、Rigidbody2D の FreezeRotation:Z だけ ON にしました。


挙動の調整とジャンプ

 動いたものの、割とヤベー動きなので、調整できるか聞いてみます。

Q. 動きがやや緩やかです。もっと気持ちいい動きにできますか?

A. 動きがやや緩やかなのは、Rigidbody2D コンポーネントではなく、Transform コンポーネントを使用しているためです。Rigidbody2D コンポーネントは、物理演算に基づいた動きを実現するため、より気持ちいい動きができます。

下記のように、Rigidbody2D コンポーネントを使用して、プレイヤーを移動させるスクリプトを作成することができます。

// 移動速度
public float speed = 5f;

// Rigidbody2D コンポーネント
private Rigidbody2D rb;

// 初期化
void Start() {
  rb = GetComponent<Rigidbody2D>();
}

// 毎フレーム呼び出される関数
void Update() {
  // 入力値から移動量を算出
  float moveHorizontal = Input.GetAxis("Horizontal");
  float moveVertical = Input.GetAxis("Vertical");
  Vector2 moveAmount = new Vector2(moveHorizontal, moveVertical);

  // 移動量から移動
  rb.velocity = moveAmount * speed;
}

A. このスクリプトでは、Rigidbody2D コンポーネントの velocity プロパティーを使用して、プレイヤーの移動量を設定しています。velocity プロパティーを設定することで、Rigidbody2D コンポーネントが物理演算に基づいてプレイヤーを移動させるようになります。speed パラメーターで移動速度を調整することができます。

結果

 なめらかになった!(すごい)
 個人的には、「もっといい感じにして」みたいなファジーな要求にちゃんと答えられるのは大分先だと思っていたので、ベーシックな部分ではありますがここをちゃんと調整できたのはスゴイと思います。

ジャンプ

Q. とても良くなりました。ジャンプの挙動をちゃんと作ってくれますか?

A. ジャンプの挙動を作るには、Rigidbody2D コンポーネントの AddForce() メソッドを使用することができます。下記のように、ジャンプ用のスクリプトを追加することで、プレイヤーがジャンプするようになります。

// 移動速度
public float speed = 5f;

// ジャンプ力
public float jumpForce = 5f;

// Rigidbody2D コンポーネント
private Rigidbody2D rb;

// 初期化
void Start() {
  rb = GetComponent<Rigidbody2D>();
}

// 毎フレーム呼び出される関数
void Update() {
  // 入力値から移動量を算出
  float moveHorizontal = Input.GetAxis("Horizontal");
  float moveVertical = Input.GetAxis("Vertical");
  Vector2 moveAmount = new Vector2(moveHorizontal, moveVertical);

  // 移動量から移動
  rb.velocity = moveAmount * speed;

  // ジャンプ
  if (Input.GetButtonDown("Jump")) {
    rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
  }
}

A. このスクリプトでは、Input.GetButtonDown("Jump") メソッドでジャンプ入力を検知し、Rigidbody2D コンポーネントの AddForce() メソッドを使用してジャンプさせます。jumpForce パラメーターでジャンプ力を調整することができます。

結果

 できない。コードを見ればわかるが、rb.velocity は毎フレーム上書きされているので、AddForce しても次フレームで値は無視される。

文句を言う

Q. 正しくジャンプしませんでした。velocity を毎フレーム上書きしていると AddForce が上手く働かないのではないですか?

A. その通りです。Rigidbody2D コンポーネントの velocity プロパティーは、毎フレーム上書きされるため、AddForce() メソッドで追加した力も上書きされてしまいます。

代わりに、Rigidbody2D コンポーネントの AddForce() メソッドを使用して、プレイヤーを移動させるようにすることができます。下記のようなスクリプトを作成することで、プレイヤーが正しくジャンプするようになります。

// 移動速度
public float speed = 5f;

// ジャンプ力
public float jumpForce = 5f;

// Rigidbody2D コンポーネント
private Rigidbody2D rb;

// 接地判定フラグ
private bool isGrounded;

// 初期化
void Start() {
  rb = GetComponent<Rigidbody2D>();
}

// 毎フレーム呼び出される関数
void Update() {
  // 入力値から移動量を算出
  float moveHorizontal = Input.GetAxis("Horizontal");
  Vector2 moveAmount = new Vector2(moveHorizontal, 0f);

  // 移動量から移動
  rb.AddForce(moveAmount * speed);

  // ジャンプ
  if (Input.GetButtonDown("Jump") && isGrounded) {
    rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
  }
}

// 接触している間呼び出される関数
void OnCollisionStay2D(Collision2D other) {
  // 接地判定
  if (other.gameObject.tag == "Ground") {
    isGrounded = true;
  }
}

// 接触が離れたとき呼び出される関数
void OnCollisionExit2D(Collision2D other) {
  // 接地判定
  if (other.gameObject.tag == "Ground") {
    isGrounded = false;
  }
}

A. このスクリプトでは、OnCollisionStay2D() と OnCollisionExit2D() 関数を使用して、プレイヤーが接地しているかどうかを判定しています。また、ジャンプの入力判定には isGrounded フラグを(途中で終わった)

結果

 ジャンプできるようになった! 前のコードの問題を直接指摘して直すことができるのが、ChatGPT の強いところですね。ただ、AddForce を毎ターンしているのでどんどんプレイヤーが加速してすごい速度になっていきます。

再度文句を言う

Q. 正しくジャンプできましたが、移動時の AddForce が継続的に呼ばれるため、キャラクターがどんどん加速してしまいます。対策はありますか?

A. Rigidbody2D コンポーネントの AddForce() メソッドは、毎フレーム呼び出されると、指定した力が継続的に加わります。そのため、AddForce() メソッドを使用すると、キャラクターがどんどん加速してしまう可能性があります。

対策としては、毎フレーム呼び出される Update() 関数内で、移動量を設定した後に、Rigidbody2D コンポーネントの velocity プロパティーをリセットすることができます。そうすることで、AddForce() メソッドで設定した力が毎フレームリセットされるため、キャラクターがどんどん加速することがなくなります。

下記のように、Update() 関数内で velocity プロパティーをリセットするように修正することができます。

// 毎フレーム呼び出される関数
void Update() {
  // 入力値から移動量を算出
  float moveHorizontal = Input.GetAxis("Horizontal");
  Vector2 moveAmount = new Vector2(moveHorizontal, 0f);

  // 移動量から移動
  rb.AddForce(moveAmount * speed);

  // 移動量をリセット
  rb.velocity = new Vector2(rb.velocity.x, 0f);

  // ジャンプ
  if (Input.GetButtonDown("Jump") && isGrounded) {
    rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
  }
}

A. このスクリプトでは、Update() 関数内で velocity プロパティーをリセットすることで、AddForce() メソッドで設定した力が毎フレームリセットされるようになります。これにより、キャラクターがどんどん加速することがなくなります。

ん?

 それ……パッと見で動かないと思いますが……。(velocity 上書きしたらね)
 その後も何度かやり取りしましたが、ここは上手く修正できなかったので諦めました。Mathf.Abs( rigidbody.x ) が一定数値以上の時に丸めるだけなんですけどね……。


敵と死亡

 諦めて次に行きます。

Q. 周期的に動く敵キャラクターを作りたいと思います。どのようなスクリプトが好ましいですか?

A. 周期的に動く敵キャラクターを作るには、次のようなスクリプトを作成することができます。

// 移動速度
public float speed = 1f;

// 移動範囲
public float moveRange = 5f;

// 初期位置
private Vector3 originPos;

// 初期化
void Start() {
  // 初期位置を記録
  originPos = transform.position;
}

// 毎フレーム呼び出される関数
void Update() {
  // 移動
  transform.position = new Vector3(
    originPos.x + Mathf.Sin(Time.time * speed) * moveRange,
    originPos.y,
    originPos.z
  );
}

A. このスクリプトでは、Update() 関数内で Mathf.Sin() メソッドを使用して、周期的な移動を実現しています。speed パラメーターで移動速度を、moveRange パラメーターで移動範囲を調整することができます。また、敵キャラクターが画面外に出ないようにする(途中で終わった)

結果

 映像撮り忘れましたが、EnemyController.cs を作ってアタッチしたら動きました。position を直でいじってるので重力は効きませんが。

 しかし、長文になってくると高確率で途中終了するのはなんでしょうかね。

敵に当たって死ぬ

Q. プレイヤーが敵に当たったら死ぬようにしたいと思います。どのようにスクリプトを書けばいいですか?

A. プレイヤーが敵に当たったときに死ぬようにするには、「OnCollisionEnter2D」というメソッドを使用することができます。このメソッドは、2Dの物理演算を使用しているオブジェクト同士が衝突したときに呼び出されます。次に、このメソッド内で、プレイヤーが死亡する処理を記述することができます。

例えば、次のようなスクリプトを記述することで、プレイヤーが敵に当たったときに死亡するようにすることができます。

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // プレイヤーが死亡しているかどうかを表すフラグ
    private bool isDead = false;

    void OnCollisionEnter2D(Collision2D collision)
    {
        // 敵と衝突した場合
        if (collision.gameObject.tag == "Enemy")
        {
            // プレイヤーを死亡させる処理を呼び出す
            PlayerDies();
        }
    }

    void PlayerDies()
    {
        // プレイヤーが既に死亡している場合は処理を終了する
        if (isDead)
        {
            return;
        }

        // プレイヤーを死亡させる処理をここに記述する
        // 例えば、ゲームオーバー画面を表示するなど

        // フラグを立てる
        isDead = true;
    }
}

A. 上記のスクリプトでは、「OnCollisionEnter2D」メソッド内で、プレイヤーが敵に当たったかどうかを判定しています。敵に当たった場合は、「PlayerDies」というメソッドを呼び出しています。「PlayerDies」メソッド内では、プレイヤーが死亡する処理を記述することができます。このようにして、Unityを使用して2Dアクションゲームを作成する際に、プレイヤ(途中終了)

Q. プレイヤーが上から敵にぶつかった(踏んだ)ときだけ、敵を倒すようにしたいと思います。どうすれば良いですか?

A. Unityで2Dアクションゲームを作る場合、プレイヤーが上から敵を踏んだときだけ、敵を倒すようにするには、「OnTriggerEnter2D」というメソッドを使用することができます。このメソッドは、2Dの物理演算を使用しているオブジェクト同士が衝突したときに呼び出されます。また、このメソッドでは、衝突したオブジェクトが「トリガー」として設定されている場合にのみ呼び出されます。

次に、「OnTriggerEnter2D」メソッド内で、プレイヤーが上から敵を踏んだときに敵を倒す処理を記述することができます。例えば、次のようなスクリプトを記述することで、プレイヤーが上から敵を踏んだときに敵を倒すようにすることができます。

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void OnTriggerEnter2D(Collider2D collider)
    {
        // 敵と衝突した場合
        if (collider.gameObject.tag == "Enemy")
        {
            // 敵を倒す処理をここに記述する
            // 例えば、敵を消すなど
        }
    }
}

A. 上記のスクリプトでは、「OnTriggerEnter2D」メソッド内で、プレイヤーが敵と衝突したかどうかを判定しています。敵と衝突した場合は、敵を倒す処理をここに記述することができます。

また、プレイヤーが敵を踏むためには、プレイヤーと敵の両方のオブジェクトに「トリガー」を設定する必要があります。「トリガー」は、物理演算を無効にして、オブジェクト同士が衝突したときに「OnTriggerEnter2D」メソッドが呼び出されるようにするものです。「トリガー」を設定するには、Unityの(途中終了)

結果

 連続で質問してみましたが、これは処理がバッティングしてますし、「踏む」と指定したのにどこからぶち当たっても敵を倒してしまいます。
 バッティング周りはやはりまだ解決が難しいように思えますので、後者だけ直してもらいます。

文句を言う

Q. 上のスクリプトでは、上から踏んでも横から当たっても敵が倒されてしまいます。上から踏んだ時のみ反応するようにするにはどうしたら良いですか?

A. Unityで2Dアクションゲームを作る場合、プレイヤーが上から敵を踏んだときだけ、敵を倒すようにするには、踏んだ方向を判定する必要があります。「OnTriggerEnter2D」メソッド内では、プレイヤーと敵が衝突したことだけがわかりますが、どちらから衝突したかわかりません。そのため、プレイヤーが上から敵を踏んだときだけ、敵を倒す処理を実行するようにするには、踏んだ方向を判定する必要があります。

例えば、次のようなスクリプトを記述することで、プレイヤーが上から敵を踏んだときだけ、敵を倒すようにすることができます。

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void OnTriggerEnter2D(Collider2D collider)
    {
        // 敵と衝突した場合
        if (collider.gameObject.tag == "Enemy")
        {
            // 踏んだ方向を取得する
            Vector2 direction = collider.transform.position - transform.position;

            // 踏んだ方向が上方向の場合
            if (direction.y > 0)
            {
                // 敵を倒す処理をここに記述する
                // 例えば、敵を消すなど
            }
        }
    }
}

A. 上記のスクリプトでは、「OnTriggerEnter2D」メソッド内で、プレイヤーが敵と衝突したときに、踏んだ方向を取得しています。踏んだ方向は、衝突した敵の位置からプレイヤーの位置を引くことで求めることができます。また、踏んだ方向は、「Vector2」型の変数として保持されます。

次に、取得した踏んだ方向が上方向かどうかを判定しています。踏んだ方向が上方向の場合は、敵を倒す処理をここ(途中終了)

Q. 敵を倒す処理は具体的にどのように記述しますか?

A. Unityで2Dアクションゲームを作る場合、敵を倒す処理は、次のように記述することができます。

// 敵を倒す処理
void KillEnemy(GameObject enemy)
{
    // 敵を消す
    Destroy(enemy);
}

A. 上記のスクリプトでは、「KillEnemy」という関数を定義しています。この関数は、敵を倒す処理を行います。「KillEnemy」関数の引数には、倒す敵を表す「GameObject」型の変数を渡すことができます。

「KillEnemy」関数内では、「Destroy」メソッドを呼び出しています。「Destroy」メソッドは、Unityが提供するもので、引数で渡されたオブジェクトを削除することができます。つまり、「KillEnemy」関数内で「Destroy」メソッドを呼び出すことで、敵を消す処理を行うことができます。

「KillEnemy」関数を使用するには、敵を倒す処理を行うスクリプト内で、「KillEnemy」関数を呼び出す必要があります。例えば、次のように「KillEnemy」関数を呼び出すことで、敵を倒す処理を行うことができます。

// プレイヤーが敵を踏んだときに呼び出されるメソッド
void OnTriggerEnter2D(Collider2D collider)
{
    // 敵と衝突した場合
    if (collider.gameObject.tag == "Enemy")
    {
        // 踏んだ方向を取得する
        Vector2 direction = collider.transform.position - transform.position;

        // 踏んだ方向が上方向の場合
        if (direction.y > 0)
        {
            // 敵を倒す処理を呼び出す
            KillEnemy(collider.gameObject);
        }
    }
}

A. 上記のスクリプトでは、「OnTriggerEnter2D」メソッド内で、プレイヤーが敵と衝突したときに、踏んだ(途中終了)

結果

 なんとかいけ……ませんでした。踏んだ方向を取得するために「collider.transform.position - transform.position」とありますが、まずこの前後は逆ですし、y が同じ場合(横から衝突した場合)誤差次第で結果が変わってしまいます。現状、横からぶち当たっても敵を倒してしまいました。


まとめ

 まだまだ 100% ゲームを作れるとは言い難いですが、アウトプットは期待以上のものでした。今はまだ、「どこが間違えているか」指摘できるプログラマーが「何を作ればいいか」逐一指示する必要がありますが、文脈をちゃんと理解してくれるのと、間違っているところを修正してくれるので、もう少し精度が高くなれば普通に使えるような気がしてきます。
 というか、コードに日本語のコメント文がみっちり付いているのが地味に凄すぎるんですが、これどうやって学習してるんでしょうね。どこかから持ってきている……?(でも、ググっても出てこないんですよね)

 ゲームプログラマーには、「天才的なプログラマーが手遊びでゲームを作っている」パターンと「ゲームを作りたいので仕方なくプログラムを血反吐と共に書いている」タイプの2パターンありますが、ぼくは圧倒的後者なので早く AI にコード書いてほしいです。がんばれー。


プログラマーは仕事を失うのか?

この続きをみるには

この続き: 1,548文字

記事を購入

500円

購入済みの方はログイン
この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!