Terkadang, ketika Anda membaca tugas teknis dan menetapkan tenggat waktu untuk implementasi, meremehkan jumlah waktu dan upaya yang dihabiskan untuk memecahkan masalah tertentu. Itu terjadi bahwa suatu titik, yang diperkirakan oleh waktu per minggu, dilakukan pada satu jam, dan kadang-kadang sebaliknya. Tetapi artikel ini bukan tentang itu. Ini adalah demonstrasi evolusi solusi untuk suatu masalah. Dari sejak awal hingga implementasi.

Ketentuan yang Digunakan
Tandai atau spidol - gambar yang dimuat ke mesin AR, yang dikenali oleh kamera perangkat (tablet atau smartphone) dan dapat diidentifikasi secara unik
Ditemukan - status penanda ketika terdeteksi di bidang tampilan kamera
Lost - status marker saat hilang dari tampilan kamera
Itu dapat ditampilkan - ketika marker ditemukan, kami menampilkan konten yang dilampirkan pada marker
Tidak dapat ditampilkan - ketika kami menemukan marker, jangan tampilkan konten - Konten yang melekat pada marker - objek apa pun (model 3D, sprite, sistem partikel, dll.) Yang dapat dilampirkan pada marker dan, yang, karenanya, akan ditampilkan di layar jika marker ditemukan
Mark, marker, found, lost - status dasar yang melekat pada semua engine yang menyediakan fungsionalitas pengenalan
Itu dapat ditampilkan dan tidak dapat ditampilkan - negara yang digunakan untuk memecahkan masalah ini
Contoh:
- Unduh aplikasi => semua merek yang diunduh dapat dikenali
- Kami mencoba mengenali => keadaan penanda berubah menjadi "ditemukan"
- Jika marker dapat ditampilkan => nyatakan marker "ditemukan" dan kami menampilkan model yang terpasang pada marker
- Jika penanda tidak dapat ditampilkan => keadaan penanda "ditemukan", tetapi model yang terpasang tidak ditampilkan
- Merek menghilang dari bidang pandang kamera => kami mengubah status menjadi "hilang"
Pendahuluan
Ada satu kartu pos besar ukuran lembar A4. Ini dibagi menjadi 4 bagian yang sama (format satu bagian A5), pada masing-masing bagian ini ada:
- Satu tanda sudut penuh (1)
- Setengah dari tanda sisi bawah (5)
- Setengah dari tanda sisi atas (8)
- Markus Pusat Quarter (9)

Jika Anda bekerja dengan mesin pengenalan, misalnya, Vuforia, maka Anda mungkin tahu bahwa tidak ada yang namanya "kualitas pengenalan". Tanda itu dikenali atau tidak dikenali. Dengan demikian, jika mesin "melihat" merek, itu mengubah negara untuk Find
dan metode OnSuccess()
, jika "hilang" itu, keadaan berubah menjadi Lost
dan metode OnLost()
. Dengan demikian, dari kondisi yang ada dan input data, suatu situasi muncul ketika memiliki bagian dari kartu (setengah atau seperempat) adalah mungkin untuk mengenali merek.
Masalahnya adalah bahwa sesuai dengan tugas teknis, pembukaan karakter secara bertahap direncanakan. Dalam situasi ini, membuka secara bertahap adalah mungkin, tetapi mengingat bahwa tidak ada orang yang mencoba mengenali seperempat atau setengah dari merek.
Penting untuk menerapkan logika dalam bentuk kode program, yang memastikan pembukaan konten secara bertahap yang dilampirkan pada marker. Dari lokasi elemen pada kartu diketahui bahwa marker 1, 2, 3, 4 tersedia untuk ditampilkan pada awalnya.

Jika konten telah dibaca dan ditampilkan pada 2 marker, misalnya, 2 dan 3, maka kami mengizinkan menampilkan konten pada marker 6. Jika marker 1 belum dibaca, maka akses ke marker 5 ditutup. Selanjutnya dengan analogi. Kami agak memberikan izin untuk menampilkan konten di penanda sisi hanya ketika kami telah membaca penanda sudut yang berdekatan.

