万年素人からHackerへの道

万年素人がHackerになれるまで殴り書きするぜ。

UnityのJSの型キャスティング

networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3( 40,1, 40), playerList[0].number);

アプリで吐き出すのなら型をはっきりしなくても勝手にダック・タイピングになるけど、
Androidで吐き出すとなると型が厳しくる
上のコードはコンパイル無理。

networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3( 40,1, 40), (playerList[0] as CastlePlayer).number);

メソッドを実行したものを変数へ代入なしでそのまま使いたいときはこうする
"as"がいる。今回はCastlePlayer型でキャストした。

Unity MasterServerの用意 Unity

参考:http://d.hatena.ne.jp/nyakagawan/20110729/1311933651
(注意)Pro版でないとできないっぽい

DL先:http://unity3d.com/master-server/index.html
解答時にフォルダが生成されないので自分で作る

mkdir SrcRoot
cd SrcRoot
unzip MasterServer-2.0.f1.zip

ncursesが必要なのでportでインストール

sudo port install ncurses

→会社とかではできないかも?

cd SrcRoot
make

「MasterServer」という実行ファイルが出来る

  • UnityプロジェクトをDLする(MasterServerへ接続するサンプル)

DL先:http://unity3d.com/support/resources/example-projects/networking-example.html

  • Car.js(最新版ではバグなので修正する)
#pragma strict

//maximal corner and braking acceleration capabilities
var maxCornerAccel=10.0;
var maxBrakeAccel=10.0;

//center of gravity height - effects tilting in corners
var cogY = 0.0;

//engine powerband
var minRPM = 700;
var maxRPM = 6000;

//maximum Engine Torque
var maxTorque = 400;

//automatic transmission shift points
var shiftDownRPM = 2500;
var shiftUpRPM = 5500;

//gear ratios
var gearRatios = [-2.66, 2.66, 1.78, 1.30, 1.00];
var finalDriveRatio = 3.4;

//a basic handling modifier:
//1.0 understeer
//0.0 oversteer
var handlingTendency = 0.7;

//graphical wheel objects
var wheelFR : Transform;
var wheelFL : Transform;
var wheelBR : Transform;
var wheelBL : Transform;

//suspension setup
var suspensionDistance = 0.3;
var springs = 1000;
var dampers = 200;
var wheelRadius = 0.45;

//particle effect for ground dust
var groundDustEffect : Transform;

private var queryUserInput = true;
private var engineRPM : float;
private var steerVelo = 0.0;
private var brake = 0.0;
private var handbrake = 0.0;
private var steer = 0.0;
private var motor = 0.0;
private var skidTime = 0.0;
private var onGround = false;
private var cornerSlip = 0.0;
private var driveSlip = 0.0;
private var wheelRPM : float;
private var gear = 1;
private var skidmarks : Skidmarks;
private var wheels : WheelData[];
private var wheelY = 0.0;
private var rev = 0.0;

//Functions to be used by external scripts 
//controlling the car if required
//===================================================================

//return a status string for the vehicle
function GetStatus(gui : GUIText) {
	gui.text="v="+(rigidbody.velocity.magnitude * 3.6).ToString("f1") + " km/h\ngear= "+gear+"\nrpm= "+engineRPM.ToString("f0");
}

//return an information string for the vehicle
function GetControlString(gui : GUIText) {
	gui.text="Use arrow keys to control the jeep,\nspace for handbrake.";
}

//Enable or disable user controls
function SetEnableUserInput(enableInput :boolean) {
	queryUserInput=enableInput;
}

//Car physics
//===================================================================

//some whee calculation data
class WheelData{
	var rotation = 0.0;
	var coll : WheelCollider;
	var graphic : Transform;
	var maxSteerAngle = 0.0;
	var lastSkidMark = -1;
	var powered = false;
	var handbraked = false;
	var originalRotation : Quaternion;
};

function Start () {
	//setup wheels
	wheels=new WheelData[4];
	for(var i:int=0;i<4;i++)
		wheels[i] = new WheelData();
		
	wheels[0].graphic = wheelFL;
	wheels[1].graphic = wheelFR;
	wheels[2].graphic = wheelBL;
	wheels[3].graphic = wheelBR;

	wheels[0].maxSteerAngle=30.0;
	wheels[1].maxSteerAngle=30.0;
	wheels[2].powered=true;
	wheels[3].powered=true;
	wheels[2].handbraked=true;
	wheels[3].handbraked=true;

	for(w in wheels)
	{
		if(w.graphic==null)
			Debug.Log("You need to assign all four wheels for the car script!");
		if(!w.graphic.transform.IsChildOf(transform))	
			Debug.Log("Wheels need to be children of the Object with the car script");
			
		w.originalRotation = w.graphic.localRotation;

		//create collider
		var colliderObject = new GameObject("WheelCollider");
		colliderObject.transform.parent = transform;
		colliderObject.transform.position = w.graphic.position;
		w.coll = colliderObject.AddComponent(WheelCollider);
		w.coll.suspensionDistance = suspensionDistance;
		w.coll.suspensionSpring.spring = springs;
		w.coll.suspensionSpring.damper = dampers;
		//no grip, as we simulate handling ourselves
		w.coll.forwardFriction.stiffness = 0;
		w.coll.sidewaysFriction.stiffness = 0;
		w.coll.radius = wheelRadius;
	}	

	//get wheel height (height forces are applied on)
	wheelY=wheels[0].graphic.localPosition.y;
	
	//setup center of gravity
	rigidbody.centerOfMass.y = cogY;
	
	//find skidmark object
	skidmarks = FindObjectOfType(typeof(Skidmarks));
	
	//shift to first
	gear=1;
}

