Jump to content
Unity Insider Forum

Wir bauen uns ein Space Shoot em Up [Teil 11 - Items]


Mark

Recommended Posts

Nun beschäftigen wir uns mit Items. Wir wollen in der Lage sein Gegenstände aufzunehmen und anzuwenden. Da nicht jeder sondern nur der Spieler die Items aufsammeln soll werden wir dies nur auf den Spieler begrenzen. Wir fangen damit an dass wir uns ein neues C# Script mit dem Namen "Item" erstellen und die Klasse auch gleich abstract machen. Da wir immer von dieser Klasse ableiten müssen um einen Effekt zu haben.

 

Erzeugt nun eine neue Methode abstrakte Methode:

 

protected abstract bool Pickup(GameObject picker);

 

Die Pickup Methode werden wir aufrufen wenn etwas mit einem PlayerController Script unser Item berührt. Wenn die Pickup Methode true zurück gibt werden wir das Item zerstören, da es als aufgenommen gilt, ansonsten belassen wir das Item wie es ist.

 

Wir testen ob unser Item berührt wurde nicht wie bei der Projectile Klasse mithilfe der OnColliderEnter Methode sondern mit einer ähnlichen Methode, genannt OnTriggerEnter. Der Unterschied zwischen beiden ist das unser Item niemanden behindern soll, quasi für Kollisionen im herköömlichen Physikalischen Sinne unsichtbar ist. Wir erreichen dies indem wir unserem GameObjekt eine Collider Komponente geben und ihr im Inspektor sagen dass sie ein Trigger (Auslöser) ist.

 

So sieht diese Methode aus:

void OnTriggerEnter(Collider other)
{
}

 

Wir müssen nun testen ob das GameObjekt von other einen PlayerController besitzt und wenn ja dann führen wir die am Anfang des Tutorials besprochenen Aktionen durch:

 

if (other.gameObject.GetComponent<PlayerController>())
{
if (Pickup(other.gameObject))
{
	DestroyObject(gameObject);
}
}

 

Wir können uns nun eine Reihe von zusätzlichen Item Klassen schreiben, wir fangen damit an indem wir uns eine Klasse schreiben die uns ein Schutzschild schenkt, wir erstellen uns dazu das C# Script "ShildItem".

 

Wir überladen also unsere Pickup Methode und schauen ob unser picker GameObject (also unser Spieler Raumschiff) bereits die Schild Komponente besitzt. Wenn der Spieler das Schild schon hat, werden wir es einfach wieder aufladen wenn nötig und true zurück geben. Ansonsten geben wir dem Spieler Raumschiff diese Komponente und geben ebenfalls true zurück.

 

protected override bool Pickup(GameObject picker)
{
var oldShild = picker.GetComponent<Shild>();
if (oldShild)
{
	if (oldShild.CurrentShild < oldShild.MaxShild)
	{
		oldShild.CurrentShild = oldShild.MaxShild;
		return true;
	}
}
else
{
	picker.AddComponent<Shild>();
	return true;
}

return false;
}

 

Ihr könnt für die Shild Komponente falls ihr sie noch nicht habt (Teil 5) diesen Code nehmen:

 

using UnityEngine;
using System.Collections;

public class Shild : MonoBehaviour
{
public int MaxShild = 20;
public float CurrentShild = 20;
public float RegenerationRate = 1.0f;

private Destructable destructable;

void Start()
{
	destructable = GetComponent<Destructable>();
	destructable.OnPreDamage += OnPreDamage;
}

void Update()
{
	CurrentShild += Time.deltaTime * RegenerationRate;
	CurrentShild = Mathf.Min(CurrentShild, MaxShild);
}

void OnPreDamage(Damage damage)
{
	var savedDamage = Mathf.Min(damage.Amount, CurrentShild);

	CurrentShild -= savedDamage;
	damage.Amount -= savedDamage;
}	  
}

 

Um zu testen ob das Item auch etwas macht, könnt ihr einfach eine kleine Sphere in die Welt platzieren und ihr das ShildItem (nicht Shild) Script zuweisen. Vergesst dabei nicht bei dem Collider "Is Trigger" auf angehakt zu stellen.

 

Ein weiteres Item wäre ein Item welches unser Schiff heilt:

 

protected override bool Pickup(GameObject picker)
{
var destructable = picker.GetComponent<Destructable>();
if (destructable.CurrentHealthPoints < destructable.MaxHealthPoints)
{
	destructable.CurrentHealthPoints = destructable.MaxHealthPoints;
	return true;
}

return false;
}

 

