《Android 3D游戏开发技术宝典——OpenGL ES 2.0》——2.1节游戏中的音效

本节书摘来自异步社区《Android 3D游戏开发技术宝典——OpenGL ES 2.0》一书中的第2章,第2.1节游戏中的音效,作者 吴亚峰,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.1 游戏中的音效
Android 3D游戏开发技术宝典——OpenGL ES 2.0
一款好游戏,除了具备优质的画面和较高的可玩性之外,还应该有出色的音效。音效一般指的是游戏中发生特定行为或进行特定操作时播放的效果音乐或为了渲染整体气氛播放的背景音,如远处隆隆的炮声、怪物死亡的惨叫声、由远而近的脚步声等。

通过开发人员精心准备的声音特效,结合游戏的场景,可以渲染出一种紧张刺激的氛围,使玩家产生身临其境的感觉。这就像电影中的声音特效一样,假如没有了合适的音效,那么游戏和电影一样,真实感会大打折扣。

提示 按照作用的不同,可以将音效划分为即时音效和背景音乐。两种音效在Android中的实现技术是不同的,本节将向读者详细介绍两种音效在Android中的具体实现。

2.1.1 游戏中的即时音效
游戏中有时需要根据情况播放即时音效,如枪炮声、碰撞声等。即时音效的特点是短暂、可以重复、可以同时播放。由于Android提供的MediaPlayer(媒体播放器)会占用大量的系统资源,而且播放时还需要进行缓冲,有较大的时延,因此使用MediaPlayer无法实现即时音效。

Android系统的设计者也考虑到了这个问题,为即时音效的实现提供了一个专门的类——SoundPool。SoundPool类用于管理和播放应用程序中的声音资源,使用该类时首先需要通过该类将声音资源加载到内存中,然后在需要即时音效的地方播放即可,其几乎没有时延,可以满足游戏实时性的需要。

提示 由于SoundPool设计的初衷是用于无时延地播放游戏中的短促音效,因此实际开发中应该只将长度小于7s的声音资源放进SoundPool,否则可能加载失败或内存占用过大。
SoundPool类的构造器及常用方法如表2-1所列。


9b61a684e462874ea343899dfa12c9262f06e79e

2.1.2 即时音效的一个案例
了解了SoundPool类的基本操作方法之后,接下来就可以开发游戏中用到的即时音效了。本小节将向读者展示一个播放和停止即时音效的简单案例,其主要功能为,通过SoundPool声音池技术来实现一个即时音效的播放和停止,运行效果如图2-1和图2-2所示。

了解了本案例的运行效果后,接下来对其具体开发步骤进行介绍,具体如下所列。

(1)首先在Eclipse中新建名称为Sample2_1的项目,然后在项目目录下的res文件夹下新建raw文件夹。接着将需要被播放的短促音效对应的音频文件musictest.ogg复制到raw文件夹下,如图2-3所示。


5a6ca0c6bfd51ded47291957e433acba388cd632


9a64a21d7dc15da7b6913cae2aa78ac826c8b49e

提示 一般在Android手机平台上使用的即时音效文件越小越好,这有助于提高游戏的整体性能。对于同一个音效文件,在不改变其时长的情况下,可以采用降低采样率(如降低到16Kbit/s)或由立体声改为单声道的方式来缩小体积。
(2)准备好声音资源后,接下来进行本案例中Sample2_1_Activity类的开发。该类中使用了声音池技术实现了即时音效的播放,其代码如下。

