C# 从JSON反序列化类后出现NullReferenceException

C# 从JSON反序列化类后出现NullReferenceException,c#,unity3d,C#,Unity3d,我想让一个手势识别器在unity 3D上工作。脚本让我选择是否录制手势,如果不录制,则与现有手势进行比较 问题是 在第一次运行时,它工作得非常完美:它允许我绘制和录制手势,然后在禁用录制后,它将与现有的手势进行比较,以便在列表templates.templates中查找匹配项 但在我关闭游戏并重新打开它之后,它调试加载成功,我可以在Inspector中看到模板列表,但它为模板或当前手势返回NullRefferenceException错误。我在这里发疯了。你能帮我注意到在从json文件加载模板后

我想让一个手势识别器在unity 3D上工作。脚本让我选择是否录制手势,如果不录制,则与现有手势进行比较

问题是

在第一次运行时,它工作得非常完美:它允许我绘制和录制手势,然后在禁用录制后,它将与现有的手势进行比较,以便在列表templates.templates中查找匹配项

但在我关闭游戏并重新打开它之后,它调试加载成功,我可以在Inspector中看到模板列表,但它为模板或当前手势返回NullRefferenceException错误。我在这里发疯了。你能帮我注意到在从json文件加载模板后,第一次运行和第二次运行的行为有什么不同吗? 脚本如下:我序列化了所有私有内容,只是因为…:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

//******************************************* Gesture Recognizer *********************************************//
//
//      Author:         Andres De La Fuente Duran
//
//      Use:            This script can simply be attached to a camera to function.
//                      It allows for recording of 2D 'gestures', in this case meaning a set of points
//                      representing a continuous motion of the mouse (or touch input).
//                      It also allows for testing 'gestures' against a collection of templates created
//                      with this very same script.
//
//                      Template gestures are saved in a JSON file and loaded into a list at start.
//                      
//                      The way the recognition works is that a list of points is recorded (relative to
//                      the location of the initial click as (0,0)), scaled to a square resolution of 
//                      choice, reduced by interpolation to a set number of points, and then compared
//                      to gestures already processed by this script.
//
//                      This is built for maximum customizability, so you can change the number of points
//                      allowed when recording, the number of points once reduced, the rate of sampling,
//                      and the square ratio gestures are scaled to. Recording the gestures and testing
//                      can be done easily by swithching the 'recording' boolean variable.
//
//                      Some additional notes:      Because the origin of each gesture is the initial
//                                                  point, and comparison follows points in order of
//                                                  recording, directionality is captured by this
//                                                  solution. The gestures do not have to be wildly 
//                                                  different for the recognition to be reliable.
//
//                                                  However, you can turn on 'anomaliesTesting' to
//                                                  weight more heavily sudden differences in gestures
//                                                  than constant differences to allow for similar
//                                                  gestures with small modifications or flares.




//****************************************** Recognizer Class ****************************************************//
// 
//      Use:        Stores all information for the current gesture being recorded, the existing gestures,
//                  the conditions selected by an editor user, and variables needed to perform recognition.
//                  This is the central class with most of the functionality in the script.
//
//      Fields:     
//                  Editor Controlled............................................................................
//
//                  recording:          boolean to control whether to save a gesture or try to recognize it
//                  
//                  anomaliesTesting:   boolean to control whether to weight sudden differences during
//                                      comparison more than other differences
//
//                  pointsPerGesture:   the size of the array of points stored for each gesture
//                   
//                  templateSaveName:   the string name of the gesture to be saved when recording
//
//                  samplingRate:       time interval between samples while recording
//
//                  maxPointsAllowed:   the maximum number of points that will be recorded
//
//                  standardRatio:      the size of one side of the square that points will be scaled to
//
//                  devTightness:       the number of deviations from the average difference between the points
//                                      of two gestures that are allowed before they are weighted more
//
//                  anomaliesFactor:    how much extra to weight the differences that surpass the devTightness
//
//                  Control Flow................................................................................
//                  
//                  gestureStarted:     boolean to execute code to start gesture and to avoid starting anew
//                  
//                  gestureComplete:    boolean to execute recording of gesture until complete
//
//                  inputReady:         boolean to prevent execution of anything until input is lifted
//                                      so as not to start gestures immediately after one is complete
//
//                  Recording and Recognizing...................................................................
//
//                  gestureFileName:    JSON file to load saved gestures from as templates for recognition
//          
//                  startPoint:         the initial point from which to calculate every other point
//
//                  currentPoint:       the last point recorded
//                  
//                  currentGesture:     the object containing the recorded gesture for current execution
//      
//                  currentPointList:   list of points as they are recorded
//
//                  reducedPoints:      array of points for after scaling and mapping of currentPointList
//
//                  templates:          object to store list of template gestures
//
//                  tempTime:           time since last sample
//
//      Methods:    Documentation is above each significant function

