首页 >> 大全

Unity热更新方案C#Like(廿三)

2023-12-09 大全 26 作者:考证青年

C#Like是Unity的热更方案,使用纯C#语言写出可以热更新的代码,就像可以在所有平台使用DLL(动态链接库)文件一样.遵从KISS设计原则,让用户轻松构建或升级成Unity的热更新项目.

简介

本篇主要介绍如何一步一步地详细地把Unity官方免费例子Tanks! 转成可热更新项目.

搭建的详细步骤

我们首先选取一个最少代码的Unity出品的免费的例子"Tanks! "作为示范,即官方Demo里的""项目是如何制作的,下面是详细步骤:

导入相关的免费资源: 移除默认导出C#Like内置资源的:你也可以直接删掉它 修改产品名称: 修改脚本: 根据绑定不同需求,在Awake或Start里绑定预制体里的数据 (int/bool//float//Color///////)注意生命周期顺序 : Awake -> -> Start.如果绑定其他对象的数据,尽量放在Start函数而非Awake,因为在Awake的时候,其他对象尚未执行初始化C#Like免费版不支持协程,需要用来代替,更多免费版和完整版的区别详见:主页WebGL下,不知为何会报错无法识别,最终使用'.'来下载音频文件后绑上的.奇怪,不知为何' '又没问题然后我们逐一地把修改目录'\\-\'里的所有cs脚本文件: 我们复制'/-/'整个目录为'/-/'目录, 里面包含8个脚本文件,里面的8个cs文件是待修改成热更新脚本, 和原代码改动部分会标注 '// : '修改'/-///.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using UnityEngine;  namespace CSharpLike//RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/FixedUpdate',  /// 我们使用 'HotUpdateBehaviourFixedUpdate' 来绑定预制体  /// 预制体里设置'Floats': "m_DampTime"/"m_ScreenEdgeBuffer"/"m_MinSize".  ///   public class CameraControl : LikeBehaviour //RongRong : 基类'MonoBehaviour'改为'LikeBehaviour'  {  public float m_DampTime = 0.2f;                 // Approximate time for the camera to refocus.  public float m_ScreenEdgeBuffer = 4f;           // Space between the top/bottom most target and the screen edge.  public float m_MinSize = 6.5f;                  // The smallest orthographic size the camera can be.  [HideInInspector] public Transform[] m_Targets; // All the targets the camera needs to encompass.  private Camera m_Camera;                        // Used for referencing the camera.  private float m_ZoomSpeed;                      // Reference speed for the smooth damping of the orthographic size.  private Vector3 m_MoveVelocity;                 // Reference velocity for the smooth damping of the position.  private Vector3 m_DesiredPosition;              // The position the camera is moving towards.  private void Awake ()  {  m_Camera = gameObject.GetComponentInChildren ();// RongRong : 加前缀"gameObject."  // RongRong : 绑定预制体里的值.  m_DampTime = GetFloat("m_DampTime", 0.2f);  m_ScreenEdgeBuffer = GetFloat("m_ScreenEdgeBuffer", 4f);  m_MinSize = GetFloat("m_MinSize", 6.5f);  }  private void FixedUpdate ()  {  // Move the camera towards a desired position.  Move ();  // Change the size of the camera based.  Zoom ();  }  private void Move ()  {  // Find the average position of the targets.  FindAveragePosition ();  // Smoothly transition to that position.  transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);  }  private void FindAveragePosition ()  {  Vector3 averagePos = new Vector3 ();  int numTargets = 0;  // Go through all the targets and add their positions together.  for (int i = 0; i < m_Targets.Length; i++)  {  // If the target isn't active, go on to the next one.  if (!m_Targets[i].gameObject.activeSelf)  continue;  // Add to the average and increment the number of targets in the average.  averagePos += m_Targets[i].position;  numTargets++;  }  // If there are targets divide the sum of the positions by the number of them to find the average.  if (numTargets > 0)  averagePos /= numTargets;  // Keep the same y value.  averagePos.y = transform.position.y;  // The desired position is the average position;  m_DesiredPosition = averagePos;  }  private void Zoom ()  {  // Find the required size based on the desired position and smoothly transition to that size.  float requiredSize = FindRequiredSize();  m_Camera.orthographicSize = Mathf.SmoothDamp (m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime); // RongRong : C#LikeFree not support 'ref'  }  private float FindRequiredSize ()  {  // Find the position the camera rig is moving towards in its local space.  Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);  // Start the camera's size calculation at zero.  float size = 0f;  // Go through all the targets...  for (int i = 0; i < m_Targets.Length; i++)  {  // ... and if they aren't active continue on to the next target.  if (!m_Targets[i].gameObject.activeSelf)  continue;  // Otherwise, find the position of the target in the camera's local space.  Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);  // Find the position of the target from the desired position of the camera's local space.  Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;  // Choose the largest out of the current size and the distance of the tank 'up' or 'down' from the camera.  size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));  // Choose the largest out of the current size and the calculated size based on the tank being to the left or right of the camera.  size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);  }  // Add the edge buffer to the size.  size += m_ScreenEdgeBuffer;  // Make sure the camera's size isn't below the minimum.  size = Mathf.Max (size, m_MinSize);  return size;  }  public void SetStartPositionAndSize ()  {  // Find the desired position.  FindAveragePosition ();  // Set the camera's position to the desired position without damping.  transform.position = m_DesiredPosition;  // Find and set the required size of the camera.  m_Camera.orthographicSize = FindRequiredSize ();  }  }  
} //C#Like免费版
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/FixedUpdate',  /// 我们使用 'HotUpdateBehaviourFixedUpdate' 来绑定预制体  /// 预制体里设置'Floats': "m_DampTime"/"m_ScreenEdgeBuffer"/"m_MinSize".  ///   public class CameraControl : LikeBehaviour //RongRong : 基类'MonoBehaviour'改为'LikeBehaviour'  {  public float m_DampTime = 0.2f;                 // Approximate time for the camera to refocus.  public float m_ScreenEdgeBuffer = 4f;           // Space between the top/bottom most target and the screen edge.  public float m_MinSize = 6.5f;                  // The smallest orthographic size the camera can be.  [HideInInspector] public Transform[] m_Targets; // All the targets the camera needs to encompass.  private Camera m_Camera;                        // Used for referencing the camera.  private float m_ZoomSpeed;                      // Reference speed for the smooth damping of the orthographic size.  private Vector3 m_MoveVelocity;                 // Reference velocity for the smooth damping of the position.  private Vector3 m_DesiredPosition;              // The position the camera is moving towards.  private void Awake ()  {  m_Camera = gameObject.GetComponentInChildren ();// RongRong : 加前缀 "gameObject."  // RongRong : 绑定预制体里的值.  m_DampTime = GetFloat("m_DampTime", 0.2f);  m_ScreenEdgeBuffer = GetFloat("m_ScreenEdgeBuffer", 4f);  m_MinSize = GetFloat("m_MinSize", 6.5f);  }  private void FixedUpdate ()  {  // Move the camera towards a desired position.  Move ();  // Change the size of the camera based.  Zoom ();  }  private void Move ()  {  // Find the average position of the targets.  FindAveragePosition ();  // Smoothly transition to that position.  // RongRong : 免费版不支持 'ref',这里只能借助非热更代码来实现  // transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);  SampleHowToUseModifier.currentVelocity = m_MoveVelocity;  transform.localPosition = SampleHowToUseModifier.SmoothDamp(transform.position, m_DesiredPosition, m_DampTime);  m_MoveVelocity = SampleHowToUseModifier.currentVelocity;  }  private void FindAveragePosition ()  {  Vector3 averagePos = new Vector3 ();  int numTargets = 0;  // Go through all the targets and add their positions together.  for (int i = 0; i < m_Targets.Length; i++)  {  // If the target isn't active, go on to the next one.  if (!m_Targets[i].gameObject.activeSelf)  continue;  // Add to the average and increment the number of targets in the average.  averagePos += m_Targets[i].position;  numTargets++;  }  // If there are targets divide the sum of the positions by the number of them to find the average.  if (numTargets > 0)  averagePos /= numTargets;  // Keep the same y value.  averagePos.y = transform.position.y;  // The desired position is the average position;  m_DesiredPosition = averagePos;  }  private void Zoom ()  {  // Find the required size based on the desired position and smoothly transition to that size.  float requiredSize = FindRequiredSize();  // RongRong : 免费版不支持 'ref',这里只能借助非热更代码来实现  // m_Camera.orthographicSize = Mathf.SmoothDamp (m_Camera.orthographicSize, requiredSize, ref m_ZoomSpeed, m_DampTime);  SampleHowToUseModifier.currentVelocityFloat = m_ZoomSpeed;  m_Camera.orthographicSize = SampleHowToUseModifier.SmoothDamp(m_Camera.orthographicSize, requiredSize, m_DampTime);  m_ZoomSpeed = SampleHowToUseModifier.currentVelocityFloat;  }  private float FindRequiredSize ()  {  // Find the position the camera rig is moving towards in its local space.  Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);  // Start the camera's size calculation at zero.  float size = 0f;  // Go through all the targets...  for (int i = 0; i < m_Targets.Length; i++)  {  // ... and if they aren't active continue on to the next target.  if (!m_Targets[i].gameObject.activeSelf)  continue;  // Otherwise, find the position of the target in the camera's local space.  Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);  // Find the position of the target from the desired position of the camera's local space.  Vector3 desiredPosToTarget = targetLocalPos - desiredLocalPos;  // Choose the largest out of the current size and the distance of the tank 'up' or 'down' from the camera.  size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.y));  // Choose the largest out of the current size and the calculated size based on the tank being to the left or right of the camera.  size = Mathf.Max(size, Mathf.Abs(desiredPosToTarget.x) / m_Camera.aspect);  }  // Add the edge buffer to the size.  size += m_ScreenEdgeBuffer;  // Make sure the camera's size isn't below the minimum.  size = Mathf.Max (size, m_MinSize);  return size;  }  public void SetStartPositionAndSize ()  {  // Find the desired position.  FindAveragePosition ();  // Set the camera's position to the desired position without damping.  transform.position = m_DesiredPosition;  // Find and set the required size of the camera.  m_Camera.orthographicSize = FindRequiredSize ();  }  }  
}

