目录
Unity 游戏实例开发集合 之 Car Racing 2D (2D赛车) 休闲小游戏快速实现
一、简单介绍
二、Car Racing 2D (2D赛车) 游戏内容与操作
三、相关说明
四、游戏代码框架
五、知识点
六、游戏效果预览
七、实现步骤
八、工程源码地址
九、延伸扩展
Unity 游戏实例开发集合,使用简单易懂的方式,讲解常见游戏的开发实现过程,方便后期类似游戏开发的借鉴和复用。
本节介绍,Car Racing 2D (2D赛车) 休闲小游戏快速实现的方法,希望能帮到你,若有不对,请留言。
这是一个 2D 游戏,主要是使用精灵图、2D 重力、2D 碰撞体,实现,游戏实现原理:
0、这是2D 游戏基于 竖屏 1080x1920 开发的,并实现屏幕适配;
1、玩家 car y 方向位置保持不变,道路和 NPC 车 金币,从上往下移动,从而实现 玩家车 向前开动的效果
2、场景所有物体都没有重力效果,但是都挂载Rigidbody2D,Body Type 是 Kinematic , 通过 GetComponent
().Velocity 的 方向添加速度,从而实现移动的效果 3、行程数加分,原理是,根据 road 的相信移动速度,来实现车行程数加分
4、游戏暂停实现原理是:1)暂停效果:把所有运动的游戏物体空草地,Rigidbody2D的Velocity 设置为0 ,从而实现停止效果;2)继续游戏效果:把所有运动的游戏物体空草地,Rigidbody2D的Velocity 设置恢复为 原来的值,这样就恢复现场,继续游戏了
5、无限背景实现:把路设置两份(可以根据需要设置多份),1) 两份上下拼接
2)先是下边的背景显示,背景一起向下边运动,当到下边看不见,下边的看不到的背景,重新移动到上边背景的上边,这样,就实现无限循环背景了
1、游戏开始,背景 Road 自动向下移动,玩家 Car 随机位置 ,等待点击 Play 开始游戏
2、点击 Play 开始游戏,自动生成 NPC 车子和金币,点击鼠标左键,当位置在屏幕一半的左边,车子左转移动,在右边则右转移动(可以根据需要添加按键控制)
3、根据行程数,就会有对应的分数增加,如果碰到金币额外加分
4、碰到其他 NPC 车子,游戏结束
5、游戏中途可以点击暂停,继续玩或者重新开始
1、音频的枚举命名对应实际音频的名字,方便加载和指定播放音频
2、由于2D 游戏(元素都在同一平面),设置 SpriteRenderer 的 Order in Layer 来控制显示先后,Order in Layer 越大,显示就在前面,所以各个 Order in Layer 定义显示规则为 :Road 预制体为 -5,车子和金币为 0
3、Road 、车子、金币 的 Rigidbody2D, Body Type 是 Kinematic ,没有重力向下的效果
4、车子左右转弯的时候,如果使用赋值 transform.eulerAngles 的方式,注意 数值如果赋值负值,系统会自动转为 360 + 赋值的新值(从而使得旋转和怪异),所以这块要注意做好处理
private void RotateLerp(float target) {
Vector3 curRot = transform.eulerAngles;
if (curRot.z <= 90)
{
curRot.z = Mathf.Lerp(curRot.z, target, Time.deltaTime * m_RotateAnimationSpeed);
}
else { // 修正右边旋转赋值,会赋值转为对应正值旋转值(例如 -5,自动转为 360 +(-5))
curRot.z = Mathf.Lerp(curRot.z, 360+target, Time.deltaTime * m_RotateAnimationSpeed);
}
// Debug.Log(curRot.z);
transform.eulerAngles = curRot;
}
5、NPC 车子是生成的位置,是根据屏幕高的上的 百分(1+?%)多少设置的,所以,可以保证,生成前一定是看不见车子的;NPC 车子是回收的位置,也是根据屏幕的高的下 百分(-?%)多少设置的,可以保证回收车,也是看不见车的
6、脚本复刻建议:先复刻 Common 和 Tools 文件夹的脚本,然后 各个实体 文件夹的脚本,接着 Manager 的各个脚本(顺序可按实现步骤的顺序来),最后 GameManager 和 GameStart 即可
(仅做参考,可以根据需要重新架构,添加消息中心,管理一些事件,或者更好)
1、MonoBehaviour 生命周期函数:Awake,Start,Update,Destroy
2、Input 按键的监控鼠标按键的状态
3、GameObject.Instantiate 物体的生成,GameObject.Destroy 物体的销毁
4、简单的对象池管理
5、Rigidbody 重力效果,添加 EdgeCollider2D ,进行 碰撞检测(Trigger)
6、简单UGUI的使用
7、2D 游戏屏幕适配(主要是UI 、 NPC 生成位置,屏幕适配 Camera orthographicSize)
8、一些数据,路径等的统一常量管理
9、Animation、Animator 的简单使用
10、游戏中 Tag 的使用
11、IManager 简单的接口规范 Manager 类函数
12、Action
13、Resources.Load
14、简单的屏幕坐标转为世界坐标的工具类
15、 SceneManager.LoadScene 加载,和 SceneManager.GetActiveScene() 当前场景的获取
16、游戏开发的资源脚本文件夹分类管理
17、Audio 简单管理播放
18、等等
1、打开 Unity,导入相关资源,并把图片都转为精灵图
2、把 Main Camera 的位置设置为(0,0,-10),Clear Flags 设置为 Solid Color , Projection 设置为 Orthofraphic ,如图
3、在场景中,添加一个 GameObject ,改名为 World ,pos(0,0,0),删除 Directional Light ,把 Main Camera 拖到其下,并把屏幕设置为 1080x1920 作为游戏开发宽高(后面屏幕适配都是基于1080x1920做适配)
4、把 Road 拖到场景中,根据现有Game 视口,适配 Scale(这里适配得到(1.25,1.25,1.25)),添加 Rigidbody2D,BodyType 设置为 Kinematic (不需要重力效果,但是有运动学效果),并拖入到 Resources 作为预制体
5、拖 Car1到场景中,改名为 PCCar,添加 Rigidbody2D,BodyType 设置为 Kinematic (不需要重力效果,但是有运动学效果),并拖入到 Resources 作为预制体,并添加 BoxCollider2D,调整其大小适配车子大小
6、同理第 5 步骤,完成其他车辆并做成预制体
7、把 boundsGold_x 几张一起拖入到场景中,会自动生成动画,其他 同理第 5 步骤,做成预制体
8、同理第 7 步骤,把 car3 和 car6 做成带动画的预制体
9、把 Sounds 也拖到 Resources 文件夹下,使用 Resources 动态加载音频文件
10、在 World 下面添加多个 GameObject,位置都为(0,0,0),改名为 SpawnRoadPos,AudioSourceTrans,SpawnNPCPos,SpawnPCCarPos,功能如其名,用于生成Road父物体,音频源挂载,生成NPCPos的父物体,生成 PCCar 的父物体
11、在UI下面,添加一个Canvas (自动添加EventSystem),设置 Canvas Scaler 的 UI Scale Mode 为 Scale with Screen Size ,Reference Resolution 为 1080 x 1920 ,Match 滑动到 Height
12、添加 PlayPanel,各个参数设置,和效果图如下
13、添加 GamePanel,各个参数设置,和效果图如下
14、添加 PausePanel,各个参数设置,和效果图如下
15、 添加 GameOverPanel,各个参数设置,和效果图如下
16、在工程中添加 Common (一般可以游戏开发最后写,脚本中抽出来,整理统一管理),管理游戏中的一些常量,Enum 管理所有枚举,GameConfig 一些游戏配置,GameObjectPathInSceneDefine 场景中的游戏物体路径定义,ResPathDefine 预制体路径定义类,Tag tag 定义类
17、Tag tag 定义类,其中 Tag 定义方式如下,选择一个游戏物体,选择 Tag 下拉菜单,点击 Add Tag ... 添加 Tag ,然后选择 + ,命名一个名称,save 即可,这里,我们把预制体的 NPCCarx 的 Tag 设置为 NPCCar(后面碰撞体触发用到),Coin 设置为 Coin
18、在 Interface,添加各种基础的接口,便于具体类的功能指定添加
19、Tools 工具类 ,目前添加两个功能函数,把屏幕坐标转为世界坐标函数,2D游戏 屏幕适配 函数
public class Tools
{
///
/// 把屏幕坐标转为世界坐标
///
/// 对应参照对象
/// 对应参照相机
/// 屏幕位置
/// 屏幕位置的世界位置
public static Vector3 ScreenPosToWorldPos(Transform refTran, Camera refCamera, Vector2 screenPos)
{
//将对象坐标换成屏幕坐标
Vector3 pos = refCamera.WorldToScreenPoint(refTran.position);
//让鼠标的屏幕坐标与对象坐标一致
Vector3 mousePos = new Vector3(screenPos.x, screenPos.y, pos.z);
//将正确的鼠标屏幕坐标换成世界坐标交给物体
return refCamera.ScreenToWorldPoint(mousePos);
}
///
/// 2D游戏 屏幕适配
/// vaildWidth =(Screen.width/Screen.height*2*orthographicSize)
///
/// 开发时的参考屏幕宽
/// 开发时的参考屏幕高
/// 正交的相加
public static void AdaptationFor2DGame(int BaseScreenWitdh,int BaseScreenHeight,Camera orthographicCamera2D)
{
if (orthographicCamera2D.orthographic ==false)
{
Debug.LogError("AdaptationFor2DGame()/ Camera is not orthographic , Please Check !! ");
return;
}
// 获取开发设置的 正交相机的 size
float BaseOrSize = orthographicCamera2D.orthographicSize;
// 获得有效的可视宽度
float vaildWidth = (BaseScreenWitdh * 1.0f / BaseScreenHeight) * 2 * BaseOrSize;
// 实际屏幕的 宽高比
float aspectRatio = Screen.width * 1f / Screen.height;
// 根据实际屏幕和有效的可视宽度得到,适配的 size ,并赋值到相机
float adapterOrthoSize = vaildWidth / aspectRatio / 2;
orthographicCamera2D.orthographicSize = adapterOrthoSize;
}
}
20、在工程中添加 IServer 脚本,主要功能是定义服务类的一些基本功能接口
22、 在工程中添加 ResLoadServer 脚本,继承 IServer,主要功能是 加载预制体和音频文件
///
/// 加载预制体 GameObject
///
///
///
public GameObject LoadPrefab(string path)
{
if (m_PrefabsDict.ContainsKey(path) == true)
{
return m_PrefabsDict[path];
}
else
{
GameObject prefab = Load(path);
if (prefab != null)
{
m_PrefabsDict.Add(path, prefab);
}
return prefab;
}
}
///
/// 加载预制体 AudioClip
///
///
///
public AudioClip LoadAudioClip(string path)
{
if (m_AudioClipsDict.ContainsKey(path) == true)
{
return m_AudioClipsDict[path];
}
else
{
AudioClip prefab = Load(path);
if (prefab != null)
{
m_AudioClipsDict.Add(path, prefab);
}
return prefab;
}
}
23、 在工程中添加 AudioServer 脚本,继承 IServer,主要功能是加载指定音频,添加音频源,以及播放背景音乐和指定音频文件
///
/// 播放指定背景音乐
///
///
public void PlayBG(AudioClipSet audioName,bool isLoop=true)
{
if (m_AudioClipDict.ContainsKey(audioName) == true)
{
m_AudioSource.Stop();
m_AudioSource.clip = m_AudioClipDict[audioName];
m_AudioSource.loop = isLoop;
m_AudioSource.Play();
}
else
{
Debug.LogError(GetType() + "/PlayAudio()/ audio clip is null,audioName = " + audioName);
}
}
///
/// 停止背景音乐
///
public void StopBG() {
m_AudioSource.Stop();
}
///
/// 播放指定音频
///
///
public void PlayAudio(AudioClipSet audioName)
{
if (m_AudioClipDict.ContainsKey(audioName) == true)
{
m_AudioSource.PlayOneShot(m_AudioClipDict[audioName]);
}
else
{
Debug.LogError(GetType() + "/PlayAudio()/ audio clip is null,audioName = " + audioName);
}
}
///
/// 加载音频
///
private void Load()
{
for (AudioClipSet clipPath = AudioClipSet.Bonus; clipPath < AudioClipSet.SUM_COUNT; clipPath++)
{
AudioClip audioClip = m_ResLoadServer.LoadAudioClip(ResPathDefine.AUDIO_CLIP_BASE_PATH + clipPath.ToString());
m_AudioClipDict.Add(clipPath, audioClip);
}
}
24、在工程中添加 IManager 脚本,主要功能是定义管理类的一些基本功能接口
25、在工程中添加 RoadManager 脚本,主要功能是加载road ,生成 2 块Road拼接(这里拼接距离记录到GameConfig),添加 velocity 使之运动,运动指定距离,移动到上方,实现循环 Road
///
/// 加载预制体
///
private void LoadPrefab()
{
GameObject prefab = m_ResLoadServer.LoadPrefab(ResPathDefine.PREFAB_ROAD_PATH);
for (int i = 0; i < GameConfig.ROAD_TILE_COUNT; i++)
{
GameObject road = GameObject.Instantiate(prefab, m_SpawnSkyTilePosTrans);
Vector3 curPos = road.transform.position;
road.transform.position = new Vector3(curPos.x, curPos.y+(i*GameConfig.ROAD_SPRITE_INTERVAL_Y),curPos.z);
m_RoadTransformList.Add(road.transform);
m_RoadRigidbodyList.Add(road.GetComponent());
}
}
///
/// 更新Road位置
/// 当位置到达指定位置,进行位置左移,从而实现无限循环
///
private void UpdatePosOperation()
{
foreach (var item in m_RoadTransformList)
{
Vector3 curPos = item.position;
if (curPos.y <= m_TargetPosY)
{
// 移动到右边(以为走了,右边的右边,所以增加 2 * BACKGROUND_SPRITE_INTERVAL_X )
curPos = new Vector3( curPos.x, (curPos.y+ m_ResetMoveDistanceY), curPos.z);
item.position = curPos;
}
}
}
26、在工程中添加 Model 脚本,主要功能是定义了一个基本的数据模型,包含数值,和数值变化触发的事件
///
/// 数据模型
///
public class Model
{
private int m_Value;
public int Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
if (OnValueChanged != null)
{
OnValueChanged.Invoke(value);
}
}
}
}
///
/// 数值变化事件
///
public Action OnValueChanged;
}
27、在工程中添加 DataModelManager 脚本,主要功能是定义游戏中涉及到的数据,这里定义了 Score (既是 Model 类类型参数)的数据
28、在工程中添加 DrivingDistanceScoreManager 脚本,主要功能是根据行程增加分数(实际是根据road 速度,每秒增加分数)
///
/// 行程加分
///
void UpdateDistanceScore()
{
m_Timer += Time.deltaTime;
if (m_Timer>= (1.0f/GameConfig.ROAD_MOVEVELOCITY_Y))
{
m_Timer -= (1.0f / GameConfig.ROAD_MOVEVELOCITY_Y);
m_DataModelManager.Score.Value += GameConfig.DRIVING_DISTANCE_SCORE;
}
}
29、在工程中添加 ObjectPool 和 ObjectPoolContainer 脚本,主要功能是建立对象池(对象的预载,获取,释放、清空),和对象池对象使用状态(使用和未使用)管理
30、在工程中添加 ObjectPoolManager 脚本,主要功能是管理各个对象池,预载、获取、释放、各个对象池的对象
///
/// 孵化器孵化指定个数对象池对象
///
/// 预制体
/// 要预生成对象池对象
public void WarmPool(GameObject prefab,Transform parent, int count)
{
if (m_PrefabPoolDictinary.ContainsKey(prefab))
{
Debug.Log("Pool for prefab " + prefab.name + " has already been created");
}
ObjectPool pool = new ObjectPool(() => {
return InstantiatePrefab(prefab, parent);
}, DestroyClone, count);
// 添加到字典中
m_PrefabPoolDictinary[prefab] = pool;
// 更新使用数据标志
m_Dirty = true;
}
///
/// 从对象池拿出指定对象使用
///
/// 要使用的对象
/// 对象池返回的可用对象
public GameObject SpawnObject(GameObject prefab, Transform parent)
{
return SpawnObject(prefab, parent, Vector3.zero, Quaternion.identity);
}
public GameObject SpawnObject(GameObject prefab,Transform parent, Vector3 position, Quaternion rotation)
{
// 如果该预制体没有孵化,则先进行孵化 1 个
if (m_PrefabPoolDictinary.ContainsKey(prefab) == false)
{
WarmPool(prefab, parent,1);
}
// 从对象池中获取对象
ObjectPool pool = m_PrefabPoolDictinary[prefab];
GameObject clone = pool.GetObjectPoolContainerItem();
// 设置对象的位置旋转,显示物体
clone.transform.position = position;
clone.transform.rotation = rotation;
clone.SetActive(true);
// 把拿出来的对象添加到已使用的字典中
m_UsedPoolObjectbDictinary.Add(clone, pool);
// 更新使用数据标志
m_Dirty = true;
return clone;
}
///
/// 释放使用的对象池对象
///
/// 对象
public void ReleaseObject(GameObject clone)
{
clone.SetActive(false);
// 已使用的字典中
if (m_UsedPoolObjectbDictinary.ContainsKey(clone))
{
m_UsedPoolObjectbDictinary[clone].ReleaseItem(clone);
m_UsedPoolObjectbDictinary.Remove(clone);
// 更新使用数据标志
m_Dirty = true;
}
else
{
Debug.Log("No pool contains the object: " + clone.name);
}
}
31、在工程中添加 NPCManager 脚本,主要功能是加载NPC预制体,定时生成 NPC(随机NPC,随机位置的一些数据,在车道上确定,统一在GameConfig 定义) ,并判断位置,回收NPC
///
/// 加载预制体
///
private void LoadPrefab() {
for (NPCType npc = NPCType.Coin; npc < NPCType.SUM_COUNT; npc++)
{
m_NPCPrefabDict.Add(npc,m_ResLoadServer.LoadPrefab(ResPathDefine.PREFAB_NPC_BASE_PATH + npc.ToString()));
}
}
///
/// 计时生成管子,以及初始化管子和设置回收管子事件
///
void UpdateSpawnNPC()
{
m_SpawnTimer += Time.deltaTime;
if (m_SpawnTimer >= GameConfig.NPC_SPAWN_TIME_INTERVAL)
{
m_SpawnTimer -= GameConfig.NPC_SPAWN_TIME_INTERVAL;
int rand = Random.Range((int)NPCType.Coin, (int)NPCType.SUM_COUNT);
GameObject npc = m_ObjectPoolManager.SpawnObject(m_NPCPrefabDict[(NPCType)rand],m_SpawnNPCPosTrans);
npc.transform.position = m_SpawnPosArray[Random.Range(0, m_SpawnPosArray.Length)];
Rigidbody2D rb = npc.GetComponent();
rb.velocity = m_NPCMoveVelocity;
m_ShowNPCRigidbodyList.Add(rb);
m_ShowNPCTransformList.Add(npc.transform);
}
}
///
/// 判断位置,回收对象
///
void UpdateNPCPosRecycle() {
if (m_ShowNPCTransformList!=null)
{
foreach (Transform npc in m_ShowNPCTransformList)
{
if (npc.position.y<=m_TargetMovePosY)
{
npc.GetComponent().velocity = Vector2.zero;
m_ObjectPoolManager.ReleaseObject(npc.gameObject);
npc.position = m_SpawnPosArray[0];
}
}
}
}
32、在工程中添加 NPCManager 脚本,主要功能是控制车子切道转向(这里以屏幕为中心,左半屏转左,右半屏转右),包括左右移动的位置限制,转弯动画,以及碰撞 NPC 车子和金币事件委托触发
///
/// 车子切道转向
///
public void UpdateChangeLaneRotation() {
if (Input.GetMouseButtonDown(0)
&& EventSystem.current.IsPointerOverGameObject() == false)// 不是点击 UI
{
if (Input.mousePosition.x <= m_HalfWidth) // 左转
{
m_CurCarRoateDir = CarRoateDir.Left;
Rigidbody2D.velocity = m_MoveLeftVelocity;
}
else { // 右转
m_CurCarRoateDir = CarRoateDir.Right;
Rigidbody2D.velocity = m_MoveRightVelocity;
}
}
else if(Input.GetMouseButtonUp(0)) {
m_CurCarRoateDir = CarRoateDir.Normal;
Rigidbody2D.velocity = Vector2.zero ;
}
RotateSelf(m_CurCarRoateDir);
UpdateLimitPos();
}
33、在工程中添加 NPCManager 脚本,主要功能是获取UI元素,添加按钮事件,以及分数在UI上的更新事件
private void OnRestartButton()
{
m_AudioServer.PlayAudio(AudioClipSet.ButtonClick);
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
private void OnPlayButton() {
m_AudioServer.PlayAudio(AudioClipSet.ButtonClick);
if (m_OnGameResume != null)
{
m_OnGameResume.Invoke();
}
}
private void OnPauseButton()
{
m_AudioServer.PlayAudio(AudioClipSet.ButtonClick);
if (m_OnGamePause != null)
{
m_OnGamePause.Invoke();
}
}
private void UpdateScoreText(int score) {
m_ScoreText.text = score.ToString();
}
34、在工程中添加 BaseGameManager 脚本,主要功能是 定义注册个接口,和 Update Destroy 的统一处理函数
///
/// 注册 Manager
///
///
///
protected void RegisterManager(T manager) where T : IManager
{
Type t = typeof(T);
if (m_ManagerDict.ContainsKey(t) == true)
{
m_ManagerDict[t] = manager;
}
else
{
m_ManagerDict.Add(t, manager);
}
}
///
/// 注册 Server
///
///
///
protected void RegisterServer(T server) where T : IServer
{
Type t = typeof(T);
if (m_ServerDict.ContainsKey(t) == true)
{
m_ServerDict[t] = server;
}
else
{
m_ServerDict.Add(t, server);
}
}
///
/// 获取指定 Manger
/// (可以设置 public 或许更合适)
///
///
///
protected T GetManager() where T : class
{
Type t = typeof(T);
if (m_ManagerDict.ContainsKey(t) == true)
{
return m_ManagerDict[t] as T;
}
else
{
Debug.LogError("GetManager()/ not exist ,t = "+t.ToString());
return null;
}
}
///
/// 获取指定 Server
/// (可以设置 public 或许更合适)
///
///
///
protected T GetServer() where T : class
{
Type t = typeof(T);
if (m_ServerDict.ContainsKey(t) == true)
{
return m_ServerDict[t] as T;
}
else
{
Debug.LogError("GetServer()/ not exist ,t = " + t.ToString());
return null;
}
}
35、在工程中添加 GameManager 脚本(单例),继承BaseGameManager ,主要功能:1)获取场景中相关游戏物体或者 UI根物体,2)new 相关的 Manager 管理类,初始化Init,Update 、和Destroy,3)判断游戏状态,是否暂停、继续、结束
protected override void InitServer(Transform rootTrans, params object[] objs) {
m_ResLoadServer.Init(null);
m_AudioServer.Init(m_WorldTrans, m_ResLoadServer);
}
protected override void InitManager(Transform rootTrans, params object[] objs)
{
m_DataModelManager.Init(null);
m_DrivingDistanceScoreManager.Init(null, m_DataModelManager);
m_UIManager.Init(m_UITrans, m_AudioServer, m_DataModelManager);
m_ObjectPoolManager.Init(null);
m_RoadManager.Init(m_WorldTrans, m_ResLoadServer);
m_NPCManager.Init(m_WorldTrans, m_ResLoadServer, m_ObjectPoolManager);
m_PCCarManager.Init(m_WorldTrans, m_ResLoadServer, m_ObjectPoolManager,m_AudioServer,m_DataModelManager);
m_UIManager.SetOnGamePause(GamePause);
m_UIManager.SetOnGameResume(GameResume);
m_PCCarManager.SetPCCarColliderNPCCarAction(GameOver);
}
public void GamePause()
{
m_RoadManager.GamePause();
m_NPCManager.GamePause();
m_PCCarManager.GamePause();
m_UIManager.GamePause();
m_DrivingDistanceScoreManager.GamePause();
}
public void GameResume()
{
m_RoadManager.GameResume();
m_NPCManager.GameResume();
m_PCCarManager.GameResume();
m_UIManager.GameResume();
m_DrivingDistanceScoreManager.GameResume();
}
public void GameOver()
{
m_RoadManager.GameOver();
m_NPCManager.GameOver();
m_PCCarManager.GameOver();
m_UIManager.GameOver();
m_DrivingDistanceScoreManager.GameOver();
}
36、GameStart 脚本,整个游戏的入口,管理对应 GameManager 的 Awake(),Start(),Update(),OnDestroy() 对应函数功能
public class GameStart : MonoBehaviour
{
private void Awake()
{
GameManager.Instance.Awake(this);
}
// Start is called before the first frame update
void Start()
{
GameManager.Instance.Start();
}
// Update is called once per frame
void Update()
{
GameManager.Instance.Update();
}
private void OnDestroy()
{
GameManager.Instance.Destroy();
}
}
37、在场景中添加 GameObject 空物体,改名为 GameStart,并且挂载 GameStart 脚本
38、运行场景,就会自动生成场景,点击play ,开始生成 NPC 车子和 金币,撞车游戏结束
github 地址:https://github.com/XANkui/UnityMiniGameParadise
的 MGP_007CarRacing2D 工程
游戏的好不好玩,趣味性,视觉化等诸多因素影响,下面简单介绍几个方面拓展游戏的方向,仅做参考
1、可以根据自己需要修改游戏资源,换肤什么的等
2、可以根据需要添加加分特效,音效,背景更多的细节变化等等
3、添加 UI 面板等,美化游戏
4、车子 得分特效添加
5、车子不同分数下的不同状态或者皮肤,抑或速度;
6、添加最高分数保留,和游戏排行榜等;
7、车子的控制方式添加按钮操作,或者语音操作等
8、Road 车子的不同组别,比如春夏秋冬,中国日本埃及不同风格,以增加新奇性
9、等等