Advertisement
  1. Game Development
  2. Game Art Effects

Cara Menghasilkan Efek Petir  Mengejutkan 2D dengan Baik.

Scroll to top
Read Time: 15 min

() translation by (you can also view the original English article)

Petir memiliki banyak kegunaan dalam permainan, mulai dari suasana latar belakang saat badai hingga serangan petir dahsyat dari seorang penyihir. Dalam tutorial ini, saya akan menjelaskan bagaimana memprogram untuk menghasilkan efek petir 2D yang mengagumkan: petir, cabang, dan bahkan teks.

catatan:   Meskipun tutorial ini ditulis menggunakan C # dan XNA, anda harus bisa menggunakan teknik dan konsep yang sama di hamper pada semua lingkungan pengembangan game.


Pratinjau Video Akhir


Langkah 1: Buat Garis Bersinar

Blok bangunan dasar yang kita butuhkan untuk membuat petir adalah segmen garis. Mulailah dengan membuka perangkat lunak editing gambar favorit anda dan gambar garis lurus petir. Milik saya terlihat seperti ini:

Line Segment

Kita ingin menggambar garis dengan panjang yang berbeda, jadi kita akan memotong garis segmen menjadi tiga bagian seperti gambar di bawah ini. Ini akan memungkinkan kita untuk meregangkan segmen tengah ke panjang yang kita sukai.  Karena kita akan meregangkan segmen tengah, kita bisa menyimpannya hanya dengan satu piksel tebal. Juga, karena potongan kiri dan kanan adalah bayangan cermin satu sama lain, kita hanya perlu menyimpan salah satunya. Kita bisa membalikkannya dengan kode.

Divided Line Segment

Sekarang, mari kita buat kelas baru untuk menangani segmen garis gambar:

1
public class Line
2
{
3
  public Vector2 A;
4
	public Vector2 B;
5
	public float Thickness;
6
7
	public Line() { }
8
	public Line(Vector2 a, Vector2 b, float thickness = 1)
9
	{
10
		A = a;
11
		B = b;
12
		Thickness = thickness;
13
	}
14
}

A dan B adalah titik akhir garis. Dengan menskalakan dan memutar potongan-potongan garis, kita bisa menggambar garis dengan ketebalan, panjang, dan orientasi. Tambahkan metode berikut ini Draw() untuk kelas Line (garis):

1
public void Draw(SpriteBatch spriteBatch, Color color)
2
{
3
	Vector2 tangent = B - A;
4
	float rotation = (float)Math.Atan2(tangent.Y, tangent.X);
5
6
	const float ImageThickness = 8;
7
	float thicknessScale = Thickness / ImageThickness;
8
9
	Vector2 capOrigin = new Vector2(Art.HalfCircle.Width, Art.HalfCircle.Height / 2f);
10
	Vector2 middleOrigin = new Vector2(0, Art.LightningSegment.Height / 2f);
11
	Vector2 middleScale = new Vector2(tangent.Length(), thicknessScale);
12
13
	spriteBatch.Draw(Art.LightningSegment, A, null, color, rotation, middleOrigin, middleScale, SpriteEffects.None, 0f);
14
	spriteBatch.Draw(Art.HalfCircle, A, null, color, rotation, capOrigin, thicknessScale, SpriteEffects.None, 0f);
15
	spriteBatch.Draw(Art.HalfCircle, B, null, color, rotation + MathHelper.Pi, capOrigin, thicknessScale, SpriteEffects.None, 0f);
16
}

Disini, Art.LightningSegment dan Art.HalfCircle merupakan variabel statis Texture2D yang memegang gambar dari potongan segmen garis. ImageThickness diatur ke ketebalan garis tanpa cahaya.  Dalam gambar saya, itu 8 piksel. Kita mengatur tutup asal ke sisi kanan, dan segmen tengah asal ke sisi kirinya.  Ini akan membuat keduanya tergabung dengan mulus saat kita menggambar keduanya pada titik A. Segmen tengah diregangkan ke lebar yang diinginkan, dan tutup lainnya ditarik pada titik B, diputar 180 °.