修改'/-///.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using System.Collections;  
using UnityEngine;  
using UnityEngine.SceneManagement;  
using UnityEngine.UI;  namespace CSharpLike//RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/Awake',  /// 我们使用 'HotUpdateBehaviour' 来绑定预制体.  /// 预制体设置 'Integers' : "m_NumRoundsToWin"  /// 预制体设置 'Floats' : "m_StartDelay"/"m_EndDelay".  /// 预制体设置 'Game Objects' : "m_CameraControl"/"m_MessageText"/"m_Tanks0"/"m_Tanks1".  /// 预制体设置 'Colors' : "m_Tanks0"/"m_Tanks1".  ///   public class GameManager : LikeBehaviour //RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_NumRoundsToWin = 5;            // The number of rounds a single player has to win to win the game.  public float m_StartDelay = 3f;             // The delay between the start of RoundStarting and RoundPlaying phases.  public float m_EndDelay = 3f;               // The delay between the end of RoundPlaying and RoundEnding phases.  public CameraControl m_CameraControl;       // Reference to the CameraControl script for control during different phases.  public Text m_MessageText;                  // Reference to the overlay Text to display winning text, etc.  public GameObject m_TankPrefab;             // Reference to the prefab the players will control.  public TankManager[] m_Tanks;               // A collection of managers for enabling and disabling different aspects of the tanks.  private int m_RoundNumber;                  // Which round the game is currently on.  private WaitForSeconds m_StartWait;         // Used to have a delay whilst the round starts.  private WaitForSeconds m_EndWait;           // Used to have a delay whilst the round or game ends.  private TankManager m_RoundWinner;          // Reference to the winner of the current round.  Used to make an announcement of who won.  private TankManager m_GameWinner;           // Reference to the winner of the game.  Used to make an announcement of who won.  const float k_MaxDepenetrationVelocity = float.PositiveInfinity;  // RongRong : 绑定必须放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体里的数值.  m_NumRoundsToWin = GetInt("m_NumRoundsToWin", 5);  m_StartDelay = GetFloat("m_StartDelay", 3f);  m_EndDelay = GetFloat("m_EndDelay", 3f);  m_MessageText = GetComponent("m_MessageText");  m_TankPrefab = GetGameObject("m_TankPrefab");  m_Tanks = new TankManager[2];  for (int i = 0; i < 2; i++)  {  TankManager tankManager = new TankManager();  tankManager.m_SpawnPoint = GetGameObject("m_Tanks"+i).transform;  tankManager.m_PlayerColor = GetColor("m_Tanks"+i);  m_Tanks[i] = tankManager;  }  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  
#if UNITY_WEBGL  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/BackgroundMusic.wav", AudioType.WAV, (AudioClip audioClip) =>  {  if (audioClip != null)  {  gameObject.GetComponent().clip = audioClip;  gameObject.GetComponent().Play();  }  });  
#endif  }  private void Start()  {  m_CameraControl = HotUpdateBehaviour.GetComponentByType(GetGameObject("m_CameraControl"), typeof(CameraControl)) as CameraControl;  // This line fixes a change to the physics engine.  Physics.defaultMaxDepenetrationVelocity = k_MaxDepenetrationVelocity;  // Create the delays so they only have to be made once.  m_StartWait = new WaitForSeconds (m_StartDelay);  m_EndWait = new WaitForSeconds (m_EndDelay);  SpawnAllTanks();  SetCameraTargets();  // Once the tanks have been created and the camera is using them as targets, start the game.  // RongRong : 'StartCoroutine(GameLoop ())' 改为 'StartCoroutine("GameLoop");'  StartCoroutine("GameLoop");  }  private void SpawnAllTanks()  {  // For all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... create them, set their player number and references needed for control.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  m_Tanks[i].m_Instance =  GameObject.Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;  m_Tanks[i].m_PlayerNumber = i + 1;  m_Tanks[i].Setup();  }  }  private void SetCameraTargets()  {  // Create a collection of transforms the same size as the number of tanks.  Transform[] targets = new Transform[m_Tanks.Length];  // For each of these transforms...  for (int i = 0; i < targets.Length; i++)  {  // ... set it to the appropriate tank transform.  targets[i] = m_Tanks[i].m_Instance.transform;  }  // These are the targets the camera should follow.  m_CameraControl.m_Targets = targets;  }  // This is called from start and will run each phase of the game one after another.  private IEnumerator GameLoop ()  {  // Start off by running the 'RoundStarting' coroutine but don't return until it's finished.  // RongRong : 'StartCoroutine (RoundStarting ())' 改为 'StartCoroutine("RoundStarting")'  yield return StartCoroutine("RoundStarting");  // Once the 'RoundStarting' coroutine is finished, run the 'RoundPlaying' coroutine but don't return until it's finished.  // RongRong : 'StartCoroutine (RoundPlaying())' 改为 'StartCoroutine("RoundPlaying")'  yield return StartCoroutine("RoundPlaying");  // Once execution has returned here, run the 'RoundEnding' coroutine, again don't return until it's finished.  // RongRong : 'StartCoroutine (RoundEnding())' 改为 'StartCoroutine("RoundEnding")'  yield return StartCoroutine("RoundEnding");  // This code is not run until 'RoundEnding' has finished.  At which point, check if a game winner has been found.  if (m_GameWinner != null)  {  // If there is a game winner, restart the level.  SceneManager.LoadScene ("_Complete-Game_HotUpdate");  }  else  {  // If there isn't a winner yet, restart this coroutine so the loop continues.  // Note that this coroutine doesn't yield.  This means that the current version of the GameLoop will end.  // RongRong : 'StartCoroutine(GameLoop ())' 改为 'StartCoroutine("GameLoop");'  StartCoroutine("GameLoop");  }  }  private IEnumerator RoundStarting ()  {  // As soon as the round starts reset the tanks and make sure they can't move.  ResetAllTanks ();  DisableTankControl ();  // Snap the camera's zoom and position to something appropriate for the reset tanks.  m_CameraControl.SetStartPositionAndSize();  // Increment the round number and display text showing the players what round it is.  m_RoundNumber++;  m_MessageText.text = "ROUND " + m_RoundNumber;  // Wait for the specified length of time until yielding control back to the game loop.  yield return m_StartWait;  }  private IEnumerator RoundPlaying ()  {  // As soon as the round begins playing let the players control the tanks.  EnableTankControl ();  // Clear the text from the screen.  m_MessageText.text = string.Empty;  // While there is not one tank left...  while (!OneTankLeft())  {  // ... return on the next frame.  yield return null;  }  }  private IEnumerator RoundEnding ()  {  // Stop tanks from moving.  DisableTankControl ();  // Clear the winner from the previous round.  m_RoundWinner = null;  // See if there is a winner now the round is over.  m_RoundWinner = GetRoundWinner ();  // If there is a winner, increment their score.  if (m_RoundWinner != null)  m_RoundWinner.m_Wins++;  // Now the winner's score has been incremented, see if someone has one the game.  m_GameWinner = GetGameWinner ();  // Get a message based on the scores and whether or not there is a game winner and display it.  string message = EndMessage ();  m_MessageText.text = message;  // Wait for the specified length of time until yielding control back to the game loop.  yield return m_EndWait;  }  // This is used to check if there is one or fewer tanks remaining and thus the round should end.  private bool OneTankLeft()  {  // Start the count of tanks left at zero.  int numTanksLeft = 0;  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if they are active, increment the counter.  if (m_Tanks[i].m_Instance.activeSelf)  numTanksLeft++;  }  // If there are one or fewer tanks remaining return true, otherwise return false.  return numTanksLeft <= 1;  }  // This function is to find out if there is a winner of the round.  // This function is called with the assumption that 1 or fewer tanks are currently active.  private TankManager GetRoundWinner()  {  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if one of them is active, it is the winner so return it.  if (m_Tanks[i].m_Instance.activeSelf)  return m_Tanks[i];  }  // If none of the tanks are active it is a draw so return null.  return null;  }  // This function is to find out if there is a winner of the game.  private TankManager GetGameWinner()  {  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if one of them has enough rounds to win the game, return it.  if (m_Tanks[i].m_Wins == m_NumRoundsToWin)  return m_Tanks[i];  }  // If no tanks have enough rounds to win, return null.  return null;  }  // Returns a string message to display at the end of each round.  private string EndMessage()  {  // By default when a round ends there are no winners so the default end message is a draw.  string message = "DRAW!";  // If there is a winner then change the message to reflect that.  if (m_RoundWinner != null)  message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";  // Add some line breaks after the initial message.  message += "\n\n\n\n";  // Go through all the tanks and add each of their scores to the message.  for (int i = 0; i < m_Tanks.Length; i++)  {  message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";  }  // If there is a game winner, change the entire message to reflect that.  if (m_GameWinner != null)  message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";  return message;  }  // This function is used to turn all the tanks back on and reset their positions and properties.  private void ResetAllTanks()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].Reset();  }  }  private void EnableTankControl()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].EnableControl();  }  }  private void DisableTankControl()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].DisableControl();  }  }  }  
}//C#Like免费版
using System.Collections;  
using UnityEngine;  
using UnityEngine.SceneManagement;  
using UnityEngine.UI;  namespace CSharpLike//RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含 'Start/Update', 我们使用 'Update' 代替协程(因为免费版无法使用协程),  /// 我们使用 'HotUpdateBehaviourUpdate' 绑定预制体.  /// 预制体设置 'Integers' : "m_NumRoundsToWin".  /// 预制体设置 'Floats' : "m_StartDelay"/"m_EndDelay".  /// 预制体设置 'Game Objects' : "m_CameraControl"/"m_MessageText"/"m_Tanks0"/"m_Tanks1".  /// 预制体设置 'Colors' : "m_Tanks0"/"m_Tanks1".  ///   public class GameManager : LikeBehaviour //RongRong : 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_NumRoundsToWin = 5;            // The number of rounds a single player has to win to win the game.  public float m_StartDelay = 3f;             // The delay between the start of RoundStarting and RoundPlaying phases.  public float m_EndDelay = 3f;               // The delay between the end of RoundPlaying and RoundEnding phases.  public CameraControl m_CameraControl;       // Reference to the CameraControl script for control during different phases.  public Text m_MessageText;                  // Reference to the overlay Text to display winning text, etc.  public GameObject m_TankPrefab;             // Reference to the prefab the players will control.  public TankManager[] m_Tanks;               // A collection of managers for enabling and disabling different aspects of the tanks.  private int m_RoundNumber;                  // Which round the game is currently on.  // RongRong : 免费版不支持协程  //private WaitForSeconds m_StartWait;         // Used to have a delay whilst the round starts.  //private WaitForSeconds m_EndWait;           // Used to have a delay whilst the round or game ends.  private TankManager m_RoundWinner;          // Reference to the winner of the current round.  Used to make an announcement of who won.  private TankManager m_GameWinner;           // Reference to the winner of the game.  Used to make an announcement of who won.  const float k_MaxDepenetrationVelocity = float.PositiveInfinity;  // RongRong :  绑定必须放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体里的值.  m_NumRoundsToWin = GetInt("m_NumRoundsToWin", 5);  m_StartDelay = GetFloat("m_StartDelay", 3f);  m_EndDelay = GetFloat("m_EndDelay", 3f);  m_MessageText = GetComponent("m_MessageText");  m_TankPrefab = GetGameObject("m_TankPrefab");  m_Tanks = new TankManager[2];  for (int i = 0; i < 2; i++)  {  TankManager tankManager = new TankManager();  tankManager.m_SpawnPoint = GetGameObject("m_Tanks"+i).transform;  tankManager.m_PlayerColor = GetColor("m_Tanks"+i);  m_Tanks[i] = tankManager;  }  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  if (Application.platform == RuntimePlatform.WebGLPlayer)  {  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/BackgroundMusic.wav", AudioType.WAV, (AudioClip audioClip) =>  {  if (audioClip != null)  {  gameObject.GetComponent().clip = audioClip;  gameObject.GetComponent().Play();  }  });  }  }  private void Start()  {  m_CameraControl = HotUpdateBehaviour.GetComponentByType(GetGameObject("m_CameraControl"), typeof(CameraControl)) as CameraControl;  // This line fixes a change to the physics engine.  Physics.defaultMaxDepenetrationVelocity = k_MaxDepenetrationVelocity;  // Create the delays so they only have to be made once.  // RongRong : 免费版不支持协程  //m_StartWait = new WaitForSeconds (m_StartDelay);  //m_EndWait = new WaitForSeconds (m_EndDelay);  SpawnAllTanks();  SetCameraTargets();  // Once the tanks have been created and the camera is using them as targets, start the game.  mState = 1;  }  ///   /// RongRong : 我们使用7个状态代替协程  /// 0-未开始;  /// 1-回合开始;  /// 2-回合开始等待超时;  /// 3-回合进入;  /// 4-回合循环中;  /// 5-回合结束;  /// 6-回合结束等待超时;  ///   int mState = 0;  ///   /// RongRong : 我们使用累计时间代替'm_StartWait'和'm_EndWait'  ///   float mDeltaTime = 0f;  ///   /// RongRong : 我们使用 Update 代替协程  ///   /// 距离上一次Update的增量时间  void Update(float deltaTime)  {  if (mState == 1)//回合开始 : 刚进入回合开始,做初始化工作  {  RoundStarting();  mDeltaTime = 0f;  mState = 2;  }  else if (mState == 2)//回合开始等待超时: 等待超时进入下一个状态  {  mDeltaTime += deltaTime;  if (mDeltaTime >= m_StartDelay)//超时进入下一状态  {  mDeltaTime = 0f;  mState = 3;  }  }  else if (mState == 3)//回合进入: 刚进入回合,做初始化工作  {  // As soon as the round begins playing let the players control the tanks.  EnableTankControl();  // Clear the text from the screen.  m_MessageText.text = string.Empty;  mState = 4;  }  else if (mState == 4)//回合循环中: 主循环, 检查回合是否结束.  {  if (OneTankLeft())//Once just have one tank left, change to next state  mState = 5;  }  else if (mState == 5)//回合结束 : 计算结果  {  RoundEnding();  mState = 6;  }  else if (mState == 6)//回合结束等待超时: 等待超时进入下一状态  {  mDeltaTime += deltaTime;  if (mDeltaTime >= m_EndDelay)//超时进入下一状态  {  // This code is not run until 'RoundEnding' has finished.  At which point, check if a game winner has been found.  if (m_GameWinner != null)  {  // If there is a game winner, restart the level.  SceneManager.LoadScene("_Complete-Game_HotUpdate");  }  else  {  // If there isn't a winner yet, restart this coroutine so the loop continues.  mDeltaTime = 0f;  mState = 1;  }  }  }  }  private void SpawnAllTanks()  {  // For all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... create them, set their player number and references needed for control.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  m_Tanks[i].m_Instance =  GameObject.Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation) as GameObject;  m_Tanks[i].m_PlayerNumber = i + 1;  m_Tanks[i].Setup();  }  }  private void SetCameraTargets()  {  // Create a collection of transforms the same size as the number of tanks.  Transform[] targets = new Transform[m_Tanks.Length];  // For each of these transforms...  for (int i = 0; i < targets.Length; i++)  {  // ... set it to the appropriate tank transform.  targets[i] = m_Tanks[i].m_Instance.transform;  }  // These are the targets the camera should follow.  m_CameraControl.m_Targets = targets;  }  private void RoundStarting ()  {  // As soon as the round starts reset the tanks and make sure they can't move.  ResetAllTanks ();  DisableTankControl ();  // Snap the camera's zoom and position to something appropriate for the reset tanks.  m_CameraControl.SetStartPositionAndSize();  // Increment the round number and display text showing the players what round it is.  m_RoundNumber++;  m_MessageText.text = "ROUND " + m_RoundNumber;  }  private void RoundEnding ()  {  // Stop tanks from moving.  DisableTankControl ();  // Clear the winner from the previous round.  m_RoundWinner = null;  // See if there is a winner now the round is over.  m_RoundWinner = GetRoundWinner ();  // If there is a winner, increment their score.  if (m_RoundWinner != null)  m_RoundWinner.m_Wins++;  // Now the winner's score has been incremented, see if someone has one the game.  m_GameWinner = GetGameWinner ();  // Get a message based on the scores and whether or not there is a game winner and display it.  string message = EndMessage ();  m_MessageText.text = message;  }  // This is used to check if there is one or fewer tanks remaining and thus the round should end.  private bool OneTankLeft()  {  // Start the count of tanks left at zero.  int numTanksLeft = 0;  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if they are active, increment the counter.  if (m_Tanks[i].m_Instance.activeSelf)  numTanksLeft++;  }  // If there are one or fewer tanks remaining return true, otherwise return false.  return numTanksLeft <= 1;  }  // This function is to find out if there is a winner of the round.  // This function is called with the assumption that 1 or fewer tanks are currently active.  private TankManager GetRoundWinner()  {  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if one of them is active, it is the winner so return it.  if (m_Tanks[i].m_Instance.activeSelf)  return m_Tanks[i];  }  // If none of the tanks are active it is a draw so return null.  return null;  }  // This function is to find out if there is a winner of the game.  private TankManager GetGameWinner()  {  // Go through all the tanks...  for (int i = 0; i < m_Tanks.Length; i++)  {  // ... and if one of them has enough rounds to win the game, return it.  if (m_Tanks[i].m_Wins == m_NumRoundsToWin)  return m_Tanks[i];  }  // If no tanks have enough rounds to win, return null.  return null;  }  // Returns a string message to display at the end of each round.  private string EndMessage()  {  // By default when a round ends there are no winners so the default end message is a draw.  string message = "DRAW!";  // If there is a winner then change the message to reflect that.  if (m_RoundWinner != null)  message = m_RoundWinner.m_ColoredPlayerText + " WINS THE ROUND!";  // Add some line breaks after the initial message.  message += "\n\n\n\n";  // Go through all the tanks and add each of their scores to the message.  for (int i = 0; i < m_Tanks.Length; i++)  {  message += m_Tanks[i].m_ColoredPlayerText + ": " + m_Tanks[i].m_Wins + " WINS\n";  }  // If there is a game winner, change the entire message to reflect that.  if (m_GameWinner != null)  message = m_GameWinner.m_ColoredPlayerText + " WINS THE GAME!";  return message;  }  // This function is used to turn all the tanks back on and reset their positions and properties.  private void ResetAllTanks()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].Reset();  }  }  private void EnableTankControl()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].EnableControl();  }  }  private void DisableTankControl()  {  for (int i = 0; i < m_Tanks.Length; i++)  {  m_Tanks[i].DisableControl();  }  }  }  
} 