1 package com/bn/pp1;         //声明包
2 import java.util.HashMap;        //引入相关类
3 ……//此处省略了部分类的引入代码,读者可自行查看随书光盘的源代码
4 import android.widget.Toast;       //引入相关类
5 public class Sample2_1_Activity extends Activity {
6  SoundPool sp;          // 声明SoundPool的引用
7  HashMap<Integer, Integer> hm;       // 声明HashMap来存放声音文件
8  int currStreamId;         // 当前正播放的streamId
9  @Override
10             // 重写onCreate方法
11  public void onCreate(Bundle savedInstanceState) {
12   super.onCreate(savedInstanceState);
13   setContentView(R.layout.main);    // 跳转到主界面
14   initSoundPool();        // 初始化声音池的方法
15   Button b1 = (Button) this.findViewById(R.id.Button01); // 获取播放按钮
16   b1.setOnClickListener       // 为播放按钮添加监听器
17   (new OnClickListener() {
18    @Override
19    public void onClick(View v) {
20     playSound(1, 0);      // 播放1号声音资源,且播放一次
21             // 提示播放即时音效
22     Toast.makeText(getBaseContext(), "播放即时音效", Toast.LENGTH_SHORT)
23       .show();
24    }
25   });
26   Button b2 = (Button) this.findViewById(R.id.Button02); // 获取停止按钮
27   b2.setOnClickListener       // 为停止按钮添加监听器
28   (new OnClickListener() {
29    @Override
30    public void onClick(View v) {
31     sp.stop(currStreamId);    // 停止正在播放的某个声音
32             // 提示停止播放
33     Toast.makeText(getBaseContext(), "停止播放即时音效", 
34       Toast.LENGTH_SHORT)
35       .show();
36    }
37   });}
38             // 初始化声音池的方法
39  public void initSoundPool() {
40   sp = new SoundPool(4, AudioManager.STREAM_MUSIC, 0); // 创建SoundPool对象
41   hm = new HashMap<Integer, Integer>();   // 创建HashMap对象
42         // 加载声音文件musictest并且设置为1号声音放入hm中
43   hm.put(1, sp.load(this, R.raw.musictest, 1)); 
44  }
45             // 播放声音的方法
46  public void playSound(int sound, int loop) { // 获取AudioManager引用
47   AudioManager am = (AudioManager) this
48     .getSystemService(Context.AUDIO_SERVICE);
49             // 获取当前音量
50   float streamVolumeCurrent = am
51     .getStreamVolume(AudioManager.STREAM_MUSIC);
52             // 获取系统最大音量
53   float streamVolumeMax = am
54     .getStreamMaxVolume(AudioManager.STREAM_MUSIC);
55             // 计算得到播放音量
56   float volume = streamVolumeCurrent / streamVolumeMax;
57          // 调用SoundPool的play方法来播放声音文件
58   currStreamId = sp.play(hm.get(sound), volume, volume, 1, loop, 1.0f);
59 }}

第6-8行为声明所用到的SoundPool和HashMap对象的引用,其中SoundPool用来加载、播放、停止音效;HashMap用来管理音效id。currStreamId为当前正播放的streamId,用于对正在播放的音效进行管理。
第14行调用initSoundPool方法来对声音池进行初始化。
第15-37行为播放和停止按钮添加监听器,并在单击操作时显示Toast进行提示。
第39-44行为初始化声音池的方法,其首先创建SoundPool对象,然后加载音效文件到声音池并将生成的音效id存储进HashMap中。
第46-59行为播放声音的方法,其中调用SoundPool的play方法来实现即时音效的播放。

提示 通过以上的案例,读者可以看出使用SoundPool播放即时音效是非常简单的。今后的游戏开发中,只要是游戏中的即时音效都应该用此方式来实现。

2.1.3 背景音乐播放技术
背景音乐也可以采用前一小节的声音池技术,在播放背景音乐的时候,只需要把loop播放次数参数设置成-1进行无限循环即可。但由于SoundPool只适合播放不大于7秒的音效文件,限制较大。而背景音乐对时延并不敏感,因此在实际的游戏开发中,时长较长的背景音乐一般采用媒体播放器(MediaPlayer)来进行播放。

要想很好地使用MediaPlayer进行音/视频文件的播放,首先必须要熟悉MediaPlayer的生命周期。这样不仅有利于开发人员开发出更加合理的代码,而且可以达到充分利用系统资源的目的。

1.MediaPlayer的生命周期
MediaPlayer的生命周期包括10种状态,每种状态下可以调用相应的方法来实现音/视频文件的管理或播放。其各个状态及状态间的关系可以用一个简单的流程图来表示,如图2-4所示。


df24291c41c20298a310875d557d9c53de01f6b1

Idle 状态。
使用new方法创建一个MediaPlayer对象或者调用了其reset方法时,该MediaPlayer对象处于idle状态。

但通过两种不同方式进入的idle状态还是有些区别的,主要体现为:如果在这个状态下调用了getDuration等方法,若是通过reset方法进入idle状态的话会触发OnErrorListener.onError,并且MediaPlayer会进入Error状态;如果是新创建的MediaPlayer对象,则并不会触发onError,也不会进入Error状态。

End状态。
通过release方法可以进入End状态,只要MediaPlayer对象不再被使用,就应当尽快将其通过release方法释放掉,以释放其占用的软、硬件资源,这其中有些资源是互斥的(相当于临界资源)。如果MediaPlayer对象进入了End状态,则不会再进入其他任何状态了。

Initialized 状态。
这个状态比较简单,MediaPlayer调用setDataSource方法就进入了Initialized状态,表示此时要播放的文件已经设置好了。

