Potential FPS Improvement...

The place to discuss all things Receiver.
Post Reply
Pandemonia
Posts: 2
Joined: Tue Dec 14, 2010 10:53 pm

Potential FPS Improvement...

Post by Pandemonia » Tue Feb 26, 2013 2:58 am

I'm new to Unity and game programming in general, but I was looking through the Receiver source code from https://github.com/JamesWilko/ReceiverMod .

When running this with the Unity profiler, a script "ShootableLights.js" was taking up 20% of the CPU time, specifically in the Update() method. I've been able to reduce this to "0.0%" with a quick change that doesn't seem to have a visual impact on the game.

I shifted the two noisy loops from the end of Update to both Start() and WasShot(). These loops seem to be for updating the lighting contributions of the destroyable lights on their surroundings. The impact of this code change is that the lights only recalculate their contributions when theyre made or destroyed. If these lights are animated in any way while alive, my code alteration will cause problems.

I hope this is useful.
-P

The altered code:

Code: Select all

#pragma strict

var destroy_effect : GameObject;
var light_color = Color(1,1,1);
var destroyed = false;
enum LightType {AIRPLANE_BLINK, NORMAL, FLICKER}
public var light_type = LightType.NORMAL;
private var blink_delay = 0.0;
private var light_amount = 1.0;

function WasShot(obj : GameObject, pos : Vector3, vel : Vector3) {
	if(!destroyed){
		destroyed = true;
		light_amount = 0.0;
		Instantiate(destroy_effect, transform.FindChild("bulb").position, Quaternion.identity);
	}
	if(obj && obj.collider && obj.collider.material.name == "glass (Instance)"){
		GameObject.Destroy(obj);
	}
		var combined_color = Color(light_color.r * light_amount,light_color.g * light_amount,light_color.b * light_amount);
	for(var light : Light in gameObject.GetComponentsInChildren(Light)){
		light.color = combined_color;
	}
	for(var renderer : MeshRenderer in gameObject.GetComponentsInChildren(MeshRenderer)){
		renderer.material.SetColor("_Illum", combined_color);
		if(renderer.gameObject.name == "shade"){
			renderer.material.SetColor("_Illum", combined_color * 0.5);
		}
	}
}

function Start () {
		var combined_color = Color(light_color.r * light_amount,light_color.g * light_amount,light_color.b * light_amount);
	for(var light : Light in gameObject.GetComponentsInChildren(Light)){
		light.color = combined_color;
	}
	for(var renderer : MeshRenderer in gameObject.GetComponentsInChildren(MeshRenderer)){
		renderer.material.SetColor("_Illum", combined_color);
		if(renderer.gameObject.name == "shade"){
			renderer.material.SetColor("_Illum", combined_color * 0.5);
		}
	}
}

function Update () {
	if(!destroyed){
		switch(light_type){
			case LightType.AIRPLANE_BLINK:
				if(blink_delay <= 0.0){
					blink_delay = 1.0;
					if(light_amount == 1.0){
						light_amount = 0.0;
					} else {
						light_amount = 1.0;
					}
				}
				blink_delay -= Time.deltaTime;
				break;
		}
	}
}

User avatar
Count Roland
Posts: 2936
Joined: Tue Sep 25, 2007 11:15 pm
Location: Galapagos Islands, rodeoin some turtles.
Contact:

Re: Potential FPS Improvement...

Post by Count Roland » Tue Feb 26, 2013 6:33 am

I'll have to try this out, thank you.

Pengwertle
Posts: 22
Joined: Mon Aug 06, 2012 11:29 am

Re: Potential FPS Improvement...

Post by Pengwertle » Fri Mar 01, 2013 7:24 pm

Sorry, for being completely clueless, but how might I apply this to Receiver?

EDIT: So is no one going to reply...?

Llama Fragments
Posts: 1
Joined: Tue Aug 16, 2011 3:27 am

Re: Potential FPS Improvement...

Post by Llama Fragments » Mon Mar 04, 2013 11:35 pm

Pengwertle wrote:Sorry, for being completely clueless, but how might I apply this to Receiver?

EDIT: So is no one going to reply...?
If you have the actual standalone game application, there's no real way you can apply this. If you have a copy of Unity, on the other hand, you can replace some of the actual game code that Wolfire has provided for download.

I also was able to find a number of performance enhancements: In RobotScript.js, both the UpdateDrone and UpdateStationaryTurret functions were calling GameObject.Find every frame to grab the player GameObject. Find searches through all of the objects in the scene and finds one by name. Unless Unity has some sort of optimization for it, Find is O(n). The player object doesn't change that often, so you can pretty easily add this:

Code: Select all