Kelas XNA SpriteBatch memungkinkan anda melewatinya , sebuah SpriteSortMode dalam konstruktornya,  yang menunjukkan urutan yang menggambarkan sprite. Saat anda menarik garis, pastikan untuk melewati SpriteBatch dengan SpriteSortMode yang diatur ke SpriteSortMode.Texture. Hal ini untuk meningkatkan performa.

Kartu grafis (Graphics cards) sangat bagus saat menggambar tekstur yang sama berkali-kali. Namun, setiap kali mereka mengganti tekstur, ada overhead. Jika kita menggambar banyak garis tanpa menyortir, kita akan menggambar tekstur kita sesuai urutan ini:

LightningSegment, HalfCircle, HalfCircle, LightningSegment, HalfCircle, HalfCircle, ...

Hal ini berarti kita akan mengganti tekstur dua kali untuk setiap baris yang kita gambar. SpriteSortMode.Texture mengatakan pada SpriteBatch untuk menyortir panggilan Draw() dengan tekstur sehingga semua LightningSegments akan ditarik bersama dan semua HalfCircles akan digambar bersama.  Selain itu, saat kita menggunakan garis ini untuk membuat petir kilat, kita akan menggunakan campuran aditif untuk membuat cahaya dari potongan tumbukan dari kilat yang saling tumpang tindih saling digabungkan.

1
SpriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
2
// draw lines

3
SpriteBatch.End();

Langkah 2: Garis Bergerigi (Jagged Lines)

Petir cenderung membentuk garis bergerigi, jadi kita memerlukan algoritma untuk menghasilkannya. Kita akan melakukan ini dengan memilih poin secara acak di sepanjang garis, dan menggeser mereka secara acak dari garis.  Menggunakan perpindahan acak sepenuhnya cenderung membuat garis terlalu bergerigi, jadi kita akan memperlancar hasil dengan membatasi seberapa jauh titik-titik satu sama lain terdekat bias ditarik.

Making Jagged Lines

Garis dilipat dengan menempatkan titik pada offset yang sama ke titik sebelumnya; Hal ini memungkinkan garis secara keseluruhan menyimpang naik turun, sekaligus mencegah bagian darinya terlalu bergerigi. Inilah kodenya:

1
protected static List<Line> CreateBolt(Vector2 source, Vector2 dest, float thickness)
2
{
3
	var results = new List<Line>();
4
	Vector2 tangent = dest - source;
5
	Vector2 normal = Vector2.Normalize(new Vector2(tangent.Y, -tangent.X));
6
	float length = tangent.Length();
7
8
	List<float> positions = new List<float>();
9
	positions.Add(0);
10
11
	for (int i = 0; i < length / 4; i++)
12
		positions.Add(Rand(0, 1));
13
14
	positions.Sort();
15
16
	const float Sway = 80;
17
	const float Jaggedness = 1 / Sway;
18
19
	Vector2 prevPoint = source;
20
	float prevDisplacement = 0;
21
	for (int i = 1; i < positions.Count; i++)
22
	{
23
		float pos = positions[i];
24
25
		// used to prevent sharp angles by ensuring very close positions also have small perpendicular variation.

26
		float scale = (length * Jaggedness) * (pos - positions[i - 1]);
27
28
		// defines an envelope. Points near the middle of the bolt can be further from the central line.

29
		float envelope = pos > 0.95f ? 20 * (1 - pos) : 1;
30
31
		float displacement = Rand(-Sway, Sway);
32
		displacement -= (displacement - prevDisplacement) * (1 - scale);
33
		displacement *= envelope;
34
35
		Vector2 point = source + pos * tangent + displacement * normal;
36
		results.Add(new Line(prevPoint, point, thickness));
37
		prevPoint = point;
38
		prevDisplacement = displacement;
39
	}
40
41
	results.Add(new Line(prevPoint, dest, thickness));
42
43
	return results;
44
}

Kode itu mungkin terlihat sedikit mengintimidasi, tapi tidak begitu buruk begitu anda mengerti logikanya. Kita mulai dengan menghitung vektor normal dan garis singgung dari garis, bersama dengan panjangnya.  Kemudian kita secara acak memilih sejumlah posisi di sepanjang garis dan menyimpannya dalam daftar posisi kita.  Posisi diskalakan di antara keduanya 0 dan 1 dimana 0 merupakan awal garis dan 1 mewakili titik akhir. Posisi ini kemudian diurutkan agar kita dapat dengan mudah menambahkan segmen garis di antaranya.

