Jump to content
Unity Insider Forum

Einfache Skript für Hitboxen funktioniert nicht korrekt


strudi

Recommended Posts

Hallo, ich habe ein sehr einfaches und provisorisches Skript für eine Hitbox erstellt, aber ich fürchte dass ich wieder einmal irgendetwas falsch mache. ;)

 

Ich habe einen Cube-Gegner erstellt und vorne 3 weitere flache Cubes hinzugefügt die als Hitboxen dienen sollen. Hier ein Bild damit man sich das vorstellen kann:

 

post-678-013076500 1304351575_thumb.jpg

 

Wenn ich dann auf die Hitbox schiesse, wird es meist nicht erkannt, auch wenn ich direkt davor stehe und kerzengerade auf die Box schiesse. Habe auch schon versucht den Boxcollider zu vergrößern, was aber ebenfalls nicht wirklich geholfen hat. Hier mal meine kurzen und einfachen Skripts dazu:

 

Die HitBoxen werden im Gegnerskript als Array gespeichert (warum auch immer ich das gemacht habe?! ;)) Zur Zeit mal zu Testzwecken:

 

var hitZones : GameObject[];
static var playerHitZones : GameObject[];

function Awake(){

playerHitZones = hitZones;

playerHitZones[0] = GameObject.FindWithTag("hitZoneKopf");


playerHitZones[1] = GameObject.FindWithTag("hitZoneBrust");


playerHitZones[2] = GameObject.FindWithTag("hitZoneBeine");
}

 

Skript zum Projektil:

function OnTriggerEnter (hit : Collider) 
{
if(hit.tag != "Player" && hit.tag != "Schuss" && hit.tag != "Enemy" && hit.tag != "hitZoneBrust" && hit.tag != "hitZoneBeine" && hit.tag == "hitZoneKopf")
{
hit.SendMessage("SchadenNehmen", 10.0, SendMessageOptions.DontRequireReceiver);
Destroy(gameObject);
Destroy(hit.transform.root.gameObject);
//print("Getroffen"+ EnemyTestScript.playerHitZones);
}

}

 

Testskript für eine HitBox:

 

function SchadenNehmen(schaden : float)
{

if(schaden == 0)
return;
else
GUIPlayer.meinePunkte += 100;
}

 

PlayerSchiessen:

 

var spielerProjektil : Transform;
var projektilGeschwindigkeit : float = 8000.0;

function Update()
{
newUpdate();
}


function newUpdate () 
{
if(Input.GetButton("Fire1"))
{
	var schuss = Instantiate(spielerProjektil, transform.position, Quaternion.identity);
	schuss.rigidbody.AddForce(transform.forward*projektilGeschwindigkeit);

}
}

 

Wie gesagt, alles sehr einfach gehalten bis jetzt, da es nur zu Testzwecken ist. GUIPlayer.meinePunkte += 100; das wird halt zu den Punkten im PlayerScript addiert, sobald man die Hitbox getroffen hat.

Wie gesagt, es ist halt derzeit reine Glückssache ob man die Hitbox trifft oder nicht, auch wenn man direkt davor steht und ich komme leider nicht darauf, warum das so ist. Sobald man die Hitbox mal getroffen hat funktioniert auch alles wunderbar. Die Punkte werden addiert und das GameObject verschwindet auch. Nur verstehe ich nicht, warum ich teilweise so oft drauf schiessen muss, damit man die Box trifft und manchmal trifft man sie sogar gleich beim ersten Mal. Mir ist aufgefallen, dass es auch teilweise vom Winkel abhängig ist, ob man trifft oder nicht. Aber warum? ;)

 

Würde mich freuen, wenn mir jemand helfen könnte.

 

Vielen lieben Dank!

 

lg

strudi

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das hat was mit der Schußgeschwindigkeit und der Framerate zu tun. Das ist ein bekanntes Problem und wurde hier im Forum schon erörtert. Ich glaube es gab sogar ein Workarround dazu.

Wenn kleine Objekte mit hoher geschwindigkeit auf einen Collider treffen, kann es sein, dass das Objekt schon durch ist bevor die Berechnung das überhaupt registriert hat.

Bei größeren Kollidern kannste mal onTriggerStay() probieren. Dieses Enter ist ja wirklich nur kurz, stay dagegen etwas länger.

Aber such mal im Forum. Da gibts schon andere Ansätze. :)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Vielen Dank für die Hilfe!

 

OnTriggerStay funktioniert bei mir eigenartigerweise gar nicht. Da wird der Teil in der Funktion gar nicht ausgeführt. Ich belasse es in der Zwischenzeit bei OnTriggerEnter. Habe die Projektilgeschwindigkeit etwas zurück gedreht und momentan funktioniert es schon besser. :)

