Chắc chắn rồi! Để tạo ra 8 làn sóng tấn công theo kịch bản bạn mô tả trong Unity bằng C#, bạn sẽ cần kết hợp các thành phần như script điều khiển tàu mẹ, script điều khiển tàu con, quản lý trạng thái của từng làn sóng, và các hiệu ứng chuyển động.
Dưới đây là một phương pháp tiếp cận chi tiết, tập trung vào việc tạo ra một WaveManager (Quản lý Làn sóng) để điều phối hành động:
Các Thành Phần Chính
Bạn sẽ cần ít nhất ba script:
* MotherShipController (Điều khiển Tàu mẹ): Xử lý di chuyển, thả tàu con, và lùi về/biến mất.
* FighterShipController (Điều khiển Tàu con): Xử lý di chuyển và tự xếp hàng ngang.
* WaveManager (Quản lý Làn sóng): Điều phối thứ tự và logic của 8 làn sóng.
1. Thiết lập Cảnh (Scene Setup)
* Tạo Prefabs cho Tàu mẹ và Tàu con.
* Tạo một Empty GameObject trong cảnh, đặt tên là WaveManager, và gắn script WaveManager.cs vào đó.
2. Script WaveManager.cs (Quản lý Làn sóng)
Script này sẽ theo dõi làn sóng hiện tại và kích hoạt các hành động tương ứng.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class WaveManager : MonoBehaviour
{
[Header("Prefabs")]
public GameObject motherShipPrefab; // Prefab Tàu mẹ
public GameObject fighterShipPrefab; // Prefab Tàu con
[Header("Vị trí và Thông số")]
public Vector3 spawnPoint = new Vector3(0, 7, 0); // Vị trí xuất hiện của Tàu mẹ (trên cùng)
public float stopYPosition = 2f; // Vị trí Y (khoảng 2/3 màn hình từ dưới lên) mà Tàu mẹ dừng lại
// Cấu hình cho 8 làn sóng (có thể mở rộng)
// Ví dụ: int[] waveConfigs = { 4, 8, 4, 8, 4, 8, 4, 8 }; // Làn sóng 1: 4 tàu, Làn sóng 2: 8 tàu,...
// Hoặc sử dụng cấu trúc phức tạp hơn nếu cần.
private int currentWave = 1;
private const int totalWaves = 8;
private MotherShipController currentMotherShip;
void Start()
{
StartCoroutine(StartNextWaveCoroutine());
}
IEnumerator StartNextWaveCoroutine()
{
while (currentWave <= totalWaves)
{
Debug.Log($"Bắt đầu Làn sóng {currentWave}");
// 1. Tạo Tàu mẹ
GameObject motherShipObject = Instantiate(motherShipPrefab, spawnPoint, Quaternion.identity);
currentMotherShip = motherShipObject.GetComponent<MotherShipController>();
if (currentMotherShip == null)
{
Debug.LogError("MotherShipController không được tìm thấy trên Prefab Tàu mẹ.");
yield break;
}
// Thiết lập mục tiêu dừng và các thông số khác cho Tàu mẹ
currentMotherShip.targetY = stopYPosition;
// Đặt số lượng tàu con và cách xếp hàng cho làn sóng này
int numFighters = GetNumFightersForWave(currentWave);
int numRows = GetNumRowsForWave(currentWave);
// 2. Kích hoạt chuỗi hành động của Tàu mẹ
yield return StartCoroutine(currentMotherShip.InitiateAttackWave(fighterShipPrefab, numFighters, numRows));
// 3. Chờ cho đến khi Tàu mẹ biến mất và các tàu con đã vào vị trí
// (Hành động này được điều phối bên trong MotherShipController và FighterShipController)
// 4. Chờ một khoảng thời gian trước khi làn sóng tiếp theo bắt đầu
yield return new WaitForSeconds(5f);
currentWave++;
}
Debug.Log("Hoàn thành tất cả các làn sóng!");
}
// Hàm này xác định số lượng tàu con cho mỗi làn sóng (theo yêu cầu)
// Làn sóng 1 (1 hàng 4 tàu): 4 tàu con
// Làn sóng 2 (2 hàng 4x2): 8 tàu con
// Các làn sóng khác cũng diễn ra tương tự
private int GetNumFightersForWave(int wave)
{
// Ví dụ: Làn sóng lẻ 4 tàu, Làn sóng chẵn 8 tàu (có thể điều chỉnh)
return (wave % 2 != 0) ? 4 : 8;
}
// Hàm này xác định số hàng cho mỗi làn sóng
private int GetNumRowsForWave(int wave)
{
// Ví dụ: Làn sóng lẻ 1 hàng, Làn sóng chẵn 2 hàng (có thể điều chỉnh)
return (wave % 2 != 0) ? 1 : 2;
}
}
3. Script MotherShipController.cs
Script này quản lý hành vi di chuyển và thả tàu con của Tàu mẹ.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MotherShipController : MonoBehaviour
{
public float moveSpeed = 3f;
public float retreatSpeed = 5f;
[HideInInspector] public float targetY; // Vị trí Y tàu mẹ sẽ dừng
// Hàm coroutine chính điều phối hành động của Tàu mẹ
public IEnumerator InitiateAttackWave(GameObject fighterPrefab, int numFighters, int numRows)
{
// 1. Di chuyển xuống
yield return StartCoroutine(MoveToTargetY());
// 2. Thả Tàu con
Debug.Log($"Tàu mẹ thả {numFighters} tàu con.");
List<FighterShipController> fighters = DropFighters(fighterPrefab, numFighters);
// 3. Kích hoạt xếp hàng ngang cho Tàu con
float fightersMoveTime = 2f; // Thời gian ước tính để tàu con xếp hàng
// Tính toán các vị trí mục tiêu cho tàu con (cần cải tiến ở FighterShipController)
Vector3 centerPos = transform.position;
// Ví dụ: Làn sóng 1 (4 tàu, 1 hàng): FighterShipController sẽ tự xếp thành 1 hàng ngang
// Làn sóng 2 (8 tàu, 2 hàng): FighterShipController sẽ tự xếp thành 2 hàng
foreach (var fighter in fighters)
{
// Tàu con sẽ tự xác định vị trí trong hàng của nó
fighter.SetFormationData(fighters.IndexOf(fighter), numFighters, numRows);
fighter.StartFormationMove(centerPos);
}
// Đợi một khoảng thời gian cho tàu con xếp hàng
yield return new WaitForSeconds(fightersMoveTime);
// 4. Lùi về và biến mất
yield return StartCoroutine(RetreatAndDestroy());
}
IEnumerator MoveToTargetY()
{
Vector3 targetPosition = new Vector3(transform.position.x, targetY, transform.position.z);
while (transform.position.y > targetY)
{
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
yield return null;
}
transform.position = targetPosition;
}
List<FighterShipController> DropFighters(GameObject fighterPrefab, int numFighters)
{
List<FighterShipController> fighters = new List<FighterShipController>();
Vector3 dropPosition = transform.position;
for (int i = 0; i < numFighters; i++)
{
// Tạo Tàu con ngay tại vị trí Tàu mẹ
GameObject fighterObject = Instantiate(fighterPrefab, dropPosition, Quaternion.identity);
FighterShipController fighter = fighterObject.GetComponent<FighterShipController>();
if (fighter != null)
{
fighters.Add(fighter);
}
}
return fighters;
}
IEnumerator RetreatAndDestroy()
{
Debug.Log("Tàu mẹ lùi về và biến mất.");
Vector3 retreatTarget = new Vector3(transform.position.x, -10f, transform.position.z); // Rất xa cuối màn hình
while (transform.position.y > retreatTarget.y)
{
transform.position = Vector3.MoveTowards(transform.position, retreatTarget, retreatSpeed * Time.deltaTime);
yield return null;
}
Destroy(gameObject); // Biến mất
}
}
4. Script FighterShipController.cs
Script này xử lý việc Tàu con di chuyển và xếp đội hình.
using UnityEngine;
using System.Collections;
public class FighterShipController : MonoBehaviour
{
public float formationMoveSpeed = 5f;
public float spacing = 1.5f; // Khoảng cách giữa các tàu
private int fighterIndex;
private int totalFighters;
private int numRows;
// Nhận dữ liệu đội hình từ Tàu mẹ
public void SetFormationData(int index, int total, int rows)
{
fighterIndex = index;
totalFighters = total;
numRows = rows;
}
// Bắt đầu di chuyển vào đội hình
public void StartFormationMove(Vector3 centerPosition)
{
StartCoroutine(MoveToFormation(centerPosition));
}
IEnumerator MoveToFormation(Vector3 center)
{
Vector3 targetPosition = CalculateFormationTarget(center);
while (Vector3.Distance(transform.position, targetPosition) > 0.01f)
{
transform.position = Vector3.MoveTowards(transform.position, targetPosition, formationMoveSpeed * Time.deltaTime);
yield return null;
}
// Đã vào vị trí
}
// Tính toán vị trí mục tiêu dựa trên chỉ số tàu và tổng số tàu/hàng
Vector3 CalculateFormationTarget(Vector3 center)
{
if (numRows == 1) // Ví dụ: Làn sóng 1 (4 tàu, 1 hàng ngang)
{
float totalWidth = (totalFighters - 1) * spacing;
float startX = center.x - (totalWidth / 2f);
float targetX = startX + (fighterIndex * spacing);
return new Vector3(targetX, center.y - 1.5f, center.z); // Xếp dưới Tàu mẹ một chút
}
else if (numRows == 2) // Ví dụ: Làn sóng 2 (8 tàu, 2 hàng ngang 4x2)
{
int fightersPerRow = totalFighters / numRows;
int row = fighterIndex / fightersPerRow; // 0 hoặc 1
int col = fighterIndex % fightersPerRow; // 0, 1, 2, 3
float totalWidth = (fightersPerRow - 1) * spacing;
float startX = center.x - (totalWidth / 2f);
float targetX = startX + (col * spacing);
// Xếp hàng trên và hàng dưới (tùy thuộc vào row)
float targetY = center.y - 1.5f - (row * spacing);
return new Vector3(targetX, targetY, center.z);
}
// Mặc định
return center;
}
}
Tóm tắt cách hoạt động
* WaveManager bắt đầu Coroutine để xử lý làn sóng (từ 1 đến 8).
* Nó tạo ra Tàu mẹ và gọi currentMotherShip.InitiateAttackWave().
* MotherShipController thực hiện các bước sau theo thứ tự:
* MoveToTargetY(): Tàu mẹ di chuyển từ trên xuống vị trí targetY.
* DropFighters(): Tàu mẹ tạo các Tàu con tại vị trí hiện tại của nó.
* Tàu mẹ gọi StartFormationMove() trên mỗi Tàu con.
* FighterShipController tính toán vị trí xếp hàng ngang (1 hàng hoặc 2 hàng, tùy thuộc vào làn sóng) và di chuyển đến đó.
* RetreatAndDestroy(): Tàu mẹ lùi về cuối màn hình và tự hủy, cho phép làn sóng tiếp theo bắt đầu.
Bạn có thể tinh chỉnh các thông số như moveSpeed, retreatSpeed, targetY, và đặc biệt là logic trong GetNumFightersForWave và CalculateFormationTarget để phù hợp với tốc độ và hình dạng đội hình mong muốn.