Prepared 状态。
初始化完成之后还需要通过调用prepare或prepareAsync方法进行准备,这两个方法一个是同步的,一个是异步的。只有进入了Prepared状态,才表明MediaPlayer到目前为止都工作正常,可以进行音乐文件的播放。

Preparing 状态。
这个状态比较容易理解,主要是与prepareAsync异步准备方法配合,如果异步准备完成,会触发OnPreparedListener.onPrepared,进而进入Prepared状态。

Started 状态。
MediaPlayer准备完成后,通过调用start方法,将进入Started状态。所谓Started状态,也就是播放中状态,开发中可以使用isPlaying方法测试MediaPlayer是否处于Started状态。

如果播放完毕,而又设置了循环播放,则MediaPlayer仍然会处于Started状态。类似的,如果在该状态下MediaPlayer调用了seekTo或者start方法均可以让MediaPlayer停留在Started状态。

Paused 状态。
Started状态下调用pause方法可以暂停播放,从而进入Paused状态。MediaPlayer暂停后再次调用start方法则可以继续进行播放,并转到Started状态。暂停状态时可以调用seekTo方法,这是不会改变状态的。

Stop 状态。
Started或Paused状态下均可调用stop方法停止播放并进入Stop状态,而处于Stop状态的MediaPlayer要想重新播放,需要通过调用prepareAsync或prepare方法返回到先前的Prepared状态重新开始才可以。

PlaybackCompleted状态。
文件正常播放完毕,而又没有设置循环播放的话就进入该状态,并会触发OnCompletionListener接口中的onCompletion方法。此时可以调用start方法重新从头播放文件,也可以调用stop方法停止播放,或者调用seekTo方法来重新定位播放位置。

Error状态。
由于某种原因MediaPlayer出现了错误,则会触发OnErrorListener.onError回调方法,此时MediaPlayer即进入Error状态。及时捕捉并妥善处理这些错误是很重要的,这可以帮助应用程序及时释放相关的软、硬件资源,也可以改善用户体验。

如果MediaPlayer进入了Error状态,可以通过调用reset方法来恢复,使得MediaPlayer重新返回到Idle状态。

提示 从上述对生命周期的介绍中可以看出,某些情况发生时MediaPlayer会回调特定监听接口中的事件处理方法。若读者在开发中希望使用回调,则需要首先向MediaPlayer注册实现了指定监听接口的监听器。例如,可以使用setOnErrorListener方法注册实现了OnErrorListener接口的监听器,当MediaPlayer进入Error状态时监听器中的onError方法就会被回调。
2.AudioManager类
AudioManager类在Android系统中主要用来进行音/视频播放时的音量控制,使用时的基本步骤如下所列。

首先可以调用Activity对象的getSystemService(Context.AUDIO_SERVICE)方法获取AudioManager对象。
然后再调用AudioManager类中的相关方法进行音量控制。
AudioManager类中的常用方法如表2-2所列。


59a8e4eeb3c6f5de4a12efcdab312036b63ab9c3

提示 MediaPlayer类还可以对视频文件进行操作,由于本书只介绍与游戏音效相关的功能,因此不再对其进行介绍,有兴趣的读者可以自行查阅相关资料。

2.1.4 简易音乐播放器的实现
了解了MediaPlayer和AudioManager类的基本操作方法之后,就可以对游戏的背景音乐功能进行开发了。本小节将通过这两个类来实现一个简易的音乐播放器,其主要功能为对手机SD卡中的音乐文件进行播放,运行效果如图2-5所示。

提示 图2-5中从左到右分别为案例运行后,依次单击“播放音乐”按钮、“暂停播放”按钮、“增大音量”按钮后的效果图。停止播放音乐和减小音量的效果与图2-5中的已有效果类似,这里没有给出,请读者自行运行本案例进行查看。
了解了案例的具体运行效果后,接下来就介绍案例的具体开发步骤,具体如下所列。

(1)首先需要准备好要播放的音乐文件,本案例中使用的是著名的高山流水古曲,文件名为“gsls.mp3”。准备完音乐文件后,将该音乐文件通过DDMS导入到模拟器或真机的SD卡中,如图2-6所示。


d99bdd94dbce3c23f04ff54cfc7d08db0db94aba

提示 高山流水曲的音乐资源文件见随书光盘中源代码/第2章目录下的gsls.mp3。导入时直接用鼠标光标将文件拖曳到DDMS下“File Explorer”面板中的“sdcard”目录下即可。
(2)音乐资源在SD卡中放置完成后,接下来进行本案例中Sample2_2_Activity类的开发。该类使用了MediaPlayer实现了背景音乐的播放,具体代码如下。

