public AnimalData Mate()
    {
        //krzyzowanie polega na "konkursie" wynialajacym dwoch rodzicow na jednego osobnika potomnego
        //osobniki o wiekszym fitness maja wieksza szanse na zostanie wybranym do krzyzowania
        //losowana jest wartosc pomiedzy minimalna wartoscia z fitness list a maksymalna wartoscia z fitness list
        //losowany index osobnika dopoki wylosowana wczesniej wartosc nie jest mniejsza od wartosci fitness danego indexu
        //losowani dwaj rodzice, rozmnazanie jak wczesniej
        float      crossoverPower = 0.5f;
        AnimalData childData      = new AnimalData();
        int        parent1Index   = ChooseParent();
        float      parent1MGene   = _currentGeneration[parent1Index].animalData.mGene;

        childData.mGene = parent1MGene;
        int   parent2Index = ChooseParent();
        float chance;
        int   currentIteration = 0;

        while (true)
        {
            parent2Index = ChooseParent();
            float parent2MGene = _currentGeneration[parent2Index].animalData.mGene; //jeden z rodzicow przekazuje mGene
            currentIteration++;
            if (parent1MGene != parent2MGene)                                       //jesli to ten sam index to taki sam mGene, dlatego nie trzeba tego sprawdzac
            {
                break;
            }
            else if (currentIteration >= _matingMaxIterations)
            {
                if (parent1Index != parent2Index)
                {
                    break;
                }
            }
        }
        AnimalBrain childBrain = new AnimalBrain();

        childBrain.DeepCopyFrom(_currentGeneration[parent1Index].animalData.animalBrain);
        childData.partsMass               = MixDictionaries <float>(_currentGeneration[parent1Index].animalData.partsMass, _currentGeneration[parent2Index].animalData.partsMass, crossoverPower);
        childData.partsScaleMultiplier    = MixDictionaries <System.Numerics.Vector3>(_currentGeneration[parent1Index].animalData.partsScaleMultiplier, _currentGeneration[parent2Index].animalData.partsScaleMultiplier, crossoverPower);
        childData.targetJointsVelocity    = MixDictionaries <System.Numerics.Vector3>(_currentGeneration[parent1Index].animalData.targetJointsVelocity, _currentGeneration[parent2Index].animalData.targetJointsVelocity, crossoverPower);
        childData.limbsPositionMultiplier = MixDictionaries <System.Numerics.Vector3>(_currentGeneration[parent1Index].animalData.limbsPositionMultiplier, _currentGeneration[parent2Index].animalData.limbsPositionMultiplier, crossoverPower);
        childBrain.mixWeights(_currentGeneration[parent2Index].animalData.animalBrain, crossoverPower);
        float mutationRate;
        float physicalMutationRate;

        if (_ifAdaptive)
        {
            physicalMutationRate = childData.physicalMutationRate;
            mutationRate         = childData.weightsMutationRate;
        }
        else
        {
            physicalMutationRate = _globalPhyscialMutationRate;
            mutationRate         = _globalMutationRate;
        }
        for (int i = 0; i < _numberOfWeightsMutations; i++)
        {
            chance = Random.Range(0.0f, 1.0f);
            if (chance < mutationRate) //obsluga mutacji - mutacja obejmuje zmiane indexu genu z losowym innym genem
            {
                childBrain.MutateWeights();
            }
        }
        chance = Random.Range(0.0f, 1.0f);  //szanse na mutacje wag i mutacje fizycznych wlasciwosci sa inne
        if (chance < physicalMutationRate)
        {
            childData.MutateData();
        }
        childData.animalBrain = childBrain;
        return(childData);
    }