Advertisement
  1. Web Design
  2. HTML/CSS
  3. HTML

Bagaimana Mengimplementasikan dan Menggunakan Antrian Pesan di Game Anda.

Scroll to top
Read Time: 17 min

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

Sebuah permainan biasanya dibuat dari beberapa entitas yang berbeda yang berinteraksi satu sama lain. Interaksi tersebut cenderung sangat dinamis dan sangat terkait dengan gameplay.  Tutorial ini mencakup konsep dan implementasi sistem antrian pesan yang dapat menyatukan interaksi entitas, membuat kode anda mudah dikelola dan mudah dipelihara karena tumbuh dalam kompleksitas.  

Pengantar

Bom bisa berinteraksi dengan karakter dengan meledak dan menyebabkan kerusakan, sebuah alat medis dapat menyembuhkan suatu entitas, sebuah kunci bisa membuka pintu, dan seterusnya.  Interaksi dalam permainan tidak terbatas, tapi bagaimana kita bisa menjaga kode permainan tetap terkendali saat masih bisa menangani semua interaksi itu? Bagaimana kita memastikan kodenya dapat berubah dan terus bekerja saat interaksi baru dan tak terduga muncul?

Interactions in a game tend to grow in complexity very quicklyInteractions in a game tend to grow in complexity very quicklyInteractions in a game tend to grow in complexity very quickly
Interaksi dalam permainan cenderung tumbuh dalam kompleksitas dengan sangat cepat.

Seiring interaksi ditambahkan (terutama yang tak terduga), kode anda akan terlihat lebih dan lebih berantakan. Implementasi yang naif akan segera membawa anda mengajukan pertanyaan seperti:

"Ini adalah entitas A, jadi saya harus memanggil metode damage() di atasnya kan? Atau itu damageByItem ()? Mungkin DamageByWeapon () ini merupakan metode yang tepat? "

Bayangkan bahwa kekacauan kekacauan menyebar ke semua entitas permainan Anda, karena mereka semua berinteraksi satu sama lain dengan cara yang berbeda dan aneh. Untungnya, ada cara yang lebih baik, sederhana, dan lebih mudah dikelola untuk melakukannya.

Antrian Pesan

Masukkan message queue (antrian pesan) . Ide dasar dibalik konsep ini adalah menerapkan semua interaksi game sebagai sistem komunikasi (yang masih bisa digunakan saat ini): pertukaran pesan.  Orang telah berkomunikasi melalui pesan (surat) selama berabad-abad karena ini adalah sistem yang efektif dan sederhana.

Dalam layanan pos dunia nyata kita, isi setiap pesan bisa berbeda, namun cara mereka dikirim dan diterima secara fisik tetap sama.  Seorang pengirim memasukkan informasinya ke dalam amplop dan mengirimkannya ke tujuan. Tujuan dapat menjawab (atau tidak) dengan mengikuti mekanisme yang sama, cukup ganti bidang "dari / ke" pada amplop.   

Interactions made using a message queue systemInteractions made using a message queue systemInteractions made using a message queue system
Interaksi dilakukan dengan menggunakan sistem antrian pesan.

Dengan menerapkan gagasan itu pada permainan Anda, semua interaksi antar entitas dapat dilihat sebagai pesan. Jika sebuah entitas game ingin berinteraksi dengan yang lain (atau kelompoknya), yang harus dilakukan hanyalah mengirim pesan.  Tujuan akan menangani atau bereaksi terhadap pesan berdasarkan isinya dan siapa pengirimnya.

Dalam pendekatan ini, komunikasi antar entitas game menjadi terpadu. Semua entitas dapat mengirim dan menerima pesan. Tidak peduli seberapa kompleks atau anehnya interaksi atau pesannya, saluran komunikasi selalu tetap sama.

Pada bagian selanjutnya, saya akan menjelaskan pada anda bagaimana caranya menerapkan pendekatan antrian pesan pada permainan anda.

Merancang Amplop (Pesan)

Mari kita mulai dengan merancang amplop, yang merupakan elemen paling mendasar dalam sistem antrian pesan.   

Amplop bisa digambarkan seperti pada gambar di bawah ini:  

Structure of a messageStructure of a messageStructure of a message
Struktur Pesan