public class GestureRecognizer : MonoBehaviour {

    public bool recording = true;
    public bool anomaliesTesting = false;
    public string templateSaveName;
    public int pointsPerGesture = 30;
    public float samplingRate = 0.01f;
    public bool limitSamples = false;
    public int maxPointsAllowed = 100;
    public float standardRatio = 100f;
    public float devTightness = 1f;
    public float anomaliesFactor = 5f;

    [SerializeField]private bool gestureStarted;
    [SerializeField] private bool gestureComplete;
    [SerializeField] private bool inputReady;

    [SerializeField] private string gestureFileName = "gestures.json";
    [SerializeField] private TwoDPoint startPoint;
    [SerializeField] private TwoDPoint currentPoint;
    [SerializeField] private DrawnGesture currentGesture;
    [SerializeField] private List<TwoDPoint> currentPointList;
    [SerializeField] private TwoDPoint[] reducedPoints;
    [SerializeField] private GestureTemplates templates;
    [SerializeField] private float tempTime = 0f;




    private void Awake()
    {

    }

    void Start () {
        LoadTemplates();
        varInitialization();
    }

    #region variable initialization and reset
    private void varInitialization()
    {
        currentPoint = new TwoDPoint(0, 0);
        startPoint = new TwoDPoint(0, 0);
        currentPointList = new List<TwoDPoint>();
        currentPointList.Add(new TwoDPoint(0, 0));
        reducedPoints = new TwoDPoint[pointsPerGesture];
        for (int i = 0; i < pointsPerGesture; i++)
        {
            reducedPoints[i] = new TwoDPoint(0, 0);
        }
        gestureStarted = false;
        gestureComplete = false;
        inputReady = false;
        currentGesture = new DrawnGesture("currentGesture", pointsPerGesture);
    }


    private void varReset()
    {
        for (int i = 0; i < pointsPerGesture; i++)
        {
            reducedPoints[i].SetX(0);
            reducedPoints[i].SetY(0);
        }
        currentPointList.Clear();
        currentPointList.Add(new TwoDPoint(0,0));
        gestureStarted = false;
        gestureComplete = false;
    }

    #endregion

    void Update() {
        tempTime += Time.deltaTime;
        if (Input.GetMouseButton(0))
        {
            if (inputReady)
            {
                if (!gestureStarted)
                {
                    gestureStarted = true;
                    StartGesture();
                }
                if ((!gestureComplete) && (tempTime > samplingRate))
                {
                    tempTime = 0f;
                    ContinueGesture();
                }
                if (gestureComplete)
                {
                    EndGesture();

                }
            }
        } else
        {
            if (gestureStarted)
            {
                EndGesture();
            }
            inputReady = true;
        }
    }


    //******************************************
    //      Save and Load Gestures
    //
    //      SaveTemplates
    //      use:                writes templates to json file
    //      LoadTemplates
    //      use:                called on start to read json templates
    //                          object from file if it's there
    [SerializeField]
    private void SaveTemplates()
    {
        string filePath = Application.dataPath + "/StreamingAssets/" + gestureFileName;
        string saveData = JsonUtility.ToJson(templates);
        File.WriteAllText(filePath, saveData);
        Debug.Log("Template Saved!");
    }
    [SerializeField]
    private void LoadTemplates()
    {
        templates = new GestureTemplates();
        string filePath = Path.Combine(Application.streamingAssetsPath, gestureFileName);
        if (File.Exists(filePath))
        {
            string data = File.ReadAllText(filePath);
            templates = JsonUtility.FromJson<GestureTemplates>(data);

            Debug.Log("Templates Loaded!");


        }
    }


    //***************************************
    //      StartGesture
    //
    //      use:            Set up recording of gesture by
    //                      setting the start point and control bool.
    //                      Called when player first clicks.
    [SerializeField]
    private void StartGesture()
    {
        Debug.Log("gesture started");
        startPoint.SetX(Input.mousePosition.x);
        startPoint.SetY(Input.mousePosition.y);
        gestureComplete = false;
    }


