Unity3d 虚拟摇杆实现 模拟摩托车行驶小Demo
今天,为大家分享一下,如何做到一个虚拟摇杆实现物体的移动和特定的操作。下面是简单通过虚拟摇杆可以实现摩托车的行驶。【PS:因为项目代码比较多,也许文章讲得不够全面清楚,希望大家能多多包涵。如果大家想要源码或者资源的话,可以直接加QQ群:575561285】好吧!废话不多讲,直接进入主题吧!
1.新建unity3d 项目,然后自己可以合理搭一个场景,导入需要的资源,例如,我这边会导入一些摩托车的模型。
2.首先考虑的是摩托车自身属性设置,然后可以给他们挂上脚本,属性:Pro_bike 5.js,控制声音:Bike_sound.js ;刹车控制:Skid Marks.js;
然后具体的代码如下:
Pro_bike 5.js
#pragma strict
/// Writen by Boris Chuprin smokerr@mail.ru
///////////////////////////////////////////////////////// wheels ///////////////////////////////////////////////////////////
// defeine wheel colliders
var coll_frontWheel : WheelCollider;
var coll_rearWheel : WheelCollider;
// visual for wheels
var meshFrontWheel : GameObject;
var meshRearWheel : GameObject;
// check isn't front wheel in air for front braking possibility
private var isFrontWheelInAir : boolean = true;
//////////////////////////////////////// Stifness, CoM(center of mass), crahsed /////////////////////////////////////////////////////////////
//for stiffness counting when rear brake is on. Need that to lose real wheel's stiffness during time
private var stiffPowerGain : float = 0.0;
//for CoM moving along and across bike. Pilot's CoM.
private var tmpMassShift : float = 0.0;
// crashed status. To know when we need to desable controls because bike is too leaned.
public var crashed : boolean = false;
// there is angles when bike takes status crashed(too much lean, or too much stoppie/wheelie)
var crashAngle01: float;//crashed status is on if bike have more Z(side fall) angle than this // 70 sport, 55 chopper
var crashAngle02: float;//crashed status is on if bike have less Z(side fall) angle than this // 290 sport, 305 chopper
var crashAngle03: float;//crashed status is on if bike have more X(front fall) angle than this // 70 sport, 70 chopper
var crashAngle04: float;//crashed status is on if bike have more X(back fall) angle than this // 280 sport, 70 chopper
// define CoM of bike
var CoM : Transform; //CoM object
var normalCoM : float; //normalCoM is for situation when script need to return CoM in starting position // -0.77 sport, -0.38 chopper
var CoMWhenCrahsed : float; //we beed lift CoM for funny bike turning around when crahsed // -0.2 sport, 0.2 chopper
//////////////////// "beauties" of visuals - some meshes for display visual parts of bike ////////////////////////////////////////////
var rearPendulumn : Transform; //rear pendulumn
var steeringWheel : Transform; //wheel bar
var suspensionFront_down : Transform; //lower part of front forge
private var normalFrontSuspSpring : int; // we need to declare it to know what is normal front spring state is
private var normalRearSuspSpring : int; // we need to declare it to know what is normal rear spring state is
private var forgeBlocked : boolean= true; // variable to lock front forge for front braking
//why we need forgeBlocked ?
//There is little bug in PhysX 3.3 wheelCollider - it works well only with car mass of 1600kg and 4 wheels.
//If your vehicle is not 4 wheels or mass is not 1600 but 400 - you are in troube.
//Problem is absolutely epic force created by suspension spring when it's full compressed, stretched or wheel comes underground between frames(most catastrophic situation)
//In all 3 cases your spring will generate crazy force and push rigidbody to the sky.
//so, my solution is find this moment and make spring weaker for a while then return to it's normal condition.
private var baseDistance : float; // need to know distance between wheels - base. It's for wheelie compensate(dont want wheelie for long bikes)
// we need to clamp wheelbar angle according the speed. it means - the faster bike rides the less angle you can rotate wheel bar
var wheelbarRestrictCurve : AnimationCurve = new AnimationCurve(new Keyframe(0f, 20f), new Keyframe(100f, 1f));//first number in Keyframe is speed, second is max wheelbar degree
// temporary variable to restrict wheel angle according speed
private var tempMaxWheelAngle : float;
//variable for cut off wheel bar rotation angle at high speed
private var wheelPossibleAngle : float = 0.0;
//for wheels vusials match up the wheelColliders
private var wheelCCenter : Vector3;
private var hit : RaycastHit;
/////////////////////////////////////////// technical variables ///////////////////////////////////////////////////////
static var bikeSpeed : float; //to know bike speed km/h
static var isReverseOn : boolean = false; //to turn On and Off reverse speed
// Engine
var frontBrakePower : float; //brake power absract - 100 is good brakes // 100 sport, 70 chopper
var EngineTorque : float; //engine power(abstract - not in HP or so) // 85 sport, 100 chopper
// airRes is for wind resistance to large bikes more than small ones
var airRes : float; //Air resistant // 1 is neutral
/// GearBox
var MaxEngineRPM : float; //engine maximum rotation per minute(RPM) when gearbox should switch to higher gear // 12000 sport, 7000 chopper
var EngineRedline : float; // 12200 sport, 7200 chopper
var MinEngineRPM : float; //lowest RPM when gear need to be switched down // 6000 sport, 2500 chopper
static var EngineRPM : float; // engine current rotation per minute(RPM)
// gear ratios(abstract)
var GearRatio: float[];//array of gears // 6 sport, 5 chopper
var CurrentGear : int = 0; // current gear
private var ctrlHub : GameObject;// gameobject with script control variables
private var outsideControls : controlHub;// making a link to corresponding bike's script
///////////////////////////////////////////////////ESP system ////////////////////////////////////////////////////////
private var ESP : boolean = false;//ESP turned off by default
////////////////////////////////////////////////ON SCREEN INFO ///////////////////////////////////////////////////////
function OnGUI ()
{
var biggerText = new GUIStyle("label");
biggerText.fontSize = 40;
var middleText = new GUIStyle("label");
middleText.fontSize = 22;
var smallerText = new GUIStyle("label");
smallerText.fontSize = 14;
//to show in on display interface: speed, gear, ESP status
GUI.color = Color.black;
GUI.Label(Rect(Screen.width*0.9,Screen.height*0.67, 120, 80), String.Format(""+ "{0:0.}", bikeSpeed), biggerText);
GUI.Label (Rect (Screen.width*0.725,Screen.height*0.64, 60, 80), "" + (CurrentGear+1),biggerText);
if (!ESP){
GUI.color = Color.grey;
GUI.Label (Rect (Screen.width*0.885, Screen.height*0.55,60,40), "ESP", middleText);
} else {
GUI.color = Color.green;
GUI.Label (Rect (Screen.width*0.885, Screen.height*0.86,60,40), "ESP", middleText);
}
if (!isReverseOn){
GUI.color = Color.grey;
GUI.Label (Rect (Screen.width*0.885, Screen.height*0.96,60,40), "REAR", smallerText);
} else {
GUI.color = Color.red;
GUI.Label (Rect (Screen.width*0.885, Screen.height*0.96,60,40), "REAR", smallerText);
}
// user info help lines
GUI.color = Color.white;
GUI.Box (Rect (10,10,180,20), "A,W,S,D - main control", smallerText);
GUI.Box (Rect (10,25,220,20), "2 - more power to accelerate", smallerText);
GUI.Box (Rect (10,40,120,20), "X - rear brake", smallerText);
GUI.Box (Rect (10,55,320,20), "Q,E,F,V - shift center of mass of biker", smallerText);
GUI.Box (Rect (10,70,320,20), "R - restart / RightShift+R - full restart", smallerText);
GUI.Box (Rect (10,85,180,20), "RMB - rotate camera around", smallerText);
GUI.Box (Rect (10,100,120,20), "Z - turn on/off ESP", smallerText);
GUI.Box (Rect (10,115,320,20), "C - toggle reverse", smallerText);
GUI.color = Color.black;
}
function Start () {
ctrlHub = GameObject.Find("gameScenario");//link to GameObject with script "controlHub"
outsideControls = ctrlHub.GetComponent(controlHub);//to connect c# mobile control script to this one
var setInitialTensor : Vector3 = GetComponent.<Rigidbody>().inertiaTensor;//this string is necessary for Unity 5.3 with new PhysX feature when Tensor decoupled from center of mass
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);// now Center of Mass(CoM) is alligned to GameObject "CoM"
GetComponent.<Rigidbody>().inertiaTensor = setInitialTensor;////this string is necessary for Unity 5.3 with new PhysX feature when Tensor decoupled from center of mass
// wheel colors for understanding of accelerate, idle, brake(white is idle status)
meshFrontWheel.GetComponent.<Renderer>().material.color = Color.black;
meshRearWheel.GetComponent.<Renderer>().material.color = Color.black;
//for better physics of fast moving bodies
GetComponent.<Rigidbody>().interpolation = RigidbodyInterpolation.Interpolate;
// too keep EngineTorque variable like "real" horse powers
EngineTorque = EngineTorque * 20;
//*30 is for good braking to keep frontBrakePower = 100 for good brakes. So, 100 is like sportsbike's Brembo
frontBrakePower = frontBrakePower * 30;//30 is abstract but necessary for Unity5
//tehcnical variables
normalRearSuspSpring = coll_rearWheel.suspensionSpring.spring;
normalFrontSuspSpring = coll_frontWheel.suspensionSpring.spring;
baseDistance = coll_frontWheel.transform.localPosition.z - coll_rearWheel.transform.localPosition.z;// now we know distance between two wheels
}
function FixedUpdate (){
// if RPM is more than engine can hold we should shift gear up
EngineRPM = coll_rearWheel.rpm * GearRatio;
if (EngineRPM > EngineRedline){
EngineRPM = MaxEngineRPM;
}
ShiftGears();
// turn ESP on (no stoppie, no wheelie, no falls when brake is on when leaning)
ESP = outsideControls.ESPMode;
ApplyLocalPositionToVisuals(coll_frontWheel);
ApplyLocalPositionToVisuals(coll_rearWheel);
//////////////////////////////////// part where rear pendulum, wheelbar and wheels meshes matched to wheelsColliers and so on
//beauty - rear pendulumn is looking at rear wheel
rearPendulumn.transform.localRotation.eulerAngles.x = 0-8+(meshRearWheel.transform.localPosition.y*100);
//beauty - wheel bar rotating by front wheel
suspensionFront_down.transform.localPosition.y =(meshFrontWheel.transform.localPosition.y - 0.15);
meshFrontWheel.transform.localPosition.z = meshFrontWheel.transform.localPosition.z - (suspensionFront_down.transform.localPosition.y + 0.4)/5;
// debug - all wheels are white in idle(no accelerate, no brake)
meshFrontWheel.GetComponent.<Renderer>().material.color = Color.black;
meshRearWheel.GetComponent.<Renderer>().material.color = Color.black;
// drag and angular drag for emulate air resistance
if (!crashed){
GetComponent.<Rigidbody>().drag = GetComponent.<Rigidbody>().velocity.magnitude / 210* airRes; // when 250 bike can easy beat 200km/h // ~55 m/s
GetComponent.<Rigidbody>().angularDrag = 7 + GetComponent.<Rigidbody>().velocity.magnitude/20;
}
//determinate the bike speed in km/h
bikeSpeed = Mathf.Round((GetComponent.<Rigidbody>().velocity.magnitude * 3.6)*10) * 0.1; //from m/s to km/h
//////////////////////////////////// acceleration & brake /////////////////////////////////////////////////////////////
//////////////////////////////////// ACCELERATE /////////////////////////////////////////////////////////////
if (!crashed && outsideControls.Vertical >0 && !isReverseOn){//case with acceleration from 0.0 to 0.9 throttle
coll_frontWheel.brakeTorque = 0;//we need that to fix strange unity bug when bike stucks if you press "accelerate" just after "brake".
coll_rearWheel.motorTorque = EngineTorque * outsideControls.Vertical;
// debug - rear wheel is green when accelerate
meshRearWheel.GetComponent.<Renderer>().material.color = Color.green;
// when normal accelerating CoM z is averaged
CoM.localPosition.z = 0.0 + tmpMassShift;
CoM.localPosition.y = normalCoM;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
//case for reverse
if (!crashed && outsideControls.Vertical >0 && isReverseOn){
coll_rearWheel.motorTorque = EngineTorque * -outsideControls.Vertical/10+(bikeSpeed*50);//need to make reverse really slow
// debug - rear wheel is green when accelerate
meshRearWheel.GetComponent.<Renderer>().material.color = Color.green;
// when normal accelerating CoM z is averaged
CoM.localPosition.z = 0.0 + tmpMassShift;
CoM.localPosition.y = normalCoM;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
//////////////////////////////////// ACCELERATE full throttle ///////////////////////////////////////////////////////
if (!crashed && outsideControls.Vertical >0.9 && !isReverseOn){// acceleration >0.9 throttle for wheelie
coll_frontWheel.brakeTorque = 0;//we need that to fix strange unity bug when bike stucks if you press "accelerate" just after "brake".
coll_rearWheel.motorTorque = EngineTorque * 1.2;//1.2 mean it's full throttle
meshRearWheel.GetComponent.<Renderer>().material.color = Color.green;
GetComponent.<Rigidbody>().angularDrag= 20;//for wheelie stability
if (!ESP){// when ESP we turn off wheelie
CoM.localPosition.z = -(2-baseDistance/1.4) + tmpMassShift;// we got 1 meter in case of sportbike: 2-1.4/1.4 = 1; When we got chopper we'll get ~0.8 as result
//still working om best wheelie code
var stoppieEmpower : float = (bikeSpeed/3)/100;
// need to supress wheelie when leaning because it's always fall and it't not fun at all
if (this.transform.localEulerAngles.z < 70){
var angleLeanCompensate = this.transform.localEulerAngles.z/30;
if (angleLeanCompensate > 0.5){
angleLeanCompensate = 0.5;
}
}
if (this.transform.localEulerAngles.z > 290){
angleLeanCompensate = (360-this.transform.localEulerAngles.z)/30;
if (angleLeanCompensate > 0.5){
angleLeanCompensate = 0.5;
}
}
if (stoppieEmpower + angleLeanCompensate > 0.5){
stoppieEmpower = 0.5;
}
CoM.localPosition.y =-(1 - baseDistance/2.8) - stoppieEmpower ;
CoM.localPosition.y = CoM.localPosition.y += 0.002;
}
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
//this is attenuation for rear suspension targetPosition
//I've made it to prevent very strange launch to sky when wheelie in new Phys3
if (this.transform.localEulerAngles.x > 200.0){
coll_rearWheel.suspensionSpring.spring = normalRearSuspSpring + (360-this.transform.localEulerAngles.x)*500;
if (coll_rearWheel.suspensionSpring.spring >= normalRearSuspSpring + 20000) coll_rearWheel.suspensionSpring.spring = normalRearSuspSpring + 20000;
}
} else RearSuspensionRestoration();
//////////////////////////////////// BRAKING /////////////////////////////////////////////////////////////
//////////////////////////////////// front brake /////////////////////////////////////////////////////////
if (!crashed && outsideControls.Vertical <0 && !isFrontWheelInAir){
coll_frontWheel.brakeTorque = frontBrakePower * -outsideControls.Vertical;
coll_rearWheel.motorTorque = 0; // you can't do accelerate and braking same time.
//more user firendly gomeotric progession braking. But less stoppie and fun :( Boring...
//coll_frontWheel.brakeTorque = frontBrakePower * -outsideControls.Vertical-(1 - -outsideControls.Vertical)*-outsideControls.Vertical;
if (!ESP){ // no stoppie when ESP is on
if (bikeSpeed >1){// no CoM pull up when speed is zero
//when rear brake is used it helps a little to prevent stoppie. Because in real life bike "stretch" a little when you using rear brake just moment before front.
if(outsideControls.rearBrakeOn){
var rearBrakeAddon = 0.0025;
}
CoM.localPosition.y = CoM.localPosition.y += (frontBrakePower/200000)+tmpMassShift/50-rearBrakeAddon;
CoM.localPosition.z = CoM.localPosition.z += 0.05;
}
else if (bikeSpeed <=1 && !crashed && this.transform.localEulerAngles.z < 45 || bikeSpeed <=1 && !crashed && this.transform.localEulerAngles.z >315){
if (this.transform.localEulerAngles.x < 5 || this.transform.localEulerAngles.x > 355){
CoM.localPosition.y = normalCoM;
}
}
if (CoM.localPosition.y >= -0.1) CoM.localPosition.y = -0.1;
if (CoM.localPosition.z >= 0.2+(GetComponent.<Rigidbody>().mass/1100)) CoM.localPosition.z = 0.2+(GetComponent.<Rigidbody>().mass/1100);
////////////
//this is attenuation for front suspension when forge spring is compressed
//I've made it to prevent very strange launch to sky when wheelie in new Phys3
//problem is launch bike to sky when spring must expand from compressed state. In real life front forge can't create such force.
var maxFrontSuspConstrain : float;//temporary variable to make constrain for attenuation ususpension(need to make it always ~15% of initial force)
maxFrontSuspConstrain = CoM.localPosition.z;
if (maxFrontSuspConstrain >= 0.5) maxFrontSuspConstrain = 0.5;
var springWeakness : int= normalFrontSuspSpring-(normalFrontSuspSpring*1.5) * maxFrontSuspConstrain;
}
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
// debug - wheel is red when braking
meshFrontWheel.GetComponent.<Renderer>().material.color = Color.red;
//we need to mark suspension as very compressed to make it weaker
forgeBlocked = true;
} else FrontSuspensionRestoration(springWeakness);//here is function for weak front spring and return it's force slowly
//////////////////////////////////// rear brake /////////////////////////////////////////////////////////
// rear brake - it's all about lose side stiffness more and more till rear brake is pressed
if (!crashed && outsideControls.rearBrakeOn){
coll_rearWheel.brakeTorque = frontBrakePower / 2;// rear brake is not so good as front brake
if (this.transform.localEulerAngles.x > 180 && this.transform.localEulerAngles.x < 350){
CoM.localPosition.z = 0.0 + tmpMassShift;
}
coll_frontWheel.sidewaysFriction.stiffness = 1.0 - ((stiffPowerGain/2)-tmpMassShift*3);
stiffPowerGain = stiffPowerGain += 0.025 - (bikeSpeed/10000);
if (stiffPowerGain > 0.9 - bikeSpeed/300){ //orig 0.90
stiffPowerGain = 0.9 - bikeSpeed/300;
}
coll_rearWheel.sidewaysFriction.stiffness = 1.0 - stiffPowerGain;
meshRearWheel.GetComponent.<Renderer>().material.color = Color.red;
} else{
coll_rearWheel.brakeTorque = 0;
stiffPowerGain = stiffPowerGain -= 0.05;
if (stiffPowerGain < 0){
stiffPowerGain = 0;
}
coll_rearWheel.sidewaysFriction.stiffness = 1.0 - stiffPowerGain;// side stiffness is back to 2
coll_frontWheel.sidewaysFriction.stiffness = 1.0 - stiffPowerGain;// side stiffness is back to 1
}
//////////////////////////////////// reverse /////////////////////////////////////////////////////////
if (!crashed && outsideControls.reverse && bikeSpeed <=0){
outsideControls.reverse = false;
if(isReverseOn == false){
isReverseOn = true;
} else isReverseOn = false;
}
//////////////////////////////////// turnning /////////////////////////////////////////////////////////////
// there is MOST trick in the code
// the Unity physics isn't like real life. Wheel collider isn't round as real bike tyre.
// so, face it - you can't reach accurate and physics correct countersteering effect on wheelCollider
// For that and many other reasons we restrict front wheel turn angle when when speed is growing
//(honestly, there was a time when MotoGP bikes has restricted wheel bar rotation angle by 1.5 degree ! as we got here :)
tempMaxWheelAngle = wheelbarRestrictCurve.Evaluate(bikeSpeed);//associate speed with curve which you've tuned in Editor
if (!crashed && outsideControls.Horizontal !=0){
//if (!crashed && Input.GetAxis("Horizontal") !=0){//DEL OLD
// while speed is high, wheelbar is restricted
coll_frontWheel.steerAngle = tempMaxWheelAngle * outsideControls.Horizontal;
//coll_frontWheel.steerAngle = tempMaxWheelAngle * Input.GetAxis("Horizontal");//DEL OLD
steeringWheel.rotation = coll_frontWheel.transform.rotation * Quaternion.Euler (0, coll_frontWheel.steerAngle, coll_frontWheel.transform.rotation.z);
} else coll_frontWheel.steerAngle = 0;
/////////////////////////////////////////////////// PILOT'S MASS //////////////////////////////////////////////////////////
// it's part about moving of pilot's center of mass. It can be used for wheelie or stoppie control and for motocross section in future
//not polished yet. For mobile version it should back pilot's mass smooth not in one tick
if (outsideControls.VerticalMassShift >0){
tmpMassShift = outsideControls.VerticalMassShift/12.5;//12.5 to get 0.08m at final
CoM.localPosition.z = tmpMassShift;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
if (outsideControls.VerticalMassShift <0){
tmpMassShift = outsideControls.VerticalMassShift/12.5;//12.5 to get 0.08m at final
CoM.localPosition.z = tmpMassShift;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
if (outsideControls.HorizontalMassShift <0){
CoM.localPosition.x = outsideControls.HorizontalMassShift/40;//40 to get 0.025m at final
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
if (outsideControls.HorizontalMassShift >0){
CoM.localPosition.x = outsideControls.HorizontalMassShift/40;//40 to get 0.025m at final
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
//auto back CoM when any key not pressed
if (!crashed && outsideControls.Vertical == 0 && !outsideControls.rearBrakeOn || (outsideControls.Vertical < 0 && isFrontWheelInAir)){
CoM.localPosition.y = normalCoM;
CoM.localPosition.z = 0.0 + tmpMassShift;
coll_frontWheel.motorTorque = 0;
coll_frontWheel.brakeTorque = 0;
coll_rearWheel.motorTorque = 0;
coll_rearWheel.brakeTorque = 0;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
//autoback pilot's CoM along
if (outsideControls.VerticalMassShift == 0){
CoM.localPosition.z = 0.0;
tmpMassShift = 0.0;
}
//autoback pilot's CoM across
if (outsideControls.HorizontalMassShift == 0){
CoM.localPosition.x = 0.0;
}
/////////////////////////////////////////////////////// RESTART KEY ///////////////////////////////////////////////////////////
// Restart key - recreate bike few meters above current place
if (outsideControls.restartBike){
if (outsideControls.fullRestartBike){
transform.position = Vector3(0,1,-11);
transform.rotation=Quaternion.Euler( 0.0, 0.0, 0.0 );
}
crashed = false;
transform.position+=Vector3(0,0.1,0);
transform.rotation=Quaternion.Euler( 0.0, transform.localEulerAngles.y, 0.0 );
GetComponent.<Rigidbody>().velocity=Vector3.zero;
GetComponent.<Rigidbody>().angularVelocity=Vector3.zero;
CoM.localPosition.x = 0.0;
CoM.localPosition.y = normalCoM;
CoM.localPosition.z = 0.0;
//for fix bug when front wheel IN ground after restart(sorry, I really don't understand why it happens);
coll_frontWheel.motorTorque = 0;
coll_frontWheel.brakeTorque = 0;
coll_rearWheel.motorTorque = 0;
coll_rearWheel.brakeTorque = 0;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
///////////////////////////////////////// CRASH happens /////////////////////////////////////////////////////////
// conditions when crash is happen
if ((this.transform.localEulerAngles.z >=crashAngle01 && this.transform.localEulerAngles.z <=crashAngle02) || (this.transform.localEulerAngles.x >=crashAngle03 && this.transform.localEulerAngles.x <=crashAngle04)){
GetComponent.<Rigidbody>().drag = 0.1; // when 250 bike can easy beat 200km/h // ~55 m/s
GetComponent.<Rigidbody>().angularDrag = 0.01;
crashed = true;
CoM.localPosition.x = 0.0;
CoM.localPosition.y = CoMWhenCrahsed;//move CoM a little bit up for funny bike rotations when fall
CoM.localPosition.z = 0.0;
GetComponent.<Rigidbody>().centerOfMass = Vector3(CoM.localPosition.x, CoM.localPosition.y, CoM.localPosition.z);
}
if (crashed) coll_rearWheel.motorTorque = 0;//to prevent some bug when bike crashed but still accelerating
}
//function Update () {
//not use that because everything here is about physics
//}
///////////////////////////////////////////// FUNCTIONS /////////////////////////////////////////////////////////
function ShiftGears() {
if ( EngineRPM >= MaxEngineRPM ) {
var AppropriateGear : int = CurrentGear;
for ( var i = 0; i < GearRatio.length; i ++ ) {
if (coll_rearWheel.rpm * GearRatio < MaxEngineRPM ) {
AppropriateGear = i;
break;
}
}
CurrentGear = AppropriateGear;
}
if ( EngineRPM <= MinEngineRPM ) {
AppropriateGear = CurrentGear;
for ( var j = GearRatio.length-1; j >= 0; j -- ) {
if (coll_rearWheel.rpm * GearRatio > MinEngineRPM ) {
AppropriateGear = j;
break;
}
}
CurrentGear = AppropriateGear;
}
}
function ApplyLocalPositionToVisuals (collider : WheelCollider) {
if (collider.transform.childCount == 0) {
return;
}
var visualWheel : Transform = collider.transform.GetChild (0);
wheelCCenter = collider.transform.TransformPoint (collider.center);
if (Physics.Raycast (wheelCCenter, -collider.transform.up, hit, collider.suspensionDistance + collider.radius)) {
visualWheel.transform.position = hit.point + (collider.transform.up * collider.radius);
if (collider.name == "coll_front_wheel") isFrontWheelInAir = false;
} else {
visualWheel.transform.position = wheelCCenter - (collider.transform.up * collider.suspensionDistance);
if (collider.name == "coll_front_wheel") isFrontWheelInAir = true;
}
var position : Vector3;
var rotation : Quaternion;
collider.GetWorldPose (position, rotation);
visualWheel.localEulerAngles = Vector3(visualWheel.localEulerAngles.x, collider.steerAngle - visualWheel.localEulerAngles.z, visualWheel.localEulerAngles.z);
visualWheel.Rotate (collider.rpm / 60 * 360 * Time.deltaTime, 0, 0);
}
//need to restore spring power for rear suspension after make it harder for wheelie
function RearSuspensionRestoration (){
if (coll_rearWheel.suspensionSpring.spring > normalRearSuspSpring){
coll_rearWheel.suspensionSpring.spring = coll_rearWheel.suspensionSpring.spring -= 500;
}
}
//need to restore spring power for front suspension after make it weaker for stoppie
function FrontSuspensionRestoration (sprWeakness : int){
if (forgeBlocked) {//supress front spring power to avoid too much force back
coll_frontWheel.suspensionSpring.spring = sprWeakness;
forgeBlocked = false;
}
if (coll_frontWheel.suspensionSpring.spring < normalFrontSuspSpring){//slowly returning force to front spring
coll_frontWheel.suspensionSpring.spring = coll_frontWheel.suspensionSpring.spring += 500;
}
}
Bike_sound.js
#pragma strict
/// Writen by Boris Chuprin smokerr@mail.ru
var linkToBike : pro_bike5;// making a link to corresponding bike's script
//why it's here ? because you might use this script with many bikes in one scene
var lastGear : int;//we need to know what gear is now
private var highRPMAudio : AudioSource;// makeing second audioSource for mixing idle and high RPMs
private var skidSound : AudioSource;// makeing another audioSource for skidding sound
// creating sounds(Link it to real sound files at editor)
var engineStartSound : AudioClip;
var gearingSound : AudioClip;
var IdleRPM : AudioClip;
var highRPM : AudioClip;
var skid : AudioClip;
//we need to know is any wheel skidding
var isSkidingFront : boolean = false;
var isSkidingRear : boolean = false;
private var ctrlHub : GameObject;// gameobject with script control variables
private var outsideControls : controlHub;// making a link to corresponding bike's scriptt
function Start () {
ctrlHub = GameObject.Find("gameScenario");//link to GameObject with script "controlHub"
outsideControls = ctrlHub.GetComponent(controlHub);//to connect c# mobile control script to this one
//assign sound to audioSource
highRPMAudio = gameObject.AddComponent(AudioSource);
highRPMAudio.loop = true;
highRPMAudio.playOnAwake = false;
highRPMAudio.clip = highRPM;
highRPMAudio.pitch = 0;
highRPMAudio.volume = 0.0;
//same assign for skid sound
skidSound = gameObject.AddComponent(AudioSource);
skidSound.loop = false;
skidSound.playOnAwake = false;
skidSound.clip = skid;
skidSound.pitch = 1.0;
skidSound.volume = 1.0;
//real-time linking to current bike
linkToBike = this.GetComponent(pro_bike5);
GetComponent.<AudioSource>().PlayOneShot(engineStartSound);
playEngineWorkSound();
lastGear = linkToBike.CurrentGear;
}
function Update(){
//Idle plays high at slow speed and highRPM sound play silent at same time. And vice versa.
GetComponent.<AudioSource>().pitch = Mathf.Abs(linkToBike.EngineRPM/ linkToBike.MaxEngineRPM) + 1.0;
GetComponent.<AudioSource>().volume = 1.0 - (Mathf.Abs(linkToBike.EngineRPM/ linkToBike.MaxEngineRPM));
highRPMAudio.pitch = Mathf.Abs(linkToBike.EngineRPM/ linkToBike.MaxEngineRPM);
highRPMAudio.volume = Mathf.Abs(linkToBike.EngineRPM/ linkToBike.MaxEngineRPM);
// all engine sounds stop when restart
if (outsideControls.restartBike){
GetComponent.<AudioSource>().Stop();
GetComponent.<AudioSource>().pitch = 1.0;
GetComponent.<AudioSource>().PlayOneShot(engineStartSound);
playEngineWorkSound();
}
//gear change sound
if (linkToBike.CurrentGear != lastGear){
GetComponent.<AudioSource>().PlayOneShot(gearingSound);//звук переключения передач
lastGear = linkToBike.CurrentGear;
}
//skids sound
if (linkToBike.coll_rearWheel.sidewaysFriction.stiffness < 0.5 && !isSkidingRear && linkToBike.bikeSpeed >1){//почему 0.5 ? как бы лучше это обыграть ?
skidSound.Play();
isSkidingRear = true;
} else if (linkToBike.coll_rearWheel.sidewaysFriction.stiffness >= 0.5 && isSkidingRear || linkToBike.bikeSpeed <=1){
skidSound.Stop();
isSkidingRear = false;
}
if (linkToBike.coll_frontWheel.brakeTorque >= (linkToBike.frontBrakePower-10) && !isSkidingFront && linkToBike.bikeSpeed >1){
skidSound.Play();
isSkidingFront = true;
} else if (linkToBike.coll_frontWheel.brakeTorque < linkToBike.frontBrakePower && isSkidingFront || linkToBike.bikeSpeed <=1){
skidSound.Stop();
isSkidingFront = false;
}
}
function playEngineWorkSound(){
yield WaitForSeconds(1);//need a pause to hear ingnition sound first
GetComponent.<AudioSource>().clip = IdleRPM;
GetComponent.<AudioSource>().Play();
highRPMAudio.Play();
}
Skid Marks.js
#pragma strict
var linkToBike : pro_bike5;
var skidMarkDecal : Transform;
private var hit : WheelHit;
private var skidMarkPos : Vector3;
private var rotationToLastSkidmark : Quaternion;
private var lastSkidMarkPos : Vector3;
private var relativePos : Vector3;
function Start () {
linkToBike = this.GetComponent(pro_bike5);
}
function FixedUpdate(){
//skidmarks for rear wheel(braking drifting)
if (linkToBike.coll_rearWheel.sidewaysFriction.stiffness < 0.5 && linkToBike.bikeSpeed >1){
if (linkToBike.coll_rearWheel.GetGroundHit(hit)){
skidMarkPos = hit.point;
skidMarkPos.y += 0.02;
skidMarkDecal.transform.localScale.x = 1.0;
if (lastSkidMarkPos != Vector3.zero){
relativePos = lastSkidMarkPos - skidMarkPos;
rotationToLastSkidmark = Quaternion.LookRotation(relativePos);
Instantiate(skidMarkDecal, skidMarkPos, rotationToLastSkidmark);
}
lastSkidMarkPos = skidMarkPos;
}
}
//skidmarks for front wheel(braking)
if (linkToBike.coll_frontWheel.brakeTorque >= (linkToBike.frontBrakePower-10) && linkToBike.bikeSpeed >1){
if (linkToBike.coll_frontWheel.GetGroundHit(hit)){
skidMarkPos = hit.point;
skidMarkPos.y += 0.02;
skidMarkDecal.transform.localScale.x = 0.6;
if (lastSkidMarkPos != Vector3.zero){
relativePos = lastSkidMarkPos - skidMarkPos;
rotationToLastSkidmark = Quaternion.LookRotation(relativePos);
Instantiate(skidMarkDecal, skidMarkPos, rotationToLastSkidmark);
}
lastSkidMarkPos = skidMarkPos;
}
}
}
function Update () {
}【脚本参数设置如图】
3.接下来,我们需要在场景中,新建一个gameScenario游戏对象GameObject,作为整个游戏控制中心,并且在gameScenario对象中附加 速度控制:Speedometer.js
控制整个游戏对象:controlHub.cs
Speedometer.js
//speedometer
var GUIDashboard : Texture2D;
var dashboardArrow : Texture2D;
private var topSpeed: float;//220 for sport/ 180 for chopper
private var stopAngle: float;//-200 for sport/ ... for chopper
private var topSpeedAngle: float;
var speed: float;
//tachometer
var chronoTex: Texture2D;
private var topRPM: float;// 14000 for sport/ ... for chopper
private var stopRPMAngle: float;//-200 for sport/... ... for chopper
private var topRPMAngle: float;
var RPM: float;
//link to bike script
var linkToBike : pro_bike5;
function Start () {
linkToBike = GameObject.Find("rigid_bike").GetComponent("pro_bike5");
findCurrentBike();
}
function OnGUI() {
// speedometer
GUI.DrawTexture(Rect(Screen.width*0.85, Screen.height*0.55, GUIDashboard.width/2, GUIDashboard.height/2), GUIDashboard);
var centre = Vector2(Screen.width*0.85 + GUIDashboard.width / 4, Screen.height*0.55 + GUIDashboard.height / 4);
var savedMatrix = GUI.matrix;
var speedFraction = speed / topSpeed;
var needleAngle = Mathf.Lerp(stopAngle, topSpeedAngle, speedFraction);
GUIUtility.RotateAroundPivot(needleAngle, centre);
GUI.DrawTexture(Rect(centre.x, centre.y - dashboardArrow.height/4, dashboardArrow.width/2, dashboardArrow.height/2), dashboardArrow);
GUI.matrix = savedMatrix;
//tachometer
GUI.DrawTexture(Rect(Screen.width*0.65, Screen.height*0.5, chronoTex.width/1.5, chronoTex.height/1.5), chronoTex);
var centreTacho = Vector2(Screen.width*0.65 + chronoTex.width / 3, Screen.height*0.5+ chronoTex.height / 3);
var savedTachoMatrix = GUI.matrix;
var tachoFraction = RPM / topRPM;
var needleTachoAngle = Mathf.Lerp(stopRPMAngle, topRPMAngle, tachoFraction);
GUIUtility.RotateAroundPivot(needleTachoAngle, centreTacho);
GUI.DrawTexture(Rect(centreTacho.x, centreTacho.y - dashboardArrow.height/3, dashboardArrow.width/1.5, dashboardArrow.height/1.5), dashboardArrow);
GUI.matrix = savedTachoMatrix;
}
function FixedUpdate(){
speed = linkToBike.bikeSpeed;
RPM = linkToBike.EngineRPM;
}
function findCurrentBike(){
var curBikeName : GameObject;
curBikeName = GameObject.Find("rigid_bike");
if (curBikeName != null){
SetSpeedometerSettings("sport");
return;
}
}
function SetSpeedometerSettings(par : String){
if (par == "sport"){
topSpeed = 210;
stopAngle = -215;
topSpeedAngle = 0;
topRPM = 12000;
stopRPMAngle = -200;
topRPMAngle = 0;
yield WaitForSeconds(0.5);
var linkToBike1 = GameObject.Find("rigid_bike").GetComponent("pro_bike5");
linkToBike = linkToBike1;
}
}
controlHub.cs
using UnityEngine;
using System.Collections;
//this is script contains all variables translated to bike's script.
//so, it's just a holder for all control variables
//mobile/keyboard scripts sends nums(float, int, bools) to this one
public class controlHub : MonoBehaviour{//need that for mobile controls
public float Vertical;//variable translated to bike script for bike accelerate/stop and leaning
public float Horizontal;//variable translated to bike script for pilot's mass shift
public float VerticalMassShift;//variable for pilot's mass translate along bike
public float HorizontalMassShift;//variable for pilot's mass translate across bike
public bool ESPMode;//variable for turn ESP on and off
public bool rearBrakeOn;//this variable says to bike's script to use rear brake
public bool restartBike;//this variable says to bike's script restart
public bool fullRestartBike; //this variable says to bike's script to full restart
public bool reverse;//for reverse speed
}
4.场景中相机的跟随和切换类:camSwitcher.cs;
using UnityEngine;
using System.Collections;
public class camSwitcher : MonoBehaviour
{
public Camera backCamera;
public Camera aroundCamera;
public Transform cameraTarget;
private Camera currentCamera;
//////////////////// for back Camera
float dist = 3.0f;
float height = 1.0f;
//////////////////// for around Camera
private float distance = 3.0f;
private float xSpeed = 10.0f;
private float ySpeed = 10.0f;
private float yMinLimit = -90;
private float yMaxLimit = 90;
private float distanceMin = 2;
private float distanceMax = 10;
private float x = 0.0f;
private float y = 0.0f;
private float smoothTime = 0.2f;
private float xSmooth = 0.0f;
private float ySmooth = 0.0f;
private float xVelocity = 0.0f;
private float yVelocity = 0.0f;
//new camera behaviour
private float currentTargetAngle;
private GameObject ctrlHub;// gameobject with script control variables
private controlHub outsideControls;// making a link to corresponding bike's script
// Use this for initialization
void Start ()
{
ctrlHub = GameObject.Find("gameScenario");//link to GameObject with script "controlHub"
outsideControls = ctrlHub.GetComponent<controlHub>();//to connect c# mobile control script to this one
backCamera.enabled = true;
aroundCamera.enabled = false;
currentCamera = backCamera;
if (GetComponent<Rigidbody> ()) GetComponent<Rigidbody> ().freezeRotation = true;
currentTargetAngle = cameraTarget.transform.eulerAngles.z;
}
// Update is called once per frame
void LateUpdate ()
{
#if UNITY_STANDALONE || UNITY_WEBPLAYER// turn camera rotaion ONLY for mobile for free touch screen anywhere
if (Input.GetMouseButton (1)) {
backCamera.enabled = false;
aroundCamera.enabled = true;
backCamera.gameObject.SetActive (false);
aroundCamera.gameObject.SetActive (true);
currentCamera = aroundCamera;
x += Input.GetAxis ("Mouse X") * xSpeed;
y -= Input.GetAxis ("Mouse Y") * ySpeed;
y = Mathf.Clamp (y, yMinLimit, yMaxLimit);
xSmooth = Mathf.SmoothDamp (xSmooth, x, ref xVelocity, smoothTime);
ySmooth = Mathf.SmoothDamp (ySmooth, y, ref yVelocity, smoothTime);
distance = Mathf.Clamp (distance + Input.GetAxis ("Mouse ScrollWheel") * distance, distanceMin, distanceMax);
currentCamera.transform.localRotation = Quaternion.Euler (ySmooth, xSmooth, 0);
currentCamera.transform.position = currentCamera.transform.rotation * new Vector3 (0.0f, 0.0f, -distance) + cameraTarget.position;
} else {
#endif
backCamera.enabled = true;
aroundCamera.enabled = false;
backCamera.gameObject.SetActive (true);
aroundCamera.gameObject.SetActive (false);
currentCamera = backCamera;
//////////////////// code for back Camera
backCamera.fieldOfView = backCamera.fieldOfView + outsideControls.Vertical * 20f * Time.deltaTime;
if (backCamera.fieldOfView > 85) {
backCamera.fieldOfView = 85;
}
if (backCamera.fieldOfView < 50) {
backCamera.fieldOfView = 50;
}
if (backCamera.fieldOfView < 60) {
backCamera.fieldOfView = backCamera.fieldOfView += 10f * Time.deltaTime;
}
if (backCamera.fieldOfView > 60) {
backCamera.fieldOfView = backCamera.fieldOfView -= 10f * Time.deltaTime;
}
float wantedRotationAngle = cameraTarget.eulerAngles.y;
float wantedHeight = cameraTarget.position.y + height;
float currentRotationAngle = currentCamera.transform.eulerAngles.y;
float currentHeight = currentCamera.transform.position.y;
currentRotationAngle = Mathf.LerpAngle (currentRotationAngle, wantedRotationAngle, 3 * Time.deltaTime);
currentHeight = Mathf.Lerp (currentHeight, wantedHeight, 2 * Time.deltaTime);
Quaternion currentRotation = Quaternion.Euler (0, currentRotationAngle, 0);
currentCamera.transform.position = cameraTarget.position;
currentCamera.transform.position -= currentRotation * Vector3.forward * dist;
currentCamera.transform.position = new Vector3 (currentCamera.transform.position.x, currentHeight, currentCamera.transform.position.z);
currentCamera.transform.LookAt (cameraTarget);
//New camera features.
//Now camera leaning with biker, so horizon is not always horizontal :)
//If you don't like it, just disable
//from this -----------------------------------------------------------------------
// rotate camera according with bike leaning
if (cameraTarget.transform.eulerAngles.z >0 && cameraTarget.transform.eulerAngles.z < 180) {
currentTargetAngle = cameraTarget.transform.eulerAngles.z/10;
}
if (cameraTarget.transform.eulerAngles.z >180){
currentTargetAngle = -(360-cameraTarget.transform.eulerAngles.z)/10;
}
currentCamera.transform.rotation = Quaternion.Euler (height*10, currentRotationAngle, currentTargetAngle);
//to this -------------------------------------------------------------------------
#if UNITY_STANDALONE || UNITY_WEBPLAYER// turn camera rotaion ONLY for mobile for free touch screen anywhere
}
#endif
}
}
5.讲到这一步是比较关键的,正是通过GUI做出虚拟摇杆,虚拟摇杆控制类:mobileControls.cs;
using UnityEngine;
using UnityEngine.UI;//need that for mobile controls
using UnityEngine.EventSystems;//need that for mobile controls
using System.Collections;
public class mobileControls : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler {//need that for mobile controls
public Image bgImgJoyRight; //UI object for right Joystick
public Image bgImgJoyLeft; //UI object for left Joystick
private Image joystickImg; //"picture" of stick itself
private Image joystickImgLeft;//"picture" of stick itself for joyLeft
private Vector3 inputVector; //this will be translated to bike script for bike accelerate/stop and leaning
private Vector3 inputVectorLeft;//this will be translated to bike script for pilot's mass shift
public bool rearBrakeOn;//this variable says to bike's script to use rear brake
public bool restartBike;//this variable says to bike's script restart
//public bool ESPMode;//this variable switch ESP on and off
private GameObject ctrlHub;// making a link to corresponding bike's script
private controlHub outsideControls;// making a link to corresponding bike's script
private void Start(){
ctrlHub = GameObject.Find("gameScenario");//link to GameObject with script "controlHub"
outsideControls = ctrlHub.GetComponent<controlHub>();// making a link to corresponding bike's script
joystickImg = bgImgJoyRight.transform.GetChild(0).GetComponent<Image>();//find stick
joystickImgLeft = bgImgJoyLeft.transform.GetChild(0).GetComponent<Image>();//find stick
}
//Uncomment it if you want to lean by device rotation :)
//from here---------------------------------------------------
//private void Update(){
// outsideControls.Horizontal = Input.acceleration.x*1.25f;
//}
//to here---------------------------------------------------
//don't forget to comment standard leaning style few strings below
///////////////////////////////////////// Joystick Section /////////////////////////////////////////////////////////////////////////////////////
public virtual void OnDrag(PointerEventData ped){
Vector2 pos;//position for joystick Right
if (RectTransformUtility.ScreenPointToLocalPointInRectangle (bgImgJoyRight.rectTransform, ped.position, ped.pressEventCamera, out pos)) {
if (ped.position.x > Screen.width/2) {//use buttons should't take effect to joystics
pos.x = (pos.x / bgImgJoyRight.rectTransform.sizeDelta.x);
pos.y = (pos.y / bgImgJoyRight.rectTransform.sizeDelta.y);
inputVector = new Vector3 (pos.x * 2 + 1, 0, pos.y * 2 - 1);
inputVector = (inputVector.magnitude > 1.0f) ? inputVector.normalized : inputVector;
//move stick
joystickImg.rectTransform.anchoredPosition =
new Vector3 (inputVector.x * (bgImgJoyRight.rectTransform.sizeDelta.x / 2), inputVector.z * (bgImgJoyRight.rectTransform.sizeDelta.y / 2));
outsideControls.Vertical = inputVector.z;
//Comment it if you want to lean by device rotation :)
//from here---------------------------------------------------
outsideControls.Horizontal = inputVector.x;
//to here---------------------------------------------------
//don't forget to uncomment rotate leaning style few strings above
}
}
Vector2 pos_j2;//position for joystick Left
if (RectTransformUtility.ScreenPointToLocalPointInRectangle (bgImgJoyLeft.rectTransform, ped.position, ped.pressEventCamera, out pos_j2)){
if (ped.position.x < Screen.width/2 && ped.position.y < 170){//use buttons should't take effect to joystics
pos_j2.x = (pos_j2.x / bgImgJoyLeft.rectTransform.sizeDelta.x);
pos_j2.y = (pos_j2.y / bgImgJoyLeft.rectTransform.sizeDelta.y);
inputVectorLeft = new Vector3(pos_j2.x*2 + 1,0,pos_j2.y*2 - 1);
inputVectorLeft = (inputVectorLeft.magnitude > 1.0f)?inputVectorLeft.normalized:inputVectorLeft;
//move stick
joystickImgLeft.rectTransform.anchoredPosition =
new Vector3(inputVectorLeft.x * (bgImgJoyLeft.rectTransform.sizeDelta.x/2) ,inputVectorLeft.z * (bgImgJoyLeft.rectTransform.sizeDelta.y/2));
outsideControls.VerticalMassShift = inputVectorLeft.z;
outsideControls.HorizontalMassShift = inputVectorLeft.x;
}
}
}
/////////////////////////////////////////// pointer function ////////////////////////////////////////////////////////////////////
public virtual void OnPointerDown(PointerEventData ped){
if (ped.pointerEnter.name == "Stick") OnDrag(ped);
if (ped.pointerEnter.name == "Button_X") outsideControls.rearBrakeOn = true;
if (ped.pointerEnter.name == "Button_R") outsideControls.restartBike = true;
if (ped.pointerEnter.name == "Button_Rf") outsideControls.fullRestartBike = true;
if (ped.pointerEnter.name == "Button_E") {//we need to switch ESP on and off after each press on the ESP key
if(!outsideControls.ESPMode){
outsideControls.ESPMode = true;
} else {
outsideControls.ESPMode = false;
}
}
if (ped.pointerEnter.name == "Button_rev") outsideControls.reverse = true;
}
public virtual void OnPointerUp(PointerEventData ped){
inputVector = Vector3.zero;
joystickImg.rectTransform.anchoredPosition = Vector3.zero;
inputVectorLeft = Vector3.zero;
joystickImgLeft.rectTransform.anchoredPosition = Vector3.zero;
outsideControls.rearBrakeOn = false;
outsideControls.restartBike = false;
outsideControls.fullRestartBike = false;
outsideControls.reverse = false;
outsideControls.Vertical = 0;
outsideControls.Horizontal = 0;
outsideControls.VerticalMassShift = 0;
outsideControls.HorizontalMassShift = 0;
}
}
6.最后直接运行看效果。【PS:HAHA!有空动手练练也是蛮有趣的】
路过的帮顶 果断围观 坚定完毕
页:
[1]