Wäre es besser, das Schießen per Raycast zu realisieren? Bin der Meinung so etwas schon mal gelesen zu haben und das dies eine bessere Variante sein soll?!

 

@Sascha: Ich glaub da kann wohl so ziemlich alles optimiert bzw. vereinfacht werden, oder? ;)

 

Danke!

 

lg

strudi

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habe ein weiteres Problem, ich poste mal den Code bzw den erweiterten Code von oben:

 

Projektil:

 

function OnTriggerEnter (hit : Collider) 
{
if(hit.tag != "Player" && hit.tag != "Schuss" && hit.tag != "Enemy" && hit.tag != "hitZoneBrust" && hit.tag != "hitZoneBeine" && hit.tag == "hitZoneKopf")
{
hit.SendMessage("SchadenNehmen", 100, SendMessageOptions.DontRequireReceiver);
Destroy(hit.transform.root.gameObject);
}

if(hit.tag != "Player" && hit.tag != "Schuss" && hit.tag != "Enemy" && hit.tag != "hitZoneKopf" && hit.tag != "hitZoneBeine" && hit.tag == "hitZoneBrust")
{
hit.SendMessage("SchadenNehmen", 50, SendMessageOptions.DontRequireReceiver);
Destroy(hit.transform.root.gameObject);
}

if(hit.tag != "Player" && hit.tag != "Schuss" && hit.tag != "Enemy" && hit.tag != "hitZoneKopf" && hit.tag != "hitZoneBrust" && hit.tag == "hitZoneBeine")
{
hit.SendMessage("SchadenNehmen",25, SendMessageOptions.DontRequireReceiver);
Destroy(hit.transform.root.gameObject);
}
}

function Update(){

Destroy(transform.gameObject, 2);

}

 

HitBoxen:

var Test : GUIText;
var textFont : Font;

var clone : GUIText;

function SchadenNehmen(schaden : int)
{
if(schaden == 0)
return;

else if(schaden == 100){

GUIPlayer.meinePunkte += schaden;
GUIPlayer.spielZeit +=5;

clone = Instantiate(Test);

clone.guiText.alignment = TextAlignment.Center;
clone.guiText.font = textFont;
clone.guiText.fontSize = 20;
clone.guiText.material.color = Color.yellow;
clone.guiText.text = schaden.ToString()+" Punkte\n" + "Kopfschuss";

Destroy(clone, 1);
}
else if(schaden == 50){
GUIPlayer.meinePunkte += schaden;
GUIPlayer.spielZeit +=3;

clone = Instantiate(Test);

clone.guiText.alignment = TextAlignment.Center;
clone.guiText.font = textFont;
clone.guiText.fontSize = 20;
clone.guiText.material.color = Color.yellow;
clone.guiText.text = schaden.ToString()+" Punkte\n"+"Glatter Durchschuss";

Destroy(clone, 1);
}
else if(schaden == 25){
GUIPlayer.meinePunkte += schaden;
GUIPlayer.spielZeit +=1;

clone = Instantiate(Test);

clone.guiText.alignment = TextAlignment.Center;
clone.guiText.font = textFont;
clone.guiText.fontSize = 20;
clone.guiText.material.color = Color.yellow;
clone.guiText.text = schaden.ToString()+" Punkte\n"+"Beine ab";

Destroy(clone, 1);
}
}

 

Mein Problem ist, dass die Kugel die ich schiesse, die Hitbox zweimal trifft, somit verdoppelt sich der Schaden und somit auch die Punkte die ich anzeigen will. Weiß jemand warum das so ist? Also warum die Kugel die Hitboxen zweimal trifft? Und wenn ich den Schaden den ich bei den Punkten addiere, dann durch 2 teile, ist der Punktestand korrekt bis auf den letzten Wert mit 25. Dieser ist doppelt logischerweise 50, aber wenn ich den durch 2 teile, wird bei der Punkteanzeige nur 24 angezeigt.

 

Hoffe es kann mir jemand nochmal helfen. Vielen Dank!

 

lg

strudi

Link zu diesem Kommentar
Auf anderen Seiten teilen

Also, die meiste Schreibarbeit sparst Du dir mit besserer Kenntnis von bbolschen Ausdrücken.

Bei deiner Zeile

if(hit.tag != "Player" && hit.tag != "Schuss" && hit.tag != "Enemy" && hit.tag != "hitZoneKopf" && hit.tag != "hitZoneBrust" && hit.tag == "hitZoneBeine")

hast Du so viel Redundanz drin wie selten einer.

 