修改'/-///.cs',以下是完整版和免费版对应的改后热更新代码(两者相同):

using System;  
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  [Serializable]// RongRong : 不支持 '[Serializable]' 或 '[HideInInspector]', 你可以保留它,但它没任何效果  public class TankManager  {  // This class is to manage various settings on a tank.  // It works with the GameManager class to control how the tanks behave  // and whether or not players have control of their tank in the  // different phases of the game.  public Color m_PlayerColor;                             // This is the color this tank will be tinted.  public Transform m_SpawnPoint;                          // The position and direction the tank will have when it spawns.  [HideInInspector] public int m_PlayerNumber;            // This specifies which player this the manager for.  [HideInInspector] public string m_ColoredPlayerText;    // A string that represents the player with their number colored to match their tank.  [HideInInspector] public GameObject m_Instance;         // A reference to the instance of the tank when it is created.  [HideInInspector] public int m_Wins;                    // The number of wins this player has so far.  private TankMovement m_Movement;                        // Reference to tank's movement script, used to disable and enable control.  private TankShooting m_Shooting;                        // Reference to tank's shooting script, used to disable and enable control.  private GameObject m_CanvasGameObject;                  // Used to disable the world space UI during the Starting and Ending phases of each round.  public void Setup ()  {  // Get references to the components.  // RongRong : 'm_Instance.GetComponent ();' 改为 'HotUpdateBehaviour.GetComponentByType(m_Instance, typeof(TankMovement)) as TankMovement;'  m_Movement = HotUpdateBehaviour.GetComponentByType(m_Instance, typeof(TankMovement)) as TankMovement;  // RongRong : 'm_Instance.GetComponent ();' 改为 'HotUpdateBehaviour.GetComponentByType(m_Instance, typeof(TankShooting)) as TankShooting;'  m_Shooting = HotUpdateBehaviour.GetComponentByType(m_Instance, typeof(TankShooting)) as TankShooting;  m_CanvasGameObject = m_Instance.GetComponentInChildren ().gameObject;  // Set the player numbers to be consistent across the scripts.  m_Movement.m_PlayerNumber = m_PlayerNumber;  m_Shooting.m_PlayerNumber = m_PlayerNumber;  // Create a string using the correct color that says 'PLAYER 1' etc based on the tank's color and the player's number.  m_ColoredPlayerText = "PLAYER " + m_PlayerNumber + "";  // Get all of the renderers of the tank.  MeshRenderer[] renderers = m_Instance.GetComponentsInChildren ();  // Go through all the renderers...  for (int i = 0; i < renderers.Length; i++)  {  // ... set their material color to the color specific to this tank.  renderers[i].material.color = m_PlayerColor;  }  }  // Used during the phases of the game where the player shouldn't be able to control their tank.  public void DisableControl ()  {  m_Movement.behaviour.enabled = false;  m_Shooting.behaviour.enabled = false;  m_CanvasGameObject.SetActive (false);  }  // Used during the phases of the game where the player should be able to control their tank.  public void EnableControl ()  {  m_Movement.behaviour.enabled = true;  m_Shooting.behaviour.enabled = true;  m_CanvasGameObject.SetActive (true);  }  // Used at the start of each round to put the tank into it's default state.  public void Reset ()  {  m_Instance.transform.position = m_SpawnPoint.position;  m_Instance.transform.rotation = m_SpawnPoint.rotation;  m_Instance.SetActive (false);  m_Instance.SetActive (true);  }  }  
}

