Hallo Sun,
schön das du Fortschritte machst. Ich sehe ein paar Kleinigkeiten in deinem Code, die ich zuvor noch anmerken möchte, bevor ich zu den Fragen komme:
Anstatt
|
C#-Quelltext
|
1
|
cam = transform.GetChild(0).GetComponent<Camera>();
|
Solltest du lieber
|
C#-Quelltext
|
1
|
camera = GetComponentInChildren<Camera>();
|
verwenden, oder, wenn es überhaupt deine einzige Kamera und damit die MainCamera ist (das ist jene Kamera welche in der Szene das Tag "MainCamera" inne hat), kannst du auch ganz einfach
|
C#-Quelltext
|
1
|
camera = Camera.main;
|
verwenden.
Hier der Link zur Referenz. (Schreib ruhig cam aus, es ist unleserlich Abkürzungen zu verwenden)
Für den zu verwendenden Button solltest du dir lieber einen Bezeichner wählen und diesen in den InputManager eintragen (du findest diesen unter Edit->Project Settings->Input). Du kannst dann dort alle nötigen Einstellung für deinen Button durchführen (sogar den Bezeichner mehrfach verwenden, solltest du die selben Eingaben für Controller o.ä. wünschen. Das funktioniert sogar ganz ohne weiteren Code!). Das könnte so aussehen:
|
C#-Quelltext
|
1
2
3
|
if (Input.GetButtonDown("PlaceBlock")) {
//...
}
|
Hier noch der Link zur Referenz.
Zu deinen Fragen:
-Wird dein System später auf einem Terrain auch noch funktionieren
Njaein. Die wenigen Zeilen die ich dir gegeben habe setzen darauf, dass Blöcke auf einem festen Raster gesetzt werden können - eben wie in Minecraft. Die Umsetzung für ein Terrain ist jedoch ebenso einfach. Die Berechnung ist lediglich ein kleines bisschen anders.
-Der erste/zweite Block wird meistens nicht da gesetzt wo er eigentlich sein sollte die anderen danach schon
Du hast einen Fehler eingebaut, denn du erzeugst dein
placeableObject sowohl in der Zeile 26, wie auch nochmal in der
SetBlock in Zeile 35. Jedoch wird nur das zweite mit einer Position versetzt. Zeile 26 ist demnach unnötig und muss gelöscht werden. Ebenso setzt du in Zeile 36 nicht die Position der Instanz die du erzeugt hast, sondern die des Prefabs! Möchtest du es so schreiben, dann musst du es so machen:
|
C#-Quelltext
|
1
2
|
GameObject newBlock = Instantiate(placeableObject);
newBlock.transform.position = placePosition;
|
(Der Rückgabewerte von
Instantiate ist die neue Instanz deines Prefabs, welches nun in der Szene existiert)
-Meistens muss ich zweimal klicken bis ein Block gesetzt wird
Da musst du etwas konkreter werden, da aus dem Code nicht zu schließen ist, warum es manchmal nicht funktionieren sollte. Allerdings kann ich sagen, dass du dem Raycast nicht deine Länge mitgibst, welche du aus deinem
GameManagerScript ausliest. Die Methode
Physics.Raycast kann nämlich ebenfalls eine Länge entgegen nehmen. Aktuell zeigst du zwar mit DrawRay eine Länge, welche aber nicht der konkreten des
Physics.Raycast's entspricht - dieser ist nämlich standardmäßig
Mathf.Infinity. Was du tun könntest wäre also:
|
C#-Quelltext
|
1
2
|
if(Physics.Raycast(buildray, out hit, range)) {
}
|
Es gibt desweiteren eine Überladung der Methode welche direkt den Ray entgegen nimmt. Du musst also nicht origin und direction übergeben
-Und die Blöcke werden alle mit ein bisschen Abstand zur Plane gesetzt kann ich das ganze irgendwie ändern, dass wenn ich den Block auf eine Plane setze er immer sich in der Plane befindet
Das liegt daran, dass du das Ergebnis aus dem getroffenen Punkt und der Richtung durch die Normale mittels
Mathf.RoundToInt nochmals auf eine Ganzzahl rundest. Das macht natürlich nur Sinn in meinem Beispiel bei einem festen Raster. Du möchtes es aber tatsächlich immer auf genau jenem Punkt wo du es triffst. Du kannst es also dadurch weglassen und schreiben:
|
C#-Quelltext
|
1
|
Vector3 position = hit.point + hit.normal * 0.5f;
|
Beachte jedoch, dass diese Zeile natürlich nur funktioniert, wenn die Skalierung des Würfels sich nicht verändert und immer bei 1 ist. Was du dagegen tun kannst ist, die Skalierung einfach mit einzubeziehen.
Hier die Referenz als kleiner Tipp.
Das Resultat könnte also ungefähr so oder ähnlich aussehen:
|
C#-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public GameObject placeableObject;
public GameManager gameManager;
private Camera mainCamera;
private void Start() {
Cursor.lockState = Cursor.Locked;
mainCamera = Camera.main;
}
private void Update() {
if (Input.GetButtonDown("PlaceBlock")) {
Ray buildray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(buildray, out hit, gameManager.range)) {
Vector3 position = hit.point + hit.normal * 0.5f;
Instantiate(placeableObject, position, Quaternion.identity);
}
}
}
|
Im Übrigen nimmt
Instantiate auch eine Position und Rotation mit entgegen, wenn du möchtest. In diesem Fall habe ich die Rotation auf
Quaternion.identity gestellt, was gleichbedeutend mit keiner Rotation ist (genau genommen ist die Rotation dadurch ausgerichtet auf die Achsen des Weltkoordinatensystem oder die des Parents - je nachdem). Solltest du also später ein verformtes Terrain haben müsstest du hier noch die Rotation mit einbeziehen, so dass es auch richtig gedreht auf dem Terrain steht. Die Normale wird dir wieder behilflich sein.
Nicht zuletzt möchte ich dir noch einen kleinen Rat mitgeben, welchen Anfänger häufig vernachlässigen und dadurch in Probleme geraten. Verwende für das Setzen von Referenzen innerhalb der eigenen GameObject Hierarchie lieber die Methode
Awake anstatt
Start und für das Setzen für Referenzen auf "fremde" GameObjects (d.h. GameObjects auf denen dieses Skript nicht liegt oder welche nicht Childs sind) die
Start Methode. Diese Vorgehensweise hat zur Folge das sich erst alle Skripte "intern korrekt Vernetzen" bevor sie Referenzen zu Anderen aufbauen, denn
Awake wird stets vor
Start aufgerufen. Es gibt noch einige weitere Vorteile zum Beispiel das Ordnen von
Awakes, oder das Setzen von Werten erst wenn das Objekt tatsächlich aktiviert wird durch die
Start. Ich verweis dich mal an die Dokumentation:
Awake,
Start,
Unity Lifecycle. (Schau dir mal das abgebildete Script Flowchart an).
Schönes Wochenende