Wenn hit.tag == "hitZoneBeine" ist, dann kannst Du daraus doch automatisch schließen, dass hit.tag nichts anderes ist! Es kann ja nicht mehrere Werte gleichzeitig haben!

Ersetze also einfach die ganze Zeile durch

if(hit.tag == "hitZoneBeine")

 

Das ist erstmal ganz wichtig für schönen Code.

 

Dann aber mal was viel wichtigeres: Tags werden auf gar keinen Fall für einzelne Objekte benutzt, um sie zu identifizieren.

Dafür benutzt man Referenzen, die hin und her gereicht werden.

Du benutzt ja bereits den Collider, der bei deinem OnTriggerEnter-Event übergeben wird.

An dieser Stelle sollte man ein Skript haben, dass alle Hitboxen haben, und den Collider fragen, ob sein GameObject so eins hat.

Wenn ja, dann frage diesen Skript, wie viel Schaden der Treffer macht (ist in der Skriptinstanz als Variable definiert).

 

Aber bevor wir da in die Tiefe gehen: Wenn Du Raycasts benutzt, kannst Du damit natürlich nur InstantHit-Waffen machen, also Waffen, die in dem Moment treffen, wo sie abgefeuert werden (keine Raketen z.B.).

Allerdings scheint das genau das zu sein, was Du brauchst, deiner Projektilgeschwindigkeit nach.

Du solltest also definitiv Raycasts benutzten, weil sie immer zu 100% genau sind, egal, wie dünn dien Collider ist.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dafür benutzt man Referenzen, die hin und her gereicht werden.

Du benutzt ja bereits den Collider, der bei deinem OnTriggerEnter-Event übergeben wird.

An dieser Stelle sollte man ein Skript haben, dass alle Hitboxen haben, und den Collider fragen, ob sein GameObject so eins hat.

Wenn ja, dann frage diesen Skript, wie viel Schaden der Treffer macht (ist in der Skriptinstanz als Variable definiert).

 

Herzlichen Dank für die Hilfe!

 

Ob ich das mit den Referenzen so richtig verstanden habe, weiß ich noch nicht genau. ;)

Also sollte ich ein Skript machen und darin sämtliche Hitboxen die ein zB Gegner hat und dort eintragen? Mit sämtlichen Schadens- bzw. Punkteberechnungen?

Welches Gameobject erhält dann das Skript? Der Gegner?

 

Werde mich mal hinsetzen und probieren ob ich das so schaffe. ;)

 

Vielen Dank nochmals!

 

lg

strudi

Link zu diesem Kommentar
Auf anderen Seiten teilen

Du machst wie viele andere auch den Fehler, anzunehmen, man sollte in Unity mit möglichst wenig Scripts/Scriptinstanzen arbeiten.

Das Gegenteil ist der Fall.

 

Jede deiner Hitboxen ist ein eigenes Gameobject, und jede erhält seine eigene Instanz des Hitbox-Scripts. (In jedes Objekt ein Mal rein ziehen).

Und jedes dieser Scripts kennst seinen Schadensmultiplikator und gibt dann ggf. an das Spieler-Objekt, das die Gesundheit verwaltet, eine Schadensmeldung weiter.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hmm, vielen Dank!

 

Das Skript für die Hitboxen (weiter oben) habe ich eh auf jede einzelne Hitbox reingezogen gehabt. Oder meinst du für jede einzelne Hitbox (oder andere Gameobjects) ein eigens Skript erstellen (mit anderen Werten)?

 

Ich werds mir mal ansehen, vielen Dank! ;)

 

lg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Meinst du, dass man dann die Werte über den Inspector verändert oder per Code? Wahrscheinlich über den Inspector, denn wenn ich die Werte über den Code ändere, ändert sich dieser für jedes GameObject.

 

Mein Problem ist, dass ich den Gegner inkl. Hitboxen per Prefab in die Welt setze. Da wüsste ich nicht, wie ich es sonst lösen sollte.

 

Ich bedanke mich trotzdem für die Hilfe und die Geduld mit mir! ;)

 

lg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Script für die Hitboxen, Name egal:

var damageMultiplier : float = 1;
@SerializeField
private var health : CharacterHealth;

function ApplyDamage(amount : float)
{
health.ReceivedDamage(amount * damageMultiplier);
}

 

Script für eine Figur namens CharacterHealth.js:

private var health : float = 100;

//Funktion heisst NICHT ApplyDamage(), da nur die Hitboxen darauf reagieren sollen.
function ReceivedDamage(amount : float)
{
health -= amount;
if(health <= 0)
{
 // TODO: Sterben
}
}

 

Script für Waffe, Name egal:

var damagePerShot : float = 1;
// TODO: Mehr Zeugs, wie Schüsse pro Sekunde, Waffe Verziehen, was immer Du willst