修改'/-//Shell/.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/OnTriggerEnter',  /// 我们使用 'HotUpdateBehaviourTrigger' 绑定预制体.  /// 预制体设置 'Game Objects' : "m_ExplosionParticles"/"m_ExplosionAudio".  /// 预制体设置 'Floats' : "m_MaxDamage"/"m_ExplosionForce"/"m_MaxLifeTime"/"m_ExplosionRadius".  /// 预制体设置 'Strings' : "m_TankMask".  ///   public class ShellExplosion : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public LayerMask m_TankMask;                        // Used to filter what the explosion affects, this should be set to "Players".  public ParticleSystem m_ExplosionParticles;         // Reference to the particles that will play on explosion.  public AudioSource m_ExplosionAudio;                // Reference to the audio that will play on explosion.  public float m_MaxDamage = 100f;                    // The amount of damage done if the explosion is centred on a tank.  public float m_ExplosionForce = 1000f;              // The amount of force added to a tank at the centre of the explosion.  public float m_MaxLifeTime = 2f;                    // The time in seconds before the shell is removed.  public float m_ExplosionRadius = 5f;                // The maximum distance away from the explosion tanks can be and are still affected.  // RongRong : 绑定数值必须在放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 从预制体里绑定数值  m_MaxDamage = GetFloat("m_MaxDamage", 100f);  m_ExplosionForce = GetFloat("m_ExplosionForce", 1000f);  m_MaxLifeTime = GetFloat("m_MaxLifeTime", 2f);  m_ExplosionRadius = GetFloat("m_ExplosionRadius", 5f);  m_TankMask = LayerMask.NameToLayer(GetString("m_TankMask", "Players"));// RongRong : 不支持绑定结构体,我们使用字符串来绑定,然后转回结构体  m_ExplosionParticles = GetComponent("m_ExplosionParticles");  m_ExplosionAudio = GetComponent("m_ExplosionAudio");  
#if UNITY_WEBGL  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/ShellExplosion.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ExplosionAudio.clip = audioClip;  });  
#endif  }  private void Start ()  {  // If it isn't destroyed by then, destroy the shell after it's lifetime.  // RongRong : 加前缀 "GameObject."  GameObject.Destroy (gameObject, m_MaxLifeTime);  }  private void OnTriggerEnter (Collider other)  {  // Collect all the colliders in a sphere from the shell's current position to a radius of the explosion radius.  // RongRong : 'm_TankMask' 改为 '1 << m_TankMask.value'. 调试模式下数值为 512(等于 1 << 9).  Collider[] colliders = Physics.OverlapSphere (transform.position, m_ExplosionRadius, 512);  // Go through all the colliders...  for (int i = 0; i < colliders.Length; i++)  {  // ... and find their rigidbody.  Rigidbody targetRigidbody = colliders[i].GetComponent ();  // If they don't have a rigidbody, go on to the next collider.  if (targetRigidbody == null)  continue;  // Add an explosion force.  targetRigidbody.AddExplosionForce (m_ExplosionForce, transform.position, m_ExplosionRadius);  // Find the TankHealth script associated with the rigidbody.  // RongRong : 'targetRigidbody.GetComponent ();' 改为 'HotUpdateBehaviour.GetComponentByType(targetRigidbody, typeof(TankHealth)) as TankHealth;'  TankHealth targetHealth = HotUpdateBehaviour.GetComponentByType(targetRigidbody, typeof(TankHealth)) as TankHealth;  // If there is no TankHealth script attached to the gameobject, go on to the next collider.  // RongRong : '!targetHealth' 改为 'targetHealth == null'  if (targetHealth == null)  continue;  // Calculate the amount of damage the target should take based on it's distance from the shell.  float damage = CalculateDamage (targetRigidbody.position);  // Deal this damage to the tank.  targetHealth.TakeDamage (damage);  }  // Unparent the particles from the shell.  m_ExplosionParticles.transform.parent = null;  // Play the particle system.  m_ExplosionParticles.Play();  // Play the explosion sound effect.  m_ExplosionAudio.Play();  // Once the particles have finished, destroy the gameobject they are on.  ParticleSystem.MainModule mainModule = m_ExplosionParticles.main;  // RongRong : 加前缀 "GameObject."  GameObject.Destroy (m_ExplosionParticles.gameObject, mainModule.duration);  // Destroy the shell.  // RongRong : 加前缀 "GameObject."  GameObject.Destroy (gameObject);  }  private float CalculateDamage (Vector3 targetPosition)  {  // Create a vector from the shell to the target.  Vector3 explosionToTarget = targetPosition - transform.position;  // Calculate the distance from the shell to the target.  float explosionDistance = explosionToTarget.magnitude;  // Calculate the proportion of the maximum distance (the explosionRadius) the target is away.  float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;  // Calculate damage as this proportion of the maximum possible damage.  float damage = relativeDistance * m_MaxDamage;  // Make sure that the minimum damage is always 0.  damage = Mathf.Max (0f, damage);  return damage;  }  }  
}//C#Like免费版
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/OnTriggerEnter',  /// 使用 'HotUpdateBehaviourTrigger' 绑定预制体.  /// 预制体设置 'Game Objects' : "m_ExplosionParticles"/"m_ExplosionAudio".  /// 预制体设置 'Floats' : "m_MaxDamage"/"m_ExplosionForce"/"m_MaxLifeTime"/"m_ExplosionRadius".  /// 预制体设置 'Strings' : "m_TankMask".  ///   public class ShellExplosion : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public LayerMask m_TankMask;                        // Used to filter what the explosion affects, this should be set to "Players".  public ParticleSystem m_ExplosionParticles;         // Reference to the particles that will play on explosion.  public AudioSource m_ExplosionAudio;                // Reference to the audio that will play on explosion.  public float m_MaxDamage = 100f;                    // The amount of damage done if the explosion is centred on a tank.  public float m_ExplosionForce = 1000f;              // The amount of force added to a tank at the centre of the explosion.  public float m_MaxLifeTime = 2f;                    // The time in seconds before the shell is removed.  public float m_ExplosionRadius = 5f;                // The maximum distance away from the explosion tanks can be and are still affected.  // RongRong : 绑定数值必须在放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体数值  m_MaxDamage = GetFloat("m_MaxDamage", 100f);  m_ExplosionForce = GetFloat("m_ExplosionForce", 1000f);  m_MaxLifeTime = GetFloat("m_MaxLifeTime", 2f);  m_ExplosionRadius = GetFloat("m_ExplosionRadius", 5f);  m_TankMask = LayerMask.NameToLayer(GetString("m_TankMask", "Players"));// RongRong : 不支持绑定结构体,我们用字符串代替,然后转回结构体  m_ExplosionParticles = GetComponent("m_ExplosionParticles");  m_ExplosionAudio = GetComponent("m_ExplosionAudio");  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  if (Application.platform == RuntimePlatform.WebGLPlayer)  {  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/ShellExplosion.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ExplosionAudio.clip = audioClip;  });  }  }  private void Start ()  {  // If it isn't destroyed by then, destroy the shell after it's lifetime.  // RongRong : 加前缀 "GameObject."  GameObject.Destroy (gameObject, m_MaxLifeTime);  }  private void OnTriggerEnter (Collider other)  {  // Collect all the colliders in a sphere from the shell's current position to a radius of the explosion radius.  // RongRong : 'm_TankMask' 改为 '512'. 调试数值为 512(等于 1 << m_TankMask.value).  Collider[] colliders = Physics.OverlapSphere (transform.position, m_ExplosionRadius, 512);  // Go through all the colliders...  for (int i = 0; i < colliders.Length; i++)  {  // ... and find their rigidbody.  Rigidbody targetRigidbody = colliders[i].GetComponent ();  // If they don't have a rigidbody, go on to the next collider.  if (targetRigidbody == null)  continue;  // Add an explosion force.  targetRigidbody.AddExplosionForce (m_ExplosionForce, transform.position, m_ExplosionRadius);  // Find the TankHealth script associated with the rigidbody.  // RongRong : 'targetRigidbody.GetComponent ();' 改为 'HotUpdateBehaviour.GetComponentByType(targetRigidbody, typeof(TankHealth)) as TankHealth;'  TankHealth targetHealth = HotUpdateBehaviour.GetComponentByType(targetRigidbody, typeof(TankHealth)) as TankHealth;  // If there is no TankHealth script attached to the gameobject, go on to the next collider.  // RongRong : '!targetHealth' 改为 'targetHealth == null'  if (targetHealth == null)  continue;  // Calculate the amount of damage the target should take based on it's distance from the shell.  float damage = CalculateDamage (targetRigidbody.position);  // Deal this damage to the tank.  targetHealth.TakeDamage (damage);  }  // Unparent the particles from the shell.  m_ExplosionParticles.transform.parent = null;  // Play the particle system.  m_ExplosionParticles.Play();  // Play the explosion sound effect.  m_ExplosionAudio.Play();  // Once the particles have finished, destroy the gameobject they are on.  ParticleSystem.MainModule mainModule = m_ExplosionParticles.main;  // RongRong : 添加前缀 "GameObject."  GameObject.Destroy (m_ExplosionParticles.gameObject, mainModule.duration);  // Destroy the shell.  // RongRong : 添加前缀 "GameObject."  GameObject.Destroy (gameObject);  }  private float CalculateDamage (Vector3 targetPosition)  {  // Create a vector from the shell to the target.  Vector3 explosionToTarget = targetPosition - transform.position;  // Calculate the distance from the shell to the target.  float explosionDistance = explosionToTarget.magnitude;  // Calculate the proportion of the maximum distance (the explosionRadius) the target is away.  float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;  // Calculate damage as this proportion of the maximum possible damage.  float damage = relativeDistance * m_MaxDamage;  // Make sure that the minimum damage is always 0.  damage = Mathf.Max (0f, damage);  return damage;  }  }  
}

