Einheit: Erstellen Sie erfolgreich ein Netz aus Spielereingaben als benutzerdefinierten IndikatorC#

Ein Treffpunkt für C#-Programmierer
Guest
 Einheit: Erstellen Sie erfolgreich ein Netz aus Spielereingaben als benutzerdefinierten Indikator

Post by Guest »

Ich stoße hier mit dem Kopf gegen die Wand-->
Aufgabe: Für mein kleines Projekt (isometrisch mit geneigter Kamera) möchte ich dem Spieler einen benutzerdefinierten Wirkungsbereich für seine Fähigkeit erstellen lassen, Das heißt, wenn er die Fertigkeit aktiviert, kann er die Maus ziehen und einen benutzerdefinierten Bereich erstellen (z. B. einen Indikator, der angibt, welchen Bereich die Fertigkeit abdeckt), mit fester Breite und maximaler Länge, die an die maximale Länge der Fertigkeit gebunden ist. Der Startpunkt sollte innerhalb seines maximalen Radius liegen (er kann ihn aber weit erweitern).
Meine Idee war also, ein Mesh zu verwenden (ein Spielobjekt mit einem Mesh-Filter und einem Mesh-Renderer). Ich habe daraus ein vorgefertigtes Objekt erstellt.
Um die Schleife zu bewältigen, habe ich hier diesen asynchronen Task, der wartet, bis die Bedingungen erfüllt sind:

Code: Select all

public class FreeAOEState : SpellState
{
public override async Task ExcecuteState(SpellTemplate spellTemplate)
{
var spellRadius = spellTemplate.GetParameter(SpellParameterType.SpellRange);
var maxLength = spellTemplate.GetParameter(SpellParameterType.AreaSize);
var width = spellTemplate.GetTargetParameter(SpellTargetFeatures.Width);
bool isDrawningStarted = false;
MeshFilter meshFilter = new();
// control points for spline
List controlPoints = new();
float totalLength = 0f;
spellTemplate.spellIndicator.InitializeFreeAOE(spellRadius);
while(!isDrawningStarted)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
if(spellTemplate.spellIndicator.StartFreeAOEDrawning(spellRadius,
out Vector3 startPoint, controlPoints))
{
spellTemplate.spellIndicator.InstantiateFreeAOE(startPoint, out meshFilter);
isDrawningStarted = true;
}
}
await Task.Yield();
}

var time = 0f;
while (time < 2f * Config.MAX_TIME_READY_SPELL)
{
time += Time.deltaTime;

var result = spellTemplate.spellIndicator.UpdateFreeAEOIndicatorWithSpline(
width,
maxLength,
ref totalLength,
controlPoints,
meshFilter);
if (Input.GetKeyUp(KeyCode.Mouse0) || result.IsDrawingFinished)
{
spellTemplate.SpellEffect();
await Task.Delay(400);
break;
}
await Task.Yield();
}
spellTemplate.spellIndicator.HideCursor();
}
}
Wir stellen vor:

Code: Select all

public struct FreeAOEIndicatorResult
{
public bool IsDrawingFinished;
public float TotalLength;
}
Eine einfache Struktur als Ausgabe SpellTemplate.spellIndicator.UpdateFreeAEOIndicatorWithSpline(), um zu überprüfen, wann die Bedingung erfüllt ist und die Schleife unterbrochen werden kann.
Das Gesuchte Das Verhalten ist wie folgt: Der Spieler klickt (vorerst) mit der linken Maustaste und instanziiert das vorgefertigte Netz, wenn der ausgewählte Punkt innerhalb des SpellRadius (Entfernung vom Spieler) liegt. Das ist die Bedingung, um zuerst die while-Schleife zu schließen. Die zweite Schleife sollte einfach die von der Mausbewegung aufgezeichneten Punkte hinzufügen und sie zur Liste „controlPoints“ hinzufügen, um das Netz aus dem Spline zu erstellen. Brechen Sie davon ab, wenn die linke Maustaste losgelassen wurde oder die Länge des Netzes größer als die maximale Länge des Zaubers ist.
Die verwendeten Methoden werden im Skript „spellTemplate.spellIndicator“ platziert und sind die folgenden:
Erste Methode: Überprüfen Sie, ob der Raycast einen Punkt innerhalb des Radius trifft, der auf dem Spieler mit Radius = SpellRadius zentriert ist