//update wheel status
function UpdateWheels()
{
	//calculate handbrake slip for traction gfx
 	var handbrakeSlip=handbrake*rigidbody.velocity.magnitude*0.1;
	if(handbrakeSlip>1)
		handbrakeSlip=1;
		
	var totalSlip=0.0;
	onGround=false;
	for(w in wheels)
	{		
		//rotate wheel
		w.rotation += wheelRPM / 60.0 * -rev * 360.0 * Time.fixedDeltaTime;
		w.rotation = Mathf.Repeat(w.rotation, 360.0);		
		w.graphic.localRotation= Quaternion.Euler( w.rotation, w.maxSteerAngle*steer, 0.0 ) * w.originalRotation;

		//check if wheel is on ground
		if(w.coll.isGrounded)
			onGround=true;
			
		var slip = cornerSlip+(w.powered?driveSlip:0.0)+(w.handbraked?handbrakeSlip:0.0);
		totalSlip += slip;
		
		var hit : WheelHit;
		var c : WheelCollider;
		c = w.coll;
		if(c.GetGroundHit(hit))
		{
			//if the wheel touches the ground, adjust graphical wheel position to reflect springs
			w.graphic.localPosition.y-=Vector3.Dot(w.graphic.position-hit.point,transform.up)-w.coll.radius;
			
			//create dust on ground if appropiate
			if(slip>0.5 && hit.collider.tag=="Dusty")
			{
				groundDustEffect.position=hit.point;
				groundDustEffect.particleEmitter.worldVelocity=rigidbody.velocity*0.5;
				groundDustEffect.particleEmitter.minEmission=(slip-0.5)*3;
				groundDustEffect.particleEmitter.maxEmission=(slip-0.5)*3;
				groundDustEffect.particleEmitter.Emit();								
			}
			
			//and skid marks				
			if(slip>0.75 && skidmarks != null)
				w.lastSkidMark=skidmarks.AddSkidMark(hit.point,hit.normal,(slip-0.75)*2,w.lastSkidMark);
			else
				w.lastSkidMark=-1;
		}
		else w.lastSkidMark=-1;
	}
	totalSlip/=wheels.length;
}

//Automatically shift gears
function AutomaticTransmission()
{
	if(gear>0)
	{
		if(engineRPM>shiftUpRPM&&gear<gearRatios.length-1)
			gear++;
		if(engineRPM<shiftDownRPM&&gear>1)
			gear--;
	}
}

//Calculate engine acceleration force for current RPM and trottle
function CalcEngine() : float
{
	//no engine when braking
	if(brake+handbrake>0.1)
		motor=0.0;
	
	//if car is airborne, just rev engine
	if(!onGround)
	{
		engineRPM += (motor-0.3)*25000.0*Time.deltaTime;
		engineRPM = Mathf.Clamp(engineRPM,minRPM,maxRPM);
		return 0.0;
	}
	else
	{
		AutomaticTransmission();
		engineRPM=wheelRPM*gearRatios[gear]*finalDriveRatio;
		if(engineRPM<minRPM)
			engineRPM=minRPM;
		if(engineRPM<maxRPM)
		{
			//fake a basic torque curve
			var x = (2*(engineRPM/maxRPM)-1);
			var torqueCurve = 0.5*(-x*x+2);
			var torqueToForceRatio = gearRatios[gear]*finalDriveRatio/wheelRadius;
			return motor*maxTorque*torqueCurve*torqueToForceRatio;
		}
		else
			//rpm delimiter
			return 0.0;
	}
}

//Car physics
//The physics of this car are really a trial-and-error based extension of 
//basic "Asteriods" physics -- so you will get a pretty arcade-like feel.
//This may or may not be what you want, for a more physical approach research
//the wheel colliders
function HandlePhysics () {
	var velo=rigidbody.velocity;
	wheelRPM=velo.magnitude*60.0*0.5;

	rigidbody.angularVelocity=new Vector3(rigidbody.angularVelocity.x,0.0,rigidbody.angularVelocity.z);
	var dir=transform.TransformDirection(Vector3.forward);
	var flatDir=Vector3.Normalize(new Vector3(dir.x,0,dir.z));
	var flatVelo=new Vector3(velo.x,0,velo.z);
	var rev=Mathf.Sign(Vector3.Dot(flatVelo,flatDir));
	//when moving backwards or standing and brake is pressed, switch to reverse
	if((rev<0||flatVelo.sqrMagnitude<0.5)&&brake>0.1)
		gear=0;
	if(gear==0)
	{	
		//when in reverse, flip brake and gas
		var tmp=brake;
		brake=motor;
		motor=tmp;
		
		//when moving forward or standing and gas is pressed, switch to drive
		if((rev>0||flatVelo.sqrMagnitude<0.5)&&brake>0.1)
			gear=1;
	}
	var engineForce=flatDir*CalcEngine();
	var totalbrake=brake+handbrake*0.5;
	if(totalbrake>1.0)totalbrake=1.0;
	var brakeForce=-flatVelo.normalized*totalbrake*rigidbody.mass*maxBrakeAccel;

	flatDir*=flatVelo.magnitude;
	flatDir=Quaternion.AngleAxis(steer*30.0,Vector3.up)*flatDir;
	flatDir*=rev;
	var diff=(flatVelo-flatDir).magnitude;
	var cornerAccel=maxCornerAccel;
	if(cornerAccel>diff)cornerAccel=diff;
	var cornerForce=-(flatVelo-flatDir).normalized*cornerAccel*rigidbody.mass;
	cornerSlip=Mathf.Pow(cornerAccel/maxCornerAccel,3);
	
	rigidbody.AddForceAtPosition(brakeForce+engineForce+cornerForce,transform.position+transform.up*wheelY);
	
	var handbrakeFactor=1+handbrake*4;
	if(rev<0)
		handbrakeFactor=1;
	var veloSteer=((15/(2*velo.magnitude+1))+1)*handbrakeFactor;
	var steerGrip=(1-handlingTendency*cornerSlip);
	if(rev*steer*steerVelo<0)
		steerGrip=1;
	var maxRotSteer=2*Time.fixedDeltaTime*handbrakeFactor*steerGrip;
	var fVelo=velo.magnitude;
	var veloFactor=fVelo<1.0?fVelo:Mathf.Pow(velo.magnitude,0.3);
	var steerVeloInput=rev*steer*veloFactor*0.5*Time.fixedDeltaTime*handbrakeFactor;
	if(velo.magnitude<0.1)
		steerVeloInput=0;
	if(steerVeloInput>steerVelo)
	{
		steerVelo+=0.02*Time.fixedDeltaTime*veloSteer;
		if(steerVeloInput<steerVelo)
			steerVelo=steerVeloInput;
	}
	else
	{
		steerVelo-=0.02*Time.fixedDeltaTime*veloSteer;
		if(steerVeloInput>steerVelo)
			steerVelo=steerVeloInput;
	}
	steerVelo=Mathf.Clamp(steerVelo,-maxRotSteer,maxRotSteer);	
	transform.Rotate(Vector3.up*steerVelo*57.295788);
}

function FixedUpdate () {
	//query input axes if necessarry
	if(queryUserInput)
	{
		brake = Mathf.Clamp01(-Input.GetAxis("Vertical"));
		handbrake = Input.GetButton("Jump")?1.0:0.0;
		steer = Input.GetAxis("Horizontal");
	 	motor = Mathf.Clamp01(Input.GetAxis("Vertical"));
 	}
	else
	{
		motor = 0;
		steer = 0;
		brake = 0;
		handbrake = 0;
	}


	//if car is on ground calculate handling, otherwise just rev the engine
 	if(onGround)
		HandlePhysics();
	else
		CalcEngine();	
		
	//wheel GFX
	UpdateWheels();

	//engine sounds
	audio.pitch=0.5+0.2*motor+0.8*engineRPM/maxRPM;
	audio.volume=0.5+0.8*motor+0.2*engineRPM/maxRPM;
}

//Called by DamageReceiver if boat destroyed
function Detonate()
{
	//destroy wheels
	for( w in wheels )
		w.coll.gameObject.active=false;

	//no more car physics
	enabled=false;
}

@script RequireComponent (Rigidbody)
@script RequireComponent (AudioSource)
  • "Networking Scripts"フォルダにある、ConnectGuiMasterServer.jsを修正する

Awake()メソッドのすぐしたへ

var isConnectMyMasterServer = true;// falseだとUnity共有MasterServerに接続
if( isConnectMyMasterServer ) {
	MasterServer.ipAddress = "127.0.0.1";
	MasterServer.port = 10000;
}
  • ファイルを弄る
HOMEDIR=[SrcRoot]	#実行ファイルが配置されているところ
USERNAME=自分のPC名前
OPTIONS="-s 3600 -p 10000 -l"
  • サーバ起動
cd SrcRoot
sudo config/unity-masterserver start

Androidのために修正する
→Pluginsフォルダを生成して、Networking ScriptsフォルダにもともとあったNetworkRigidbody.csとNetworkInterpolatedTransform.csを挿入する

  • Networking Scripts -> CarNetworkInit.js(修正)
function OnNetworkInstantiate (msg : NetworkMessageInfo) {
	// This is our own player
	if (networkView.isMine)
	{
		Camera.main.SendMessage("SetTarget", transform);
		(GetComponent("NetworkRigidbody") as NetworkRigidbody).enabled = false;
	}
	// This is just some remote controlled player, don't execute direct
	// user input on this
	else
	{
		name += "Remote";
		GetComponent(Car).SetEnableUserInput(false);
		(GetComponent("NetworkRigidbody") as NetworkRigidbody).enabled = true;
	}
}
  • Authoritative Server -> AuthServerSpawnPlayer.js
var playerPrefab : Transform;
// Local player information when one is instantiated
private var localPlayer : NetworkPlayer;
private var localTransformViewID : NetworkViewID;
private var localAnimationViewID : NetworkViewID;
private var isInstantiated : boolean = false;
// The server uses this to track all intanticated player
private var playerInfo : Array = new Array();

class PlayerInfo {
	var transformViewID : NetworkViewID;
	var animationViewID : NetworkViewID;
	var player : NetworkPlayer;
}

function OnGUI () {
	if (Network.isClient && localPlayer.ToString() != 0 && !isInstantiated) 
		if (GUI.Button(new Rect(20,Screen.height-60, 90, 20),"SpawnPlayer"))
		{
			// Spawn the player on all machines
			networkView.RPC("SpawnPlayer", RPCMode.AllBuffered, localPlayer, localTransformViewID, localAnimationViewID);
			isInstantiated = true;
		}
}

// Receive server initialization, record own identifier as seen by the server.
// This is later used to recognize if a network spawned player is the local player.
// Also record assigned view IDs so the server can synch the player correctly.
@RPC
function InitPlayer (player : NetworkPlayer, tViewID : NetworkViewID, aViewID : NetworkViewID) {
	Debug.Log("Received player init "+ player +". ViewIDs " + tViewID + " and " + aViewID);
	localPlayer = player;
	localTransformViewID = tViewID;
	localAnimationViewID = aViewID;
}

// Create a networked player in the game. Instantiate a local copy of the player, set the view IDs
// accordingly. 
@RPC
function SpawnPlayer (playerIdentifier : NetworkPlayer, transformViewID : NetworkViewID, animationViewID : NetworkViewID) {
	Debug.Log("Instantiating player " + playerIdentifier);
	var instantiatedPlayer : Transform = Instantiate(playerPrefab, transform.position, transform.rotation);
	var networkViews = instantiatedPlayer.GetComponents(NetworkView);
	
	// Assign view IDs to player object
	if (networkViews.Length != 2) {
		Debug.Log("Error while spawning player, prefab should have 2 network views, has "+networkViews.Length);
		return;
	} else {
		(networkViews[0] as NetworkView).viewID = transformViewID;
		(networkViews[1] as NetworkView).viewID = animationViewID;
	}
	// Initialize local player
	if (playerIdentifier == localPlayer) {
		Debug.Log("Enabling user input as this is the local player");
		// W are doing client prediction and thus enable the controller script + user input processing
		instantiatedPlayer.GetComponent(ThirdPersonController).enabled = true;
		instantiatedPlayer.GetComponent(ThirdPersonController).getUserInput = true;
		// Enable input network synchronization (server gets input)
		instantiatedPlayer.GetComponent(NetworkController).enabled = true;
		instantiatedPlayer.SendMessage("SetOwnership", playerIdentifier);
		return;
	// Initialize player on server
	} else if (Network.isServer) {
		instantiatedPlayer.GetComponent(ThirdPersonController).enabled = true;
		instantiatedPlayer.GetComponent(AuthServerPersonAnimation).enabled = true;
		// Record player info so he can be destroyed properly
		var playerInstance : PlayerInfo = new PlayerInfo();
		playerInstance.transformViewID = transformViewID;
		playerInstance.animationViewID = animationViewID;
		playerInstance.player = playerIdentifier;
		playerInfo.Add(playerInstance);
		Debug.Log("There are now " + playerInfo.length + " players active");
	}
}

// This runs if the scene is executed from the loader scene.
// Here we must check if we already have clients connect which must be reinitialized.
// This is the same procedure as in OnPlayerConnected except we process already
// connected players instead of new ones. The already connected players have also
// reloaded the level and thus have a clean slate.
function OnNetworkLoadedLevel() {
	if (Network.isServer && Network.connections.Length > 0) {
		for (var p : NetworkPlayer in Network.connections) {
			Debug.Log("Resending player init to "+p);
			var transformViewID : NetworkViewID = Network.AllocateViewID();
			var	animationViewID : NetworkViewID = Network.AllocateViewID();
			Debug.Log("Player given view IDs "+ transformViewID + " and " + animationViewID);
			networkView.RPC("InitPlayer", p, p, transformViewID, animationViewID);
		}
	}
}

// Send initalization info to the new player, before that he cannot spawn himself
function OnPlayerConnected (player : NetworkPlayer) {
	Debug.Log("Sending player init to "+player);
	var transformViewID : NetworkViewID = Network.AllocateViewID();
	var	animationViewID : NetworkViewID = Network.AllocateViewID();
	Debug.Log("Player given view IDs "+ transformViewID + " and " + animationViewID);
	networkView.RPC("InitPlayer", player, player, transformViewID, animationViewID);
}

function OnPlayerDisconnected (player : NetworkPlayer) {
	Debug.Log("Cleaning up player " + player);
	// Destroy the player object this network player spawned
	var deletePlayer : PlayerInfo;
	for (var playerInstance : PlayerInfo in playerInfo) {
		if (player == playerInstance.player) {
			Debug.Log("Destroying objects belonging to view ID " + playerInstance.transformViewID);
			Network.Destroy(playerInstance.transformViewID);
			deletePlayer = playerInstance;
		}
	}
	playerInfo.Remove(deletePlayer);
	Network.RemoveRPCs(player, 0);
	Network.DestroyPlayerObjects(player);
}
  • Vehicles -> SurfaceEffects -> Tracks -> Skidmarks.js(修正する)
-//Script used by the car Script to create skidmark meshes when cornering.
//Just create an empty GameObject and attach this.
RequireComponent(MeshFilter);
RequireComponent(MeshRenderer);

//maximal number of skidmarks 
var maxMarks = 512;

//width of skid marks
var markWidth = 0.225;

//time interval new mesh segments are generated in
//the lower this value, the smoother the generated tracks
var updateRate = 0.1;
	
private var indexShift = 0;
private var numMarks = 0;
private var updateTime = 0.0;	
private var newTrackFlag = true;
private var updateMeshFlag = true;

//data structure describing a section of skidmarks
class markSection{
	var pos : Vector3;
	var normal : Vector3;
	var posl : Vector3;
	var posr : Vector3;
	var intensity = 0.0;
	var lastIndex = -1;
};

var skidmarks : markSection[];	
	
// Use this for initialization
function Start () {
	//create structures
	skidmarks=new markSection[maxMarks];
	for(var i:int=0;i<maxMarks;i++)
		skidmarks[i]=new markSection();
	if(GetComponent(MeshFilter).mesh==null)
		GetComponent(MeshFilter).mesh=new Mesh();
}
	
//called by the car script to add a skid mark at position pos with the supplied normal.
//transparency can be specified in the intensity parameter. Connects to the track segment 
//indexed by lastIndex (or it won't display if lastIndex is -1). returns an index value 
//which can be passed as lastIndex to the next AddSkidMark call
function AddSkidMark(pos : Vector3, normal : Vector3,intensity : float,lastIndex : int) : int
{
	intensity=Mathf.Clamp01(intensity);
	
	//get index for new segment
	var currIndex:int=numMarks;
	
	//reuse lastIndex if we don't need to create a new one this frame
	if(lastIndex!=-1 && !newTrackFlag)
		currIndex=lastIndex;
		
	//setup skidmark structure
	var curr=skidmarks[currIndex%maxMarks];
	curr.pos=pos+normal*0.05-transform.position;
	curr.normal=normal;
	curr.intensity=intensity;
	
	if(lastIndex==-1 || newTrackFlag)
		curr.lastIndex=lastIndex;
		
	//if we have a valid lastIndex, get positions for marks
	if(curr.lastIndex!=-1)
	{
		var last = skidmarks[curr.lastIndex%maxMarks];
		var dir = (curr.pos-last.pos);
		var xDir : Vector3 = Vector3.Cross(dir,normal).normalized;
		
		curr.posl=curr.pos+xDir*markWidth*0.5;
		curr.posr=curr.pos-xDir*markWidth*0.5;
		
		if(last.lastIndex==-1)
		{
			last.posl=curr.pos+xDir*markWidth*0.5;
			last.posr=curr.pos-xDir*markWidth*0.5;
		}
	}
	if(lastIndex==-1 || newTrackFlag)
		numMarks++;
	updateMeshFlag=true;
	return currIndex;
}

//regenerate the skidmarks mesh	
function UpdateMesh () {
	//count visible segments
	var segmentCount:int = 0;
	for(var i:int=0;i<numMarks&&i<maxMarks;i++)
		if(skidmarks[i].lastIndex!=-1&&skidmarks[i].lastIndex>numMarks-maxMarks)
			segmentCount++;
	
	//create skidmark mesh coordinates
	var vertices=new Vector3[segmentCount*4];
	var normals=new Vector3[segmentCount*4];
	var colors=new Color[segmentCount*4];
	var uvs = new Vector2[segmentCount*4];
	var triangles=new int[segmentCount*6];
	segmentCount = 0;
	for (i=0;i<numMarks&&i<maxMarks;i++)
		if(skidmarks[i].lastIndex!=-1&&skidmarks[i].lastIndex>numMarks-maxMarks)
		{
			var curr=skidmarks[i];
			var last = skidmarks[curr.lastIndex%maxMarks];
						
			vertices[segmentCount*4+0]=last.posl;
			vertices[segmentCount*4+1]=last.posr;
			vertices[segmentCount*4+2]=curr.posl;
			vertices[segmentCount*4+3]=curr.posr;
			
			normals[segmentCount*4+0]=last.normal;
			normals[segmentCount*4+1]=last.normal;
			normals[segmentCount*4+2]=curr.normal;
			normals[segmentCount*4+3]=curr.normal;

			colors[segmentCount*4+0]=new Color(1,1,1,last.intensity);
			colors[segmentCount*4+1]=new Color(1,1,1,last.intensity);
			colors[segmentCount*4+2]=new Color(1,1,1,curr.intensity);
			colors[segmentCount*4+3]=new Color(1,1,1,curr.intensity);

			uvs[segmentCount*4+0]=new Vector2(0,0);
			uvs[segmentCount*4+1]=new Vector2(1,0);
			uvs[segmentCount*4+2]=new Vector2(0,0);
			uvs[segmentCount*4+3]=new Vector2(1,0);
			
			triangles[segmentCount*6+0]=segmentCount*4+0;
			triangles[segmentCount*6+1]=segmentCount*4+1;
			triangles[segmentCount*6+2]=segmentCount*4+2;
			
			triangles[segmentCount*6+3]=segmentCount*4+2;
			triangles[segmentCount*6+4]=segmentCount*4+1;
			triangles[segmentCount*6+5]=segmentCount*4+3;
			segmentCount++;			
		}
	
	//update mesh
	var mesh = GetComponent(MeshFilter).mesh;
	mesh.Clear();
	mesh.vertices=vertices;
	mesh.normals=normals;
	mesh.triangles=triangles;
	mesh.colors=colors;
	mesh.uv=uvs;
	updateMeshFlag=false;
}

function Update()
{
	//update mesh if skidmarks have changed since last frame
	if(updateMeshFlag)
		UpdateMesh();
}

function FixedUpdate()
{
	//set flag for creating new segments this frame if an update is pending
	newTrackFlag = false;
	updateTime += Time.deltaTime;
	if(updateTime > updateRate)
	{
		newTrackFlag = true;
		updateTime -= updateRate;
	}
}
  • Networking Scripts -> ConnectGuiMasterServer.js(修正する)
DontDestroyOnLoad(this);

var gameName = "You must change this";
var serverPort = 25002;

private var timeoutHostList = 0.0;
private var lastHostListRequest = -1000.0;
private var hostListRefreshTimeout = 10.0;

private var connectionTestResult : ConnectionTesterStatus = ConnectionTesterStatus.Undetermined;
private var filterNATHosts = false;
private var probingPublicIP = false;
private var doneTesting = false;
private var timer : float = 0.0;
private var useNat = false;		// Should the server enabled NAT punchthrough feature

private var windowRect : Rect;
private var serverListRect;
private var hideTest = false;
private var testMessage = "Undetermined NAT capabilities";

// Enable this if not running a client on the server machine
//MasterServer.dedicatedServer = true;

function OnFailedToConnectToMasterServer(info: NetworkConnectionError)
{
	Debug.Log(info);
}

function OnFailedToConnect(info: NetworkConnectionError)
{
	Debug.Log(info);
}

function OnGUI ()
{
	windowRect = GUILayout.Window (0, windowRect, MakeWindow, "Server Controls");
	if (Network.peerType == NetworkPeerType.Disconnected && MasterServer.PollHostList().Length != 0)
		serverListRect = GUILayout.Window(1, serverListRect, MakeClientWindow, "Server List");
}

function Awake ()
{
	
	var isConnectMyMasterServer = true;//falseにするとUnity共有MasterServerに接続
	if( isConnectMyMasterServer ) {
		MasterServer.ipAddress = "127.0.0.1";
		MasterServer.port = 10000;
	}
	

	windowRect = Rect(Screen.width-300,0,300,100);
	serverListRect = Rect(0, 0, Screen.width - windowRect.width, 100);
	// Start connection test
	connectionTestResult = Network.TestConnection();
	
	// What kind of IP does this machine have? TestConnection also indicates this in the
	// test results
	if (Network.HavePublicAddress())
		Debug.Log("This machine has a public IP address");
	else
		Debug.Log("This machine has a private IP address");
}

function Update()
{
	// If test is undetermined, keep running
	if (!doneTesting)
		TestConnection();
}

function TestConnection()
{
	// Start/Poll the connection test, report the results in a label and react to the results accordingly
	connectionTestResult = Network.TestConnection();
	switch (connectionTestResult)
	{
		case ConnectionTesterStatus.Error: 
			testMessage = "Problem determining NAT capabilities";
			doneTesting = true;
			break;
			
		case ConnectionTesterStatus.Undetermined: 
			testMessage = "Undetermined NAT capabilities";
			doneTesting = false;
			break;
						
		case ConnectionTesterStatus.PublicIPIsConnectable:
			testMessage = "Directly connectable public IP address.";
			useNat = false;
			doneTesting = true;
			break;
			
		// This case is a bit special as we now need to check if we can 
		// circumvent the blocking by using NAT punchthrough
		case ConnectionTesterStatus.PublicIPPortBlocked:
			testMessage = "Non-connectble public IP address (port " + serverPort +" blocked), running a server is impossible.";
			useNat = false;
			// If no NAT punchthrough test has been performed on this public IP, force a test
			if (!probingPublicIP)
			{
				Debug.Log("Testing if firewall can be circumvented");
				connectionTestResult = Network.TestConnectionNAT();
				probingPublicIP = true;
				timer = Time.time + 10;
			}
			// NAT punchthrough test was performed but we still get blocked
			else if (Time.time > timer)
			{
				probingPublicIP = false; 		// reset
				useNat = true;
				doneTesting = true;
			}
			break;
		case ConnectionTesterStatus.PublicIPNoServerStarted:
			testMessage = "Public IP address but server not initialized, it must be started to check server accessibility. Restart connection test when ready.";
			break;
			
		case ConnectionTesterStatus.LimitedNATPunchthroughPortRestricted:
			Debug.Log("LimitedNATPunchthroughPortRestricted");
			testMessage = "Limited NAT punchthrough capabilities. Cannot connect to all types of NAT servers.";
			useNat = true;
			doneTesting = true;
			break;
					
		case ConnectionTesterStatus.LimitedNATPunchthroughSymmetric:
			Debug.Log("LimitedNATPunchthroughSymmetric");
			testMessage = "Limited NAT punchthrough capabilities. Cannot connect to all types of NAT servers. Running a server is ill adviced as not everyone can connect.";
			useNat = true;
			doneTesting = true;
			break;
		
		case ConnectionTesterStatus.NATpunchthroughAddressRestrictedCone:
		case ConnectionTesterStatus.NATpunchthroughFullCone:
			Debug.Log("NATpunchthroughAddressRestrictedCone || NATpunchthroughFullCone");
			testMessage = "NAT punchthrough capable. Can connect to all servers and receive connections from all clients. Enabling NAT punchthrough functionality.";
			useNat = true;
			doneTesting = true;
			break;

		default: 
			testMessage = "Error in test routine, got " + connectionTestResult;
	}
	//Debug.Log(connectionTestResult + " " + probingPublicIP + " " + doneTesting);
}

