Πώς να ξεκινήσετε με την επαυξημένη πραγματικότητα στο Swift, τον εύκολο τρόπο

Αν κοιτάξετε γύρω, αυτή είναι η χρυσή εποχή της τεχνολογίας. Κάθε βασική σημείωση προσθέτει κάτι νέο στην υπάρχουσα στοίβα τεχνολογιών. Είναι συναρπαστικό να βλέπουμε πώς αυτές οι αναδυόμενες τεχνολογίες έχουν βελτιώσει τα όρια της φαντασίας μας. Ως προγραμματιστής, πρέπει να είμαστε περήφανοι που είμαστε οι πρώτοι χρήστες αυτών των τεχνολογιών.

Αλλά κάθε νέα τεχνολογία έρχεται με μια αρκετά απότομη καμπύλη μάθησης. Απλώς δεν μπορείτε να παρακολουθήσετε μια βασική σημείωση ή ένα βίντεο στο Youtube και να αρχίσετε να αναπτύσσετε μια εφαρμογή. Αλλά τα καλά νέα είναι ότι, με το AR στο Swift, είναι εξαιρετικά εύκολο να δουλέψετε με βασικές εφαρμογές AR. Η Apple έκανε το μεγαλύτερο μέρος της ανύψωσης για εσάς. Ακολουθήστε και θα δείτε πόσο εύκολο μπορεί να είναι.

Ας σκάψουμε…

Σε αυτό το σεμινάριο, θα μάθουμε τα απαραίτητα εργαλεία και τεχνικές του AR στο Swift που θα μας επιτρέψουν να δημιουργήσουμε μια εφαρμογή που διακοσμεί το δάπεδο σας με μερικά δροσερά πλακάκια δαπέδου και ξύλινες υφές. Η ολοκληρωμένη εφαρμογή θα μοιάζει με αυτό:

Ας ξεκινήσουμε δημιουργώντας μια εφαρμογή ενιαίας προβολής στο Xcode και το ονομάσουμε Home Decor

Προσθήκη δικαιωμάτων κάμερας

Τώρα το πρώτο πράγμα που θα κάνουμε είναι να μεταβούμε στο αρχείο info.plist και να ενεργοποιήσουμε τη χρήση της κάμερας. Η ικανότητα κάμερας είναι το πρώτο πράγμα που χρειάζεστε για μια εφαρμογή AR. Βρείτε το κλειδί Περιγραφή Χρήσης κάμερας, όπως η παρακάτω εικόνα, και δώστε το κατάλληλο μήνυμα. Αυτό το μήνυμα θα εμφανιστεί κατά την πρώτη εκκίνηση της εφαρμογής ζητώντας τα δικαιώματα κάμερας από τον χρήστη.

Προσθήκη δυνατοτήτων ARKit στην εφαρμογή

Μεταβείτε στο Main.storyboard. Μεταφέρετε και αποθέστε μια προβολή ARKit SceneKit στο ViewController και καρφιτσώστε το ARSCNView στις άκρες του ViewController.

Δημιουργήστε ένα IBOutlet στην κλάση ViewController και ονομάστε το sceneView. Μόλις το κάνετε αυτό, ένα λάθος δηλώνοντας αδήλωτη ARSCNView , θα εμφανιστεί, ως υπεύθυνος άποψή μας δεν αναγνωρίζει τίποτα τύπου ARSCNView. Για να το επιλύσουμε αυτό και για να χρησιμοποιήσουμε άλλες λειτουργίες ARKit, πρέπει να εισαγάγουμε το ARKit στον ελεγκτή προβολής.

Τώρα μεταβείτε από το storyboard στο αρχείο view.swift. Δηλώστε μια ιδιότητα τύπου ARWorldTrackingConfiguration πριν από τη μέθοδο viewDidLoad () και ονομάστε τη διαμόρφωση. Και ο ελεγκτής προβολής θα μοιάζει με αυτό (έχω αφαιρέσει τη μέθοδο didReceiveMemoryWarning):

