एल्डन रिंग क्वेस्ट ट्रैकर बनाना
मुझे स्किरिम पसंद था। मैंने खुशी-खुशी कई सौ घंटे इसे खेलने और फिर से खेलने में बिताए। इसलिए जब मैंने हाल ही में एक नए गेम के बारे में सुना, द स्किरिम ऑफ़ द 2020s , मुझे इसे खरीदना था। इस प्रकार जॉर्ज आरआर मार्टिन के कहानी मार्गदर्शन के साथ विशाल ओपन-वर्ल्ड आरपीजी एल्डन रिंग के साथ मेरी गाथा शुरू होती है।
खेल के पहले घंटे के भीतर, मैंने सीखा कि सोल गेम कितने क्रूर हो सकते हैं। मैं चट्टान के किनारे की दिलचस्प गुफाओं में घुस गया, ताकि मैं अंदर ही अंदर मर जाऊं कि मैं अपनी लाश को वापस नहीं पा सका।
मैंने अपने सारे रन खो दिए।
जब मैं लिफ्ट से सियोफ्रा नदी की ओर जा रहा था, तो मुझे आश्चर्य हुआ, केवल यह देखने के लिए कि अनुग्रह के निकटतम स्थल से बहुत दूर, भयानक मौत मेरा इंतजार कर रही थी। मैं फिर से मरने से पहले बहादुरी से भाग गया।
मैं भूतिया शख्सियतों और आकर्षक एनपीसी से मिला, जिन्होंने मुझे संवाद की कुछ पंक्तियों के साथ लुभाया ... जिसे मैं जरूरत पड़ने पर तुरंत भूल गया।
10/10, अत्यधिक अनुशंसित।
विशेष रूप से एल्डन रिंग के बारे में एक बात ने मुझे परेशान किया - कोई खोज ट्रैकर नहीं था। कभी अच्छा खेल, मैंने अपने iPhone पर एक नोट्स दस्तावेज़ खोला। बेशक, वह लगभग पर्याप्त नहीं था।
आरपीजी प्लेथ्रू विवरण ट्रैक करने में मेरी मदद करने के लिए मुझे एक ऐप की आवश्यकता थी। ऐप स्टोर पर कुछ भी वास्तव में जो मैं ढूंढ रहा था उससे मेल नहीं खाता था, इसलिए स्पष्ट रूप से मुझे इसे लिखना होगा। इसे बिखरी हुई अंगूठी कहा जाता है, और यह अभी ऐप स्टोर पर उपलब्ध है।
तकनीकी विकल्प
दिन में, मैं दायरे स्विफ्ट एसडीके के लिए प्रलेखन लिखता हूं। मैंने हाल ही में रियलमी के लिए एक स्विफ्टयूआई टेम्प्लेट ऐप लिखा था, जो डेवलपर्स को एक स्विफ्टयूआई स्टार्टर टेम्प्लेट प्रदान करने के लिए, लॉगिन प्रवाह के साथ पूरा करने के लिए। दायरे स्विफ्ट एसडीके टीम लगातार स्विफ्टयूआई सुविधाओं की शिपिंग कर रही है, जिसने इसे बनाया है - मेरी शायद पक्षपातपूर्ण राय में - ऐप विकास के लिए एक मृत सरल प्रारंभिक बिंदु।
मैं कुछ ऐसा चाहता था जिसे मैं सुपर फास्ट बना सकूं - आंशिक रूप से इसलिए मैं ऐप लिखने के बजाय एल्डन रिंग खेलने के लिए वापस आ सकता हूं, और आंशिक रूप से अन्य ऐप्स को बाजार में हरा सकता हूं जबकि हर कोई अभी भी एल्डन रिंग के बारे में बात कर रहा है। मुझे इस ऐप को बनाने में महीनों नहीं लगे। मैं इसे कल चाहता था। Realm + SwiftUI इसे संभव बनाने वाला था।
डेटा मॉडलिंग
मुझे पता था कि मैं खेल में खोजों को ट्रैक करना चाहता हूं। खोज मॉडल आसान था:
class Quest: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name = ""
@Persisted var isComplete = false
@Persisted var notes = ""
}
मुझे वास्तव में केवल एक नाम, खोज पूर्ण होने पर टॉगल करने के लिए एक बूल, एक नोट्स फ़ील्ड और एक विशिष्ट पहचानकर्ता की आवश्यकता थी।
जैसा कि मैंने अपने गेमप्ले के बारे में सोचा था, हालांकि, मुझे एहसास हुआ कि मुझे केवल खोज की आवश्यकता नहीं है - मैं स्थानों का ट्रैक भी रखना चाहता था। मैं ठोकर खाई - और जल्दी से जब मैंने मरना शुरू किया - इतने सारे शांत स्थान जिनमें शायद दिलचस्प गैर-खिलाड़ी चरित्र (एनपीसी) और भयानक लूट थे। मैं इस बात पर नज़र रखने में सक्षम होना चाहता था कि क्या मैंने किसी स्थान को साफ़ कर दिया था, या बस उससे दूर भाग गया था, इसलिए मुझे बाद में वापस जाना और बेहतर गियर और अधिक क्षमता होने के बाद इसे देखना याद था। इसलिए मैंने एक स्थान वस्तु जोड़ी:
class Location: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name = ""
@Persisted var isCleared = false
@Persisted var notes = ""
}
हम्म। यह काफी कुछ खोज मॉडल जैसा दिखता था। क्या मुझे वास्तव में एक अलग वस्तु की आवश्यकता थी? फिर मैंने उन शुरुआती स्थानों में से एक के बारे में सोचा जहां मैं गया था - चर्च ऑफ एलेह - जिसमें एक स्मिथ एविल था। मैंने अभी तक अपने गियर को बेहतर बनाने के लिए कुछ भी नहीं किया था, लेकिन यह जानना अच्छा हो सकता है कि भविष्य में किन स्थानों पर स्मिथ एविल था जब मैं अपग्रेड करने के लिए कहीं जाना चाहता था। इसलिए मैंने एक और बूल जोड़ा:
@Persisted var hasSmithAnvil = false
फिर मैंने सोचा कि कैसे उसी लोकेशन पर एक व्यापारी भी था। मैं भविष्य में जानना चाहता हूं कि क्या किसी स्थान पर एक व्यापारी था। इसलिए मैंने एक और बूल जोड़ा:
@Persisted var hasMerchant = false
महान! स्थान वस्तु को क्रमबद्ध किया गया।
पर... कुछ और था। मुझे एनपीसी से इन सभी दिलचस्प कहानियों की जानकारी मिलती रही। और क्या हुआ जब मैंने एक खोज पूरी की - क्या मुझे इनाम लेने के लिए एनपीसी में वापस जाने की आवश्यकता होगी? इसके लिए मुझे यह जानना होगा कि मुझे खोज किसने दी थी और वे कहाँ स्थित थे। एक तीसरा मॉडल जोड़ने का समय, एनपीसी, जो सब कुछ एक साथ जोड़ देगा:
class NPC: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name = ""
@Persisted var isMerchant = false
@Persisted var locations = List<Location>()
@Persisted var quests = List<Quest>()
@Persisted var notes = ""
}
महान! अब मैं एनपीसी को ट्रैक कर सकता था। मैं उन दिलचस्प कहानियों पर नज़र रखने में मेरी मदद करने के लिए नोट्स जोड़ सकता था, जबकि मैं यह देखने के लिए इंतजार कर रहा था कि क्या सामने आएगा। मैं खोज और स्थानों को एनपीसी के साथ जोड़ सकता था। इस वस्तु को जोड़ने के बाद, यह स्पष्ट हो गया कि यह वह वस्तु थी जो दूसरों को जोड़ती थी। एनपीसी स्थानों पर हैं। लेकिन मैं कुछ ऑनलाइन पढ़ने से जानता था कि कभी-कभी एनपीसी खेल में घूमते हैं, इसलिए स्थानों को कई प्रविष्टियों का समर्थन करना होगा - इसलिए सूची। एनपीसी क्वेस्ट देते हैं। लेकिन वह भी एक सूची होनी चाहिए, क्योंकि पहली बार एनपीसी से मुझे एक से अधिक खोज मिलीं। जब आप पहली बार खेल में प्रवेश करते हैं तो टूटे हुए कब्रिस्तान के ठीक बाहर वर्रे ने मुझसे कहा था कि "अनुग्रह के धागे का पालन करें" और "महल में जाएं।" सही, क्रमबद्ध!
अब मैं UI बनाने के लिए SwiftUI प्रॉपर्टी रैपर के साथ अपने ऑब्जेक्ट का उपयोग कर सकता था।
SwiftUI Views + Realm की जादुई संपत्ति के रैपर
चूंकि सब कुछ एनपीसी से लटका हुआ है, इसलिए मैं एनपीसी के विचारों से शुरू करूंगा। @ObservedResults
संपत्ति आवरण आपको ऐसा करने का एक आसान तरीका देता है।
struct NPCListView: View {
@ObservedResults(NPC.self) var npcs
var body: some View {
VStack {
List {
ForEach(npcs) { npc in
NavigationLink {
NPCDetailView(npc: npc)
} label: {
NPCRow(npc: npc)
}
}
.onDelete(perform: $npcs.remove)
.navigationTitle("NPCs")
}
.listStyle(.inset)
}
}
}
अब मैं सभी एनपीसी की एक सूची के माध्यम से पुनरावृति कर सकता था, एक स्वचालित था onDelete
NPC को हटाने के लिए कार्रवाई, और Realm के .searchable
. के कार्यान्वयन को जोड़ सकता है जब मैं खोज और फ़िल्टरिंग जोड़ने के लिए तैयार था। और यह मूल रूप से इसे मेरे डेटा मॉडल से जोड़ने के लिए एक पंक्ति थी। क्या मैंने उल्लेख किया कि Realm + SwiftUI अद्भुत है? स्थान और खोज के साथ एक ही काम करना काफी आसान था, और ऐप उपयोगकर्ताओं के लिए किसी भी पथ के माध्यम से अपने डेटा में गोता लगाना संभव बनाता है।
फिर, मेरा NPC विवरण दृश्य @ObservedRealmObject
. के साथ काम कर सकता है संपत्ति आवरण एनपीसी विवरण प्रदर्शित करने के लिए, और एनपीसी को संपादित करना आसान बनाता है:
struct NPCDetailView: View {
@ObservedRealmObject var npc: NPC
var body: some View {
VStack {
HStack {
Text("Notes")
.font(.title2)
Spacer()
if npc.isMerchant {
Image(systemName: "dollarsign.square.fill")
}
Spacer()
Text($npc.notes)
Spacer()
}
}
}
@ObservedRealmObject
. का एक अन्य लाभ क्या मैं $
. का उपयोग कर सकता था एक त्वरित लेखन आरंभ करने के लिए संकेतन, इसलिए नोट्स फ़ील्ड केवल संपादन योग्य होगा। उपयोगकर्ता टैप कर सकते हैं और बस अधिक नोट्स जोड़ सकते हैं, और रीयलम केवल परिवर्तनों को सहेज लेगा। नोटों को अपडेट करने के लिए अलग से संपादन दृश्य या स्पष्ट लेखन लेनदेन खोलने की कोई आवश्यकता नहीं है।
इस समय, मेरे पास एक काम करने वाला ऐप था और मैं इसे आसानी से भेज सकता था।
लेकिन ... मेरे पास एक विचार था।
ओपन वर्ल्ड आरपीजी गेम्स के बारे में मुझे जो चीजें पसंद थीं, उनमें से एक उन्हें अलग-अलग पात्रों के रूप में और अलग-अलग विकल्पों के साथ फिर से खेलना था। तो शायद मैं एल्डन रिंग को एक अलग वर्ग के रूप में फिर से खेलना चाहता हूं। या - शायद यह विशेष रूप से एल्डन रिंग ट्रैकर नहीं था, लेकिन शायद मैं इसका उपयोग किसी आरपीजी गेम को ट्रैक करने के लिए कर सकता था। मेरे D&D खेलों के बारे में क्या?
अगर मैं कई गेम ट्रैक करना चाहता हूं, तो मुझे अपने मॉडल में कुछ जोड़ना होगा। मुझे गेम या प्लेथ्रू जैसी किसी चीज़ की अवधारणा चाहिए थी।
डेटा मॉडल पर पुनरावृति
मुझे NPCs, स्थानों और खोजों को शामिल करने के लिए किसी वस्तु की आवश्यकता थी जो इस का एक हिस्सा थे। प्लेथ्रू, ताकि मैं उन्हें अन्य प्लेथ्रू से अलग रख सकूं। तो क्या हुआ अगर वह एक खेल था?
class Game: Object, ObjectKeyIdentifiable {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var name = ""
@Persisted var npcs = List<NPC>()
@Persisted var locations = List<Location>()
@Persisted var quests = List<Quest>()
}
ठीक है! महान। अब मैं इस गेम में मौजूद एनपीसी, लोकेशन और क्वेस्ट को ट्रैक कर सकता हूं और उन्हें अन्य गेम से अलग रख सकता हूं।
गेम ऑब्जेक्ट को समझना आसान था, लेकिन जब मैंने @ObservedResults
के बारे में सोचना शुरू किया मेरे विचार में, मुझे एहसास हुआ कि अब यह काम नहीं करेगा। @ObservedResults
किसी विशिष्ट ऑब्जेक्ट प्रकार के लिए सभी परिणाम लौटाएं। इसलिए अगर मैं इस गेम के लिए केवल एनपीसी प्रदर्शित करना चाहता हूं, तो मुझे अपने विचार बदलने होंगे।*
- स्विफ्ट एसडीके संस्करण 10.24.0 ने
@ObservedResults
में स्विफ्ट क्वेरी सिंटैक्स का उपयोग करने की क्षमता को जोड़ा , जो आपकोwhere
. का उपयोग करके परिणामों को फ़िल्टर करने की अनुमति देता है पैरामीटर। मैं निश्चित रूप से भविष्य के संस्करण में इसका उपयोग करने के लिए रिफैक्टरिंग कर रहा हूं! स्विफ्ट एसडीके टीम लगातार नई स्विफ्टयूआई उपहार जारी कर रही है।
ओह। इसके अलावा, मुझे इस खेल में अन्य खेलों के एनपीसी से अलग करने का एक तरीका चाहिए। एचआरएम अब बैकलिंकिंग पर गौर करने का समय हो सकता है। Realm Swift SDK डॉक्स में स्पेलुंकिंग के बाद, मैंने इसे NPC मॉडल में जोड़ा:
@Persisted(originProperty: "npcs") var npcInGame: LinkingObjects<Game>
अब मैं एनपीसी को गेम ऑब्जेक्ट से बैकलिंक कर सकता था। लेकिन, अफसोस, अब मेरे विचार और जटिल हो गए हैं।
मॉडल में बदलाव के लिए SwiftUI व्यू अपडेट करना
चूँकि मैं अब केवल अपनी वस्तुओं का एक सबसेट चाहता हूँ (और यह @ObservedResults
से पहले था अपडेट), मैंने अपने सूची दृश्यों को @ObservedResults
. से बदल दिया है करने के लिए @ObservedRealmObject
, खेल देख रहे हैं:
@ObservedRealmObject var game: Game
अब मुझे गेम में एनपीसी, लोकेशन और क्वेस्ट को जोड़ने और संपादित करने के लिए त्वरित-लेखन के लाभ मिलते हैं, लेकिन मेरी सूची कोड को थोड़ा अपडेट करना पड़ा:
ForEach(game.npcs) { npc in
NavigationLink {
NPCDetailView(npc: npc)
} label: {
NPCRow(npc: npc)
}
}
.onDelete(perform: $game.npcs.remove
अभी भी बुरा नहीं है, लेकिन रिश्तों के एक और स्तर पर विचार करना है। और चूंकि यह @ObservedResults
. का उपयोग नहीं कर रहा है , मैं .searchable
. के दायरे कार्यान्वयन का उपयोग नहीं कर सका , लेकिन इसे स्वयं लागू करना होगा। कोई बड़ी बात नहीं, बल्कि और काम।
जमी हुई वस्तुएं और सूचियों में शामिल होना
अब, इस बिंदु तक, मेरे पास एक कार्यशील ऐप है। मैं इसे इस रूप में भेज सकता था। रीयलम स्विफ्ट एसडीके प्रॉपर्टी रैपर के साथ सब कुछ अभी भी आसान है जो सभी काम कर रहा है।
लेकिन मैं चाहता था कि मेरा ऐप और अधिक करे।
मैं एनपीसी दृश्य से स्थान और खोज जोड़ने में सक्षम होना चाहता था, और उन्हें स्वचालित रूप से एनपीसी में जोड़ दिया था। और मैं खोज दृश्य से एक खोज-दाता को देखने और जोड़ने में सक्षम होना चाहता था। और मैं स्थान दृश्य से स्थानों पर एनपीसी को देखने और जोड़ने में सक्षम होना चाहता था।
इस सब के लिए सूचियों में बहुत कुछ जोड़ने की आवश्यकता थी, और जब मैंने ऑब्जेक्ट बनाने के बाद त्वरित लेखन के साथ ऐसा करने की कोशिश करना शुरू किया, तो मुझे एहसास हुआ कि यह काम नहीं करेगा। मुझे वस्तुओं को मैन्युअल रूप से पास करना होगा और उन्हें जोड़ना होगा।
मैं जो चाहता था वह ऐसा कुछ करना था:
func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
let realm = try! Realm()
let thisLocation = game.locations.where { $0.name == locationName }.first!
try! realm.write {
npc!.locations.append(thisLocation)
}
}
यह वह जगह है जहां कुछ ऐसा है जो एक नए डेवलपर के रूप में मेरे लिए पूरी तरह से स्पष्ट नहीं था, मेरे रास्ते में आना शुरू हो गया। मुझे पहले कभी भी थ्रेडिंग और फ्रोजन ऑब्जेक्ट्स के साथ कुछ भी नहीं करना पड़ा था, लेकिन मुझे क्रैश हो रहे थे जिनके त्रुटि संदेशों ने मुझे यह सोचने पर मजबूर कर दिया कि यह उससे संबंधित था। सौभाग्य से, मुझे जमे हुए वस्तुओं को पिघलने के बारे में एक कोड उदाहरण लिखना याद आया ताकि आप उनके साथ अन्य धागे पर काम कर सकें, इसलिए यह डॉक्स पर वापस आ गया था - इस बार थ्रेडिंग पेज पर जो फ्रोजन ऑब्जेक्ट्स को कवर करता है। (जब से मैं मोंगोडीबी में शामिल हुआ हूं, रियलम स्विफ्ट एसडीके टीम ने और सुधार किए हैं - हाँ!)
डॉक्स पर जाने के बाद, मेरे पास कुछ ऐसा था:
func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
let realm = try! Realm()
Let thawedNPC = npc.thaw()
let thisLocation = game.locations.where { $0.name == locationName }.first!
try! realm.write {
thawedNPC!.locations.append(thisLocation)
}
}
यह सही लग रहा था, लेकिन अभी भी दुर्घटनाग्रस्त हो रहा था। लेकिन क्यों? (यह तब है जब मैंने दस्तावेज़ों में अधिक विस्तृत कोड उदाहरण प्रदान नहीं करने के लिए खुद को शाप दिया था। इस ऐप पर काम करने से निश्चित रूप से कुछ क्षेत्रों में हमारे दस्तावेज़ीकरण में सुधार के लिए कुछ टिकट तैयार किए गए हैं!)
मंचों पर चर्चा करने और महान ऑरेकल Google से परामर्श करने के बाद, मैं एक सूत्र में भागा जहां कोई इस मुद्दे के बारे में बात कर रहा था। यह पता चला है, आपको न केवल उस वस्तु को पिघलाना है जिसे आप जोड़ने की कोशिश कर रहे हैं, बल्कि वह चीज भी है जिसे आप जोड़ने की कोशिश कर रहे हैं। यह एक अधिक अनुभवी डेवलपर के लिए स्पष्ट हो सकता है, लेकिन इसने मुझे थोड़ी देर के लिए उलझा दिया। तो मुझे वास्तव में जो चाहिए वह कुछ ऐसा था:
func addLocationToNpc(npc: NPC, game: Game, locationName: String) {
let realm = try! Realm()
let thawedNpc = npc.thaw()
let thisLocation = game.locations.where { $0.name == locationName }.first!
let thawedLocation = thisLocation.thaw()!
try! realm.write {
thawedNpc!.locations.append(thawedLocation)
}
}
महान! समस्या सुलझ गयी। अब मैं ऑब्जेक्ट्स के एपेंडिंग (और हटाने, जैसा कि यह निकला) को मैन्युअल रूप से संभालने के लिए आवश्यक सभी funcs बना सकता था।
बाक़ी सब कुछ बस SwiftUI है
इसके बाद, ऐप बनाने के लिए मुझे जो कुछ भी सीखना था, वह सिर्फ स्विफ्टयूआई था, जैसे फ़िल्टर कैसे करें, फ़िल्टर को उपयोगकर्ता-चयन योग्य कैसे बनाएं, और .searchable
के अपने संस्करण को कैसे कार्यान्वित करें। .
निश्चित रूप से कुछ चीजें हैं जो मैं नेविगेशन के साथ कर रहा हूं जो इष्टतम से कम हैं। कुछ UX सुधार हैं जो मैं अभी भी करना चाहता हूँ। और मेरा @ObservedRealmObject var game: Game
. स्विच करना @ObservedResults
पर वापस जाएं नई फ़िल्टरिंग सामग्री के साथ उन कुछ सुधारों में मदद मिलेगी। लेकिन कुल मिलाकर, Realm Swift SDK प्रॉपर्टी रैपर ने इस ऐप को लागू करना इतना आसान बना दिया कि मैं भी इसे कर सकता था।
कुल मिलाकर, मैंने ऐप को दो वीकेंड और मुट्ठी भर वीकनेस में बनाया है। संभवत:उस समय का एक सप्ताह के अंत में मैं सूचियों के मुद्दे में संलग्न होने के साथ फंस रहा था, और ऐप के लिए एक वेबसाइट भी बना रहा था, ऐप स्टोर में जमा करने के लिए सभी स्क्रीनशॉट प्राप्त कर रहा था, और सभी "व्यवसाय" सामान जो एक होने के साथ-साथ चलते हैं इंडी ऐप डेवलपर।
लेकिन मैं यहां आपको यह बताने के लिए हूं कि अगर मैं, मेरे नाम से ठीक एक पूर्व ऐप वाला एक कम-अनुभवी डेवलपर - और मेरे नेतृत्व से बहुत सारी प्रतिक्रिया के साथ - बिखरी हुई अंगूठी जैसा ऐप बना सकता है, तो आप भी कर सकते हैं। और यह स्विफ्टयूआई + दायरे स्विफ्ट एसडीके की स्विफ्टयूआई सुविधाओं के साथ बहुत आसान है। यह कितना आसान है यह देखने के लिए एक अच्छे उदाहरण के लिए SwiftUI क्विक स्टार्ट देखें।