Code: Select all

public bool StartFreeAOEDrawning(float spellRadius, out Vector3 startPoint,
List controlPoints)
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
startPoint = Vector3.zero;

var playerPos = Player.Instance.playerTransform.position;

Debug.DrawRay(ray.origin, ray.direction * 100f, Color.green, 2f);  // visual debug

if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity))
{

if (Vector3.Distance(hitInfo.point, playerPos) > spellRadius)
{
return false;
}

startPoint = hitInfo.point;
controlPoints.Add(startPoint);

DrawHitPoint(hitInfo.point);

return true;
}

return false;
}
Erstellen Sie eine visuelle Darstellung des durch Raycasting getroffenen Punkts

Code: Select all

private void DrawHitPoint(Vector3 hitPoint)
{
// yellow sphere to check impact point
GameObject hitMarker = GameObject.CreatePrimitive(PrimitiveType.Sphere);
hitMarker.transform.position = hitPoint;
hitMarker.transform.localScale = Vector3.one * 0.3f;
hitMarker.GetComponent().material.color = Color.yellow;
Destroy(hitMarker, 2f);
}
Instanziieren Sie jetzt einfach das Netz

Code: Select all

public void InstantiateFreeAOE(Vector3 startPoint, out MeshFilter meshFilter)
{
_currentArea = Instantiate(meshPrefabToInstantiate, startPoint, Quaternion.identity);
_currentArea.transform.position = startPoint;

if (_currentArea.TryGetComponent(out meshFilter))
{
meshFilter.mesh = new Mesh();
_currentMeshFilter = meshFilter;
}
else
{
meshFilter = null;
}
}
Legen Sie einfach die Maxradius-Leinwand für die visuelle Darstellung des maximalen Radius fest

Code: Select all

public void InitializeFreeAOE(float spellRadius)
{
spellCastRadiusCanvas.SetActive(true);
_spellCastRadiusCanvasRectTransform.sizeDelta = new Vector2(spellRadius * 2f, spellRadius * 2f);
_spellCastRadiusImageRect.sizeDelta = new Vector2(spellRadius * 2f, spellRadius * 2f);
}
Jetzt müssen wir in der Hauptschleife die Mausposition erfassen, die Punkte speichern und an den Spline-Ersteller senden und ein Netz mit der Breite erstellen und zurückgeben, wenn die Länge > maxLength ist

Code: Select all

public FreeAOEIndicatorResult UpdateFreeAEOIndicatorWithSpline(
float width,
float maxLength,
ref float totalLength,
List controlPoints,
MeshFilter meshFilter)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity))
{
Vector3 currentPoint = hit.point;

if (controlPoints.Count > 0)
{
float distance = Vector3.Distance(controlPoints[^1], currentPoint);
if (distance > 0.5f)
{
controlPoints.Add(currentPoint);
}
}
//here, we're generating the curve
var curvePoints = GenerateCatmullRomCurve(controlPoints, 20);

// here we draw the curve
DrawMeshFromCurve(curvePoints, width, meshFilter);

// retrieve the length
float length = 0f;
for (int i = 1; i < curvePoints.Count; i++)
{
length += Vector3.Distance(curvePoints[i - 1], curvePoints[i]);
}

totalLength = length;

// our condition to cut the loop
if (totalLength >= maxLength)
{
return new FreeAOEIndicatorResult { IsDrawingFinished = true, TotalLength = totalLength };
}

return new FreeAOEIndicatorResult { IsDrawingFinished = false, TotalLength = totalLength };
}