    //***************************************
    //      ContinueGesture
    //
    //      use:            Update min and max x and y values for
    //                      the current gesture being recorded
    //                      and add the new point to the list.
    //                      Called while player holds input down.
    [SerializeField]
    private void ContinueGesture()
    {
        currentPoint.SetX(Input.mousePosition.x - startPoint.GetX());
        currentPoint.SetY(Input.mousePosition.y - startPoint.GetY());
        currentPointList.Add(new TwoDPoint(currentPoint.GetX(), currentPoint.GetY()));
        if (currentPoint.GetX() > currentGesture.GetMaxX())
        {
            currentGesture.SetMaxX(currentPoint.GetX());
        }
        if (currentPoint.GetX() < currentGesture.GetMinX())
        {
            currentGesture.SetMinX(currentPoint.GetX());
        }
        if (currentPoint.GetY() > currentGesture.GetMaxY())
        {
            currentGesture.SetMaxY(currentPoint.GetY());
        }
        if (currentPoint.GetY() < currentGesture.GetMinY())
        {
            currentGesture.SetMinY(currentPoint.GetY());
        }
        if (limitSamples && currentPointList.Count >= maxPointsAllowed)
        {
            gestureComplete = true;
            Debug.Log(message: "Gesture Complete!");

        }
    }


    //***************************************
    //      EndGesture
    //
    //      use:            Resets control bools and other variables
    //                      records gesture to the templates object
    //                      or calls recognition.
    //                      Called when max recording points reached.
    [SerializeField]
    private void EndGesture()
    {
        if (inputReady) inputReady = false;
        gestureStarted = false;
        gestureComplete = true;
        Rescale(currentGesture);
        MapPoints(currentGesture);
        if (recording)
        {
            currentGesture.SetName(templateSaveName);
            templates.templates.Add(new DrawnGesture(currentGesture.GetName(), pointsPerGesture, currentGesture.GetMaxX(), currentGesture.GetMaxY(),
                currentGesture.GetMinX(), currentGesture.GetMinY(), currentGesture.GetPoints()));



        } else
        {
            DrawnGesture m = FindMatch(currentGesture, templates);
            Debug.Log(m.GetName());
        }
        varReset();
    }
    [SerializeField]
    private void OnApplicationQuit()
    {
        SaveTemplates();
    }

    //***************************************
    //      Rescale
    //
    //      use:        scales recorded list of points to a square field
    //                  of a chosen size by multiplication of the factor
    //                  of the desired size it already is
    //                  Called on every gesture after recording
    [SerializeField]
    private void Rescale(DrawnGesture gesture)
    {
        float scale = 1f;
        float xrange = gesture.GetMaxX() - gesture.GetMinX();
        float yrange = gesture.GetMaxY() - gesture.GetMinY();
        if (xrange >= yrange)
        {
            scale = standardRatio / (gesture.GetMaxX() - gesture.GetMinX());
        } else
        {
            scale = standardRatio / (gesture.GetMaxY() - gesture.GetMinY());
        }
        if (scale != 1)
        {
            foreach (TwoDPoint point in currentPointList)
            {
                point.SetX(point.GetX() * scale);
                point.SetY(point.GetY() * scale);
            }
        }
    }


