Please suggest on collider performance issue

Hello,

I’m working on a racing game. Let me describe the scenery for you. The track is completely 2D and camera is looking top to bottom. Besides that there are several low poly 3D objects. So far I get about 11 draw calls and use 1.7k tris/984 verts.

Now, my problem is, that I need to detect whenever the car collides with something in the scenery and as the track is pretty complex I need quite a lot of colliders. In one scene I use about 75 of them (only box and sphere ones). Let’s say the game runs ok, but not as smooth as I want. I’m on the 3GS and don’t even want to think about performance on a 1st gen. iPod Touch :slight_smile:

Anyway, are there some tricks I could make use of? Maybe a different technique to detect collision or something that allows me to temporary get rid of colliders I don’t need?

Any help appreciated. Thanks in advance

having he same collider problem. What i do:

  • disable the colliders you dont need in the current cameraview (via triggers)
  • using position calculations instead of colliders (eg. if position.x < 10 then…)

Thanks for your answer! Using triggers sounds great. Position calculations might not work in my case, but I’ll play with both ideas.

Ok, I did some tests and looks like I was after the wrong thing right from the start. The colliders don’t seem to be the problem. I completely removed them and tried again and still had the performance hick ups. Somewhere on this forum I found a little script to display frames per second on a GUI texture. Right after launching the game I get about 29.9-30.0 fps, which is ok. When I start driving it sometimes goes down to 27-28 and drops down to 23-24 fps when I make a turn to the right or left. Then it goes back to 29-30.

I downloaded the Jcar demo and installed it on my iPhone and even though it uses about 18 draw calls (11 on my game) and 10k verts (1.7k on my game), it runs SUPER smooth. This tells me, that the problem must be somewhere else. Jcar even uses 3 cameras. 1 for HUD elemenst and 2 for showing the scene. I use 2 cameras - 1 for the scene and one for HUD elements.

Let me share a screenshot of the scene with you:

This is the camera I use for showing the HUD elements:

And here is the main camera:

And the stats:

Do you see anything strange, that could cause my performance issues?
Thanks in advance.

Ok, got some progress here. I noticed, that all my objects had cast/receive shadows turned on. Don’t know if that was the problem, but after I removed it, performance got a lot better. Almost stable 30 fps now.

Was that it or did I do something else? :slight_smile: Not 100% sure, because I changed several things I can’t remember now :?

Unity iPhone has no realtime shadows, so whether cast/receive shadows is checked or not wouldn’t make any difference.

–Eric

Try lowering your physics iterations :slight_smile:

The fact that going around turns causes a significant frame drop, you might have some costly scripting operations that can be tuned.

What type of stuff do you have going on in your update methods?

I have a main script, which handles the touch events and controls the car. I think, that cleaning up the code is a good way to start. However, I’ll also study the JCar sample again, as that one works as expected an is in fact way more complex.

Thanks again for your help!

Alright, let me give you an update.

After many hours of unsuccessful tweaking, I decided to create a new project and build the basic scenery from scratch.

Here is what I did:

  1. Created 3 x 3 cubes and reduced their height, so they would look like planes.
  2. Imported the track textures (each 512x512, .jpg format)
  3. Put the track textures on the cubes.
  4. Imported the JCar and JControlledCar scripts.
  5. Built a very simple “car”. Just a slightly stretched cube and 4 cylinders as wheels.
  6. Made the main camera child of the car and rotated it, so it would look top to bottom.
  7. Connected the scripts - Built the project for my iPhone.

Stats window shows 288 verts, 216 tris and 9 draw calls.

This time there are NO colliders and NO scripts I wrote myself inside the project.
Now guess what? :slight_smile: STILL no smooth movement.

I also tried an enlarged plane, that has absolutely no texture on it instead of the cubes and performance was the same.

I compared player settings, etc. to the JCar demo and they both matched.

Any clue, what could cause this? iPhone 3GS runs at 30.0 fps, iPod Touch 1st gen. at 22-28 fps, but again not smooth at all. JCar demo on the other hand runs fast as hell, has 10k+ verts, 3D models, mesh colliders, etc…

Could it be the fact, that my camera is top to bottom? As you can imagine, I ran out of ideas. Any new input appreciated. Thanks in advance.

So it really comes down to your scripting, particuarly your controls or car scripts.

Post your update loop.