function MakeWindow (id : int)
{	
	hideTest = GUILayout.Toggle(hideTest, "Hide test info");
	
	if (!hideTest)
	{
		GUILayout.Label(testMessage);
		if (GUILayout.Button ("Retest connection"))
		{
			Debug.Log("Redoing connection test");
			probingPublicIP = false;
			doneTesting = false;
			connectionTestResult = Network.TestConnection(true);
		}
	}
	
	if (Network.peerType == NetworkPeerType.Disconnected)
	{
		GUILayout.BeginHorizontal();
		GUILayout.Space(10);
		// Start a new server
		if (GUILayout.Button ("Start Server"))
		{
			Network.InitializeServer(32, serverPort, useNat);
			MasterServer.RegisterHost(gameName, "stuff", "l33t game for all");
		}

		// Refresh hosts
		if (GUILayout.Button ("Refresh available Servers") || Time.realtimeSinceStartup > lastHostListRequest + hostListRefreshTimeout)
		{
			MasterServer.RequestHostList (gameName);
			lastHostListRequest = Time.realtimeSinceStartup;
		}
		
		GUILayout.FlexibleSpace();
		
		GUILayout.EndHorizontal();
	}
	else
	{
		if (GUILayout.Button ("Disconnect"))
		{
			Network.Disconnect();
			MasterServer.UnregisterHost();
		}
		GUILayout.FlexibleSpace();
	}
	GUI.DragWindow (Rect (0,0,1000,1000));
}

function MakeClientWindow(id : int)
{
	GUILayout.Space(5);

	var data : HostData[] = MasterServer.PollHostList();
	var count = 0;
	for (var element in data)
	{
		GUILayout.BeginHorizontal();

		// Do not display NAT enabled games if we cannot do NAT punchthrough
		if ( !(filterNATHosts && element.useNat) )
		{
			var connections = element.connectedPlayers + "/" + element.playerLimit;
			GUILayout.Label(element.gameName);
			GUILayout.Space(5);
			GUILayout.Label(connections);
			GUILayout.Space(5);
			var hostInfo = "";
			
			// Indicate if NAT punchthrough will be performed, omit showing GUID
			if (element.useNat)
			{
				GUILayout.Label("NAT");
				GUILayout.Space(5);
			}
			// Here we display all IP addresses, there can be multiple in cases where
			// internal LAN connections are being attempted. In the GUI we could just display
			// the first one in order not confuse the end user, but internally Unity will
			// do a connection check on all IP addresses in the element.ip list, and connect to the
			// first valid one.
			for (var host in element.ip)
				hostInfo = hostInfo + host + ":" + element.port + " ";
			
			//GUILayout.Label("[" + element.ip + ":" + element.port + "]");	
			GUILayout.Label(hostInfo);	
			GUILayout.Space(5);
			GUILayout.Label(element.comment);
			GUILayout.Space(5);
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("Connect"))
				Network.Connect(element);
		}
		GUILayout.EndHorizontal();	
	}
}
  • Castles ->CastlesNetworkInit.js
var castlePrefab : Transform;
var playerPrefab : Transform;

private var invisiblePlayer : Object;

// Server variables
var maxPlayers = 1;
private var playerCount : int = 0;

// The playerList is used by both server and client, the client sets his offical server approved info here.
var playerList : Array = new Array();

// Local player credentials (his profile)
public var playerName : String = "Testing";
public var playerColor : Color = Color.yellow;

private var isInitialized : boolean = false;
private var gameStarted : boolean = false;

private var playerWindow = Rect (10,60,110,50);

private var serverWindow = Rect (130,60,110,50);

private var colorSelection : String[] = ["Green", "Yellow", "Black", "Grey"];
private var selectedColor : int;
private var playerNumbers : String[] = ["2", "3", "4"];

class CastlePlayer {
	var number : int;
	var color : Color;
	var name : String;
	var player : NetworkPlayer;
	
	function ToString() : String {
		return name + " " + number + " " + color + " " + player;
	}
}

function OnGUI() {
	if (isInitialized) {
		GUI.Label(new Rect(Screen.width-150, 20, 140, 50), "Player name: "+ (playerList[0] as CastlePlayer).name + "\nPlayer number: " + (playerList[0] as CastlePlayer).number);
	}
	else {
		playerWindow = GUILayout.Window(1, playerWindow, MakePlayerWindow, "Player Info");
		serverWindow = GUILayout.Window(2, serverWindow, MakeServerWindow, "Server Info");
	}
	if (isInitialized && !gameStarted) {
		GUI.contentColor = Color.blue;
		GUI.Label(new Rect(20, Screen.height-60, 250, 20), "Ghost mode, waiting for more players");
	}
}

function MakePlayerWindow(id : int) {
	GUILayout.Label("Name:");
	playerName = GUILayout.TextField(playerName);
	GUILayout.Label("Color:");
	selectedColor = GUILayout.SelectionGrid(selectedColor, colorSelection, 1);
	switch (selectedColor) {
		case 0: playerColor = Color.green; break;
		case 1: playerColor = Color.yellow; break;
		case 2: playerColor = Color.black; break;
		case 3: playerColor = Color.grey; break;
		default: playerColor = Color.blue; break;
	}
}

function MakeServerWindow(id : int) {
	GUILayout.Label("Max Players:");
	var tmp = GUILayout.SelectionGrid(maxPlayers-2, playerNumbers, 3);
	maxPlayers = tmp + 2;
}
function OnNetworkLoadedLevel ()
{
	// Start invisible floating body camera
	invisiblePlayer = Instantiate(playerPrefab, new Vector3(0,2,0), Quaternion.identity);
	Camera.main.SendMessage("SetTarget", invisiblePlayer);
	
	if (Network.isServer && !isInitialized) {
		// Doing server/client now
		Network.maxConnections = maxPlayers-1;
		var playerInfo = new CastlePlayer();
		playerInfo.number = playerCount++;
		playerInfo.name = playerName;
		playerInfo.color = playerColor;
		// Add self to list
		playerList.Add(playerInfo);
		isInitialized = true;
	}
	
	// If I'm a client and I'm already connected to a server, then re-request player entry to game
	if (Network.isClient && Network.connections.Length > 0) {
		OnConnectedToServer();
	}
}

@RPC
function ReceivePlayer (number: int, r: float, g: float, b: float, name: String, player: NetworkPlayer) {
	var somePlayer : CastlePlayer = new CastlePlayer();
	somePlayer.number = number;
	somePlayer.color = new Color(r,g,b);
	somePlayer.name = name;
	somePlayer.player = player;
	playerList.Add(somePlayer);
	Debug.Log("Received info on player: " + somePlayer);
}

// Server uses this to tell the client his network player is approved and ready to join
@RPC
function InitializeClient (player: NetworkPlayer, number: int, r: float, g: float, b: float) {
	Debug.Log("Client initialized with number "+number);
	
	var localPlayer : CastlePlayer = new CastlePlayer();
	localPlayer.player = player;
	localPlayer.name = playerName;
	localPlayer.number = number;
	localPlayer.color = new Color(r,g,b);
	playerList.Add(localPlayer);
	isInitialized = true;
}

