写在前面————
哈哈, 开始做关于脚本热更新的第一个例子了,鉴于本人还是个没毕业的学生,所以不足之处还希望大家包涵~~~~~~
注意:
1.本文章是用Space Shooter(Unity3d官方的小游戏)做的例子
2.Space Shooter源码和Scorpio-CSharp 的下载链接我会放在文章最后面
3.如有任何疑问,请加群245199668
正文
1.新建项目,把下载好的Space Shooter导入Unity中, 打开Done_Main场景,
2.下载Scorpio-CSharp,你会得到几个文件:Scorpio,ScorpioDemo,ScorpioExec,ScorpioMaker,ScorpioTest。把Scorpio整个文件夹复制到 Unity3d里(如果还不知道Scorpio-CSharp是什么或者是热更新是什么请点这里和这里)。
3.做一个底层。新建2个脚本,ActionManager和ScriptComponent。ActionManager主要是提供一些方法方便调用,而ScriptComponent是为了过渡C# 脚本,或者也可以说是从C#到Scorpio-CSharp的一个接口。而运行一个Scorpio-CSharp脚本需要压入程序集(PushAssembly)和导入类(import_type)
Singleton:方便全局调用
1 //单例模式 2 public abstract class Singleton<T> where T : class, new() 3 { 4 static T msInstance = null; 5 public static T Instance { get { return msInstance ?? (msInstance = new T()); } } 6 }
ActionManager:
1.它可以随时随地运行一个热更新脚本,但是该脚本挂载的ScriptComponent是摄像机的,也就是说this.gameObject获得的是摄像机的,OnTriggerEnter之类的函数也是摄像机。
2.它也可以通过RunScript运行指定物体上的热更新脚本
注意,所有的热更新脚本已通过ActionManager把常用类导入进去了且都放在了Resources文件夹下,通过Resources.Load读取。
1 #define DEBUG 2 3 using UnityEngine; 4 using System .Collections; 5 using Scorpio; 6 7 public class ActionScriptManager : Singleton<ActionScriptManager> 8 { 9 public ActionScriptManager ( ) 10 { 11 script = new Script ( ); 12 ScriptComponent sc = Camera.main.gameObject.AddComponent<ScriptComponent>(); 13 sc .Init ( script ); 14 try 15 { 16 SetScript ( script ); 17 18 importType = Resources .Load ( "ImportType" ) as TextAsset; 19 } 20 catch ( System .Exception e ) 21 { 22 Debug .LogError ( "StackInfo : " + script .GetStackInfo ( ) ); 23 Debug .LogError ( e .ToString ( ) ); 24 } 25 #if DEBUG 26 stopwatch = new System .Diagnostics .Stopwatch ( ); 27 #endif 28 } 29 30 public void SetScript(Script scriptTmp) 31 { 32 if(scriptTmp != null) 33 { 34 scriptTmp .LoadLibrary ( ); 35 scriptTmp .PushAssembly ( GetType ( ) .Assembly ); 36 //system 37 scriptTmp .PushAssembly ( typeof ( byte ) .Assembly ); 38 //UnityEngine 39 scriptTmp .PushAssembly ( typeof ( Object ) .Assembly ); 40 } 41 } 42 43 public ScriptObject RunScript ( string scoName ) 44 { 45 return RunScript ( script , scoName ); 46 } 47 48 public ScriptObject RunScript ( Script scriptTmp, string scoName ) 49 { 50 ScriptObject ret; 51 52 if(scriptTmp == null) 53 { 54 Debug .LogError ( scriptTmp + " is null" ); 55 return null; 56 } 57 58 textAsset = Resources .Load ( scoName ) as TextAsset; 59 if(textAsset == null) 60 { 61 Debug .LogError ( scoName + " is null" ); 62 return null; 63 } 64 65 targetText = importType .text; 66 targetText += textAsset .text; 67 68 #if DEBUG 69 stopwatch .Start ( ); 70 #endif 71 72 ret = scriptTmp .LoadString ( targetText ); 73 74 #if DEBUG 75 stopwatch .Stop ( ); 76 Debug .Log ( scoName + " 运行时间: " + stopwatch .ElapsedMilliseconds .ToString ( ) + "ms" ); 77 #endif 78 79 return ret; 80 } 81 82 //脚本源文件 83 TextAsset textAsset; 84 //注册 85 TextAsset importType; 86 //目标字符串 87 string targetText; 88 //热更新脚本 89 Script script; 90 #if DEBUG 91 //计时器 92 System.Diagnostics.Stopwatch stopwatch; 93 #endif 94 }
ScriptComponent:
1 using UnityEngine; 2 using Scorpio; 3 4 public class ScriptComponent : MonoBehaviour 5 { 6 public Script script; 7 public ScriptTable scriptTable; 8 9 public void Init(Script source) 10 { 11 script = source; 12 scriptTable = source .GetGlobalTable ( ); 13 scriptTable .SetValue ( "transform" , script .CreateUserdata ( this .transform ) ); 14 scriptTable .SetValue ( "gameObject" , script .CreateUserdata ( this .gameObject ) ); 15 OnStart ( ); 16 } 17 18 void OnStart() 19 { 20 Call ( "Awake" ); 21 } 22 23 void Start() 24 { 25 Call ( "Start" ); 26 } 27 28 void Update() 29 { 30 Call ( "Update" ); 31 } 32 33 void FixedUpdate() 34 { 35 Call ( "FixedUpdate" ); 36 } 37 38 void OnGUI ( ) 39 { 40 Call ( "OnGUI" ); 41 } 42 43 void OnCollisionEnter ( Collision otherCollider ) 44 { 45 Call ( "OnCollisionEnter" , otherCollider ); 46 } 47 48 void OnCollisionStay ( Collision otherCollider ) 49 { 50 Call ( "OnCollisionStay" , otherCollider ); 51 } 52 53 void OnCollisionExit ( Collision otherCollider ) 54 { 55 Call ( "OnCollisionExit" , otherCollider ); 56 } 57 58 void OnTriggerEnter ( Collider otherCollider ) 59 { 60 Call ( "OnTriggerEnter" , otherCollider ); 61 } 62 63 void OnTriggerStay ( Collider otherCollider ) 64 { 65 Call ( "OnTriggerStay" , otherCollider ); 66 } 67 68 void OnTriggerExit ( Collider otherCollider ) 69 { 70 Call ( "OnTriggerStay" , otherCollider ); 71 } 72 73 public void Call(string func, params object[] args) 74 { 75 if(scriptTable != null && scriptTable.HasValue(func)) 76 { 77 ( ( ScriptFunction ) scriptTable .GetValue ( func ) ) .call ( args ); 78 } 79 } 80 }
ImportType:
1 //UnityEngine 2 GameObject = import_type("UnityEngine.GameObject") 3 Object = import_type("UnityEngine.Object") 4 Mathf = import_type("UnityEngine.Mathf") 5 PrimitiveType = import_type("UnityEngine.PrimitiveType") 6 Resources = import_type("UnityEngine.Resources") 7 Debug = import_type("UnityEngine.Debug") 8 Transform = import_type("UnityEngine.Transform") 9 Vector2 = import_type("UnityEngine.Vector2") 10 Vector3 = import_type("UnityEngine.Vector3") 11 Vector4 = import_type("UnityEngine.Vector4") 12 Application = import_type("UnityEngine.Application") 13 Time = import_type("UnityEngine.Time") 14 Camera = import_type("UnityEngine.Camera") 15 LineRenderer = import_type("UnityEngine.LineRenderer") 16 Screen = import_type("UnityEngine.Screen") 17 SleepTimeout = import_type("UnityEngine.SleepTimeout") 18 Input = import_type("UnityEngine.Input") 19 SpriteRenderer = import_type("UnityEngine.SpriteRenderer") 20 KeyCode = import_type("UnityEngine.KeyCode") 21 Color = import_type("UnityEngine.Color") 22 Random = import_type("UnityEngine.Random") 23 Quaternion = import_type("UnityEngine.Quaternion") 24 Animator = import_type ("UnityEngine.Animator") 25 Renderer = import_type ("UnityEngine.Renderer") 26 Collision = import_type("UnityEngine.Collision") 27 AudioSource = import_type("UnityEngine.AudioSource") 28 Rigidbody = import_type("UnityEngine.Rigidbody") 29 //System 30 byte = import_type("System.byte") 31 int = import_type("System.Int32") 32 float = import_type("System.Single") 33 double = import_type("System.Double") 34 bool = import_type("System.Boolean") 35 string = import_type("System.string") 36 37 List = import_type("System.Collections.Generic.List`1") 38 ListInt = generic_type(List, int) 39 ListGameObject = generic_type(List, GameObject) 40 41 //Owner class 42 ActionScriptManager = import_type("ActionScriptManager") 43 ScriptComponent = import_type("ScriptComponent") 44 Script = import_type("Scorpio.Script")
4.修改Done_PlayerController成热更新脚本,在Done_GameController脚本的Start函数中加入下面代码,修改类Done_Boundary,加入构造函数。
1 ScriptComponent sc = GameObject.FindGameObjectWithTag("Player").AddComponent<ScriptComponent>(); 2 Script s = new Script(); 3 sc .Init ( s ); 4 ActionScriptManager .Instance .SetScript ( s ); 5 ActionScriptManager .Instance .RunScript ( s , "PlayerController" );
Done_PlayerController.txt:
1 var speed = 10; 2 var tilt = 5; 3 var boundary = Done_Boundary(-6, 6, -4, 8); 4 var shot; 5 var shotSpawn; 6 var fireRate = 0.25; 7 var nextFire = 0; 8 function Update () 9 { 10 shot = Resources.Load("Done_Bolt"); 11 shotSpawn = transform.FindChild("Shot Spawn"); 12 if (Input.GetButton("Fire1") && Time.time > nextFire) 13 { 14 nextFire = Time.time + fireRate; 15 GameObject.Instantiate(shot, shotSpawn.position, shotSpawn.rotation); 16 gameObject.GetComponent(typeof(AudioSource)).Play (); 17 } 18 } 19 function FixedUpdate() 20 { 21 var moveHorizontal = Input.GetAxis ("Horizontal"); 22 var moveVertical = Input.GetAxis ("Vertical"); 23 var movement = Vector3 (moveHorizontal, 0.0, moveVertical); 24 gameObject.GetComponent(typeof (Rigidbody)).velocity = Vector3.op_Multiply(movement, speed); 25 gameObject.GetComponent(typeof(Rigidbody)).position = Vector3 26 ( 27 Mathf.Clamp (gameObject.GetComponent(typeof(Rigidbody)).position.x, boundary.xMin, boundary.xMax), 28 0.0, 29 Mathf.Clamp (gameObject.GetComponent(typeof(Rigidbody)).position.z, boundary.zMin, boundary.zMax) 30 ); 31 gameObject.GetComponent(typeof (Rigidbody)).rotation 32 = Quaternion.Euler(0, 0, gameObject.GetComponent(typeof (Rigidbody)).velocity.x * -tilt); 33 }
今天就先到这里了。。。
————————————————————————————————华丽的分割线———————————————————————————————————————————————
注意:
1.假如在热更新脚本里使用系统或者unity的类,需先把程序集压入到Script里面(俩个类在同意程序集只用压一次), 然后再热更新脚本前头声明该类来自那个程序集(每个类都必须声明)。
如果说是自己写的类直接import_type和scriptTmp .PushAssembly ( GetType ( ) .Assembly );
例如(GameObject和Object在同一程序集里):
scriptTmp .PushAssembly ( typeof ( Object ) .Assembly );
GameObject = import_type("UnityEngine.GameObject")
Object = import_type("UnityEngine.Object")
2.Scorpio-CSharp脚本类似js脚本,声明全部用var
3.默认数字为double和long,0为long,0.0为double
4.没有new关键字,类初始化可以直接GameObject(),不需要new
5.可以GetComponent(typeof(AudioSource)),不能GetComponent<AudioSource>()
6.没有重写+=,-=,/=,*=操作符,可以通过Vector3.op_Multiply(乘),Vector3.op_Addition(加)实现
7.热更新脚本没有继承mono使用创建和摧毁前得加上GameObject(GameObject.Destroy)
链接:
Space Shooter: http://pan.baidu.com/s/1geuEEkF
Scorpio-CSharp: https://github.com/qingfeng346/Scorpio-CSharp#showcase
更多例子请看ScorpioDemo。