import UIKitimport ARKit
class ViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {super.viewDidLoad()}

Να επιτρέπεται ο εντοπισμός σφαλμάτων

Αυτή η μεταβλητή config θα καθορίσει τις διαμορφώσεις της περιόδου λειτουργίας σκηνής. Θα δούμε τη χρήση του αργότερα στην ενότητα. Τώρα, στη μέθοδο viewDidLoad μετά το super.viewDidLoad (), προσθέστε τα εξής:

sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]

Εδώ ενεργοποιούμε τις επιλογές εντοπισμού σφαλμάτων για το sceneView, το οποίο δεν είναι τίποτα άλλο από την προβολή κάμερας με τις δυνατότητες του πλαισίου AR. Το ARSCNDebugOptions.showWorldOrigin θα εμφανίσει την παγκόσμια προέλευση στην οθόνη. Αυτό θα μας βοηθήσει να βρούμε το σημείο αναφοράς όλων των άλλων θέσεων. Το ARSCNDebugOptions.showFeaturePoints θα εμφανίσει όλα τα σημεία στην οθόνη τα οποία η κάμερα AR έχει αναγνωρίσει στη γύρω περιοχή.

Τώρα για να ξεκινήσει η περίοδος λειτουργίας AR, πρέπει να εκτελέσουμε μια συνεδρία στο sceneView με τις διαμορφώσεις που αναφέρονται στη μεταβλητή config. Ακριβώς κάτω από τη γραμμή sceneView.debugOptions, γράψτε:

sceneView.session.run(config)

Τώρα εκτελέστε την εφαρμογή στη συσκευή σας (όχι σε προσομοιωτή, καθώς δεν διαθέτει κάμερα). Θα εμφανιστεί η ειδοποίηση που ζητάει την κάμερα με το μήνυμα που έχετε γράψει και θα πρέπει να την επιτρέψετε. Περιμένετε λίγο όσο φορτώνει την παγκόσμια προέλευση.

Εάν βρίσκεστε εδώ, έχετε ήδη εκτελέσει μια εφαρμογή AR. Συγχαρητήρια!

Πώς λειτουργούν οι άξονες AR

Η κόκκινη γραμμή ή ο άξονας X χρησιμοποιείται για την τοποθέτηση αντικειμένων αριστερά ή δεξιά της παγκόσμιας προέλευσης. Η πράσινη γραμμή ή ο άξονας Υ χρησιμοποιείται για την τοποθέτηση αντικειμένων στην κορυφή ή στο κάτω μέρος της παγκόσμιας προέλευσης. Και η μπλε γραμμή ή ο άξονας Ζ χρησιμοποιείται για να προσδιορίσει πόσο κοντά ή μακριά θα τοποθετηθεί ένα αντικείμενο από την παγκόσμια προέλευση.

Μια θετική τιμή του Χ θα τοποθετήσει ένα αντικείμενο στα δεξιά της παγκόσμιας προέλευσης και το αρνητικό θα το τοποθετήσει στα αριστερά. Το θετικό για το Υ θα το τοποθετήσει στην κορυφή και το αρνητικό θα το τοποθετήσει στο κάτω μέρος της παγκόσμιας προέλευσης. Το θετικό για το Ζ θα το τοποθετήσει πιο κοντά και το αρνητικό θα το τοποθετήσει πιο μακριά από την παγκόσμια προέλευση.

Προσθήκη εικονικού αντικειμένου

Ας προσθέσουμε κάποια εικονικά αντικείμενα στη σκηνή. Η κάψουλα 3D θα ήταν μια καλή επιλογή. Δηλώστε ένα capsuleNode τύπου SCNNode και δώστε του μια γεωμετρία κάψουλας. Δώστε το ύψος 0,1 μέτρων και ακτίνα 0,03 μέτρων.

let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1