1 package com.bn.pp2;         //声明包
2 import java.io.IOException;       //引入相关类
3 ……//此处省略了部分类的引入代码,读者可自行查看随书光盘的源代码
4 import android.widget.Toast;       //引入相关类
5 public class Sample2_2_Activity extends Activity {
6  MediaPlayer mp;          // 声明MediaPlayer的引用
7  AudioManager am;          // 声明AudioManager的引用
8  private int maxVolume;        // 最大音量值   
9  private int currVolume;        // 当前音量值   
10  private int stepVolume;       // 每次调整的音量幅度 
11  @Override
12  public void onCreate(Bundle savedInstanceState)  // 重写onCreate方法
13  {
14   super.onCreate(savedInstanceState);
15   setContentView(R.layout.main);    // 跳转到主界面
16   mp = new MediaPlayer();   // 创建MediaPlayer实例对象
17   try {
18    mp.setDataSource("/sdcard/gsls.mp3"); //为MediaPlayer设置要播放文件资源
19    mp.prepare();    // MediaPlayer进行缓冲准备
20   }
21   catch (Exception e) {
22    e.printStackTrace();
23   }
24          // 获取AudioManager对象引用
25   am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); 
26          // 获取最大音乐音量   
27   maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);    
28          // 每次调整的音量大概为最大音量的1/6   
29   stepVolume = maxVolume / 6;  
30   Button bstart = (Button) this.findViewById(R.id.Button01); // 获取开始按钮
31   bstart.setOnClickListener   // 为开始按钮添加监听器
32   (new OnClickListener() {
33    @Override
34    public void onClick(View v) {
35     mp.start();    // 调用MediaPlayer的start方法来播放音乐
36     Toast.makeText(getBaseContext(),“开始播放‘高山流水曲’”,
37       Toast.LENGTH_LONG).show();
38    }
39   });
40   Button bpause = (Button) this.findViewById(R.id.Button02); // 获取暂停按钮
41   bpause.setOnClickListener   // 为暂停按钮添加监听器
42   (new OnClickListener() {
43    @Override
44    public void onClick(View v) {
45     mp.pause();    // 调用MediaPlayer的pause方法暂停播放音乐
46     Toast.makeText(getBaseContext(), "暂停播放'高山流水曲'",
47       Toast.LENGTH_LONG).show();
48    }
49   });
50   Button bstop = (Button) this.findViewById(R.id.Button03); // 获取停止按钮
51   bstop.setOnClickListener       // 为停止按钮添加监听器
52   (new OnClickListener() {
53    @Override
54    public void onClick(View v) {
55     mp.stop();    // 调用MediaPlayer的stop方法停止播放音乐
56     try {
57      mp.prepare();     //进入准备状态
58     } catch (IllegalStateException e) {  //捕获异常
59      e.printStackTrace();
60     } catch (IOException e) {    //捕获异常
61      e.printStackTrace();
62     }
63     Toast.makeText(getBaseContext(), "停止播放'高山流水曲'",
64       Toast.LENGTH_LONG).show();
65    }
66   });
67   Button bUp = (Button) this.findViewById(R.id.Button04); // 获取增大音量按钮
68   bUp.setOnClickListener      // 为增大音量按钮添加监听器
69   (new OnClickListener() {
70    @Override
71    public void onClick(View v) {
72             // 获取当前音量  
73     currVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);    
74             //增加音量,但不超过最大音量值
75     int tmpVolume = currVolume + stepVolume;  //临时音量
76     currVolume = tmpVolume < maxVolume ? tmpVolume:maxVolume;
77     am.setStreamVolume(AudioManager.STREAM_MUSIC, currVolume,
78       AudioManager.FLAG_PLAY_SOUND);
79     Toast.makeText(getBaseContext(), "增大音量",
80       Toast.LENGTH_SHORT).show();
81    }
82   });
83   Button bDown = (Button) this.findViewById(R.id.Button05); // 获取减小音量按钮
84   bDown.setOnClickListener       // 为减小音量按钮添加监听器
85   (new OnClickListener() {
86    @Override
87    public void onClick(View v) {
88              // 获取当前音量  
89     currVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);    
90              //减小音量,但不小于0
91     int tmpVolume = currVolume - stepVolume;  //临时音量
92     currVolume = tmpVolume > 0 ? tmpVolume:0;  
93     am.setStreamVolume(AudioManager.STREAM_MUSIC, currVolume,
94       AudioManager.FLAG_PLAY_SOUND);
95     Toast.makeText(getBaseContext(), "减小音量",
96       Toast.LENGTH_SHORT).show();
97    }
98 });}}