Sehr einfach das ganze. Wie wäre es nun mit etwas interessantem? Neue Waffen! Wir erzeugen uns dafür wieder ein Script und leiten es wie bisher von Item ab.

 

Zusätzlich zur Pickup Methode fügen wir aber noch eine Variable hinzu:

 

public Weapon WeaponPrefab;

 

Diese werden wir verwenden um all unsere Waffenslots mit diesem Prefab zu bestücken. WaffenSlots müssen wir aber noch irgendwie identifizieren können. Dazu erstellen wir uns fix ein weiteres Script ohne nennenswerten Inhalt und nennen es einfach nur "WeaponSlot":

 

using UnityEngine;
using System.Collections;

public class WeaponSlot : MonoBehaviour {

}

 

Die Klasse ist leer weil wir sie nur benutzen um einen Slot zu identifizieren, wir können dazu getComponentssInChildren verwenden so wie wir es bereits beim WeaponController getan haben. Platziert dieses Script also auf alle Unter GameObjekte des Spieler Raumschiffes.

 

Nun wieder zurück zum WeaponItem Script. Es wird Zeit die Pickup Methode zu füllen.

 

protected override bool Pickup(GameObject picker)
{
var slots = picker.GetComponentsInChildren<WeaponSlot>();
foreach (var slot in slots)
{
	foreach (Transform child in slot.transform)
	{
		DestroyObject(child.gameObject);
	}

	var newWeapon = (Weapon)Instantiate(WeaponPrefab, slot.transform.position, slot.transform.rotation);
	newWeapon.transform.parent = slot.transform;
}

picker.GetComponent<WeaponController>().NotifyWeaponChange();

return true;
}

 

Zuerst holen wir uns alle WeaponSlot Komponenten im picker GameObjekt und Unterobjekten. Bei jedem gefundenen Slot löschen wir alle Unter GameObjekte in ihnen.

Anschließend werden wir uns eine neue Waffe erzeugen und sie dort platzieren wo der Slot zu finden ist, ausserdem setzen wir die neue Waffe als Unterobjekt des Slots.

 

Wir ihr nun sicher erahnen könnt brauchen wir als Waffe nun folgendes: Ein Prefab mit einer Weapon Komponente, wir können uns dafür entweder ein neues Leeres GameObjekt erstellen oder ein GameObjekt mit einem Aussehen. Wenn ihr dieses Prefab erzeugt habt (setzt beim prefab unbedingt die Transform Komponente zurück, also Position und Rotation auf 0 setzen) könnt ihr es schon einmal auf alle Slots in eurem Spieler Raumschiff setzen indem ihr es dort einfach hineinzieht.

 

Die Items als Prefab zu halten macht auch noch Sinn, da ihr so nicht immer alle GameObjekte direkt in der Szene aufbauen müsst.

 

Da wir nun mit WaffenSlots arbeiten anstatt die Waffen direkt an unserem Raumschiff als Komponente zu haben, müssen wir die ProjectileWeapon Klasse ein wenig anpassen.

 

Die Klasse benutzt nämlich das aktuelle Rigidbody des GameObjects welches die Weapon Komponente hat. Deshalb müssen wir die Verwendung des Rigidbodys durch eine Methode ersetzen welche die GameObjekt Hierarchy nach unten durchgeht und uns das passende Rigidbody sucht:

 

Fügt also folgende Methode hinzu:

 

private Rigidbody FindRigidbody(Transform owner)
{
if (owner.rigidbody)
{
	return owner.rigidbody;
}
return FindRigidbody(owner.transform.parent);
}

 

Und ändert folgende Zeile:

 

projectile.rigidbody.velocity = rigidbody.velocity;

 

durch diese:

 

projectile.rigidbody.velocity = FindRigidbody(transform).velocity;

 

Ihr könnt nun beliebige Items erzeugen und in der Szene platzieren.

 

Unitypackage:

https://dl.dropboxus...11.unitypackage

 

Webplayer:

https://dl.dropboxus...t11/Part11.html

 

Teil 12:

http://forum.unity-c...-teil-12-waves/

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 6 months later...

Ich habs jetzt noch nicht eingebaut, weil ich ein Inventar dazwischen schalten will.

 