Τώρα τοποθετήστε το 0,1 μέτρο αριστερά της παγκόσμιας προέλευσης, 0,1 μέτρο πάνω από την παγκόσμια προέλευση και 0,1 μέτρο μακριά από την παγκόσμια προέλευση:

capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)

Τώρα, προσθέστε τον κόμβο στη σκηνή:

sceneView.scene.rootNode.addChildNode(capsuleNode)

Το sceneView περιέχει μια σκηνή που είναι υπεύθυνη για τη συγκράτηση όλων των αντικειμένων 3D σε μορφή SCNNode που θα σχηματίσουν τη σκηνή 3D. Προσθέτουμε την κάψουλα στον ριζικό κόμβο της σκηνής. Η θέση του ριζικού κόμβου ευθυγραμμίζεται ακριβώς με τη θέση της παγκόσμιας προέλευσης. Αυτό σημαίνει ότι η θέση του είναι (0,0,0).

Προς το παρόν, η μέθοδος viewDidLoad μοιάζει με αυτήν:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Τώρα εκτελέστε την εφαρμογή.

Δροσερός! Μόλις τοποθετήσαμε ένα εικονικό αντικείμενο στον πραγματικό κόσμο. Μπορείτε να παίξετε με διαφορετικές θέσεις και διαφορετικές γεωμετρίες για να εξερευνήσετε περισσότερα. Τώρα ας περιστρέψουμε την κάψουλα 90 μοίρες γύρω από τον άξονα Ζ έτσι ώστε να βρίσκεται επίπεδη στον άξονα Χ και να αλλάζει το χρώμα της σε μπλε.

Euler γωνίες

Οι γωνίες Euler είναι υπεύθυνες για τη γωνία εμφάνισης ενός SCNNode. Θα δούμε πώς να το χρησιμοποιήσουμε για να περιστρέψουμε την κάψουλα.

Κάθε SCNGeometry μπορεί να προσθέσει υλικά σε αυτό, το οποίο καθορίζει την εμφάνιση της γεωμετρίας. Τα υλικά έχουν μια διάχυτη ιδιότητα η οποία, όταν ρυθμίζεται, διαδίδει το περιεχόμενό της σε όλη τη γεωμετρία.

Στο viewDidLoad, προσθέστε τις παρακάτω γραμμές αφού ορίσετε τη θέση της κάψουλας.

capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2

Εδώ, στην πρώτη γραμμή, θέτουμε το μπλε χρώμα στο πρώτο υλικό του κόμβου που θα εξαπλωθεί σε όλη την κάψουλα και θα το κάνει να φαίνεται μπλε. Στη γραμμή 2, θέτουμε τη γωνία Z Euler σε ακτίνια 90 μοιρών. Τέλος, η προβολή μας φορτώνεται και μοιάζει με αυτό:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1
capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Τώρα εκτελέστε την εφαρμογή.

Μεγάλος! Μια μπλε κάψουλα ύπνου στον τοίχο! Μπορείτε ακόμη και να προσθέσετε υφές ως διάχυτα περιεχόμενα για να κάνετε ένα αντικείμενο να φαίνεται πιο ρεαλιστικό. Θα το χρησιμοποιήσουμε στην επόμενη ενότητα όταν τοποθετούμε τις υφές των πλακιδίων στο πάτωμα.

Τώρα που έχουμε τοποθετήσει με επιτυχία εικονικά αντικείμενα στον πραγματικό κόσμο, ήρθε η ώρα να διακοσμήσουμε το πραγματικό μας δάπεδο με εικονικά πλακάκια δαπέδου. Για να επιτύχουμε το εφέ δαπέδου, θα χρησιμοποιήσουμε μια γεωμετρία SCNPlane. Το SCNPlane δεν έχει βάθος όπως άλλες γεωμετρίες 3D, γεγονός που το καθιστά ιδανικό για την εφαρμογή μας.

ARSCENEΠροβολή εκπροσώπων