第6-10行为声明需要用到的MediaPlayer和AudioManager对象的引用、最大音量值、当前音量值及每次调整的音量幅度等。
第16-23行获取了MediaPlayer类对象的引用,设置了音乐资源的路径,并调用prepare方法进入了准备状态。
第24-29行获取了AudioManager类对象的引用,同时获取了音乐播放的最大音量值,并设置每次调整的音量幅度为最大值的1/6。
第30-98行分别获取了开始按钮、暂停按钮、停止按钮以及增大和减小音量按钮对象的引用,并分别为这些按钮添加了监听器。这样在用户单击这些按钮时程序就能完成指定的功能了,例如,按下开始按钮开始播放音乐、按下减小音量按钮减小播放音量等。

第1章 新一代的王者——android概览 1 1.1 智能手机市场现状 1 1.1.1 五大智能手机操作系统 1 1.1.2 智能手机市场的新星 2 1.2 android平台的特点及未来的趋势 3 1.2.1 全新理念带来的体验风暴 3 1.2.2 国手机市场的主导性作用 4 1.2.3 手机3d游戏和应用增长迅速 4 1.3 如何搭建android开发环境 4 1.3.1 sdk的安装及环境配置 5 1.3.2 eclipse集成开发环境的搭建 7 1.3.3 创建并启动模拟器 9 1.4 hello android应用程序的开发 11 1.4.1 第一个android应用程序 12 1.4.2 android应用程序的项目结构 14 1.4.3 android应用程序的调试 16 1.4.4 实际设备的联机调试 18 1.5 android应用程序运行的机制 19 1.5.1 应用程序的系统架构 19 .1.5.2 应用程序框架 20 1.5.3 android运行时 20 1.5.4 系统库 21 1.5.5 底层linux内核 21 1.6 本章小结 22 第2章 游戏开发相关android基础知识 23 2.1 游戏音效 23 2.1.1 游戏的即时音效 23 2.1.2 即时音效的一个案例 24 2.1.3 背景音乐播放技术 27 2.1.4 简易音乐播放器的实现 29 2.2 简单数据的存储——preferences 33 2.2.1 preferences简介 33 2.2.2 preferences实现访问时间的记录 33 2.3 手机自带数据库——sqlite 34 2.3.1 初识sqlite 35 2.3.2 sqlite数据库的基本操作 35 2.3.3 sqlite数据库的简单案例 37 2.3.4 使用contentprovider组件共享数据 40 2.3.5 使用contentresolver获取分享数据 42 2.4 文件i/o 43 2.4.1 访问sd卡的文件 44 2.4.2 访问手机的文件夹 46 2.4.3 读取assets文件夹下的内容 48 2.5 2d动画的开发 50 2.5.1 surfaceview用法简介 50 2.5.2 使用surfaceview实现2d动画 50 2.6 socket网络通信 56 2.6.1 socket开发基本知识 56 2.6.2 服务器端 57 2.6.3 客户端 58 2.7 蓝牙通信 59 2.7.1 蓝牙通信的基本知识 60 2.7.2 聊天案例概览 60 2.7.3 聊天案例的开发过程 62 2.8 本章小结 75 第3章 初识opengl es 2.0 77 3.1 opengl es 2.0概览 77 3.1.1 opengl es 2.0简介 77 3.1.2 初识opengl es 2.0应用程序 80 3.2 着色器与渲染管线 89 3.2.1 opengl es 1.x的渲染管线 89 3.2.2 opengl es 2.0的渲染管线 94 3.2.3 opengl es立体物体的构建 97 3.3 主流android手机gpu大pk 98 3.3.1 手机gpu四大家族对比 99 3.3.2 主流gpu的性能参数比较 103 3.4 本章小结 104 第4章 着色语言shading language 105 4.1 着色语言概述 105 4.2 着色语言基础 106 4.2.1 数据类型概述 106 4.2.2 数据类型的基本使用 110 4.2.3 运算符 112 4.2.4 类型转换 114 4.2.5 限定符 115 4.2.6 流程控制 119 4.2.7 函数的声明与使用 121 4.2.8 片元着色器浮点变量精度的指定 122 4.2.9 程序的基本结构 123 4.3 特殊的内建变量 123 4.3.1 顶点着色器的内建变量 124 4.3.2 片元着色器的内建变量 124 4.4 着色语言的内置函数 125 4.4.1 角度转换与三角函数 126 4.4.2 指数函数 127 4.4.3 常见函数 127 4.4.4 几何函数 130 4.4.5 矩阵函数 132 4.4.6 向量关系函数 133 4.4.7 纹理采样函数 134 4.4.8 微分函数 135 4.5 本章小结 135 第5章 投影及各种变换 136 5.1 摄像机的设置 136 5.2 两种投影方式 137 5.2.1 正交投影 137 5.2.2 透视投影 145 5.3 各种变换 148 5.3.1 基本变换的相关数学知识 148 5.3.2 平移变换 148 5.3.3 旋转变换 152 5.3.4 缩放变换 153 5.3.5 基本变换的实质 155 5.4 绘制方式 156 5.4.1 各种绘制方式概览 156 5.4.2 点与线段绘制方式 157 5.4.3 三角形条带与扇面绘制方式 159 5.4.4 顶点法与索引法 165 5.5 设置合理的视角 167 5.6 卷绕和背面剪裁 173 5.6.1 基本知识 173 5.6.2 一个简单的案例 174 5.7 本章小结 176 第6章 光照 177 6.1 曲面物体的构建 177 6.1.1 球体构建的基本原理 177 6.2.2 案例效果概览 178 6.2.3 开发步骤 179 6.2 基本光照效果 182 6.2.1 光照的基本模型 182 6.2.2 环境光 183 6.2.3 散射光 185 6.2.4 镜面光 190 6.2.5 三种光照通道的合成 194 6.3 定位光与定向光 196 6.4 点法向量和面法向量 199 6.5 光照的每顶点计算与每片元计算 202 6.6 本章小结 204 第7章 纹理映射 205 7.1 初识纹理映射 205 7.1.1 基本原理 205 7.1.2 纹理映射的简单案例 206 7.2 纹理拉伸 212 7.2.1 两种拉伸方式概览 212 7.2.2 不同拉伸方式的案例 214 7.3 纹理采样 217 7.3.1 纹理采样概述 217 7.3.2 最近点采样 218 7.3.3 线性纹理采样 219 7.3.4 min与mag采样 220 7.3.5 不同纹理采样方式的案例 221 7.4 mipmap纹理技术 226 7.5 多重纹理与过程纹理 227 7.5.1 案例概览 227 7.5.2 将2d纹理映射到球面上的策略 228 7.5.3 案例的场景结构 229 7.5.4 开发过程 230 7.6 本章小结 238 第8章 3d基本形状的构建 239 8.1 圆柱体 239 8.1.1 顶点原始位置的生成 239 8.1.2 案例的开发 241 8.2 圆锥体 244 8.2.1 顶点原始位置的生成 244 8.2.2 案例的开发 246 8.3 圆环体 248 8.3.1 顶点原始位置的生成 248 8.3.2 案例的开发 249 8.4 螺旋管 251 8.4.1 顶点原始位置的生成 252 8.4.2 案例的开发 252 8.5 几何球 253 8.5.1 顶点原始位置的生成 254 8.5.2 案例的开发 255 8.6 足球碳分子模型的搭建 262 8.6.1 搭建的基本原理 262 8.6.2 案例的开发 264 8.7 贝塞尔曲线及旋转面 270 8.7.1 三维旋转曲面的生成 270 8.7.2 贝塞尔曲线 270 8.7.3 bezier曲线生成工具 272 8.7.4 印度古典建筑场景的开发 274 8.8 本章小结 276 第9章 3d模型加载 277 9.1 obj模型文件概览 277 9.1.1 obj文件的格式 277 9.1.2 用3dmax设计3d模型 278 9.2 加载obj文件 279 9.2.1 加载仅有顶点坐标与面数据的obj文件 279 9.2.2 加载后自动计算面法向量 283 9.2.3 加载后自动计算平均法向量 286 9.2.4 加载纹理坐标 289 9.3 本章小结 292 第10章 混合与雾 293 10.1 混合技术 293 10.1.1 混合基本知识 293 10.1.2 源因子和目标因子 294 10.1.3 简单混合效果案例 295 10.2 地月系云层效果的实现 297 10.3 雾 300 10.3.1 雾的原理与优势 300 10.3.2 雾的简单实现 301 10.4 本章小结 304 第11章 常用3d开发技巧 305 11.1 标志板 305 11.1.1 案例效果与基本原理 305 11.1.2 开发步骤 306 11.2 灰度图地形 310 11.2.1 基本原理 311 11.2.2 普通灰度图地形 311 11.2.3 过程纹理地形 314 11.2.4 mipmap地形 317 11.3 天空盒与天空穹 318 11.3.1 天空盒 318 11.3.2 天空穹 320 11.3.3 天空盒与天空穹的使用技巧 321 11.4 镜像技术 322 11.4.1 镜像基本原理 322 11.4.2 基本效果案例 322 11.4.3 升级效果案例 325 11.5 动态文本输出 327 11.5.1 案例效果与基本原理 327 11.5.2 具体开发步骤 328 11.6 非真实感绘制 330 11.6.1 案例效果与基本原理 330 11.6.2 具体开发步骤 331 11.7 本章小结 332 第12章 几种剪裁与测试 333 12.1 剪裁测试 333 12.1.1 基本原理与核心代码 333 12.1.2 一个主次视角的简单案例 334 12.2 alpha测试 335 12.2.1 alpha测试基本原理 335 12.2.2 一个椭圆窗口的案例 335 12.3 模板测试 337 12.3.1 基本原理 337 12.3.2 一个简单的案例 340 12.4 任意剪裁平面 341 12.4.1 基本原理 341 12.4.2 茶壶被任意平面剪裁的案例 342 12.5 本章小结 344 第13章 顶点着色器的妙用 345 13.1 飘扬的旗帜 345 13.1.1 基本原理 345 13.1.2 开发步骤 346 13.2 扭动的软糖 349 13.2.1 基本原理 349 13.2.2 开发步骤 350 13.3 风吹椰林场景的开发 351 13.3.1 椰子树随风摇摆的基本原理 351 13.3.2 开发步骤 352 13.4 展翅飞翔的雄鹰 356 13.4.1 基本原理 356 13.4.2 开发步骤 357 13.5 二维扭曲 359 13.5.1 基本原理 359 13.5.2 开发步骤 360 13.6 opengl es 1.x与opengl es 2.0实现方案的对比 363 13.7 本章小结 364 第14章 片元着色器的妙用 365 14.1 程序纹理技术 365 14.1.1 砖块着色器 365 14.1.2 沙滩球着色器 367 14.2 数字图像处理 368 14.2.1 卷积的基本知识 369 14.2.2 平滑过滤 369 14.2.3 边缘检测 371 14.2.4 锐化处理 372 14.2.5 浮雕效果 373 14.2.6 图像渐变 374 14.3 分形着色器 375 14.3.1 曼德布罗集简介 375 14.3.2 曼德布罗集着色器的实现 376 14.3.3 将曼德布罗集纹理应用到实际物体上 378 14.3.4 茱莉亚集着色器的实现 379 14.4 本章小结 380 第15章 真实光学环境的模拟 381 15.1 投影贴图 381 15.1.1 案例效果与基本原理 381 15.1.2 开发步骤 382 15.2 反射环境模拟 385 15.2.1 案例效果与基本原理 385 15.2.2 开发步骤 386 15.3 折射环境模拟 388 15.3.1 案例效果与基本原理 388 15.3.2 开发步骤 390 15.4 色散效果的模拟 391 15.4.1 案例效果与基本原理 391 15.4.2 开发步骤 392 15.5 菲涅尔效果的模拟 393 15.5.1 案例效果与基本原理 393 15.5.2 开发步骤 394 15.6 凹凸映射 395 15.6.1 案例效果与基本原理 395 15.6.2 法向量纹理图的生成 396 15.6.3 案例的开发 399 15.7 平面阴影 404 15.7.1 案例效果与基本原理 405 15.7.2 开发步骤 406 15.8 阴影映射 409 15.8.1 案例效果与基本原理 409 15.8.2 距离纹理图的生成 411 15.8.3 阴影场景的绘制 417 15.9 光线跟踪 419 15.9.1 案例效果与基本原理 419 15.9.2 开发步骤 423 15.10 本章小结 436 第16章 游戏开发的物理学 437 16.1 碰撞检测基本技术 437 16.1.1 aabb包围盒的基本原理 437 16.1.2 aabb包围盒的计算 438 16.1.3 aabb包围盒的碰撞检测 440 16.1.4 一个aabb包围盒的案例 442 16.1.5 旋转后的aabb包围盒 444 16.1.6 aabb包围盒的使用要点 448 16.1.7 obb包围盒 448 16.2 穿透效应 449 16.2.1 案例的运行效果与基本原理 449 16.2.2 具体开发步骤 450 16.3 粒子系统 450 16.3.1 案例运行效果与基本原理 451 16.3.2 cpu版案例的开发 452 16.3.3 gpu版案例开发步骤 453 16.4 弹簧质点模型 455 16.4.1 案例运行效果与基本原理 455 16.4.2 具体开发步骤 457 16.5 本章小结 462 第17章 游戏的心脏——物理引擎 463 17.1 物理引擎很重要 463 17.1.1 什么是物理引擎 463 17.1.2 常见的物理引擎 463 17.2 jbullet物理引擎 466 17.2.1 基本的物理学概念 466 17.2.2 jbullet常用类的介绍 468 17.3 箱子相互碰撞的案例 475 17.3.1 案例运行效果及准备工作 476 17.3.2 案例的基本框架结构 476 17.3.3 常量类——constant 477 17.3.4 3d场景渲染类——mysurfaceview 478 17.3.5 水平地面——texfloor类 480 17.3.6 箱子——texcube类 481 17.4 复合碰撞形状的使用 483 17.4.1 案例运行效果 483 17.4.2 立方体圆柱复合形状——cubecylinder类 483 17.5 凹凸地形的案例 486 17.5.1 案例运行效果 486 17.5.2 地形类——landform 486 17.6 任意形状物体的碰撞 488 17.6.1 案例运行效果 488 17.6.2 加载物体类——loadedobjectvertexnormal 488 17.6.3 加载物体刚体类——loadrigidbody 490 17.7 铰链关 491 17.7.1 铰链关的基本知识 491 17.7.2 案例的运行效果 492 17.7.3 铰链关旋转角速度的计算 493 17.7.4 3d界面渲染类——mysurfaceview 494 17.8 滑动关 496 17.8.1 滑动关的基本知识 496 17.8.2 案例效果图 497 17.8.3 3d界面渲染类——mysurfaceview 498 17.9 六自由度关 502 17.9.1 六自由度关的基本知识 502 17.9.2 案例的运行效果 503 17.9.3 人偶类——doll 503 17.9.4 拾取时采用的点对点关 505 17.10 本章小结 509 第18章 传感器应用的开发 510 18.1 基本的开发流程 510 18.2 加速度传感器 512 18.2.1 加速度传感器简介 513 18.2.2 案例的开发 514 18.3 磁场传感器 514 18.3.1 磁场传感器简介 514 18.3.2 案例的开发 514 18.4 光传感器 516 18.4.1 光传感器简介 516 18.4.2 案例的开发 516 18.5 温度传感器 518 18.5.1 温度传感器简介 518 18.5.2 案例的开发 518 18.6 接近传感器 519 18.6.1 接近传感器简介 519 18.6.2 案例的开发 520 18.7 姿态传感器 521 18.7.1 姿态传感器简介 521 18.7.2 案例的开发 522 18.8 本章小结 528 第19章 游戏开发小贴士 529 19.1 3d拾取技术 529 19.1.1 案例效果与基本原理 529 19.1.2 开发步骤 531 19.2 多点触控 537 19.2.1 案例效果与基本原理 537 19.2.2 开发步骤 538 19.3 多键监听 541 19.3.1 案例效果与基本原理 541 19.3.2 开发步骤 542 19.4 本章小结 544 第20章 bn赛艇 545 20.1 游戏背景及功能概述 545 20.1.1 背景概述 545 20.1.2 功能介绍 545 20.2 游戏的策划及准备工作 547 20.2.1 游戏的策划 547 20.2.2 android平台下游戏的准备工作 548 20.3 游戏的架构 552 20.3.1 各个类简要介绍 553 20.3.2 游戏框架简介 558 20.4 主控制类myactivity 559 20.5 2d界面相关类 563 20.5.1 欢迎界面类welcomeview 563 20.5.2 2d界面父类mysfview 565 20.5.3 主菜单类menuview 565 20.5.4 2d界面绘制类viewfordraw 566 20.5.5 数据库工具类dbutil 568 20.5.6 android系统版本对话框androidversiondialog 570 20.6 选船界面相关类 570 20.6.1 着色器管理类shadermanager 571 20.6.2 围墙类colorlightrect 571 20.6.3 选船房间类housefordraw 573 20.6.4 展台类displaystation 575 20.6.5 赛艇类boat 576 20.6.6 选船界面xcsurfaceview 576 20.7 游戏界面相关类 582 20.7.1 进度条类process 583 20.7.2 3d物体父类bndrawer 584 20.7.3 3d物体控制类tdobjectforcontrol 584 20.7.4 赛艇类boat 585 20.7.5 可碰撞物体父类kzbjdrawer 586 20.7.6 可碰撞物体控制类kzbjforcontr
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值