    //***************************************
    //      MapPoints
    //
    //      use:        maps the list of recorded points to a desired
    //                  number of points by calculating an even distance
    //                  between such a number of points and interpolating
    //                  when that distance is reached upon traversal of the
    //                  list
    //                  Called after scaling on every gesture
    //
    //      param:      gesture:    the object to store the new array
    [SerializeField]
    private void MapPoints(DrawnGesture gesture)
    {
        reducedPoints[0].SetX(currentPointList[0].GetX());
        reducedPoints[0].SetY(currentPointList[0].GetY());
        int newIndex = 1;
        float totalDistance = TotalDistance();
        float coveredDistance = 0;
        float thisDistance = 0;
        float idealInterval = totalDistance / pointsPerGesture;
        for (int i = 0; i < currentPointList.Count - 1; i++)
        {
            thisDistance = PointDistance(currentPointList[i], currentPointList[i + 1]);
            bool passedIdeal = (coveredDistance + thisDistance) >= idealInterval;
            if (passedIdeal)
            {
                TwoDPoint reference = currentPointList[i];
                while (passedIdeal && newIndex < reducedPoints.Length)
                {
                    float percentNeeded = (idealInterval - coveredDistance) / thisDistance;
                    if (percentNeeded > 1f) percentNeeded = 1f;
                    if (percentNeeded < 0f) percentNeeded = 0f;
                    float new_x = (((1f - percentNeeded) * reference.GetX()) + (percentNeeded * currentPointList[i + 1].GetX()));
                    float new_y = (((1f - percentNeeded) * reference.GetY()) + (percentNeeded * currentPointList[i + 1].GetY()));
                    reducedPoints[newIndex] = new TwoDPoint(new_x, new_y);
                    reference = reducedPoints[newIndex];
                    newIndex++;
                    thisDistance = (coveredDistance + thisDistance) - idealInterval;
                    coveredDistance = 0;
                    passedIdeal = (coveredDistance + thisDistance) >= idealInterval;
                }
                coveredDistance = thisDistance;
            } else
            {
                coveredDistance += thisDistance;
            }
            gesture.SetPoints(reducedPoints);
        }

    }


    //***************************************
    //      FindMatch
    //
    //      use:        determines template gesture with the minimum
    //                  average distance between points to the 
    //                  currently recorded gesture
    //                  Called after finishing a gesture when not
    //                  recording
    //
    //      param:      playerGesture:  current gesture to be matched
    //                  templates:      object containting list of 
    //                                  gestures to compare against
    //
    //      return:     returns gesture object of the minimum 
    //                  difference template
    [SerializeField]
    private DrawnGesture FindMatch(DrawnGesture playerGesture, GestureTemplates templates)
    {
        float minAvgDifference = float.MaxValue;
        DrawnGesture match = new DrawnGesture("no match", pointsPerGesture);
        foreach(DrawnGesture template in templates.templates)
        {
            Debug.Log(template.GetName());
            float d = AverageDifference(playerGesture, template);
            Debug.Log(d.ToString());
            if (d < minAvgDifference)
            {
                minAvgDifference = d;
                match = template;               
            }
        }
        return match;
    }


    //***************************************
    //      AverageDifference
    //
    //      use:        caluclates the average distance between 
    //                  the points of two gestures
    //
    //      param:      playerGesture:  first to be compared
    //                  template:       gesture to be compared against
    //
    //      return:     returns float value of the average distance
    //                  between points of two parameter gestures
    [SerializeField]
    private float AverageDifference(DrawnGesture playerGesture, DrawnGesture template)  
    {
        int numPoints = playerGesture.GetNumPoints();

        if (numPoints != template.GetNumPoints())
        {
            Debug.Log("Number of points differs from templates");
            return -1f;
        }

        float totalDifference = 0;

        for (int i = 0; i < numPoints; i++)
        {
            totalDifference += PointDistance(playerGesture.GetPoints()[i], template.GetPoints()[i]);
        }

        return (totalDifference / numPoints);
    }


    //***************************************
    //      AverageDistanceWithAnomalies
    //
    //      use:        calculates the average difference between 
    //                  the points of two gestures but weighing
    //                  those which deviate significantly by 
    //                  multiplying them
    //                  Both the tightness of this and the factor
    //                  of multiplication are customizable
    //                  above
    //
    //      param:      playerGesture:  first to be compared
    //                  template:       gesture to be compared against
    //
    //      return:     returns float value of the average distance
    //                  between points of two parameter gestures
    //                  with weights
    [SerializeField]
    private float AverageDifferenceWithAnomalies(DrawnGesture playerGesture, DrawnGesture template)
    {
        int numPoints = playerGesture.GetNumPoints();

        if (numPoints != template.GetNumPoints())
        {
            Debug.Log("Number of points differs from templates");
            return -1f;
        }

        float totalDifference = 0;
        float[] sampleDifferences = new float[numPoints];
        float[] sampleDeviations = new float[numPoints];
        float standardDev = 0;

        for (int i = 0; i < numPoints; i++)
        {
            float thisDistance = PointDistance(playerGesture.GetPoints()[i], template.GetPoints()[i]);
            sampleDifferences[i] = thisDistance;
            totalDifference += thisDistance;
        }

        float average = totalDifference / numPoints;

        for (int i = 0; i < numPoints; i++)
        {
            sampleDeviations[i] = Math.Abs(sampleDifferences[i] - average);
            standardDev += sampleDifferences[i];
        }

        standardDev = standardDev / numPoints;

        for (int i = 0; i < numPoints; i++)
        {
            if (Math.Abs(sampleDeviations[i]) > devTightness * standardDev)
            {
                totalDifference -= sampleDifferences[i];
                totalDifference += anomaliesFactor * sampleDifferences[i];
            }
        }

        average = totalDifference / numPoints;

        return (average);
    }

