廿三 Unity热更新方案C#Like

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_WEBGLResourceManager.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 statemState = 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_WEBGLResourceManager.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_WEBGLResourceManager.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 = http://www.kingceram.com/post/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 = http://www.kingceram.com/post/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 Tanksprivate 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_WEBGLResourceManager.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;});#elsem_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 = http://www.kingceram.com/post/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 spawningfor(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 = http://www.kingceram.com/post/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 Tanksprivate 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 = http://www.kingceram.com/post/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 spawningfor(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 = http://www.kingceram.com/post/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_WEBGLResourceManager.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;});#elsem_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 UIm_CurrentLaunchForce = m_MinLaunchForce;m_AimSlider.value = http://www.kingceram.com/post/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 = http://www.kingceram.com/post/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 = http://www.kingceram.com/post/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 UIm_CurrentLaunchForce = m_MinLaunchForce;m_AimSlider.value = http://www.kingceram.com/post/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 = http://www.kingceram.com/post/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 = http://www.kingceram.com/post/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 '面板 最终的配置如图:
?
【廿三Unity热更新方案C#Like】编辑器测试调试导出最终产品 本系列文章导读: