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版でないとできないっぽい
- MasterServerをDL(ソースなのでコンパイル必須)
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; } }
-//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」とかで使う