If you’re worried about people stealing your code, don’t be. There’s heaps of people on here who can script better than you or I and they give their stuff away for free :slight_smile:

No, I’m not worried at all, since I use the JCar script. I wanted to make sure, that it was not my coding, that caused the problems.

However, in the meantime I got rid of all the cubes and created my own low poly planes in cinema4D. I haven’t tested performance yet, but I’m down to 7 draw calls and only 14! tris/49 verts.

I’ll post here as soon as I tested it.

Ok, tested it. Well, what can I say. It’s better, but still not the same performance as JCar demo.

I’ll now import the JCar model and give it a ride.

Done. I can now definitely say, that the hick ups only occur, when I ride on my 3 x 3 custom planes. They only have 2 polygons each, but every one of them has it’s own texture.

The track basically is a 1536 x 1536 pixels image, which I sliced up in photoshop. So, I created 9 images, 512 x 512 each. Those I simply put on my custom planes, which are all static by the way. Textures are PRVTC compressed of course.

Does anyone know a technique, that allows me to keep the same track size, but with less textures? Maybe I need to think of a completely different solution for my game?

If you post your Update code, I will take a look at it. You can PM if you don’t want to make it public, but I wouldn’t be too concerned. I’m wondering if you are also taking advantage of some of the more basic iPhone optimizations, but looking at a chunk of code would be enough to tell.

Thanks. As already mentioned before, I’m not worried at all. I’m currently using the JCar scripts, as I wanted to make sure that this problem wasn’t caused by my bad coding. Since JCar performs great, I’m now almost 100% sure, that it’s not the script.

Anyway, here you go:

JControlledCar.cs:

using UnityEngine;
using System.Collections;

public class JControlledCar : JCar {
	
	// automatic, if true car shifts automatically up/down
	public bool automatic = true;

	public float shiftDownRPM = 1500.0f; // rpm script will shift gear down
	public float shiftUpRPM = 2700.0f; // rpm script will shift gear up

	void Update() {
		if (Input.GetKeyDown("page up")) {
			ShiftUp();
		}
		if (Input.GetKeyDown("page down")) {
			ShiftDown();
		}
		if (Input.GetKeyDown("g")) {
			automatic = !automatic;
		}
		if (Input.GetKeyDown("t")) {
			switch (wheelDrive) {
				case JWheelType.Front : wheelDrive = JWheelType.All; break;
				case JWheelType.Back : wheelDrive = JWheelType.Front; break;
				case JWheelType.All : wheelDrive = JWheelType.Back; break;
			}
			foreach (WheelData w in wheels) {
				WheelCollider col = w.col;
				col.motorTorque = 0f;
				col.brakeTorque = 0f;
			}
		}
	}

	
	// handle the physics of the engine
	void FixedUpdate () {
		
		float steer = 0; // steering -1.0 .. 1.0
		float accel = 0; // accelerating -1.0 .. 1.0
		bool brake = false; // braking (true is brake)
		
//		if ((checkForActive == null) || checkForActive.active) {
//			// we only look at input when the object we monitor is
//			// active (or we aren't monitoring an object).
//			steer = Input.GetAxis("Horizontal");
//			accel = Input.GetAxis("Vertical");
//			brake = Input.GetButton("Jump");
//		}
		if ((checkForActive == null) || checkForActive.active) {
			// we only look at input when the object we monitor is
			// active (or we aren't monitoring an object).

			// don't know if I guessed the right axi (what's plural for axis?) here
			// now you have to hold the iphone more or less flat and move from there
			// just experiment with it, for really good control you maybe have to look
			// for something more advanced like averaging the samples to get less
			// jitter and stuff.
			steer = iPhoneInput.acceleration.x;
			accel = Mathf.Clamp(4f * (-iPhoneInput.acceleration.y -0.5f), -1f, 1f);
// Debug.Log("v = " + iPhoneInput.acceleration + " accel = " + accel);
			// for brake I only check if there is a touch started or going on
			for (int i = 0; i < iPhoneInput.touchCount; i++) {
				iPhoneTouch touch = iPhoneInput.GetTouch(i);
				if ((touch.phase != iPhoneTouchPhase.Ended)  (touch.phase != iPhoneTouchPhase.Ended)) {
					brake = true;
				}
			}
		}
		
		// handle automatic shifting
		if (automatic  (CurrentGear == 1)  (accel < 0.0f)) {
			ShiftDown(); // reverse
		}
		else if (automatic  (CurrentGear == 0)  (accel > 0.0f)) {
			ShiftUp(); // go from reverse to first gear
		}
		else if (automatic  (MotorRPM > shiftUpRPM)  (accel > 0.0f)) {
			ShiftUp(); // shift up
		}
		else if (automatic  (MotorRPM < shiftDownRPM)  (CurrentGear > 1)) {
			ShiftDown(); // shift down
		}
		if (automatic  (CurrentGear == 0)) {
			accel = - accel; // in automatic mode we need to hold arrow down for reverse
		}
		if (brake) {
			accel = -1f;
		}
		
		HandleMotor(steer, accel);
	}
}