Dua bidang pertama (sender dan destination) adalah referensi ke entitas yang dibuat dan entitas yang akan menerima pesan ini. Dengan menggunakan bidang-bidang itu, baik pengirim maupun penerima dapat mengetahui dari mana pesan tersebut akan dikirim dan dari mana asalnya.

Dua bidang lainnya (Type dan Data) bekerja sama untuk memastikan pesan ditangani dengan benar. Bagian Type  menggambarkan apa pesan ini; misalnya, jika tipe ini   "damage", penerima akan menangani pesan ini sebagai perintah untuk mengurangi poin kesehatan; jika tipenya adalah  "pursue", penerima akan menganggapnya sebagai instruksi untuk mengejar sesuatu - dan seterusnya.

Bagian data terhubung langsung dengan bidang type. Seperti  contoh sebelumnya, jika tipe pesannya adalah   "damage", maka bidang data akan berisi angka-katakan,   10-yang menggambarkan jumlah kerusakan yang harus diaplikasikan pada poin kesehatan.  Jika jenis pesannya adalah   "pursue",   data akan berisi objek yang menggambarkan target yang harus diupayakan.

Bidang data dapat berisi informasi apapun, yang menjadikan amplop sarana komunikasi yang serbaguna. Apa pun dapat ditempatkan di bidang itu: bilangan bulat, pelampung, senar, dan bahkan benda lainnya.  Aturan praktisnya adalah bahwa penerima harus tahu apa yang ada di bidang data berdasarkan apa yang ada di bidang type.

Semua teori tersebut dapat diterjemahkan dalam kelas yang sangat sederha bernama Message . Ini mengandung empat properti. Satu untuk setiap bidang.

1
Message = function (to, from, type, data) {
2
    // Properties

3
    this.to = to;        // a reference to the entity that will receive this message

4
    this.from = from;    // a reference to the entity that sent this message

5
    this.type = type;    // the type of this message

6
    this.data = data;    // the content/data of this message

7
};

Sebagai contoh penggunaan ini, jika suatu entitas A ingin mengirim sebuah pesan  "damage" ke entitas B, hal yang harus dilakukan adalah melakukan instantiate objek kelas  Message, setel properti  untuk  B, atur properti dari dirinya sendiri (entity  A),  atur type untuk "damage" dan terakhir, atur data ke beberapa nomor (10, misalnya):

1
// Instantiate the two entities

2
var entityA = new Entity();
3
var entityB = new Entity();
4
5
// Create a message to entityB, from entityA,

6
// with type "damage" and data/value 10.

7
var msg = new Message();
8
9
msg.to = entityB;
10
msg.from = entityA;
11
msg.type = "damage";
12
msg.data = 10;
13
14
// You can also instantiate the message directly

15
// passing the information it requires, like this:

16
var msg = new Message(entityB, entityA, "damage", 10);

Sekarang kita punya cara untuk membuat pesan, sekarang saatnya memikirkan kelas yang akan menyimpan dan mengantarkannya.

Menerapkan Antrian

Kelas yang bertanggung jawab untuk menyimpan dan menyampaikan pesan akan dipanggil sebagai MessageQueue (antrian pesan).  Ini akan bekerja sebagai kantor pos: semua pesan diserahkan ke kelas ini, yang memastikan mereka akan dikirim ke tempat tujuan mereka.

Untuk saat ini, kelas MessageQueue akan memiliki struktur yang sangat sederhana:

1
/**

2
 * This class is responsible for receiving messages and

3
 * dispatching them to the destination.

4
 */
5
MessageQueue = function () {
6
    this.messages = [];  // list of messages to be dispatched

7
};
8
9
// Add a new message to the queue. The message must be an

10
// instance of the class Message.

11
MessageQueue.prototype.add = function(message) {
12
    this.messages.push(message);
13
};

Properti   messages   adalah sebuah array.  Ini akan menyimpan semua pesan yang akan dikirimkan oleh MessageQueue.    Metode add()  menerima objek kelas Message  sebagai parameter, dan menambahkan objek itu ke dalam daftar pesan.  

Begini cara contoh entitas A kita sebelumnya mengirim pesan pada entitas B tentang kerusakan yang akan bekerja dengan menggunakan kelas MessageQueue:

