كرة متعددة
الدرس 15 من 16 في دورة لعبة Unity: تحطيم الطوب على Coddy.
تُقسِّم تعزيزات الكرة المتعددة الكرةَ إلى كرتين: تنطلق نسختان من مكانها. تنسخ Instantiate كائنًا من المشهد كما تنسخ كائنًا أوليًا، لكن لا ينتقل إلا ما يحفظه Inspector: الحقول العامة والحقول الموسومة بـ [SerializeField]، مثل speed وpaddle. يبدأ الحقل الخاص مثل launched بقيمته الافتراضية، وهي false، وتبدأ سرعة Rigidbody عند الصفر. لذا يجب إخبار النسخة بما ينبغي فعله، وإلا فستبقى ملازمة للمضرب.
عند اللعب بعدة كرات، يحتفظ GameManager بها في List<Ball>، ولا تتسبب الكرة المفقودة في خسارة حياة إلا إذا كانت الأخيرة. وينطبق الأمر نفسه على الوحل الذي ينقسم:
List<Slime> slimes = new List<Slime>();
public void Split(Slime slime)
{
Slime half = Instantiate(slime, slime.transform.position, Quaternion.identity);
half.Hop(Vector3.left); // تبدأ الحقول الخاصة بالنسخة بقيم جديدة: أخبرها بما يجب أن تفعله
slimes.Add(half);
}
public void SlimeDied(Slime slime)
{
slimes.Remove(slime);
Destroy(slime.gameObject);
if (slimes.Count == 0) Debug.Log("Room cleared");
}قد تكون الكرة الأولى في القائمة نسخةً، إذ يمكن أن تُفقَد الكرة الأصلية بينما تواصل النسخ التحليق؛ لذا يستخدم الكود الذي يحتاج إلى الكرة القائمةَ، لا حقل Ball الموجود في المشهد.
التحدي
صعب- في
GameManager، أضفusing System.Collections.Generic;وList<Ball> balls = new List<Ball>();. يضيفStart()أولًاballإلى القائمة. - اكتب
public void SplitBall(): أنشئ نسختين منballs[0]في موضعه، وأطلق إحداهما باتجاهnew Vector3(-0.6f, 1f, 0f)والأخرى باتجاهnew Vector3(0.6f, 1f, 0f)، ثم أضف كلتيهما إلى القائمة. - عدّل
BallLost: عندما يكون في القائمة أكثر من كرة واحدة، أزل الكرة التي فُقدت منها ودمّرها، من دون خسارة أي حياة. عند إنهاء مستوى، أعدballs[0]إلى المضرب بدلًا منball. - في
PowerUp، يستدعي Multiball الدالةGameManager.Instance.SplitBall()عند التقاطه.
اضغط Play، والتقط قوة تعزيز وردية، ثم اضغط Check.
جرّب بنفسك
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public Brick brickPrefab;
public Transform wall;
public string[] levels;
public float spacingX = 1.75f;
public float spacingY = 0.75f;
public float topY = 6f;
public Ball ball;
public int lives = 3;
public TMP_Text scoreText;
public TMP_Text livesText;
public GameObject gameOverPanel;
public GameObject[] powerUps;
public float dropChance = 0.15f;
int score;
int level;
int bricksLeft;
void Awake()
{
Instance = this;
}
void Start()
{
BuildLevel(0);
ShowHud();
}
void BuildLevel(int index)
{
level = index;
string[] rows = levels[index].Split('/');
for (int row = 0; row < rows.Length; row++)
{
string line = rows[row];
float left = -(line.Length - 1) * spacingX / 2f;
for (int col = 0; col < line.Length; col++)
{
char c = line[col];
if (c == '.') continue;
Vector3 pos = new Vector3(left + col * spacingX, topY - row * spacingY, 0f);
Brick brick = Instantiate(brickPrefab, pos, Quaternion.identity, wall);
brick.SetHits(c - '0');
bricksLeft++;
}
}
}
public void BrickBroken(Brick brick)
{
score += brick.points;
bricksLeft--;
ShowHud();
if (Random.value < dropChance)
{
GameObject prefab = powerUps[Random.Range(0, powerUps.Length)];
Instantiate(prefab, brick.transform.position, Quaternion.identity);
}
if (bricksLeft == 0)
{
ball.ResetToPaddle();
BuildLevel((level + 1) % levels.Length);
}
}
public void BallLost(Ball lost)
{
lives--;
ShowHud();
if (lives == 0)
{
gameOverPanel.SetActive(true);
lost.gameObject.SetActive(false);
}
else lost.ResetToPaddle();
}
public void PlayAgain()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
void ShowHud()
{
scoreText.text = "Score: " + score;
livesText.text = "Lives: " + lives;
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.