Du bist nicht angemeldet.

Werbeanzeige

  • »SunGamingStudios« ist der Autor dieses Themas

Beiträge: 9

Wohnort: -

Beruf: -

  • Private Nachricht senden

1

06.06.2017, 12:08

Unity - Erstellung eines Bausystems

Hallo zusammmen,

nach einigen eigenen Versuchen und unzähligen Google Sucheen habe ich mein Problem selbst noch nicht lösen können deswegen bitte ich jetzt euch Forenuser mir weiter zu helfen. Ich versuche seit einer Woche ein Bausystem in Unity (Scriptsprache C#) umzusetzten mit dem man am Anfang einen normal 1*1*1 cube setzten kann, dies soll späternatürlich auf mehrere Items erweitert werden, aber ich scheitere derzeit noch an der anfänglichen Umsetzung. Meine Grundidee ist das ich einen Raycast erzeuge der von einem First Person Controller ausgeht, sobald der Spieler dann die linke Maustaste drückt und der Raycast den Untergrund oder einen anderen Cube berührt soll an dieser Position auf ganze Blöcke also auf ein Raster gerundet ein Block gesetzt werden. Mein erster Umsetzunsversuch war mit Hilfe der zurückgegebenen RaycastHit Info den Treffer Punkt auszulesen und danach mit Mathf.RoundToNextInt auf einen ganze Zahl zu runden. Leider stellte sich schnell heraus, dass dieser einfache Ansatz keine lösung brachte die Blöcke wurden darauf immer irgendwo gesetzt aber nicht wo ich wollte. Ein zweiter ansatz war auf den Vector3 der Position des getrofenen Untergrunds die Normale der getroffenen Seite zu addieren leider auch ohne Erfolg. Nun hoffe ich das einer von euch mir bei einem brauchbaren Ansatz helfen könnte um das Problem zu lösen, ich werde hier nochmal schnell meinen Code anfügen, dass ihr auch alles nachvollziehen könnt;).

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class BuildingThings : MonoBehaviour {

    private float range;
    public GameObject placeableObject;
    
    // Update is called once per frame
    void Update () {
        GameObject gameManager = GameObject.Find("GameManager");
        range = gameManager.GetComponent<GameManagerScript>().buildRange;
         

        Camera cam = transform.GetChild(0).GetComponent<Camera>();

        Ray buildray = cam.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
        Debug.DrawRay(buildray.origin, buildray.direction * range, Color.green);
        RaycastHit hit;

        if (Input.GetMouseButtonDown(1))
        {
            if(Physics.Raycast(buildray.origin, buildray.direction, out hit)){
                Instantiate(placeableObject);
                placeableObject.transform.position = new Vector3( Mathf.RoundToInt(hit.point.x), Mathf.RoundToInt(hit.point.y), Mathf.RoundToInt(hit.point.z));
            }
        }
    }
}


Danke schonmal im voraus für euer Hilfe

SunGaming

Schorsch

Supermoderator

Beiträge: 5 196

Wohnort: Wickede

Beruf: Student

  • Private Nachricht senden

2

06.06.2017, 12:17

Sollen deine Blöcke selbst auf einem richtigen Raster gesetzt werden können? So wie man es zum Beispiel auch bei Tilemaps kennt? Falls ja ist es eigentlich ganz einfach. Du berechnest per Raycast die Position des Hits auf der Oberfläche. Bei Unity zeigt die Y Achse nach oben, die Y Koordinate deines Hits ist also der untere Punkt deines zu setzenden Blocks. Du musst also nur noch X und Z Werte auf das Raster bringen. Beginnt dein Raster direkt beim Ursprung des Koordinatensystems so kannst du die Werte folgendermaßen berechnen:
gesuchtes_X = (X % Rasterbreite) * Rasterbreite
gesuchtes_Z = (Z % Rasterbreite) * Rasterbreite
X und Z sind jetzt die Koordinaten einer Ecke. Möchtest du den Mittelpunkt der Fläche haben addierst du eben noch den nötigen Offset hinzu.

Ist dein Raster nicht am Ursprung des Koordinatensystems ausgerichtet so musst du zuerst den jeweiligen Offset von X und Z abziehen bevor du mit oberen Formeln weiter rechnest. Am Ende hast du dann deinen gesuchten Punkt.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Renegade

Alter Hase

Beiträge: 480

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

3

06.06.2017, 14:41

Hey Sun,
ich habe vor einer ganzen Weile mal für meine Studenten einen Minecraft Klon mittels Unity gebaut, der die Funktionalität die du benötigst beinhaltet.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void TryToCreateBlock() {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, _rayLength)) {
            Chunk chunk = hit.collider.GetComponent<Chunk>();
            if (chunk != null) {
                int x = Mathf.RoundToInt(hit.point.x + hit.normal.x * 0.5f);
                int y = Mathf.RoundToInt(hit.point.y + hit.normal.y * 0.5f);
                int z = Mathf.RoundToInt(hit.point.z + hit.normal.z * 0.5f);
                x -= chunk.x;
                y -= chunk.y;
                z -= chunk.z;
                chunk.SetBlock(x, y, z, new Block());
            }
        }
    }