JCar.cs:

/**
 * A simple car physics script using wheel colliders.
 * Jaap Kreijkamp [email]jaap@ctrl-j.com.au[/email]
 *
 * orientation should be that front of car is in direction of
 * the 'blue arrow' in Unity, the roof should be in direction of
 * the green angle. Thus with rotation 0, 0, 0, adding 1 to Z
 * will move car 1m forward, adding 1 to Y will move car 1m
 * upward. The wheels should be children of the car object this
 * script is added to and connected to the wheelFL, wheelFR, ...
 * variables.
 *
 * Please modify script and do whatever you like with it,
 * in it's current state it should give a working car,
 * but by no means perfect (or even close) behavior.
 * It's my first attempt and don't really need in my current
 * project so haven't put too much effort into it to perfect
 * it. As often people are looking for help to getting
 * a car working with wheel colliders, I'd appreciate when
 * improvements are posted back on the unity forums.
 *
 * Lastly, thanks to all the people helping me on forums (especially
 * the order of initialisation problem and other example
 * code that helped me much in learning how to do stuff like
 * this).
 */

using UnityEngine;
using System.Collections;

// used to indicate if car is frontwheeldrive, backwheeldrive or 4wheeldrive
// also (mis)used to indicate if a wheel is a front wheel or a back wheel
public enum JWheelType {
	Front = 1,
	Back = 2,
	All = 3
}


public class JCar : MonoBehaviour {	

	// if connected the controls will block if object not active
	// (for example steer only if car camera is active).
	public GameObject checkForActive;
	
	// if set car records position, accel, ... data every time
	// handle motor is called.
	public JRecordRoute recordRoute;
	
	// returns current speed in Km/H
	public float CurrentSpeed {
		get { return rigidbody.velocity.magnitude * 3.6f; }
	}
	
	public int CurrentGear {
		get { return currentGear; }
	}
	
	public float MotorRPM {
		get { return motorRPM; }
	}
	
	public bool MotorRunning {
		get { return (audioSource != null)  audioSource.isPlaying; }
	}
	
	public float TotalDistance {
		get { return totaldist; }
	}
	
	public Transform wheelFR; // connect to Front Right Wheel transform
	public Transform wheelFL; // connect to Front Left Wheel transform
	public Transform wheelBR; // connect to Back Right Wheel transform
	public Transform wheelBL; // connect to Back Left Wheel transform

	// power curves (power1 is for gear 1 and reverse!)
	// horizontal axis is RPM
	// vertical axis is power, where 5000f equals to 100%
	// (you can go over 5000f if you want, but for easier tuning it's probably
	//  better to adjust the torque parameter and keep the curve below 5000f.)
	// the default values are totally useless, so you have to edit the curves,
	// you can do this by adding CurveEditor.cs and ResponseCurveEditor.cs to
	// your project (in Editor directory of JCar or in the Animation-API example).
	// the best way to set curves is steep increase with top at 2000RPM, then
	// slow decrease back to 0 at high RPM, say 5000.
	// power1 starts curve at halfway to have enough power to drive away, power2
	// starts curve at 1/4 or so, you can drive away with gear2 but it's more
	// difficult
	public AnimationCurve power1 = AnimationCurve.Linear(0.0f, 5000.0f, 8000f, 0.0f);
	public AnimationCurve power2 = AnimationCurve.Linear(0.0f, 5000.0f, 8000f, 0.0f);
	public AnimationCurve power3 = AnimationCurve.Linear(0.0f, 5000.0f, 8000f, 0.0f);
	public AnimationCurve power4 = AnimationCurve.Linear(0.0f, 5000.0f, 8000f, 0.0f);
	public AnimationCurve power5 = AnimationCurve.Linear(0.0f, 5000.0f, 8000f, 0.0f);