1
// Instantiate the two entities and the message queue

2
var entityA = new Entity();
3
var entityB = new Entity();
4
var messageQueue = new MessageQueue();
5
6
// Create a message to entityB, from entityA,

7
// with type "damage" and data/value 10.

8
var msg = new Message(entityB, entityA, "damage", 10);
9
10
// Add the message to the queue

11
messageQueue.add(msg);

Sekaranf kita memiliki cara membuat dan menyimpan pesan dalam antiran (quenue). Sekarang waktunya membuat mereka mencapai tujuannya.

Mengirim Pesan

Untuk membuat kelas  MessageQueue  sebenarnya  kirimkan pesan yang diposting, pertama kita perlu mendefinisikannya   bagaimana entitas akan menangani dan menerima pesan. Cara termudah adalah dengan menambahkan metode yang dinamai  onMessage () untuk setiap entitas yang dapat menerima pesan:

1
/**

2
 * This class describes a generic entity.

3
 */
4
Entity = function () {
5
    // Initialize anything here, e.g. Phaser stuff

6
};
7
8
// This method is invoked by the MessageQueue

9
// when there is a message to this entity.

10
Entity.prototype.onMessage = function(message) {
11
    // Handle new message here

12
};

Kelas MessageQueue akan memanggil metode   onMessage ()   dari masing-masing entitas yang harus menerima pesan. Parameter yang dilewatkan ke metode tersebut adalah pesan yang disampaikan oleh sistem antrian (dan diterima oleh tujuan).  

Kelas MessageQueue akan mengirimkan pesan ke antreannya sekaligus, di metode dispatch():

1
/**

2
 * This class is responsible for receiving messages and

3
 * dispatching them to the destination.

4
 */
5
MessageQueue = function () {
6
    this.messages = [];  // list of messages to be dispatched

7
};
8
9
MessageQueue.prototype.add = function(message) {
10
    this.messages.push(message);
11
};
12
13
// Dispatch all messages in the queue to their destination.

14
MessageQueue.prototype.dispatch = function() {
15
    var i, entity, msg;
16
17
    // Iterave over the list of messages

18
    for(i = 0; this.messages.length; i++) {
19
        // Get the message of the current iteration

20
        msg = this.messages[i];
21
22
        // Is it valid?

23
        if(msg) {
24
            // Fetch the entity that should receive this message 

25
            // (the one in the 'to' field)

26
            entity = msg.to;
27
28
            // If that entity exists, deliver the message.

29
            if(entity) {
30
                entity.onMessage(msg);
31
            }
32
33
            // Delete the message from the queue

34
            this.messages.splice(i, 1);
35
            i--;
36
        }
37
    }
38
};

Metode ini mengulai  semua pesan dalam antrian dan, untuk setiap pesan,    bidang to yang digunakan untuk mengambil referensi ke penerima.  Metode  onMessage () penerima kemudian dipanggil, dengan pesan saat ini sebagai parameter, dan pesan yang disampaikan kemudian dihapus dari daftar  MessageQueue. Proses ini diulang sampai semua pesan dikirim.

Menggunakan Antrian Pesan

Saatnya untuk melihat semua rincian pelaksanaan ini bekerja sama. Mari kita gunakan sistem antrian pesan kita dalam sebuah demo yang sangat sederhana yang terdiri dari beberapa entitas bergerak yang saling berinteraksi satu sama lain. Agar sederhana, kita akan bekerja dengan tiga entitas: Healer (penyembuh), Runner (pelari) dan Hunter (pemburu).

Runner memiliki bar kesehatan dan bergerak secara acak. Healer akan menyembuhkan setiap Runner yang lewat didekatnya; di sisi lain, Hunter akan menimbulkan kerusakan di tempat terdekat dari Runner. Semua interaksi akan ditangani dengan menggunakan sistem antrian pesan.

Menambahkan Antrian Pesan

Mari kita mulai dengan membuat PlayState yang berisi daftar entitas (healer, runner, dan hunter) dan sebuah instance dari kelas MessageQueue:

1
var PlayState = function() {
2
    var entities;  	// list of entities in the game

3
	var messageQueue;	// the message queue (dispatcher)

4
5
	this.create = function() {
6
		// Initialize the message queue

7
		messageQueue = new MessageQueue();
8
9
		// Create a group of entities.

10
		entities = this.game.add.group();
11
	};
12
13
	this.update = function() {
14
		// Make all messages in the message queue

15
    	// reach their destination.

16
		messageQueue.dispatch();
17
	};
18
};

Dalam lingkaran permainan (game loop), diwakili oleh metode update(), antrian pesan dari metode dispatch() dipanggil, jadi semua pesan dikirim di akhir setiap frame permainan.

Menambahkan Runner

Kelas Runner memiliki struktur sebagai berikut:

1
/**

2
 * This class describes an entity that just

3
 * wanders around.

4
 */
5
Runner = function () {
6
    // initialize Phaser stuff here...

7
};
8
9
// Invoked by the game on each frame

10
Runner.prototype.update = function() {
11
    // Make things move here...

12
}
13
14
// This method is invoked by the message queue

15
// to make the runner deal with incoming messages.

16
Runner.prototype.onMessage = function(message) {
17
    var amount;
18
19
    // Check the message type so it's possible to

20
    // decide if this message should be ignored or not.

21
    if(message.type == "damage") {
22
        // The message is about damage.

23
        // We must decrease our health points. The amount of

24
        // this decrease was informed by the message sender

25
        // in the 'data' field.

26
        amount = message.data;
27
        this.addHealth(-amount);
28
29
    } else if (message.type == "heal") {
30
        // The message is about healing.

31
        // We must increase our health points. Again the amount of

32
        // health points to increase was informed by the message sender

33
        // in the 'data' field.

34
        amount = message.data;
35
        this.addHealth(amount);
36
37
    } else {
38
        // Here we deal with messages we are not able to process.

39
        // Probably just ignore them :)

40
    }
41
};

Bagian yang paling penting adalah metode onMessage() yang dipanggil oleh antrian pesan setiap kali ada pesan baru untuk contoh ini. Seperti yang sudah dijelaskan sebelumnya, bidang type dalam pesan tersebut digunakan untuk memutuskan apa komunikasi ini.

Berdasarkan jenis pesannya, tindakan yang benar dilakukan: jika tipe pesannya adalah "damage", titik kesehatan menurun; jika tipe pesannya adalah "heal", poin kesehatan meningkat. Jumlah poin kesehatan yang meningkat atau menurun ditentukan oleh pengirim di bidang pesan data.

Dalam PlayState, kami menambahkan beberapa runner ke daftar entitas:

1
var PlayState = function() {
2
    // (...)

3
4
	this.create = function() {
5
		// (...)

6
7
		// Add runners

8
		for(i = 0; i < 4; i++) {
9
			entities.add(new Runner(this.game, this.game.world.width * Math.random(), this.game.world.height * Math.random()));
10
		}
11
	};
12
13
	// (...)

14
};

Hasilnya adalah empat pelari bergerak secara acak.:

Menambahkan Hunter (pemburu)

Kelas Hunter mengikuti struktur sebagai berikut:

1
/**

2
 * This class describes an entity that just

3
 * wanders around hurting the runners that pass by.

4
 */
5
Hunter = function (game, x, y) {
6
    // initialize Phaser stuff here

7
};
8
9
// Check if the entity is valid, is a runner, and is within the attack range.

10
Hunter.prototype.canEntityBeAttacked = function(entity) {
11
    return  entity && entity != this &&
12
            (entity instanceof Runner) &&
13
            !(entity instanceof Hunter) &&
14
            entity.position.distance(this.position) <= 150;
15
};
16
17
// Invoked by the game during the game loop.

18
Hunter.prototype.update = function() {
19
    var entities, i, size, entity, msg;
20
21
    // Get a list of entities

22
    entities = this.getPlayState().getEntities();
23
24
    for(i = 0, size = entities.length; i < size; i++) {
25
        entity = entities.getChildAt(i);
26
27
        // Is this entity a runner and is it close?

28
        if(this.canEntityBeAttacked(entity)) {
29
            // Yeah, so it's time to cause some damage!

30
            msg = new Message(entity, this, "damage", 2);
31
32
            // Send the message away!

33
            this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong.

34
        }
35
    }
36
};
37
38
// Get a reference to the game's PlayState

39
Hunter.prototype.getPlayState = function() {
40
    return this.game.state.states[this.game.state.current];
41
};
42
43
// Get a reference to the game's message queue.

44
Hunter.prototype.getMessageQueue = function() {
45
    return this.getPlayState().getMessageQueue();
46
};

Hunter juga akan bergerak, tapi akan menyebabkan kerusakan pada semua pelari yang mendekat. Perilaku ini diterapkan di metode update(), di mana semua entitas permainan diperiksa dan pelari diberi pesan tentang kerusakan.

Pesan kerusakan dibuat sebagai berikut:

1
msg = new Message(entity, this, "damage", 2);

Pesan berisi informasi tentang tujuan (entitas, dalam kasus ini, yang merupakan entitas yang dianalisis dalam iterasi saat ini), pengirim (ini, yang mewakili hunter yang melakukan serangan tersebut), jenis pesannya (" damage ") dan jumlah damage / kerusakan (2, dalam hal ini, ditugaskan ke bidang pesan data).

Pesan tersebut kemudian diposting ke tujuan melalui perintah this.getMessageQueue () menambahkan (msg), yang menambahkan pesan yang baru dibuat ke antrian pesan.

Akhirnya, kita tambahkan Hunter ke daftar entitas di PlayState:

1
var PlayState = function() {
2
    // (...)

3
4
    this.create = function() {
5
		// (...)

6
        
7
		// Add hunter at position (20, 30)

8
    	entities.add(new Hunter(this.game, 20, 30));
9
	};
10
11
	// (...)

12
};

Hasilnya adalah beberapa runner bergerak, menerima pesan dari hunter saat mereka saling berdekatan:

Saya menambahkan amplop terbang sebagai bantuan visual untuk membantu menunjukkan apa yang sedang terjadi.

Menambakan Healer (Penyembuh)

Kelas Healer memiliki struktrur sebagai berikut:

1
/**

2
 * This class describes an entity that is

3
 * able to heal any runner that passes nearby.

4
 */
5
Healer = function (game, x, y) {
6
    // Initializer Phaser stuff here

7
};
8
9
Healer.prototype.update = function() {
10
    var entities, i, size, entity, msg;
11
12
    // The the list of entities in the game

13
    entities = this.getPlayState().getEntities();
14
15
    for(i = 0, size = entities.length; i < size; i++) {
16
        entity = entities.getChildAt(i);
17
18
        // Is it a valid entity?

19
        if(entity) {
20
            // Check if the entity is within the healing radius

21
            if(this.isEntityWithinReach(entity)) {
22
                // The entity can be healed!

23
                // First of all, create a new message regaring the healing

24
                msg = new Message(entity, this, "heal", 2);
25
26
                // Send the message away!

27
                this.getMessageQueue().add(msg); // or just entity.onMessage(msg); if you want to bypass the message queue for some reasong.

28
            }
29
        }
30
    }
31
};
32
33
// Check if the entity is neither a healer nor a hunter and is within the healing radius.

34
Healer.prototype.isEntityWithinReach = function(entity) {
35
    return !(entity instanceof Healer) && !(entity instanceof Hunter) && entity.position.distance(this.position) <= 200;
36
};
37
38
// Get a reference to the game's PlayState

39
Healer.prototype.getPlayState = function() {
40
    return this.game.state.states[this.game.state.current];
41
};
42
43
// Get a reference to the game's message queue.

44
Healer.prototype.getMessageQueue = function() {
45
    return this.getPlayState().getMessageQueue();
46
};

Kode dan strukturnya sangat mirip dengan kelas Hunter, kecuali beberapa perbedaan. Sama halnya dengan hunter yang diimplementasikan, metode update() healer diualang atas daftar entitas dalam permainan, pesan setiap entitas dalam jangkauan penyembuhannya:

1
msg = new Message(entity, this, "heal", 2);

Pesan ini juga memiliki tujuan (entitas), pengirim (ini, penyembuh yang melakukan aksinya), jenis pesan ("heal / penyembuhan") dan jumlah titik penyembuhan (2, yang ditugaskan di bidang pesan data).

Kami menambahkan healer ke daftar entitas di PlayState cara yang sama dengan hunter kita dan hasilnya adalah adegan dengan runner, hunter dan healer:

Dan itu dia! Kita memiliki tiga entitas yang berbeda yang berinteraksi satu sama lain dengan bertukar pesan.

Diskusi Mengenai Fleksibilitas

Sistem antrian pesan ini merupakan cara serbaguna untuk mengelola interaksi dalam sebuah game. Interaksi dilakukan melalui saluran komunikasi yang bersatu dan memiliki satu antarmuka yang mudah digunakan dan diimplementasikan.

Seiring bertambahnya permainan anda berada dalam kompleksitas, interaksi baru mungkin dibutuhkan. Beberapa dari mereka mungkin benar-benar tak terduga, jadi anda harus menyesuaikan kode anda untuk menghadapinya. Jika anda menggunakan sistem antrian pesan, masalah ada pada menambahkan pesan baru di suatu tempat dan menanganinya di tempat lain.

Misalnya, bayangkan Anda ingin membuat hunter berinteraksi dengan healer; Anda hanya perlu membuat hunter mengirim pesan dengan interaksi baru-misalnya, "flee" - dan pastikan bahwa healer bisa mengatasinya di metode onMessage:

1
// In the Hunter class:

2
Hunter.prototype.someMethod = function() {
3
    // Get a reference to a nearby healer

4
    var healer = this.getNearbyHealer();
5
    
6
    // Create message about fleeing a place

7
    var place = {x: 30, y: 40};
8
    var msg = new Message(entity, this, "flee", place);
9
10
    // Send the message away!

11
    this.getMessageQueue().add(msg);
12
};
13
14
// In the Healer class:

15
Healer.prototype.onMessage = function(message) {
16
    if(message.type == "flee") {
17
        // Get the place to flee from the data field in the message

18
        var place = message.data;
19
        
20
        // Use the place information

21
        flee(place.x, place.y);
22
    }
23
};

Mengapa Tidak Mengirim Pesan secara Langsung?

Meski bertukar pesan antar entitas bisa bermanfaat, anda mungkin berpikir mengapa MessageQueue sangat dibutuhkan. Tidak bisakah anda memanggil receiver itu? Metode onMessage() sendiri tidak mengandalkan pada MessageQueue, seperti pada kode di bawah ini?

1
Hunter.prototype.someMethod = function() {
2
    // Get a reference to a nearby healer

3
    var healer = this.getNearbyHealer();
4
    
5
    // Create message about fleeing a place

6
    var place = {x: 30, y: 40};
7
    var msg = new Message(entity, this, "flee", place);
8
9
    // Bypass the MessageQueue and directly deliver

10
    // the message to the healer.

11
    healer.onMessage(msg);
12
};

Anda pasti bisa menerapkan sistem pesan seperti itu, tapi penggunaan sebuah MessageQueue memiliki beberapa keuntungan.

Misalnya, dengan memusatkan pengiriman pesan, anda dapat menerapkan beberapa fitur keren seperti pesan tertunda, kemampuan untuk memberi pesan pada sekelompok entitas, dan informasi debug visual (seperti amplop terbang yang digunakan dalam tutorial ini).

Ada ruang untuk kreativitas di kelas MessageQueue, terserah pada anda dan persyaratan permainan anda.

Kesimpulan

Menangani interaksi antara entitas permainan menggunakan sistem antrian pesan adalah cara agar kode anda tetap teratur dan siap dimasa depan. Interaksi baru dapat dengan mudah dan cepat ditambahkan, bahkan gagasan anda yang paling rumit sekalipun, selama mereka dienkapsulasi sebagai pesan.

Seperti yang dibahas dalam tutorial, anda bisa mengabaikan penggunaan antrian pesan utama dan hanya mengirim pesan langsung ke entitas. Anda juga bisa memusatkan komunikasi menggunakan pengiriman (kelas MessageQueue dalam kasus kita) untuk memberi ruang bagi fitur baru di masa depan, seperti pesan tertunda.

Saya harap Anda menemukan pendekatan ini berguna dan menambahkannya ke sabuk utilitas pengembang game Anda. Metode ini mungkin tampak berlebihan untuk proyek kecil, namun pastinya akan membuat Anda sedikit sakit kepala dalam jangka panjang untuk permainan yang lebih besar.

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 Web Design 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.