ΠΓΣ 518: ΧΕΔΙΑΜΟ ΠΑΙΧΝΙΔΙΩΝ ΗΛΕΚΣΡΟΝΙΚΩΝ ΤΠΟΛΟΓΙΣΩΝ Θζμα: Triggers και αλληλεπίδραςη με περιβάλλον Για αυτό το tutorial χρθςιμοποιείςτε βάςθ τον κϊδικα που ςασ δίνεται ςτο Resources/projectUnityToStartWithForTutorial_TriggersInteraction[_WithPrefabsCreated] Σα ολοκλθρωμζνα scripts αυτοφ του tutorial κα τα βρείτε ςτα resources Widget_Status_Tutorial_Triggers_Interaction_completed.js Widget_Controller_Tutorial_Triggers_Interaction_completed.js (ιδιο με προθγοφμενου tutorial) Widget_PickupItems_Tutorial_Triggers_Interaction _completed.js Widget_Inventory_Tutorial_Triggers_Interaction _completed.js Widget_DamageTrigger_Tutorial_Triggers_Interaction _completed.js Σα triggers και άλλοι τρόποι αλλθλεπίδραςθσ με το περιβάλλον αποτελοφν πολφ ςθμαντικό τμιμα του περιβάλλοντοσ και του ςχεδιαςμοφ του παιχνιδιοφ βοθκοφν ςτο να δθμιουργθκοφν αντικείμενα για να μαηζψεισ, παγίδεσ, puzzles, και γενικά όλα τα κομμάτια που δεν χειρίηονται από ζνα εχκρό με ΑΙ (artificial intelligence). ε ζνα παιχνίδι ζνα trigger είναι οτιδιποτε μπορεί να ενεργοποιθκεί/λειτουργιςει, το οποίο ςτθ ςυνζχεια ςτζλνει κάποιου είδουσ μινυμα (send message) θ ξεκινά ζνα event. Σα triggers μπορεί να είναι μθ ορατζσ περιοχζσ ι όγκοι (invisible areas or volumes) τα οποία περικλείουν ζνα ςυγκεκριμζνο χϊρο, και τα οποία ενεργοποιοφνται όταν ο παίκτθσ ειςζλκει ςε αυτι τθν περιοχι ι εκτελζςει μια ςυγκεκριμζνθ λειτουργία ενόςω βρίςκεται ςτθν ςυγκεκριμζνθ περιοχι. Πολλζσ δραςτθριότθτεσ (activities) που αφοροφν τον παίκτθ, όπωσ να ανοίξει μια πόρτα, να ξεκινιςει ζνασ ανελκυςτιρασ, μάηεμα ενόσ αντικειμζνου, όλα αυτά δουλεφουν λόγω των triggers και των volumes τουσ. Διαφορετικά game engines χειρίηονται τα triggers και τα trigger volumes με διαφορετικό τρόπο. Triggers ςτο Unity το Unity, τα triggers ςυνδζονται απευκείασ ςτα GameObjects. - 1 -
Σα collision components των GameObjects (π.χ. box, sphere, mesh, κτλ) λειτουργοφν ςαν trigger, αντί ςαν κάποιο φυςικό αντικείμενο ςτο Inspector, και ζτςι προςδιορίηουν τον χϊρο ςτον οποίο ο παίκτθσ μπορεί να αλλθλεπιδρά μαηί τουσ. Οποτεδιποτε ο παίκτθσ ςυγκρουςτεί/αγγίξει (collide) με το collision volume ενόσ Game Object (GO), το trigger μπορεί να ενεργοποιθκεί μζςω scripts. To GO μπορεί να είναι αόρατο (να μθν ζχει mesh renderer) οπότε απλά προςδιορίηει κάποια περιοχι, ι μπορεί να είναι απτό αντικείμενο το οποίο ο παίκτθσ μπορεί να δει, π.χ. μια πόρτα. SETTING UP A BASIC TRIGGER OBJECT 1. Import το Pickup_gear και το Generic_Pickups texture από το Resources/Probs. Σοποκετείςτε ζνα instance του Pickup_gear ςτθ ςκθνι ςασ και ανακζςτε του ζνα νζο material το οποίο κα ονομάςετε generic_pickups όπωσ φαίνεται ακολοφκωσ. 2. Δθμιουργείςτε ζνα prefab του αντικειμζνου και ονομάςτε το Pickup_Gear. Project view create Prefab κάντε drag το αντικείμενο από το Hierarchy ςτο κενό prefab που δθμιουργιςατε. Στη συνζχεια τις αλλαγζς θα τις κάνουμε στο οποιοδήποτε instance ζχουμε στη σκηνή. prefab και αυτόματα θα γίνετε update ΑΝ ΕΧΕΣΕ ΧΡΗΙΜΟΠΟΙΗΕΙ ΑΝ ΒΑΗ ΣΟ PROJECT ME ΔΗΜΙΟΤΡΓΗΜΕΝΑ ΣΑ PREFABS ΑΡΧΙΣΕ ΑΠΟ ΕΔΩ. 3. Σοποκετείςτε ζνα instance του Pickup_Gear prefab κοντά ςτο Widget ςασ μπορείτε να αλλάξετε το scale (του prefab) ςε (20,20,20). TRY IT! (no collision added yet) - 2 -
4. Δϊςτε ςτο gear ζνα νζο Sphere collider Επιλζξτε το Pickup_Gear prefab ςτο Project View Components Physics Sphere collider Radius= 0.1 Centre = (0,0,0) TRY IT! (collision added) 5. το sphere collider component (Project view - pickup gear prefab) IsTrigger = true TRY IT! (θ ςφαίρα (sphere collider ) τϊρα κα λειτουργεί ςαν trigger!! (και όχι ςαν physics-e.g. collision)) Εννοείτε ότι μποροφμε να προςκζςουμε άλλο physics component (με disable το istrigger) αν κζλουμε να ζχουμε και το collision ταυτόχρονα. τθ ςυνζχεια πρζπει να κακορίςουμε τι κα ακριβϊσ κα κάνει αυτό το trigger. τθν περίπτωςθ του Widget κζλουμε όταν o χαρακτιρασ μασ περάςει πάνω από αυτά τα αντικείμενα, να τα μαηεφει και να (pick up) και να πθγαίνουν ςτον κατάλογο με αντικείμενα που κατζχει (inventory) (το οποίο και αυτό κα υλοποιιςουμε). PICK UP SCRIPT 6. Δθμιουργείςτε ζνα νζο javascript script και ονομάςτε το PickupItems. Διαγράψετε το Update function κακϊσ κα χρθςιμοποιιςουμε κάποιο άλλο function του MonoBehaviour class αυτι τθ φορά. Σο script αυτό κα είναι υπεφκυνο για όλα τα πικανά αντικείμενα που μποροφμε να μαηζψουμε ςτο παιχνίδι γι αυτό κα πρζπει να το υλοποιιςουμε με τζτοιο τρόπο που κα μπορεί να καλφψει όλα τα αντικείμενα. Θα ςυςχετιςτεί με το κάκε ζνα από τα αντικείμενα που μπορεί να περιςυλλεγεί (δθλ. κάκε instance των gear, screw κτλ). 7. Διλωςθ μεταβλθτϊν var itemtype; Σι είδοσ αντικειμζνου είναι gear ι screw ι.. κτλ - 3 -
var itemamount = 1; Πόςα αντικείμενα αντιπροςωπεφει π.χ. κα μποροφςαμε να είχαμε ζνα GO το οποίο αντιςτοιχεί ςε 10 gears. private var pickedup = false; Κακορίηει αν το ςυγκεκριμζνο αντικείμενο ζχει περιςυλλεγεί ι όχι. 8. function OnTriggerEnter(collider: Collider){... Ειδικι ςυνάρτθςθ του Unity δεν τθν καλοφμε εμείσ. H ςυνάρτηςη OnTriggerEnter καλείται αυτόματα κάθε φορά που ζνα collidable Game Object (π.χ. χάρακτήρασ) ειςζλθει ςτο καθοριςμζνο trigger s volume area. Σο όριςμα collider (τφπου Collider) κα είναι το GameObject που ζκανε collision με το αντικείμενο (ειςιλκε ςτθν περιοχι - trigger s volume area) ςτο οποίο είναι attached το script μασ. function OnTriggerEnter(collider: Collider){ //make sure that this is a player hitting the item and not an enemy var playerstatus : Widget_Status = collider.getcomponent(widget_status); if(playerstatus == null) return; //stop it from being picked up twice by accident if(pickedup) return; /*we should add here the code in order to add to inventory the picked up object we ll do it later*/ pickedup = true; //Get rid of it now that it's in the inventory Destroy(gameObject); 9. Θζλουμε να βεβαιωκοφμε ότι το GameObject που μπικε ςτθν περιοχι του trigger είναι το Widget και όχι κάποιο άλλο GO (π.χ κάποιοσ εχκρόσ ι άλλοσ NPC) //make sure that this is a player hitting the item and not an enemy var playerstatus : Widget_Status = collider.getcomponent(widget_status); if(playerstatus == null) return; - 4 -
10. Αν ζχει ιδθ περιςυλλεγει το αντικείμενο από πριν, δεν κζλουμε να γίνει κάτι άλλο δεν χρειάηεται να ςυνεχίςει θ εκτζλεςθ τθσ ςυνάρτθςθσ περαιτζρω //stop it from being picked up twice by accident if(pickedup) return; 11. τθ ςυνζχεια πρζπει να προςκζςουμε το αντικείμενο ςτο inventory του παίκτθ (κα το υλοποιιςουμε αργότερα), να ςθμειϊςουμε ότι ζχει περιςυλλεγεί το αντικείμενο, και να το αφαιρζςουμε από τθν ςκθνι μασ. /*we should add here the code in order to add to inventory the picked up object we ll do it later*/ pickedup = true; //Get rid of it now that it's in the inventory Destroy(gameObject); Destroy είναι ιδθ υλοποιθμζνθ ςυνάρτθςθ που ζχουν όλα τα GameObjects λζειςτο Unity να αφαιρζςει το instance του GO από τθ ςκθνι. gameobject είναι keyword (υπάρχει από τo Unity) και αναφζρεται ςτο GO ςτο οποίο το script είναι attached. Σο gameobject μπορείτε να το χρθςιμοποιιςετε και για άλλουσ ςκοπουσ π.χ. για να προςκζςετε spherecollider ςτο αντικείμενο μζςω κϊδικα και όχι μζςω του interface του Unity. gameobject.addcomponent(spherecollider); 12. @script AddComponentMenu("Inventory/PickupItems") 13. Κάνετε attach το script αυτό ςτο prefab του Pickup_Gear. INVENTORY MANAGEMENT Χρειάηεται να δθμιουργιςουμε ζνα inventory manager το οποίο κα χειρίηεται τα αντικείμενα που μαηεφει ο χαρακτιρασ μασ. Π.χ. χρειαηόμαςτε κάποιο είδοσ πίνακα για να κρατοφμε πόςα αντικείμενα από το κάκε είδοσ μάηεψε ο παίκτθσ. 14. Δθμιουργείςτε ζνα Javascript script και ονομάςτε το Widget_Inventory. 15. Για να ευκολφνουμε τθν διαδικαςία κα φτιάξουμε ζνα enumeration το οποίο απαρυκμίηει αυτόματα τα ςτοιχεία (items) που περιζχει ζτςι δεν χρειάηεται εμείσ να - 5 -
κυμόμαςτε ποια κζςθ του πίνακα ςυςχετίηουμε με κάποιο ςτοιχείο αλλά αντί αυτοφ κα γράφουμε το όνομα του ςτοιχείου (item). //All the available items in the game available for the character to find enum InventoryItem{ DEBUG_ITEM, SCREW, NUT, //this will correspond to gear BOSS_TRAY, BOSS_PLOWBLADE, BOSS_WINDBLADE, ENERGYPACK, REPAIRKIT, COUNT_NUM_ITEMS enum keyword που μασ επιτρζπει να δθλϊςουμε κάποιο enumeration InventoryItem το όνομα που δϊςαμε εμείσ ςτο ςυγκεκριμζνο enumeration Γράφουμε ςαν μια λίςτα όλα τα ςτοιχεία που κζλουμε να ζχουμε ςτο enumeration. SCREW, NUT, BOSS_TRAY, BOSS_PLOWBLADE, BOSS_WINDBLADE, ENERGYPACK, REPAIRKIT είναι όλα αντικείμενα που κα μπορεί να μαηζψει/αλλθλεπιδράςει το widget μαηί τουσ. Tα DEBUG_ITEM και COUNT_NUM_ITEMS είναι 2 ειδικά ςτοιχεία που δεν είναι αντικείμενα που χρθςιμοποιοφνται ςτο παιχνίδι. Σο DEBUG_ITEM το ζχουμε απλά για να μποροφμε να το χρθςιμοποιιςουμε όταν κζλουμε να δοκιμάςουμε κάτι χωρίσ να μπερδεφουμε τα υπόλοιπα. Σο COUNT_NUM_ITEMS κρατά/αντιςτοιχεί ςτον αρικμό των αντικειμζνων που είναι ςτθ λίςτα πρζπει πάντα να βρίςκεται ςτο τζλοσ τθσ λίςτασ. 16. Διλωςθ μεταβλθτϊν var widgetinventory: int[] ; Δθμιουργία μεταβλθτισ για τον πίνακα που κα αποκθκεφουμε τα πόςα ςτοιχεία κα κρατάει ο παίκτθσ var playerstatus: Widget_Status; playerstatus = GetComponent(Widget_Status) ; Για εφκολθ αναφορά ςτο script που είναι υπεφκυνο για το status του Widget private var repairkithealamt = 5.0; private var energypackhealamt = 5.0; - 6 -
Πϊσ (πόςεσ μονάδεσ) υγείασ/ενζργειασ κα παίρνει το Widget όταν κα μαηεφει ζνα repairkit ι ζνα energypack. 17. Πρζπει να αρχικοποιιςουμε το inventory του Widget ϊςτε το Widget να μπορεί να αρχίςει ςτο παιχνίδι οπότε δθμιουργοφμε μια ςυνάρτθςθ που κα είναι υπεφκυνθ για τισ αρχικοποιιςεισ //Initilaize Widget's starting Inventory function Start(){ widgetinventory = new int[inventoryitem.count_num_items]; for (var item in widgetinventory){ widgetinventory[item] = 0; //Give Widget some starting items widgetinventory[inventoryitem.energypack] = 1; widgetinventory[inventoryitem.repairkit] = 2; Αρχικοποιοφμε τον πίνακα να ζχει τόςεσ κζςεισ όςα και τα διαφορετικά αντικείμενα που ζχουμε. Θζτουμε για όλα τα ιδθ ςτοιχείων, ο αρχικόσ αρικμόσ που κατζχει ο παίκτθσ να είναι 0. Για κάποια αντικείμενα μποροφμε να δϊςουμε ςτθν κατοχι του παίκτθ από τθν αρχι κάποιο ςυγκεκριμζνο αρικμό (π.χ. δίνουμε 1 ENERGYPACK και 2 REPAIRKIT). Επιπλζον χρειαηόμαςτε κάποιεσ βοθκθτικζσ ςυναρτιςεισ για να μποροφμε να προςκζτουμε και να αφαιροφμε αντικείμενο από το inventory. 18. function GetItem(item: InventoryItem, amount: int){ widgetinventory[item] += amount; H GetItem κα είναι υπεφκυνθ για το μάηεμα των αντικειμζνων. Όταν καλοφμε τθν ςυνάρτθςθ ςαν παραμζτρουσ κα δίνουμε το είδοσ του αντικειμζνου που μαηζψαμε και το πόςα αντικείμενα (ςυνικωσ κα είναι 1, αλλά μπορεί ςε παιχνίδια να κζλουμε μαηεφοντασ ο παίκτθσ ζνα ςυγκεκριμζνο αντικείμενο να είναι ςαν να μαηεφει ταυτόχρονα πολλά άλλα αντικείμενα π.χ. μαηεφοντασ ζνα κθςαυρό να παίρνει 10 κζρματα.) - 7 -
19. function UseItem(item: InventoryItem, amount: int){ if(widgetinventory[item] <= 0) return; widgetinventory[item] -= amount; switch(item){ case InventoryItem.ENERGYPACK: playerstatus.addenergy(energypackhealamt); break; case InventoryItem.REPAIRKIT: playerstatus.addhealth(repairkithealamt); break; Όταν ο χριςτθσ κζλει να χρθςιμοποιιςει/ξοδζψει τα αντικείμενα που ζχει ςτθν κατοχι του. Όταν καλοφμε τθν ςυνάρτθςθ ςαν παραμζτρουσ κα δίνουμε το είδοσ του αντικειμζνου που κζλουμε να ξοδευτεί και το πόςα τζτοια αντικείμενα κα ξοδευτοφν. το παιχνίδι αυτό, τα ENERGYPACKs χρθςιμοποιοφνται/ξοδεφονται για να προςκζςουμε ενζργεια ςτο Widget και τα REPAIRKITs για να προςκζςουμε υγεία. 20. Άλλεσ βοθκθτικζσ ςυναρτιςεισ που μπορεί να χρειαςτοφμε function CompareItemCount(compItem: InventoryItem, compnumber: int){ return widgetinventory[compitem] >= compnumber; Επιςτρζφει true αν υπάρχουν τουλάχιςτον compnumber αντικείμενα αυτοφ του είδουσ ςτο inventory, αλλιϊσ επιςτρζφει false. function GetItemCount(compItem: InventoryItem){ return widgetinventory[compitem]; Επιςτρζφει πόςα αντικείμενα του ςυγκεκριμζνου είδουσ υπάρχουν ςτο inventory. 21. Για να το προςκζςουμε ςτο menu του Unity για εφκολθ πρόςβαςθ. - 8 -
@script AddComponentMenu("Inventory/Widget's Inventory") 22. Κάνετε attach το script αυτό ςτο prefab του Widget. PICK UP SCRIPT (ςυνζχεια) Σϊρα που φτιάξαμε το script για το inventory, χρειάηεται να ενθμερϊςουμε το PickupItems script μασ, ζτςι ϊςτε όντοσ να προςκζτουμε τα αντικείμενα ςτο Widget s inventory. (Μπορείτε να αντιγράψετε τον κϊδικα από αυτό που ςασ δίνεται ςτα RESOURCES ι κάντε comment out τισ 3 γραμμζσ που αφοροφν τον κϊδικα για το Inventory) 23. Ανοίξετε το PickupItems.js και κάνετε τισ ακόλουκεσ τροποποιιςεισ: a. Σϊρα μποροφμε να δθλϊςουμε τον τφπο τθσ μεταβλθτισ itemtype που κα είναι τφπου InventoryItem (δθλαδι κα παίρνει μια τιμι από τθν λίςτα του αντίςτοιχου enumeration που δθλϊςαμε ςτο Widget_Inventory script) var itemtype : InventoryItem; b. τθν ςυνάρτθςθ OnTriggerEnter πρζπει να προςκζςουμε των κϊδικα για να προςκζτουμε όντοσ τα αντικείμενα ςτο inventory, το οποίο προθγουμζνωσ είχαμε πει ότι κα το υλοποιιςουμε ςε μεταγενζςτερο ςτάδιο. Σϊρα που ζχουμε υλοποιιςει τθν ςυνάρτθςθ GetItem ςτο script που είναι υπεφκυνο για το inventory, μποροφμε να τθν καλοφμε από τθν ςυνάρτθςθ OnTriggerEnter function OnTriggerEnter(collider: Collider){... if(pickedup) return; //If everything's good, put it in Widget's Inventory var widgetinventory = collider.getcomponent(widget_inventory); widgetinventory.getitem(itemtype, itemamount); pickedup = true;... c. Κάντε update ςτο component PickupItems του gear pickup prefab, το πεδίο ItemType ςε NUT. Αυτό μασ επιτρζπει να αποθηκεφονται τα gears/nuts ςτην ςωςτή θζςη του Inventory. Πειραματιςτείτε: - 9 -
Α. τοποκετείςτε πολλά instances Pickup_Gear ςτθ ςκθνι και γράψετε ζνα print statement print(widgetinventory[item]); ή print(item + ": "+ widgetinventory[item]); ςτθν ςυνάρτθςθ GetItem για να τυπϊνει πόςα αντικείμενα αυτοφ του είδουσ ζχει ςτθν κατοχι του το Widget. Β. Επαναλάβετε για το screw Αντίςτοιχα βιματα *1,2,3,4,5, 13, 23c (ItemType ςε SCREW)]. DEATH TRIGGERS το παιχνίδι «κζλουμε» όταν το Widget μπει ςτο νερό να πεκαίνει Ζχουμε ιδθ κάνει τθν περιςςότερθ δουλειά ζχουμε ιδθ: - φτιάξει το νερό (water plane) ςτο περιβάλλον - γράψει ςυναρτιςεισ για να εφαρμόηουμε κάποια ηθμιά (damage) ςτο Widget και να το «ςκοτϊνουμε» αν χρειαςτεί ςτο Widget_status.js 24. Επιλζξετε το plane του νεροφ και δθμιουργείςτε ζνα Box collider Components Physics Box collider Box collider size: y =0.05 istrigger = true 25. Δθμιουργείςτε ζνα νεό Javascript script και ονομάςτε το DamageTrigger. Διαγράψετε το Update function και προςκζςτε τισ ακόλουκεσ γραμμζσ κϊδικα. var damage: float = 20.0; var playerstatus : Widget_Status; function OnTriggerEnter(){ print("ow!"); playerstatus = GameObject.Find("Widget").GetComponent(Widget_Status); playerstatus.applydamage(damage); @script AddComponentMenu("Environment Props/DamageTrigger") Αντί τθσ ςυνάρτθςθσ Find μποροφμε να χρθςιμοποιιςουμε τθν FindWithTag playerstatus = GameObject.FindWithTag("Player").GetComponent(Widget_Status); και να δϊςουμε το tag Player ςτο Widget μασ ι γενικά ςε οποιοδιποτε GameObject. - 10 -
Μποροφμε να το κάνουμε αυτό ςτο Inspector ακριβϊσ κάτω από το όνομα του GO. Είναι χριςιμο όταν ζχουμε πολλά τα ίδια GO και κζλουμε να τα διαχωρίςουμε. Π.χ. κα μποροφςαμε να είχαμε πολλά Widgets αλλά μόνο ζνα να το χειρίηεται ο παίκτθσ οπότε μόνο ςε αυτό μποροφμε να δϊςουμε το tag Player. (Σο όνομα του Tag είναι δικι μασ επιλογι.) 26. Attach το script ςτο water plane. TRY IT! 27. Μζνει να αλλάξουμε τθν ςυνάρτθςθ Die ςτο Widget_Status script, ζτςι όταν ξαναγεννθκεί το Widget μασ να εμφανίηεται ςτθ κζςθ που κζλουμε (και όχι πάλι μζςα ςτο νερό αλλιϊσ κα ξαναπεκαίνει αμζςωσ.) function Die(){ print("dead!"); playercontroller.iscontrollable = false; HideCharacter(); yield WaitForSeconds(1); controller.transform.position.x = 50.0; controller.transform.position.y = 2.0; controller.transform.position.z = 100.0; ShowCharacter(); health = maxhealth; - 11 -