    //***************************************
    //      TotalDistance
    //
    //      use:        calculates the total distance covered
    //                  when traversing the current list of recorded
    //                  points in order of recording
    //                  Called when determining ideal intervals
    //                  for mapping onto desired number of points
    [SerializeField]
    private float TotalDistance()
    {
        float totalDistance = 0;
        for(int i = 0; i < currentPointList.Count - 1; i++)
        {
            totalDistance += PointDistance(currentPointList[i], currentPointList[i + 1]);
        }
        Debug.Log("total distance: " + totalDistance);
        return totalDistance;
    }


    //***************************************
    //      PointDistance
    //
    //      use:        calculates the absolute value of the distance
    //                  between two points using pythagorean theorem
    [SerializeField]
    private float PointDistance(TwoDPoint a, TwoDPoint b)
    {
        float xDif = a.GetX() - b.GetX();
        float yDif = a.GetY() - b.GetY();
        return Mathf.Sqrt((xDif * xDif) + (yDif * yDif));
    }
}





//******************************************************* Templates ******************************************************//
//
//      Use:    Groups gestures to be used for comparison to a player's attempts

[Serializable]
public class GestureTemplates
{

    public List<DrawnGesture> templates;

    public GestureTemplates()
    {
        templates = new List<DrawnGesture>();
    }

}





//******************************************************** Gestures ******************************************************//
//
//      Use:    Groups all information pertinent to a 'gesture'
//              which is essentially a single stroke drawing represented by points
//
//      Fields:     points:     list of points representing the gesture, only populated once a hand drawn gesture is 
//                              reduced by the MapPoints method
//
//                  min/max:    these are the minimum and maximum x and y values of the points (starting point 
//                              is used as the origin)
//
//                  numPoints:  the size of the points array (set to a variable of the GestureRecognizer class to 
//                              keep control there)
//
//                  name:       string that will be returned when matched with a non-recorded gesture
//
//      Methods:    Initializer(2 parameters):  use when creating a new gesture for later use
//
//                  Initializer(7 parameters):  use when copying data from another gesture
//
//                  Reset:                      for use in clearing the gesture used for each player gesture attempt

[Serializable]
public class DrawnGesture
{
    [SerializeField]private TwoDPoint[] points;
    [SerializeField] private string name;
    [SerializeField] private float maxX;
    [SerializeField] private float minX;
    [SerializeField] private float maxY;
    [SerializeField] private float minY;
    [SerializeField] private int numPoints;

    public DrawnGesture(string newName, int pointsPerGesture)
    {
        numPoints = pointsPerGesture;
        points = new TwoDPoint[numPoints];
        name = newName;
        maxX = 0;
        maxY = 0;
    }
    public DrawnGesture(string newName, int pointsPerGesture, float max_x, float max_y, float min_x, float min_y, TwoDPoint[] newPoints)
    {
        numPoints = pointsPerGesture;
        points = new TwoDPoint[numPoints];
        SetPoints(newPoints);
        name = newName;
        maxX = max_x;
        minX = min_x;
        maxY = max_y;
        minY = min_y;
    }
    public void Reset()
    {
        maxX = 0;
        minX = 0;
        maxY = 0;
        minY = 0;
        name = "";
        Array.Clear(points, 0, numPoints);
    }

    public TwoDPoint[] GetPoints()
    {
        return points;
    }
    public void SetPoints(TwoDPoint[] new_points)
    {
        for(int i = 0; i < numPoints; i++)
        {
            points[i] = new TwoDPoint(new_points[i].GetX(), new_points[i].GetY());
        }
    }
    public string GetName()
    {
        return name;
    }
    public void SetName(string n)
    {
        name = n;
    }
    public float GetMaxX()
    {
        return maxX;
    }
    public void SetMaxX(float x)
    {
        maxX = x;
    }
    public float GetMaxY()
    {
        return maxY;
    }
    public void SetMaxY(float y)
    {
        maxY = y;
    }
    public float GetMinY()
    {
        return minY;
    }
    public void SetMinY(float y)
    {
        minY = y;
    }
    public float GetMinX()
    {
        return minX;
    }
    public void SetMinX(float x)
    {
        minX = x;
    }
    public int GetNumPoints()
    {
        return numPoints;
    }
    public void SetNumPoints(int n)
    {
        numPoints = n;
    }
}