	public float suspensionDistance = 0.2f; // amount of movement in suspension
	public float springs;// = 2f; // suspension springs
	public float dampers;// = 0.01f; // how much damping the suspension has
	public float wheelRadius;// = 0.15f; // the radius of the wheels
	public float torque = 150f; // the base power of the engine (per wheel, and before gears)
	public float brakeTorque;// = 2000f; // the power of the braks (per wheel)
	public float wheelWeight;// = 3f; // the weight of a wheel
	public Vector3 shiftCentre;// = new Vector3(0.0f, -0.25f, 0.0f); // offset of centre of mass
	
	public float maxSteerAngle = 30.0f; // max angle of steering wheels
	public JWheelType wheelDrive = JWheelType.Front; // which wheels are powered
	
	public float idleRPM = 500.0f; // idle rpm
	
	public float fwdStiffnessFront;// = 0.15f; // for wheels, determines slip
	public float fwdStiffnessBack;// = 0.15f; // for wheels, determines slip
	public float swyStiffnessFront;// = 0.03f; // for wheels, determines slip
	public float swyStiffnessBack;// = 0.015f; // for wheels, determines slip
	
	// gear ratios (index 0 is reverse)
	public float[] gears = { -10f, 9f, 6f, 4.5f, 3f, 2.5f };
	
	// how much RPM can increase/decrease per second
	public float maxRPMIncrease = 500f;
	public float maxRPMDecrease = 1000f;
	
	
	public float killEngineSoundTimeout = 3.0f; // time until engine sound is cut off (in s.)
		
	int currentGear = 1; // duh.
		
	// shortcut to the component audiosource (engine sound).
	AudioSource audioSource;

	float totaldist = 0f;
	float rpmtodist; // used for distance calculation initialized in Start
	
	// every wheel has a wheeldata struct, contains useful wheel specific info
	public class WheelData {
		public Transform transform;
		public GameObject go;
		public WheelCollider col;
		public Vector3 startPos;
		public float rotation = 0.0f;
		public float maxSteer;
		public JWheelType wheeltype;
		public bool floorcontact;
	};
	
	float CalcPower(int gear, float rpm) {
		AnimationCurve ac;
		switch (gear) {
			case 0 : ac = power1; break;
			case 1 : ac = power1; break;
			case 2 : ac = power2; break;
			case 3 : ac = power3; break;
			case 4 : ac = power4; break;
			case 5 : ac = power5; break;
			default : ac = power5; break;
		}
		// we use 5000 in power curve as about 100% so we div by 5000 here...
		float val = ac.Evaluate(rpm) * torque / 5000f;
		return (gear == 0)?-val:val;
	}
	
	protected WheelData[] wheels; // array with the wheel data
	
	// setup wheelcollider for given wheel data
	// wheel is the transform of the wheel
	// maxSteer is the angle in degrees the wheel can steer (0f for no steering)
	// motor if wheel is driven by engine or not
	WheelData SetWheelParams(Transform wheel, float maxSteer, JWheelType wheeltype) {
		if (wheel == null) {
			throw new System.Exception("wheel not connected to script!");
		}
		WheelData result = new WheelData(); // the container of wheel specific data

		// we create a new gameobject for the collider and move, transform it to match
		// the position of the wheel it represents. This allows us to do transforms
		// on the wheel itself without disturbing the collider.
		GameObject go = new GameObject("WheelCollider");
		go.transform.parent = transform; // the car, not the wheel is parent
		go.transform.position = wheel.position; // match wheel pos
		
		// create the actual wheel collider in the collider game object
		WheelCollider col = (WheelCollider) go.AddComponent(typeof(WheelCollider));
		col.motorTorque = 0.0f;
		
		// store some useful references in the wheeldata object
		result.transform = wheel; // access to wheel transform 
		result.go = go; // store the collider game object
		result.col = col; // store the collider self
		result.startPos = go.transform.localPosition; // store the current local pos of wheel
		result.maxSteer = maxSteer; // store the max steering angle allowed for wheel
		result.wheeltype = wheeltype; // store if wheel is connected to engine


		// see docs, haven't really managed to get this work
		// like i would but just try out a fiddle with it.
		WheelFrictionCurve fc = col.forwardFriction;
		// fc.asymptoteValue = 5000.0f;
		// fc.extremumSlip = 2.0f;
		// fc.asymptoteSlip = 20.0f;
		fc.stiffness = (wheeltype==JWheelType.Front)?fwdStiffnessFront:fwdStiffnessBack;
		col.forwardFriction = fc;
		fc = col.sidewaysFriction;
		// fc.asymptoteValue = 7500.0f;
		// fc.asymptoteSlip = 2.0f;
		fc.stiffness = (wheeltype==JWheelType.Front)?swyStiffnessFront:swyStiffnessBack;
		col.sidewaysFriction = fc;
		
		return result; // return the WheelData
	}
	