修改'/-//Shell/.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using UnityEngine;  
using UnityEngine.UI;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/OnEnable',  /// we using 'HotUpdateBehaviour' to bind prefabe.  /// We add 'Floats' name "m_StartingHealth" in prefab.  /// We add 'Colors' name "m_FullHealthColor"/"m_ZeroHealthColor" in prefab.  /// We add 'Game Objects' name "m_Slider"/"m_FillImage"/"m_ExplosionPrefab" in prefab.  ///   public class TankHealth : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public float m_StartingHealth = 100f;               // The amount of health each tank starts with.  public Slider m_Slider;                             // The slider to represent how much health the tank currently has.  public Image m_FillImage;                           // The image component of the slider.  public Color m_FullHealthColor = Color.green;       // The color the health bar will be when on full health.  public Color m_ZeroHealthColor = Color.red;         // The color the health bar will be when on no health.  public GameObject m_ExplosionPrefab;                // A prefab that will be instantiated in Awake, then used whenever the tank dies.  private AudioSource m_ExplosionAudio;               // The audio source to play when the tank explodes.  private ParticleSystem m_ExplosionParticles;        // The particle system the will play when the tank is destroyed.  private float m_CurrentHealth;                      // How much health the tank currently has.  private bool m_Dead;                                // Has the tank been reduced beyond zero health yet?  private void Awake ()  {  // RongRong : 绑定预制体的数值.  m_StartingHealth = GetFloat("m_StartingHealth", 100f);  m_Slider = GetComponent("m_Slider");  m_FillImage = GetComponent("m_FillImage");  m_FullHealthColor = GetColor("m_FullHealthColor");  m_ZeroHealthColor = GetColor("m_ZeroHealthColor");  m_ExplosionPrefab = GetGameObject("m_ExplosionPrefab");  // Instantiate the explosion prefab and get a reference to the particle system on it.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  GameObject go = GameObject.Instantiate(m_ExplosionPrefab) as GameObject;  m_ExplosionParticles = go.GetComponent ();  // Get a reference to the audio source on the instantiated prefab.  m_ExplosionAudio = m_ExplosionParticles.GetComponent ();  // Disable the prefab so it can be activated when it's required.  m_ExplosionParticles.gameObject.SetActive (false);  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  
#if UNITY_WEBGL  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/TankExplosion.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ExplosionAudio.clip = audioClip;  });  
#endif  }  private void OnEnable()  {  // When the tank is enabled, reset the tank's health and whether or not it's dead.  m_CurrentHealth = m_StartingHealth;  m_Dead = false;  // Update the health slider's value and color.  SetHealthUI();  }  public void TakeDamage (float amount)  {  // Reduce current health by the amount of damage done.  m_CurrentHealth -= amount;  // Change the UI elements appropriately.  SetHealthUI ();  // If the current health is at or below zero and it has not yet been registered, call OnDeath.  if (m_CurrentHealth <= 0f && !m_Dead)  {  OnDeath ();  }  }  private void SetHealthUI ()  {  // Set the slider's value appropriately.  m_Slider.value = m_CurrentHealth;  // Interpolate the color of the bar between the choosen colours based on the current percentage of the starting health.  m_FillImage.color = Color.Lerp (m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);  }  private void OnDeath ()  {  // Set the flag so that this function is only called once.  m_Dead = true;  // Move the instantiated explosion prefab to the tank's position and turn it on.  m_ExplosionParticles.transform.position = transform.position;  m_ExplosionParticles.gameObject.SetActive (true);  // Play the particle system of the tank exploding.  m_ExplosionParticles.Play ();  // Play the tank explosion sound effect.  m_ExplosionAudio.Play();  // Turn the tank off.  gameObject.SetActive (false);  }  }  
} //C#Like免费版
using UnityEngine;  
using UnityEngine.UI;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/OnEnable',  /// 我们使用 'HotUpdateBehaviour' 绑定数据.  /// 预制体设置 'Floats' : "m_StartingHealth".  /// 预制体设置 'Colors' : "m_FullHealthColor"/"m_ZeroHealthColor".  /// 预制体设置 'Game Objects' : "m_Slider"/"m_FillImage"/"m_ExplosionPrefab".  ///   public class TankHealth : LikeBehaviour // RongRong : Change 'MonoBehaviour' to 'LikeBehaviour'  {  public float m_StartingHealth = 100f;               // The amount of health each tank starts with.  public Slider m_Slider;                             // The slider to represent how much health the tank currently has.  public Image m_FillImage;                           // The image component of the slider.  public Color m_FullHealthColor = Color.green;       // The color the health bar will be when on full health.  public Color m_ZeroHealthColor = Color.red;         // The color the health bar will be when on no health.  public GameObject m_ExplosionPrefab;                // A prefab that will be instantiated in Awake, then used whenever the tank dies.  private AudioSource m_ExplosionAudio;               // The audio source to play when the tank explodes.  private ParticleSystem m_ExplosionParticles;        // The particle system the will play when the tank is destroyed.  private float m_CurrentHealth;                      // How much health the tank currently has.  private bool m_Dead;                                // Has the tank been reduced beyond zero health yet?  private void Awake ()  {  // RongRong : 绑定预制体数据  m_StartingHealth = GetFloat("m_StartingHealth", 100f);  m_Slider = GetComponent("m_Slider");  m_FillImage = GetComponent("m_FillImage");  m_FullHealthColor = GetColor("m_FullHealthColor");  m_ZeroHealthColor = GetColor("m_ZeroHealthColor");  m_ExplosionPrefab = GetGameObject("m_ExplosionPrefab");  // Instantiate the explosion prefab and get a reference to the particle system on it.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  GameObject go = GameObject.Instantiate(m_ExplosionPrefab) as GameObject;  m_ExplosionParticles = go.GetComponent ();  // Get a reference to the audio source on the instantiated prefab.  m_ExplosionAudio = m_ExplosionParticles.GetComponent ();  // Disable the prefab so it can be activated when it's required.  m_ExplosionParticles.gameObject.SetActive (false);  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  if (Application.platform == RuntimePlatform.WebGLPlayer)  {  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/TankExplosion.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ExplosionAudio.clip = audioClip;  });  }  }  private void OnEnable()  {  // When the tank is enabled, reset the tank's health and whether or not it's dead.  m_CurrentHealth = m_StartingHealth;  m_Dead = false;  // Update the health slider's value and color.  SetHealthUI();  }  public void TakeDamage (float amount)  {  // Reduce current health by the amount of damage done.  m_CurrentHealth -= amount;  // Change the UI elements appropriately.  SetHealthUI ();  // If the current health is at or below zero and it has not yet been registered, call OnDeath.  if (m_CurrentHealth <= 0f && !m_Dead)  {  OnDeath ();  }  }  private void SetHealthUI ()  {  // Set the slider's value appropriately.  m_Slider.value = m_CurrentHealth;  // Interpolate the color of the bar between the choosen colours based on the current percentage of the starting health.  m_FillImage.color = Color.Lerp (m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);  }  private void OnDeath ()  {  // Set the flag so that this function is only called once.  m_Dead = true;  // Move the instantiated explosion prefab to the tank's position and turn it on.  m_ExplosionParticles.transform.position = transform.position;  m_ExplosionParticles.gameObject.SetActive (true);  // Play the particle system of the tank exploding.  m_ExplosionParticles.Play ();  // Play the tank explosion sound effect.  m_ExplosionAudio.Play();  // Turn the tank off.  gameObject.SetActive (false);  }  }  
} 