Πριν ξεκινήσουμε την ανίχνευση κατωφλίου, θα διερευνήσουμε μερικές μεθόδους εκπροσώπων της σκηνής μας για να καταλάβουμε με ποιες δυνατότητες είμαστε εξοπλισμένοι για να αλληλεπιδρούμε με μια συνεχιζόμενη περίοδο AR.

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)

Κάθε φορά που κινούμαστε ή γέρνουμε τη συσκευή μας με μια περίοδο λειτουργίας AR, το ARKit προσπαθεί να βρει διαφορετικά ARAnchors στη γύρω περιοχή. Ένα ARAnchor περιέχει πληροφορίες σχετικά με μια πραγματική θέση και προσανατολισμό που μπορούν να χρησιμοποιηθούν για την τοποθέτηση ενός αντικειμένου.

Μόλις βρεθεί μια άλλη άγκυρα, ένας νέος κόμβος προστίθεται στη σκηνή με τις ίδιες πληροφορίες για να φιλοξενήσει αυτήν την πρόσφατα βρεθείσα αγκύρωση Αυτή η μέθοδος πληρεξούσιου θα μας ενημερώσει για αυτό. Θα το χρησιμοποιήσουμε για να βρούμε όλες τις θέσεις στο πάτωμα για να τοποθετήσουμε τα πλακάκια.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor)

Τις περισσότερες φορές, όλοι οι κόμβοι που προστίθενται από τις άγκυρες ανήκουν στο ίδιο αντικείμενο. Ας υποθέσουμε ότι κινούνται γύρω από το πάτωμα και η συσκευή βρίσκει έναν αριθμό αγκυρίων σε διαφορετικές θέσεις. Προσπαθεί να προσθέσει όλους τους κόμβους για αυτές τις άγκυρες, καθώς πιστεύει ότι όλες αυτές οι άγκυρες ανήκουν σε διαφορετικά αντικείμενα.

Αλλά το ARKit τελικά αναγνωρίζει ότι όλα ανήκουν στον ίδιο όροφο, οπότε ενημερώνει τον πρώτο κόμβο του πρώτου ορόφου προσαρτώντας διαστάσεις άλλων διπλών κόμβων. Αυτή η μέθοδος πληρεξούσιου θα μας ενημερώσει για αυτό.

func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)

Μετά την ενημέρωση του πρώτου μοναδικού κόμβου με διαστάσεις όλων των άλλων διπλών κόμβων, το ARKit καταργεί όλους τους διπλούς κόμβους και η μέθοδος ανάθεσης μας ειδοποιεί. Θα χρησιμοποιήσουμε όλες τις παραπάνω μεθόδους εκπροσώπων στην εφαρμογή μας (και ο σκοπός τους θα γίνει σαφέστερος).

Ανίχνευση αεροπλάνου

Προς το παρόν, η σκηνή μας προσπαθεί να συγκεντρώσει όλες τις άγκυρες που συναντά, καθώς αυτή είναι η προεπιλεγμένη συμπεριφορά. Επειδή όμως το δάπεδο είναι οριζόντια επιφάνεια, ενδιαφερόμαστε μόνο για αγκύρια που βρίσκονται σε οριζόντια επίπεδα. Επομένως, επιστρέψτε στη μέθοδο viewDidLoad και γράψτε τον παρακάτω κώδικα προτού εκτελέσετε τη γραμμή περιόδου λειτουργίας (δηλαδή πριν από τη σκηνή sceneView.session.run (config)).

config.planeDetection = .horizontal

Στη μέθοδο viewDidLoad, μπορείτε να καταργήσετε τα πάντα μετά το sceneView.session.run (config) όπως ήταν για την τοποθέτηση του καψακίου στην οθόνη και δεν το χρειαζόμαστε πια. Δεδομένου ότι θα χρησιμοποιούμε όλες τις προαναφερθείσες μεθόδους πληρεξούσιων, πρέπει να κάνουμε το viewController μας αντιπρόσωπο του σκηνικού View. Πριν από το κλείσιμο της μεθόδου viewDidLoad (), προσθέστε την παρακάτω γραμμή.