// Client uses this to announce himself and his desired credentials (like color) to the server
@RPC
function RequestPlayer (name: String, r: float, g: float, b: float, info: NetworkMessageInfo) {
	var newColor = new Color(r,g,b);
	// Check if color is already used
	var available : boolean = true;
	for (var p : CastlePlayer in playerList) {
		if (newColor == p.color)
			available = false;
	}
	// Create new player in list
	var playerInfo = new CastlePlayer();
	playerInfo.player = info.sender;

	// Initilize the rest of the variables
	playerInfo.number = playerCount++;
	if (available)
		playerInfo.color = newColor;
	else {
		Debug.Log("Requested color already in use, giving player default color");
		playerInfo.color = Color.green;
	}
	playerInfo.name = name;

	playerList.Add(playerInfo);

	// Send init message to client, confirming he is in the game and his parameters are initialized
	networkView.RPC("InitializeClient", info.sender, info.sender, playerInfo.number, playerInfo.color.r, playerInfo.color.g, playerInfo.color.b);
	
	// If all players have arrived, start the game
	if (Network.isServer && playerCount == maxPlayers)
	{
		// Send player list to clients
		for (var receiver : CastlePlayer in playerList) {
			for (var playerInfo : CastlePlayer in playerList) {
				if (receiver.player != playerInfo.player && receiver.player.ToString() != "0") {
					Debug.Log("Sending info on player " + playerInfo.player + " to player " + receiver.player);
					networkView.RPC("ReceivePlayer", receiver.player, playerInfo.number, playerInfo.color.r, playerInfo.color.g, playerInfo.color.b, playerInfo.name, playerInfo.player);
				}
			}
		}
		Debug.Log ("All " + playerCount + " player(s) connected, starting game.");
		networkView.RPC("OnGameReady", RPCMode.All);
	}
}

// Run everywhere when all players have connected, also runs on server as he is also a client
@RPC
function OnGameReady ()
{
	Debug.Log ("Starting game with player number " + (playerList[0] as CastlePlayer).number);

	var viewID : NetworkViewID = Network.AllocateViewID();
	switch ((playerList[0] as CastlePlayer).number)
	{
		case 0:
			networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3(-40,1,-40), (playerList[0] as CastlePlayer).number);
			Camera.main.transform.position = new Vector3(-45,5,-45);
			break;
		case 1:
			networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3(-40,1, 40), (playerList[0] as CastlePlayer).number);
			Camera.main.transform.position = new Vector3(-45,5,45);
			break;
		case 2: 
			networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3(40,1, -40), (playerList[0] as CastlePlayer).number);
			Camera.main.transform.position = new Vector3(45,5,-45);
			break;
		case 3: 
			networkView.RPC("SpawnCastle", RPCMode.AllBuffered, viewID, new Vector3( 40,1, 40), (playerList[0] as CastlePlayer).number);
			Camera.main.transform.position = new Vector3(45,5,45);
			break;
		default: 
			Debug.Log ("Invalid player number given during game start: " + (playerList[0] as CastlePlayer).number);
	}
	gameStarted = true;
	
	// Destroy invisible player
	Destroy((invisiblePlayer as GameObject).gameObject);
}

function GetColor(playerNumber: int) : Color {
	for (var p : CastlePlayer in playerList) {
		if (p.number == playerNumber)
			return p.color;
	}
	Debug.Log("Couldn't find color for player " + playerNumber);
	return Color.red;
}

@RPC
function SpawnCastle(viewID: NetworkViewID, position: Vector3, number: int) {
	var castle: Object = Instantiate(castlePrefab, position, transform.rotation);

	(castle as GameObject).GetComponent(NetworkView).viewID = viewID;

	if (!(castle as GameObject).GetComponent(NetworkView).isMine) {
		(castle as GameObject).name += "Remote";
		Debug.Log("Looking up color for player " +number);
		(castle  as GameObject).renderer.material.color = GetColor(number);
	}
	else {
		(castle as GameObject).renderer.material.color = (playerList[0] as CastlePlayer).color;
		Debug.Log("Instantiated own castle");
	}
}

// When connected to server, request to get local player initialized in the game
function OnConnectedToServer() {
	networkView.RPC("RequestPlayer", RPCMode.Server, playerName, playerColor.r, playerColor.g, playerColor.b);
}

// Process each player when he connects to the on the server. Add to player list.
function OnPlayerConnected (player : NetworkPlayer)
{
	Debug.Log ("Server: player " + player.ToString() + " connected");
	if (gameStarted) {
		Debug.Log("Rejecting new player as the game is already started");
		Network.CloseConnection(player, true);
	}
}

function OnPlayerDisconnected (player : NetworkPlayer)
{
	Network.RemoveRPCs(player, 0);
	Network.DestroyPlayerObjects(player);
}
  • Networking Scripts -> ThirdPersonNetworkInit.js(修正する)
function OnNetworkInstantiate (msg : NetworkMessageInfo) {
	// This is our own player
	if (networkView.isMine)
	{
		Camera.main.SendMessage("SetTarget", transform);
		(GetComponent("NetworkInterpolatedTransform") as NetworkInterpolatedTransform).enabled = false;
	}
	// This is just some remote controlled player
	else
	{
		name += "Remote";
		GetComponent(ThirdPersonController).enabled = false;
		GetComponent(ThirdPersonSimpleAnimation).enabled = false;
		(GetComponent("NetworkInterpolatedTransform") as NetworkInterpolatedTransform).enabled = true;
	}
}
  • Vehicles->SurfaceEffects->Spume->WaterSpume.js(Androidではバグるので修正)
-//Attach this script to an object to create a foam layer on top 
//of the attached trigger collider, which automatically creates foam whenever anything
//touches the trigger.

RequireComponent(BoxCollider);
RequireComponent(MeshFilter);
RequireComponent(MeshRenderer);

//maximal number of simultaneous foam particles
var maxParticles = 1024;
//maximal seconds of life for one particle
var maxLife = 16.0;	

private var index = 0;

//structure for each particle
class FoamParticle{
	var pos : Vector3;
	var velo : Vector3;
	var ext : Vector3;
	var intensity = 0.0;
	var startIntensity =0.0;
};
var foam : FoamParticle[];	
	
// Use this for initialization
function Start () {
	collider.isTrigger = true;

	//create particle array
	foam=new FoamParticle[maxParticles];
	for(var i:int =0;i<maxParticles;i++)
		foam[i]=new FoamParticle();
		
	//create particle mesh
	if(GetComponent(MeshFilter).mesh==null)
		GetComponent(MeshFilter).mesh=new Mesh();
}
	