修改'/-//Shell/.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/OnEnable/OnDisable/Start/Update/FixedUpdate',  /// 我们使用 'HotUpdateBehaviourCommon' 绑定预制体, 设置 'scriptUpdateFPS' 数值为 10000, 设置 'enableFixedUpdate' 数值为 true.  /// 预制体设置 'Integers' : "m_PlayerNumber".  /// 预制体设置 'Floats' : "m_Speed"/"m_TurnSpeed"/"m_PitchRange".  /// 预制体设置 'Audio Clips' : "m_EngineIdling"/"m_EngineDriving".  /// 预制体设置 'Game Objects' : "m_MovementAudio".  ///   public class TankMovement : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_PlayerNumber = 1;              // Used to identify which tank belongs to which player.  This is set by this tank's manager.  public float m_Speed = 12f;                 // How fast the tank moves forward and back.  public float m_TurnSpeed = 180f;            // How fast the tank turns in degrees per second.  public AudioSource m_MovementAudio;         // Reference to the audio source used to play engine sounds. NB: different to the shooting audio source.  public AudioClip m_EngineIdling;            // Audio to play when the tank isn't moving.  public AudioClip m_EngineDriving;           // Audio to play when the tank is moving.  public float m_PitchRange = 0.2f;           // The amount by which the pitch of the engine noises can vary.  private string m_MovementAxisName;          // The name of the input axis for moving forward and back.  private string m_TurnAxisName;              // The name of the input axis for turning.  private Rigidbody m_Rigidbody;              // Reference used to move the tank.  private float m_MovementInputValue;         // The current value of the movement input.  private float m_TurnInputValue;             // The current value of the turn input.  private float m_OriginalPitch;              // The pitch of the audio source at the start of the scene.  private ParticleSystem[] m_particleSystems; // References to all the particles systems used by the Tanks  private void Awake ()  {  // RongRong : 'GetComponent ();' 改为 'gameObject.GetComponent();'  m_Rigidbody = gameObject.GetComponent();  // RongRong : 绑定预制体数值.  m_PlayerNumber = GetInt("m_PlayerNumber", 1);  m_Speed = GetFloat("m_Speed", 12f);  m_TurnSpeed = GetFloat("m_TurnSpeed", 180f);  m_PitchRange = GetFloat("m_PitchRange", 0.2f);  m_MovementAudio = GetComponent("m_MovementAudio");  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  
#if UNITY_WEBGL  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/EngineIdle.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_EngineIdling = audioClip;  });  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/EngineDriving.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_EngineDriving = audioClip;  });  
#else  m_EngineIdling = GetAudioClip("m_EngineIdling");  m_EngineDriving = GetAudioClip("m_EngineDriving");  
#endif  }  private void OnEnable ()  {  // When the tank is turned on, make sure it's not kinematic.  m_Rigidbody.isKinematic = false;  // Also reset the input values.  m_MovementInputValue = 0f;  m_TurnInputValue = 0f;  // We grab all the Particle systems child of that Tank to be able to Stop/Play them on Deactivate/Activate  // It is needed because we move the Tank when spawning it, and if the Particle System is playing while we do that  // it "think" it move from (0,0,0) to the spawn point, creating a huge trail of smoke  // RongRong : 'GetComponentsInChildren();' 改为 'gameObject.GetComponentsInChildren();'  m_particleSystems = gameObject.GetComponentsInChildren();  for (int i = 0; i < m_particleSystems.Length; ++i)  {  m_particleSystems[i].Play();  }  }  private void OnDisable ()  {  // When the tank is turned off, set it to kinematic so it stops moving.  m_Rigidbody.isKinematic = true;  // Stop all particle system so it "reset" it's position to the actual one instead of thinking we moved when spawning  for(int i = 0; i < m_particleSystems.Length; ++i)  {  m_particleSystems[i].Stop();  }  }  private void Start ()  {  // The axes names are based on player number.  m_MovementAxisName = "Vertical" + m_PlayerNumber;  m_TurnAxisName = "Horizontal" + m_PlayerNumber;  // Store the original pitch of the audio source.  m_OriginalPitch = m_MovementAudio.pitch;  }  private void Update ()  {  // Store the value of both input axes.  m_MovementInputValue = Input.GetAxis (m_MovementAxisName);  m_TurnInputValue = Input.GetAxis (m_TurnAxisName);  EngineAudio ();  }  private void EngineAudio ()  {  // If there is no input (the tank is stationary)...  if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)  {  // ... and if the audio source is currently playing the driving clip...  if (m_MovementAudio.clip == m_EngineDriving)  {  // ... change the clip to idling and play it.  m_MovementAudio.clip = m_EngineIdling;  m_MovementAudio.pitch = Random.Range (m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);  m_MovementAudio.Play ();  }  }  else  {  // Otherwise if the tank is moving and if the idling clip is currently playing...  if (m_MovementAudio.clip == m_EngineIdling)  {  // ... change the clip to driving and play.  m_MovementAudio.clip = m_EngineDriving;  m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);  m_MovementAudio.Play();  }  }  }  private void FixedUpdate ()  {  // Adjust the rigidbodies position and orientation in FixedUpdate.  Move ();  Turn ();  }  private void Move ()  {  // Create a vector in the direction the tank is facing with a magnitude based on the input, speed and the time between frames.  Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;  // Apply this movement to the rigidbody's position.  m_Rigidbody.MovePosition(m_Rigidbody.position + movement);  }  private void Turn ()  {  // Determine the number of degrees to be turned based on the input, speed and time between frames.  float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;  // Make this into a rotation in the y axis.  Quaternion turnRotation = Quaternion.Euler (0f, turn, 0f);  // Apply this rotation to the rigidbody's rotation.  m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);  }  }  
}//C#Like免费版
using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Awake/OnEnable/OnDisable/Start/Update/FixedUpdate',  /// 我们使用 'HotUpdateBehaviourCommon' 来绑定预制体, 设置 'scriptUpdateFPS' 数值为 10000, 设置 'enableFixedUpdate' 数值为 true.  /// 预制体设置 'Integers' : "m_PlayerNumber".  /// 预制体设置 'Floats' : "m_Speed"/"m_TurnSpeed"/"m_PitchRange".  /// 预制体设置 'Audio Clips' : "m_EngineIdling"/"m_EngineDriving".  /// 预制体设置 'Game Objects' : "m_MovementAudio".  ///   public class TankMovement : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_PlayerNumber = 1;              // Used to identify which tank belongs to which player.  This is set by this tank's manager.  public float m_Speed = 12f;                 // How fast the tank moves forward and back.  public float m_TurnSpeed = 180f;            // How fast the tank turns in degrees per second.  public AudioSource m_MovementAudio;         // Reference to the audio source used to play engine sounds. NB: different to the shooting audio source.  public AudioClip m_EngineIdling;            // Audio to play when the tank isn't moving.  public AudioClip m_EngineDriving;           // Audio to play when the tank is moving.  public float m_PitchRange = 0.2f;           // The amount by which the pitch of the engine noises can vary.  private string m_MovementAxisName;          // The name of the input axis for moving forward and back.  private string m_TurnAxisName;              // The name of the input axis for turning.  private Rigidbody m_Rigidbody;              // Reference used to move the tank.  private float m_MovementInputValue;         // The current value of the movement input.  private float m_TurnInputValue;             // The current value of the turn input.  private float m_OriginalPitch;              // The pitch of the audio source at the start of the scene.  private ParticleSystem[] m_particleSystems; // References to all the particles systems used by the Tanks  private void Awake ()  {  // RongRong : 'GetComponent ();' 改为 'gameObject.GetComponent();'  m_Rigidbody = gameObject.GetComponent();  // RongRong : 绑定预制体  m_PlayerNumber = GetInt("m_PlayerNumber", 1);  m_Speed = GetFloat("m_Speed", 12f);  m_TurnSpeed = GetFloat("m_TurnSpeed", 180f);  m_PitchRange = GetFloat("m_PitchRange", 0.2f);  m_MovementAudio = GetComponent("m_MovementAudio");  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  if (Application.platform == RuntimePlatform.WebGLPlayer)  {  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/EngineIdle.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_EngineIdling = audioClip;  });  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/EngineDriving.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_EngineDriving = audioClip;  });  }  else  {  m_EngineIdling = GetAudioClip("m_EngineIdling");  m_EngineDriving = GetAudioClip("m_EngineDriving");  }  }  private void OnEnable ()  {  // When the tank is turned on, make sure it's not kinematic.  m_Rigidbody.isKinematic = false;  // Also reset the input values.  m_MovementInputValue = 0f;  m_TurnInputValue = 0f;  // We grab all the Particle systems child of that Tank to be able to Stop/Play them on Deactivate/Activate  // It is needed because we move the Tank when spawning it, and if the Particle System is playing while we do that  // it "think" it move from (0,0,0) to the spawn point, creating a huge trail of smoke  // RongRong : 'GetComponentsInChildren();' 改为 'gameObject.GetComponentsInChildren();'  m_particleSystems = gameObject.GetComponentsInChildren();  for (int i = 0; i < m_particleSystems.Length; ++i)  {  m_particleSystems[i].Play();  }  }  private void OnDisable ()  {  // When the tank is turned off, set it to kinematic so it stops moving.  m_Rigidbody.isKinematic = true;  // Stop all particle system so it "reset" it's position to the actual one instead of thinking we moved when spawning  for(int i = 0; i < m_particleSystems.Length; ++i)  {  m_particleSystems[i].Stop();  }  }  private void Start ()  {  // The axes names are based on player number.  m_MovementAxisName = "Vertical" + m_PlayerNumber;  m_TurnAxisName = "Horizontal" + m_PlayerNumber;  // Store the original pitch of the audio source.  m_OriginalPitch = m_MovementAudio.pitch;  }  private void Update ()  {  // Store the value of both input axes.  m_MovementInputValue = Input.GetAxis (m_MovementAxisName);  m_TurnInputValue = Input.GetAxis (m_TurnAxisName);  EngineAudio ();  }  private void EngineAudio ()  {  // If there is no input (the tank is stationary)...  if (Mathf.Abs (m_MovementInputValue) < 0.1f && Mathf.Abs (m_TurnInputValue) < 0.1f)  {  // ... and if the audio source is currently playing the driving clip...  if (m_MovementAudio.clip == m_EngineDriving)  {  // ... change the clip to idling and play it.  m_MovementAudio.clip = m_EngineIdling;  m_MovementAudio.pitch = Random.Range (m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);  m_MovementAudio.Play ();  }  }  else  {  // Otherwise if the tank is moving and if the idling clip is currently playing...  if (m_MovementAudio.clip == m_EngineIdling)  {  // ... change the clip to driving and play.  m_MovementAudio.clip = m_EngineDriving;  m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);  m_MovementAudio.Play();  }  }  }  private void FixedUpdate ()  {  // Adjust the rigidbodies position and orientation in FixedUpdate.  Move ();  Turn ();  }  private void Move ()  {  // Create a vector in the direction the tank is facing with a magnitude based on the input, speed and the time between frames.  Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;  // Apply this movement to the rigidbody's position.  m_Rigidbody.MovePosition(m_Rigidbody.position + movement);  }  private void Turn ()  {  // Determine the number of degrees to be turned based on the input, speed and time between frames.  float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;  // Make this into a rotation in the y axis.  Quaternion turnRotation = Quaternion.Euler (0f, turn, 0f);  // Apply this rotation to the rigidbody's rotation.  m_Rigidbody.MoveRotation (m_Rigidbody.rotation * turnRotation);  }  }  
}