sceneView.delegate = self

Θα πρέπει να λάβετε ένα σφάλμα τώρα, καθώς ο ελεγκτής προβολής μας εξακολουθεί να μην συμμορφώνεται με τον εκπρόσωπο της σκηνής. Για να το εφαρμόσουμε, ας δημιουργήσουμε μια επέκταση του ελεγκτή προβολής στο τέλος του αρχείου ViewController.swift.

extension ViewController:ARSCNViewDelegate{}

Η μέθοδος αντιπροσώπου didAdd SCNNode θα ενεργοποιείται κάθε φορά που ανακαλύπτεται ένα μέρος του δαπέδου και ένας νέος κόμβος προστίθεται στη σκηνή με βάση την άγκυρα. Μέσω αυτής της μεθόδου, θα δημιουργήσουμε έναν κόμβο κατωφλίου και θα τον προσθέσουμε ως θυγατρικό του κόμβου που προστέθηκε πρόσφατα στη θέση της άγκυρας.

Το ARArchor μπορεί να αποτελείται από τέσσερις διαφορετικούς τύπους για να επιλύσει τέσσερις διαφορετικούς σκοπούς. Εδώ μας ενδιαφέρει μόνο το ARPlaneAnchor που ανιχνεύει τα οριζόντια ή κάθετα επίπεδα.

Δημιουργία κόμβων δαπέδου AR

Ας δημιουργήσουμε μια συνάρτηση που θα λάβει ένα ARPlaneAnchor ως παράμετρο, να δημιουργήσουμε έναν κόμβο δαπέδου στη θέση της άγκυρας και να τον επιστρέψουμε.

func createFloorNode(anchor:ARPlaneAnchor) ->SCNNode{
let floorNode = SCNNode(geometry: SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))) //1
floorNode.position=SCNVector3(anchor.center.x,0,anchor.center.z) //2
floorNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //3
floorNode.geometry?.firstMaterial?.isDoubleSided = true //4
floorNode.eulerAngles = SCNVector3(Double.pi/2,0,0) //5
return floorNode //6
}

Ας περάσουμε από τη γραμμή γραμμής λειτουργίας και να την συζητήσουμε με περισσότερες λεπτομέρειες. Ακολουθήστε την περιγραφή κάθε γραμμής, καθώς είναι το πιο δύσκολο μέρος.

1. Δημιουργούμε έναν κόμβο με γεωμετρία SCNPlane που έχει το μέγεθος της άγκυρας. Η έκταση του ARPlaneAnchor διατηρεί τις πληροφορίες θέσης. Το γεγονός ότι η έκταση.z έχει χρησιμοποιηθεί ως ύψος και όχι έκταση. Μπορεί να είναι λίγο συγκεχυμένο. Εάν φανταστείτε ότι ένας τρισδιάστατος κύβος τοποθετείται σε ένα πάτωμα και θέλετε να το κάνετε επίπεδο κατά μήκος μιας 2D επιφάνειας, θα αλλάζατε το y στο μηδέν και θα πήγαινε επίπεδο. Τώρα, για να πάρετε το μήκος αυτής της 2D επιφάνειας, θα εξετάζατε το z, έτσι δεν είναι; Το δάπεδο μας είναι επίπεδο, οπότε χρειαζόμαστε έναν επίπεδο κόμβο όχι έναν κύβο.

2. Ρυθμίζουμε τη θέση του κόμβου. Καθώς δεν χρειαζόμαστε υψόμετρο, κάνουμε το μηδέν.

3. Ρυθμίστε το χρώμα του δαπέδου σε μπλε.

4. Το χρώμα του υλικού θα εμφανίζεται μόνο στη μία πλευρά, εκτός αν αναφέρουμε συγκεκριμένα ότι είναι διπλής όψης.

