Jump to content
Unity Insider Forum

Sneyke

Members
  • Posts

    17
  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

Sneyke's Achievements

Member

Member (2/3)

3

Reputation

  1. Ich hab mir mal den Thread komplett durchgelesen. Richtig cool. Gibts eigentlich mal wieder was neues? Das letzte Update ist schon ein Monat her.
  2. Eine Möglichkeit wäre den Schuss lokal durchzuführen und dann Server-seitig zu prüfen. Also erstmal abfeuern und etwas treffen. Wenn etwas relevantes getroffen wurde, überprüft der Server ob alles seine Richtigkeit hat, also ob es auch für den Server und für den/die getroffenen Spieler plausibel ist (Prüfen ob der getroffene Spiele nicht schon 10 Meter weiter gerannt ist, und der Schütze nur aufgrund eines Lags getroffen hat). In der Zwischenzeit kannst du ja lokal den Dingen soweit ihren Lauf lassen (Getroffener Spieler bekommt schaden, Animation, usw.). Falls der Server dann der Meinung ist, dass alles fair und richtig ist, kann er dann die restlichen Clients benachrichtigen. Falls nicht (sei es wegen Lags oder Hacks), wird der Ausgangs-Client kurz "zurückgespult". Also im Prinzip spielt dann jeder sein eigenes Spiel in Echtzeit abgekoppelt vom Rest. Falls ich aber lokal vorbeiballer, und das Projektil aufgrund der Verzögerung dann aber "in Wirklichkeit" doch etwas trifft, wird das einfach ignoriert. Weil das ja nichts mit meinem selbst gezielten Schuss zu tun hat. Das wäre auch generell nicht richtig nachvollziehbar. Es zählt nur das was der Server als Fair und Richtig deklariert. So ein ähnliches Verhalten kann man ja bei Netzwerkshootern beobachten.
  3. Coole Sache. Was vielleicht interessant wäre, wäre noch der vergleich zu z.B. einfach 1000000 Additionen auf deinem System. Weil ich weis jetzt nicht ob 150ms für Normalize viel sind oder nicht. Ist ja eigentlich ein nur durch seine länge teilen.
  4. 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.
  5. 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.
  6. 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!
×
×
  • Create New...