• Announcements

    • Lars

      Allgemeine Forenregeln   03/13/2017

      Forenregeln Nimm dir bitte einen Moment um die nachfolgenden Regeln durchzulesen. Wenn du diese Regeln akzeptierst und die Registration fortsetzen willst, klick einfach auf den "Mit der Registrierung fortfahren"-Button. Um diese Registration abzubrechen, klick bitte einfach auf den "Zurück" Button deines Browsers. Wir garantieren nicht für die Richtigkeit, Vollständigkeit und Brauchbarkeit der Nachrichten und sind auch nicht dafür verantwortlich. Die Beiträge drücken die Meinung des Autors des Beitrags aus, nicht zwangsläufig das, wofür die Forensoftware steht. Jeder Nutzer, der denkt, dass ein veröffentlichter Beitrag unzulässig bzw. störend ist, ist aufgefordert uns unverzüglich per E-Mail zu kontaktieren. Wir haben das Recht störende Beiträge zu löschen und bemühen uns, das in einem realistischem Zeitraum zu erledigen (sofern wir beschlossen haben, dass die Löschung notwendig ist). Du akzeptierst, durchgehend während der Nutzung dieses Services, dass du dieses Forum nicht dazu missbrauchen wirst, Inhalte zu veröffentlichen, welche bewusst falsch und/oder verleumderisch, ungenau, beleidigend, vulgär, hasserfüllt, belästigend, obszön, sexuell belästigend, bedrohlich, die Privatsphäre einer Person verletzend oder in irgend einer Art und Weise das Gesetz verletzen. Des Weiteren akzeptierst du, dass du keine urheberrechtlich geschützte Inhalte ohne Erlaubnis des Besitzers in diesem Forum veröffentlichst. Mit dem Klick auf den "Mit der Registrierung fortfahren"-Button, akzeptierst du zudem unsere Datenschutzerklärung und stimmst der Speicherung deiner IP-Adresse und personenbezogenen Daten zu, die dafür benötigt werden, um dich im Falle einer rechtswidrigen Tat zurückverfolgen zu können bzw. permanent oder temporär aus dem Forum ausschließen zu können. Es besteht keine Pflicht zur Abgabe der Einwilligung, dies erfolgt alles auf freiwilliger Basis.   Zusatzinformationen Der Forenbetreiber hat das Recht, Nutzer ohne Angabe von Gründen permanent aus dem Forum auszuschließen. Des Weiteren hat er das Recht, Beiträge, Dateianhänge, Umfrage, Blogeinträge, Galleriebilder oder Signaturen ohne Angabe von Gründen zu entfernen. Mit der Registrierung verzichtest du auf alle Rechte an den von dir erstellten Inhalten, bzw. treten diese an das Unity-Insider.de und Unity-Community.de ab. Dies bedeutet im Klartext, dass das Unity-Insider.de und Unity-Community.de frei über deine Texte verfügen kann, sofern diese nicht wiederum die Rechte anderer verletzen. Es besteht weiterhin kein Anspruch von registrierten Nutzern bzw. ehemaligen registrierten Nutzern darauf, dass erstellte Inhalte und/oder die Mitgliedschaft (User) wieder gelöscht werden (Erhaltung der Konsistenz dieses Forums).   Einwilligungserklärung Wenn du mit der Speicherung deiner personenbezogenen Daten sowie den vorstehenden Regeln und Bestimmungen einverstanden bist, kannst du mit einem Klick auf den Mit der Registrierung fortfahren-Button unten fortfahren. Ansonsten drücke bitte Zurück. Stand: 07.03.2011

Sneyke

Members
  • Content count

    4
  • Joined

  • Last visited

Community Reputation

3 Neutral

