iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Build on Google AI

從基礎到實作:使用 Google AI 學習程式設計系列 第 9

Day 9|讓道具生成位置避開障礙物

  • 分享至 

  • xImage
  •  

調整道具生成方式

昨天時間緊迫,測試時有發現道具生成在障礙物上,所以今天打算請AI教我如何避開障礙物來進行生成這步驟。

這次的想法比較簡單:

  • 如果生成的是上方岩石,道具就生成在中下方。
  • 如果生成的是下方岩石,道具就生成在中上方。
  • 道具的位置仍然保留一些隨機變化。
  • 道具的移動速度繼續跟著障礙物速度一起變化。

首先要讓岩石記住生成位置

原本只負責隨機生成上方或下方岩石,這次增加一個變數,記錄最後一次生成的是上方岩石還是下方岩石。
這樣 ItemSpawner 就可以取得這個資訊,再決定道具要生成在哪一側。

using UnityEngine;

public class RockSpawner : MonoBehaviour
{
    [Header("岩石 Prefab 設定")]
    public GameObject topRockPrefab;
    public GameObject bottomRockPrefab;

    [Header("生成位置設定")]
    public float topYPosition = 3.5f;
    public float bottomYPosition = -3.5f;
    public float yOffset = 0.5f;

    [Header("生成頻率與速度")]
    public float spawnRate = 2f;
    public float currentSpeed = 4f;
    public float speedIncreaseAmount = 0.8f;
    public float speedUpInterval = 30f;

    // 【新增】:紀錄最後一次生成的是否為上方岩石
    [HideInInspector]
    public bool lastSpawnedIsTop = false;

    private float spawnTimer = 0f;
    private float speedTimer = 0f;

    void Start()
    {
        SpawnRock();
    }

    void Update()
    {
        spawnTimer += Time.deltaTime;
        if (spawnTimer >= spawnRate)
        {
            SpawnRock();
            spawnTimer = 0f;
        }

        speedTimer += Time.deltaTime;
        if (speedTimer >= speedUpInterval)
        {
            currentSpeed += speedIncreaseAmount;
            
            if (spawnRate > 0.8f)
            {
                spawnRate -= 0.1f;
            }

            BackgroundRepeat[] scrollables = FindObjectsOfType<BackgroundRepeat>();
            foreach (var bg in scrollables)
            {
                bg.UpdateSpeed(currentSpeed);
            }

            speedTimer = 0f;
        }
    }

    void SpawnRock()
    {
        // 50% 機率生成上岩石,50% 機率生成下岩石
        lastSpawnedIsTop = Random.value > 0.5f;

        GameObject prefabToSpawn = lastSpawnedIsTop ? topRockPrefab : bottomRockPrefab;
        float targetY = lastSpawnedIsTop ? topYPosition : bottomYPosition;
        targetY += Random.Range(-yOffset, yOffset);

        Vector3 spawnPosition = new Vector3(transform.position.x, targetY, 0);
        GameObject newRock = Instantiate(prefabToSpawn, spawnPosition, Quaternion.identity);

        PipeMove rockMove = newRock.GetComponent<PipeMove>();
        if (rockMove != null)
        {
            rockMove.moveSpeed = currentSpeed;
        }
    }
}

這次主要增加的是 lastSpawnedIsTop。
它會記錄最近一次生成的岩石位置,讓其他程式可以利用這個資訊判斷道具應該往哪裡生成。

調整道具生成位置

接著修改昨天的 ItemSpawner。
這次不再單純讓道具在固定範圍內隨機生成,而是先確認最近生成的岩石是在上方還是下方,再讓道具優先出現在另一側。
例如上方有岩石時,道具就放在中下方;下方有岩石時,道具則放在中上方。

using UnityEngine;

public class ItemSpawner : MonoBehaviour
{
    [Header("道具清單")]
    [Tooltip("羽毛或紫色方塊等道具 Prefab")]
    public GameObject[] itemPrefabs;

    [Header("生成時間範圍(秒)")]
    public float minSpawnInterval = 5f;
    public float maxSpawnInterval = 15f;

    [Header("生成高度設定(錯位分流)")]
    [Tooltip("當右側刷出【上方岩石】時,道具優先出現的 Y 軸位置(中下方)")]
    public float itemYWhenTopRockSpawns = -1.5f;

    [Tooltip("當右側刷出【下方岩石】時,道具優先出現的 Y 軸位置(中上方)")]
    public float itemYWhenBottomRockSpawns = 1.5f;

    [Tooltip("Y 軸隨機微調幅度(增添高低變化)")]
    public float yOffset = 0.3f;

    [Header("關聯的 RockSpawner")]
    public RockSpawner rockSpawner;

    private float timer = 0f;
    private float nextSpawnTime = 0f;

    void Start()
    {
        if (rockSpawner == null)
        {
            rockSpawner = FindObjectOfType<RockSpawner>();
        }

        ResetTimer();
    }

    void Update()
    {
        timer += Time.deltaTime;

        if (timer >= nextSpawnTime)
        {
            SpawnRandomItem();
            ResetTimer();
        }
    }

    void ResetTimer()
    {
        timer = 0f;
        nextSpawnTime = Random.Range(minSpawnInterval, maxSpawnInterval);
    }

    void SpawnRandomItem()
    {
        if (itemPrefabs == null || itemPrefabs.Length == 0) return;

        int randomIndex = Random.Range(0, itemPrefabs.Length);
        GameObject selectedItem = itemPrefabs[randomIndex];

        float targetY = 0f;

        // 【空間分流邏輯】:根據岩石生成位置,讓道具優先生成在另一側的空間
        if (rockSpawner != null)
        {
            targetY = rockSpawner.lastSpawnedIsTop ? itemYWhenTopRockSpawns : itemYWhenBottomRockSpawns;
        }
        else
        {
            targetY = Random.Range(-1.5f, 1.5f);
        }

        // 微幅隨機偏移,增加視覺變化
        targetY += Random.Range(-yOffset, yOffset);

        Vector3 spawnPosition = new Vector3(transform.position.x, targetY, 0f);

        GameObject newItem = Instantiate(selectedItem, spawnPosition, Quaternion.identity);

        // 複用 PipeMove 組件同步移動速度
        PipeMove itemMove = newItem.GetComponent<PipeMove>();
        if (itemMove != null && rockSpawner != null)
        {
            itemMove.moveSpeed = rockSpawner.currentSpeed;
        }
    }
}

這次修改主要是讓道具的生成位置不再完全隨機,而是會參考目前障礙物的生成方向。
這樣在遊玩時,道具比較不容易直接出現在岩石裡面,也能讓玩家有比較合理的路線可以取得道具。

第九天成果

今天主要是修改道具的生成方式,讓道具可以參考障礙物的位置來決定生成高度。
原本道具只是隨機出現在場景中,實際測試後發現有可能和岩石重疊,因此這次增加了岩石位置的判斷,讓道具優先生成在障礙物的另一側。
這次也是在之前的程式基礎上繼續修改,而不是重新製作新的系統。
目前遊戲的基本架構已經慢慢建立起來,接下來會繼續嘗試加入道具效果,看看這個小遊戲最後能做到什麼程度。


上一篇
Day 8|開始加入遊戲道具
系列文
從基礎到實作:使用 Google AI 學習程式設計9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言