Jump to content
Unity Insider Forum

Leaderboard


Popular Content

Showing content with the highest reputation since 06/10/2020 in all areas

  1. 2 points
    Ich geh mal davon aus, das die Distanz-Berechnungen und der Direction-Vector stimmt. Das solltest du mal mit Debug.Line prüfen. Ansonsten sehe ich du übergibst hier eine Mask (playerMask) und dann auch noch falsch, weil an der 3. Position die Distanz übergeben wird. RaycastHit2D hit2D = Physics2D.Raycast(transform.position,direction*distance, playerMask); Probier es mal so: Debug.DrawRay(transform.position, direction*distance, Color.green, 2f); RaycastHit2D hit2D = Physics2D.Raycast(transform.position, direction*distance);
  2. 2 points
    Der Klassiker und ein hervorragendes Beispiel dafür, dass implizite Casts eine sehr schlechte Idee sind. Nur kurz zur Erklärung, evtl auch für andere, die den Thread finden: Raycast erwartet keine LayerMask, sondern ein int, das als Bitmaske gelesen wird, also dessen Bits als eine Reihe von Booleans funktionieren. Jedes Bit der Zahl steht dabei für einen Layer, und bei 0 wird dieser Layer ignoriert. Deswegen gibt's auch nur bis zu 32 Layer: So viele Bits hat ein int. Unitys LayerMask-Struct hat einen impliziten Cast zu int, sodass man nicht "(int)lm" schreiben muss. Allerdings ist, wie @Zer0Cool richtig sagt, der dritte Parameter vom Typ float, und von float zu int gibt es ebenfalls einen impliziten Cast. Diese beiden Zeilen werden ohne Beanstandung kompiliert: int foo = LayerMask.GetMask("Object"); float foo = LayerMask.GetMask("Object"); Somit akzeptiert Unity eine Layermask als Raycast-Distanz-Parameter, und man sucht such dumm und dusselig.
  3. 1 point
    Ich vermute mal du hast die Bäume auf das Terrain gemalt... Ab einem bestimmten Abstand rendert Unity dabei nicht mehr die Bäume, sondern ersetzt diese durch einfache Billboards (die sind einfache 2D-Grafiken die sich immer zu der Kamera drehen) die das Terrainsystem automatisch erzeugt. Die Bäume werden dann über einen Billboard-Renderer gerendert. Du kannst einmal Testen, ob die grauen Bäume tatsächlich Billboards sind, indem du die Distanz ab dem die Billboards angezeigt werden verschiebst. Dabei müsst du den "Billboard Start" verändern: https://docs.unity3d.com/Manual/terrain-OtherSettings.html Leider kenne ich das Problem mit den grauen Billboards. Das Problem kann dabei folgende (mir bekannte) Ursachen haben: Deine Light-Settings in der Scene sind noch nicht korrekt eingestellt. Hier kann man beispielsweise die Lightmap für die Scene erzeugen (siehe "Generate Lightning"): https://docs.unity3d.com/Manual/Lightmapping.html Damit bekommen auch die Billboards eine andere "Beleuchtung": Eine andere Ursache war einmal die Fog-Einstellungen in Unity. War der Fog aktiviert, dann führte dies dazu das die Billboards quasi als im Nebel dargestellt wurden und damit grau wurden. Die Einstellung für Fog ist ebenfalls im Lightning-Fenster im Szene-Tab (oder bei HDRP im Post-Processing Profil). Du kannst ja mal schauen, ob hier ein Haken gesetzt ist. Sollte man hier die Einstellung verändert haben, dann kann es sein, dass man noch einmal all Texturen der Bäume bzw. das Asset neu importieren muss, damit Unity die Billboard-Texturen neu berechnet. Eine weitere Ursache (und auf diese Ursache würde ich tippen) kann das verwendete Baum-Asset sein, es gibt leider Assets bei denen die Erzeugung der Billboards über Unity nicht richtig funktionert, hier hilft es meist nur das Asset auszutauschen. Dabei kann das Problem die verwendeten Shader dieses Assets sein, das verwendete Baummodel oder allgemein ein "falsch" eingestelltes Asset. Es gibt auch "Lösungen" die dann den Billboard-Renderer manipulieren, aber meist erzeugt man dann damit nur andere Probleme (wie glänzende oder weiße Bäume in der Nacht etc.) Bezüglich der Shader, du kannst auch einmal prüfen, ob dein Baum-Asset einen der folgenden Shader in seinen Materials verwendet, wenn nicht, dann könnten die verwendeten Shader "inkompatibel" mit dem Unity-Terrain sein und so das Problem verursacht werden:
  4. 1 point
    Warum ist ein Laser, der, wenn du nicht gerade astronomisch relevante Größenordnung im Spiel hast, nahezu gleichzeitig losfliegt und irgendwo ankommt, überhaupt mit einem Rigidbody definiert? Laser ist wohl das beste Beispiel für etwas, das man mit Raycasts modellieren möchte. Und dann den Rigidbody komplett weg lassen.
  5. 1 point
    Vielen vielen dank. Ich habe durch den Debug.Line(musste es in update machen da der raycast während dem einen Frame des drückens der maus nicht sichbar wahr) gesehen das meine Direction(keine ahnung warum xD) verkehrt herum war also einfach -direction hat das Problem gelöst xP
  6. 1 point
    Ich hab mal noch einen Highlight-Shader eingebunden und damit jeder was davon hat kommt es hier in den Thread. Der Shader ist nicht ganz perfekt aber dafür kostenlos und man muss kein Postprocessing betreiben (was mehr Performance kostet) um das Gleiche zu erreichen. Damit man nicht den Unity-Standardshader anpassen muss verwende ich hier einfach 2 Materials für den gleichen Mesh. Das 1. Material rendert den Mesh und das 2. Material rendert die Outline um den Mesh. Man könnte das ganze auch in einen Shader packen, aber sobald sich der Unity-Standardshader ändert muss man diese Code wieder nachziehen. Die Klasse Selectable muss entsprechend angepasst werden um diesen Shader zu verwenden: using System.Collections; using System.Collections.Generic; using UnityEngine; public class Selectable : MonoBehaviour { public Material defaultMaterial; public Material highlightMaterial; public float maxDistanceCamera = 10f; public float outLineWitdh = 0.0005f; private Camera m_Camera; private SelectionManager m_SelectionManager; private Renderer m_Renderer; private Transform originalParent; private Vector3 originalPosition; private Quaternion originalRotation; private Material originalMaterial; private float distanceToCamera; // Start is called before the first frame update void Start() { originalParent = this.transform.parent; originalPosition = this.transform.position; originalRotation = this.transform.rotation; m_SelectionManager = GameObject.FindObjectOfType<SelectionManager>(); m_Renderer = this.GetComponentInChildren<Renderer>(); m_Camera = GameObject.FindObjectOfType<Camera>(); originalMaterial = m_Renderer.material; } public void Reset() { this.transform.parent = originalParent; this.transform.position = originalPosition; this.transform.rotation = originalRotation; m_Renderer.material = defaultMaterial; m_SelectionManager.CurrentSelection = null; } void OnMouseDown() { distanceToCamera = (transform.position - m_Camera.transform.position).magnitude; if (distanceToCamera < maxDistanceCamera) { m_SelectionManager.CurrentSelection = this.gameObject; this.transform.parent = m_SelectionManager.SelectableHandle; this.transform.localPosition = Vector3.zero; } } //If your mouse hovers over the GameObject void OnMouseOver() { distanceToCamera = (transform.position - m_Camera.transform.position).magnitude; if (distanceToCamera < maxDistanceCamera) { //m_Renderer.material = highlightMaterial; Material[] materials = m_Renderer.materials; materials = new Material[2]; materials[0] = highlightMaterial; materials[0].SetFloat("_Outline", outLineWitdh); materials[1] = originalMaterial; m_Renderer.materials = materials; } } //The mouse is no longer hovering over the GameObject void OnMouseExit() { //m_Renderer.material = defaultMaterial; Material[] materials = new Material[1]; materials[0] = originalMaterial; m_Renderer.materials = materials; } } Das Selectable-Skript bekommt nun ein Material mit diesem Shader zugewiesen: Das Ergebnis sieht dann so aus: Für jedes Mesh muss leider separat die Dicke der Outline eingestellt werden, so hat z.B. die Vase eine Dicke von 0.01 und die Figur rechts daneben 0.0005. Der Shader ist im Anhang. Silhouette Only.shader
  7. 1 point
    Sieht bei google link aber falsch aus. Zu mindest zeigt es bei mi reine Welt oder Erde Icon. Richtig ist es das hier: bzw https://github.githubassets.com/favicons/favicon.svg https://github.githubassets.com/favicons/favicon.png Meistens werden sie als example.com/favicon.svg oder png usw gespeichert, aber manche Seiten benutzen noch unterordner sowas wie media oder assets usw. Falls du das automatisieren willst musstes du die seite runterladen also quelltext haben und nach dem favicon verlinkung gucken.
  8. 1 point
    So ich hab mich mal dran gemacht, das Rotieren war der "schwerste Part": - es gibt nun eine neue Klasse und die muss an jedes GO gehangen werden welches man "aufheben" können soll (die GOs heißen bei dir "default") - jedes Selectable GO braucht einen Collider (z.b. Box-Collider) - der "SelectionManager" ist nun nur noch für die Steuerung zuständig, detektiert nicht mehr und ändert auch den Renderer der Selectable GOs nicht mehr. - mit ALT kann der Mauscursor eingeschaltet werden und damit kann der Spieler nun das Objekt mit dem Mauszeiger anklicken oder markieren - bei LMB wird das Objekt an den Spieler herangezogen (in das Pivot GO das unter dem Rigidbody liegen sollte - siehe unten) - mit der Maus kann das Object nun rotiert werden, dabei werden die Kamera-Achsen als Rotations-Achsen genommen - drückt der Spieler LMB noch einmal wird das Objekt zurückgelegt und der Spieler kann sich wieder frei bewegen - der Controller wird während der Aktionen entsprechend deaktiviert - die Kameradistanz bis zu welcher die Objekte selektierbar sein dürfen kann eingestellt werden Wichtig ist noch folgendes: Alle GOs die man aufheben können soll müssen nun die Selectable Klasse bekommen! Alle GOs die Selectable sein sollen dürfen nicht mehr static sein! Unter dem Rigidbody liegt nun ein Pivot-Objekt, in welches die Selectable-Objekte gezogen werden. Dieses GO muss dem SelectionManager Skript zugewiesen werden (siehe entsprechender Slot). using System.Collections; using System.Collections.Generic; using UnityEngine; public class Selectable : MonoBehaviour { public Material defaultMaterial; public Material highlightMaterial; public float maxDistanceCamera = 10f; private Camera m_Camera; private SelectionManager m_SelectionManager; private Renderer m_Renderer; private Transform originalParent; private Vector3 originalPosition; private Quaternion originalRotation; private float distanceToCamera; // Start is called before the first frame update void Start() { originalParent = this.transform.parent; originalPosition = this.transform.position; originalRotation = this.transform.rotation; m_SelectionManager = GameObject.FindObjectOfType<SelectionManager>(); m_Renderer = this.GetComponentInChildren<Renderer>(); m_Camera = GameObject.FindObjectOfType<Camera>(); } public void Reset() { this.transform.parent = originalParent; this.transform.position = originalPosition; this.transform.rotation = originalRotation; m_Renderer.material = defaultMaterial; m_SelectionManager.CurrentSelection = null; } void OnMouseDown() { distanceToCamera = (transform.position - m_Camera.transform.position).magnitude; if (distanceToCamera < maxDistanceCamera) { m_SelectionManager.CurrentSelection = this.gameObject; this.transform.parent = m_SelectionManager.SelectableHandle; this.transform.localPosition = Vector3.zero; } } //If your mouse hovers over the GameObject void OnMouseOver() { distanceToCamera = (transform.position - m_Camera.transform.position).magnitude; if (distanceToCamera < maxDistanceCamera) { m_Renderer.material = highlightMaterial; } } //The mouse is no longer hovering over the GameObject void OnMouseExit() { m_Renderer.material = defaultMaterial; } } using System.Collections; using System.Collections.Generic; using UnityEngine; public class SelectionManager : MonoBehaviour { public Transform SelectableHandle; [HideInInspector] public GameObject CurrentSelection; private FirstPersonController firstPersonController; private Camera m_Camera; private int leftMBCounter = 0; private void Start() { m_Camera = GameObject.FindObjectOfType<Camera>(); firstPersonController = GameObject.FindObjectOfType<FirstPersonController>(); if (SelectableHandle == null) { Debug.LogWarning("Bitte ein leeres Container-Gameobjekt für die Positionierung eines Selectable unter dem Rigidbody anlegen."); Debug.LogWarning("Es wird ein default Container-Gameobjekt erzeugt."); GameObject selectableHandle = new GameObject(); selectableHandle.name = "SelectableHandle"; selectableHandle.transform.parent = firstPersonController.transform; selectableHandle.transform.localPosition = new Vector3(0, 1.3f, 1.3f); SelectableHandle = selectableHandle.transform; } } // Update is called once per frame private void Update() { if (Input.GetKeyDown(KeyCode.LeftAlt) && CurrentSelection == null) { firstPersonController.enabled = false; Cursor.visible = true; //Cursor.lockState = CursorLockMode.Confined; Cursor.lockState = CursorLockMode.None; } if (Input.GetKeyUp(KeyCode.LeftAlt) && CurrentSelection == null) { firstPersonController.enabled = true; } if (Input.GetMouseButtonDown(0) && CurrentSelection!= null) { leftMBCounter++; // Hide mouse cursor Cursor.visible = false; Cursor.lockState = CursorLockMode.Locked; // Prevent player from moving around firstPersonController.enabled = false; if (leftMBCounter > 1) { CurrentSelection.GetComponent<Selectable>().Reset(); // Unlock the player so he can move around again firstPersonController.enabled = true; leftMBCounter = 0; // Clear the selection CurrentSelection = null; } } if (CurrentSelection != null) { RotateObject(); } } public void RotateObject() { float InputX = Input.GetAxis("Mouse X"); float InputY = Input.GetAxis("Mouse Y"); CurrentSelection.transform.RotateAround(CurrentSelection.transform.position, m_Camera.transform.up, InputX * 550f * Time.deltaTime); CurrentSelection.transform.RotateAround(CurrentSelection.transform.position, m_Camera.transform.right, InputY * 550f * Time.deltaTime); } } Bekannte Probleme: Wenn der Spieler sehr dicht an einem Tisch steht, dann collidiert der Collider des Objektes mit dem Collider des Tisches.
  9. 1 point
    Etwas kann ich schon helfen, hab die letzten Tage ja viel hier gelernt ^^. Beim Ray kannst du eine LayerMask mitgeben, die er dann auch nur trifft, da kannst du dir die ganze Abfrage sparen. Die Länge des Rays kannst du auch im Ray festlegen und zwar vor der LayerMask ^^! Das hatte ich vergessen und dafür Stunden geopfert... Das Material würde ich einfach in eine Variable OldMaterial oder so packen, wenn ich selektieren kann und danach einfach wieder zuordnen. Das löst jetzt zwar nicht alles, aber ich hoffe, es hilft etwas. Gruß Uwe
  10. 1 point
    Hast Recht, darum geht's im Artikel, aber gerade der erste Punkt gilt auch im Play Mode. Wenn dein Objekt zusammen mit einem anderen Objekt zu Beginn der Szene existiert, dann ist unklar, wer von beiden zuerst Awake, OnEnable oder Start aufgerufen kriegt. Wenn du ein Objekt erst später instanzierst, dann ist klar, dass es später drankommt. In gefühlten 90% der Fälle ist diese nicht definierte Reihenfolge der Grund für unterschiedliche Ergebnisse unter mehr oder weniger gleichen Bedingungen. Hast du ansonsten irgendwelche Fehlermeldungen in den Momenten, wo es nicht funktioniert?
  11. 1 point
    Du bist mein HELD! Was mich das jetzt an Nerven gekostet hat. Ich wusste gar nicht, dass man die Zeit standardmäßig überhaupt auf 0 setzen kann, da werde ich auf jeden Fall ein Script für einbauen. Vielen Dank! Das Bier ist dir Gewiss. Am liebsten persönlich, aber du wohnst vermutlich wieder weit entfernt. Also falls du irgendwie in der Region Bamberg unterwegs bist, gerne hier, ansonsten Adresse per PN und du kriegst paar Bamberger Biere geschickt Danke ^^
  12. 1 point
    Ah das kostet dich mindestens 1 Bier, das war gut versteckt: Der Timescale stand auf 0 und damit wurden keine Eingaben von Unity mehr verarbeitet und auch sonst stand die ganze Szene und deshalb fiel auch der Spieler durch den Boden (nur der Physik-Timescale arbeitete noch). Ich denke das Menu setzt den Timescale auf 0 und irgendwann ist der mal "hängengeblieben", oder jemand hat manuell in den Projekt-Settings herumgespielt . Man sollte also in die Startroutinen noch einbauen, daß der Timescale wieder auf 1 gesetzt wird.
  13. 1 point
    In der Hierarchie Rechtsklick auf die Prefab-Instanz, dann "Unpack Prefab".
  14. 1 point
    Der 3. Parameter der Methode ist die Länge des Raycast und hier wurde in deiner Klasse die Mask übergeben. Die Mask stimmt so, habe ich noch einmal geprüft, aber besser man übergibt value (ansonsten funktioniert deine Klasse aber so wie sie soll): hit = Physics2D.Raycast(transform.position, transform.up, 10f, lm.value); // 10 ist die Länge des Raycasts
  15. 1 point
    Da könnte der Spawner ggf. noch gar nicht aktiviert sein und daher die "Restart"-Eingabe ins Leere laufen... Lass den Code mal so wie er war und versuch mal folgendes: private void Awake() { IniGegner(); } // Start is called before the first frame update void Start() { StartCoroutine(SpawnRandomGegner()); } // Reactivates the coroutine after GO was disabled void OnEnable() { StartCoroutine(SpawnRandomGegner()); } Ansonsten füge mal ein Debug.Log in den Code ein, um zu schauen ob die Co-Routine läuft: IEnumerator SpawnRandomGegner() { //Warte eine gewisse Zeit yield return new WaitForSeconds(Random.Range(1.5f, 4.5f)); //Aktiviere Gegner Debug.Log("Coroutine is trying to spawn next enemy ..."); int index = Random.Range(0, GegnerToSpawn.Count); while(true) { if(!GegnerToSpawn[index].activeInHierarchy) { GegnerToSpawn[index].SetActive(true); GegnerToSpawn[index].transform.position = transform.position; Debug.Log("Coroutine spawned enemy at " + transform.position); break; } else { index = Random.Range(0, GegnerToSpawn.Count); } } StartCoroutine(SpawnRandomGegner()); }
  16. 1 point
    Was meinst du genau mit Untergrund? Hintergrund: Es wären unterschiedliche Lösungen, je nachdem ob es sich um ein Unityterrain handelt oder ob du einfach nur auf verschiedene GO reagieren möchtest die in der Szene verteilt sind. Wenn es GO sind die in der Scene verteilt sind würde ich: https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseEnter.html und https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseExit.html verwenden und den Mousecursor verändern je nachdem was für eine Komponente an dem GO hängt (so wie du es oben schon angedeutet hast). Das aktuelle GO ist dir bekannt, da du für die beiden oberen Methoden ein entsprechendes Skript an die zu erkennenden GO hängen musst.
  17. 1 point
    Moin, welche Shader werden denn bei den Materialien genutzt? Möglicherweise wäre auch ein Blick in der Console hilfreich
  18. 1 point
    Hi@all, hab mich die letzten paar Tage mal an ein kleines Projekt herangewagt, bei dem ein Objekt (z.B. ein Character) sich an der Oberfläche eines anderen Objekts entlang bewegen kann, unabhängig dessen Form. Hier mal ein kleines Demovideo dazu. An den Scripts muss zwar noch einiges getan werden, was usability, readability und performance angeht aber das Grundprinzip funktioniert schon ganz gut. Ich würde mich freuen, wenn ihr einen Kommentar da lasst und eure Meinung dazu schreibt. Kritik ist gerne gesehen. Dankeschön.
  19. 1 point
    Von Dateien würde ich dringend abraten, von "Managerobjekten" auch. Du kannst einfach eine statische Variable definieren: public static Typ variablenname; und fertig. Denk daran, dass statische Variablen nur einmal pro Programm und nicht einmal pro Objekt existieren. ScriptableObjects, wie von @chrische5 beschrieben, gehen auch, erfordern aber etwas initialen Mehraufwand, um sie für die Benutzung als Laufzeit-Datenhalter fitzumachen. Kannst aber z.B. Soda benutzen, da ist die Arbeit schon für dich erledigt
  20. 1 point
    Gerade unerfahrene Leute haben starke Bedenken, wenn es um die Performance in Unity geht. Sie haben aus unterschiedlichen Quellen gehört, dass Dies oder Jenes schlecht sein soll und jetzt gehen sie total ängstlich an das Projekt heran. Denen möchte ich aber sagen: Macht euch nicht verrückt! Denn wie immer ist alles eine Frage von der Menge an leistungsfressenden Dingen, dem gewünschten Resultat und der Hardware, auf der das Spiel laufen soll. Viele Dinge beziehen sich auf Hardware, die kaum noch genutzt wird, wie z.B. das iPad1, welches nicht mehr als 120MB Ram für das Spiel zur Verfügung gestellt hatte. Oder aber es bezog sich auf veraltete Techniken bei denen Unity schon längst performatere Dinge gebastelt hat, wie die unterschiedlichen Lighting-Arten. Ich will mal versuchen, die einzelnen Bereiche abzuarbeiten. Das Grafische: Alles was ich sehen kann kostet Leistung. Mal mehr, mal weniger! Je mehr Realismus ich in ein Spiel rein bringen will umso mehr Leistung wird verbraucht. Denn jedes Poygon, jedes Licht, jede Schattenberechnung und jedes Material verbraucht etwas. Diese Dinge belasten meist die Grafikkarte, denn die ist es, die das ja anzeigen und vorher berechnen muss. Habt ihr ein Spiel mit üppiger Szenerie und schaut euch zur Laufzeit mal den Profiler an, dann seht ihr, dass der Grafikbereich die meiste Leistung schluckt. Unity ist von sich aus schon bemüht, sparsam zu sein und kann gleichartige Dinge zusammen fassen, wenn sie z.B. das gleiche Material haben und auch vom selben Licht bestrahlt werden. Das reduziert die Drawcalls, die einen Großteil der Performance ausmachen. Man hat die Möglichkeit Beleuchtungen und Verschattung vor zu berechnen und die Ausleuchtung einer Szene zu backen. Das reduziert ganz massiv die Last auf der Grafikkarte, kostet aber Speicherplatz und natürlich geht die Dynamik der Beleuchtung dadurch flöten. Aber was bringt es, wenn man genau diese Dynamik haben will. Genau, es bringt nichts! Mit Shadern sieht es genauso aus. Ein einfacher Shader mit nur einer Textur ist recht sparsam. Er gibt aber auch nicht viel her. Schöner ist er, wenn er glänzen kann, transparent ist, erhaben ist und eben realistisch aussieht. Will ich das haben muss ich ihn auch einsetzen. Die Geometrie eines Körpers sollte natürlich so sparsam wie möglich aufgebaut sein. Die Zeit, wo ein Oberarm aus 12 Polygonen besteht, ist aber längst vorbei. Ich kann zwar vieles mit einer NormalMap simulieren, aber wenn die Ränder des Polygons zu eckig sind, sieht man das und es wirkt einfach nicht. Also auch da muss ich mich den Qualitätsansprüchen stellen und mehr Polygone nutzen. Ich kann sogar DirectX11 einsetzen und aus wenigen Polygonen eine Million Polygone machen. Ja, aber eben nur auf einer Grafikkarte, die das auch kann. Egal, wollt ihr die Technik einsetzen dann funktioniert es eben auch nur auf einer Hardware, die das auch kann. Lasst euch also nicht zu stark einschränken. Ihr werdet natürlich irgendwann merken, dass das Ganze zu viel geworden ist und die Framerate einbricht. Meist lässt sich das aber mit etwas geänderter Beleuchtung, LOD oder anderen Dingen schon wieder beheben. Da aber niemand genau sagen kann, wo die Grenzen liegen werden, bringt es auch nichts, wenn man vorher seitenweise darüber diskutiert. Die Physik: Die physikalischen Berechnungen kosten Leistung, die der Prozessor bringen muss. Und es ist klar, dass viele Berechnungen auch viel Leistung kosten. Trotzdem ist das jetzt kaum ein Bereich, wo man "extrem" sparen muss. Unity kann ohne weiteres mehrere 100 Objekte gleichzeitig physikalisch berechnen. Einfach schauen, dass nur die Objekte einen Rigidbody bekommen, die auch einen haben müssen weil sie der Schwerkraft oder anderen Forces folgen sollen und/oder Kollisionen auswerten sollen. Collider so einfach wie möglich halten und lieber einen Box-Collider mehr nehmen, als einen Mesh Collider zu nutzen. Meist muss es nämlich gar nicht ganz genau sein. Merkt eh keiner. Der Code: Ja, hier kann und muss man aufpassen. Denn wenige Kleinigkeiten können das System ganz schön verlangsamen. Aber macht euch auch hier nicht verrückt. Denn auch hier ist alles eine Frage der Dosis. Ein paar extreme Dinge will ich aber mal aufzählen. Als Faustregel gilt, dass jede Unity-eigene Funktion, die im Script steht, auch durchlaufen wird. Egal, ob innerhalb dieser Funktion etwas drin steht oder nicht. Die OnGUI Funktion ist die hungrigste, weswegen man so wenig Scripte wie möglich mit dieser Funktion bestücken sollte. Legt also nur die Funktionen an, die auch wirklich nötig sind, auch wenn das einsparpotential hier (außer bei der OnGUI) nur gering ist. Immer wenn aus einem Script heraus ein anderes Objekt oder dessen Komponente gesucht wird, kostet es Performance. Es hilft aber nichts. Manchmal muss man einfach nach anderen Objekten oder Komponenten suchen. Um jedoch so wenig Leistung wie möglich zu verbrauchen, sollte man nur einmal danach suchen und einfach die Referenz des Gesuchten in eine Variable speichern. Manche Befehle sind recht hungrig, wie z.B. die Entfernungsmessung über Vector3.Distance. Da sollte man sich überlegen, ob denn wirklich in jedem Frame die Entfernung gemessen werden muss oder reicht es vielleicht auch, wenn es nur wenige Male pro Sekunde passiert. So eine Messung würde ja in jedem Frame eine gewisse Grundlast verursachen, die nicht unbedingt sein muss. Und gerade wenn viele Objekte viele Entfernungsmessungen machen, ist es sinnvoll das Ganze in der Zeit aufzuteilen um die Grundlast zu verringern. So ist das natürlich auch mit komplexen Berechnungen die in einer Schleife ausgeführt werden. SendMessage ist ein teurer Befehl, der an ein gewisses Objekt und an all seine Komponenten etwas sendet. Egal ob die Komponente damit etwas anfangen kann, oder nicht. Diesen Befehl sollte man wirklich nur sparsam nutzen. Will ich einem anderen Script jedes Frame etwas mitteilen, dann ist dieser Befehl dafür total ungeeignet. Für ein einmaliges oder seltenes Senden ist er aber voll ok. Für das ständige Übergeben von Informationen bietet sich an, die Komponente, also das andere Script, vorher zu suchen und als Referenz in eine Variable zu speichern. Jetzt kann ich auf alle Public Variablen oder Funktionen des anderen Scripts zugreifen und sie aufrufen oder manipulieren. Das kostet nicht oder kaum mehr, als wenn es eine Variable des eigenen Scripts wäre. Alles das, was ich da aufgezählt habe macht sich erst ab einer gewissen Menge bemerkbar. Je schwächer die Hardware ist, desto früher merkt man es. Nur weil etwas viel Performance kostet, müsst ihr nicht darauf verzichten. Ihr solltet aber weise damit umgehen. Nur weil ich etwas jedes Frame tue, muss das nicht schlecht sein. Es kommt halt immer darauf an, was ich da tue und wie viele Objekte das gleichzeitig machen. Macht euch vorher Gedanken, was ihr für euer Spiel alles braucht und wie ihr das am besten lösen könnt. Aber lasst euch durch diese Planung nicht blockieren. Fangt einfach an. Vieles ist im Nachhinein leicht änderbar. Spiele, die auf einem PC schön aussehen sollen und können, können nicht unbedingt genauso für ein Handy übernommen werden. Es macht aber nicht unbedingt Sinn, sich auf den kleinsten gemeinsamen Nenner zu einigen. Manchmal muss man einfach mehrere Versionen für ein und das selbe Spiel bauen, weil die Qualitysettings nicht ausreichen werden. Fazit: Es ist also alles gar nicht so schlimm!
  21. 1 point
    Ich habe den Code nicht komplett analysiert, da er sehr etwas unübersichtlich ist, aber folgende 2 Zeilen sind eine potentielle Fehlerquelle: int zufall = Random.Range(0, Raumposition.Count); transform.position = Spawnpoints[zufall]; // setzte Position auf einen existierenden spawnpoint Sobald die Längen beider Listen unterschiedlich sind führt dies vermutlich zu Effekten die du so nicht wolltest, also ich denke du wolltest zufall hier auf die Größe von Spawnpoints setzen: int zufall = Random.Range(0, Spawnpoints.Count); transform.position = Spawnpoints[zufall]; // setzte Position auf einen existierenden spawnpoint
  22. 1 point
    Also wenn du UMA meinst, soviel ich weiß ist es genau dafür gemacht. Kleidungen die sich an verschiedene (UMA-)Modelle anpassen können und Human-Modelle die adaptiv sind: https://assetstore.unity.com/packages/3d/characters/uma-2-unity-multipurpose-avatar-35611 In UMA können die Proportionen eines Modells später im Game dynamisch verändert werden (Charaktererstellung) und die Kleidungsmodelle müssen sich daran dynamisch anpassen können. Daher müssen diese Modelle ebenfalls UMA kompatibel sein. Nachteil ist, dass es sowas wie ein geschlossenes System ist. Die Human-Modelle müssen UMA-kompatibel erstellt werden und auch Assets für Kleidung etc. müssen UMA-konform erzeugt werden. Wenn du im Unity Shop nach UMA suchst findest du entsprechend das System (siehe UMA2 oben) aber auch Assets die UMA konform sind. Der andere Ansatz wäre Modelle selbst zu erstellen und die entsprechende Kleidung über ein identisches Human-Rig und über eine identische Skalierung austauschbar zu machen. Hier ist es aber glaube ich nicht möglich, dass die Kleidungsmodelle auf dynamisch veränderte Körperproportionen reagieren, alles muss quasi zumindest von den Proportionen her einheitlich sein, außer Änderungen die über das Rig abgedeckt werden. Viele Spiele gehen aber diesen einfacheren Weg. Die Spielermodelle können dann zwar variabel sein, also unterschiedliche Größen haben und äußerliche Veränderungen, aber die Proportionen bleiben größtenteils intakt. Wenn es dir nur um Erstellung von Kleidung geht könnte man auch UMA-konforme Kleidung erstellen, damit kann ein Anwender von UMA diese dann leicht einbinden. Es kommt also ganz darauf an wie flexibel dein System sein soll und was dein Ziel ist.
  23. 1 point
    Bin nicht der 3D-Modelling Profi, aber soweit ich es kenne, dann müssten dafür alle Charaktere und die Kleidungen das gleiche " Human Rig" verwenden. Wenn ich richtig liege, dann steuert das Rig dann auch das Größenverhältnis zwischen dem Body und der Kleidung. Hier eine Methode (+Skript) welches dann letztendlich die Bones der Kleidung mit den (zu animierenden) Bones des Charakters ersetzt: https://answers.unity.com/questions/44355/shared-skeleton-and-animation-state.html siehe auch https://forum.unity.com/threads/tutorial-how-to-make-clothes-animate-along-with-character.475253/ Es gibt auch fertige Assets die so etwas leisten z.b. UMA Ansonsten gibt es da glaube ich keine fertigen Lösungen die Unity von Haus aus bietet.
  24. 1 point
    Circle Dash Guten Tag liebe Spieleprogrammierer. Ich habe ein Spiel mit Unity entwickelt was sich Circle Dash nennt. Das Spiel soll ein Mobile Game sein was man momentan auch auf den eigen Browser spielen kann. Ich habe bis jetzt noch nicht viele Spiele entwickelt. Ich wollte es euch einmal zeigen um ein Feedback von euch zu bekommen. Bitte seid ehrlich, denn ich möchte darin besser werden gute Spiele entwickeln zu können. Ich bin offen für neue Vorschläge, Rat und Tipps(Game Art, Spiele Mechanik, Farbe, Aussehen, Sound, Hintergrundmusik, Effekte, Spielgefühl). Ich werde das Spiel noch weiterentwickeln(Neue Updates) und versuchen das Spiel zu verbessern. Ich habe das Spiel alleine entwickelt und bin nur ein Programmierer als kein Game Artist und versuche deswegen mit simplen Grafiken zu arbeiten die ich selber erstelle. Viel Spaß beim Spielen und Grüße an alle. Circle Dash Supporte mich auf Patreon
  25. 1 point
    Wenn es nicht da liegen soll und dann wohl auch nicht liegt, dann kannst du in der Start auch nicht über gameObjct.GetComponent<> versuchen eine Referenz zu bilden. Verstehst du das? gameObject ist das GameObject auf dem das Script liegt. Also quasi der Vatter! Hat dieses GameObject kein Textmesh, kann es auch nicht mit GetComponent gefunden werden, denn es ist ja keine Textmesh Komponente auf dem Gameobject. Und weil das so ist, wird deine Variable nach dem Versuch null sein! Egal ob sie vorher eine Referent zum Textmesh hatte. Leg einfach dein Textmesh im Inspector auf den Slot vom Script und nimm die Zeile in der Start raus. Schon wird's gehen.
  26. 1 point
    Deine Fehlermeldung bedeutet IMMER dass auf etwas zugegriffen werden soll was aber nicht bekannt ist. Unity schmeißt dir immer auch die Zeile im Code raus, wo dieser Zugriff stattfindet. Da kann man also leicht erkennen, um welches Objekt oder Komponente es sich handelt. Wenn dein Codebeispiel nur dieses TextMeshProGUI ansprechen will, dann ist es dieses auch. Komisch ist jetzt an deinem Beispiel, dass du oben ja eine public Variable bildest, wo du über den Inspector einfach das Textmesh rein legen könntest. In der Start Methode versuchst du aber dann eine Referenz zu bilden, indem du davon aus gehst, dass das Textmesh genau auf diesem GameObject liegt, wo auch dieses Script liegt. Aber da wird es wohl nicht liegen. Ja und selbst wenn du via Inspector das Textmesh der Variable zugewiesen hast, es aber nicht auf diesem GO liegt, würdest du in der Start die Variable WaveNumber überschreiben. Und zwar mit "null". WaveNummer hätte jetzt also keine Referenz und deswegen kommt der Fehler. Fazit: Dein Textmesh und dein Script liegen nicht auf dem gleichen GameObject!
  27. 1 point
    Hallo zusammen, Beim letzten Mal haben ich ja gezeigt wie man seine Fähigkeiten mit einem "Cooldown" versehen kann. In dieser Runde werde ich euch zeigen, wie man grundlegend ein Kampfsystem angehen kann. Dabei sei wie immer gesagt, dass es sich um das Prinzip handelt und das Ganze natürlich weiter ausgebaut werden kann (und muss). Ziel ist es am Ende zwei Testattacken nutzen zu können, die mit einem Cooldown versehen sind. Diese Attacken machen anhand von unterschiedlichen Charakterwerten Schaden am Gegner. Dinge wie Animationen und der damit verbundene Zeitversatz zwischen Auslösen der Attacke und der verzögerten übermittlung des Schadenswertes werden komplett vernachlässigt.(der Schaden sollte ja nicht schon am Gegner abgezogen werden, während der Angreifer erst zum Schlag ausholt) Wer das Tutorial nachvollziehen möchte, sollte vorher Teil 1 und Teil 2 gemacht haben. Zuerst einmal müssen wir klarstellen, wen wir angreifen. Dafür erweitern wir das Player.cs ganz simpel um public GameObject target; . Cool sind auch ein paar klassische RPG-Werte mit denen man arbeiten kann. Die kommen ebenfalls in die Player.cs AttackStat strength; AttackStat agility; int healthpoints = 100; AttackStat ist ein enum und sieht folgendermaßen aus: public enum AttackStat { Strength, Agility, Intellect //...Spirit, Will.... etc } schreibt es im Script üBER die Player-Klasse in Player.cs. Enums eigenen sich für solche Dinge recht gut, da sich hier ein definierter Name und ein Wert an einer Stelle speichern lassen. Außerdem muss noch die Start Methode angepasst werden und wird um folgende Zeile erweitert: strength = (AttackStat)10; agility = (AttackStat)10; healthpoints = 110; Für ein Kampfsystem brauchen wir natürlich noch: Attacken. Was sind Kämpfe schon ohne Attacken?! Dafür legen wir eine neues C#-Script an und nennen es Attack using UnityEngine; using System.Collections; public class Attack { } Dieses Script dient als Basisklasse und natürlich können spezielle Attacken davon erben. Für den Anfang reicht aber dieses eine Script. Um die Attacken nutzen zu können legen wir nun noch eine Variable in der Player.cs an Attack _attack; und initialisieren sie in der Start Methode mit _attack = new Attack(); Jetzt kann zwar eine Instanz von Attack erzeugt werden, aber was sollte da mit schon passieren? es braucht noch eine kleine Methode und ein paar Variablen. using UnityEngine; using System.Collections; public class Attack { public int baseDamage = 10; // Basisschaden der attacke public void attack(Player attacker, Player defender, float _damageMultiplier, AttackStat _stat) { defender.DoDamage((int)(baseDamage + attacker.GetStatValue(_stat) * _damageMultiplier)); } } Was genau ist DoDamage und GetStatValue? Ersteres ist nichts anderes als eine einfache Methode, die unsere Healthpoints in Player.cs bearbeitet: public void DoDamage(int _damage) { if (healthpoints > 0) { healthpoints -= _damage; } else healthpoints = 0; } GetStatValue sieht folgendermaßen aus: public int GetStatValue(AttackStat s) { switch (s) { case AttackStat.Strength: return (int)AttackStat.Strength; case AttackStat.Agility: return (int)AttackStat.Agility; default: return 1; } } Um das Ganze zu vereinfachen gibt GetStatValue anhand des AttackStat dessen Wert zurück. Hier wäre Platz für Formeln, oder besser noch ein Aufruf eines Scripts, das aus dem AttackStat einen neuen Wert wie AttackPower berechnet und zurückgeben kann! In unserem Fall ist es nur der Strength-Wert, also 10. Wenn also der Attacker seine Attacke auslöst, wird in der Attack-Klasse anhand dessen Werte berechet, welchen Schaden der Defender (der Angegriffene/Verteidiger) bekommt. Auch hier lässt sich noch viel machen, zb. um den Schaden anhand eines Wertes des Defenders zu verringern. Soetwas kann dann so aussehen: (bitte nicht in das Script einfügen) defender.DoDamage((int)(baseDamage + attacker.GetStatValue(_stat) * _damageMultiplier) - defender.GetStatValue(AttackStat.DamageReduce)); DamageReduce muss dann jedoch im enum angelegt werden und natürlich initialisiert werden! Damit die Attacken nun bei Knopfdruck aktiviert werden, bzw die Schadenswerte auch an den Defender übermittelt werden, muss die attack-Methode ausgelöst werden. Dies geschieht in Player.cs, in der Update-Methode und zwar dann, wenn der Knopf gedrückt wurde und der CoolDown bei 0 ist. _attack.attack(this, target.GetComponent<Player>(), ABILITIES["Mortal Strike"]._damageModifier, strength); wir übergeben "this", also die Player.cs aus der die Attacke ausgelöst wird, das Player.cs-Script des Ziels, den Modifier der Fähigkeit und den Wert, der die Attacke beeinflusst. Gleiches gilt für die andere Attacke: _attack.attack(this, target.GetComponent<Player>(), ABILITIES["Heroic Strike"]._damageModifier, agility); Nun Geht es an Testen und dafür kopieren wir das GameObject des Spielers und nennen es Defender. Für die Qick and Dirty Variante habe ich anfangs drei bools in die Player.cs genommen: public bool useAbilities = true; public bool showHealth = true; public bool showCooldowns = true; stellt diese im Inspector beim Player auf: useAbilities = true; showHealth = false und showCooldowns = true; Beim Defender stellt alles auf false, bis auf showHealth, das sollte true sein, Die OnGUI-Methode muss nun noch angepasst werden, denn sonst sehen wir ja nichts. void OnGUI() { if (showCooldowns) { GUILayout.Label("press key 1 or 2"); foreach (KeyValuePair<string, Ability> _kvp in ABILITIES) { GUILayout.Label("attackers CoolDown :" + _kvp.Value._name + " ready in: " + _kvp.Value._coolDownTimer.ToString()); } } if (showHealth) { GUILayout.BeginArea(new Rect(50, 125, 250, 250)); GUILayout.Label("Defenders Health :" + healthpoints.ToString()); GUILayout.EndArea(); } } Nun muss beim Player der Defender in den GameObject-Slot des Inspector gezogen werden und andersherum, damit wir auch ein target haben. Nun sollte dem Defender Healthpoints abgezogen werden, wenn man die Attacken ausführt Hier der Dropbox-Link. zur aktuellsten Version 1_7 Und viel Spass beim erweitern dieses Systems
  28. 1 point
    Hallo zusammen! Weiter geht es mit dem AbilitySystem. Wir nehmen ein paar kleinere Änderungen vor, die uns das Leben als Spieleentwickler erleichtern werden. Da ich in Teil 1 bereits erwÄhnt habe, dass wir unsere Abilities überarbeiten werden und auf eine andere Art und Weise laden. Die Variante, die mir persönlich am meisten zusagt, ist die der ScriptableObjects. Was genau das ist kann man hier im Forum in diversen Tutorials erfahren, zB. hier! Daher will ich uns nicht lÄnger damit aufhalten ScriptableObjects zu erklÄren, sondern wie wir sie in der Praxis für unsere Abilities nutzen. Diejenigen, die ScriptableObjects bereits kennen werden eine Ahnung haben was nun passieren wird;) Zuersteinmal verÄndern wir unsere Ability Klasse geringfügig: using UnityEngine; [system.Serializable] public class Ability : ScriptableObject { public Ability(string _n, float _dn) { this._name = _n; this._damageModifier = _dn; } public string _name = "Attack"; public int _cost = 0; public float _coolDown = 1.5f; public float _coolDownTimer = 0f; public float _damageModifier = 1f; //... type, icon, class etc } Wichtig ist, dass wir die UnityEngine Bibliothek einbinden, sonst wirds nichts mit dem ScriptableObject. Außerdem erbt Ability jetzt von ScriptableObject. Auch das ist wichtig. Außerdem ist die Klasse nun serialisierbar... auch hier: was genau das bedeutet ist bereits in Tutorials abgedeckt. Siehe hier! Die Idee ist nun folgende: ScriptableObjects lassen sich im Projekt als Assets ablegen, Ähnlich wie Prefabs. Dies ermöglicht uns einfach den Unity DragnDrop Workflow beizubehalten um so die Assets zu kopieren und zu verÄndern. Damit lassen sich in kürzester Zeit x Abilities erstellen, ganz ohne dass wir jedes mal eine neue Klasse anlegen müssen etc. Die Designer werden sich freuen! Wie erstellt man nun dieses Asset? Auch dazu gibts ausführlicheres im Forum (Siehe Link oben) und ich spare mir die Details. Wir benötigen auch nur ein kleines Script, das alles weitere über die Hierachy gemacht werden kann ohne einen großen Manager zu erstellen etc. Für den Anfang reicht das auch. Das Script: using UnityEngine; using System.Collections; using UnityEditor; public class CreateScriptableObjects { static string _path = "Assets/Abilities/Resources/"; static string _file = "Ability"; [MenuItem("RPG Generator/Create/Ability")] public static void CreateAbility() { ScriptableObject asset = ScriptableObject.CreateInstance(typeof(Ability)); AssetDatabase.CreateAsset(asset, _path + _file + ".asset"); EditorUtility.FocusProjectWindow(); Selection.activeObject = asset; } } ACHTUNG: die Ordnerhierachie müsst ihr schnell selber erstellen UND dieses Script muss in einen Ordner namens Editor! Also den auch noch schnell erstellen. Im Menu erscheint nun ein weiterer Punkt, unter dem ihr Create/Ability findet. Ist die Ordnerstruktur korrekt, sprich der Pfad zum Zielordner, könnt ihr nun per Klick ein Asset erstellen. Dieses Asset kann nun umbenannt werden. Nennen wir es wieder "Mortal Strike" und wie ihr schon seht lassen sich diverse Variablen per Inspector einstellen. _name : Mortal Strike _coolDown : 1,3 _damageModifier : 1,5 Danach gibts Copypasta - einfach das Asset anklicken und Strg + D . Jetzt noch umbennen, einstellen: _name : Heroic Strike _coolDown : 3.0 _damageModifier : 2,2 Wir haben nun 2 Abilities und auf diese Weise noch x Andere machen! Schnell und einfach. Was wir nun noch machen müssen: Abilities laden und zwar nur anhand (ich erwÄhnte es eingangs) an ihrem Namen! Dafür gehen wir wieder in die Player.cs und modifizieren 2 Dinge: public Ability[] _abilityInit; // zu den variable packen // dieses Array nutzen wir nur zur Initialisierung, da wir spÄter mit Instanzen und und nicht mit den Orginalen arbeiten wollen void Start() { foreach (Ability _a in _abilityInit) { AddAbility(_a._name); } } und folgendes void AddAbility(string _abilityName) { Ability _ability = UnityEngine.Object.Instantiate((Ability)Resources.Load(_abilityName, typeof(Ability))) as Ability; ABILITIES.Add(_ability._name, _ability); } : Jetzt noch die Abilities im Inspector ins abilityInit ziehen. Tada! Weiter geht´s im nÄchsten Teil! Dropbox Link
  29. 1 point
    Hallo und willkommen zur zweiten Reihe einer kleinen Tutorialreihe rund um das Thema RPG. In diesem Teil möchte ich euch demonstrieren wie man seinem Character Fähigkeiten gibt. Dies soll als Basis für ein Kampfsystem dienen. Im Rahmen des Tutorials und der Tatsache, das dieses Gebiet komplex werden kann, wird dieses AbilitySystem natürlich nicht riesig werden und nur die grundlegende Herangehensweise demonstrieren. Jedoch wird es so angelegt, dass es nach oben skalierbar lässt! Dieses Tutorial richtet sich eher an Anfänger als an Fortgeschrittene und natürlich auch an Diejenigen, die solche Systeme schon immer interessiert haben. Wie immer ist das Ganze eine von vielen Möglichkeiten ein AbilitySystem umzusetzen. Legen wir los und definieren erstmal die Fähigkeit. Dafür legen wir uns eine Klasse an: public class Ability { } Soweit so gut, wir haben eine Fähigkeit. Das ging schnell...wir sind fertig nene... Diese Fähigkeit wird uns nun als Basis für alle anderen Fähigkeiten dienen, die einem so einfallen könnten. Damit wir mit dieser Fähigkeit etwas anfangen können, bekommt sie einige Parameter und wir nehmen an, dass diese Fähigkeit Schaden am Gegner verursachen soll. Ganz im Stile der MMO´s geben wir den Fähigkeiten Abklingzeiten, bzw "CoolDowns", die bestimmen in welchem Interval die Fähigkeit benutzt werden kann. public class Ability { public string _name = "Attack"; public int _cost = 0; // Resourcenverbrauch (Mana, etc) public float _coolDown = 1.5f; // je nach Fähigkeit unterschiedlich public float _coolDownTimer = 0f; // ein timer... public float _damageModifier = 1f; // der Wert mit dem der Schadenswert verrechnet wird. //... type, icon, class etc - hier kann es noch einiges geben um die Fähigkeit näher zu definieren, zb welche Klasse kann sie benutzen oder macht die Fähigkeit magischen oder physischen Schaden. enums bieten sich an! } Um Fähigkeiten zu nutzen, müssen sie natürlich irgendwie ins Spiel gelangen. Dazu nutze ich gerne Dictionaries. Sicher mag jetzt der ein oder andere sagen, das man ja auch Lists nutzen kann oder ArrayLists. Ich entscheide mich jedoch definitiv für das Dict und lasse es folgendermaßen aussehen: public Dictionary<string, Ability> ABILITIES = new Dictionary<string, Ability>(); Zum Nachlesen: http://www.dotnetperls.com/dictionary Dieses Dict kann man jetzt einfach zu seinem CharacterScript hinzufügen ODER wir machen ein neues und nennen es Player.cs using UnityEngine; using System.Collections; using System.Collections.Generic; public class Player : MonoBehaviour { #region VARIABLES public Dictionary<string, Ability> ABILITIES = new Dictionary<string, Ability>(); //... etc #endregion } Wichtig: using System.Collections.Generic; ! Sonst wird´s nichts mit dem Dict;) Der Grund Dictionaries zu verwenden ist einfach der, dass sich auch später wesentlich besser nachvollziehen lässt welche Fähigkeit gerade wohin geschickt wird etc. alles läuft anhand des Names der Fähigkeit (anstatt einer ID), so dass auch nach wochen noch klar ist, was eine Fähigkeit ist, warum sie so heißt. Ich für meinen Teil kann mit der Fähigkeit "Mortal Strike" mehr anfangen als mit der id 6534651 Davon abgesehen nutzen wir eh eine recht übersichtliche Anzahl von Fähigkeiten und nicht 100000. Um nun endlich die Fähigkeit verfügbar zu machen, muss sie noch ins Dict, das machen wir in der Start Methode. public class Player : MonoBehaviour { #region VARIABLES public Dictionary<string, Ability> ABILITIES = new Dictionary<string, Ability>(); //... etc #endregion void Start() { Ability _ability = new Ability(); ABILITIES.Add(_ability._name, _ability); } } Damit das noch ein wenig flexibler wird: public class Player : MonoBehaviour { #region VARIABLES public Dictionary<string, Ability> ABILITIES = new Dictionary<string, Ability>(); //... etc #endregion void Start() { Ability _ability = new Ability(); AddAbility(_ability); } void AddAbility(Ability _ability) { ABILITIES.Add(_ability._name, _ability); } } Damit es noch etwas schicker wird geben wir der Ability einen Konstruktor mit einigen überladungen mit. public class Ability { public Ability(string _n, float _dn) { this._name = _n; this._damageModifier = _dn; } public string _name = "Attack"; public int _cost = 0; public float _coolDown = 1.5f; public float _coolDownTimer = 0f; public float _damageModifier = 1f; //... type, icon, class etc } Das sollte reichen für´s Erste, später gibt es hier noch einiges umzustellen, also nicht voreilig werden;) Jetzt schauen wir nochmal in die Start Methode der Player.cs void Start() { Ability _ability = new Ability("Mortal Strike", 1.3f); AddAbility(_ability); } Wir können nun natürlich auch mehrere Fähigkeiten on the fly erstellen: void Start() { Ability _ability = new Ability("Mortal Strike", 1.8f); Ability _ability2 = new Ability("Heroic Strike", 1.2f); AddAbility(_ability); AddAbility(_ability2); } Schnell sollte hier klar werden, dass sich das so auf Dauer nicht so schön bearbeiten lässt und wir Unmengen an Variablen erzeugen. Eine Lösung werden wir uns später anschauen. Um nun die Fähigkeit auch mal zu "benutzen" müssen wir sie noch auslösen. Auch das passiert in der Player.cs! Wir erweitern das Script um eine Update Methode, lösen die Fähigkeiten per Tastatur aus und benutzen den CoolDown. void Update() { if(Input.GetKeyDown(KeyCode.Alpha1)) { if(ABILITIES["Mortal Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Mortal Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Mortal Strike"]._coolDownTimer = ABILITIES["Mortal Strike"]._coolDown; } } if(Input.GetKeyDown(KeyCode.Alpha2)) { if(ABILITIES["Heroic Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Heroic Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Heroic Strike"]._coolDownTimer = ABILITIES["Heroic Strike"]._coolDown; } } } Damit die Abklingzeit wieder zurückgesetzt werden kann (wir wollen ja die Fähigkeit nach Zeit X wieder benutzen ) müssen wir sie noch updaten. foreach(KeyValuePair<string, Ability> _kvp in ABILITIES) { if(_kvp.Value_coolDownTimer > 0) { _kvp.Value._coolDownTimer -= Time.deltaTime; } else { _kvp.Value._coolDownTimer = 0; } } Das Ganze in die Update Methode: void Update() { if(Input.GetKeyDown(KeyCode.Alpha1)) { if(ABILITIES["Mortal Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Mortal Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Mortal Strike"]._coolDownTimer = ABILITIES["Mortal Strike"]._coolDown; } } if(Input.GetKeyDown(KeyCode.Alpha2)) { if(ABILITIES["Heroic Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Heroic Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Heroic Strike"]._coolDownTimer = ABILITIES["Heroic Strike"]._coolDown; } } foreach(KeyValuePair<string, Ability> _kvp in ABILITIES) { if(_kvp.Value_coolDownTimer > 0) { _kvp.Value_coolDownTimer += Time.deltaTime; } else { _kvp.Value_coolDownTimer = 0; } } } Die Player.cs sollte nun so aussehen: public class Player : MonoBehaviour { #region VARIABLES public Dictionary<string, Ability> ABILITIES = new Dictionary<string, Ability>(); //... etc #endregion #region Start void Start() { Ability _ability = new Ability("Mortal Strike", 1.8f); Ability _ability2 = new Ability("Heroic Strike", 1.2f); AddAbility(_ability); AddAbility(_ability2); } void AddAbility(Ability _ability) { ABILITIES.Add(_ability._name, _ability); } #endregion void Update() { #region INPUT /// Inputs werden später überarbeitet if(Input.GetKeyDown(KeyCode.Alpha1)) { if(ABILITIES["Mortal Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Mortal Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Mortal Strike"]._coolDownTimer = ABILITIES["Mortal Strike"]._coolDown; } } if(Input.GetKeyDown(KeyCode.Alpha2)) { if(ABILITIES["Heroic Strike"]._coolDownTimer == 0) { Debug.Log(ABILITIES["Heroic Strike"]._damageModifier); // _coolDownTimer auf _coolDown setzen, damit der Skill nur einmal verfügbar ist -> "Abklingzeit"! ABILITIES["Heroic Strike"]._coolDownTimer = ABILITIES["Heroic Strike"]._coolDown; } } #endregion #region COOLDOWN foreach(KeyValuePair<string, Ability> _kvp in ABILITIES) { if(_kvp.Value._coolDownTimer > 0) { _kvp.Value._coolDownTimer --= Time.deltaTime; } else { _kvp.Value._coolDownTimer = 0; } } #endregion } #region SHOW COOLDOWNS void OnGUI() { foreach (KeyValuePair<string, Ability> _kvp in ABILITIES) { GUILayout.Label(_kvp.Value._name + " ready in: " + _kvp.Value._coolDownTimer.ToString()); } } #endregion } Soweit, so gut. Im nächsten Teil kümmern wir uns darum, dass die Abilities und ihre Werte einfacher zu modifizieren sind, ohne sie jedes Mal in der Start Methode neu zu setzen. Danach integrieren wir ein kleines Kampfsystem in dem wir in der Console ein wenig "Schattenboxen" um so die Fähigkeiten zu testen #edit: bugfrei! Dropbox Link zum Package - ohne jedoch die Herangehensweise zu verstehen bringt es nicht viel;) Weiter geht´s hier --> rpg-basics-abilities-teil-2 <--
  30. 1 point
    Basierend auf diesen Thread: http://forum.unity3d...ctices-Megapost habe ich mal ein kleines 1x1 zu der Unity internen Serialisierung zusammengefasst und aufgeschrieben. Bei der Unity Serialisierung geht es darum wie Unity die Elemente der Szene /Scripte speichert um sie beim start/beenden des Editors/Players wieder zu laden. Mit Serialisierung ist Speichern gemeint und mit Deserialisierung das Laden. Ausgangsbasis ist dieses kleine Script: public class TestScript : MonoBehaviour { public Foo memberFoo1; public Foo memberFoo2; } Nichts wird serialisiert, weil die Klasse Foo nicht mit [serializable] markiert wurde und nicht von ScriptableObject ableitet: public class Foo { ... } Die Instanz der Klasse wird jedes mal neu serialisiert wenn sie referenziert (benutzt) wird: [serializable] public class Foo { // Wird serialisiert/deserialisiert solange der Typ serialisierbar ist und die Sichtbarkeit auf public steht public int a; // Wird nicht serialisiert da es nicht public ist und nicht mit [serializeField] markiert wurde private bool b; // Wird serialisiert weil es mit [serializeField] markiert wurde auch wenn es private ist [serializeField] private float c; } Beispiel der Zuweisung: var fooInstance = new Foo(); member1Foo = fooInstance; member2Foo = fooInstance; // wird serialisiert auch wenn es bereits durch member1Foo serialisiert wird Die Instanz der Klasse wird nur ein einziges mal serialisiert/deserialisiert, weil die Klasse von ScriptableObject abgeleitet wurde: [serializable] public class Foo : ScriptableObject { // Der Konstruktor wird aufgerufen, aber eventuell zugewiesene Daten // könnten durch die Deserialisierung, welche nach dem erzeugen statt findet, // überschrieben werden public Foo() {} // Benutzt diese Methode als Ersatz zum Konstruktor um Daten // nach der Deserialisierung zu initialisieren, // dies wird direkt nach dem erstellen der Instanz und dem // Deserialisieren aufgerufen. void OnEnable() {} } Beispiel der Zuweisung: var fooInstance = ScriptableObject.CreateInstance<Foo>(); member1Foo = fooInstance; member2Foo = fooInstance; // hat auch nach der Deserialisierung die gleiche Instanz wie member1Foo Nur die Basisklasse Foo von Bar wird serialisiert/deserialisiert: [serializable] public class Foo { public int a; } [serializable] public class Bar : Foo { public int b; } Beispiel der Zuweisung: var barInstance = new Bar(); member1Foo = barInstance; // Nach der Deserialisierung ist der Typ des Inhaltes ein Foo und kein Bar Der tatsächliche Typ wird serialisiert/deserialisiert: [serializable] public class Foo : ScriptableObject { public int a; } [serializable] public class Bar : Foo { public int b; } Beispiel der Zuweisung: var barInstance = ScriptableObject.CreateInstance<Bar>(); member1Foo = barInstance; // Nach der Deserialisierung ist der Typ des Inhaltes Bar Verwendung der ScriptableObjects in Editoren/ausserhalb von Objekten der Szene: Um ScriptableObjects auch da zu verwenden wo es keine Szenen Elemente gibt, welche sie referenzieren, muss man ein speziellen Flag setzen, der besagt dass Unity diese ScriptableObject Instanz nicht zerstören soll wenn sie unbenutzt ist, dies geschieht mit dem hier: var fooInstance = ScriptableObject.CreateInstance<Foo>(); fooInstance.hideFlags = HideFlags.HideAndDontSave; Wenn man diesen Flag gesetzt hat muss man selbst die Lebenszeit des ScriptableObjectes bestimmen und mit: fooInstance.Destroy(); die Lebenszeit des Objektes beenden um keine Speicherlecks zu erzeugen.

Announcements

Hy, wir programmieren für dich Apps(Android & iOS):

Weiterleitung zum Entwickler "daubit"



×
×
  • Create New...