Unity3D游戏开发从零单排(四) - 制作一个iOS游戏
提要
此篇是一个国外教程的翻译,尽管有点老,可是适合新手入门。
自己去写代码。debug,布置场景,能够收获到非常多。游戏邦上已经有前面两部分的译文,这里翻译的是游戏的最后一个部分。
欢迎回来
在第一篇中,我们学会了怎么在Unity中搭建游戏的场景,而且设置模型的物理属性。
在第二篇中。我们学会了怎么在unity中使用脚本。而且创建了大部分的游戏逻辑,包括投球和得分!
在这最后一节中,我们将会为用户创建一个菜单系统,而且和GameController进行交互,我们開始吧。
在设备上測试
到眼下为止,我们仅仅在unity内建的模拟器中进行了測试。如今你的项目能够正常执行了。是时候让他在真实的设备里跑跑測试了。
点击菜单条 File->Build Setting.你懊悔看到以下的对话框:
首先,确定你选择对了平台(iOS旁边应该有一个unity的标志。假设没有。选择iOS,然后点Switch Platform)
选择Player Settings。在inspector面板中就能够对游戏进行编译设置。
(译注:Android平台的执行能够參考 - Unity3D游戏开发从零单排(一) - 真机执行)
这里有一大堆能够设置的东西,可是如今你真正须要关心的是保证游戏的屏幕方向是正确的。
在Resoluion and Presentation面板中的devices orientation的下拉选框中选择Landscape-Left,其它保持默认,接下来是Other Settings。
在这个部分你须要输入你的developer Bundle Identifier(和在XCode里面一样)。余下的部分保持默认。
是时候动真格的了
当你设置好编译的依稀选项之后,回到Build Setting对话框,点Build。
Unity将会弹出一个选择项目位置的对话框。当你选择好存储位置后,Unity将会启动XCode执行项目。而且准备好了编译和执行项目了。
注意:不要仅仅在模拟器里跑你的游戏。由于Unity仅仅提供了iOS的设备。在你的移动设备上去跑游戏。
点这里查看跟多相关的内容。
执行成功之后,接下来就是为你的游戏制作一个简单的菜单了。
保存分数
首先下载这个资源,里面包括了一些工具类来处理数据的存储和读取。解压压缩包,将里面的.cs文件拖到项目的Scripts目录中。
数据存储的这些类的实现并不在教程范围内,以下我们写一个小的測试来学习怎样使用它。
在场景中创建一个空的GameObject,将LeadeboardController脚本拖上去。
再在这个GameObject上加入一个脚本组件。命名为LeaderboardControllerTest,測试的内容是存储一些分数,然后再取回来。
你须要在測试类中索引LeaderboardController,加入一个LeaderboarfController的公有数据成员,代码例如以下:
using UnityEngine;
using System.Collections.Generic;
using System.Collections; public class LeaderboardControllerTest : MonoBehaviour {
public LeaderboardController leaderboard; void Start () {
} void Update () {
}
}
注:注意 using System.Collections.Generic;放在类的最上面,这是引入相关的包,在以下的包中你就能够使用Generric中的特有的collections了。
关于Generic包的文档能够參见这里。
使用LeaderboardController中的AddPlayerScore方法来測试:
void Start () {
leaderboard.AddPlayersScore( 100 );
leaderboard.AddPlayersScore( 200 );
}
这样就能够将分数保存在闪存中。即使关掉应用,数据还是能够取回来。
为了取回数据。须要注冊LeaderboardControllers的OnScoresLoaded方法。同一时候还要实现相应的handler函数,代码例如以下.
顺便提一下 - 异步调用的方式能够同意你去扩展LeaderboardController。假设你想的话,让它能够处理一个远程的leaderboard。
void Start () {
leaderboard.OnScoresLoaded += Handle_OnScoresLoaded; leaderboard.AddPlayersScore( 100 );
leaderboard.AddPlayersScore( 200 ); leaderboard.FetchScores();
} public void Handle_OnScoresLoaded( List<ScoreData> scores ){
foreach( ScoreData score in scores ){
Debug.Log ( score.points );
}
}
參数List传回的是ScoreData对象的一个list,ScoreData是一个简单数据对象,将分数进行了了一个简单的封装。
在Handle_OnScoresLoaded方法中将会遍历每一个每一个分数对象。然后将分数打印输出,这就是我们想要的。
就是这样。
接下来要执行看看了:
创建一个新的GameObject。命名为LeaderboardControllder,将LeaderboardController.cs加入上去。
选定LeaderboardContrillerTest对象,将LeaderboardControllder赋给脚本中相应的公有成员。
点执行,看分数是不是在终端现实了!
创建一个简单的菜单
是时候做点让人兴奋的新东西了 —— 你将会学到怎样创建一个游戏菜单!
以下是我们将要做成的样子:
在Unity3D中实现用户界面有3种方式,每一种都各有优缺点。以下就细致讨论一下。
1)GUI
Unity提供了一套自己定义的用户界面,通过MonoBehaviour的回调函数OnGUI来处理事件,Unity支持用皮肤来改变这些界面的外观。
对于那些不是非常在意性能的设备,这是一个非常理想的解决方式,它提供了非常丰富的预设控制。可是考虑到性能问题。自带的GUI不应该在游戏进行时出现。
2)GUITexture和GUIText
Unity提供了两个组件。GUITexture和GUIText,这两个组件能够让你能够在屏幕上呈现2D的图像和文字。
你能够非常方便地通过扩展这两个组件来创建自己的用户界面,相比于GUI 组件。性能上优秀非常多。
3)3D Planes/Texture Altas
假设你要创建一个在游戏顶层现实的菜单(比方HUD),那么这个就是最好的选择。即使这个最麻烦!
:] 可是一旦你创建好了顶层显示的相关类。你就非常easy将它们适用在其它的新的项目中。
3D planes即用一套3D 平面来实现HUD,这些3D平面关联着同一个纹理集合,纹理集合就是将一些细小的纹理拼接在一起。组成一张大的纹理图片(译注:分辨率通常为2
的n次方,方面一次性载入到内存),这个和Cocos2D中的sprite sheet的概念相似!:]
由于各个HUD共享的是同一个材质(指向同一个纹理)。通常仅仅须要一个调用就能够将HUD全部渲染出来。在大部分情况下,你须要为HUD创建一个专用的摄像机,让它们看起来是正交投影的。而不是透视投影的(指的是摄像机的种类)。
在这个游戏中,我们的选择是第一种,Unity自带GUI。除了上面我们提到的它的一些缺点,它最大的优点就是有预置的控制。能够让这篇教程更简单一些。
以下我们首先为主菜单创建皮肤。
然后你完毕渲染主菜单的代码,最后将它链接到GameController上。
听起来是不是非常棒!那即可动吧。少年!:]
皮肤
Unity提供了一种叫Skin的东西来装饰GUI,这个东西能够简单的类比成Html的CSS。
我已经创建好了两个Skin(在第一部分的教程中已经导入到工程里面了),一个是480*320的分辨率。还有一个是960*640的用于是视网膜屏幕的。
以下的图片是480*320的Skin的属性。
Skin的属性文件有非常多的选项,让你能够为你的项目创建独一无二的属性。在这个项目中,你仅仅须要关心字体。
接下来打开GameMenuSmall,将scoreboard字体拖拽到Font属性而且将字体设置到16. 打开GameMenuNormal,将scoreboard字体拖拽到Font属性而且将字体设置到32.下一步就是制作真真的主菜单了。
主菜单
编译执行
像之前做的一样,File->Build Settings.点击buildbutton,開始測试你的第一个游戏吧!
编译并执行XCode项目,你就能够在你的设备上看到一个美丽的而且能够work的菜单了。
主菜单
这个部分主要是GameMenuController的代码,负责渲染主菜单而且处理用户的输入。以下是代码中比較重要的片段,终于都会和游戏连接起来。
创建一个名为GameMenuController的脚本,创建以下的一些变量。
using UnityEngine;
using System.Collections; [RequireComponent (typeof (LeaderboardController))]
public class GameMenuController : MonoBehaviour { public Texture2D backgroundTex;
public Texture2D playButtonTex;
public Texture2D resumeButtonTex;
public Texture2D restartButtonTex;
public Texture2D titleTex;
public Texture2D leaderboardBgTex;
public Texture2D loginCopyTex;
public Texture2D fbButtonTex;
public Texture2D instructionsTex; public GUISkin gameMenuGUISkinForSmall;
public GUISkin gameMenuGUISkinForNormal; public float fadeSpeed = 1.0f;
private float _globalTintAlpha = 0.0f; private GameController _gameController;
private LeaderboardController _leaderboardController;
private List<ScoreData> _scores = null; public const float kDesignWidth = 960f;
public const float kDesignHeight = 640f; private float _scale = 1.0f;
private Vector2 _scaleOffset = Vector2.one; private bool _showInstructions = false;
private int _gamesPlayedThisSession = 0;
}
首先,里面有一系列的公有成员,能够在unity中通过拖拽对象来设置,这些变量是渲染主菜单的各个元素。
接下来的两个变量变量来索引之前创建的那两个Skin。再以下的变量时用来淡入淡出主菜单。我们也须要用私有变量来索引GameController和LeaderboardController来获得分数对象。接下来是一系列用于处理屏幕分辨率的变量,比方iPhone 3GS(480*420)和iPhone4(960*360)。最后是用来管理GameMenuController的组件状态的变量。
加入Awake()和Start()方法。例如以下:
void Awake(){
_gameController = GetComponent<GameController>();
_leaderboardController = GetComponent<LeaderboardController>();
} void Start(){
_scaleOffset.x = Screen.width / kDesignWidth;
_scaleOffset.y = Screen.height / kDesignHeight;
_scale = Mathf.Max( _scaleOffset.x, _scaleOffset.y ); _leaderboardController.OnScoresLoaded += HandleLeaderboardControllerOnScoresLoaded; _leaderboardController.FetchScores();
}
在Start方法中,从LeaderboardController中获得score对象。同一时候,计算出和屏幕分辨率相关的一些比例,让Gui能够自适应,
代码中scale offsets用来保证GUI元素能够正常地缩放。比方,假设一个菜单是960*640的。当前设备的分辨率是480*320,然后你须要做的就是将菜单缩小50%,那么scaleOffset就是0.5. 这么做在简单的多分辨率设备的适配中会非常不错,你不须要反复创建资源。
一旦scores载入完毕,在本地存储起来,将会用来渲染GUI.
public void HandleLeaderboardControllerOnScoresLoaded( List<ScoreData> scores ){
_scores = scores;
}
測试測试!
让我们略微歇息一下,測试測试眼下为止我们所做的东西。
在GameMenuController中加入以下的code:
void OnGUI () {
GUI.DrawTexture( new Rect( 0, 0, Screen.width, Screen.height ), backgroundTex );
if (GUI.Button( new Rect ( 77 * _scaleOffset.x, 345 * _scaleOffset.y, 130 * _scale, 130 * _scale ), resumeButtonTex, GUIStyle.none) ){
Debug.Log( "Click" );
}
}
上面的代码片段主要是OnGUI函数的写法,这个函数的行为相似于Update(),而且提供了对GUI Component的读取。GUI Component提供了一系列的静态方法来实现标准的用户控制,点我去官方站点学习很多其它的OnGui和GUI类。
第一句话是用一张纹理绘制整个屏幕。
GUI.DrawTexture( new Rect( 0, 0, Screen.width, Screen.height ), backgroundTex );
接下来用的推断语句中,GUI.Button方法在指定的一个地方渲染一个Button(用之前我们计算的缩放比例来定位)。
这种方法的返回值和用户是否点击这个Button有关,点了就是true。没点是false.
if (GUI.Button( new Rect ( 77 * _scaleOffset.x, 345 * _scaleOffset.y, 130 * _scale, 130 * _scale ), resumeButtonTex, GUIStyle.none) ){
Debug.Log( "Click" );
}
上面的代码中,假设用户点击了button,console中就会打印Click。
为了測试。将GameMenuController脚本附加到GameController上,将相应的公有变量拖拽上去,例如以下图:
如今測试吧,点击执行,你会开发出现了一个button。点击它你就能够在终端看到打印的信息。
还不赖吧?完毕菜单的第一步就搞定了!:]
使用skins
如今你确定了你的方向找对了,接下来依据屏幕的尺寸来设置skin。用以下的代码来替换OnGUI函数:
if( _scale < 1 ){
GUI.skin = gameMenuGUISkinForSmall;
} else{
GUI.skin = gameMenuGUISkinForNormal;
}
skins能够确保你使用正确的字体大小(依据屏幕尺寸);使用哪一个skin是依据前面计算的_scale值。假设_scale小于1.0就使用small skin。不是的话就是使用normal skin。
显示和隐藏
相比于粗鲁地弹出菜单,用淡入淡出来处理是更好的选择。为了实现淡入淡出。我们须要处理GUI的static 变量 content Color (这回影响到GUI类里绘制的全部内容);
为了处理淡入,你应该慢慢地将_globalTintAlpha的值从0添加到1.然后将它赋给GUI.contenColor变量。将西面的代码加入OnGUI函数:
_globalTintAlpha = Mathf.Min( 1.0f, Mathf.Lerp( _globalTintAlpha, 1.0f, Time.deltaTime * fadeSpeed ) ); Color c = GUI.contentColor;
c.a = _globalTintAlpha;
GUI.contentColor = c;
你须要一些方法来显示和隐藏菜单,创建以下两个方法,Show和Hide:
public void Show(){
// ignore if you are already enabled
if( this.enabled ){
return;
}
_globalTintAlpha = 0.0f;
_leaderboardController.FetchScores();
this.enabled = true;
} public void Hide(){
this.enabled = false;
}
代码没啥好说的。
菜单显示的内容依据游戏的状态不同。内容也不同,比方当游戏结束的时候菜单的内容就和暂停时显示的菜单不一样。
在OnGUI函数中加入以下的代码:
GUI.DrawTexture( new Rect( 0, 0, Screen.width, Screen.height ), backgroundTex ); if( _gameController.State == GameController.GameStateEnum.Paused ){
if (GUI.Button( new Rect ( 77 * _scaleOffset.x, 345 * _scaleOffset.y, 130 * _scale, 130 * _scale ), resumeButtonTex, GUIStyle.none) ){
_gameController.ResumeGame();
} if (GUI.Button( new Rect ( 229 * _scaleOffset.x, 357 * _scaleOffset.y, 100 * _scale, 100 * _scale ), restartButtonTex, GUIStyle.none) ){
_gameController.StartNewGame();
}
} else{
if (GUI.Button( new Rect ( 77 * _scaleOffset.x, 345 * _scaleOffset.y, 130 * _scale, 130 * _scale ), playButtonTex, GUIStyle.none) )
{
if( _showInstructions || _gamesPlayedThisSession > 0 ){
_showInstructions = false;
_gamesPlayedThisSession++;
_gameController.StartNewGame();
} else{
_showInstructions = true;
}
}
}
你应该非常熟悉这些代码,就是依据游戏的状态渲染相应的纹理和button。暂停状态下的两个button分别同意玩家返回和又一次開始:
if (GUI.Button( new Rect ( 77 * _scaleOffset.x, 345 * _scaleOffset.y, 130 * _scale, 130 * _scale ), resumeButtonTex, GUIStyle.none) ){
_gameController.ResumeGame();
} if (GUI.Button( new Rect ( 229 * _scaleOffset.x, 357 * _scaleOffset.y, 100 * _scale, 100 * _scale ), restartButtonTex, GUIStyle.none) ){
_gameController.StartNewGame();
}
注:想知道我是怎么知道那些精确的尺寸的?答案是使用GIMP。
还有一个状态是GameOver,这个时候你须要渲染開始button。
注:你可能注意到了两个变量 _showInstructions 和 _gamesPlayerdThisSession 。
_gamesPlayerThisSession是用来记录当前你玩了多少场游戏的,假设是一次,那么 _showInstructions 就为真。那么我们就能够在玩家開始玩游戏之前给出一些游戏提示。
(译注:这两个变量都作为GameMenuController的私有成员)
是时候測试了
在完毕GameMenuController之前,确保如今的每一个功能都如预期那样工作。一切都设置好了之后。你就能够看到和以下相似的画面了:
完毕GameMenuController
最后要完毕的是标题。提示还有分数。
绘制标题还是提示是依据上面提到的 _showInstructions 标志位;将以下的代码加入在OnGUI方法的最以下:
if( _showInstructions ){
GUI.DrawTexture( new Rect( 67 * _scaleOffset.x, 80 * _scaleOffset.y, 510 * _scale, 309 * _scale ), instructionsTex );
} else{
GUI.DrawTexture( new Rect( 67 * _scaleOffset.x, 188 * _scaleOffset.y, 447 * _scale, 113 * _scale ), titleTex );
}
注意,最后的代码是处理分数板的,OnGUI提供了groups。你能够通过group将东西放在一起。至于某个层(竖着的或者横着的)。以下的代码是绘制leaderboard和一些
简单的button,用于关联facebook和Twitter。然后将全部分数一个个加起来。
将以下的代码加入在OnGUI方法的最以下:
GUI.BeginGroup( new Rect( Screen.width - (214 + 10) * _scale, (Screen.height - (603 * _scale)) / 2, 215 * _scale, 603 * _scale ) ); GUI.DrawTexture( new Rect( 0, 0, 215 * _scale, 603 * _scale ), leaderboardBgTex ); Rect leaderboardTable = new Rect( 17 * _scaleOffset.x, 50 * _scaleOffset.y, 180 * _scale, 534 * _scale );
if( _leaderboardController.IsFacebookAvailable && !_leaderboardController.IsLoggedIn ){
leaderboardTable = new Rect( 17 * _scaleOffset.x, 50 * _scaleOffset.y, 180 * _scale, 410 * _scale );
GUI.DrawTexture( new Rect( 29* _scaleOffset.x, 477* _scaleOffset.y, 156 * _scale, 42 * _scale ), loginCopyTex );
if (GUI.Button( new Rect ( 41 * _scaleOffset.x, 529 * _scaleOffset.y, 135 * _scale, 50 * _scale ), fbButtonTex, GUIStyle.none) )
{
_leaderboardController.LoginToFacebook();
}
}
GUI.BeginGroup( leaderboardTable );
if( _scores != null ){
for( int i=0; i<_scores.Count; i++ ){
Rect nameRect = new Rect( 5 * _scaleOffset.x, (20 * _scaleOffset.y) + i * 35 * _scale, 109 * _scale, 35 * _scale );
Rect scoreRect = new Rect( 139 * _scaleOffset.x, (20 * _scaleOffset.y) + i * 35 * _scale, 52 * _scale, 35 * _scale ); GUI.Label( nameRect, _scores[i].name );
GUI.Label( scoreRect, _scores[i].points.ToString() );
}
}
GUI.EndGroup();
GUI.EndGroup(); }
这就是GameMenuController了,最后还要将GameMenuContrller关联到GameController类上面,将GameController脚本打开。然后实现关联。
声明几个变量:
private GameMenuController _menuController;
private LeaderboardController _leaderboardController;
public Alerter alerter;
在Awake函数中初始化一下:
void Awake() {
_instance = this;
_menuController = GetComponent<GameMenuController>();
_leaderboardController = GetComponent<LeaderboardController>();
}
最明显的变化时GameController中State的处理,用以下的代码替代UpdateStatePlay的部分,后面我们会具体说代码。
public GameStateEnum State{
get{
return _state;
}
set{
_state = value; // MENU
if( _state == GameStateEnum.Menu ){
player.State = Player.PlayerStateEnum.BouncingBall;
_menuController.Show();
} // PAUSED
else if( _state == GameStateEnum.Paused ){
Time.timeScale = 0.0f;
_menuController.Show();
} // PLAY
else if( _state == GameStateEnum.Play ){
Time.timeScale = 1.0f;
_menuController.Hide(); // notify user
alerter.Show( "GAME ON", 0.2f, 2.0f );
} // GAME OVER
else if( _state == GameStateEnum.GameOver ){
// add score
if( _gamePoints > 0 ){
_leaderboardController.AddPlayersScore( _gamePoints );
} // notify user
alerter.Show( "GAME OVER", 0.2f, 2.0f );
}
}
}
代码的意思就如它写的那样,当State为Menu或者Pause,你仅仅要用GameMenuController的show方法来让自己显示就能够了,当state设为Play的时候,用hide方法来隐藏。终于state被设为GameOver的时候。将玩家的分数加入到leaderboard中(就如demo中你写的代码那样)。
最后,注意这段代码有依赖于一个Alerter对象。
所以为了让它跑起来。创建一个空对象,将Alert脚本加入上去。然后将Alerter拖拽到GameController的相应属性上。
编译执行
就像之前的一样,File->Build Setting打开编译对话框,点击编译button来測试终于的游戏!
编译并执行XCode项目。你将会在你的设备上看到一个美腻的菜单。
优化
优化方面的内容能够写成一本书了!即使你认为游戏的表现还能接受,你有考虑过那一大堆ipod touch和iPhone 3G的感受么?你已经花了非常大的力气去完毕一个游戏,你应该不愿意那些持有老设备的玩家认为你的游戏非常卡吧!
以下的一些条款在开发的时候最好牢记在心:
最小化调用绘制函数 —— 尽可能少地调用绘制函数,你能够共享纹理和材质,避免使用透明的shader - 使用mobile shader来替代。限制场景的光源数量,使用贴图组合来实现HUD.
注意有些场景的复杂度 —— 使用优化的模型,意味着模型具有更少的图元。
你通常能够将模型的细节烘培到纹理之中,而不是使用高精度的模型,烘培对于光照相同适用。记住玩家玩的游戏是在非常小的一个屏幕上,非常多细节都会被忽略。
适用假的阴影 —— 动态阴影在iOS中并不能适用,可是能够使用projectors来做成一个假的阴影。唯一可能引起的问题就是projector会调用绘制函数,所以假设可能的话,尽可能用一个带阴影纹理的平面加上一个特粒子Shader来模拟。
警惕在Update/FixexUpdate方法中的不论什么代码 —— 理想情况下,Update和FixedUpdate函数须要每秒跑30到60次。所以在调用这两个函数之前,预处理好不论什么能够做的事情。
同一时候也要注意你加在当中的逻辑,特别是和物理相关的!
关闭全部不使用的东西 —— 假设一个脚本不须要执行,就关掉它,尽管它看起来没那么重要 —— app中全部的一切都会消耗CPU!
尽可能使用最简单的组件 —— 假设你仅仅须要一个组件中非常小的一部分功能,其它大部分都用不着,那么你能够自己实现你最须要的那部分功能而不是直接拿来用。
比方CharacterController就是一个非常诱人的组件,所以最好用Rigidbody来实现你自己的解决方式。
整个开发过程都使用真机来測试 —— 当执行游戏的时候,打开console的debug log窗体。那样就能够看你的app消耗了多少cpu。你须要这样做:在XCode中找到iPhone_Profiler.h文件,而且将ENABLE_INTERNAL_PROFILER 设为1.这样就能够更加具体地看到你的app的执行情况。
若果你有Unity3D Advance版本号,里面有个profiler能够查看脚本中每一个方法消耗的时间。profiler的信息就像以下这样:
帧率能够表示游戏执行的速度。默认的速度哦要么是30。要么60.游戏的平均帧率应该接近这两个值。
draw-call表示当前渲染调用的次数 - 就像前面提到的,通过共享纹理和材质,将这个数字保持得越低越好。
(译注:一个 Draw Call,等于呼叫一次 DrawIndexedPrimitive (DX) or glDrawElements (OGL),等于一个 Batch。
尽可能地降低Drawcall的数量。
IOS设备上建议不超过100。降低的方法主要有例如以下几种:Frustum Culling,Occlusion Culling,Texture Packing。Frustum Culling是Unity内建的,我们须要做的就是寻求一个合适的远裁剪平面;Occlusion Culling。遮挡剔除,Unity内嵌了Umbra。一个非常好OC库。但Occlusion Culling也并非放之四海而皆准的,有时候进行OC反而比不进行还要慢,建议在OC之前先确定自己的场景是否适合利用OC来优化。Texture Packing,或者叫Texture Atlasing,是将同种shader的纹理进行拼合,依据Unity的static batching的特性来降低draw call。
建议使用,但也有弊端,那就是一定要将场景中距离相近的实体纹理进行拼合,否则。拼合后非常可能会添加每帧渲染所需的纹理大小,加大内存带宽的负担。
这也就是为什麽会出现“DrawCall降了,渲染速度也变慢了”的原因)
verts表示当前须要渲染多少顶点。
player-detail能够告诉我们游戏引擎的每一个部分消耗了多少时间。
你还能够做非常多
你能够下载完整的项目。然后再unity中打开。(译注:有点坑爹,非常多bug)
到如今为止,你已经做得非常好了,但我们的旅程远不会到此为止!:] 保持这股势头,你将会成为一个unity高手,当然,这也须要很多其它的各方面的技能。
以下是一些扩展游戏的建议:
● 加入音效。
声音是交互内容的非常重要的一个内容,所以花些时间去找些音乐和音效加到游戏中去吧。
● 关联Facebook。
● 添加多玩家模式,让玩家们能够同一个设备上竞技。轮流投球。
● 添加角色让玩家选择;
● 支持多种手势。不同的手势代表不同的投篮方式;
● 加入带奖励的篮球,这种篮球有不一样的物理属性,比方不同的重量。
● 加入新的关卡。每一个关卡有新的挑战!
这些足够让你忙一阵了!
希望你喜欢这个系列的教程,而且能够学习一些unity的仅仅是。我希望看到你们将来制作出属于自己的unity app!
资源下载
參考
Intermediate Unity 3D for iOS: Part 3/3 - http://www.raywenderlich.com/20420/beginning-unity-3d-for-ios-part-3
其它
原工程的代码导入到unity3d4.3无法正常执行,个人又一次编写了一个android和pc上都能执行的完整版本号,点我下载。
Unity3D游戏开发从零单排(四) - 制作一个iOS游戏的更多相关文章
- Unity3D游戏开发从零单排(三) - 极速创建狂拽酷炫的游戏地形
提要 在Unity工作流程内,地形是一个必不可少的重要元素.不论是游戏或虚拟现实都会使用到各种类型的地形效果,在这个教学中我们须要了解到地形的制作基本概念与,当中对于Unity的地形操作部分须要大量的 ...
- Unity3D游戏开发从零单排(五) - 导入CS模型到Unity3D
游戏动画基础 Animation组件 Animation组件是对于老的动画系统来说的. 老的动画形同相应的动画就是clip,每一个运动都是一段单独的动画,使用Play()或CrossFade(),直接 ...
- Unity3D游戏开发从零单排(六) - 人物运动及攻击连击
提要 今天要实现的是一个简单人物控制器. 包括用w,a,s,d来控制人物上下左右跑动,鼠标左击发出连招,都是基于老的lagacy的动画.尽管unity3d自带有charactorcontroller, ...
- C# Unity游戏开发——Excel中的数据是如何到游戏中的 (四)2018.4.3更新
本帖是延续的:C# Unity游戏开发--Excel中的数据是如何到游戏中的 (三) 最近项目不算太忙,终于有时间更新博客了.关于数据处理这个主题前面的(一)(二)(三)基本上算是一个完整的静态数据处 ...
- 《C++游戏开发》笔记十四 平滑过渡的战争迷雾(二) 实现:真正的迷雾来了
本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9712321 作者:七十一雾央 新浪微博:http:/ ...
- cocos2d-x 游戏开发之有限状态机(FSM) (四)
cocos2d-x 游戏开发之有限状态机(FSM) (四) 虽然我们了解了FSM,并且可以写自己的FSM,但是有更好的工具帮我们完成这个繁琐的工作.SMC(http://smc.sourceforge ...
- 用Phaser来制作一个html5游戏——flappy bird (一)
Phaser是一个简单易用且功能强大的html5游戏框架,利用它可以很轻松的开发出一个html5游戏.在这篇文章中我就教大家如何用Phaser来制作一个前段时间很火爆的游戏:Flappy Bird,希 ...
- [置顶] cocos2d-x 3.0游戏开发xcode5帅印博客教学 003.[HoldTail]游戏世界以及背景画面
cocos2d-x 3.0游戏开发xcode5帅印博客教学 003.[HoldTail]游戏世界以及背景画面 写给大家的前言,在学习cocos2d-x的时候自己走了很多的弯路,也遇到了很多很多问题,不 ...
- 用Phaser来制作一个html5游戏——flappy bird (二)
在上一篇教程中我们完成了boot.preload.menu这三个state的制作,下面我们就要进入本游戏最核心的一个state的制作了.play这个state的代码比较多,我不会一一进行说明,只会把一 ...
随机推荐
- EasyUI - Layout 布局控件
效果: Html代码: <div id="cc" class="easyui-layout" style="width: 600px; heig ...
- 基于visual Studio2013解决面试题之0807strstr函数
题目
- mysql 结果集合切换
结果集A: 转换成为结果集B: mysql中实现例如以下: SELECT a.biz_date, CASE WHEN a.`event` = 'downClick' THEN a.uv END AS ...
- JAVA中字符串比較equals()和equalsIgnoreCase()的差别
.使用equals( )方法比較两个字符串是否相等.它具有例如以下的一般形式: boolean equals(Object str) 这里str是一个用来与调用字符串(String)对象做比較的字符串 ...
- View实现涂鸦、撤销以及重做功能
import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import j ...
- Linux - VIM(VI)编辑器
VIM(VI)编辑器 简介: VI是一个命令行界面下的文本编辑工具,最早在1976年BILL Joy开发,当时的名字叫做ex.VI支持绝大多数操作系统(最早在BSD上发布),并且功能已经十分强大. 1 ...
- WITH+HInt MATERIALIZE 不见得有效
那个要多次调用才需要物化的. 只调用一次,物化没用 MATERIALIZE 语法:MATERIALIZE 描述:指示优化器将内联视图实体化————执行过程中会创建基于视图的临时表. with dd ...
- Android颜色转换工具类ColorUtil
项目中需要根据ScrollView的滚动距离来动态设置Topbar的背景透明度,网上有类似的开源库FadingActionBar,使用的是ActionBar做的.而我的项目中并没有使用ActionBa ...
- ASP.NET - GridView实现点击编辑列
加载: 点击编辑: 数据库设计: 前端代码: DataKeyNames="ID" 设置点击“编辑”选项的时候,要获取的值,一般获取ID主键,便于修改数据. AutoGenerat ...
- jsp:include怎么设置才能正确显示包含的页面呢
1.项目的所有jsp都放在WEB-INF文件夹之下,使用的是SpirngMVC进行了过滤,jsp:include只能引入WEB-INF外部的jsp文件,对于改变后缀显示为htm的jsp的WEB-INF ...