	// Use this for initialization
	void Start () {
		rigidbody.isKinematic = true;
		// used for distance calculation using rpm of wheels
		rpmtodist = 2f * Mathf.PI * wheelRadius / 60f;
		
		// 4 wheels, if needed different size just modify and modify
		// the wheels[...] block below.
		wheels = new WheelData[4];
		
		// we use 4 wheels, but you can change that easily if neccesary.
		// this is the only place that refers directly to wheelFL, ...
		// so when adding wheels, you need to add the public transforms,
		// adjust the array size, and add the wheels initialisation here.
		wheels[0] = SetWheelParams(wheelFR, maxSteerAngle, JWheelType.Front);
		wheels[1] = SetWheelParams(wheelFL, maxSteerAngle, JWheelType.Front);
		wheels[2] = SetWheelParams(wheelBR, 0.0f, JWheelType.Back);
		wheels[3] = SetWheelParams(wheelBL, 0.0f, JWheelType.Back);
		
		// found out the hard way: some parameters must be set AFTER all wheel colliders
		// are created, like wheel mass, otherwise your car will act funny and will
		// flip over all the time.
		foreach (WheelData w in wheels) {
			WheelCollider col = w.col;
			col.suspensionDistance = suspensionDistance;
			JointSpring js = col.suspensionSpring;
			js.spring = springs;
			js.damper = dampers;			
			col.suspensionSpring = js;
			col.radius = wheelRadius;
			col.mass = wheelWeight;
						
		}
		
		// we move the centre of mass (somewhere below the centre works best.)
		rigidbody.centerOfMass += shiftCentre;
		rigidbody.mass = rigidbody.mass;
		rigidbody.isKinematic = false;
		
		// shortcut to audioSource should be engine sound, if null then no engine sound.
		audioSource = (AudioSource) GetComponent(typeof(AudioSource));
		if (audioSource == null) {
			Debug.Log("No audio source, add one to the car with looping engine noise (but can be turned off");
		}
		else {
			audioSource.velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
			
			AudioListener listener = (AudioListener) Camera.main.GetComponent(typeof(AudioListener));
			listener.velocityUpdateMode = AudioVelocityUpdateMode.Fixed;
		}
	}
	
	
	float shiftDelay = 0.0f;
	
	// handle shifting a gear up
	public void ShiftUp() {
		float now = Time.timeSinceLevelLoad;
		
		// check if we have waited long enough to shift
		if (now < shiftDelay) return;
		
		// check if we can shift up
		if (currentGear < gears.Length - 1) {
			currentGear ++;
			
			// we delay the next shift with 1s. (sorry, hardcoded)
			shiftDelay = now + 1.0f;
		}
	}
	
	// handle shifting a gear down
	public void ShiftDown() {
		float now = Time.timeSinceLevelLoad;

		// check if we have waited long enough to shift
		if (now < shiftDelay) return;
		
		// check if we can shift down (note gear 0 is reverse)
		if (currentGear > 0) {
			currentGear --;

			// we delay the next shift with 1/10s. (sorry, hardcoded)
			shiftDelay = now + 0.1f;
		}
	}
	
	float motorRPM = 0.0f;
	float killEngine = 0.0f;

	int slowUpdate = 0;
			