Was mir jedoch aufgefallen ist: Die Waffen-Scripts brauchen ja einen Rigidbody und ein Schiffs-gameObject (owner)

Wenn man nun Prefabs hat und WaffenSlots, weiss ich eventuell nicht mehr, wie weit "hoch" ich in der Hierarchie muss. Deshalb:

 

1. Eine Klasse namens "isShip" erstellen und diese am Schiff anhängen. (Wenn das Schiff in einem Gruppen/Rudel-GameObject ist, dann kann ich nicht mehr einfach das "oberste" gameObject nehmen.)

 

2. Bei den Waffen folgende Funktion einbauen und noch zwei variablen:

protected GameObject myParent;
protected RigidBody myRigidBody;
void GetShip()
{
  myParent=gameObject;
  // first set rigidbody
   if(myParent.rigidBody)
    myRigidBody=myParent.rigidBody;
  bool done=false;
  while(!done)
  {
  if(!myParent.GetComponent<isShip>() && myParent.transform.parent)
  {
	   myParent=myParent.transform.parent;
   }else{
	  done=true;
   }
   // reset rigidbody
   if(myParent.rigidBody)
	  myRigidBody=myParent.rigidBody;
  }
}

 

Nun kann man by Fire() GetShip() benutzen und dann myParent und myRigidBody für das ownerObject bzw. den rigidbody.

 

Habs noch nicht getestet, hoffe es funzt. ;)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Beim Schild habe ich ein bisschen anders verfahren:

 

Eventuell kann mein (erstes) Schiff keinen Schild haben und zweitens weiss ich nicht, ob beim wiederholten auflesen die OnPreDamage-Funktion nochmal geadded wird.

 

Ich habe also meinem Schiff schon von Anfang an einen Schild gegeben, mit einer bool Variable "isInstalled", welche an den wichtigen Stellen (auch in OnPreDamage() !!) abgefragt wird.

 

Nun habe ich dem Schild noch ein Aussehen gegeben, indem ich dem eine Kugel ans Playerschiff angehängt habe. Von dieser schalte ich den Mesh-Renderer bzw. den Collider ein oder aus, je nachdem was gerade "benutzt" wird.

 

Achja, und mein Schild fängt erst nach einer bestimmten Zeit nach dem letzten Treffer wieder an, sich aufzuladen.

 

Hier ist der Code:

[RequireComponent(typeof(Destructable))]
public class Shield : MonoBehaviour
{
   public bool isInstalled = false;
   public Transform meshAndCollider=null;
   public float strength = 100.0f;
   public float waitForReloadTime = 1.0f;
   public float reloadPerSecond = 10.0f;
   public float minEffectTime = 0.1f;
   // maybe we will change the strength of the shield directly.
   public float actualStrength;
   private bool wasInstalled;
   private float actualWaitTime;
   private float actualEffectTime;
   private MeshRenderer[] meshRenderer = null;
   private Collider[] meshCollider = null;
// Use this for initialization
void Start ()
   {
    wasInstalled = isInstalled;
    if (meshAndCollider)
    {
	    meshRenderer = meshAndCollider.GetComponents<MeshRenderer>();
	    meshCollider = meshAndCollider.GetComponents<Collider>();
    }
    if (isInstalled)
	    Install();
    else
	    Uninstall();
    Destructable destructable = GetComponent<Destructable>();
    destructable.onPreDamage += OnPreDamage;
}
   private void Install()
   {
    isInstalled = true;
    actualStrength = strength;
    actualWaitTime = 0.0f;
    actualEffectTime = 0.0f;

    EnableRenderers(false);
    EnableColliders(true);
   }
   private void Uninstall()
   {
    isInstalled = false;
    EnableRenderers(false);
    EnableColliders(false);
   }
// Update is called once per frame
void Update ()
   {
    // maybe install it again, check isInstalled
    // this is for when Install() / Uninstall() was not called but isInstalled has changed.
    if (wasInstalled != isInstalled)
    {
	    wasInstalled = isInstalled;
	    if (isInstalled)
		    Install();
	    else
		    Uninstall();
    }
    if (isInstalled)
    {
	    actualEffectTime += Time.deltaTime;
	    actualWaitTime += Time.deltaTime;
	    if (actualEffectTime >= minEffectTime)
	    {
		    EnableRenderers(false);
		    actualEffectTime = minEffectTime + 1.0f;
	    }
	    if (actualWaitTime >= waitForReloadTime)
	    {
		    actualStrength += Time.deltaTime * reloadPerSecond;
		    actualWaitTime = waitForReloadTime + 1.0f;
	    }
	    actualStrength = Mathf.Min(actualStrength, strength);
	    if (actualStrength <= 0.0f)
	    {
		    EnableColliders(false);
		    EnableRenderers(false);
	    }else{
		    EnableColliders(true);
	    }
    }
}
   void OnPreDamage(Damage damage)
   {
    if (isInstalled)
    {
	    // reset times
	    actualWaitTime = 0.0f;
	    if (actualStrength > 0.0f)
	    {
		    actualEffectTime = 0.0f;
		    EnableRenderers(true);
	    }
	    var savedDamage = Mathf.Min(damage.Amount, actualStrength);
	    actualStrength -= savedDamage;
	    damage.Amount -= savedDamage;
    }
   }
   private void EnableRenderers(bool enable)
   {
    if (meshRenderer.Length > 0)
    {
	    foreach (MeshRenderer renderer in meshRenderer)
		    renderer.enabled = enable;
    }
   }
   private void EnableColliders(bool enable)
   {
    if (meshCollider.Length > 0)
    {
	    foreach (Collider coll in meshCollider)
		    coll.enabled = enable;
    }
   }
}

 