About Sneyke

  • Rank
    Newbie
  1. Bei mir hat es mit circa 10 Jahren (~ 2000/2001) angefangen. Da habe ich zumindest angefangen mich ein wenig mit Modding von Games zu beschäftigen, da mein älterer Bruder das auch gemacht hat. Das war damals noch so zeug wie "Quake 3" und "Star Wars Jedi Knight 2". Danach kamen so mit 12-13 erste Minigames aus dem RPG-Maker. Doch wirklich etwas brauchbares kam da nie zustande. So zwischen 12 und 15 kam dann auch erste Programmiererfahrung in C++ dazu. Leider bin ich über Konsolen-Programme nie hinaus gekommen. Am Programmieren sollte ich zunächst das ernsthafte Interesse verlieren. Immer mal wieder habe ich es dennoch Versucht von vorne anzufangen, mit meinem C++ Buch von 2002. Erst nachdem ich (nach 3 Wochen) meine Kaufmännische Ausbildung beendet hatte (absolut nichts für mich), habe ich beschlossen wieder zu Programmieren und auch beruflich in diese Richtung zu gehen. Hierbei haben mir die, wenn auch wenigen, Erfahrungen aus meiner C++-Zeit sehr geholfen. Daran habe ich solch einen Gefallen gefunden, dass ich heute behaupten kann ein Passabler Java-Entwickler zu sein. Nach mehrfachen Anläufen in diversen Engines, habe ich es dann auch endlich geschafft mich ein wenig in Unity einzuarbeiten, dass ich nun meine Freizeit mit Basteleien füllen kann
  2. Danke fürs Feedback. Ja... die Groß- und Kleinschreibung... Da tu ich mir noch schwer, weil ich eigentlich aus der Java-Ecke komm. Daher kommt glaub ich auch mein double-Wahn (in Java sind floats irgendwie verpönt. Zumindest in den Firmen in denen ich bis jetzt war). Aber ich will mich nicht rausreden Ich wusste gar nicht dass Unity schon so viel von Haus aus mitbringt. Bezüglich der Fraktion stimme ich dir natürlich uneingeschränkt zu. Ich würde selbst sogar behaupten, String für sowas zu benutzen fällt unter "Bad Practice". Da Stringvergleiche (zumindest in Java) echt teuer sind, sollte man immer Enums benutzen. Aber für das Beispiel habe ich es bei einem String belassen. Ohne Worte... Sehr Peinlich Cool dass du dir die Mühe gemacht hast. Ich werde die Sachen übernehmen und dann das Tut nochmal überarbeiten.
  3. Danke für die Antwort. Hab mir auch Mühe gegeben . Eventuell habe ich ein bisschen wirr geschrieben am Anfang. Zu dem angesprochenen Problem: Genau das soll vermieden werden. Der Raycast wird in jedem Frame, von der aktuellen Position des Projektils, nur so weit nach vorne durchgeführt wie er innerhalb der Zeit von Time.deltaTime zurücklegen konnte (also ~ 0.001LE). Wenn es dabei etwas trifft, wird es in die Hitliste gelegt, wenn nicht, dann wird die Position aktualisiert. In der if-Abfrage sieht man ja wie "kurz" der Ray ist: if (Physics.Raycast(shot.Postion, shot.Direction, out hit, shot.Speed * Time.deltaTime)) Wenn man die Projektilgeschwindigkeit in meinem Code z.B. auf 1 setzt, dann sieht man einen kurzen, langsamen grünen Strahl, der so lange gerade aus fliegt, bis er was trifft oder Zerstört wird. Der Strahl an sich ist immer nur ein paar Zentimeter lang. Dafür muss man die Debug.DrawRay()-Zeile entkommentieren. Wenn ich daheim bin mach ich mal einen Screenshot dass man sich das besser vorstellen kann. Dieses Problem das du angesprochen hast, wäre aufgetreten wenn ich die "einzig halbwegs gute Idee" aus dem Internet übernommen hätte. Die hätten es nach genau dem Schema durchgeführt wie von dir beschrieben. Da ich mir sofort die gleichen Fehlerquellen eingefallen sind, habe ich mir dieses System ausgedacht. Nur komisch dass soweit bis jetzt noch niemand gedacht hat. Oder ich war nur unfähig zu suchen.
  4. Hi, mein erster Beitrag ist direkt mal ein Tutorial. Da ich keine Beispiele zu “Schießen mit Raycasts, mit beachtung von Projektilgeschwindigkeit in Unity”, also einer verzögerung von abfeuern bis Einschlag, gefunden habe, möchte ich heute ein kleines Tutorial hierzu vorstellen. Es richtet sich eher an Anfänger die schon ein wenig Erfahrung haben. Ich hoffe ich habe keine zu groben Fehler gemacht und bitte um Feedback. Vorwort Da ich mich momentan mit Shooter-Mechaniken beschäftige habe ich mich mit diesem Thema mal befasst. Um das Rad nicht neu zu erfinden, hab ich erst mal Google angeworfen um zu gucken wie “Schießen in Spielen” generell gemacht wird, bzw. obs da eventuell schon was fertiges gibt. FPS-Baukästen habe ich außen vor gelassen da ich dabei nichts über die Mechaniken lerne. Da für richtige Shooter stets von Prefab-Shooting abgeraten wurde, sollte es nun mit Raycasts gemacht werden. Leider gab es dazu keine oder nur schlechte Ratschläge im Netz. Die “beste” Idee die ich hierfür gefunden habe war: Abfeuern - die Distanz messen - anhand der Distanz die Flugzeit berechnen - nach ablauf der Zeit den Raycast machen. Von Code Samples keine Spur. Hätte ich aber sowieso nicht benutzt. Andere haben empty Objects mit einem Collider durch die gegend geschossen und meinten das is anders als Prefab-Shooting. Welche Probleme bei sowas auftreten können, sieht man in diversen Prefab-Raycast-Vergleichsvideos. Also habe ich mir selbst ein System überlegt. Vermutlich kann man das noch besser/anders machen. Aber es funktioniert. In diesem Beispiel verwende ich eine fixe Projektilgeschwindigkeit, folgend “bSpeed” genannt, und lasse physikalische Einwirkungen wie Schwerkraft erstmal außen vor. Eventuell kann ich das noch ergänzen wenn Bedarf besteht. Die Theorie In meinen Überlegungen sah das so aus: Ich habe eine Waffen in der Hand, welche Projektile abfeuert (welch Überraschung). Diese Projektile fliegen dann von der Position des Abfeuerns mit der Geschwindigkeit bSpeed/Sekunde in eine bestimmte Richtung. und zwar so lange bis sie etwas treffen oder sie für das Spiel unwichtig werden. Soweit so gut. Klingt gar nicht schwer. Wie Rechnet man jetzt das Ganze aus? Es folgt ein kleiner Ausflug in die Mathematik Da wir uns in einem dreidimensionalen Koordinatensystem befinden geht das am einfachsten mit Vektoren. Falls jemand nicht weiß was Vektoren sind und wie man mit ihnen rechnet, würde ich empfehlen an dieser Stelle dieses Tutorial zu pausieren und sich schnell Vektoren anzuschauen. Vektoren sind wirklich kein Hexenwerk. Ich werde aber die einzelnen Rechnungen schrittweise ausführen damit man gut folgen kann. Also rechnen wir am besten mal ein beispiel. Ich werde die Vektoren folgend ausschreiben: (x | y | z) Gegeben sind: Postion des Abfeuerns “pos”: (6 | 2 | 1) Richtung beim abfeuern “dir”: ( 1 | 2 | 1,5) bSpeed: 3 (Strecke die das Projektil pro Sekunde zurücklegt) Man könnte die Vektoren jetzt einfach addieren um das Projektil “nach vorne” zu bringen: neue Position = “pos” + “dir” neue Position = (6 | 2 | 1) + ( 1 | 2 | 1,5) neue Position = (6 + 1 | 2 + 2 | 1 + 1,5) neue Position = (7 | 4 | 2,5) Das Problem hierbei ist, dass der Betrag eines Vektors unabhängig von der Richtung ist. deshalb kann es in diesem Kontext zu falschen Ergebnissen kommen. Der Betrag eines Vektors ist im Prinzip nichts anderes als die Hypotenuse eines Dreiecks aus dem Satz des Pythagoras. Deshalb wird er genauso berechnet. Der Betrag ist in unserem Beispiel außerdem die Strecke die dieser Vektor, also auch unser Projektil pro Sekunde in die Richtung “dir” zurücklegen würde. Zur kontrolle berechnen wir den Betrag von “dir”, um zu sehen ob der Betrag unserer definierten Strecke entspricht: betrag = wurzel(1^2 + 2^2 + 1,5^2) betrag = wurzel(1 + 4 + 2,25) betrag = 2,693 Das Projektil würde nur 2,693, statt unseren definierten 3 Längeneinheiten pro Sekunde zurücklegen. Somit wäre das Ergebnis falsch. Der Vektor (3 | 6 | 4,5) hätte zum Beispiel die gleiche Richtung wie der Vektor “dir” aber den dreifachen Betrag, was heißt, dass das Projektil auch das dreifache an Strecke pro Sekunde zurücklegen würde. Dieses Ergebnis wäre ebenso falsch. Deshalb kann man das auf diese weise nicht berechnen. Welchen Vektor müssen wir denn nun zum Vektor “pos” addieren um das Projektil 3 Längeneinheiten (bSpeed) nach vorne zu bringen? Und wie bekommen wir diesen Vektor? Gesucht ist also: Vektor “posDelta” (Dessen Betrag genau bSpeed beträgt) Dazu schauen wir, um welchen wert wir den Vektor “dir” kürzen oder verlängern müssen, dass wir genau diesen Betrag erhalten. Wie müssen nun den Betrag von “dir” in einer Gleichung auf den Wert von bSpeed bekommen, und dann anhand des Teilers den Vektor ändern dass dessen Betrag bSpeed entspricht. Alles in einzelnen Schritten: Konkrete Frage: Durch welche Zahl müssen wir den Betrag des Vektors teilen um 3 (bSpeed) zu erhalten? Also bilden wir folgende Formel: Der Betrag von “dir” beträgt wie oben errechnet 2,693: 2,693 / x = bSpeed 2,693 / x = 3 nach x auflösen 2,693 / 3 = x 0,898 = x Da für Vektoren kein Divisionsverfahren definiert ist, müssen wir diesen Teiler durch die Kehrwert-Aktion in einen Multiplikator umwandeln: 1 / 0,898 = 1,1136 diesen Wert nennen wir “deltaFactor”. Den gekürzten/verlängerten Vektor erhalten wir indem wir den Vektor mit “deltaFactor” multiplizieren. dir * deltaFactor = posDelta (1 | 2 | 1,5) * 1,1136 = posDelta (1 * 1,1136 | 2 * 1,1136 | 1,5 * 1,1136) = posDelta (1,1136 | 2,2272 | 1,6704) = posDelta Um zu kontrollieren ob wir mit diesem Vektor auch wirklich 3 Längeneinheiten pro Sekunde (bSpeed) vorankommen, berechnen wir erneut den Betrag: Betrag = wurzel(1,1136^2 + 2,2272^2 + 1,6704^2) Betrag = wurzel(1,2401 + 4,96042 + 2,79023) Betrag = wurzel(8,99075) Betrag = 2,99845 = 3 Laut Kontrolle ist unser gesuchter Vektor also “posDelta” = (1,1136 | 2,2272 | 1,6704) Wenn wir diesen Vektor “posDelta” nun zum Vektor “pos” addieren, Wandert unser Projektil 3 Längeneinheiten (bSpeed) nach vorne. neue Position = “pos” + “posDelta” neue Position = (6 | 2 | 1) + (1,1136 | 2,2272 | 1,6704) neue Position = (6 + 1,1136 | 2 + 2,2272 | 1 + 1,6704) neue Position = (7,1136 | 4,2272 | 2,6704) Jetzt haben wir die neue Position berechnet, die das Projektil nach einer komplette Sekunde Flugzeit hat. Wir müssen aber noch den Faktor Zeit einrechnen. Momentan wird immer die Strecke von einer Sekunde addiert, egal was passiert. Das heißt, dass das Projektil 3 Längeneinheiten fliegt, egal wie viel Zeit in wirklichkeit vergangen ist. Pro berechnung werden 3 Längeneinheiten addiert. Aber das Projektil soll in einer in Sekunde 3 Längeneinheiten fliegen, in einer halben aber nur 1,5 LE, in einer 0,1 Sekunden nur 0,3 LE, usw.. Deshalb brauchen wir noch den Faktor Zeit. Dieser wird einfach mit dem Vektor “posDelta” multipliziert: “posDelta” * zeit (1,1136 | 2,2272 | 1,6704) * zeit (1,1136 * time | 2,2272 * time | 1,6704 * time) Diese Rechnung muss durchgeführt werden BEVOR man “posDelta” zu “pos” addiert. Sonst hat man keine funktionierende Flugbahn, aber ein Klasse “Spieler-Abspace-Beam-System” weil sich der Character höchstwahrscheinlich wild durch die Gegend Portet (Habs noch nicht ausprobiert). Den Faktor “time” haben wir oben nicht definiert. Brauchen wir auch nicht. Denn “time” wird später dann zu Unitys Time.deltaTime. Deshalb können wir ab hier nicht mehr weiterrechnen bzw. sind fertig mit Rechnen. Den Algorithmus nochmal zusammengefasst: Gegeben: Vektor pos Vektor dir Zahl bSpeed Gesucht: Zahl deltaFactor Vektor posDelta Nicht definiert: Zahl time (in sekunden) Eigentlicher Vorgang: deltaFactor = 1/(Betrag dir / bSpeed) posDelta = dir * deltaFactor neue Position = pos + posDelta * time Zu guter Letzt müssen wir noch definieren was ein unwichtig gewordenes Projektil ist. In unserem Fall ganz einfach: Wenn man vorbei geschossen hat und die Kugel jetzt in den unendlichen Weiten des PCs rumschwirrt. Soviel zur Theorie. Wie baue ich das jetzt in meinen Code ein? Hier werde ich erstmal die Elementaren Klassen und Methoden erklären und danach alles zusammenführen. Wir brauchen eine Klasse FlyingShot: using System; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; class FlyingShot { private Vector3 position; public Vector3 Postion { get { return position; } set { position = value; } } private Vector3 direction; public Vector3 Direction { get { return direction; } set { direction = value; } } private float speed; public float Speed { get { return speed; } set { speed = value; } } private bool finished = false; public bool Finished { get { return finished; } private set { } } public Vector3 lengthPerSecond; private float maxLifeInSec = 5.0f; private float actualLivedSec = 0.0f; public FlyingShot(Vector3 position, Vector3 direction, float speed) { this.position = position; this.direction = direction; this.speed = speed; CalcPositionDelta(); } } Vektor3 positon ist die Position des Abfeuerns und entspricht unserem Vektor “pos”. Vektor3 direction -> unser “dir” von oben. Float Speed -> “bSpeed” von oben. Die Methode CalcPostionDelta berechnet unser “posDelta” von oben. Diese wird beim Erzeugen des Schusses einmal aufgerufen und der Vektor gespeichert. Wir müssen ihn nur einmal berechnen, da dieser Schuss ja pro frame die gleiche Geschwindigkeit hat und in die gleiche Richtung fliegt. Den Satz des Pythagoras berechnet Unity für uns mit Vector3.magnitude. private void CalcPositionDelta() { float div = Direction.magnitude / speed; positionDelta = new Vector3(Direction.x / div, Direction.y / div, Direction.z / div); } und mit CalcNextPosition berechnen wir pro frame die neue Position des Projektils. Wie ihr seht wird hier das “time” von oben durch Time.DeltaTime ersetzt. public void CalcNextPosition() { position = new Vector3(position.x + positionDelta.x * Time.deltaTime, position.y + positionDelta.y * Time.deltaTime, position.z + positionDelta.z * Time.deltaTime); //Ich glaub es Würde auch so gehen: // postition += postionDelta * Time.deltaTime // müsste ich mal probieren //Hier wird noch die “Lebenszeit” des Projektils berechnet CalcLifeTime(); } Die Lebenszeit des Projektils wird so berechnet: private void CalcLifeTime() { actualLivedSec += Time.deltaTime; if(actualLivedSec >= maxLifeInSec) { FinishShot(); } } Dann haben wir noch die Methode finishShot: public void FinishShot() { finished = true; } diese brauchen wir später um eingeschlagene oder unwichtig gewordene Schüsse wegzuräumen. Und dann brauchen wir noch das MonoBehavior Shooting: public class Shooting : MonoBehaviour { public Camera camera; public double fireRate; public float bSpeed; private double cooldown = 0; private bool shooting = false; private List<FlyingShot> activeShots; private List<RaycastHit> hitShots; void Awake() { activeShots = new List<FlyingShot>(); hitShots = new List<RaycastHit>(); } // Use this for initialization void Start() { } // Update is called once per frame void Update() { Shoot(); } } Die obersten drei Variablen werden im Editor befüllt. Die 2 Listen sind zum Speichern der noch nicht weg geräumten Schüsse. Die Methode InitShot Erzeugt nur einen neuen fliegenden Schuss und speichert ihn in der Liste. private void InitShot() { activeShots.Add(new FlyingShot(camera.transform.position, camera.transform.forward, bSpeed)); } CheckTarget führt den eigentlichen Raycast aus. Der erzeugte FlyingShot wird direkt bei der Kamera erstellt. Normalerweise würde ich dafür extra nochmal ein Objekt vorne an der Waffe anbringen. Und dann mit einem zweiten Raycast dann noch die Richtung angleichen (Siehe das Raycast-Shooting Tutorial von der Unity Website). Aber das würde den Code für dieses Beispiel nur aufblähen. Jedenfalls wird dann von diesem FlyingShot der Raycast ausgeführt. Falls dieser nichts trifft, wird eine neue Position berechnet und im nächsten Frame wieder Geprüft. Um zu sehen wie der Schuss durch die Gegend fliegt, kann man noch die DrawRay-Zeile entkommentieren. Dann sieht man in der Sceneview einen grünen Laserstrahl. Bei einem Treffer wird dann der RayCastHit in die Hit-Liste gelegt. private Transform CheckTarget(FlyingShot shot, out RaycastHit hit) { if (Physics.Raycast(shot.Postion, shot.Direction, out hit, shot.Speed * Time.deltaTime)) { Debug.Log("HIT"); shot.FinishShot(); return hit.transform; } else { shot.calcNextPosition(); //Debug.DrawRay(shot.Postion, shot.Direction * Time.deltaTime, Color.green, 1.0f); } return null; } CalcShotPostions führt pro Frame CheckTarget aus. private void CalcShotPostions() { activeShots.ForEach(delegate (FlyingShot shot) { RaycastHit hit; Transform trans = CheckTarget(shot, out hit); if (hit.transform != null) { Debug.Log("Adding hit"); hitShots.Add(hit); } }); } Und CheckForHits verarbeitet dann alle Hits die in der Liste liegen und schmeißt sie hinterher raus. Dies ist dann auch der Punkt an dem ihr eure Events einbauen müsst (z.B. Damage, Destroyen, etc.). Ich habe zum Beispiel eine weitere Klasse WLCharacter der ich Schaden zuweisen kann. private void CheckForHits() { hitShots.ForEach(delegate (RaycastHit hit) { Transform target = hit.transform; print("Target" + target.ToString()); WLCharacter enemy = CheckIfEnemy(target); if (enemy != null) { Hit(enemy); }else { } //spawnBulletHole(hit); hitShots.Remove(hit); }); } Alles zusammen dann sieht so aus: using UnityEngine; using System.Collections; using System.Collections.Generic; [RequireComponent(typeof(AudioSource))] public class Shooting : MonoBehaviour { public Camera camera; public double fireRate; public float bSpeed; private double cooldown = 0; public AudioClip impact; private AudioSource audio; private Animation anim; private bool shooting = false; public Transform muzzleFlash; public Transform muzzleSpawn; public GameObject holePrefab; private List<FlyingShot> activeShots; private List<RaycastHit> hitShots; // Use this for initialization void Start() { audio = GetComponent<AudioSource>(); anim = GetComponent<Animation>(); activeShots = new List<FlyingShot>(); hitShots = new List<RaycastHit>(); } // Update is called once per frame void Update() { Shoot(); } private void Shoot() { cooldown = calcCooldown(); CheckForHits(); CalcShotPostions(); CleanShotList(); if (AllowedToShoot()) { if (Input.GetAxisRaw("Fire1") == 1) { playVisualEffekts(); cooldown = 1000 / fireRate; InitShot(); } else { anim.Stop(); shooting = false; } } } private WLCharacter CheckIfEnemy(Transform target) { WLCharacter wlchar = target.GetComponent<WLCharacter>(); if (wlchar != null) { string frac = wlchar.Fraction; if (fac.Equals("bandits")) { Debug.Log("faction is " + frac); return wlchar; } } return null; } private void Hit(WLCharacter enemy) { double damage = enemy.getDamage(10d); Debug.Log(damage + " points of damage are dealed"); } private bool AllowedToShoot() { return cooldown <= 0; } private double calcCooldown() { if (cooldown <= 0) { return cooldown; } else { double tmpCooldown = cooldown - Time.deltaTime * 1000; return tmpCooldown; } } private void playVisualEffekts() { audio.PlayOneShot(impact, 0.7F); Object tempMuzzle = Instantiate(muzzleFlash, muzzleSpawn.position, muzzleSpawn.rotation); ((Transform)tempMuzzle).parent = muzzleSpawn; if (!shooting) { anim.Play("GunShaking"); shooting = true; } } private void spawnBulletHole(RaycastHit hit) { GameObject hole = (GameObject)Instantiate(holePrefab, hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal)); hole.transform.SetParent(hit.transform); } private void InitShot() { activeShots.Add(new FlyingShot(camera.transform.position, camera.transform.forward, bSpeed)); } private void CalcShotPostions() { activeShots.ForEach(delegate (FlyingShot shot) { RaycastHit hit; Transform trans = CheckTarget(shot, out hit); if (hit.transform != null) { Debug.Log("Adding hit"); hitShots.Add(hit); } }); } private Transform CheckTarget(FlyingShot shot, out RaycastHit hit) { if (Physics.Raycast(shot.Postion, shot.Direction, out hit, shot.Speed)) { Debug.Log("HIT"); shot.FinishShot(); return hit.transform; } else { shot.calcNextPosition(); //Debug.DrawRay(shot.Postion, shot.Direction, Color.green, 1.0f); } return null; } private void CleanShotList() { activeShots.ForEach(delegate (FlyingShot shot) { if (shot.Finished) { activeShots.Remove(shot); } }); } private void CheckForHits() { hitShots.ForEach(delegate (RaycastHit hit) { Transform target = hit.transform; print("Target" + target.ToString()); WLCharacter enemy = CheckIfEnemy(target); if (enemy != null) { Hit(enemy); }else { } spawnBulletHole(hit); hitShots.Remove(hit); }); } } Ich prüfe noch ab ob der Spieler in diesem Frame überhaupt schießen darf. Dafür habe ich einen simplen Timer eingebaut. private bool AllowedToShoot() { return cooldown <= 0; } private double CalcCooldown() { if (cooldown <= 0) { return cooldown; } else { double tmpCooldown = cooldown - Time.deltaTime * 1000; return tmpCooldown; } } Außerdem habe ich noch eine Methode “PlayVisualEffects” die mir eben “Visuelle Effekte” abspielt. Zum Beispiel das feuer aus dem Lauf und so. Für den Sound habe ich auch noch eine Methode. Aber auf diese Dinge will ich jetzt nicht eingehen. Da gibts andere Tutorials. Fazit Ich hoffe das Tutorial ist verständlich und hilft euch weiter. Ein Tutorials zu machen ist sehr viel Arbeit und da es mein erstes ist hoffe ich dass es nicht allzu wirr ist. Bei Coding- oder inhaltlichen Fehlern einfach kommentieren. Viel Spaß beim Basteln!