	public void HandleMotor(float steer, float accel) {
		float delta = Time.fixedDeltaTime;
		bool brake = false;

		if (recordRoute != null) {
			recordRoute.Add(this.gameObject, accel, steer, CurrentGear);
		}
		if (accel < 0.0f) {
			// if we try to decelerate we brake.
			brake = true;
			accel = 0.0f;
		}

		float rpm = 0.0f;
		int motorizedWheels = 0;

		// calc rpm from current wheel speed and do some updating
		foreach (WheelData w in wheels) {
			WheelHit hit;
			WheelCollider col = w.col;
			bool motor = ((w.wheeltype  wheelDrive) != 0);
			
			// only calculate rpm on wheels that are connected to engine
			if (motor) {
				rpm += col.rpm;
				motorizedWheels++;
			}
			
			// calculate the local rotation of the wheels from the delta time and rpm
			// then set the local rotation accordingly (also adjust for steering)
			w.rotation = Mathf.Repeat(w.rotation + delta * col.rpm * 360.0f / 60.0f, 360.0f);
			w.transform.localRotation = Quaternion.Euler(w.rotation, col.steerAngle, 0.0f);
			
			// let the wheels contact the ground, if no groundhit extend max suspension distance
			Vector3 lp = w.transform.localPosition;
			if (col.GetGroundHit(out hit)) {
				lp.y -= Vector3.Dot(w.transform.position - hit.point, transform.up) - col.radius;
				w.floorcontact = true;
			}
			else {
				lp.y = w.startPos.y - suspensionDistance;
				w.floorcontact = false;
			}
			w.transform.localPosition = lp;
		}
		// calculate the actual motor rpm from the wheels connected to the engine
		// note we haven't corrected for gear yet.
		if (motorizedWheels > 1) {
			rpm = rpm / motorizedWheels;
		}
		// we use the rpm to calculate distance car has driven
		totaldist += rpm * rpmtodist * delta;
		
		// correct RPM for gear used
		float calcRPM = Mathf.Abs(rpm * gears[currentGear]);
		motorRPM = Mathf.Clamp(calcRPM, motorRPM - maxRPMDecrease * delta, motorRPM + maxRPMIncrease * delta);
		
		// don't blow the engine
		if (motorRPM > 6500.0f) motorRPM = 6500.0f;
		
		// calc torque given RPM, gear, accel (throttle) and number of motorized wheels
		float newTorque = CalcPower(currentGear, motorRPM) * accel / motorizedWheels;

		// go set torque to the wheels
		foreach (WheelData w in wheels) {
			WheelCollider col = w.col;
			
			// of course, only the wheels connected to the engine can get engine torque
			if ((w.wheeltype  wheelDrive) != 0) {
				if (w.floorcontact) {
					col.motorTorque = newTorque * accel;				}
				else {
					col.motorTorque = newTorque * accel * .1f;
				}
			}
			// check if we have to brake
			col.brakeTorque = (brake)?brakeTorque:0.0f;
			
			// set steering angle
			col.steerAngle = steer * w.maxSteer;
		}
		// let engine not run below idle
		motorRPM = Mathf.Max(motorRPM, idleRPM);
		
		// if we have an audiosource (motorsound) adjust pitch using rpm		
		if ((audioSource != null)  (slowUpdate-- <= 0)) {
			slowUpdate = 10;
			// calculate pitch (keep it within reasonable bounds)
			float pitch = Mathf.Clamp(1.0f + ((motorRPM - idleRPM) / (idleRPM)), 1.0f, 10.0f);
			audioSource.pitch = pitch;
			
			if ((motorRPM > idleRPM + 1f) || (accel > 0.0f)) {
				// turn on sound if it's not playing yet and RPM is > idle
				if (!audioSource.isPlaying) {
					audioSource.Play();
				}
				// how long we should wait with engine RPM is idle before killing engine sound
				killEngine = Time.time + killEngineSoundTimeout;
			}
			else if ((audioSource.isPlaying)  (Time.time > killEngine)) {
				// standing still, kill engine sound.
				audioSource.Stop();
			}
		}
	}	
}

That’s all the code I currently use.

Another interesting fact I just discovered:

I removed the main camera from the car and gave it a fixed view. Now I can drive the car without having the camera move along with it. Guess what - same issue. The car simply doesnt run smooth. I think I need to check back on my physics setup, which is the same as on JCar demo by the way.

[EDIT: I see the very same thing happening when I run it on my desktop. I mean the preview window inside Unity, not the blurry one on remote]

friendlydeveloper, trying running it in Update() rather than FixedUpdate().