isInstalled kann man einfach an- oder ausschalten.

meshAndCollider sollte mit dem Schild-Mesh/Collider-Gameobject befüllt werden.

waitForReloadTime ist die Zeit in Sekunden, welche gewartet wird (nach dem letzten Treffer), bevor der Schild anfängt, sich wieder aufzufüllen.

reloadPerSecond ist die Anzahl Schildpunkte, welche dann pro Sekunde aufgefüllt werden.

minEffectTime ist die minimale Zeit, an welcher der Mesh des Schildes sichtbar ist. Der Mesh wird erst sichtbar, wenn man getroffen wird.

 

Das ist das wichtigste in Kürze.

 

Have Fun. Ich hoffe, ich hab nach all den Posts wo ich einfach hier nicht weitergelesen habe, auch mal wieder etwas sinnvolles gepostet. Sorry für das.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Da erwähnst du was, das aktuelle Schild von mir hat das Problem dass es nicht wieder Problemlos entfernt werden kann, da fehlt ein OnDestroy/OnDisable mit dem sich vom event des Destructables wieder entbunden wird.

 

Aber dazu war es auch nicht gedacht, einmal vorhanden immer vorhanden, kann aber jeder so halten wie er möchte :)

 

Kurzer Hinweis zu deinem isInstalled, ich würde dir empfehlen es, wenn du es machst so zu machen:

 

Nur Install/Uninstall als public anbieten, isInstalled nur private.

 

oder folgendes:

 

private bool isInstalled;
public IsInstalled
{
 get { return isInstalled; }
 set
 {
   if (isInstalled == value)
     return;
   isInstalled = value;
   if (isInstalled)
     Install();
   else
     Uninstall();
 }
}

 

So kannst du dir den Code in Update ersparen und das ganze wäre etwas sauberer.

 

Folgendes ist nur eine Stilfrage, aber ich finde es macht den Code lesbarer wenn man anstelle von dem hier:

 

if (isInstalled)
{
 ... // Viel Code
}

 

folgendes schreibt (aufgefallen in OnPreDamage):

 

if (!isInstalled)
 return;

... // Viel Code

 

So rückt der Code nicht so weit nach rechts und man bringt eventuelle Klammern nicht so schnell durcheinander.

Link zu diesem Kommentar
Auf anderen Seiten teilen

So rückt der Code nicht so weit nach rechts und man bringt eventuelle Klammern nicht so schnell durcheinander.

 

Danke, das mach ich.

 

Ich hab isInstalled so "installiert", damit man den Wert auch im Inspektor ändern kann. Ist ein bisschen umständlich, ich weiss....

 

Wie ist es denn, wenn ich OnPreDamage wieder entfernen will? bzw. Ich habe jetzt umgedacht, und anstatt den Schild gleich zu "vorinstallieren" möchte ich Slots machen dafür - wo man auch was anderes reinmachen kann, wie zum Beispiel einen Hull-Repair-Bot oder so... ;)

 

Wie kann ich also diese Funktionen wieder entfernen?

Und wie geht das mit OnDisable? (Der Schild existiert ja eventuell noch, kommt halt ins Inventar....)

 

Danke für die Hilfe.

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...