- 浏览: 9659 次
文章分类
最新评论
Android 歌词同步滚动效果
摘自:http://www.cnblogs.com/wpptec/archive/2012/09/05/2671303.html
歌词是播放器类App必不可少的组件,而一般的歌词组件都需要做到歌词的显示与播放进度同步。我们知道,歌词是如下所示的文件:
lrc
[ti:原来爱情这么伤]
[ar:梁咏琪]
[al:给自己的情歌]
[00:00.55]梁咏琪 - 原来爱情这么伤
[00:05.43]作词:彭学斌
[00:06.68]作曲:彭学斌
[00:09.63]
[00:22.27]我睁开眼睛 却感觉不到天亮
[00:29.74]东西吃一半 莫名其妙哭一场
[00:37.06]我忍住不想 时间变得更漫长
[00:44.09]也与你有关 否则又开始胡思乱想
[00:53.81]我日月无光 忙得不知所以然
[00:59.96]找朋友交谈 其实全帮不上忙
[01:07.49]以为会习惯 有你在才是习惯
[01:14.62]你曾住在我心上 现在空了一个地方
[01:21.89]原来爱情这么伤 比想象中还难
[01:29.90]泪水总是不听话 幸福躲起来不声不响
[01:37.43]太多道理太牵强 道理全是一样
[01:44.34]说的时候很简单 爱上后却正巧打乱
[02:00.00]我日月无光 忙得不知所以然
[02:07.41]找朋友交谈 其实全帮不上忙
[02:15.07]以为会习惯 有你在才是习惯
[02:21.88]你曾住在我心上 现在空了一个地方
[02:29.38]原来爱情这么伤 比想象中还难
[02:36.60]泪水总是不听话 幸福躲起来不声不响
[02:44.22]太多道理太牵强 道理全是一样
[02:50.78]说的时候很简单 爱上后却正巧打乱
[03:00.32]只想变的坚强 强到能够去忘
[03:07.29]无所谓悲伤 只要学会抵抗
[03:14.19]原来爱情这么伤
[03:20.78]原来爱情是这样 这样峰回路转
[03:28.12]泪水明明流不干 瞎了眼还要再爱一趟
[03:35.83]有一天终于打完 思念的一场战
[03:43.45]回过头再看一看 原来爱情那么伤
[03:54.76]下次还会不会这样
[88:88.88]
[ar:梁咏琪]
[al:给自己的情歌]
[00:00.55]梁咏琪 - 原来爱情这么伤
[00:05.43]作词:彭学斌
[00:06.68]作曲:彭学斌
[00:09.63]
[00:22.27]我睁开眼睛 却感觉不到天亮
[00:29.74]东西吃一半 莫名其妙哭一场
[00:37.06]我忍住不想 时间变得更漫长
[00:44.09]也与你有关 否则又开始胡思乱想
[00:53.81]我日月无光 忙得不知所以然
[00:59.96]找朋友交谈 其实全帮不上忙
[01:07.49]以为会习惯 有你在才是习惯
[01:14.62]你曾住在我心上 现在空了一个地方
[01:21.89]原来爱情这么伤 比想象中还难
[01:29.90]泪水总是不听话 幸福躲起来不声不响
[01:37.43]太多道理太牵强 道理全是一样
[01:44.34]说的时候很简单 爱上后却正巧打乱
[02:00.00]我日月无光 忙得不知所以然
[02:07.41]找朋友交谈 其实全帮不上忙
[02:15.07]以为会习惯 有你在才是习惯
[02:21.88]你曾住在我心上 现在空了一个地方
[02:29.38]原来爱情这么伤 比想象中还难
[02:36.60]泪水总是不听话 幸福躲起来不声不响
[02:44.22]太多道理太牵强 道理全是一样
[02:50.78]说的时候很简单 爱上后却正巧打乱
[03:00.32]只想变的坚强 强到能够去忘
[03:07.29]无所谓悲伤 只要学会抵抗
[03:14.19]原来爱情这么伤
[03:20.78]原来爱情是这样 这样峰回路转
[03:28.12]泪水明明流不干 瞎了眼还要再爱一趟
[03:35.83]有一天终于打完 思念的一场战
[03:43.45]回过头再看一看 原来爱情那么伤
[03:54.76]下次还会不会这样
[88:88.88]
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:
- packagecom.music.lyricsync;
- publicclassLyricObject{
- publicintbegintime;//开始时间
- publicintendtime;//结束时间
- publicinttimeline;//单句歌词用时
- publicStringlrc;//单句歌词
- }
可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕中央高亮显示的那句歌词。在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。MainActivity代码如下:
- packagecom.music.lyricsync;
- importjava.io.IOException;
- importandroid.app.Activity;
- importandroid.media.MediaPlayer;
- importandroid.net.Uri;
- importandroid.os.Bundle;
- importandroid.os.Environment;
- importandroid.os.Handler;
- importandroid.view.View;
- importandroid.view.View.OnClickListener;
- importandroid.widget.Button;
- importandroid.widget.SeekBar;
- importandroid.widget.SeekBar.OnSeekBarChangeListener;
- publicclassMainActivityextendsActivity{
- /**Calledwhentheactivityisfirstcreated.*/
- privateLyricViewlyricView;
- privateMediaPlayermediaPlayer;
- privateButtonbutton;
- privateSeekBarseekBar;
- privateStringmp3Path;
- privateintINTERVAL=45;//歌词每行的间隔
- @Override
- publicvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- //this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- //getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- setContentView(R.layout.main);
- mp3Path=Environment.getExternalStorageDirectory().getAbsolutePath()+"/LyricSync/1.mp3";
- lyricView=(LyricView)findViewById(R.id.mylrc);
- mediaPlayer=newMediaPlayer();
- //this.requestWindowFeature(Window.FEATURE_NO_TITLE);
- ResetMusic(mp3Path);
- SerchLrc();
- lyricView.SetTextSize();
- button=(Button)findViewById(R.id.button);
- button.setText("播放");
- seekBar=(SeekBar)findViewById(R.id.seekbarmusic);
- seekBar.setOnSeekBarChangeListener(newOnSeekBarChangeListener(){
- @Override
- publicvoidonStopTrackingTouch(SeekBarseekBar){
- //TODOAuto-generatedmethodstub
- }
- @Override
- publicvoidonStartTrackingTouch(SeekBarseekBar){
- //TODOAuto-generatedmethodstub
- }
- @Override
- publicvoidonProgressChanged(SeekBarseekBar,intprogress,
- booleanfromUser){
- //TODOAuto-generatedmethodstub
- if(fromUser){
- mediaPlayer.seekTo(progress);
- lyricView.setOffsetY(220-lyricView.SelectIndex(progress)
- *(lyricView.getSIZEWORD()+INTERVAL-1));
- }
- }
- });
- button.setOnClickListener(newOnClickListener(){
- @Override
- publicvoidonClick(Viewv){
- //TODOAuto-generatedmethodstub
- if(mediaPlayer.isPlaying()){
- button.setText("播放");
- mediaPlayer.pause();
- }else{
- button.setText("暂停");
- mediaPlayer.start();
- lyricView.setOffsetY(220-lyricView.SelectIndex(mediaPlayer.getCurrentPosition())
- *(lyricView.getSIZEWORD()+INTERVAL-1));
- }
- }
- });
- mediaPlayer.setOnCompletionListener(newMediaPlayer.OnCompletionListener(){
- @Override
- publicvoidonCompletion(MediaPlayermp){
- ResetMusic(mp3Path);
- lyricView.SetTextSize();
- lyricView.setOffsetY(200);
- mediaPlayer.start();
- }
- });
- seekBar.setMax(mediaPlayer.getDuration());
- newThread(newrunable()).start();
- }
- publicvoidSerchLrc(){
- Stringlrc=mp3Path;
- lrc=lrc.substring(0,lrc.length()-4).trim()+".lrc".trim();
- LyricView.read(lrc);
- lyricView.SetTextSize();
- lyricView.setOffsetY(350);
- }
- publicvoidResetMusic(Stringpath){
- mediaPlayer.reset();
- try{
- mediaPlayer.setDataSource(mp3Path);
- mediaPlayer.prepare();
- }catch(IllegalArgumentExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }catch(IllegalStateExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }catch(IOExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }
- }
- classrunableimplementsRunnable{
- @Override
- publicvoidrun(){
- //TODOAuto-generatedmethodstub
- while(true){
- try{
- Thread.sleep(100);
- if(mediaPlayer.isPlaying()){
- lyricView.setOffsetY(lyricView.getOffsetY()-lyricView.SpeedLrc());
- lyricView.SelectIndex(mediaPlayer.getCurrentPosition());
- seekBar.setProgress(mediaPlayer.getCurrentPosition());
- mHandler.post(mUpdateResults);
- }
- }catch(InterruptedExceptione){
- //TODOAuto-generatedcatchblock
- e.printStackTrace();
- }
- }
- }
- }
- HandlermHandler=newHandler();
- RunnablemUpdateResults=newRunnable(){
- publicvoidrun(){
- lyricView.invalidate();//更新视图
- }
- };
- }
歌词View的代码如下:
- packagecom.music.lyricsync;
- importjava.io.BufferedReader;
- importjava.io.File;
- importjava.io.FileInputStream;
- importjava.io.FileNotFoundException;
- importjava.io.IOException;
- importjava.io.InputStreamReader;
- importjava.util.Iterator;
- importjava.util.TreeMap;
- importjava.util.regex.Matcher;
- importjava.util.regex.Pattern;
- importandroid.content.Context;
- importandroid.graphics.Canvas;
- importandroid.graphics.Color;
- importandroid.graphics.Paint;
- importandroid.util.AttributeSet;
- importandroid.util.Log;
- importandroid.view.MotionEvent;
- importandroid.view.View;
- publicclassLyricViewextendsView{
- privatestaticTreeMap<Integer,LyricObject>lrc_map;
- privatefloatmX;//屏幕X轴的中点,此值固定,保持歌词在X中间显示
- privatefloatoffsetY;//歌词在Y轴上的偏移量,此值会根据歌词的滚动变小
- privatestaticbooleanblLrc=false;
- privatefloattouchY;//当触摸歌词View时,保存为当前触点的Y轴坐标
- privatefloattouchX;
- privatebooleanblScrollView=false;
- privateintlrcIndex=0;//保存歌词TreeMap的下标
- privateintSIZEWORD=0;//显示歌词文字的大小值
- privateintINTERVAL=45;//歌词每行的间隔
- Paintpaint=newPaint();//画笔,用于画不是高亮的歌词
- PaintpaintHL=newPaint();//画笔,用于画高亮的歌词,即当前唱到这句歌词
- publicLyricView(Contextcontext){
- super(context);
- init();
- }
- publicLyricView(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- init();
- }
- /*(non-Javadoc)
- *@seeandroid.view.View#onDraw(android.graphics.Canvas)
- */
- @Override
- protectedvoidonDraw(Canvascanvas){
- if(blLrc){
- paintHL.setTextSize(SIZEWORD);
- paint.setTextSize(SIZEWORD);
- LyricObjecttemp=lrc_map.get(lrcIndex);
- canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*lrcIndex,paintHL);
- //画当前歌词之前的歌词
- for(inti=lrcIndex-1;i>=0;i--){
- temp=lrc_map.get(i);
- if(offsetY+(SIZEWORD+INTERVAL)*i<0){
- break;
- }
- canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*i,paint);
- }
- //画当前歌词之后的歌词
- for(inti=lrcIndex+1;i<lrc_map.size();i++){
- temp=lrc_map.get(i);
- if(offsetY+(SIZEWORD+INTERVAL)*i>600){
- break;
- }
- canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*i,paint);
- }
- }
- else{
- paint.setTextSize(25);
- canvas.drawText("找不到歌词",mX,310,paint);
- }
- super.onDraw(canvas);
- }
- /*(non-Javadoc)
- *@seeandroid.view.View#onTouchEvent(android.view.MotionEvent)
- */
- @Override
- publicbooleanonTouchEvent(MotionEventevent){
- //TODOAuto-generatedmethodstub
- System.out.println("bllll==="+blScrollView);
- floattt=event.getY();
- if(!blLrc){
- //returnsuper.onTouchEvent(event);
- returnsuper.onTouchEvent(event);
- }
- switch(event.getAction()){
- caseMotionEvent.ACTION_DOWN:
- touchX=event.getX();
- break;
- caseMotionEvent.ACTION_MOVE:
- touchY=tt-touchY;
- offsetY=offsetY+touchY;
- break;
- caseMotionEvent.ACTION_UP:
- blScrollView=false;
- break;
- }
- touchY=tt;
- returntrue;
- }
- publicvoidinit(){
- lrc_map=newTreeMap<Integer,LyricObject>();
- offsetY=320;
- paint=newPaint();
- paint.setTextAlign(Paint.Align.CENTER);
- paint.setColor(Color.GREEN);
- paint.setAntiAlias(true);
- paint.setDither(true);
- paint.setAlpha(180);
- paintHL=newPaint();
- paintHL.setTextAlign(Paint.Align.CENTER);
- paintHL.setColor(Color.RED);
- paintHL.setAntiAlias(true);
- paintHL.setAlpha(255);
- }
- /**
- *根据歌词里面最长的那句来确定歌词字体的大小
- */
- publicvoidSetTextSize(){
- if(!blLrc){
- return;
- }
- intmax=lrc_map.get(0).lrc.length();
- for(inti=1;i<lrc_map.size();i++){
- LyricObjectlrcStrLength=lrc_map.get(i);
- if(max<lrcStrLength.lrc.length()){
- max=lrcStrLength.lrc.length();
- }
- }
- SIZEWORD=320/max;
- }
- protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
- mX=w*0.5f;
- super.onSizeChanged(w,h,oldw,oldh);
- }
- /**
- *歌词滚动的速度
- *
- *@return返回歌词滚动的速度
- */
- publicFloatSpeedLrc(){
- floatspeed=0;
- if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){
- speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20);
- }elseif(offsetY+(SIZEWORD+INTERVAL)*lrcIndex<120){
- Log.i("speed","speedistoofast!!!");
- speed=0;
- }
- //if(speed<0.2){
- //speed=0.2f;
- //}
- returnspeed;
- }
- /**
- *按当前的歌曲的播放时间,从歌词里面获得那一句
- *@paramtime当前歌曲的播放时间
- *@return返回当前歌词的索引值
- */
- publicintSelectIndex(inttime){
- if(!blLrc){
- return0;
- }
- intindex=0;
- for(inti=0;i<lrc_map.size();i++){
- LyricObjecttemp=lrc_map.get(i);
- if(temp.begintime<time){
- ++index;
- }
- }
- lrcIndex=index-1;
- if(lrcIndex<0){
- lrcIndex=0;
- }
- returnlrcIndex;
- }
- /**
- *读取歌词文件
- *@paramfile歌词的路径
- *
- */
- publicstaticvoidread(Stringfile){
- TreeMap<Integer,LyricObject>lrc_read=newTreeMap<Integer,LyricObject>();
- Stringdata="";
- try{
- FilesaveFile=newFile(file);
- //System.out.println("是否有歌词文件"+saveFile.isFile());
- if(!saveFile.isFile()){
- blLrc=false;
- return;
- }
- blLrc=true;
- //System.out.println("bllrc==="+blLrc);
- FileInputStreamstream=newFileInputStream(saveFile);//context.openFileInput(file);
- BufferedReaderbr=newBufferedReader(newInputStreamReader(stream,"GB2312"));
- inti=0;
- Patternpattern=Pattern.compile("\\d{2}");
- while((data=br.readLine())!=null){
- //System.out.println("++++++++++++>>"+data);
- data=data.replace("[","");//将前面的替换成后面的
- data=data.replace("]","@");
- Stringsplitdata[]=data.split("@");//分隔
- if(data.endsWith("@")){
- for(intk=0;k<splitdata.length;k++){
- Stringstr=splitdata[k];
- str=str.replace(":",".");
- str=str.replace(".","@");
- Stringtimedata[]=str.split("@");
- Matchermatcher=pattern.matcher(timedata[0]);
- if(timedata.length==3&&matcher.matches()){
- intm=Integer.parseInt(timedata[0]);//分
- ints=Integer.parseInt(timedata[1]);//秒
- intms=Integer.parseInt(timedata[2]);//毫秒
- intcurrTime=(m*60+s)*1000+ms*10;
- LyricObjectitem1=newLyricObject();
- item1.begintime=currTime;
- item1.lrc="";
- lrc_read.put(currTime,item1);
- }
- }
- }
- else{
- StringlrcContenet=splitdata[splitdata.length-1];
- for(intj=0;j<splitdata.length-1;j++)
- {
- Stringtmpstr=splitdata[j];
- tmpstr=tmpstr.replace(":",".");
- tmpstr=tmpstr.replace(".","@");
- Stringtimedata[]=tmpstr.split("@");
- Matchermatcher=pattern.matcher(timedata[0]);
- if(timedata.length==3&&matcher.matches()){
- intm=Integer.parseInt(timedata[0]);//分
- ints=Integer.parseInt(timedata[1]);//秒
- intms=Integer.parseInt(timedata[2]);//毫秒
- intcurrTime=(m*60+s)*1000+ms*10;
- LyricObjectitem1=newLyricObject();
- item1.begintime=currTime;
- item1.lrc=lrcContenet;
- lrc_read.put(currTime,item1);//将currTime当标签item1当数据插入TreeMap里
- i++;
- }
- }
- }
- }
- stream.close();
- }
- catch(FileNotFoundExceptione){
- }
- catch(IOExceptione){
- }
- /*
- *遍历hashmap计算每句歌词所需要的时间
- */
- lrc_map.clear();
- data="";
- Iterator<Integer>iterator=lrc_read.keySet().iterator();
- LyricObjectoldval=null;
- inti=0;
- while(iterator.hasNext()){
- Objectob=iterator.next();
- LyricObjectval=(LyricObject)lrc_read.get(ob);
- if(oldval==null)
- oldval=val;
- else
- {
- LyricObjectitem1=newLyricObject();
- item1=oldval;
- item1.timeline=val.begintime-oldval.begintime;
- lrc_map.put(newInteger(i),item1);
- i++;
- oldval=val;
- }
- if(!iterator.hasNext()){
- lrc_map.put(newInteger(i),val);
- }
- }
- }
- /**
- *@returntheblLrc
- */
- publicstaticbooleanisBlLrc(){
- returnblLrc;
- }
- /**
- *@returntheoffsetY
- */
- publicfloatgetOffsetY(){
- returnoffsetY;
- }
- /**
- *@paramoffsetYtheoffsetYtoset
- */
- publicvoidsetOffsetY(floatoffsetY){
- this.offsetY=offsetY;
- }
- /**
- *@return返回歌词文字的大小
- */
- publicintgetSIZEWORD(){
- returnSIZEWORD;
- }
- /**
- *设置歌词文字的大小
- *@paramsIZEWORDthesIZEWORDtoset
- */
- publicvoidsetSIZEWORD(intsIZEWORD){
- SIZEWORD=sIZEWORD;
- }
- }
xml布局文件如下:
- <?xmlversion="1.0"encoding="utf-8"?>
- <RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#FFFFFF">
- <com.music.lyricsync.LyricView
- android:id="@+id/mylrc"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_marginBottom="50dip"
- android:layout_marginTop="50dip"/>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:orientation="horizontal">
- <Button
- android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <SeekBar
- android:id="@+id/seekbarmusic"
- android:layout_width="205px"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginBottom="5px"
- android:progress="0"/>
- </LinearLayout>
- </RelativeLayout>
相关推荐
CoCoPlayer 可以让初学者学习使用android播放音乐,实现歌词同步滚动显示效果,自定义view等用法,也包含了LRC解析相关代码,解析LRC使用的正则表达式~请多多支持~~CoCoPlayer 完整正在开发中,开发完成之后将把源码...
主要介绍了Android实现两个ScrollView互相联动的同步滚动效果代码,涉及Android操作ScrollView实现联动功能的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
最近在做Android 的MP3播放的项目,要实现歌词的自动滚动,以及同步显示。 lyric的歌词解析主要用yoyoplayer里面的,显示部分参考了这里 ,这里只是模拟MP3歌词的滚动。 先上一下效果图: 滚动实现的代码其实也简单...
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体: 代码如下: public class ...在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。MainActivity代码如下: 代码
文件中收集了三个实现股票联动效果的控件,上下左右同步滚动效果。
继承自ViewGroup然后配合Scroller完成滑动效果 主要弄清楚scrollTo和scrollBy 及scroller的用法即可很容易实现
支持和 ListView 的无缝同步滚动 和 CoordinatorLayout 的嵌套滚动 . 支持自动刷新、自动上拉加载(自动检测列表惯性滚动到底部,而不用手动上拉). 支持自定义回弹动画的插值器,实现各种炫酷的动画效果. 支持设置...
公司的项目需要一个视频的滚动列表。 搜了些文章比较常见的是根据列表项的可视百分比来判断的。实现起来略复杂。 这里想了一个在要求不高的情况...基于SurfaceView的VideoView由于没有同步缓冲区,它不能在ListView中
使用RecyclerView实现双表联动效果,点击左侧分类后右侧数据改变,滑动右侧列表时左侧分类跟随变动~ 如有不懂可在我该篇博客内留言,博客地址:https://blog.csdn.net/qq_20451879/article/details/81664188
16.3.3 使用Camera实现2D图像的深度效果 16.3.4 探索AnimationListener类 16.3.5 关于变换矩阵的一些说明 16.4 资源 16.5 小结 第17章 地图和基于位置的服务 17.1 地图包 17.1.1 从Google获取...
16.3.3 使用Camera实现2D图像的深度效果 16.3.4 探索AnimationListener类 16.3.5 关于变换矩阵的一些说明 16.4 资源 16.5 小结 第17章 地图和基于位置的服务 17.1 地图包 17.1.1 从Google获取...
}1.3同步一下配置结束了2.使用 2.1、正负数的使用 默认效果从零开始,以最小精度开始动画 ,支持正负数,可以直接设置 ntvTestOne.setNumberValue("100"); ntvTestOne.setNumberValue("-100");2.2支持...
因为最近一个项目的需求,写了一个双向的ListView. ...并且达到了很好的同步效果. 想到一直以来都是从网上拿东西,也应该有贡献精神.所以这里把代码共享,本人尽量的屏蔽了实现的细节.以下提供API,希望对大家有帮助.
支持广泛的便笺粒度,允许进行网格内或网格外编程直观的多点触控支持无处不在手机或平板电脑可以处理的曲目数量回路长度和位置可调一系列内置鼓采样丰富的效果集,包括混响,立体声延迟(自由或速度同步),...
*Tickers:在全球范围内添加滚动/闪烁信息! *装饰师:个性化每个国家的外观(颜色、质地、标签) *地图编辑器:修改提供的地图和创建您的虚构场景! *小地图:拖放迷你地图预置到场景方便一键式世界导航! -...
6.9 多层背景滚动效果 188 6.10 本章小结 190 第7章 物理模拟与碰撞检测 192 7.1 概述 192 7.2 游戏中的碰撞检测 193 7.3 碰撞检测的方法 194 7.3.1 平面几何在碰撞检测中的应用 194 7.3.2 物体的包围盒 197 7.3.3 ...