Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Sneyke

Frischling

  • »Sneyke« ist der Autor dieses Themas

Beiträge: 33

Beruf: Softwareentwickler

  • Private Nachricht senden

1

27.03.2017, 08:47

Tutorial - Schießen in Unity mit Raycasts, unter Beachtung von Projektilgeschwindigkeit

Hi,

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 “bSpeed” 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:

C#-Quelltext

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

C#-Quelltext

1
2
3
4
5
6
7
8
private void CalcPositionDelta()
    {
        double deltaFactor = Math.Sqrt((Direction.x * Direction.x) + (Direction.y * Direction.y) + (Direction.z * Direction.z)) / speed;
        float div = (float) deltaFactor;

        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.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
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 ausprobieren

    //Hier wird noch die “Lebenszeit” des Projektils berechnet
        CalcLifeTime();

    }


Die Lebenszeit wird so berechnet:

C#-Quelltext

1
2
3
4
5
6
7
8
9
private void CalcLifeTime()
{
    actualLivedSec += Time.deltaTime;

        if(actualLivedSec >= maxLifeInSec)
        {
            FinishShot();
        }
    }


Dann haben wir noch die Methode finishShot:

C#-Quelltext

1
2
3
4
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:

C#-Quelltext

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

    // Use this for initialization
    void Start()
    {
        activeShots = new List<FlyingShot>();
        hitShots = new List<RaycastHit>();
    }

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

C#-Quelltext

1
2
3
4
5
    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.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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.

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    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:

C#-Quelltext

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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 (frac.Equals("bandits"))
            {
                Debug.Log("fraction 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()
    {
        if (cooldown <= 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }


    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.

C#-Quelltext

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
    private bool AllowedToShoot()
    {
        if (cooldown <= 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }


    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!

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Sneyke« (27.03.2017, 13:15)


Thoran

Alter Hase

Beiträge: 520

Wohnort: Stuttgart

Beruf: Senior Software Engineer

  • Private Nachricht senden

2

27.03.2017, 10:38

Ich hab mir Dein Tutorial nicht durchgelesen, zumal ich kein Unity mache, aber du solltest es in das Wiki einstellen. Hier im Forum geht es verloren.
https://www.spieleprogrammierer.de/wiki/Kategorie:Tutorial
Mein Entwicklertagebuch
Aktuelles Projekt: Universum Espionage
Eingestellt:Spieleengine SilverCore
Organisator "Spieleentwickler Stammtisch Stuttgart"

Wirago

Alter Hase

Beiträge: 1 193

Wohnort: Stockerau

Beruf: CRM Application Manager

  • Private Nachricht senden

3

27.03.2017, 11:01

Gibt auch eine Tutorialsektion hier im Forum -> https://www.spieleprogrammierer.de/27-tutorials/

Sneyke

Frischling

  • »Sneyke« ist der Autor dieses Themas

Beiträge: 33

Beruf: Softwareentwickler

  • Private Nachricht senden

4

27.03.2017, 13:12

Ouh, die Tutorial-Sektion hab ich bis jetzt noch nie gesehen. Dann sollte man das verschieben. Ins Wiki werde ich es auch übernehmen, sofern euer Feedback gut ausfällt.

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

5

27.03.2017, 18:12

Im Wiki können andere User ja weiter bearbeiten und man kann über Änderungen und Inhalt diskutieren.
„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.“

6

27.03.2017, 20:05

§x(t)=v*t*\frac{d}{||d||}, v\in \mathbb{R}, d \in \mathbb{R}^n§

Wenn ich mich nicht irre?

Werbeanzeige