voidLateUpdate(){ //for all wheels for(int i = 0; i < wheelMeshes.Length; i++){ //set the wheel mesh to the position of the wheel collider Quaternion quat; Vector3 pos; wheelColliders[i].GetWorldPose(out pos, out quat); wheelMeshes[i].position = pos; //rotate the wheel wheelMeshes[i].Rotate(Vector3.right * Time.deltaTime * wheelRotateSpeed); } if(Input.GetMouseButton(0) || Input.GetAxis("Horizontal") != 0){ UpdateTargetRotation(); } elseif(targetRotation != 0){ //rotate back to the center targetRotation = 0; } //apply the rotation by rotating towards the target angle on the y axis Vector3 rotation = new Vector3(transform.localEulerAngles.x, targetRotation, transform.localEulerAngles.z); transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(rotation), rotateSpeed * Time.deltaTime); }
voidUpdateTargetRotation(){ if(Input.GetAxis("Horizontal") == 0){ //get the mouse position if(Input.mousePosition.x > Screen.width * 0.5f){ //rotate right targetRotation = rotationAngle; } else{ //rotate left targetRotation = -rotationAngle; } } else{ targetRotation = (int)(rotationAngle * Input.GetAxis("Horizontal")); } }
voidStart(){ beginPoints = new Vector3[(int)dimensions.x + 1]; //start by generating two world pieces for(int i = 0; i < 2; i++){ GenerateWorldPiece(i); } }
用一个数组收集生成的地图块,方便切换
1 2 3 4 5 6 7 8 9
voidGenerateWorldPiece(int i){ //create a new cylinder and put it in the pieces array pieces[i] = CreateCylinder(); //position the cylinder according to its index pieces[i].transform.Translate(Vector3.forward * (dimensions.y * scale * Mathf.PI) * i); //update this piece so it will have an endpoint and it will move etc. UpdateSinglePiece(pieces[i]); }
voidUpdateSinglePiece(GameObject piece){ //add the basic movement script to our newly generated piece to make it move towards the player BasicMovement movement = piece.AddComponent<BasicMovement>(); //make it move with a speed of globalspeed movement.movespeed = -globalSpeed; //set the rotate speed to the lamp (directional light) rotate speed if(lampMovement != null) movement.rotateSpeed = lampMovement.rotateSpeed; //create an endpoint for this piece GameObject endPoint = new GameObject(); endPoint.transform.position = piece.transform.position + Vector3.forward * (dimensions.y * scale * Mathf.PI); endPoint.transform.parent = piece.transform; endPoint.name = "End Point"; //change the perlin noise offset to make sure each piece is different from the last one offset += randomness; //change the obstacle chance which means there will be more obstacles over time if(startObstacleChance > 5) startObstacleChance -= obstacleChanceAcceleration; }
IEnumerator UpdateWorldPieces(){ //remove the first piece (that is not visible to the player anymore) Destroy(pieces[0]); //assign the second piece to the first piece in the world array pieces[0] = pieces[1]; //new create a new second piece pieces[1] = CreateCylinder(); //position the new piece and rotate it to match the first piece pieces[1].transform.position = pieces[0].transform.position + Vector3.forward * (dimensions.y * scale * Mathf.PI); pieces[1].transform.rotation = pieces[0].transform.rotation; //update this newly generated world piece UpdateSinglePiece(pieces[1]); //wait a frame yieldreturn0; }
voidLateUpdate(){ //if the second piece is close enough to the player, we can remove the first piece and update the terrain if(pieces[1] && pieces[1].transform.position.z <= 0) StartCoroutine(UpdateWorldPieces()); //update all items in the scene like spikes and gates UpdateAllItems(); } voidUpdateAllItems(){ //find all items GameObject[] items = GameObject.FindGameObjectsWithTag("Item"); //for all items for(int i = 0; i < items.Length; i++){ //get all meshrenderers of this item foreach(MeshRenderer renderer in items[i].GetComponentsInChildren<MeshRenderer>()){ //show this item if it's sufficiently close to the player bool show = items[i].transform.position.z < showItemDistance;
if(show) renderer.shadowCastingMode = (items[i].transform.position.y < shadowHeight) ? ShadowCastingMode.On : ShadowCastingMode.Off; //only enable the renderer if we want to show this item renderer.enabled = show; } } }
if(z < startTransitionLength && beginPoints[0] != Vector3.zero){ //if so, we must combine the perlin noise value with the begin points //we need to increase the percentage of the vertice that comes from the perlin noise //and decrease the percentage from the begin point //this way it will transition from the last world piece to the new perlin noise values //the percentage of perlin noise in the vertices will increase while we're moving further into the cylinder float perlinPercentage = z * (1f/startTransitionLength); //don't use the z begin point since it will not have the correct position and we only care about the noise on x and y axis Vector3 beginPoint = new Vector3(beginPoints[x].x, beginPoints[x].y, vertices[index].z); //combine the begin point(which are the last vertices from the previous world piece) and original vertice to smoothly transition to the new world piece vertices[index] = (perlinPercentage * vertices[index]) + ((1f - perlinPercentage) * beginPoint); } elseif(z == zCount){ //it these are the last vertices, update the begin points so the next piece will transition smoothly as well beginPoints[x] = vertices[index]; }
voidLateUpdate(){ //Check if the camera has a target to follow if(!camTarget) return; //Some private variables for the rotation and position of the camera float wantedRotationAngle = camTarget.eulerAngles.y; float wantedHeight = camTarget.position.y + height; float currentRotationAngle = transform.eulerAngles.y; float currentHeight = transform.position.y; currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, wantedRotationAngle, rotationDamping * Time.deltaTime); currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime); Quaternion currentRotation = Quaternion.Euler(0, currentRotationAngle, 0); transform.position = camTarget.position; transform.position -= currentRotation * Vector3.forward * distance; //Set camera postition transform.position = new Vector3(transform.position.x, currentHeight, transform.position.z); //Look at the camera target transform.LookAt(camTarget); }
添加粒子效果
增加粒子和打滑效果,把组件partical system拖上去,添加材质粒子,调整一下角度大小
在轮子底部再创建一个打滑的印记object,打滑效果脚本实现,写一个协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
IEnumerator SkidMark(){ //loops continuesly while(true){ //wait for the delay in between individual skid marks yieldreturnnewWaitForSeconds(skidMarkDelay); //show skidmarks if we need skidmarks now if(skidMarkRoutine){ //for both rear wheels, instantiate a skid mark and parent it to the environment so it moves realistically for(int i = 0; i < skidMarkPivots.Length; i++){ GameObject newskidMark = Instantiate(skidMark, skidMarkPivots[i].position, skidMarkPivots[i].rotation); newskidMark.transform.parent = generator.GetWorldPiece(); newskidMark.transform.localScale = new Vector3(1, 1, 4) * skidMarkSize; } } } }
voidUpdateEffects(){ //if both wheels are off the ground, add force will be true bool addForce = true; //check if we rotated the car since last frame bool rotated = Mathf.Abs(lastRotation - transform.localEulerAngles.y) > minRotationDifference; //for both grass effects (rear wheels) for(int i = 0; i < 2; i++){ //get the rear wheels (one of them in each iteration) Transform wheelMesh = wheelMeshes[i + 2]; //check if this wheel is grounded currently if(Physics.Raycast(wheelMesh.position, Vector3.down, grassEffectOffset * 1.5f)){ //if so, show the grass effect if(!grassEffects[i].gameObject.activeSelf) grassEffects[i].gameObject.SetActive(true); //update the grass effect height and the skidmark height to match this wheel float effectHeight = wheelMesh.position.y - grassEffectOffset; Vector3 targetPosition = new Vector3(grassEffects[i].position.x, effectHeight, wheelMesh.position.z); grassEffects[i].position = targetPosition; skidMarkPivots[i].position = targetPosition; //this wheel is grounded so we don't need any extra force at the back of the car addForce = false; } elseif(grassEffects[i].gameObject.activeSelf){ //if we're not grounded, don't show the grass effect grassEffects[i].gameObject.SetActive(false); } } //add force at the back of the car for stabilization if(addForce){ rb.AddForceAtPosition(back.position, Vector3.down * constantBackForce); //don't show the skidmarks skidMarkRoutine = false; } else{ if(targetRotation != 0){ //if the car has rotated show the skid mark if(rotated && !skidMarkRoutine){ skidMarkRoutine = true; } elseif(!rotated && skidMarkRoutine){ skidMarkRoutine = false; } } else{ //don't show the skidmark if we're rotating back to the center skidMarkRoutine = false; } } //update the last rotation (which is now the current rotation since everything has been updated) lastRotation = transform.localEulerAngles.y; }
voidCreateItem(Vector3 vert, int x){ //get the center of the cylinder but use the z value from the vertice Vector3 zCenter = new Vector3(0, 0, vert.z); //check if we get a correct angle between the center and the vertice if(zCenter - vert == Vector3.zero || x == (int)dimensions.x/4 || x == (int)dimensions.x/4 * 3) return; //create a new item with a small chance of being a gate (gateChance) and a big chance of being an obstacle GameObject newItem = Instantiate((Random.Range(0, gateChance) == 0) ? gate : obstacles[Random.Range(0, obstacles.Length)]); //rotate the item inwards towards the center position newItem.transform.rotation = Quaternion.LookRotation(zCenter - vert, Vector3.up); //position the item at the vertice position newItem.transform.position = vert; //parent the new item to the current cylinder so it will move and rotate along newItem.transform.SetParent(currentCylinder.transform, false); }
voidSetScore(){ //update the highscore if our score is higher then the previous best score if(score > PlayerPrefs.GetInt("best")) PlayerPrefs.SetInt("best", score); //show the score and the high score gameOverScoreLabel.text = "score: " + score; gameOverBestLabel.text = "best: " + PlayerPrefs.GetInt("best"); }
publicvoidGameOver(){ //the game cannot be over multiple times so we need to return if the game was over already if(gameOver) return; //update the score and highscore SetScore(); //show the game over animation and play the audio gameOverAnimator.SetTrigger("Game over"); gameOverAudio.Play(); //the game is over gameOver = true; //break the car car.FallApart(); //stop the world from moving or rotating foreach(BasicMovement basicMovement in GameObject.FindObjectsOfType<BasicMovement>()){ basicMovement.movespeed = 0; basicMovement.rotateSpeed = 0; } }
另添加一个小车碰到障碍物撞毁的脚本在car里,资源prefab已经有了
1 2 3 4 5
publicvoidFallApart(){ //destroy the car Instantiate(ragdoll, transform.position, transform.rotation); gameObject.SetActive(false); }