Пример #1
0
    private void CreateNoteList()
    {
        int bpm     = NoteDataLoader.Instance.BPM;
        int maxBeat = NoteDataLoader.Instance.MaxBeat;

        JsonData jsonData = NoteDataLoader.Instance.NoteData;

        JsonData jsonBar = jsonData["Note"];

        for (int i = 0; i < jsonBar.Count; i++)
        {
            JsonData jsonBeat = jsonBar[i];
            for (int j = 0; j < jsonBeat.Count; j++)
            {
                JsonData jsonNote = jsonBeat[j];
                for (int k = 0; k < jsonNote.Count; k++)
                {
                    JsonData jsonNoteData = jsonNote[k];

                    NoteType     type      = (NoteType)(int)jsonNoteData["Type"];
                    int          length    = (int)jsonNoteData["Length"];
                    int          slideTime = (int)jsonNoteData["SlideTime"];
                    NoteSlideWay slideWay  = (NoteSlideWay)(int)jsonNoteData["SlideWay"];
                    bool         roundTrip = (bool)jsonNoteData["RoundTrip"];

                    Note note = null;

                    if (type == NoteType.TAP)
                    {
                        GameObject noteObject = Instantiate <GameObject>(TapNotePrefab);

                        note          = noteObject.GetComponent <TapNote>();
                        note.Type     = type;
                        note.TimeSeen = ((60.0f / bpm) / (maxBeat / 4)) * ((i * maxBeat) + j);
                    }
                    else if (type == NoteType.LONG)
                    {
                        GameObject noteObject = Instantiate <GameObject>(LongNotePrefab);

                        note          = noteObject.GetComponent <LongNote>();
                        note.Type     = type;
                        note.Length   = length;
                        note.TimeSeen = ((60.0f / bpm) / (maxBeat / 4)) * ((i * maxBeat) + j);
                    }
                    else if (type == NoteType.SLIDE)
                    {
                        GameObject noteObject = Instantiate <GameObject>(SlideNotePrefab);
                        GameObject pathObject = null;

                        CreateSlidePath(ref pathObject, length, slideWay, roundTrip, k);

                        SlideNote slideNote = noteObject.transform.FindChild("NoteImage").GetComponent <SlideNote>();
                        slideNote.maskImage = pathObject.GetComponent <Image>();
                        slideNote.pathImage = pathObject.transform.FindChild("PathImage").GetComponent <Image>();

                        note           = slideNote;
                        note.Type      = type;
                        note.Length    = length;
                        note.SlideTime = slideTime;
                        note.SlideWay  = slideWay;
                        note.RoundTrip = roundTrip;
                        note.TimeSeen  = ((60.0f / bpm) / (maxBeat / 4)) * ((i * maxBeat) + j);
                    }
                    else if (type == NoteType.SNAP)
                    {
                        GameObject noteObject = Instantiate <GameObject>(SnapNotePrefab);

                        note          = noteObject.GetComponent <SnapNote>();
                        note.Type     = type;
                        note.TimeSeen = ((60.0f / bpm) / (maxBeat / 4)) * ((i * maxBeat) + j);
                    }

                    if (note != null)
                    {
                        note.SetNoteActive(false);
                        if (type == NoteType.SLIDE)
                        {
                            note.transform.parent.position    = new Vector3(0.0f, 0.0f, -1.0f);
                            note.transform.parent.eulerAngles = new Vector3(0.0f, 0.0f, -45.0f * k);
                            note.transform.eulerAngles        = new Vector3(0.0f, 0.0f, 0.0f);
                        }
                        else if (type == NoteType.SNAP)
                        {
                            note.transform.position = new Vector3(0.0f, 0.0f, -1.0f);
                        }
                        else
                        {
                            note.transform.position = notePosition[k].position;
                        }

                        m_noteList.Add(note);

                        if (type == NoteType.SNAP)
                        {
                            break;
                        }
                    }
                }
            }
        }
    }