In diesem Fall wird erst nach dem Chunk gefragt (jeder Chunk speichert eine bestimmte Anzahl von Würfeln als eine gesamte Geometrie, anstatt einzelne Würfel zu rendern), und anhand des Treffers wird der neue Mittelpunkt des Würfels berechnet. Der Trick ist, über die Normale (das ist ein senkrechter Vektor der auf der angeklickten Fläche des getroffenen Würfels steht), die Position zu berechnen. Dem ganzen liegt natürlich zu Grunde, das alles auf einem Raster mit festen Größen geschieht (in diesem Fall ist jeder Würfel 1x1x1 Einheiten groß).

(Link)

Hier eine kleine Veranschaulichung.

(Link)

Und so könnte das Ergebnis dann aussehen ^^

Noch eine kleine Anmerkung zu deinem Code:
Du musst deinen GameManager nicht in jeder Update Routine suchen. Ebenso gilt für die Kamera. Außerdem solltest du Find Methoden so gut es geht vermeiden. Du könntest beide Komponenten ebenso über eine öffentliches Feld deinem Skript zuweisen. Das spart ungeheuerlich viel Rechenaufwand. Schau dir ruhig mal den Profiler unter (Window->Profiler oder Strg+7) an. Du wirst den Unterschied bei der CPU Usage deutlich sehen (blaue Farbe für Scripts).

C#-Quelltext

1
2
3
4
5
6
7
public class BuildingThings : MonoBehaviour {

    private float range;
    public GameObject placeableObject;
    public GameManagerScript gameManager;
    public Camera camera;
    [...]


PS: Da es dich sicher interessiert, hier noch ein Bild von dem prozeduralen Terrain mittels Perlin Noise. Wenn du Fragen hast, stell sie ruhig - ich helfe gerne 8o

(Link)
Liebe Grüße,
René

Dieser Beitrag wurde bereits 12 mal editiert, zuletzt von »Renegade« (06.06.2017, 19:22)


NachoMan

Community-Fossil

Beiträge: 3 905

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

4

06.06.2017, 17:44

Du solltest das lieber so schreiben und cam zum Member machen und dir die Kamera nur in der Start-Methode holen.

Quellcode

1
2
//Camera cam = transform.GetChild(0).GetComponent<Camera>();
Camera cam = GetComponentInChildren<Camera>();
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

KeksX

Community-Fossil

Beiträge: 2 143

Beruf: Game Designer

  • Private Nachricht senden

5

06.06.2017, 18:45

@Renegade:

Besteht die Chance, dass du den Code mit einer Open Source Lizenz veröffentlichst? Wäre für viele, inklusive des OP, extrem nützlich.
WIP Website: kevinheese.de

Renegade

Alter Hase

Beiträge: 480

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

6

06.06.2017, 21:44

@Renegade:

Besteht die Chance, dass du den Code mit einer Open Source Lizenz veröffentlichst? Wäre für viele, inklusive des OP, extrem nützlich.


Ich werde über ein Tutorial nachdenken. Unkommentiert möchte ich den Code nicht veröffentlichen. Über welchen Themenschwerpunkt würdest du dich denn freuen?
Liebe Grüße,
René

  • »SunGamingStudios« ist der Autor dieses Themas

Beiträge: 9

Wohnort: -

Beruf: -

