Jump to content
Unity Insider Forum

Leaderboard

Popular Content

Showing content with the highest reputation since 11/26/2021 in all areas

  1. Echt jetzt? Das ist es? Du ballerst eine verschlüsselte Fehlerbeschreibung ohne Punkt und Komma raus. Da drin steht dann etwas von einer Plattform, einer AudioSource, OnCollisionEnter2D, einem RB2D und "natürlich" FixedUpdate und Update. Völlig ohne Zusammenhang! Jetzt versucht @Peanutdir zu helfen und fragt dich etwas. Deine Antwort darauf ist "Nein". Kurz darauf fällt dir ein, dass ein RB ja nach unten Fallen würde und dein FixedUpdate trotzdem nicht funktioniert. Jetzt gibt dir Peanut die Info, dass man die Gravity bei einem RB ja ausschalten kann und er würde gerne mehr über das Problem erfahren, weil er dir immer noch helfen will. Und was machst du? Du schreibst, dass du das Problem gefunden hast und bedankst dich. Klasse! Niemand weiß, was dein Problem war und niemand weiß, wie du es gelöst hast. Dafür ist ein Forum nicht da. Ein Forum dient nämlich auch als Nachschlagewerk. Es kann anderen Leuten helfen, indem sie nach erstmal nach einem Problem suchen und auch eine mögliche Lösung dazu finden. Dieser Thread ist verschwendet, denn man kann nicht erkennen was eigentlich dein Problem war und eine Lösung gibt es auch nicht. Aber mach du mal.
    3 points
  2. Moin, laut Doku ist die Methode jetzt öffentlich, daher greift BindingFlags.NonPublic jetzt nicht mehr. https://docs.unity3d.com/2021.3/Documentation/ScriptReference/EditorGUIUtility.SetIconForObject.html Heißt aber auch, du benötigst keine Reflection mehr und kannst die Methode direkt aufrufen: private void DrawIcon(GameObject gameObject, int idx) { GUIContent[] icons = GetTextures("sv_label_", string.Empty, 0, 8); GUIContent icon = icons[idx]; EditorGUIUtility.SetIconForObject(gameObject, icon.image); }
    2 points
  3. Hallo, ich möchte euch ein kleines Video meines Projektes zeigen. Wo stehe ich im Moment? Jack erhält zu Beginn des Spiels eine Begrüßung von Fuchs Eddi, ein paar Anweisungen und seine erste Aufgabe. Dabei geht es erstmal nur um das Anbauen und Ernten von Maiskolben.
    2 points
  4. Jeah, nach gut 20 Stunden Arbeit bin ich nun fertig und es funktioniert völlig fehlerfrei. Hätte ich nie gedacht und wollte schon aufgeben 😂 ☺️ Wer möchte kann das Script gerne nutzen, vielleicht hat ja auch noch jemand bedarf an sowas. Dazu einfach das Input Script dem EventSystem unter das "Input System UI Input Module"(Wichtig, da darf nichts anderes dazwischen sein) geben und die Tags "Horizontal Menu" und "Vertical Menu" hinzufügen und den entsprechenden Panels zuweisen. Das InputModule, welches man erstellt muss man entweder umbenennen in InputManager, oder in Script InputSO alle "InputManger" durch was anderes ersetzen. Das gleich gilt auf für die ActionMaps. Bei mehr als einer wird man allerdings noch mehr Anpassungen vornehmen müssen, sofern diese die Funktionalität auch benötigen. Wenn noch jemand Anmerkungen und Verbesserungen hat, gerne her damit 😁 Input.cs using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.UI; using UnityEngine.UI; internal struct NavigationModel { public Vector2 move; public int consecutiveMoveCount; public MoveDirection lastMoveDirection; public float lastMoveTime; public AxisEventData eventData; public void Reset() { move = Vector2.zero; } } public class Input : BaseInputModule { public InputSO inputSO; private NavigationModel m_NavigationState; private string parentTag; private bool isDynamicList; protected override void OnEnable() { base.OnEnable(); inputSO.moveVector += SetInputDirection; inputSO.navigate += CheckDynamicListObject; inputSO.submit += SetParentOnSubmit; } protected override void OnDisable() { base.OnDisable(); inputSO.moveVector -= SetInputDirection; inputSO.navigate -= CheckDynamicListObject; inputSO.submit -= SetParentOnSubmit; } public void OnClick() { eventSystem.SetSelectedGameObject(eventSystem.firstSelectedGameObject); } private void ProcessNavigation(ref NavigationModel navigationState) { var usedSelectionChange = false; if (eventSystem.currentSelectedGameObject != null) { var data = GetBaseEventData(); ExecuteEvents.Execute(EventSystem.current.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler); usedSelectionChange = data.used; } if (!eventSystem.sendNavigationEvents) return; var movement = navigationState.move; if (!usedSelectionChange && (!Mathf.Approximately(movement.x, 0f) || !Mathf.Approximately(movement.y, 0f))) { var time = Time.unscaledTime; var moveVector = navigationState.move; var moveDirection = MoveDirection.None; if (moveVector.sqrMagnitude > 0) { if(parentTag == "Horizontal Menu") { if (Mathf.Abs(moveVector.x) > Mathf.Abs(moveVector.y)) moveDirection = moveVector.x > 0 ? MoveDirection.Right : MoveDirection.Left; } else if (parentTag == "Vertical Menu") { if (Mathf.Abs(moveVector.x) < Mathf.Abs(moveVector.y)) moveDirection = moveVector.y > 0 ? MoveDirection.Up : MoveDirection.Down; } else if (Mathf.Abs(moveVector.x) > Mathf.Abs(moveVector.y)) moveDirection = moveVector.x > 0 ? MoveDirection.Right : MoveDirection.Left; else moveDirection = moveVector.y > 0 ? MoveDirection.Up : MoveDirection.Down; } if (moveDirection != m_NavigationState.lastMoveDirection) m_NavigationState.consecutiveMoveCount = 0; if (moveDirection != MoveDirection.None) { var allow = true; if (m_NavigationState.consecutiveMoveCount != 0) { if (m_NavigationState.consecutiveMoveCount > 1) allow = time > m_NavigationState.lastMoveTime + GetComponent<InputSystemUIInputModule>().moveRepeatRate; else allow = time > m_NavigationState.lastMoveTime + GetComponent<InputSystemUIInputModule>().moveRepeatDelay; } if (allow) { Selectable newBtn = eventSystem.currentSelectedGameObject.GetComponent<Selectable>().FindSelectable(moveVector); if (newBtn.transform.parent.tag != parentTag) return; var eventData = m_NavigationState.eventData; if (eventData == null) { eventData = new AxisEventData(eventSystem); m_NavigationState.eventData = eventData; } eventData.Reset(); eventData.moveVector = moveVector; eventData.moveDir = moveDirection; ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, eventData, ExecuteEvents.moveHandler); usedSelectionChange = eventData.used; m_NavigationState.consecutiveMoveCount = m_NavigationState.consecutiveMoveCount + 1; m_NavigationState.lastMoveTime = time; m_NavigationState.lastMoveDirection = moveDirection; } } else m_NavigationState.consecutiveMoveCount = 0; } else m_NavigationState.consecutiveMoveCount = 0; if (!usedSelectionChange && EventSystem.current.currentSelectedGameObject != null) { var submitAction = GetComponent<InputSystemUIInputModule>().submit?.action; var cancelAction = GetComponent<InputSystemUIInputModule>().cancel?.action; var data = GetBaseEventData(); if (cancelAction != null && cancelAction.WasPressedThisFrame()) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler); if (!data.used && submitAction != null && submitAction.WasPressedThisFrame()) ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler); } } public override void Process() { ProcessNavigation(ref m_NavigationState); if (parentTag != "Horizontal Menu" && parentTag != "Vertical Menu") { parentTag = eventSystem.currentSelectedGameObject.transform.parent.tag; UnityEditorInternal.ComponentUtility.MoveComponentDown(this); isDynamicList = false; } } public void SetInputDirection(Vector2 moveVector) { m_NavigationState.move = moveVector; } public void CheckDynamicListObject() { parentTag = eventSystem.currentSelectedGameObject.transform.parent.tag; if (!isDynamicList) { if (parentTag == "Horizontal Menu" || parentTag == "Vertical Menu") { isDynamicList = true; UnityEditorInternal.ComponentUtility.MoveComponentUp(this); } } } public void SetParentOnSubmit() { parentTag = eventSystem.currentSelectedGameObject.transform.parent.tag; } } InputSO.cs using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; [CreateAssetMenu(fileName = "AdvancedInput")] public class InputSO : ScriptableObject, InputManager.IMenuActions { public InputManager InputManager; public event UnityAction navigate; public event UnityAction<Vector2> moveVector; public event UnityAction submit; private void OnEnable() { if (InputManager == null) InputManager = new InputManager(); InputManager.Menu.SetCallbacks(this); InputManager.Menu.Enable(); } private void OnDisable() { InputManager.Menu.Disable(); } public void OnCancel(InputAction.CallbackContext context) { //throw new System.NotImplementedException(); } public void OnNavigate(InputAction.CallbackContext context) { navigate?.Invoke(); moveVector?.Invoke(context.ReadValue<Vector2>()); } public void OnSubmit(InputAction.CallbackContext context) { submit?.Invoke(); } }
    2 points
  5. Hallo, mir ist heute eine Idee für ein neues Projekt eingefallen. Ich bin zufällig beim "Googeln" auf das Retro-Spiel Hugo gestossen. Ist ja in der Tat ein Klassiker. Ob man das heute noch spielen möchte mag ich nicht zu beurteilen. Allerdings nehme ich dieses Spielprinzip mal auf und werde mich an eine Hugo Clone ranmachen. Einen Protadonisten habe ich auch schon. Es handelt sich um Teo Turtle, nicht schwer zu erraten, eine humanoide Schildkröte.
    2 points
  6. Also ich für mich persönlich habe knapp 10 Jahre verschiedene Techniken mit Unity ausprobiert. Habe eigene Libraries geschrieben, die leider auch unfertig sind (kaum Zeit dafür mittlerweile). Hier muss ich auch erwähnen, dass es nur meine persönliche Erfahrung und Meinung ist. Wie man schon durch @Sascha's langen Texten sehen kann, könnte man Bücher schreiben um zu erklären, welche Techniken man verwenden kann. Das ist auch der Grund warum ich mit Youtube Kanal anfing. Nicht um zu zeigen wie man machen sollte, eher um meine eigene Erfahrung, wie ich Sachen bewältige und was ich cool finde usw. Ich verfolge meist eine einzige Regel in Unity. So gut wie möglich alles in seinen eigenen Script packen. Um mal einen guten Beispiel zu geben. Ich benutzte DamageSystem.cs. Dort ist Health zu finden. Allerdings was ist nun hier das Problem? Ist doof zu erweitern. Möchte ich nun Armor hinzufügen muss ich diesen Script bearbeiten. Was ist also besser? Genau, wieso nicht gleich Health.cs, Armor.cs usw. unabhängig davon schreiben. Diese wiederum können so gut aufgebaut werden, dass bereits automatische Events usw. eingebaut werden. Beispiel UnityEvents, Automatische Veränderungschecks (quasi, ist der neue Wert wie der Alte? Nein? Dann Event auslösen usw.) Vorteil hier ist, ich kann beliebige neue Attributen einbauen und DamageSystem muss nicht angefasst werden, in dem ich Component reinziehe oder rausziehe. Hab ich Stamina.cs Component beim Spieler, dann hat er auch Stamina. In dem Fall muss mein DamageSystem gar nicht wissen, um was es geht. Es ist wie Lieferer, der weiß nicht was in dem Paket ist, aber kann damit arbeiten. Ebenfalls kann man auch mit GetComponent abfragen, ob Health, Armor, Stamina usw existiert und je nachdem anders handeln. Nun wie kann ich das nun mit UI dynamisch verbinden? Dasselbe Spiel dort auch. UI Elemente werden aktiviert oder deaktiviert je nachdem welche Componenten es existieren. Mit GetComponent oder TryGetComponent kann man abfragen, ob etwas existiert. Manager Es wurde ja bereits gesagt, dass bei Namen wie Manager Vorteile und Nachteile gibt. Um aber mal Proargumente abzugeben, kam bei mir auch ab und zu vor, dass irgendwo mal "Manager" entstanden ist, da keine eindeutige Namen zugewiesen werden konnte. Eben, weil sie gleich mehrere Aufgaben tun. Das ist manchmal auch gut so, denn ich möchte auch ein Component in Unity haben, dass es möglichst erleichtert etwas leicht zu konfigurieren. Ich bin auch kein Fan davon und benutze wirklich sehr selten sowas oder solche Namen (aber mir geht ja um Pro hier gerade). Für mich sind meist Manager, die irgendwie eine Art Überwachung betreiben. Ein Beispiel ist, wie finde einen bestimmten Gegner auf der Karte. Wo hole ich die Information her. EnemyManager oder EnemyManagement haben solche Informationen meistens (zugeben, würde ich nicht mal so machen, aber war ein Beispiel, was mir spontan einfiel). Z.B. EnemyManager.Find(id). Noch nie ist mir aber im Leben passiert, dass ich durch anderen 3rd Party Libraries bei einem Namen wie "Manager" aufgehalten wurde zu programmieren oder verwirrt war und ich dachte "Oh Gott, ich bin hilflos, was tut denn dieser Manager nun?" (um bissel zu übertreiben :D). Denn allein wenn man "Enemy.cs" sehen würden wissen wir ja auch nicht genau, was dort alles möglich ist. Wir wissen geht um Enemy. Was tut es denn?. Arbeitet Enemy denn mit Transform (also mit movements)? Hat es Health eingebaut? Benutzt es Animationen? Fragen über fragen. Oder sind die alle vllt. in separaten Components usw. unterteilt? Mal ja mal nein (in meinem Fall meistens, ja). Denn bei simplen Namensvergebungen aus Kombination meist aus Nominativ und (nominalisiertes) Verb also "Was ist das?" und "Was tut es?" wie zum Bespiel WorldSpawner, EnemyDamageReceiver" UIElementTransition ist man schnell informiert, aber bei komplexeren wie Util(s), DamageSystem, HUD, UI oder bei komplett erfundenen Namen wie Tama, Checky schauen wir trotzdem die Docs oder Source an um zu verstehen, was genau da möglich ist und passiert. Unabhängig davon behaupte ich mal, dass man selbst bei WorldSpawner dennoch anschauen würden, was eigentlich da alles möglich ist. Dank unser IDE sehen wir ja meist auch was möglich ist (yay!). Natürlich, würde es WorldManager heißen, würden man auch nicht wissen, ob es eine Spawn-Funktion hat und solche aufgeben erledigt. Allerdings hab ich mal solche Klassen gesehen, die auch eine Funktion hatte. Z.B. WorldSpawner.Spawn(World world). Klasse! Nehme als Beispiel mal Mirrors NetworkManager: Das ist ein Runtime - Component. Sowohl Einstellung, aber auch sowohl Aufgaben wie Verbindungaufbauen, Callbacks ausführen etc. tut dieser Manager. Nun was könnte hier der Name am besten sein, um präzise mir verständlich zu machen, was genau die Klasse macht? Man hätte vllt auch gleich Mirror nennen können. Würde trotzdem nichts aussagen für mich. Aber, muss ich denn genau wissen, was im Background passiert? Denn den Usern bzw. Programmierern wollen wir doch so einfach wie möglich machen. Ich will hier jetzt nicht als Pro Manager rüber kommen, aber ich wollte mal auch die guten Seiten mal darstellen, wieso man manchmal Manager verwendet. Ich weiß, nun man könnte ja Unterteilen. Genau dazu komm ich jetzt. Um Mal gegen zu argumentieren. Neben Mirror wurde aus der gleichen Projekt eine andere Version erstellt, nennt sich Mirage. Mirage ist das komplette Gegenteil von Mirrors Vorgehensweise. Mirage fokussiert sich auf die wesentlichen Elemente des C#'s OOP. In Mirage siehst du also die ganzen Unterteilungen zu Configuration, SceneManagement usw. in verschiedenen Komponenten. Eigentlich fand ich die IDEE super, denn genau mein Geschmack... dachte ich. Am Ende hatte mein GameObject nicht nur zwei Components, wie da oben wie wir sehen "NetworkManager" und "Transport", sondern knapp ca. 10 verschiedene. Das ist für mich einfach gesagt Spaghetti. Um mal was zu ändern muss ich die Components durch gehen und bei verschiedenen Stellen etwas einstellen. Ich verliere den Übersicht und lästige hin und her gehen nervt total. Mag sein, dass Mirage hier technisch was falsch tut, aber das ist ein Beispiel, wieso mal ein "Manager" gut tut. Ganz großer Nachteil Ein ganz großer Nachteil bei sowas ist, die Arbeit mit mehreren Personen. Manager sind echt nicht dafür gemacht. Manager sind meistens abhängig bzw. die Scripte sind abhängig von Manager. Das ist genau so auch bei Singleton so. Hab bereits dafür eine gute Technik entwickelt (yay!) und alles ist automatisiert. Sollte ich also mal Singleton, oder Manager verwenden müssen, dann spawnen die auch selbstständig jedes mal, egal in welcher Scene ich starte. Vorteile und Nachteile bei Fehlersuche Ich will ehrlich sein. Ich sehe da kein Unterschied, denn beide sind aufwendig für mich. Wenn man in seinem Manager ein Fehler hat, dann weiß man auch dieser Fehler ist irgendwo in dem Script ist. Aber wenn alles irgendwie miteinander verknüpft hat, auch wenn es decoupled arbeitet, dann hat man z.B. das Problem, dass man alles zurück verfolgen muss, wo dieser Fehler her kommt. Beispiel A->B->D, C->D, E->D, F->D und Der Fehler ist bei D. Nun muss ich heraus finden, wieso dieser Fehler bei D entstanden ist. Wie man durch Pfeile erkennen kann, kommen am Ende alle zu D. Das heißt A, B, C, E, F können dieser Fehler verursacht haben. Was ich zum Beispiel nicht mag, aber in dem Projekt mit arbeite und leider so eine Struktur hat ist z.B. sowas: Uff und wieso hat er Entwickler das so gemacht??? Das tun zum Beispiel auch viele und ich verstehe nicht weshalb sie das tun Hier heißt der Player zwar nur Player, aber ganz klar was wir hier sehen ist, dass es ein PlayerManager ist . Meine Empfehlung.. arbeite hier mit UnityEvents. UnityEvent UnityEvent ist Liebe <3. UnityEvents sorgt dafür, dass man zwei unabhängige Scripte zusammen arbeiten lassen kann, da du die Einstellung durch Inspector durchführst und nicht im Code. Dank Serialization kann Unity die Listener für uns registrieren. Sagen wir mal du hast Shoot() ausgeführt und mit Raycast machst du GetComponent<Health>() und nun kannst du health.ApplyDamage(int dmg) ausführen. Nun willst du ja UI oder Healthbar usw. updaten. UnityEvent kann das tun. Egal ob UI da ist oder nicht.. ApplyDamage wird immer funktionieren und die Scripte sind auch nicht abhängig davon. Man stelle sich UnityEvent wie ein unsichtbarer Middlesman vor. Jemand der für uns eine Aufgabe übernimmt im Background. Nachteil Leider hat auch UnityEvent einen doofen Nachteil, wenn man sie ganz normal verwendet. Wie kannst du nachvollziehen, wo du UnityEvent verwendet hast.? Ist schwer. Kannst du so gut wie gar nicht. Beispiel. Du hast ein UI Script und da steht public void UpdateHealthText(string text). Nun muss du irgendwie herausfinden, wo UpdateHealthText benutzt wird. Kannst du nicht (zumindest fällt mir da keine Idee ein, ob man das im Editor rausfinden kann). Vllt hast du es beim Spieler bei Health.cs verwendet, oder beim Player.cs oder einfach irgendwelche anderen Scripte usw. Fakt ist. Zwar wird ApplyDamage in Health funktionieren.. allerdings beim Invoke des UnityEvents wird eine Fehlermeldung rausspucken, dass UnityEvent eine Funktion nicht mehr findet. Sprich.. man muss darauf auch achten, dass man beim UnityEvent, wieder UpdateHealthText rausnimmt. Wäre eig schön, wenn UnityEvent die möglichkeit hätte sowas zu ignorieren. Dann wäre das kein Problem. Allerdings kann man dafür eigene Visualizer schreiben, wie hier gemacht wurde: https://assetstore.unity.com/packages/tools/utilities/event-visualizer-163380 (ich will nicht lügen, habe es gerade ergooglet). Um das umzugehen gibt es dafür gibt es ein paar Tricks. Listener und Receiver schreiben. Das ist zu deep um jetzt da reinzugehen, aber das Gute bei der Sache ist, dass der Listener immer da ist beim UI als Component und das Receiver genauso da ist beim Health. Der Listener kann quasi schauen, ob eine Instanz existiert if health == null, dann tue nix. Daher, ob du UI löschen tust, oder Health.. keine Fehlermeldung wird entstehen. Meine eigene Lösung dazu war eine anderes eigenes System. Den nannte ich SmartEvents und muss gecodet werden. Heißt hier arbeitet man nicht mit dem Inspector. Ich finde mit IDE zu arbeiten immer bequemer, da ich auch schauen kann, wo was benutzt wird. Dafür sind die IDEs einfach zu gut geworden mittlerweile. Wenn ihr UnityEvent liebt dann bitte downloaded unbedingt das heir: https://github.com/MerlinVR/EasyEventEditor EasyEventEditor fügt mehr Optionen hinzu. Beispiel private Methoden werden auch sichtbar. So oder so verstehe ich nicht, wieso das allgemein nicht möglich ist.. denn manchmal will ich private Methoden nur für UnityEvent benutzen. Wenn man richtig hardcore unterwegs sein will Schaue dir Serialization an. Wie es funktioniert. Wenn man seine eigene Serialization schreiben kann, kann man mächtige Sachen erschaffen. Ein Beispiel. Wir benutzen Tilemap. Wir finden aber Tilemap abzuspeichern sehr krass vom Speicherplatz her. Deswegen speichern wir die wichtigen Daten als JSON ab und lassen in Runtime da wieder lesen. Wir sparen dadurch über 80% Speicherplatz. Denn in der Scene oder als Prefab waren das für uns zu viel Daten. Ich bin leider nicht soweit gekommen, aber quasi damit wollte ich auch meine Scripte entkoppen und mit eigenen Serialization tool arbeiten. Quasi die eigenen EventSysteme werden in JSON abgespeichert und wieder geladen. Leider konnte ich das noch nicht testen, aber die Idee könnte gut funktionieren.
    2 points
  7. @Banenchen Du solltest deine Fragestellung mal etwas überarbeiten. Die Frage "Warum ... wenn ich dieses Script habe" würde ich jetzt beantworten mit "Weil du diesen Script benutzen tust". Wir kommen also so nicht weiter. Daher was ist dein Problem, wo vermutest du dein Fehler, was hast du probiert und was genau willst du erreichen?
    2 points
  8. Hm, ich dachte, ich hätte da einen Post irgendwo, aber ich finde ihn gerade nicht. Eine LayerMask ist eine Bitmaske. Bitmasken sind binäre Zahlen (so wie alle Zahlen im Rechner), bei denen jede Stelle ein Boolean darstellt. Wie bei Dezimalzahlen auch, sind rechts die kleineren Werte und nach links wird's immer größer. Daher ist das erste Bit ganz rechts, das zweite links daneben usw. Ich zähle im Folgenden von 0 an statt von 1, das macht's später einfacher. Wenn du also #0 und #2 "an"-haben willst, wäre das 101. Da wir einen bestimmten Datentypen benutzen, haben wir eine feste Anzahl an Bits. In der Regel nehmen wir ein int, das hat 32 Bits. Also wäre es eigentlich 00000000000000000000000000000101 Du kannst also 32 Bits (oder auch "Flags") an- oder ausschalten. Falls du dich mal gefragt hast, warum man genau 32 Layer definieren kann (naja, minus die 8 vorbelegten) - das ist der Grund. Als nächstes brauchst du Operatoren, um mit diesen Zahlen zu arbeiten. Es sind einfach nur ints, deshalb kannst du ganz normal + und - und so weiter benutzen. Aber für Bitmasks interessant sind sog. bitweise Operatoren. Da gibt es erstmal &, | und ^. & und | sind wie && und || ...nur halt bitweise. ^ ist dabei das bitweise Äquivalent zu !=. Das heißt, dass der Operator durch die beiden Zahlen links und rechts davon durchgeht und das 0te Bit mit dem anderen 0ten Bit vergleicht, dann die beiden ersten, usw. Dabei gilt: // UND (beide müssen 1 sein) 0 & 0 == 0 0 & 1 == 0 1 & 0 == 0 1 & 1 == 1 // ODER (mindestens eins von beiden muss 1 sein) 0 | 0 == 0 0 | 1 == 1 1 | 0 == 1 1 | 1 == 1 // EXKLUSIV-ODER (UNGLEICH) (eins muss 1 sein, das andere muss 0 sein) 0 ^ 0 == 0 0 ^ 1 == 1 1 ^ 0 == 1 1 ^ 1 == 0 Da das eben für jedes Bit der einen Zahl mit dem entsprechenden Bit der anderen Zahl gemacht wird, entsteht eine neue Bitmaske: 1010 & 0011 ====== 0010 1010 | 0011 ====== 1011 Jetzt kann man diese Zahlen ins Dezimalsystem übertragen und sieht unintuitive Zahlen: 1010 = 10 & 0011 = 3 ====== 0010 = 2 1010 = 10 | 0011 = 3 ====== 1011 = 11 Daran erkennt man, dass man die komischen Dezimalzahlen, die man bei der Arbeit mit Bitmasken vielleicht zu Gesicht bekommt, einfach ignorieren sollte. Dann hast du noch sog. Bitshift-Operatoren. Damit kannst du die Einsen in einer Bitmaske nach links oder rechts wandern lassen: // << lässt alle Einsen einmal nach links wandern 0101 << 1 == 1010 // mit >> geht's nach rechts 0110 >> 1 == 0011 Wenn du das mit den Binärzahlen verinnerlicht hast, wirst du vielleicht merken, dass << 1 verdoppeln und >> 1 halbieren heißt. Statt einer 1 kann man da auch eine größere Zahl oder 0 hinschreiben. Dann werden die Einsen nicht nur einmal verschoben, sondern eben so oft wie diese Zahl hoch ist. Wo wir bei binären Zahlen sind... du kannst zum Glück seit C#7 Binärzahlen über Literale definieren, heißt: Du schreibst sie einfach hin. Mit einem "0b" davor. int number = 0b1110; Debug.Log(number); // 14 Damit kann man evtl. ein bisschen rumexperimentieren. Zuletzt darf man auch den guten alten Vergleichsoperator nicht vergessen. Du kannst wunderbar eine Bitmaske mit == mit einer anderen vergleichen. Da kommt dann true heraus wenn alle Bits übereinstimmen (es also dieselbe Zahl ist - wer hätte es gedacht?). So... Wie benutzen wir das ganze jetzt? Wenn du eine LayerMask definierst (z.B. über ein serialisiertes Feld, sodass du es im Editor einstellst, oder über LayerMask.GetMask), dann hast du ein LayerMask-Struct, das sich wie ein int benutzen lässt. LayerMask.GetLayer gibt dir sogar direkt ein int zurück. Wenn du allerdings den Layer eines GameObjects anschaust, dann kriegst du eine Zahl, die keine Bitmaske ist, sondern den Index des Layers darstellt. Wenn du jetzt schauen willst, ob der Layer des Objekts auf der LayerMask angeschaltet ist, musst du eine LayerMask erstellen, in der der Layer des Objekts angeschaltet ist und der Rest aus diese LayerMask mit der gegebenen LayerMask vergleichen Für 1. nutzen wir Bitshift: int objectLayerMask = 1 << targetObject.layer; 1 ist ja 00000001, also eine einzelne 1 ganz rechts. Diese 1 schieben wir mit Bitshift so oft nach Links wie der Index des Layers ist. Ist das GO z.B. auf Layer 4, wird die 1 viermal nach links geschoben und wir erhalten 00010000. Jetzt nehmen wir den &-Operator und verrechnen diese Bitmaske mit der LayerMask: int andMask = layerMask & objectLayerMask; Da die zweite Zahl nur eine 1 enthält, werden alle anderen Bits des Ergebnisses 0, denn bei & müssen beide Bits 1 sein, damit 1 rauskommt. Das einzige Bit, bei dem 1 rauskommen kann, ist das vierte von rechts (von 0 an gezählt), da hier eine 1 in der Objekt-Bitmaske steht. Damit aber auch wirklich eine 1 herauskommt, muss genau dieses Bit auch bei der vorgegebenen LayerMask 1 sein. Es gibt also nur zwei mögliche Ergebnisse: Die LayerMask hat den Layer des GameObjects aus, dann hat das Ergebnis durchgehend nur Nullen. Die LayerMask hat den Layer an, dann kriegen wir eine Bitmaske als Ergebnis, die irgendwo eine 1 hat und sonst Nullen. Und damit haben wir die beiden Fälle abgedeckt: Layer ist in LayerMaske an, ja oder nein. Wir nehmen also != und schauen, ob das Ergebnis == 0 ist (alles Nullen) oder eben nicht: bool layerIsInLayerMask = andMask != 0; Oder, alles in einer Zeile: if ((layerMask & (1 << targetObject.layer)) != 0) Du kannst die auch eine nette Methode schreiben, um das lesbarer zu machen: using UnityEngine; public static class LayerMaskExtensions { public static bool HasLayer(this LayerMask layerMask, int layerIndex) { return (layerMask & (1 << layerIndex)) != 0; } } Jetzt kannst du sowas machen: myLayerMask.HasLayer(targetGameObject.layer)
    2 points
  9. Hallo Du könntest einen LineRenderer nutzen. https://docs.unity3d.com/Manual/class-LineRenderer.html Christoph
    2 points
  10. Persistent sind die Listener, die im Editor eingestellt werden. Mit AddListener, RemoveListener und RemoveAllListeners arbeitest du nur mit Listenern, die im Code behandelt werden. Du hast quasi zwei Listen: Die im Editor und die, die du im Code erzeugst.
    2 points
  11. Moin! 1. Die üblichere Schreibweise ist die Lambda-Schreibweise. Die gibt's in mehreren Sprachen und wird, wenn man sie erstmal kennt, meistens als angenehmer empfunden als da "delegate" hinzuschreiben. Ein Lambda-Ausdruck (genau wie mit "delegate") ist einfach eine Schreibweise für eine Methode ohne Namen. Man kann also als Beispiele Methoden nehmen und dann die entsprechenden Lambda-Ausdrücke zeigen: a) private void Foo() { Debug.Log(5); } als Lambda-Ausdruck wäre () => Debug.Log(5) b) private void Foo() { Debug.Log(5); Debug.Log(10); } als Lambda-Ausdruck wäre () => { Debug.Log(5); Debug.Log(10); } Hier braucht man geschweifte Klammern, weil mehr als eine Anweisung drinsteht. c) Jetzt mal mit Parametern: private void Foo(int number) { Debug.Log(number); } wäre number => Debug.Log(number) und private void Foo(int number, string text) { Debug.Log(number + text); } wäre (number, text) => Debug.Log(number + text) Man bemerke hier, dass keine Parametertypen angegeben werden - da steht also nix davon, dass "text" ein string ist. Das liegt daran, dass Lambda-Ausdrücke zu Methodenreferenzen evaluieren. Diese werden als normale Werte behandelt - man kann sie also in eine Variable speichern oder eben als Parameter übergeben. Und in diesen Fällen ist vom Typ der Variable bzw. des Parameters her schon vorgegeben, dass da eine Referenz auf eine Methode kommen muss, die ein int- und einen string-Parameter hat. Action<int, string> foo = (number, text) => Debug.Log(number + text); // und jetzt kann man die Methode aufrufen foo(5, " Stück"); Mit Lambda-Ausdrücken kann man einem Objekt mehr als nur simple Werte in die Hand drücken, sondern ganze Verhaltensweisen. Deshalb kannst du die Dinger benutzen, um Buttons zu sagen, was sie tun sollen. btn.onClick.AddListener(() => GetComponent<MenuManager>().ButtonAction(i)); Soviel erstmal dazu. 2. Jetzt musst du (leider) das Konzept von Closures kennenlernen. Eine Closure ist wie ein Objekt, also ein Ding, das bestimmte Werte hat. Und jede anonyme Methode (also die, die du mit einem Lambda-Ausdruck erzeugst), hat so eine. Sie enthält die für die Ausführung der Methode relevanten Variablen aus der Umgebung, in der der Lambda-Ausdruck steht. var number = 5; Action foo = () => Debug.Log(number); foo(); Hier wird eine int-Variable mit dem Wert 5 definiert, eine Methode die den Wert der Variable ausgibt, und dann wird diese Methode aufgerufen. Es sollte also recht klar sein, dass die Zahl 5 ausgegeben wird. Die Variable "number" ist Teil der Closure der Methode, die von der Variable "foo" referenziert wird. Jetzt kommt der Knackpunkt: Closures machen pass-by-reference. Das heißt, dass hier nicht die 5 in der Closure gespeichert wird, sondern eine Referenz auf die Variable "number". Wenn du folgendes tust: var number = 5; Action foo = () => Debug.Log(number); number = 10; foo(); dann wird tatsächlich 10 ausgegeben und nicht 5, weil beim Aufruf der Methode der aktuelle Wert der Variable angeschaut wird. Wenn du also eine Schleife hast und in dieser Schleife Methoden erzeugst, die irgendetwas mit der Zählvariable i tun, dann wird am Ende jede dieser anonymen Methoden den aktuellen Wert von i nehmen - und das wird nun einmal der Wert am Ende des Schleifendurchlaufs sein, also in deinem Fall 1 bei einem Button bzw. 6 bei sechs Buttons. Die Lösung des Problems ist jetzt ein bisschen stumpf: Du machst in der Schleife eine neue Variable und kopierst da den Wert rein; dann benutzt du diese Variable in deinem Lambda-Ausdruck statt i: for(int i = 0; i < list.count; i++) { var btn = list[i]; var index = i; btn.onClick.AddListener(() => GetComponent<MenuManager>().ButtonAction(index)); } Dann wird die Variable "index" angeschaut, wenn man auf einen Button klickt - und der Wert dieser Variable ändert sich nicht mehr, weil sie am Ende des jeweiligen Durchlaufs (eigentlich) out of scope geht. Übrigens kannst du es dir (so als Schmakerl) sparen, jedes Mal GetComponent aufzurufen, indem du das vorher einmal machst: var menuManager = GetComponent<MenuManager>(); for(int i = 0; i < list.count; i++) { var btn = list[i]; var index = i; btn.onClick.AddListener(() => menuManager.ButtonAction(index)); }
    2 points
  12. Hallo Das müsste passen https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/operators/operator-overloading Christoph
    2 points
  13. Entweder du nimmst einen Shader, der Beidseitig funktioniert, oder aber du erzeugst dir ein Kugel in einem 3d Programm, wie z.B. Blender, und drehst da einfach die Normalen der Flächen um.
    2 points
  14. Soda ist während des aktuellen Unity Asset Store Sales übrigens um stolze 50% reduziert! https://assetstore.unity.com/packages/tools/integration/soda-scriptableobject-dependency-architecture-137653
    2 points
  15. Mit dem Profiler wusste ich bis vor 3 Stunden noch nicht wirklich umzugehen. Ich konnte aber durch Zufall im Profiler das Problem oder Probleme lokalisieren und verstehe jetzt auch wo ich beim nächsten Mal suchen muss. Zum Glück hat sich ein weiterer Zufall dazu gesellt und ich konnte eine Lösung finden. Durch Spine 2D wurde ein Mesch-Generator angezeigt mit um die 40% Auslastung und im Deep Mode konnte ich das ganze auf Spine's Clipping Funktion zurück führen. Die Lösung war in diesem Fall das Clipping zu vermeiden, dafür genügt es den Hacken in der Skeleton Animation bei "Use Clipping" zu entfernen(Für Alle die das selbe Problem haben)(Clipping scheint ein Feature zu sein, welches ich in meinem Fall nicht benötige). MfG DI3FL4MM3
    2 points
  16. Hallo Ich will euch heute mal mein derzeitiges Projekt zeigen. Es heißt „Konsequenz“ und wie der Titel schon sagt, spielen Konsequenzen eine bedeutende Rolle. Einmal spielerisch, also welche Entscheidungen ich als Spieler treffe und auch die Geschichte dreht sich um die Folgen des eigenen Handels. Es gibt keinerlei Kämpfe, dafür viel zu lesen und zu reden. Mein großes Vorbild ist dabei „Disco Elysium“. Natürlich unerreichbar, aber als Richtung in die das Ganze zielt. Ich habe schon viele Zeilen Dialoge geschrieben und feile die ganze Zeit an den Texten. Hier ein paar Impressionen: Die Unterwelt: Das Inventar des Spielers mit dem detaillierten Blick auf ein Item: Das Questfenster: Und hier ein paar Dialogzeilen: Und hier mit einem „Angler“: Es wird drei Welten geben. Die erste ist nun fertig und ich muss zugeben, dass ich den Aufwand total unterschätzt habe. Ich habe bereits sehr viel Zeit investiert und es ist noch kein Ende in Sicht. Allein das Finden von Bugs ist dermaßen mühselig… Da es mein erstes richtiges Spiel wird, musste ich auch viel Lehrgeld zahlen. Einige Module habe ich komplett neu entwickelt, bei einigen habe ich auf bewährte Lösungen aus dem store gegriffen. Ich habe mir irgendwann die Frage gestellt, was ich eigentlich will: Spiele programmieren oder bsp. Lokalisierung. Ich habe mich dann manchmal für das Spiel entschieden! Es gibt noch viel zu tun: - Licht (hatte ich schon, aber zur Zeit aus, weil es noch zu hässlich war) - Sound - Ganz viel Finetuning (QoL – Sachen) Jetzt freue ich mich auf eure ehrliche Rückmeldung!! Christoph
    2 points
  17. Da bieten sich die Trigger an. Du brauchst also einen oder mehrere Collider, die über der Wiese liegen. Denen gibst du einen Tag, z.B. Grass und stellst den/die Collider auf Trigger. Wenn dein Player da rein läuft, werden Triggerevents gestartet. Die kannst du im Code nutzen. Das OnTgriggerEnter() Event wäre dann dafür da, zu sagen, dass du ab jetzt auf dem Grass bist. Beim verlassen des Triggers, würde OnTriggerExit() aufgerufen werden, was dir sagt, dass du das Gras wieder verlassen hast. https://docs.unity3d.com/ScriptReference/Collider.html Schau beim Link und scroll runter zu den Messages. Da findest du alles Nötige.
    1 point
  18. Bei 3D Objekten, die in einem 3D Programm, wie z.B. Blender erstellt wurden, kommt die Position des Achsennullpunktes von dem Ersteller selbst. Den kann man in Blender usw. hinschieben wo man will und man kann ihn natürlich auch drehen und somit bestimmen was oben und was vorne ist. Bei den Grundobjekten von Unity ist das nicht so, da ist das Dingen wo es ist. Was du aber machen kannst: Erzeuge ein EmptyObject und ordne den Grundkörper diesem Empty unter. Die Transformwerte, die du jetzt bei dem Grundkörper siehst, sind Werte im Bezug auf den Vater. Schreibst du bei der Position 0,0,0, dann ist das Grundobjekt genau am Punkt des Empty. Du kannst jetzt also das Grundobjekt, also das Kind des Empty so verschieben, dass der Nullpunkt des Empty z.B. am unteren Rand des Kindes ist. Gibts du jetzt nicht dem Grundobjekt einen Collider und Rigidbody, sondern dem Vater, also dem Empty, dann ist der Vater das entscheidende, wenn es um Kollisionen und Kräfte geht. Das KindObjekt ist nur fürs aussehen da. Übrigens, du kannst im Szeneview oben in der Leiste einstellen, welchen Nullpunkt du sehen willst. Entweder den Zentrierten, oder den Wirklichen. (Global oder Local). Um den echten Nullpunkt zu sehen (Bei importierten Objekten, wie oben beschrieben) musst du auf Local gehen. Bei den Grundkörpern und einem EmptyObject ist sind beide Punkte natürlich identisch.
    1 point
  19. Moin! ScriptableObjects sind genau dazu da, eine Identität und oder Daten zu haben, die im Editor eingestellt und dann in den Build mitgenommen werden. Wenn man das Konzept ein bisschen streckt, dann kann man auch dynamische Daten drin haben. Aber auch das ist nur sinnvoll, wenn man auch die Identität nutzt. Falls das mit der "Identität" nicht so klar ist, so als Beispiel: Du kannst ein komplett leeres ScriptableObject nutzen, um Spielfiguren in Teams einzuteilen. Zwei Figuren, die dasselbe "Team-SO" referenzieren, sind im selben Team und machen sich gegenseitig keinen Schaden. Das meine ich mit Identität. Wenn ich jetzt aber so Felder wie "iD" und "StructurActual" sehe, dann nehme ich an, dass du hier ein Laufzeit-Objekt repräsentieren willst. Also ein Objekt, dass der Spieler während des Spiels erstellt. Zum Beispiel, indem er es im Shop kauft oder so. Das geht zwar, macht aber so seine Problemchen. Da du aber wiederum Felder wie "Description" hast, die recht eindeutig Daten beinhalten, die du im Editor eingibst, sind ScriptableObjects schon gut. Ich würde empfehlen, das aufzuteilen. Eine ScriptableObject-Klasse beinhaltet die statischen Daten, die du im Editor eingibst: public class ModuleInfo : ScriptableObject { public string name; public string description; public int maxIntegrity; } Und eine nicht-SO-Klasse kannst du zur Laufzeit instanziieren, um Laufzeit-Objekte zu repräsentieren: class Module { public readonly ModuleInfo moduleInfo; public int integrity; } Wie du siehst, referenziert es ein ModuleInfo-Objekt, das die statischen Daten beinhaltet. So kannst du auch wunderbar beliebig viele Triebwerke (also gleichartige Module) erlauben. Das ist wäre immer ein bisschen schwer, wenn du alle potentiellen Module im Voraus im Editor anlegen müsstest.
    1 point
  20. Ich hab mal ne neue Klasse geschrieben: namespace UnityEngine.UI { using UnityEngine.EventSystems; using UnityEngine.Events; [RequireComponent(typeof(Image))] public class ToggleButton : UIBehaviour, IPointerDownHandler, IPointerUpHandler { [System.Serializable] private class BoolEvent : UnityEvent<bool> { } private Image image; private Sprite defaultSprite; public Sprite pressedSprite; public Sprite activeSprite; public bool isOn; [Space] [SerializeField] private BoolEvent onValueChanged = default; protected override void Awake() { base.Awake(); image = GetComponent<Image>(); defaultSprite = image.sprite; } protected override void Start() { base.Start(); ApplyUnpressedState(); } void IPointerDownHandler.OnPointerDown(PointerEventData eventData) { image.sprite = pressedSprite; isOn = !isOn; onValueChanged.Invoke(isOn); } void IPointerUpHandler.OnPointerUp(PointerEventData eventData) { ApplyUnpressedState(); } private void ApplyUnpressedState() { image.sprite = isOn ? activeSprite : defaultSprite; } } } Die packst du dir einfach auf ein leeres GameObject in deinem Canvas. Da wird automatisch ein Image mit dazu erzeugt. Dem Image gibst du das Default-Bild (15), dann packst du bei "Pressed Sprite" das gedrückte Bild (17) und bei "Active Sprite" das losgelassene, aber aktive Bild (16) hin. Das Event ist wie gehabt. Wenn du da noch mehr haben willst, z.B. Mouse Hover oder so, dann musst du das um die entsprechenden Interfaces (IPointerEnterHandler, IPointerExitHandler) erweitern.
    1 point
  21. Kann es sein, dass du in deinem Projekt irgendwelche Sonderzeichen oder Umlaute nutzt, die nicht UTF8-konform sind? Also bei Variablennamen, Klassen/Scriptnamen oder Objektnamen. Ist halt Apple-Scheiße! Glaub mal nicht, dass die mit erweiterten Zeichensätzen richtig umgehen können.
    1 point
  22. Freut mich sehr Ich muss das Lob aber auch zurück geben. Deine Fragestellungen sind sehr gut und ausführlich, und haben immer alle wichtigen Infos mit drin. Da muss man nicht raten, und das macht es sehr recht einfach, dir zu helfen
    1 point
  23. In diesem Update stecken jede Menge technische und visuelle Neuheiten.• Blöcke können jetzt nicht nur geschoben, sondern auch gestapelt werden.• Genau wie Plattformen, können jetzt auch solide Objekte einen Pfad folgen und dabei Wege blockieren oder Dinge zerquetschen.• Zwei neue Tilesets: Erde & Eis• Triggerzonen können Events auslösen. Damit ist es möglich Lichter, Partikeleffekte oder bewegliche Objekte zu aktivieren, Gegner zu spawnen und das Wetter, Musik sowie den Wasserstand zu ändern.• Einstellbare Kamerapositionen ermöglichen es, den Fokus an wichtige Stellen zu lenken. Eine statische Kamera kann für einen Bosskampf genutzt werden oder sorgt mit einer -sich selbst bewegenden- Kamera für Zeitdruck.• Platzierbare Partikeleffekte wie Feuer, Rauch, Regen und Schnee sorgen für Atmosphäre• 4 neue Animationen für Abigail. Salto, Pirouette, Lauf-dreh-Bewegung und Kopfkollision• Viele neue Projektile, die von Gegner abgefeuert werden können. Darunter wirbelnde Klingen (die später für Scarlet gedacht sind), welche den Spieler verfolgen oder von Oberflächen abprallen können.• Wasser (Ist noch in einem sehr frühen Stadium und hat noch keine Funktion)
    1 point
  24. Hallo, meine unterirdische Höhle habe ich fast fertig. Riffolk trifft hier eine Kreatur aus längst vergessener Zeit Orfayas. Da Riffolk in dieser Höhle gefangen ist, muss er einen Ausweg finden. Dafür muss er einige Kämpfe bestehen und ein Rätsel lösen. Hier ein kleines Video vom Level:
    1 point
  25. Moin,ich bin Sam von MilleniumSpark.com und ich bin ganz neu im Forum, freut mich euch kennenzulernen. Wir suchen momentan nach Gleichgesinnten, die Lust haben die Spielebranche ein wenig zu bereichern und wenn ich "wir" schreibe, meine ich hauptsächlich mich und ein paar Freunde. Wem die 60€+AAA-Titel mit unzähligen kostenpflichtigen DLCS auch auf die Nerven gehen, der ist bei uns genau richtig. Natürlich stecken hinter so großen Firmen wie EA, Paradox usw. immer Menschen die am Ende des Tages auch Ihr Geld verdienen müssen, aber in unseren Augen ist das eine extrem fehlgeleitete Firmenpolitik. Wir sagen: "Das geht besser".Wir sind keine professionelle Gruppierung, Firma oder Sekte , sondern einfach ein paar Leute die an der Entwicklung von Spielen interessiert sind. Der Fokus liegt bei der Aktion auf das sammeln von Erfahrung und Wissen. <-- Soweit so gut, hat man schon oft gehört und gelesen. Wir wollen uns nicht übernehmen und uns Zeit lassen. Kein Druck, keine Deadlines, keine Prügel vom Chef, Alles ganz entspannt.Wir haben am 30.06.2022 angefangen an TheAges zu arbeiten und stehen noch ganz am Anfang. Wir verdienen mit dem Projekt kein Geld und haben vorerst auch nicht vor mit dem Spiel kommerzielle Interessen zu verfolgen. Wenn sich das Projekt entwickelt, kann man immer noch schauen, ob es überhaupt sinnvoll wäre das Spiel auf Plattformen wie Steam, Amazon Games, Epic usw.. anzubieten. Das ist aber noch Zukunftsmusik und Spieleentwicklung braucht nun mal leider sehr viel Zeit.Wir benutzen vorerst, kostenfreie Assets aus dem Unity-Store und nutzen diese als Platzhalter. Nach und nach sollen alle Modelle und Grafiken durch eigene ersetzt werden. Das verbraucht gefühlt am meisten Zeit, da wir keine besonders erfahrene Modellierer bei uns haben, gleiches gilt für 3D-Animationen. Sollten wir am Ende alleine mit dem Projekt verweilen, dann werden wir uns da natürlich trotzdem durchzwingen und uns die erforderlichen Fähigkeiten aneignen, darum geht es ja auch bei dem Ganzen, neue Dinge zu lernen und besser zu werden in dem was man schon kann.Projekt-Informationen: Projektname: "The Ages"Engine: UnityCode: C#Grafikstil: Stylized Low-Poly ArtGenre: 3D-Third-Person-HacknSlay Fantasy-RPGWas macht man im Spiel?Wir kämpfen uns durch Gegnerhorden in feinster HacknSlay-Manier, sammeln Loot, reiten auf Mounts, verbessern unsere Fähigkeiten, erledigen Quests, kaufen uns ein Zuhause, heiraten und erschaffen uns ein Vermächtnis das die Zeit überdauern wird. Es gibt kein Spieler-Level, aber dafür Prestige und je nachdem wie man sich in Dialogen mit NPCs verhalten hat, steigt der Wert oder sinkt. Durch Quests, das erkunden der Welt usw. erhält man Titel, die dem Spieler Boni gewähren und die Aktionen der NPCs beeinflussen. Vielleicht kennt ja noch Jemand eins meiner Lieblingsspiele: Fable. Vom Gameplay her, geht es sehr wahrscheinlich in diese Richtung. Gameplaytechnisch also keine Revolution und wir möchten den Fokus darauf legen eine möglichst gute Story zu erzählen und dem Spieler das Gefühl zu geben ein vollwertiges Spiel zu spielen.Story:Als nicht legitimierter und verstoßener Erbe des Throns von Valya, wachsen wir in einem vom Königshaus weit entfernten Dorf auf. König Grimwar, machtbesessen, grausam und von einer unscheinbaren Kraft getrieben, regiert das Land mit eiserner Faust. Valya geht unter seiner Herrschaft den Bach runter und verwandelt sich in einen stinkenden Sumpf, voller Monster, Banditen und Gesocks. Wir spielen Dromur, einen Holzfäller mit flachen Witzen und dicken Muskeln. Eines Tages wird Dromurs Dorf von Grogger-Räubern angegriffen und dem Erdboden gleichgemacht. Wir eilen zur Hilfe, doch Etwas schlägt uns bewusstlos, wir rollen einen Berghang hinab und uns empfängt die Dunkelheit. Hier beginnt die Story, die erst noch geschrieben werden muss. (Die Vorgeschichte ist bereits fertig und jetzt schreiben wir die Haupt-Story von "The Ages" drumherum.)Impressionen: Was suchen wir eigentlich?In den folgenden Bereichen könntest du uns helfen:Projektleitung Grafikdesign Art-Designs Sounddesign Programmierung Marketing Anwendungstests Storytelling 3D-Texturierung Leveldesign Konzeption Modellierung Animation SFX VFX oder als Synchronsprecher Was wir im groben von dir in den verschiedenen Bereichen erwarten, kannst du auf unserer Webseite unter Jobs nachlesen. Voraussetzung für die Mitwirkung am Projekt:Du solltest:- mindestens 18 Jahre alt sein- ein funktionstüchtiges Headset/Mikrofon besitzen- Interesse und eigene Erfahrung mit einbringen- politisch keiner extremen oder verfassungsfeindlichen Organisation angehören, ansonsten hat Politik bei uns generell nichts zu suchen- Spaß an der Spieleentwicklung haben- auch ein Privatleben führen- Fragen stellen wenn Etwas ist.An alle erfahrenen Entwickler, die eventuell sogar aktiv in der Branche tätig sind, sind herzlich eingeladen Vorträge über unseren Discord zu halten. Zeit ist Geld und es gibt nicht umsonst Ausbildungen in diesem Bereich, aber wer trotzdem ein wenig von seinem Wissen teilen möchte, würde uns einen großen Gefallen tun. Das war es erstmal von uns, wenn du Interesse hast, schreib gerne einen Kommentar unter diesen Beitrag, eine E-Mail über bewerbung@MilleniumSpark.com oder komm direkt auf unseren Discord und schreib ein wenig über dich im Bereich "Bewerbung".Wenn du das Projekt cool findest und uns unterstützen möchtest, dann schaue doch mal auf unseren Youtube-Kanal vorbei, hier posten wir sporadisch immer mal wieder kurze Updates über TheAges.Social-Media:Youtube: https://www.youtube.com/c/MilleniumsparkDiscord: https://discord.gg/yBPrq6rsd7mit freundlichen Grüßen,Samkruso und das MilleniumSpark-Team <3
    1 point
  26. Moin! Das geht nicht - hier überschreibst du mit jeder Zeile die vorhergehende. Am Ende dieses Codes hättest du nur die Z-Rotation eingefroren. RigidbodyConstaints sind Bitflags - du kannst sie daher mit | kombinieren: unlock.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezePositionZ; Aber da du hier alle bis auf einen haben willst, bietet es sich an, stattdessen RigidbodyConstraints.FreezeAll zu nehmen und davon mit - eine der Achsen abzuziehen, die dann nicht gefroren wäre: unlock.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll - RigidbodyConstraints.FreezePositionX;
    1 point
  27. Hallo Ich habe ein Prefab, welche ich zur Laufzeit mit Instantiate() erzeuge. Das klappt alles ohne Probleme. Wenn ich allerdings zwei oder mehr Objekte erstelle und bei einem davon Werte ändere, ändern sich diese auch bei den ebenfalls erstellten Objekten. Ist das so gewollt oder habe ich ein Wahrnehmungsproblem und der Fehler liegt ganz woanders? Stimmt nicht, habe es praktisch in dem Moment entdeckt, als ich auf Absenden geklickt habe. Fehler muss bei mir liegen. Sorry. Christoph
    1 point
  28. Also komm, ein Minimum an Mühe darfst du dir mit deinem Post schon geben.
    1 point
  29. Vielen Dank. Du hast mir sehr geholfen. Bisschen Google und schon hab ich Tutorials gefunden, wie man UV-Maps in Blender macht. Funktioniert super. Vielen Dank 😘
    1 point
  30. Hallo, ich habe noch zwei weitere Skills ins Spiel gebracht. Der erste neue Skill ist ein Magieturm den Riffolk erzeugen kann. Der Turm verschießt Energiekugeln und eliminiert sich dann nach 30 Sekunden. Als zweiten neuen Skill kann er einen Wolf als Begleiter spawnen. Hier ein kleines unmoderiertes Video.
    1 point
  31. Hi Erst mal danke für den Beitrag. Ich habe nun Zugriff zum Dateisystem. Ich musste in der AndroidManifest.xml im Application-Tag noch folgendes ergänzen: <application android:requestLegacyExternalStorage="true"> Ich kann nun auf das Dateisystem zugreifen, wenn ich den Pfad direkt anspreche (/storage/emulated/0/) aber der Pfad "/storage/emulated/" ist immer noch leer. Android mag das einfach nicht so, wenn man direkt im Filesystem rumwurstelt. Deshalb habe ich nach einer Lösung gesucht und nun das kostenlose "Runtime File Browser" entdeckt. War in 5 Minuten implementiert und funktioniert perfekt. Jetzt habe ich einfach meinen eigenen File Browser mit diesem ersetzt. Ich hatte noch das Problem, keine Zugriff auf die Files auf der SD-Karte zu haben. Da ist Android auch irgendwie empfindlich. Trotz den Berechtigungen. Android mag es einfach nicht, wenn man Dateien umbenennt oder verschiebt (ist ja das selbe). Deshalb habe ich das ganze jetzt so geändert, dass ich die Datei auch nicht mehr umbenennen muss (hab vorher an den Dateinamen eine ID gehängt, damit die Datei wieder erkannt werden kann, falls der User sie verschiebt). Jetzt läuft alles gut 😃
    1 point
  32. Hallo Ich weiß es nicht, halte es für nicht wichtig, würde aber in deinem Fall den Profiler anwerfen. Christoph
    1 point
  33. Unity hat ja einen FoV-Wert, der den von der vertikalen Achse der Kamera abgedeckten Bereich beschreibt. Wenn ich deine Beschreibung richtig verstanden habe, dann möchtest du stattdessen einen Winkel für die horizontale Achse definieren. Dafür gibt's mehrere Möglichkeiten. Diese hier wäre einfach nur, dass du mit dieser Methode den festgelegten horizontalen Winkel reinschmeißt, den Aspect Ratio der Kamera (also Breite durch Höhe des Bildes) dazu, und dann eben einen Winkel für die vertikale Achse kriegst. Hab's nicht getestet, aber sowas in der Art: camera.fieldOfView = camera.HorizontalToVerticalFieldOfView(60, camera.aspect); Wobei man ja sowieso annehmen muss, dass die Methode intern nur eine Multiplikation macht Die 60 ersetzt du jedenfalls durch einen beliebigen Wert, bei dem dir der Bildausschnitt gefällt. Ausführen kannst du das dann in Start().
    1 point
  34. Hallo, heute gibt es etwas vom Gameplay zu sehen. Der Spieler hat den Auftrag sich in der Shanty-Town umzusehen und Informationen zu sammeln. Ein Soldat wird ihn dabei begleiten. Jetzt gibt es ein erstes Treffen mit den Zombies.
    1 point
  35. Aber in jedem Frame einmal? Oder alle auf einmal? Denn das klingt für mich im Zweifelsfall nach gut einem Dutzend Instanzen dieses Scripts in deiner Szene.
    1 point
  36. Hallo var mousePos = Input.mousePosition; go.transform.position = mousePos; müsste gehen. Christoph
    1 point
  37. Instantiate gibt das gespawnte Objekt zurück. Du kannst da also entweder eine Komponente draufklatschen, die die Bewegung übernimmt, oder du übergibst die Referenz auf das Objekt an das Script, das die Maus im Auge behält.
    1 point
  38. Schau mal hier: Inklusive dem zweiten Post danach von mir. Mit "delegate" baust du (in diesem Fall) zur Laufzeit eine neue Methode. Das kannst du auch mit einem Standard-Lambda-Ausdruck machen. Statt delegate { OnRightMouse(obj);} schreibst du dann () => OnRightMouse(obj) Mit "delegate" geht das bestimmt auch irgendwie, aber mit dieser Schreibweise kannst du in die Klammer Parameter hinzufügen (siehe verlinkter Post). So kannst du Parameter wie das BaseEventData entgegennehmen und weiterreichen, aber auch andere Parameterwerte übergeben (wie dein obj). Also: AddEvent(obj, EventTriggerType.PointerClick, eventData => OnClick(obj, eventData)); private void OnClick(GameObject obj, BaseEventData baseEventData) { var eventData = ...; if (eventData.button == PointerEventData.InputButton.Right) { // stuff } }
    1 point
  39. Da hab ich direkt schonmal Feedback zu diesen Medien für dich Wenn du einen Link posten willst, öffne diesen Link vorher einmal im Inkognito-Modus und schau, wie er aussieht. Du hast hier z.B. deinen Dashboard-Link gepostet. Und wenn ich den anklicke, sehe ich nur "Zugriff verweigert", weil ich (zum Glück) nicht als du angemeldet bin Wenn du einen asynchronen Post machst (so wie diesen Forenpost - du schickst ihn ab, ich sehe ihn 2 Stunden/2 Tage/... später), dann solltest du da permanent verfügbare Medien drin haben. Dein Twitch-Kanal ist aktuell (keine Überraschung) nicht live, und dazu hat er auch keine Aufzeichnungen. Ich gehe da also hin, sehe gar nichts, und gehe wieder. Das liegt halt in der Natur der Sache, aber wenn du so Werbung machst, dann musst du immer auch etwas zum sofort anschauen dazu packen. Sonst vergessen dich die Leute nach zwei Sekunden wieder. Wenn du wirklich nur Leute auf deinen Twitch-Stream locken willst, dann poste den irgendwo, wo Leute das sofort sehen. Und zwar dann, wenn du auch wirklich gerade live bist. Damit gemeint sind Chats wie z.B. auf Discord.
    1 point
  40. Am Besten ist es sich ein Ziel zu setzen. So macht das lernen mehr Spaß. Versuche nicht die Funktionen einzeln zu lernen. Fang an ein Raumschiff oder ein Haus oder ein Auto (dein eigenes Auto) zu modellieren. Schau mal hier ->
    1 point
  41. 😄 habe meinen Fehler bemerkt und wollte den Eintrag schon abschließen, aber du bist ja unglaublich schnell...
    1 point
  42. Joa, ich auch, aber das ist so ein kurzer Code dass es mir egal war. Sobald es minimal komplizierter wird, kommt man mit der Herangehensweise nicht mehr weit
    1 point
  43. Hallo Lies dir das mal durch: http://blog.13pixels.de/2019/what-exactly-is-fixedupdate/ Christoph
    1 point
  44. Ein kleines Update zum aktuellen stand der Dinge; wir haben jetzt bewegliche Plattformen, Falltüren, Sprungfedern und einen neuen Gegner. 😃
    1 point
  45. Hallo Habe ich schon, aber dein pooling asset habe ich mir gegönnt. Christoph
    1 point
  46. Kommt drauf an! Ich weiß, lästige Antwort, aber doch so oft die richtige Mit Prefabs kannst du erstmal nicht so viel falsch machen. Gerade, wenn man Nested Prefabs nutzt, wird's wirklich simpel. Da kannst du alle deine wichtigen Prefabs einfach in ein Prefab nesten und nur dieses eine reinziehen. DontDestroyOnLoad zu nutzen würde erstmal (!) bedeuten, dass du deine wichtigen GameObjects in einer oder mehreren Szenen hast und diese Szene(n) zu irgendeinem frühen Zeitpunkt einmal geladen sind. Dann flagst du deine Objekte mit DontDestroyOnLoad und ja, sie verschwinden dann nicht mehr beim Szenenwechsel. Früher blieben die in der Tat einfach in der Hierarchie. Heute macht Unity, zumindest im Editor, eine neue "DontDestroyOnLoad-Szene" auf, in der alle geflagten Objekte wohnen. Diese Szene wird nicht entladen, wenn man eine neue Szene nicht-additiv lädt. Ob das im Build auch so ist, hab ich nie getestet Diese Herangehensweise mit DontDestroyOnLoad habe ich immer mal wieder empfohlen gesehen (im Sinne von "mach dir eine 'wichtige Objekte'-Szene und lade die als erstes"), aber ich finde sie sehr problematisch. Nehmen wir mal ein Pausemenü-UI-Prefab. Das willst du ja in jedem Level gleichermaßen haben... außer im Hauptmenü. Du musst also irgendwie dafür sorgen, dass das Ding in der Hauptmenü-Szene deaktiviert ist und sich ansonsten wieder re-aktiviert. Da habe ich schon öfter mal sowas wie if (geladeneSzene.name == "main menu") gesehen. Fürchterlich! Da musst du einfach immer im Hinterkopf behalten, dass du die Hauptmenü-Szene nie umbenennen darfst. Als Solo-Dev vielleicht noch erträglich, aber sollte man sich nicht immer Techniken angewöhnen, die auch im Team funktionieren? Und was, wenn es mal mehr Szenen (Optionen? Credits? Multiplayer-Lobby?) gibt, die auch kein Pausemenü haben sollen? Du musst darüber hinaus sicherstellen, dass diese spezielle Szene einmal geladen wird, bevor irgendetwas anderes passiert. Gibt also vermutlich eine weitere Einschränkung für die Build List. Und was, wenn du im Editor eine Szene testen willst? Da kannst du dir Editor-Code schreiben, der diese Szene immer einmal lädt, sobald der Play Mode gestartet wird. Wieder etwas, das man irgendwie schon machen kann, aber... worauf ich hinaus will ist: Meiner Meinung nach sollte man Einschränkungen darin, was man im Editor machen darf, vermeiden. Und die Komplexität und Menge der Arbeitsschritte, die es braucht, um etwas zu implementieren, gering halten. Dieser ganze Ansatz ist unter diesem Gesichtspunkt eine riesige Katastrophe. Für mich ist der Unity Editor der Ort, an dem Gamedesign passieren soll. Je mehr Gamedesign im Code passiert, und je mehr verpflichtendes Setup im Editor passiert, damit der technische Code richtig funktioniert, desto schlechter ist die Architektur demnach. Jetzt darfst du das natürlich anders sehen, aber für den Fall dass du da zustimmst, hier eine andere Variante: Du kannst den Code selber Dinge instanziieren lassen. Hier ein Beispiel: public static class CoroutineService { private class Worker : MonoBehaviour { } private static Worker worker; [RuntimeInitializeOnLoadMethod] private static void Initialize() { var go = new GameObject("Coroutine Service"); go.hideFlags = HideFlags.HideAndDontSave; Object.DontDestroyOnLoad(go); worker = go.AddComponent<Worker>(); } public static Coroutine StartCoroutine(IEnumerator coroutine) { return worker.StartCoroutine(coroutine); } public static void StopCoroutine(Coroutine coroutine) { worker.StopCoroutine(coroutine); } } Du siehst eine statische Klasse, die ein GameObject bei Spielstart erzeugt. Das Attribut [RuntimeInitializeOnLoadMethod] ist pures Gold. Du packst es über eine beliebige statische Methode und diese wird direkt beim Spielstart, noch vor irgendwelchen Awakes oder Starts der ersten Szene, ausgeführt. Das ist dann wie bei der speziellen Szene, die ich weiter oben meinte - nur halt nicht im Editor, sondern im Code. Der Reihe nach: new GameObject ist klar. HideFlags.HideAndDontSave sorgen dafür, dass das Objekt nicht in der Hierarchie auftaucht und auch nie irgendwie in der Szene gespeichert wird. So kann man weniger versehentlich kaputt machen. DontDestroyOnLoad ist jetzt auch klar: Das Objekt bleibt während des gesamten Spiels verfügbar und ist nicht beim ersten Szenenwechsel wieder weg. AddComponent ist auch klar - eine Referenz auf die erzeugte Komponente wird gemerkt. Die anderen beiden Methoden erlauben dir jetzt einfach, die statische Klasse um das Ausführen/Stoppen einer Coroutine zu bitten, und das Arbeiter-GameObject, das komplett von dieser Klasse verwaltet wird und im Editor komplett unsichtbar ist, führt das dann aus. CoroutineService.StartCoroutine(MyCoroutine()); Das ist echt super für technische Sachen. Aber so Sachen wie "in welchen Szenen gibt es ein Pause-Menü?" sind einfach nicht technisch. Das ist 100% Gamedesign. Und deshalb würde ich für das Pause-Menü hiervon abraten und ganz schlicht über das Reinziehen eines Prefabs definieren, dass diese Szene ein Pause-Menü zu haben hat. Einen Nachteil hat mein Code da oben noch: Was ist, wenn du ein global verfügbares Objekt haben, aber etwas im Editor einstellen oder referenzieren willst? Statt eines CoroutineService, was wäre mit einem BackgroundMusicService? Eine AudioSource, die immer da ist und der man neue Musik zum Spielen geben kann. So eine AudioSource hat einiges an Einstellungen, die man im Editor machen kann und will. Spatial Blend, Volume, und nicht zuletzt Audio Mixer Group. Und darauf habe ich zum Glück auch eine Antwort, da ich weiß, dass du Soda besitzt: Dafür ist das ModuleSettings-System da
    1 point
  47. Ist es möglich, dass jemand diese Zeile erklärt? Ability _ability = UnityEngine.Object.Instantiate((Ability)Resources.Load(_abilityName, typeof(Ability))) as Ability; Finde es schade, dass man auf alles genau eingeht und dann so eine Monsterzeile einfach unkommentiert lässt.
    1 point
  48. @Helishcoffe Oder halt Gizmos.DrawWireCube bzw Handles.DrawWireCube
    1 point

Announcements

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

Weiterleitung zum Entwickler "daubit"



×
×
  • Create New...