Lingkaran melewati titik-titik yang dipilih secara acak dan menggesernya sepanjang normal dengan jumlah acak. Faktor skala ada untuk menghindari sudut yang terlalu tajam, dan amplop (envelope) memastikan kilat benar-benar menuju titik tujuan dengan membatasi perpindahan saat kita mendekati titik akhir.

Lightning BoltLightning BoltLightning Bolt

Langkah 3: Animasi

Kilat harus berkedip terang dan kemudian memudar. Untuk mengatasinya, buatlah sebuah kelas LightningBolt.

1
class LightningBolt
2
{
3
	public List<Line> Segments = new List<Line>();
4
5
	public float Alpha { get; set; }
6
	public float FadeOutRate { get; set; }
7
	public Color Tint { get; set; }
8
9
	public bool IsComplete { get { return Alpha <= 0; } }
10
11
	public LightningBolt(Vector2 source, Vector2 dest) : this(source, dest, new Color(0.9f, 0.8f, 1f)) { }
12
13
	public LightningBolt(Vector2 source, Vector2 dest, Color color)
14
	{
15
		Segments = CreateBolt(source, dest, 2);
16
17
		Tint = color;
18
		Alpha = 1f;
19
		FadeOutRate = 0.03f;
20
	}
21
22
	public void Draw(SpriteBatch spriteBatch)
23
	{
24
		if (Alpha <= 0)
25
			return;
26
27
		foreach (var segment in Segments)
28
			segment.Draw(spriteBatch, Tint * (Alpha * 0.6f));
29
	}
30
31
	public virtual void Update()
32
	{
33
		Alpha -= FadeOutRate;
34
	}
35
36
	protected static List<Line> CreateBolt(Vector2 source, Vector2 dest, float thickness)
37
	{
38
		// ...

39
	}
40
41
	// ...

42
}

Untuk menggunakan ini, cukup buat LightningBolt yang baru dan memanggil Update() serta Draw() setiap frame. Panggilan Update() membuatnya memudar. IsComplete akan memberitahu anda ketika petir telah sepenuhnya memudar keluar.

Anda sekarang dapat menarik petir anda dengan menggunakan kode berikut di kelas Game anda:

1
LightningBolt bolt;
2
MouseState mouseState, lastMouseState;
3
4
protected override void Update(GameTime gameTime)
5
{
6
	lastMouseState = mouseState;
7
	mouseState = Mouse.GetState();
8
9
	var screenSize = new Vector2(GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
10
	var mousePosition = new Vector2(mouseState.X, mouseState.Y);
11
12
	if (MouseWasClicked())
13
		bolt = new LightningBolt(screenSize / 2, mousePosition);
14
15
	if (bolt != null)
16
		bolt.Update();
17
}
18
19
private bool MouseWasClicked()
20
{
21
	return mouseState.LeftButton == ButtonState.Pressed && lastMouseState.LeftButton == ButtonState.Released;
22
}
23
24
protected override void Draw(GameTime gameTime)
25
{
26
	GraphicsDevice.Clear(Color.Black);
27
28
	spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
29
30
	if (bolt != null)
31
		bolt.Draw(spriteBatch);
32
33
	spriteBatch.End();
34
}

Langkah 4: Cabang Petir

Anda bisa menggunakan kelas LightningBolt sebagai blok bangunan untuk menciptakan efek petir yang lebih menarik. Misalnya, anda bisa membuat cabang petir seperti yang ditunjukkan di bawah ini:

Brand Lightning

Untuk membuat cabang petir, kita memilih titik acak di sepanjang petir dan menambahkan petir baru yang keluar dari titik-titik ini. Pada kode di bawah ini, kita membuat antara tiga dan enam cabang yang terpisah dari petir utama pada sudut 30°.

1
class BranchLightning
2
{
3
	List<LightningBolt> bolts = new List<LightningBolt>();
4
		
5
	public bool IsComplete { get { return bolts.Count == 0; } }
6
	public Vector2 End { get; private set; }
7
	private Vector2 direction;
8
9
	static Random rand = new Random();
10
11
	public BranchLightning(Vector2 start, Vector2 end)
12
	{
13
		End = end;
14
		direction = Vector2.Normalize(end - start);
15
		Create(start, end);
16
	}
17
18
	public void Update()
19
	{
20
		bolts = bolts.Where(x => !x.IsComplete).ToList();
21
		foreach (var bolt in bolts)
22
			bolt.Update();
23
	}
24
25
	public void Draw(SpriteBatch spriteBatch)
26
	{
27
		foreach (var bolt in bolts)
28
			bolt.Draw(spriteBatch);
29
	}
30
31
	private void Create(Vector2 start, Vector2 end)
32
	{
33
		var mainBolt = new LightningBolt(start, end);
34
		bolts.Add(mainBolt);
35
36
		int numBranches = rand.Next(3, 6);
37
		Vector2 diff = end - start;
38
39
		// pick a bunch of random points between 0 and 1 and sort them

40
		float[] branchPoints = Enumerable.Range(0, numBranches)
41
			.Select(x => Rand(0, 1f))
42
			.OrderBy(x => x).ToArray();
43
44
		for (int i = 0; i < branchPoints.Length; i++)
45
		{
46
			// Bolt.GetPoint() gets the position of the lightning bolt at specified fraction (0 = start of bolt, 1 = end)

47
			Vector2 boltStart = mainBolt.GetPoint(branchPoints[i]);
48
49
			// rotate 30 degrees. Alternate between rotating left and right.

50
			Quaternion rot = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.ToRadians(30 * ((i & 1) == 0 ? 1 : -1)));
51
			Vector2 boltEnd = Vector2.Transform(diff * (1 - branchPoints[i]), rot) + boltStart;
52
			bolts.Add(new LightningBolt(boltStart, boltEnd));
53
		}
54
	}
55
56
	static float Rand(float min, float max)
57
	{
58
		return (float)rand.NextDouble() * (max - min) + min;
59
	}
60
}

Langkah 5: Teks Petir

Berikut adalah video efek lain yang dapat anda temukan dari petir:

Pertama kita perlu mendapatkan piksel dalam teks yang ingin kita gambar. Kita melakukan ini dengan menarik teks ke sebuah RenderTarget2D dan membaca kembali data pixel dengan RenderTarget2D.GetData<T>().  Jika anda ingin membaca lebih lanjut tentang membuat efek partikel teks, saya memiliki  tutorial lebih rinci di sini.

Kita menyimpan koordinat piksel dalam teks sebagai  List <Vector2>. Kemudian, setiap frame, kita secara acak mengambil sepasang titik-titik ini dan membuat ledakan petir di antara keduanya.  Kita ingin merancangnya sehingga dua titik lebih dekat satu sama lain, semakin besar kesempatan kita membuat petir di antara keduanya.  Ada teknik sederhana yang bisa kita gunakan untuk mencapai hal ini: kita akan memilih poin pertama secara acak, dan kemudian kita akan memilih sejumlah titik lain secara acak dan memilih yang terdekat.

Jumlah poin kandidat yang kita uji akan mempengaruhi tampilan teks petir; memeriksa sejumlah besar titik akan memungkinkan kita menemukan titik-titik yang sangat dekat untuk menarik petir di antara keduanya, yang akan membuat teks itu sangat rapi dan terbaca, namun dengan sedikit petir yang panjang di antara huruf.  Angka yang lebih kecil akan membuat teks petir terlihat lebih menonjol tapi kurang terbaca.