  • Private Nachricht senden

7

07.06.2017, 11:25

Danke für eure Hilfe

Hallo zusammen danke für eure Hilfe da ich heute den ganzen Tag unterwegs bin werde ich mich morgen nachmittag einaml hinhocken und eure Tipps austesten.
@Renegade ich komme gern auf dein Angebot mit dem Prozedural generierten Terrain zurück da ich danach such schon länger Ausschau halte dafür werde ich dir demnächst (wahescheinlich morgen :P ) eine Nachricht schreiben.

KeksX

Community-Fossil

Beiträge: 2 143

Beruf: Game Designer

  • Private Nachricht senden

8

07.06.2017, 12:57

@Renegade:

Besteht die Chance, dass du den Code mit einer Open Source Lizenz veröffentlichst? Wäre für viele, inklusive des OP, extrem nützlich.


Ich werde über ein Tutorial nachdenken. Unkommentiert möchte ich den Code nicht veröffentlichen. Über welchen Themenschwerpunkt würdest du dich denn freuen?


Eigentlich ist mir gar kein Themenschwerpunkt wichtig. Ich glaube einfach, dass du relativ viel Qualität zu bieten hast und ein wenig gegen die Schluderigkeit der bisherigen Unity Beispielprojekte arbeiten könntest. Aber wenn ich mir ein Thema aussuchen müsste, wäre das Thema Chunkgenerierung und Darstellung natürlich am interessantesten.
WIP Website: kevinheese.de

  • »SunGamingStudios« ist der Autor dieses Themas

Beiträge: 9

Wohnort: -

Beruf: -

  • Private Nachricht senden

9

16.06.2017, 13:09

Hey Renegade,
hier ist erstmal mein überarbeiteter Code:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class BuildingThings : MonoBehaviour {

    private float range;
    public GameObject placeableObject;
    public Camera cam;

    private void Start()
    {
        GameObject gameManager = GameObject.Find("GameManager");
        range = gameManager.GetComponent<GameManagerScript>().buildRange;


        cam = transform.GetChild(0).GetComponent<Camera>();
    }

    // Update is called once per frame
    void Update () {

        Ray buildray = cam.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
        Debug.DrawRay(buildray.origin, buildray.direction * range, Color.green);
        RaycastHit hit;

        if (Input.GetMouseButtonDown(1))
        {
            if(Physics.Raycast(buildray.origin, buildray.direction, out hit)){
                Instantiate(placeableObject);
                Vector3 placePosition = new Vector3( Mathf.RoundToInt(hit.point.x + hit.normal.x * 0.5f), Mathf.RoundToInt(hit.point.y + hit.normal.y *      0.5f),Mathf.RoundToInt(hit.point.z + hit.normal.z * 0.5f));
                SetBlock(placePosition);
            }
        }
    }

    private void SetBlock(Vector3 placePosition)
    {
        Instantiate(placeableObject);
        placeableObject.transform.position = placePosition;
    }
}




Ich hab deine Code soweit umgesetzt bis auf dein Chunksystem, aber ich ahbe jetzt noch folgende Probleme zu fixen:
(Zur Info mein Untergrund besteht nicht aus Blöcken sondern aus einer Plane, da ich später ein hochauflösendes Terrain haben möchte)
-Wird dein System später auf einem Terrain auch noch funktionieren
-Der erste/zweite Block wird meistens nicht da gesetzt wo er eigentlich sein sollte die anderen danach schon
-Meistens muss ich zweimal klicken bis ein Block gesetzt wird
-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

Ich weiß, dass ich jetzt ziemlich viel von dir verlange aber du scheinst mir ziemlich hilfsbereit und dich sehr gut auszukennen :)
Wäre nett wenn du mir hier weiterhelfen kannst :)

Auf bitte Renegades poste ich diese private Nachricht noch einmal öffentlich um sie hier mit euch allen zu diskutiren ;)

Renegade

Alter Hase

Beiträge: 480

Wohnort: Berlin

Beruf: Certified Unity Developer

  • Private Nachricht senden

10

16.06.2017, 20:55

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 :)
Liebe Grüße,
René

Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von »Renegade« (16.06.2017, 21:08)


Werbeanzeige