修改'/-//Shell/.cs',以下是完整版和免费版对应的改后热更新代码:

//C#Like完整版
using UnityEngine;  
using UnityEngine.UI;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/OnEnable/Update',  /// 我们使用 'HotUpdateBehaviourUpdate' 来绑定预制体.  /// 预制体设置 'Integers' : "m_PlayerNumber"  /// 预制体设置 'Game Objects' : "m_Shell"/"m_FireTransform"/"m_AimSlider"/"m_ShootingAudio"  /// 预制体设置 'Floats' : "m_MinLaunchForce"/"m_MaxLaunchForce"/"m_MaxChargeTime"  /// 预制体设置 'Audio Clips' : "m_ChargingClip"/"m_FireClip"  ///   public class TankShooting : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_PlayerNumber = 1;              // Used to identify the different players.  public Rigidbody m_Shell;                   // Prefab of the shell.  public Transform m_FireTransform;           // A child of the tank where the shells are spawned.  public Slider m_AimSlider;                  // A child of the tank that displays the current launch force.  public AudioSource m_ShootingAudio;         // Reference to the audio source used to play the shooting audio. NB: different to the movement audio source.  public AudioClip m_ChargingClip;            // Audio that plays when each shot is charging up.  public AudioClip m_FireClip;                // Audio that plays when each shot is fired.  public float m_MinLaunchForce = 15f;        // The force given to the shell if the fire button is not held.  public float m_MaxLaunchForce = 30f;        // The force given to the shell if the fire button is held for the max charge time.  public float m_MaxChargeTime = 0.75f;       // How long the shell can charge for before it is fired at max force.  private string m_FireButton;                // The input axis that is used for launching shells.  private float m_CurrentLaunchForce;         // The force that will be given to the shell when the fire button is released.  private float m_ChargeSpeed;                // How fast the launch force increases, based on the max charge time.  private bool m_Fired;                       // Whether or not the shell has been launched with this button press.  // RongRong : 绑定数值必须在放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体数值  m_PlayerNumber = GetInt("m_PlayerNumber", 1);  m_Shell = GetComponent("m_Shell");  m_MinLaunchForce = GetFloat("m_MinLaunchForce", 15f);  m_AimSlider = GetComponent("m_AimSlider");  m_MaxLaunchForce = GetFloat("m_MaxLaunchForce", 30f);  m_MaxChargeTime = GetFloat("m_MaxChargeTime", 0.75f);  m_FireTransform = GetComponent("m_FireTransform");  m_ShootingAudio = GetComponent("m_ShootingAudio");  // RongRong : 这个仅仅WebGL有效,因为仅仅WebGL平台下音效报错  
#if UNITY_WEBGL  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/ShotCharging.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ChargingClip = audioClip;  });  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeDemo/AssetBundles/Tank/WebGL/ShotFiring.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_FireClip = audioClip;  });  
#else  m_ChargingClip = GetAudioClip("m_ChargingClip");  m_FireClip = GetAudioClip("m_FireClip");  
#endif  }  private void OnEnable()  {  // When the tank is turned on, reset the launch force and the UI  m_CurrentLaunchForce = m_MinLaunchForce;  m_AimSlider.value = m_MinLaunchForce;  }  private void Start ()  {  // The fire axis is based on the player number.  m_FireButton = "Fire" + m_PlayerNumber;  // The rate that the launch force charges up is the range of possible forces by the max charge time.  m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;  }  private void Update ()  {  // The slider should have a default value of the minimum launch force.  m_AimSlider.value = m_MinLaunchForce;  // If the max force has been exceeded and the shell hasn't yet been launched...  if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)  {  // ... use the max force and launch the shell.  m_CurrentLaunchForce = m_MaxLaunchForce;  Fire ();  }  // Otherwise, if the fire button has just started being pressed...  else if (Input.GetButtonDown (m_FireButton))  {  // ... reset the fired flag and reset the launch force.  m_Fired = false;  m_CurrentLaunchForce = m_MinLaunchForce;  // Change the clip to the charging clip and start it playing.  m_ShootingAudio.clip = m_ChargingClip;  m_ShootingAudio.Play ();  }  // Otherwise, if the fire button is being held and the shell hasn't been launched yet...  else if (Input.GetButton (m_FireButton) && !m_Fired)  {  // Increment the launch force and update the slider.  m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;  m_AimSlider.value = m_CurrentLaunchForce;  }  // Otherwise, if the fire button is released and the shell hasn't been launched yet...  else if (Input.GetButtonUp (m_FireButton) && !m_Fired)  {  // ... launch the shell.  Fire ();  }  }  private void Fire ()  {  // Set the fired flag so only Fire is only called once.  m_Fired = true;  // Create an instance of the shell and store a reference to it's rigidbody.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  Rigidbody shellInstance =  GameObject.Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;  // Set the shell's velocity to the launch force in the fire position's forward direction.  shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward;  // Change the clip to the firing clip and play it.  m_ShootingAudio.clip = m_FireClip;  m_ShootingAudio.Play ();  // Reset the launch force.  This is a precaution in case of missing button events.  m_CurrentLaunchForce = m_MinLaunchForce;  }  }  
}//C#Like免费版
using UnityEngine;  
using UnityEngine.UI;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/OnEnable/Update',  /// 我们使用 'HotUpdateBehaviourUpdate' 来绑定预制体.  /// 预制体设置 'Integers' : "m_PlayerNumber"  /// 预制体设置 'Game Objects' : "m_Shell"/"m_FireTransform"/"m_AimSlider"/"m_ShootingAudio"  /// 预制体设置 'Floats' : "m_MinLaunchForce"/"m_MaxLaunchForce"/"m_MaxChargeTime"  /// 预制体设置 'Audio Clips' : "m_ChargingClip"/"m_FireClip"  ///   public class TankShooting : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  public int m_PlayerNumber = 1;              // Used to identify the different players.  public Rigidbody m_Shell;                   // Prefab of the shell.  public Transform m_FireTransform;           // A child of the tank where the shells are spawned.  public Slider m_AimSlider;                  // A child of the tank that displays the current launch force.  public AudioSource m_ShootingAudio;         // Reference to the audio source used to play the shooting audio. NB: different to the movement audio source.  public AudioClip m_ChargingClip;            // Audio that plays when each shot is charging up.  public AudioClip m_FireClip;                // Audio that plays when each shot is fired.  public float m_MinLaunchForce = 15f;        // The force given to the shell if the fire button is not held.  public float m_MaxLaunchForce = 30f;        // The force given to the shell if the fire button is held for the max charge time.  public float m_MaxChargeTime = 0.75f;       // How long the shell can charge for before it is fired at max force.  private string m_FireButton;                // The input axis that is used for launching shells.  private float m_CurrentLaunchForce;         // The force that will be given to the shell when the fire button is released.  private float m_ChargeSpeed;                // How fast the launch force increases, based on the max charge time.  private bool m_Fired;                       // Whether or not the shell has been launched with this button press.  // RongRong : 绑定数值必须在放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体  m_PlayerNumber = GetInt("m_PlayerNumber", 1);  m_Shell = GetComponent("m_Shell");  m_MinLaunchForce = GetFloat("m_MinLaunchForce", 15f);  m_AimSlider = GetComponent("m_AimSlider");  m_MaxLaunchForce = GetFloat("m_MaxLaunchForce", 30f);  m_MaxChargeTime = GetFloat("m_MaxChargeTime", 0.75f);  m_FireTransform = GetComponent("m_FireTransform");  m_ShootingAudio = GetComponent("m_ShootingAudio");  if (Application.platform == RuntimePlatform.WebGLPlayer)  {  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/ShotCharging.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_ChargingClip = audioClip;  });  ResourceManager.LoadAudioClipAsync("https://www.csharplike.com/CSharpLikeFreeDemo/AssetBundles/Tank/WebGL/ShotFiring.wav", AudioType.WAV, (AudioClip audioClip) =>  {  m_FireClip = audioClip;  });  }  else  {  m_ChargingClip = GetAudioClip("m_ChargingClip");  m_FireClip = GetAudioClip("m_FireClip");  }  }  private void OnEnable()  {  // When the tank is turned on, reset the launch force and the UI  m_CurrentLaunchForce = m_MinLaunchForce;  m_AimSlider.value = m_MinLaunchForce;  }  private void Start ()  {  // The fire axis is based on the player number.  m_FireButton = "Fire" + m_PlayerNumber;  // The rate that the launch force charges up is the range of possible forces by the max charge time.  m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;  }  private void Update ()  {  // The slider should have a default value of the minimum launch force.  m_AimSlider.value = m_MinLaunchForce;  // If the max force has been exceeded and the shell hasn't yet been launched...  if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)  {  // ... use the max force and launch the shell.  m_CurrentLaunchForce = m_MaxLaunchForce;  Fire ();  }  // Otherwise, if the fire button has just started being pressed...  else if (Input.GetButtonDown (m_FireButton))  {  // ... reset the fired flag and reset the launch force.  m_Fired = false;  m_CurrentLaunchForce = m_MinLaunchForce;  // Change the clip to the charging clip and start it playing.  m_ShootingAudio.clip = m_ChargingClip;  m_ShootingAudio.Play ();  }  // Otherwise, if the fire button is being held and the shell hasn't been launched yet...  else if (Input.GetButton (m_FireButton) && !m_Fired)  {  // Increment the launch force and update the slider.  m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime;  m_AimSlider.value = m_CurrentLaunchForce;  }  // Otherwise, if the fire button is released and the shell hasn't been launched yet...  else if (Input.GetButtonUp (m_FireButton) && !m_Fired)  {  // ... launch the shell.  Fire ();  }  }  private void Fire ()  {  // Set the fired flag so only Fire is only called once.  m_Fired = true;  // Create an instance of the shell and store a reference to it's rigidbody.  // RongRong : 'Instantiate' 改为 'GameObject.Instantiate'  Rigidbody shellInstance =  GameObject.Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) as Rigidbody;  // Set the shell's velocity to the launch force in the fire position's forward direction.  shellInstance.velocity = m_CurrentLaunchForce * m_FireTransform.forward;  // Change the clip to the firing clip and play it.  m_ShootingAudio.clip = m_FireClip;  m_ShootingAudio.Play ();  // Reset the launch force.  This is a precaution in case of missing button events.  m_CurrentLaunchForce = m_MinLaunchForce;  }  }  
}