//add new foam particles in the extents ext around pos, with the maximal life of intensity
function AddFoamParticles(pos : Vector3,ext: Vector3,intensity : float)
{
	//flatten coordinates to all be on the water surface
	pos-=transform.position;
	pos.y=0;
	ext.y=0;
	
	intensity=Mathf.Clamp01(intensity);

	//determine number of particles based on ext and intensity
	var sizeFactor = Mathf.Sqrt(ext.magnitude);
	if(sizeFactor<1)
		intensity*=sizeFactor;
	var fnum = intensity*sizeFactor*Time.deltaTime*50;
	var num:int;
	num = Mathf.Floor(fnum);
	num+=(fnum-num)>Random.value?1:0;
		
	//create particles
	while(num>0)
	{
		var rnd = Random.insideUnitSphere;
		rnd.y = 0;
		foam[index].pos=pos+Vector3.Scale(rnd,ext);
		foam[index].velo=rnd*intensity;
		foam[index].intensity=Random.value*intensity;
		foam[index].startIntensity=foam[index].intensity;
		foam[index].ext=Random.onUnitSphere*(1.0+1.5*Random.value)*sizeFactor;
		foam[index].ext.y=0;
		index=(index+1)%maxParticles;
		num--;
	}
}

	
function Update () {
	//count and update active particles
	var segmentCount = 0;
	for(var i=0;i<maxParticles;i++)
	{
		//decrease life
		foam[i].intensity-=Time.deltaTime/maxLife;
		if(foam[i].intensity>0)
		{
			//move particles
			foam[i].pos+=foam[i].velo*Time.deltaTime;
			//grow particles
			var size = foam[i].ext.magnitude;
			foam[i].ext*=1+(Time.deltaTime*0.5/size);
			//increase count of active particles
			segmentCount++;
		}
	}	
	//calculate mesh data for particles
	var vertices = new Vector3[segmentCount*4];
	var colors = new Color[segmentCount*4];
	var uvs = new Vector2[segmentCount*4];
	var triangles = new int[segmentCount*6];
	segmentCount = 0;
	for(i=0;i<maxParticles;i++)
		if(foam[i].intensity>0)
		{
			var ext1=foam[i].ext;
			var ext2=Vector3(ext1.z,0,-ext1.x);
			vertices[segmentCount*4+0]=foam[i].pos+ext1;
			vertices[segmentCount*4+1]=foam[i].pos+ext2;
			vertices[segmentCount*4+2]=foam[i].pos-ext1;
			vertices[segmentCount*4+3]=foam[i].pos-ext2;
			
			var intensity=foam[i].intensity;
			if(foam[i].startIntensity-intensity<0.03)
				intensity*=((foam[i].startIntensity-intensity)/0.03);
			colors[segmentCount*4+0]=0.5*Color(intensity,intensity,intensity,1);
			colors[segmentCount*4+1]=0.5*Color(intensity,intensity,intensity,1);
			colors[segmentCount*4+2]=0.5*Color(intensity,intensity,intensity,1);
			colors[segmentCount*4+3]=0.5*Color(intensity,intensity,intensity,1);

			uvs[segmentCount*4+0]=Vector2(0,0);
			uvs[segmentCount*4+1]=Vector2(1,0);
			uvs[segmentCount*4+2]=Vector2(1,1);
			uvs[segmentCount*4+3]=Vector2(0,1);
			
			triangles[segmentCount*6+0]=segmentCount*4+0;
			triangles[segmentCount*6+1]=segmentCount*4+1;
			triangles[segmentCount*6+2]=segmentCount*4+2;
			
			triangles[segmentCount*6+3]=segmentCount*4+2;
			triangles[segmentCount*6+4]=segmentCount*4+0;
			triangles[segmentCount*6+5]=segmentCount*4+3;
			segmentCount++;			
		}
		
	//update mesh
	var mesh = GetComponent(MeshFilter).mesh;
	mesh.Clear();
	mesh.vertices=vertices;
	mesh.triangles=triangles;
	mesh.colors=colors;
	mesh.uv=uvs;
}

//get the rigidbody attached to a component's gameobject or it's anchestors
function GetComponentRigidBody(cmp : GameObject) : Rigidbody {
	if (cmp.rigidbody!=null)
		return cmp.rigidbody;
	else if (cmp.transform.parent!= null)
		return (GetComponentRigidBody((cmp.transform.parent as GameObject)) as Rigidbody);
	else
		return null;
}

//if something fell into the water...
function OnTriggerStay(other : Collider) {
	var rb : Rigidbody = GetComponentRigidBody(other as GameObject);
	//...and it has a rigidbody...
	if (rb!=null) {
		//...create foam particles around the object, based on the velocity of the rigidbody
		var pos = other.transform.position;
		AddFoamParticles(pos, (other as Renderer).bounds.extents, rb.velocity.magnitude * 0.05);
	}
}

カスタムインスペクター Unity

http://www.unifycommunity.com/wiki/index.php?title=Notes
→ここを参考に使ってみた。
Hierarchy上のGameObjectたちのInspector上にメモ書きできる

  • Note.cs・・・参考HPにあるのC#スクリプトを記載し、好きな場所に配置。
  • NoteEditor.cs・・・同WebページのC#スクリプトを記載し、Assets->Editorに配置(適当に配置するとエラー出る)

Note.csはメモ書きされたいGameObjectへ適用する


AddComponentMenu Unity

カットシステム本のP107

説明がわかりにくいが、よく使う自分の書いたスクリプトを、
”Unityの「Component」メニュー上に表示させたい”ときに使います。
クラス名の”上”に記載します!

今回は
[AddComponentMenu("Usefultool/AutoTilling")]
このように記載した
こうするとUnityの「Component」メニューの「Usefultool->AutoTilling」へ追加されます。スラッシュでメニューの親子関係を成立してます。

今度からこれを選択するだけで適用されます。

記載する「AutoTilling」は今回は便宜上一所ですが、スクリプトの名前と”違ってもOK”です。

using UnityEngine;
using System.Collections;

[AddComponentMenu("Usefultool/AutoTilling")]
public class AutoTiling : MonoBehaviour {
	public float ScaleToTiles = 0.667F;
	private float scaleX;
	private float scaleY;
	
	// Use this for initialization
	void Start () {
		scaleX = transform.lossyScale.x * ScaleToTiles;
        scaleY = transform.lossyScale.y * ScaleToTiles;
		renderer.material.mainTextureScale = new Vector2(scaleX, scaleY);
	}
}

このコードを任意のフォルダに「AutoTiling.cs」とかで使う