Jump to content
Unity Insider Forum

Raycast-Shooting mit beachtung von Projektilgeschwindigkeit


Sneyke

Recommended Posts

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!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das ist mal ein erfrischend anderer erster Post in diesem Forum. Sich mit einem Tutorial vorzustellen, macht schon was her. Willkommen.

Zu deinem Code: schön und verständlich erklärt. Wenn ich alles richtig verstanden habe, geht es vor allem darum, einen Raycast in die Schussrichtung abzufeuern mit einer Länge, die der aktuellen Flugzeit des Projektils entspricht. Eine Fehlermöglichkeit sehe ich darin, dass ein Gegner in die Schussbahn läuft, nachdem das Projektil bereits an der Position vorbei ist. Der Raycast würde den Gegner trotzdem treffen. Oder habe ich da was übersehen?

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für die Antwort. Hab mir auch Mühe gegeben :D. 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.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Stimme Hrungdak zu - wir haben länger keine neuen Tutorials mehr gesehen, danke!

Da du nach Feedback gefragt hast; hier sind ein, zwei Dinge, die mir aufgefallen sind.

    void Start()
    {
        audio = GetComponent<AudioSource>();
        anim = GetComponent<Animation>();

        activeShots = new List<FlyingShot>();
        hitShots = new List<RaycastHit>();

    }

Jede dieser vier Zeilen gehört eher in Awake als in Start. Awake ist für solcherlei Initialisierungen, während in Start schon Logik ausgeführt wird (z.B. das Ändern der Farbe eines Objekts). Da diese erste Logik eventuell voraussetzt, dass andere Objekte bereits mit GetComponent und dergleichen initialisiert sind, sollte die Initialisierung vor allen Starts ausgeführt werden - also in Awake.

 

        if (cooldown <= 0)
        {
            return true;
        }
        else
        {
            return false;
        }

Das hier lässt sich kürzen zu

return cooldown <= 0;

 

Dann ist mir aufgefallen, dass cooldown bei dir ein double ist, das Millisekunden darstellt. Allerdings veränderst du den Wert mithilfe von Time.deltaTime und konvertierst die Sekunden durch * 1000. Dabei generierst du aber natürlich keine zusätzliche Genauigkeit. cooldown als float zu deklarieren und sich die Konversionen zu sparen ist hier durchaus sinnvoll.

Wo wir außerdem bei doubles sind:

private void CalcPositionDelta()
    {
        double deltaFactor = Math.Sqrt((Direction.x * Direction.x) + (Direction.y * Direction.y) + (Direction.z * Direction.z)) / speed;
        float div = (float) deltaFactor;

        positionDelta = new Vector3(Direction.x / div, Direction.y / div, Direction.z / div);
        
    }

Hier schreibst du per Hand Vektormathematik, die Unity schon mitbringt :)

Den Satz des Pythagoras hat Unity bereits über Vector3.magnitude eingebaut. Du kannst also stattdessen schreiben:

private void CalcPositionDelta()
{
  float div = Direction.magnitude / speed;

  positionDelta = new Vector3(Direction.x / div, Direction.y / div, Direction.z / div);
}

Außerdem kann Unity Vektoren und Skalare miteinander verrechnen. Es geht also noch kürzer:

private void CalcPositionDelta()
{
  float div = Direction.magnitude / speed;

  positionDelta = Direction / div;
}

Und dann fällt einem leichter auf, dass hier Direction durch seine eigene Länge geteilt (also normalisiert) wird und dann mit speed multipliziert wird. Daher geht es noch kürzer:

private void CalcPositionDelta()
{
  positionDelta = Direction.normalized * speed;
}

 

Dann hast du noch inkonsistente Groß- und Kleinschreibung - einige deiner Methoden sind am Anfang klein geschrieben.

Aber von alledem abgesehen scheint das gezeigte Konzept ganz solide zu sein. Ich bin kein großer Fan von Strings zum Identifizieren (hier der Fraktion (engl. übrigens Faction)), aber da sind wir schon wieder bei den Geschmackssachen. Eine Menge zu lernen gibt es für Lernende in diesem Tutorial allemal :)

Link zu diesem Kommentar
Auf anderen Seiten teilen

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 :D

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.

 

vor 16 Stunden schrieb Sascha:

(hier der Fraktion (engl. übrigens Faction))

Ohne Worte... Sehr Peinlich :D

Cool dass du dir die Mühe gemacht hast. Ich werde die Sachen übernehmen und dann das Tut nochmal überarbeiten.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...