修改'/-///.cs',以下是完整版和免费版对应的改后热更新代码(两者相同):

using UnityEngine;  namespace CSharpLike// RongRong : 命名空间改为 "CSharpLike" 或者前面使用 "using CSharpLike;".  
{  ///   /// RongRong : 本类包含函数 'Start/Update',  /// 我们使用 'HotUpdateBehaviourUpdate' 来绑定预制体, 设置 'scriptUpdateFPS' 数值为 10000.  /// 预制体设置 'Booleans' : "m_UseRelativeRotation".  ///   public class UIDirectionControl : LikeBehaviour // RongRong : 基类 'MonoBehaviour' 改为 'LikeBehaviour'  {  // This class is used to make sure world space UI  // elements such as the health bar face the correct direction.  public bool m_UseRelativeRotation = true;       // Use relative rotation should be used for this gameobject?  private Quaternion m_RelativeRotation;          // The local rotatation at the start of the scene.  // RongRong : 绑定数值必须在放在Awake函数内,因为执行顺序为 : Awake -> OnEnable -> Start.  void Awake()  {  // RongRong : 绑定预制体的数值  m_UseRelativeRotation = GetBoolean("m_UseRelativeRotation", true);  }  private void Start ()  {  m_RelativeRotation = transform.parent.localRotation;  }  private void Update ()  {  if (m_UseRelativeRotation)  transform.rotation = m_RelativeRotation;  }  }  
}

修改预制体和场景: 然后我们逐一地把目录'\\-\'里的预制体进行修改: 我们复制'/-/'整个目录为'/-/'目录编辑器内双击打开场景'/-Game.unity',菜单'File'->'Save As...'另存为'/-.unity'修改场景'/-.unity'. 修改预制体'/-//.': 移除原组件,新增igger组件. (WebGL平台的,修改'/on'节点里的'Audio '组件的设置为None)

修改预制体'/-//.' 修改预制体'/-//n.', (WebGL平台的,'Audio '组件的设置为None)设置名字和后缀,编辑器的面板选中'/-.unity', 在面板的'Asset '设置:名字为'tanks',后缀为'ab',如下图设置:

这个时候直接点编辑器里点'▶'运行,应该可以正常调试运行的 配置裁剪'/C#Like/link.xml' -C#Like(Free)导出脚本的时候会自动识别用到代码部分用到的模块生成到'link.xml';

-C#Like(Free)导出的时候会自动识别部分用到的模块生成到'link.xml';

-没有识别到的部分,需要你自行添加到'/C#Like//.txt',如下 .ty...unity.-hlapi. 修改C#Like配置:菜单''->'C#Like'->打开'C#Like '面板 最终的配置如图:

编辑器测试调试 导出最终产品 本系列文章导读:

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了