//******************************************************** Points ********************************************************//
//
//      Use:    This is a class to maintain 2D coordinates
//      
//      Fields:     x:  the x coordinate (relative to the first point when recorded)
//                  y:  the y coordinate (also relative to first point)

public class TwoDPoint
{
    private float x;
    private float y;

    public TwoDPoint(float startx, float starty)
    {
        x = startx;
        y = starty;
    }

    public float GetX()
    {
        return x;
    }
    public void SetX(float new_x)
    {
        x = new_x;
    }
    public float GetY()
    {
        return y;
    }
    public void SetY(float new_y)
    {
        y = new_y;
    }
} 
错误如下:

NullReferenceException:对象引用未设置为对象的实例 GestureRecognizer.AverageDifference DrawngeStrue PlayerTesture,位于Assets/Scripts/GestureRecognizer.cs:475的DrawngeStrue模板 GestureRecognizer.FindMatch DrawnGesture PlayerEsture,位于Assets/Scripts/GestureRecognizer.cs:437的GestureTemplates模板 位于Assets/Scripts/gesturecognizer.cs:319的gesturecognizer.endsignature GestureRecognizer.Update at Assets/Scripts/GestureRecognizer.cs:200

重要: 我没有开发脚本,我只是想让它工作。 提前谢谢

一般来说:首先,逐行检查它,以便准确地看到空值

我刚这么做了,问题如下:

类TwoDPoint不可序列化!因此,类型为TwoDPoint[]的DrawnGesture.points字段也不可序列化

因此,当您生成JSON时,将跳过此字段。有两个地方您可能已经注意到:

您只需在保存的文件中确认即可

{"templates":[{"name":"","maxX":245.0,"minX":-4.0,"maxY":263.0,"minY":0.0,"numPoints":30}]}
&向右箭头;没有指定点的字段

您可以看到,该字段点实际上没有出现在Unity Inspector中

由于检查器使用了与JsonUtility相同的功能,因此这应该是有问题的提示

因此,稍后在LoadTemplates中加载文件时,只需执行以下操作

templates = JsonUtility.FromJson<GestureTemplates>(data);
这几乎可以解决它

下一个问题是这个类也不包含可序列化的字段!您可以通过再次标记它们来更改此设置:

实际上,整个getter setter在这种情况下没有多大意义。。。您也可以简单地公开您的字段

并将其余代码调整为直接读写X和Y,而不是使用get和set方法。可序列化类型的公共字段将自动序列化

您已经可以在Unity Inspector中看到的更改!如前所述:如果一个字段没有出现在这里,它也不会通过JsonUtility进行序列化,反之亦然

正如您所看到的,现在模板中有一个名为points的字段

它现在也出现在JSON中

{"templates":[{"points":[{"x":0.0,"y":0.0},{"x":-1.2672364711761475,"y":-4.435328006744385},{"x":-2.534141778945923,"y":-8.870744705200196},{"x":-3.6529128551483156,"y":-13.34582805633545},{"x":-4.771683692932129,"y":-17.820911407470704},{"x":-5.8904547691345219,"y":-22.295995712280275},{"x":-7.009225845336914,"y":-26.771080017089845},{"x":-8.127996444702149,"y":-31.246164321899415},{"x":-9.246767044067383,"y":-35.721248626708987},{"x":-10.365538597106934,"y":-40.19633102416992},{"x":-11.484309196472168,"y":-44.67141342163086},{"x":-12.603079795837403,"y":-49.1464958190918},{"x":-13.577103614807129,"y":-53.655174255371097},{"x":-14.543621063232422,"y":-58.165592193603519},{"x":-15.510139465332032,"y":-62.67601013183594},{"x":-16.438554763793947,"y":-67.18716430664063},{"x":-15.64819049835205,"y":-71.73175811767578},{"x":-14.857826232910157,"y":-76.27635192871094},{"x":-14.067461967468262,"y":-80.8209457397461},{"x":-13.277097702026368,"y":-85.36553955078125},{"x":-12.486733436584473,"y":-89.91014099121094},{"x":-11.696369171142579,"y":-94.4547348022461},{"x":-8.638381004333496,"y":-96.89103698730469},{"x":-4.163297653198242,"y":-98.00981140136719},{"x":0.31178566813468935,"y":-99.12857818603516},{"x":4.815909385681152,"y":-99.94639587402344},{"x":9.422344207763672,"y":-99.70394897460938},{"x":14.028779029846192,"y":-99.46150970458985},{"x":18.63521385192871,"y":-99.21906280517578},{"x":23.241649627685548,"y":-98.97662353515625}],"name":"","maxX":35.0,"minX":-13.0,"maxY":0.0,"minY":-79.0,"numPoints":30}]}
当然,它也是在加载后填充有效值的