1
public void Update()
2
{
3
	foreach (var particle in textParticles)
4
	{
5
		float x = particle.X / 500f;
6
		if (rand.Next(50) == 0)
7
		{
8
			Vector2 nearestParticle = Vector2.Zero;
9
			float nearestDist = float.MaxValue;
10
			for (int i = 0; i < 50; i++)
11
			{
12
				var other = textParticles[rand.Next(textParticles.Count)];
13
				var dist = Vector2.DistanceSquared(particle, other);
14
15
				if (dist < nearestDist && dist > 10 * 10)
16
				{
17
					nearestDist = dist;
18
					nearestParticle = other;
19
				}
20
			}
21
22
			if (nearestDist < 200 * 200 && nearestDist > 10 * 10)
23
				bolts.Add(new LightningBolt(particle, nearestParticle, Color.White));
24
		}
25
	}
26
27
	for (int i = bolts.Count - 1; i >= 0; i--)
28
	{
29
		bolts[i].Update();
30
31
		if (bolts[i].IsComplete)
32
			bolts.RemoveAt(i);
33
	}
34
}

Langkah 6: Optimalisasi

Teks petir, seperti yang ditunjukkan di atas, dapat berjalan dengan lancar jika anda memiliki komputer papan atas, tapi ini tentu sangat berat. Setiap petir berlangsung lebih dari 30 frame, dan kita membuat lusinan petir baru setiap frame.  Karena setiap petir mungkin memiliki beberapa segmen garis, dan setiap segmen garis memiliki tiga bagian, kita akhirnya menggambar banyak sprite.  Demo saya, misalnya, menarik lebih dari 25.000 gambar setiap frame dengan pengoptimalan dimatikan. Kita bisa membuat yang lebih baik.

Alih-alih menggambar setiap petir sampai habis, kita bisa menarik setiap petir baru ke target render dan memudarkan target render setiap frame.  Ini berarti, daripada harus menarik setiap petir untuk 30 frame atau lebih, kita hanya akan menggambar sekali. Ini juga berarti tidak ada kinerja tambahan untuk membuat petir kita memudar lebih lambat dan bertahan lebih lama.

Pertama, kita akan memodifikasi kelas LightningText hanya untuk menarik setiap petir untuk satu frame. Di kelas game anda, nyatakan dua variabel RenderTarget2D: currentFrame dan lastFrame pada LoadContent (), instal mereka seperti ini:

1
lastFrame    = new RenderTarget2D(GraphicsDevice, screenSize.X, screenSize.Y, false, SurfaceFormat.HdrBlendable, DepthFormat.None);
2
currentFrame = new RenderTarget2D(GraphicsDevice, screenSize.X, screenSize.Y, false, SurfaceFormat.HdrBlendable, DepthFormat.None);

Perhatikan format permukaan yang disetel HdrBlendable. HDR merupakan singkatan dari High Dynamic Range, dan ini menunjukkan bahwa permukaan HDR kita dapat mewakili rentang warna yang lebih besar.  Hal ini diperlukan karena memungkinkan target render untuk memiliki warna yang lebih terang dari putih. Bila banyak petir tumpang tindih, kita memerlukan target render untuk menyimpan jumlah keselurhan dari warnanya,  yang mungkin bertambah melampaui kisaran warna standar. Sementara warna yang lebih terang dari putih ini tetap ditampilkan sebagai warna putih di layar, penting untuk menyimpan  kecerahan penuh darinya agar bisa membuatnya memudar dengan baik.

Tip XNA: Perhatikan juga bahwa agar pencampuran HDR bekerja, anda harus mengatur profil proyek XNA ke Hi-Def. Anda bisa melakukannya dengan mengklik kanan proyek di explorer solution, pilih properties, dan kemudian pilih profil hi-def di bawah tab XNA Game Studio.

Setiap frame, pertama kita menarik isi frame terakhir ke frame saat ini, namun sedikit gelap. Kita kemudian menambahkan petir yang baru dibuat ke frame saat ini.  Akhirnya, kita membuat frame kita saat ini ke layar, dan kemudian menukar dua target render sehingga untuk frame berikutnya, lastframe akan mengacu pada frame yang baru saja kita render.