Пример #2
0
        private readonly Regex _arrow   = CompileRule("mmm5x: notes");           //Hex bar, B36 startLane, notes


        public Song ParseSong(string filename)
        {
            var theSourceFile = new FileInfo(filename);
            var reader        = theSourceFile.OpenText();

            var data = new List <string>();

            while (true)
            {
                string line = reader.ReadLine();
                if (line == null)
                {
                    break;
                }
                if (line.StartsWith("#"))
                {
                    data.Add(line.Substring(1));
                }
            }

            bool[] hasBeenRead = new bool[data.Count];

            int  ticksPerBeat = 480; //default
            int  beatsPerBar  = 4;   //default TODO this is being ignored! should be slowing down approach rate/effective BPM when increased
            int  barOffset    = 0;   //default
            Song song         = new Song();


            var barSections    = new List <BarSection>();
            var bpmDefinitions = new Dictionary <int, double>();//BPM ID -> BPM value

            string difficulty = null;

            //PASS 1: construct base rhythm model
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _barLength.Match(line);
                if (match.Success)
                {
                    barSections.Add(new BarSection(ParseMmm(match.Groups[1]), ParseInt(match.Groups[2])));
                    continue;
                }

                match = _bpmDef.Match(line);
                if (match.Success)
                {
                    bpmDefinitions.Add(ParseZz(match.Groups[1]), ParseDouble(match.Groups[2]));
                    continue;
                }

                //also parse song info while we're at it:

                match = _wave.Match(line);
                if (match.Success)
                {
                    song.AudioFile = match.Groups[1].Value;
                    continue;
                }

                match = _waveOffset.Match(line);
                if (match.Success)
                {
                    song.Offset = ParseDouble(match.Groups[1]);
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }

            //We now have enough information to construct the Bar->Beat converter
            var barToBeat = BpmGraph.ParseBars(barSections);

            var bpmChanges = new List <BeatEvent>();

            //PASS 2: construct internal rhythm model
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _bpmChange.Match(line);
                if (match.Success)
                {
                    bpmChanges.Add(new BeatEvent(barToBeat.BeatAt(ParseMmm(match.Groups[1])), bpmDefinitions[ParseInt(match.Groups[2])]));
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }
            //We can now construct our BPM Graph, which means we now have everything needed to start parsing notes.
            bpmChanges.Sort((a, b) => a.beat.CompareTo(b.beat));
            BpmGraph bpmGraph = BpmGraph.ParseBpm(bpmChanges, Array.Empty <BeatEvent>());

            var notes = new List <RhythmEvent>();

            var holdData  = new Dictionary <int, List <PartialData> >();
            var slideData = new Dictionary <int, List <PartialData> >();
            var airData   = new Dictionary <int, List <PartialData> >();

            //PASS 3: read all note data
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _tap.Match(line);
                if (match.Success)
                {
                    int barNum = ParseMmm(match.Groups[1]) + barOffset;
                    int lane   = ParseXY(match.Groups[2]);
                    ProcessNotes(match.Groups[3], (barOff, type, width) =>
                    {
                        double beat = barToBeat.BeatAt(barNum + barOff);
                        var time    = bpmGraph.FromBeat(beat);
                        var pos     = new LanePosition(lane * 2, width * 2);
                        switch (type)
                        {
                        case 1:                    //standard
                            notes.Add(new SimpleNote(time, pos, false));
                            break;

                        case 2:                    //golden
                            notes.Add(new SimpleNote(time, pos, true));
                            break;

                        case 3:                    //swipe
                            notes.Add(new SwipeNote(time, pos));
                            break;

                        case 4:                    //mine
                            notes.Add(new Mine(time, pos));
                            break;

                        //TODO types 5 & 6
                        default:
                            Debug.LogError($"Ignoring unknown tap type {type} at time {time} position {pos}");
                            break;
                        }
                    });


                    continue;
                }

                match = _arrow.Match(line);
                if (match.Success)
                {
                    int barNum = ParseMmm(match.Groups[1]) + barOffset;
                    int lane   = ParseXY(match.Groups[2]);
                    ProcessNotes(match.Groups[3], (barOff, type, width) =>
                    {
                        double beat    = barToBeat.BeatAt(barNum + barOff);
                        var time       = bpmGraph.FromBeat(beat);
                        var pos        = new LanePosition(lane * 2, width * 2);
                        bool isUp      = true;
                        int arrowShift = 0;
                        //convert the 6 cases to up/down/left/right/center information
                        switch (type)
                        {
                        case 1:
                            isUp       = true;
                            arrowShift = 0;
                            break;

                        case 2:
                            isUp       = false;
                            arrowShift = 0;
                            break;

                        case 3:
                            isUp       = true;
                            arrowShift = -1;
                            break;

                        case 4:
                            isUp       = true;
                            arrowShift = +1;
                            break;

                        case 5:
                            isUp       = false;
                            arrowShift = +1;                        //inverted because down
                            break;

                        case 6:
                            isUp       = false;
                            arrowShift = -1;                        //inverted because down
                            break;

                        default:
                            Debug.LogError($"Unknown Air Arrow {type} at time {time} position {pos}");
                            break;
                        }
                        notes.Add(new AirArrow(time, pos, isUp, arrowShift));
                    });

                    continue;
                }
                //matching holds, airholds and slides are basically exactly the same, the only thing that changes is the output array

                match = _hold.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, holdData);
                    continue;
                }

                match = _airHold.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, airData);
                    continue;
                }

                match = _slide.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, slideData);
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }

            //all note data has now been read. We'll now assemble all multi-part notes.

            //sort note data and store length so we can use this for lookups on start/end of holds (TODO)
            notes.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
            int noteDataLength = notes.Count;

            //TODO a lot of code below for (air)holds/slides is shared, may be useful to combine these into a generic function

            //holds:
            foreach (var holdChannel in holdData.Values)
            {
                //first sort all hold data in this channel
                holdChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                PartialData?startingPoint = null;

                foreach (var dataPoint in holdChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (startingPoint != null)
                        {
                            Debug.LogError($"Hold started on channel with already active hold at {startingPoint?.Time}");
                        }
                        startingPoint = dataPoint;
                        break;

                    case 2:                //end
                        if (startingPoint == null)
                        {
                            Debug.LogError($"Attempt to end hold at {dataPoint.Time} without a starting point.");
                            continue;
                        }

                        var holdNote = new HoldNote(startingPoint.Value.Time, dataPoint.Pos, new SlidePoint[1]);
                        holdNote.SlidePoints[0] = new SlidePoint(holdNote, dataPoint.Time, holdNote.Position);
                        notes.Add(holdNote);
                        startingPoint = null;
                        break;

                    case 3:                                             //relay
                        Debug.LogWarning($"Skipping hold relay point"); //TODO (will be fixed if we merge slide and hold parsing code)
                        break;

                    default:
                        Debug.LogError($"Unknown hold point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //air holds:
            foreach (var airChannel in airData.Values)
            {
                //first sort all hold data in this channel
                airChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                PartialData?startingPoint = null;

                foreach (var dataPoint in airChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (startingPoint != null)
                        {
                            Debug.LogError($"Air hold started on channel with already active hold at {startingPoint?.Time}");
                        }
                        startingPoint = dataPoint;
                        break;

                    case 2:                //end
                        if (startingPoint == null)
                        {
                            Debug.LogError($"Attempt to end air hold at {dataPoint.Time} without a starting point.");
                            continue;
                        }

                        var airHold = new AirHold(startingPoint.Value.Time, dataPoint.Pos, new SlidePoint[1]);
                        airHold.SlidePoints[0] = new SlidePoint(airHold, dataPoint.Time, airHold.Position);
                        notes.Add(airHold);
                        notes.Add(new AirAction(dataPoint.Time, dataPoint.Pos));
                        startingPoint = null;
                        break;

                    case 3:                //relay
                        notes.Add(new AirAction(dataPoint.Time, dataPoint.Pos));
                        break;

                    default:
                        Debug.LogError($"Unknown hold point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //slides:
            foreach (var slideChannel in slideData.Values)
            {
                //first sort all hold data in this channel
                slideChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                var dataPoints = new List <PartialData>();

                foreach (var dataPoint in slideChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (dataPoints.Count > 0)
                        {
                            Debug.LogError($"Hold started on channel with already active slide at {dataPoints[0].Time}");
                        }
                        dataPoints.Add(dataPoint);
                        break;

                    case 2:                //end
                        if (dataPoints.Count == 0)
                        {
                            Debug.LogError($"Attempt to end slide at {dataPoint.Time} without a starting point.");
                            continue;
                        }
                        dataPoints.Add(dataPoint);
                        //we now have all points, create a SlideNote from this:
                        var  slidePoints             = new List <SlidePoint>();
                        var  slideNote               = new SlideNote(dataPoints[0].Time, dataPoints[0].Pos, null);
                        Note previous                = slideNote;
                        SlidePoint.AnchorNote anchor = null;
                        for (int i = 1; i < dataPoints.Count; i++)                    //start from 1, the initial point is the base SlideNote
                        {
                            var pointHere = dataPoints[i];
                            if (pointHere.Type == 4)
                            {
                                //anchor point
                                anchor = new SlidePoint.AnchorNote(pointHere.Time, pointHere.Pos);
                                continue;
                            }

                            var newPoint = new SlidePoint(previous, pointHere.Time, pointHere.Pos);
                            newPoint.AnchorPoint = anchor;                        //writes anchor if set, null otherwise
                            anchor           = null;
                            newPoint.Visible = pointHere.Type == 3;               //set visible/invisible
                            slidePoints.Add(newPoint);
                            previous = newPoint;
                        }
                        slideNote.SlidePoints = slidePoints.ToArray();
                        notes.Add(slideNote);
                        dataPoints.Clear();
                        break;

                    case 3:                //relay
                    case 4:                //bezier anchor
                    case 5:                //invisible relay
                        if (dataPoints.Count == 0)
                        {
                            Debug.LogError($"Attempt to add points to slide at {dataPoint.Time} without a starting point.");
                            continue;
                        }
                        dataPoints.Add(dataPoint);
                        break;

                    default:
                        Debug.LogError($"Unknown slide point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //finally, sort the notes and return the finished chart
            notes.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));

            //TODO store difficulty in here
            var chart = new Chart("undefined", bpmGraph, notes);

            song.Charts = new List <Chart>();
            song.Charts.Add(chart);

            return(song);
        }