不是这个问题的合适标签。@PeterDuniho在很多情况下,我也会将其作为一个副本来结束,但对于Unity初学者来说,这通常非常令人沮丧。它解释了什么是NullReferenceException以及如何调试和查找它。然而,在这个特定的案例中,它根本没有解释OP为什么会发生这种情况,以及如何解决它,这对于初学者来说不是很简单。事实上,我只是在你再次关闭它之前重新打开它,以便发布一个有用的答案@德胡戈:欢迎你发表意见。我不分享它。就我而言,没有任何问题比我得到的这个NullReferenceException对问题陈述的影响更大,它只能是对规范的复制。当它只是一堵代码墙,而不是一个适当的。这个问题对未来的任何人都没有帮助,因为如果他们有同样的问题,但已经做了尽职调查,那么任何人都无法找到它。因此,根本没有理由保留它,更不用说重新打开它了。非常感谢你,@derHugo!我几乎要为这件事发疯了。正如我所说,我试图序列化所有内容,但没有注意到最后的x和y浮点尚未序列化。愚蠢的我甚至没有注意到遗漏的要点。感谢您抽出时间,非常感谢!
[System.Serializable] 
public class TwoDPoint 
{ 
    [SerializeField] private float x;
    [SerializeField] private float y;

    ...
}
[System.Serializable] 
public class TwoDPoint 
{ 
    public float X;
    public float Y;

    public TwoDPoint(float x, float y)
    {
        X = x;
        Y = y;
    }
}
{"templates":[{"points":[{"x":0.0,"y":0.0},{"x":-1.2672364711761475,"y":-4.435328006744385},{"x":-2.534141778945923,"y":-8.870744705200196},{"x":-3.6529128551483156,"y":-13.34582805633545},{"x":-4.771683692932129,"y":-17.820911407470704},{"x":-5.8904547691345219,"y":-22.295995712280275},{"x":-7.009225845336914,"y":-26.771080017089845},{"x":-8.127996444702149,"y":-31.246164321899415},{"x":-9.246767044067383,"y":-35.721248626708987},{"x":-10.365538597106934,"y":-40.19633102416992},{"x":-11.484309196472168,"y":-44.67141342163086},{"x":-12.603079795837403,"y":-49.1464958190918},{"x":-13.577103614807129,"y":-53.655174255371097},{"x":-14.543621063232422,"y":-58.165592193603519},{"x":-15.510139465332032,"y":-62.67601013183594},{"x":-16.438554763793947,"y":-67.18716430664063},{"x":-15.64819049835205,"y":-71.73175811767578},{"x":-14.857826232910157,"y":-76.27635192871094},{"x":-14.067461967468262,"y":-80.8209457397461},{"x":-13.277097702026368,"y":-85.36553955078125},{"x":-12.486733436584473,"y":-89.91014099121094},{"x":-11.696369171142579,"y":-94.4547348022461},{"x":-8.638381004333496,"y":-96.89103698730469},{"x":-4.163297653198242,"y":-98.00981140136719},{"x":0.31178566813468935,"y":-99.12857818603516},{"x":4.815909385681152,"y":-99.94639587402344},{"x":9.422344207763672,"y":-99.70394897460938},{"x":14.028779029846192,"y":-99.46150970458985},{"x":18.63521385192871,"y":-99.21906280517578},{"x":23.241649627685548,"y":-98.97662353515625}],"name":"","maxX":35.0,"minX":-13.0,"maxY":0.0,"minY":-79.0,"numPoints":30}]}