1
void DrawLightningText()
2
{
3
	GraphicsDevice.SetRenderTarget(currentFrame);
4
	GraphicsDevice.Clear(Color.Black);
5
6
	// draw the last frame at 96% brightness

7
	spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
8
	spriteBatch.Draw(lastFrame, Vector2.Zero, Color.White * 0.96f);
9
	spriteBatch.End();
10
11
	// draw new bolts with additive blending

12
	spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
13
	lightningText.Draw();
14
	spriteBatch.End();
15
16
	// draw the whole thing to the backbuffer

17
	GraphicsDevice.SetRenderTarget(null);
18
	spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
19
	spriteBatch.Draw(currentFrame, Vector2.Zero, Color.White);
20
	spriteBatch.End();
21
22
	Swap(ref currentFrame, ref lastFrame);
23
}
24
25
void Swap<T>(ref T a, ref T b)
26
{
27
	T temp = a;
28
	a = b;
29
	b = temp;
30
}

Langkah 7: Variasi Lainnya

Kita telah membahas  bagaimana membuat cabang petir dan teks petir, tapi itu tentu bukan satu-satunya efek yang bisa anda buat. Mari kita lihat beberapa variasi lain pada petir yang mungkin bisa anda gunakan.

Petir yang Bergerak

Seringkali anda mungkin ingin membuat kilatan petir yang bergerak. Anda bisa melakukan ini dengan menambahkan petir pendek baru setiap frame pada titik akhir dari frame petir sebelumnya.

1
Vector2 lightningEnd = new Vector2(100, 100);
2
Vector2 lightningVelocity = new Vector2(50, 0);
3
4
void Update(GameTime gameTime)
5
{
6
	Bolts.Add(new LightningBolt(lightningEnd, lightningEnd + lightningVelocity));
7
	lightningEnd += lightningVelocity;
8
9
	// ...

10
}

Petir yang Halus

Anda mungkin telah memperhatikan bahwa kilat menyala lebih terang pada persendian. Hal ini disebabkan pencampuran aditif. Anda mungkin ingin petir lebih halus, lebih banyak lagi petir yang rata.  Hal ini dapat dilakukan dengan mengubah fungsi wilayah campuran anda untuk memilih nilai maksimal dari warna sumber dan tujuan, seperti yang ditunjukkan di bawah ini.

1
private static readonly BlendState maxBlend = new BlendState()
2
{
3
		AlphaBlendFunction = BlendFunction.Max,
4
		ColorBlendFunction = BlendFunction.Max,
5
		AlphaDestinationBlend = Blend.One,
6
		AlphaSourceBlend = Blend.One,
7
		ColorDestinationBlend = Blend.One,
8
		ColorSourceBlend = Blend.One
9
};

Lalu, di fungsi Draw(), panggil SpriteBatch.Begin() dengan maxBlend sebagai BlendState daripada BlendState additive. Gambar di bawah menunjukkan perbedaan antara campuran aditif dan campuran maksimal pada petir.

Additive BlendingAdditive BlendingAdditive Blending
Max BlendingMax BlendingMax Blending

Tentu max blending tidak akan membiarkan cahaya dari beberapa petir atau dari background untuk ditambahkan dengan baik. Jika anda ingin petir itu sendiri terlihat mulus, tapi juga mencampur aditif dengan petir lainnya, anda bisa membuat petir pertama ke target render dengan menggunakan blending maksimal, lalu tarik target render ke layar menggunakan aditif.  Berhati-hatilah untuk tidak menggunakan terlalu banyak target render besar karena ini akan mengurangi kinerja.

Alternatif lain, yang akan bekerja lebih baik untuk sejumlah besar petir, adalah menghilangkan cahaya yang tertanam pada gambar segmen garis dan menambahkannya kembali dengan menggunakan efek cahaya pasca-pemrosesan.  Rincian penggunaan shader dan efek cahaya di luar cakupan tutorial ini, namun anda bisa menggunakan Sampel Bloom XNA untuk memulai. Teknik ini tidak akan memerlukan lebih banyak target render saat anda menambahkan lebih banyak petir.


Kesimpulan

Petir adalah efek khusus yang bagus untuk menyegarkan permainan anda. Efek yang dijelaskan dalam tutorial ini adalah titik awal yang bagus, tapi pastinya tidak semua hal bisa anda lakukan dengan petir.  Dengan sedikit imajinasi anda bisa membuat segala macam efek petir yang menakjubkan! Download kode sumbernya dan buatlah eksperimen anda sendiri.

Jika Anda menikmati artikel ini, lihatlah   tutorial tentang efek air 2D juga.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.