private var playerTemp:GameObject = null;
function UpdateDrone() {
	if (playerTemp == null){
		playerTemp = GameObject.Find("Player");
	}
...
And replace all the GameObject.Find calls with playerTemp, so it's O(n) the first time and O(1) all the other times. This could probably be optimized even further by having some sort of game-controller object that keeps track of the player for all the drones that come in and out of the game. There are also a bunch of FindChild calls that could be fixed in a similar way. I also think there's a lower-overhead way of doing the RotateAround bit in UpdateStationaryTurret, maybe some creative parenting?

Pengwertle
Posts: 22
Joined: Mon Aug 06, 2012 11:29 am

Re: Potential FPS Improvement...

Post by Pengwertle » Tue Mar 05, 2013 9:10 am

Oh, alright. Thank you for the response, though :)

Pandemonia
Posts: 2
Joined: Tue Dec 14, 2010 10:53 pm

Re: Potential FPS Improvement...

Post by Pandemonia » Wed Mar 06, 2013 11:00 pm

Sorry for missing your response; busy week at work.

Yeah, there's no real way to apply this to the main receiver game. I was posting this mostly as a way to help the devs/community.

Regarding the AI script, I did that exact same thing :) I also replaced some of the globally referenced objects with member variables in a couple other places too. I think there was a performance gain, but I wasn't able to measure it as well. It seems that the AI scripts take ~7-9% CPU per active robot within a certain radius.

User avatar
Josh707
Posts: 123
Joined: Sun Jan 22, 2012 3:09 pm
Location: Canada

Re: Potential FPS Improvement...

Post by Josh707 » Thu Oct 03, 2013 11:33 pm

Pandemonia wrote:I'm new to Unity and game programming in general, but I was looking through the Receiver source code from https://github.com/JamesWilko/ReceiverMod .

When running this with the Unity profiler, a script "ShootableLights.js" was taking up 20% of the CPU time, specifically in the Update() method. I've been able to reduce this to "0.0%" with a quick change that doesn't seem to have a visual impact on the game.

I shifted the two noisy loops from the end of Update to both Start() and WasShot(). These loops seem to be for updating the lighting contributions of the destroyable lights on their surroundings. The impact of this code change is that the lights only recalculate their contributions when theyre made or destroyed. If these lights are animated in any way while alive, my code alteration will cause problems.

I hope this is useful.
-P

The altered code:
[+] Code

Code: Select all

#pragma strict

var destroy_effect : GameObject;
var light_color = Color(1,1,1);
var destroyed = false;
enum LightType {AIRPLANE_BLINK, NORMAL, FLICKER}
public var light_type = LightType.NORMAL;
private var blink_delay = 0.0;
private var light_amount = 1.0;

function WasShot(obj : GameObject, pos : Vector3, vel : Vector3) {
	if(!destroyed){
		destroyed = true;
		light_amount = 0.0;
		Instantiate(destroy_effect, transform.FindChild("bulb").position, Quaternion.identity);
	}
	if(obj && obj.collider && obj.collider.material.name == "glass (Instance)"){
		GameObject.Destroy(obj);
	}
		var combined_color = Color(light_color.r * light_amount,light_color.g * light_amount,light_color.b * light_amount);
	for(var light : Light in gameObject.GetComponentsInChildren(Light)){
		light.color = combined_color;
	}
	for(var renderer : MeshRenderer in gameObject.GetComponentsInChildren(MeshRenderer)){
		renderer.material.SetColor("_Illum", combined_color);
		if(renderer.gameObject.name == "shade"){
			renderer.material.SetColor("_Illum", combined_color * 0.5);
		}
	}
}

function Start () {
		var combined_color = Color(light_color.r * light_amount,light_color.g * light_amount,light_color.b * light_amount);
	for(var light : Light in gameObject.GetComponentsInChildren(Light)){
		light.color = combined_color;
	}
	for(var renderer : MeshRenderer in gameObject.GetComponentsInChildren(MeshRenderer)){
		renderer.material.SetColor("_Illum", combined_color);
		if(renderer.gameObject.name == "shade"){
			renderer.material.SetColor("_Illum", combined_color * 0.5);
		}
	}
}

function Update () {
	if(!destroyed){
		switch(light_type){
			case LightType.AIRPLANE_BLINK:
				if(blink_delay <= 0.0){
					blink_delay = 1.0;
					if(light_amount == 1.0){
						light_amount = 0.0;
					} else {
						light_amount = 1.0;
					}
				}
				blink_delay -= Time.deltaTime;
				break;
		}
	}
}
Nice find, I never thought to check for simple optimizations like this, they definitely made an improvement for me. The one thing about your light script changes is that it stops those certain lights from blinking. It's an easy fix though, just add a function to update the light and material and have it called at the bottom of the switch statement. Also, why does almost every single object have an animation component? Would removing them all have any performance impact?

Another tweak worth mentioning is this 'ghost bullet' that was mentioned in this thread. In BulletScript it sets the variable old_pos when the bullet is created and linecasts from that position to the bullets current, which causes a drone that flies into any part between the bullet and the initial position to be hit. Most of the time the bullets 'life_time' runs out and is destroyed before anything reaches it, though. So if you set old_pos as transform.position at the very end of the Update function it will linecast from the previous frames position to the current which should eliminate that problem.

Post Reply