return new FreeAOEIndicatorResult { IsDrawingFinished = true, TotalLength = totalLength };
}
Befolgen Sie nun die Methoden zum Erstellen der Kurve und des Netzes um sie herum

Code: Select all

public List GenerateCatmullRomCurve(List controlPoints, int resolution)
{
List  curvePoints = new ();

for (int i = 0; i < controlPoints.Count - 3; i++)
{
Vector3 p0 = controlPoints[i];
Vector3 p1 = controlPoints[i + 1];
Vector3 p2 = controlPoints[i + 2];
Vector3 p3 = controlPoints[i + 3];

for (int j = 0; j < resolution; j++)
{
float t = j / (float)resolution;
curvePoints.Add(CatmullRom(p0, p1, p2, p3, t));
}
}

return curvePoints;
}

private Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
float t2 = t * t;
float t3 = t2 * t;

return 0.5f * ((2f * p1) +
(-p0 + p2) * t +
(2f * p0 - 5f * p1 + 4f * p2 - p3) * t2 +
(-p0 + 3f * p1 - 3f * p2 + p3) * t3);
}
void DrawMeshFromCurve(List curvePoints, float width, MeshFilter meshFilter)
{
if (curvePoints.Count < 2)
return;

int maxPoints = 1000;
if (curvePoints.Count > maxPoints)
{
curvePoints = curvePoints.GetRange(0, maxPoints);
}

int numSegments = curvePoints.Count - 1;
int[] triangles = new int[numSegments * 6];

Vector3[] vertices = new Vector3[curvePoints.Count * 2];

for (int i = 0; i < curvePoints.Count; i++)
{
Vector3 direction = (i < curvePoints.Count - 1) ?
(curvePoints[i + 1] - curvePoints[i]).normalized :
(curvePoints[i] - curvePoints[i - 1]).normalized;

Vector3 offset = Vector3.Cross(direction, Vector3.up) * (width / 2);
vertices[i * 2] = curvePoints[i] - offset;
vertices[i * 2 + 1] = curvePoints[i] + offset;

if (i < curvePoints.Count - 1)
{
int baseIndex = i * 2;
triangles[i * 6] = baseIndex;
triangles[i * 6 + 1] = baseIndex + 1;
triangles[i * 6 + 2] = baseIndex + 2;

triangles[i * 6 + 3] = baseIndex + 1;
triangles[i * 6 + 4] = baseIndex + 3;
triangles[i * 6 + 5] = baseIndex + 2;
}
}

Mesh mesh = new()
{
vertices = vertices,
triangles = triangles
};
mesh.RecalculateNormals();

meshFilter.mesh = mesh;
}
Jetzt bin ich mir ziemlich sicher, dass die Art und Weise, wie Kurve und Netz erstellt werden, korrekt ist. Wenn ich das Spiel starte, trifft der Raycast jedoch korrekt auf die Ebene, wie im folgenden Bild:
Image

Wie Sie sehen können, ist die gelbe Kugel korrekt positioniert . Aber wenn ich die Maus ziehe, wird das Netz weit entfernt von diesem Punkt erstellt, wie im folgenden Bild:
Image

Jetzt habe ich es auch mit chatgpt versucht, aber nach mehreren Stunden kann ich nicht verstehen, warum es sich so verhält. Ich vermute, dass es mit den ersten 4 Punkten zusammenhängt: Tatsächlich wird die erste Kurve erstellt, nachdem 4 Punkte aufgezeichnet wurden. Der Grund, warum es dort beginnt und nicht am Ausgangspunkt, ist mir ein Rätsel. Könnten Sie mir bitte helfen? Wenn Sie weitere Informationen benötigen, kann ich sie integrieren ... Ich glaube, ich habe hier den Kern eingefügt.
Vielen Dank!

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post