function Update()
{
// TODO: Durch irgendeinen Input und unter bestimmten Bedingungen Shoot() auslösen
}

function Shoot()
{
var hit : RaycastHit;
if(Physics.Raycast(transform.position, transform.forward, hit, 1000))
{
 hit.collider.SendMessage("ApplyDamage", damagePerShot, SendMessageOptions.DontRequireReceiver); //Löse ApplyDamage() aus, wenn der getroffene Collider ein Script hat, dass diese Funktion hat
}
}

 

CharacterHealth.js kannst Du umbenennen, solange du das Hitbox-Script entsprechend änderst.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Vielen Dank für den Lösungsansatz, werde mich nachher gleich ransetzen und mir das genauer ansehen.

 

Sieht deutlich aufgeräumter aus als mein Code-Wirr-Warr! ;) Aber wie gesagt, bin noch nicht so geübt im programmieren. Da muss ich mir noch eine Menge ansehen bzw. verstehen wo man was wie am besten einsetzt!

 

Danke schön!

 

lg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hab das jetz mal ausprobiert, aber habe immer noch Probleme. Mein Code sieht folgendermaßen aus:

 

PlayerSchiessen.js

 

var damagePerShot : float = 10;

function Update()
{
if(Input.GetButtonDown("Fire1"))
{
	Shoot();
}
//newUpdate();
}

function Shoot()
{
var hit: RaycastHit;
if(Physics.Raycast(transform.position, transform.forward, hit, 1000))
{
	hit.collider.SendMessage("ApplyDamage", damagePerShot, SendMessageOptions.DontRequireReceiver);

}
}

 

EnemyTestScript.js

 

private var health : float = 15;

function ReceivedDamage(amount : float)
{
 health -= amount;
 if(health <= 0)
{
	Destroy(transform.root.gameObject);
}

}

 

Hitzones.js

 

var Test : GUIText;
var textFont : Font;

var clone : GUIText;
var zeitPlus : int;


var damageMultiplier : float = 1;
var damage : float;
@SerializeField
private var health : EnemyTestScript;

function ApplyDamage(amount : float)
{
health.ReceivedDamage(amount * damageMultiplier);
damage = amount * damageMultiplier;

if(damage <= 100){

	GUIPlayer.meinePunkte += damage;
	GUIPlayer.spielZeit += zeitPlus;

	clone = Instantiate(Test);

	clone.guiText.alignment = TextAlignment.Center;
	clone.guiText.font = textFont;
	clone.guiText.fontSize = 20;
	clone.guiText.material.color = Color.yellow;
	clone.guiText.text = damage+" Punkte\n" + "Kopfschuss";

	Destroy(clone, 1);
}
else if(damage <= 50){

	GUIPlayer.meinePunkte += damage;
	GUIPlayer.spielZeit += zeitPlus;
	clone = Instantiate(Test);

	clone.guiText.alignment = TextAlignment.Center;
	clone.guiText.font = textFont;
	clone.guiText.fontSize = 20;
	clone.guiText.material.color = Color.yellow;
	clone.guiText.text = damage+" Punkte\n"+"Glatter Durchschuss";

	Destroy(clone, 1);
}
else if(damage <= 20){

	GUIPlayer.meinePunkte += damage;
	GUIPlayer.spielZeit += zeitPlus;

	clone = Instantiate(Test);

	clone.guiText.alignment = TextAlignment.Center;
	clone.guiText.font = textFont;
	clone.guiText.fontSize = 20;
	clone.guiText.material.color = Color.yellow;
	clone.guiText.text = damage+" Punkte\n"+"Beine ab";

	Destroy(clone, 1);
}
}

 

Ich habe im Inspector den DamagePerShot eingestellt und auch den Multiplikator. DamagePerShot ist 10 und der Multiplikator je nach Hitzone auf 10, 5 und 2 eingestellt. Und das Leben des Gegner ist auf 15 eingestellt. Es sollte doch eigentlich mit diesen Werten immer <= 0 rauskommen, oder irre ich mich da jetzt?! Daher sollte das GameObject immer gelöscht werden sobald ein Schuss irgendwo trifft. Mein Problem ist jetzt aber wieder, das ich trotzdem mehrmals auf den Gegner schießen muss, bis er verschwindet. Teilweise verschwindet er gleich beim ersten Schuss und teilweise muss ich ein paar Mal drauf schiessen bis er gelöscht wird, obwohl eigentlich das Leben des Gegner immer <= 0 ist.

 

Kann mir da jemand helfen? Was mache ich falsch? ^^

 

Ansonsten funktioniert es wunderbar! Danke

 

lg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...