Jika marker dari 1 hingga 8 tersedia dan ditemukan, maka buka konten pada marker 9 untuk tampilan. Setiap marker memiliki 2 status - konten tersedia dan tidak tersedia untuk tampilan, yang bertanggung jawab pada bidang isian public bool IsActive;
Segera jelas bahwa ini harus berupa mesin negara dengan transisi antar negara, atau penerapan pola "Negara".
SpoilerHasilnya bukan satu, bukan yang lain. Saya tidak bisa mengatakan bahwa ini adalah penopang karena solusi sepenuhnya memenuhi persyaratan di awal artikel. Tapi Anda bisa berdebat dengan saya.
Mengenai hal ini, saya memberi Anda kesempatan untuk berpikir sedikit tentang solusi yang mungkin dan implementasi dari tugas ini. Butuh sekitar 5 jam untuk menyadari dan memperbaiki di kepala saya gambar keputusan.
Untuk kejelasan, saya merekam video di mana hasil akhir dari algoritma (jika Anda bisa menyebutnya begitu) sudah ditangkap.
Pendekatan Solusi
1. Dari penanda sudut ke tengah
Hal pertama yang terlintas dalam pikiran adalah untuk menyajikan interaksi antara penanda dari sudut ke tengah. Dalam bentuk grafis, tampilannya seperti ini:

Masalahnya:
- Bagaimana cara menentukan label sisi yang akan diubah statusnya? Yang di sebelah kiri atau di kanan? Kami juga memaksa setiap penanda untuk "tahu" tentang keberadaan yang sentral.
- Penting untuk menambahkan dependensi yang tidak jelas dari kategori: penanda sisi berlangganan acara penanda sudut IsChangedEventCallback (), tindakan serupa harus dilakukan untuk penanda pusat.
- Jika kita menganggap setiap jenis penanda sebagai entitas, maka dalam hierarki entitas ini kita akan meneruskan perintah perubahan-negara dari bawah ke atas. Ini tidak terlalu baik, karena kita mengikat diri kita sendiri dengan angka, dalam hal ini, penanda sudut, kehilangan kemampuan untuk mengukur.
Tidak dapat menempatkan solusi di atas ke kepala saya karena banyaknya kasus tepi dan kompleksitas persepsi, saya mengubah pendekatan untuk memilih penanda di mana dependensi mulai menyebar.
2. Lateral tahu tentang pusat dan sudut
Memikirkan solusi paragraf 3 dari pendekatan sebelumnya, muncul ide untuk mengubah jenis penanda, dari mana keadaan penanda lainnya mulai berubah. Sebagai penanda sisi utama diambil. Dalam skenario ini, komunikasi (dependensi) terlihat seperti ini:

Dari sini segera menjadi jelas bahwa koneksi dari lateral ke pusat berlebihan, karena penanda lateral tidak perlu tahu apa-apa tentang penanda pusat, oleh karena itu pendekatan ini segera ditransformasikan menjadi yang terakhir.
3. Yang pusat tahu tentang semua orang, yang di samping tahu tentang sudut

Solusi terakhir adalah ketika penanda samping tahu tentang sudut-sudut, sudut-sudut "menjalani hidup mereka", dan yang sentral tahu tentang keadaan semua penanda.