5. Από προεπιλογή, το επίπεδο θα τοποθετηθεί κάθετα. Για να το κάνουμε οριζόντιο, πρέπει να το περιστρέψουμε κατά 90 μοίρες.

Εφαρμογή των μεθόδων πληρεξούσιου

Τώρα, ας εφαρμόσουμε τη μέθοδο εκπροσώπου didAdd SCNNode.

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} //1
let planeNode = createFloorNode(anchor: planeAnchor) //2
node.addChildNode(planeNode) //3
}

Στη γραμμή 1, ελέγχουμε εάν η άγκυρα είναι ARPlaneAnchor, καθώς θα ασχοληθήκαμε μόνο με αυτόν τον τύπο αγκύρωσης.

Στη γραμμή 2, δημιουργείται ένας νέος κόμβος με βάση την άγκυρα. Στη γραμμή 3, προστίθεται στον κόμβο.

Τώρα στο didUpdate SCNNode πληρεξούσιο, θα διαγράψουμε όλους τους βασικούς μας κόμβους. Θα το κάνουμε αυτό επειδή οι διαστάσεις του τρέχοντος κόμβου έχουν αλλάξει και οι παλιοί κόμβοι κατωφλίου δεν ταιριάζουν. Στη συνέχεια, θα προσθέσουμε και πάλι έναν νέο κόμβο κατωφλίου σε αυτόν τον ενημερωμένο κόμβο.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
let planeNode = createFloorNode(anchor: planeAnchor)
node.addChildNode(planeNode)
}

Στη μέθοδο εκπροσώπου didRemove SCNNode, θέλουμε να καθαρίσουμε όλους τους ανεπιθύμητους κόμβους μας με πολιτισμένο τρόπο.

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
guard let _ = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
}

Φτου! Αυτό είναι! Εκτελέστε την εφαρμογή.

Προσθήκη του εφέ πλακιδίων

ΟΠΑ, τι? Ένα μπλε πάτωμα; Όχι, δεν έχουμε τελειώσει ακόμη. Μόνο μια μικρή αλλαγή και θα έχουμε ένα εκπληκτικό πάτωμα!

Για να αλλάξουμε το μπλε δάπεδο σε πλακάκια, χρειαζόμαστε μια υφή. Ας google για μια υφή πλακιδίων δαπέδου. Έψαξα για "ξύλινη υφή δαπέδου" και βρήκα μερικές όμορφες εικόνες υφής. Αποθηκεύστε οποιοδήποτε από αυτά στο Mac σας και σύρετέ το στο Assets.xcassets.

Το ονόμασα WoodenFloorTile. Μπορείτε να το ονομάσετε ό, τι θέλετε. Επιστρέψτε ξανά στο αρχείο ViewController.swift. Στη συνάρτηση createFloorNode, αντί να ορίσετε το UIColor.blue ως διάχυτο περιεχόμενο, κάντε το UIImage με το όνομα που έχετε δώσει στην εικόνα στον φάκελο του στοιχείου.

floorNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "WoodenFloorTile")

Τώρα εκτελέστε την εφαρμογή και περιμένετε έως ότου φορτωθεί η παγκόσμια καταγωγή. Μόλις εντοπιστεί το πάτωμα, μετακινηθείτε για να ενημερώσετε τις πληροφορίες του κόμβου.

Ουάου, έχεις πραγματικά ένα υπέροχο πάτωμα! Μπορείτε να κατεβάσετε πολλές υφές και να τις τοποθετήσετε σε μια λίστα προβολών. Αυτό σας επιτρέπει να αλλάξετε το δάπεδο με βάση την επιλεγμένη υφή, όπως φαίνεται στο πρώτο μέρος.

Κατεβάστε το πλήρες έργο από το GitHub εδώ.

Τώρα που έχετε ένα ωραίο πάτωμα, πρέπει να σας λείπουν ωραία έπιπλα για να δώσετε στο δωμάτιό σας μια υπέροχη εμφάνιση! Θα το δουλέψουμε αργότερα.