Bekerja dengan tampilan kartu pos sangat tidak nyaman. Hubungan antara entitas tidak terlihat cukup jelas untuk dengan mudah mengubahnya menjadi kode. Upaya untuk menafsirkan dalam bentuk pohon biner dapat menimbulkan beberapa ambiguitas. Tapi di sini salah satu properti dari pohon biner dilanggar, sehingga ambiguitasnya segera menghilang. Dari mana kita dapat menyimpulkan bahwa representasi ini dapat ditafsirkan secara jelas dan digunakan untuk secara grafis mewakili solusi untuk masalah tersebut. Berdasarkan kesimpulan ini, kami akan menggunakan notasi grafik, yaitu:
- Angle Marker - Angle Node (level 3)
- Penanda Sisi - Node Samping (level 2)
- Center Marker - Center Node (level 1)
Keuntungan:
- Ketergantungan antara penanda jelas dan jelas.
- Setiap level dapat direpresentasikan dalam bentuk 3 entitas, yang masing-masing terdiri dari bagian-bagian dasar, tetapi dengan tambahannya yang melekat pada masing-masing level
- Untuk memperluas, Anda hanya perlu menambahkan tipe node baru dengan karakteristiknya sendiri
- Solusi ini mudah dibayangkan dalam gaya OO (berorientasi objek)
Implementasi
Entitas dasar
Mari kita membuat antarmuka yang berisi elemen-elemen yang melekat dalam setiap entitas (nama, negara):
public interface INode { string Name { get; set; } bool IsActive { get; set; } }
Selanjutnya, kami menjelaskan esensi dari setiap node:
- CornerNode - simpul sudut. Cukup terapkan antarmuka
INode
:
public class CornerNode : INode { public string Name { get; set; } public bool IsActive { get; set; } public Node(string name) { Name = name; IsActive = true; } }
Mengapa IsActive = true
?
JawabannyaDari kondisi masalah, konten penanda sudut pada awalnya tersedia untuk pengakuan.
- SideNode - simpul samping. Kami mengimplementasikan antarmuka
LeftCornerNode
, tetapi menambahkan bidang LeftCornerNode
dan RightCornerNode
. Dengan demikian, node samping mempertahankan keadaannya sendiri dan hanya tahu tentang keberadaan node samping.
public class SideNode : INode { public string Name { get; set; } public bool IsActive { get; set; } public CornerNode LeftCornerNode { get; } public CornerNode RightCornerNode { get; } public SideNode(string name, CornerNode leftNode, CornerNode rightNode) { Name = name; IsActive = false; LeftCornerNode = leftNode; RightCornerNode = rightNode; } }
- CenterNode adalah simpul pusat. Seperti pada yang sebelumnya, kami menerapkan
INode
. Tambahkan bidang tipe List<INode>
.
public class CentralNode : INode { public List<INode> NodesOnCard; public string Name { get; set; } public bool IsActive { get; set; } public CentralNode(string name) { Name = name; IsActive = false; } }
Kelas pembuka
Metode dan bidang pribadi
Sekarang kita telah menciptakan semua elemen kartu yang telah kita buat (semua jenis marker), kita dapat mulai menggambarkan esensi kartu itu sendiri. Saya tidak terbiasa memulai kelas dengan konstruktor. Saya selalu mulai dengan metode dasar yang melekat pada entitas tertentu. Mari kita mulai dengan bidang pribadi dan metode pribadi.
private List<CornerNode> cornerNodes; private List<SideNode> sideNodes; private CentralNode centralNode;
Dengan bidang, semuanya cukup sederhana. 2 daftar dengan sudut, simpul samping dan satu bidang simpul pusat.
Selanjutnya Anda perlu mengklarifikasi sedikit. Faktanya adalah bahwa penanda itu sendiri adalah tipe Trackable
dan tidak memiliki gagasan (dan tidak seharusnya) bahwa itu adalah bagian dari beberapa logika lain di sana. Karena itu, yang bisa kita gunakan untuk mengontrol tampilan adalah namanya. Dengan demikian, jika marker itu sendiri tidak menyimpan tipe node yang menjadi miliknya, maka kita harus mentransfer tanggung jawab ini ke kelas OpenCard
kita. Berdasarkan ini, pertama-tama kami menggambarkan 3 metode pribadi yang bertanggung jawab untuk menentukan jenis node.
private bool IsCentralNode(string name) { return name == centralNode.Name; } private bool IsSideNode(string name) { foreach (var sideNode in sideNodes) if (sideNode.Name == name) return true; return false; } private bool IsCornerNode(string name) { foreach (var sideNode in cornerNodes) if (sideNode.Name == name) return true; return false; }
Tetapi metode ini tidak masuk akal untuk digunakan secara langsung. Tidak nyaman untuk beroperasi dengan nilai-nilai Boolean ketika Anda bekerja dengan objek dari level abstraksi lain. Oleh karena itu, kita akan membuat enum NodeType
sederhana dan metode pribadi GetNodeType()
, yang merangkum sendiri semua logika yang terkait dengan menentukan jenis node.
public enum NodeType { CornerNode, SideNode, CentralNode } private NodeType? GetNodeType(string name) { if (IsCentralNode(name)) return NodeType.CentralNode; if (IsSideNode(name)) return NodeType.SideNode; if (IsCornerNode(name)) return NodeType.CornerNode; return null; }
Metode publik
IsExist
adalah metode yang mengembalikan nilai boolean yang menunjukkan apakah merek kami milik kartu pos. Ini adalah metode bantu, yang dilakukan sehingga jika penanda bukan milik kartu apa pun, kami dapat menampilkan konten di dalamnya.
public bool IsExist(string name) { foreach (var node in centralNode.NodesOnCard) if (node.Name == name) return true; if (centralNode.Name == name) return true; return false; }
CheckOnActiveAndChangeStatus
- metode (seperti namanya) di mana kami memeriksa keadaan saat ini dari simpul dan mengubah statusnya.
public bool CheckOnActiveAndChangeStatus(string name) { switch (GetNodeType(name)) { case NodeType.CornerNode: foreach (var node in cornerNodes) if (node.Name == name) return node.IsActive = true; return false; case NodeType.SideNode: foreach (var node in sideNodes) if (node.LeftCornerNode.IsActive && node.RightCornerNode.IsActive) return true; return false; case NodeType.CentralNode: foreach (var node in centralNode.NodesOnCard) if (!node.IsActive) return false; return centralNode.IsActive = true; default: return false; } }
Konstruktor
Ketika semua kartu ada di atas meja, kita akhirnya bisa pergi ke konstruktor. Mungkin ada beberapa pendekatan untuk inisialisasi. Tapi saya memutuskan untuk menghilangkan gerakan yang tidak perlu dari kelas OpenCard
. Itu harus menjawab dengan kami apakah konten tersedia untuk ditampilkan atau tidak. Oleh karena itu, kami hanya meminta daftar input node dari 2 jenis dan node pusat.
public OpenCard(List<CornerNode> listCornerNode, List<SideNode> listSideNode, CentralNode centralNode) { CornerNodes = listCornerNode; SideNodes = listSideNode; CentralNodes = centralNode; CentralNodes.NodesOnCard = new List<INode>(); foreach (var node in CornerNodes) CentralNodes.NodesOnCard.Add(node); foreach (var node in SideNodes) CentralNodes.NodesOnCard.Add(node); }
Perhatikan bahwa karena simpul pusat hanya perlu memeriksa kondisi bahwa semua simpul true
lainnya, itu cukup bagi kita untuk secara implisit INode
simpul miring dan sentral yang masuk ke konstruktor ke tipe INode
.
Inisialisasi
Apa cara paling nyaman untuk membuat objek yang tidak perlu dilampirkan (seperti komponen MonoBehaviour
) ke GameObject? - Benar, ScriptableObject
. Juga, untuk kenyamanan, tambahkan atribut MenuItem
, yang akan menyederhanakan pembuatan kartu baru.
[CreateAssetMenu(fileName = "Open Card", menuName = "New Open Card", order = 51)] public class OpenCardScriptableObject : ScriptableObject { public string leftDownName; public string rightDownName; public string rightUpName; public string leftUpName; public string leftSideName; public string rightSideName; public string downSideName; public string upSideName; public string centralName; }
Akor terakhir dalam komposisi kami akan menjadi bagian melalui array yang ditambahkan (jika ada) ScriptableObject
dan pembuatan kartu pos dari mereka. Setelah itu, tetap bagi kami dalam metode Update
untuk cukup memeriksa apakah kami dapat menampilkan konten atau tidak.
public OpenCardScriptableObject[] openCards; private List<OpenCard> _cardList; void Awake() { if (openCards.Length != 0) { _cardList = new List<OpenCard>(); foreach (var card in openCards) { var leftDown = new CornerNode(card.leftDownName); var rightDown = new CornerNode(card.rightDownName); var rightUp = new CornerNode(card.rightUpName); var leftUp = new CornerNode(card.leftUpName); var leftSide = new SideNode(card.leftSideName, leftUp, leftDown); var downSide = new SideNode(card.downSideName, leftDown, rightDown); var rightSide = new SideNode(card.rightSideName, rightDown, rightUp); var upSide = new SideNode(card.upSideName, rightUp, leftUp); var central = new CentralNode(card.centralName); var nodes = new List<CornerNode>() {leftDown, rightDown, rightUp, leftUp}; var sideNodes = new List<SideNode>() {leftSide, downSide, rightSide, upSide}; _cardList.Add(new OpenCard(nodes, sideNodes, central)); } } } void Update() { var isNotPartCard = false; foreach (var card in _cardList) { if (card.IsExist(trackableName)) isNotPartCard = true; if (card.CheckOnActiveAndChangeStatus(trackableName)) imageTrackablesMap[trackableName].OnTrackSuccess(trackable); if (!isNotPartCard) imageTrackablesMap[trackableName].OnTrackSuccess(trackable); } }
Kesimpulan
Bagi saya pribadi, kesimpulannya adalah sebagai berikut:
- Saat mencoba menyelesaikan masalah, Anda perlu mencoba memecah unsur-unsurnya menjadi bagian-bagian atom. Lebih lanjut, dengan mempertimbangkan semua opsi yang memungkinkan untuk interaksi antara bagian-bagian atom ini, Anda harus mulai dengan objek, dari mana lebih banyak koneksi akan berpotensi datang. Dengan cara lain, dapat dirumuskan sebagai: berusaha untuk mulai menyelesaikan masalah dengan elemen yang, berpotensi, akan kurang dapat diandalkan
- Jika memungkinkan, Anda harus mencoba menyajikan data sumber dalam bentuk yang berbeda. Dalam kasus saya, representasi grafik sangat membantu saya.
- Setiap entitas dipisahkan dari yang lain dengan jumlah koneksi yang berpotensi berasal dari itu.
- Banyak tugas terapan yang lebih biasa diselesaikan dengan menulis suatu algoritma dapat diwakili dalam gaya OO
- Solusi yang memiliki dependensi dering adalah solusi yang buruk
- Jika sulit untuk menjaga semua koneksi antara objek di kepala Anda, ini adalah keputusan yang buruk
- Jika Anda tidak dapat mengingat logika interaksi objek - ini adalah keputusan yang buruk
- Kruk Anda tidak selalu merupakan keputusan yang buruk
Apakah Anda tahu solusi lain? - Tulis di komentar.