Unity3D游戏开发笔记2.docx
- 文档编号:4346316
- 上传时间:2022-11-30
- 格式:DOCX
- 页数:20
- 大小:353KB
Unity3D游戏开发笔记2.docx
《Unity3D游戏开发笔记2.docx》由会员分享,可在线阅读,更多相关《Unity3D游戏开发笔记2.docx(20页珍藏版)》请在冰豆网上搜索。
Unity3D游戏开发笔记2
2013.1.19
3.【第一张地图与跳跃的改善】
继续吧,为了方便继续编写的敌人之类的脚本,所以打算今天先把第一张地图画个大概。
结果,画了我好久的地图.....≡(▔﹏▔)≡
我对美术方面的天赋简直是惨不忍睹了.....加之地图中途画的差不多的时候Unity又崩溃了一次....忘了保存(ㄒoㄒ)。
于是又忙活半天....这个地图完全靠临时发挥了,第一张地图我画了好几次了,基本上每次画出来的地形之类的都完全不一样,所以就不多解释了,就上一张大概的场景图吧:
看起来好简单吧?
唉......开启编辑器,这时侯如果在场景中转悠的话,很轻易就可以发现人物跳跃的功能有些瑕疵,就是跳动速度太快,相机都晃的花眼.....
事实证明,偷懒是行不通的......那么现在还是打开PlayerControl脚本改造一下吧。
在脚本的Move函数中,原本的跳跃功能只有短短两行,就是判断人物是否位于地面,在的话,就把人物向上抬升一段距离,结果造成跳跃过于突兀的效果。
现在的话,可以先把判断角色是否位于地面的那段if语句注释掉,删掉也可以。
为了使跳跃起来不那么突兀,我的想法是定义一个bool变量,当角色在地面上按下空格键时,变量为true,否则当角色跳起来超过一定高度就为false,判断高度的方式,我想来想去,用了射线检测的方式(当然,没用的错误的实验肯定不会写出来的)。
首先在最开始Start函数上面定义一个bool变量:
#region判断
privateboolm_jumpTmp=true;//判断是否可以跳跃
#endregion
然后在玩家按下空格后,定义一个临时的变量存放角色向下的方向,再判断角色是否位于地面,如果在地面,就可以挑。
否则跳跃超过1.5米后,就得等落地了才能跳了:
if(Input.GetKey(KeyCode.Space))
{
Vector3m_direction=m_transform.TransformDirection(Vector3.down);//方向向下的临时变量
if(m_controller.isGrounded)
{
m_jumpTmp=true;
}elseif(!
Physics.Raycast(m_transform.position,m_direction,1.5f))//判断跳跃高度
{
m_jumpTmp=false;
}
if(m_jumpTmp)
{
m_animation.CrossFade("Jump2");
m_transform.Translate(Vector3.up*m_jump*Time.deltaTime);
}
}
之所以把东西写在按空格键里面,是因为节省些许性能。
把代码如此改了之后,再回到游戏中运行试一下,跳跃果然要缓多了。
2014.1.20
4.【敌人的AI
(一)】
今天就先来写敌人的AI吧。
首先导入一群怪物的模型包,然后在根目录下新建一个文件夹,名为_Enemy,然后将导入的怪物模型移动进去,以方便后来分辨。
然后在_Script中新建一个文件夹,名为Enemy,然后在Enemy文件夹中新建一个脚本,名为Enemy。
因为考虑到敌人肯定不止一个,所以每个敌人都采用单独的脚本肯定不现实(这也使我当初重写了四五遍敌人脚本后才恍悟到的.....),我就想到是否可以直接先写一个敌人的主要脚本,往后每个敌人的脚本都从中继承,每个敌人到时只需要改一点就可以了●ω●。
在脚本最开始,首先定义敌人的属性及自身的对象:
#region属性
publicstringm_name;//名字
publicintm_level;//等级,以判断角色应当获取的经验
publicintm_maxLife;//生命
publicintm_currentLife;//当前生命
publicintm_defense;//防御力
publicintm_attack;//攻击力
publicfloatm_speed;//速度
publicintm_rotateSpeed;//旋转速度
publicfloatm_attackRadius;//攻击半径
#endregion
#region对象
publicTransformm_transform;//自己
#endregion
在这里,对敌人就不设置魔法值了,其它属性跟角色的差不多罢。
因为在游戏中我还设置了Boss,因此还必须定义一个判断敌人是否是Boss的bool变量,默认为false:
#region判断
publicboolm_isBoss=false;
#endregion
然后在Start函数中取得自身对象:
m_transform=this.transform;//获取自己对象
因为现在这个敌人脚本只是一个基类,因此还必须要求从其继承的那些敌人能够为自己的属性进行赋值,所以我又定义了一个SetAttribute的虚函数函数,类型为保护,这样在子类中就可以用这个类进行敌人各项属性的赋值:
protectedvoidSetAttribute(stringname,level,intLife,intdefense,intattack,intspeed,introtatteSpeed,floatattackRadius)
{
m_name=name;
m_level=level;
m_maxLife=Life;
m_currentLife=Life;
m_defense=defense;
m_attack=attack;
m_speed=speed;
m_rotateSpeed=rotatteSpeed;
m_attackRadius=attackRadius;
}
为了不在子类中更改Start函数,另外还要再声明一个Init的虚函数,里面什么都不写,并在Start函数最开始进行调用:
voidStart()
{
Init();
m_transform=this.transform;//获取自己对象
}
protectedvirtualvoidInit()
{
}
这样的话,子类初始化什么东西的话,就可以直接在这个函数中搞定了。
然后,敌人的状态肯定要分为没有目标时,干什么,有目标时就攻击目标之类的动作,所以,应该再定义一个bool类型的变量,进行这点的判断吧。
这个类型就定义在“判断”那一堆:
#region判断
publicboolm_isBoss=false;
protectedboolm_isIdle=true;
#endregion
这时就可以在Update函数中进行判断了:
if(m_isIdle)//如果处于空闲状态
{
}
else
{
}
这时候,我选择在最开始加上一堆放“变量”的,并在那儿定义一个随机值m_idleStat用来作为随机播放动画的随机值,m_idleTime用来限制每一个动画播放的时长:
#region变量
protectedintm_idleStat;//定义空闲状态动画的随机值
protectedfloatm_idleTime=0;//每一个空闲状态持续时间
#endregin
现在就可以在Update函数中加上这些:
if(m_isIdle)
{
m_idleTime-=Time.deltaTime;
if(m_idleTime<0)//空闲随机时间到了就随机做出新动作
{
m_idleStat=Random.Range(0,11);
audio.clip=m_sound;
audio.Play();
m_idleTime=3;
}
}
其中的随机值用来随机出0~10几个数字,用来判断随机播放什么动画。
并且,在播放动画的同时,叫一声。
声音变量定义在前面“变量”标签中,现在既然涉及到声音,那就把攻击声音和技能声音一块儿定义了:
//声音
publicAudioClipm_sound;
publicAudioClipm_attackSound;
publicAudioClipm_skillSound;
既然涉及到了动画,那就要获取动画组件了。
在脚本Start函数上面的“对象”标签中,再定义一个动画组件:
protectedAnimationm_animation;//动画组件
并在Start函数中进行初始化:
m_animation=GetComponent
这时候再定义一个“动画”标签,将敌人的各个动画分别赋值给定义的string类型,这样会更加好控制:
#region动画状态
protectedstringm_animationIdle="idle1";
protectedstringm_animationIdle2="idle2";
protectedstringm_animationIdle3="idle3";
protectedstringm_animationWalk="walk";
protectedstringm_animationAttack="attack";
protectedstringm_animationSkill="skill";
protectedstringm_animationDeath="death";
#endregion
这些动画,到时候需要在Inspector中对敌人的动画进行改名,这时先就定义好吧。
动画之类的都定义好了,在Update函数中就可以进行随机动画的判断了。
这里我使用的Swicth,以前本来也不是使用switch进行判断的,现在写的时候觉得switch可能比较简单些,就改了结果好像还复杂些了●﹏●.....不过没办法,折腾了半天,都差不多弄好了,就不改了:
switch(m_idleStat)
{
case1:
m_animation.CrossFade(m_animationIdle);
audio.clip=m_sound;
audio.Play();
break;
case2:
m_animation.CrossFade(m_animationIdle);
m_transform.Rotate(0,m_rotateSpeed*Time.deltaTime,0);
break;
case3:
m_animation.CrossFade(m_animationIdle2);
break;
case4:
m_animation.CrossFade(m_animationIdle2);
break;
case5:
m_animation.CrossFade(m_animationWalk);
m_transform.Rotate(0,m_rotateSpeed*Time.deltaTime,0);
break;
case6:
m_animation.CrossFade(m_animationWalk);
m_transform.Rotate(0,m_rotateSpeed*Time.deltaTime,0);
break;
case7:
m_animation.CrossFade(m_animationWalk);
m_transform.Rotate(0,m_rotateSpeed*Time.deltaTime,0);
break;
case8:
m_animation.CrossFade(m_animationWalk);
m_transform.Translate(Vector3.forward*m_speed*Time.deltaTime);
break;
default:
m_animation.CrossFade(m_animationIdle3);
m_transform.Translate(Vector3.forward*m_speed*Time.deltaTime);
break;
}
原理就是使用switch判断每个随机数字,在某些数字上就旋转或者前进,或者嫌在原地。
东西看起一大堆,实际上原理很简单......
然后在状态最后向自己前面发出一条射线,判断自己前方有没有人,有的话,就将状态m_isIdle置为false:
m_forward=m_transform.TransformDirection(Vector3.forward);
if(Physics.Raycast(m_transform.position,m_forward,outm_hit,m_attackDistance,m_layerMask))
{
m_attackTarget=m_hit.collider.gameObject.transform;//如果看见任何人类对象,就获取其位置,并将Idle状态置为false
m_isIdle=false;
}
其中的m_forward是向前的一个Vector3变量,定义在前面“变量”标签中:
protectedVector3m_forward;//敌人的前方向
hit是属于RaycastHit变量,所以也要在前面定义:
protectedRaycastHitm_hit;//射线碰撞
m_attackDistance是定义的敌人视线距离(还是那句话,我数学不好+▂+,就不用那些复杂的模拟现实去判断怪物的视角高度之类的了,直接一条有距离的射线从敌人面前射出去,撞上了就代表“看见了”.....),也要定义在最前面“变量”标签中:
protectedfloatm_attackDistance=8;//会攻击的视线距离
至于m_layerMask则是层蒙版,今天在这个上面又折腾了好久......我在编辑器的层标签里面新建了一个标签,命名为:
“Human”,并把它指派给了玩家,然后再在最开始变量标签中定义了一个层protectedLayerMaskm_layerMas;
在Start函数中进行初始化:
m_layerMask=1< 最后m_attackTarget就是属于“看见”的目标对象了,看见了谁,就把谁赋给它,然后对这个对象进行攻击等等,也先定义在开始的“对象”标签中: publicTransformm_attackTarget;//攻击对象 获取到了看见的对象后,就可以把m_isIdle这个空闲状态设置为false了,代表不再闲着没事儿,而是应该进行攻击了。 接下来就在Update函数中的if(m_isIdle)判断后面加上一个else语句,里面放上 RotateTo(); Attack(); 两个函数。 这两个函数写在外边,也是同样考虑到以后有的敌人可能会改变攻击方式的。 首先是Rotate函数,定义在Update函数下边吧: protectedvoidRotateTo() { Vector3m_oldAngle=m_transform.eulerAngles;//当前旋转角度 m_transform.LookAt(m_attackTarget.position);//盯着攻击对象 floatm_target=m_transform.eulerAngles.y;//目标角度吧 floatm_targetAngle=Mathf.MoveTowardsAngle(m_oldAngle.y,m_target,m_rotateSpeed*Time.deltaTime);//计算应转动的角度 m_transform.eulerAngles=newVector3(0,m_targetAngle,0);//转动角度 } 作用就是改变敌人的当前面向的角度到面向攻击对象的方向,说实话,这一段我也不是很明白,只是知道大概达到了转向的效果。 在Rotate函数下继续定义Attack攻击函数,在函数中用到的变量m_distanceToOther是敌人当前与攻击目标的距离,定义在前面的“变量”标签中: protectedfloatm_distanceToOther;//与攻击对象的距离 出此之外还有攻击的粒子效果,粒子效果放在“对象”标签中,这里我就先把两个粒子效果都定义起来了: publicTransformm_attackParticle;//普通攻击粒子效果 publicTransformm_skillParticle;//技能攻击粒子效果 这个粒子效果只是用来判断攻击对象的,真正的伤害计算我准备放到粒子效果里面,那样的话,大概能做到无论是NPC还是敌人都能进行攻击罢? (为自由的游戏而奋斗~~)。 攻击函数: protectedvoidAttack() { m_distanceToOther=Vector3.Distance(m_transform.position,m_attackTarget.position); if(m_distanceToOther { m_forward=m_transform.TransformDirection(Vector3.forward); if(Physics.Raycast(m_transform.position,m_forward,outm_hit,m_attackRadius,m_layerMask)) { m_animation.CrossFade(m_animationAttack); Instantiate(m_attackTransform,m_hit.collider.gameObject.transform.position,Quaternion.identity);//攻击,实例化一个粒子 } }elseif(m_distanceToOther<20)//如果距离小于20,对目标进行追击 { m_animation.CrossFade(m_animationWalk); m_transform.Translate(Vector3.forward*m_speed*Time.deltaTime); }elseif(m_distanceToOther<90)//如果距离大于20,就回到Idle状态,不追目标了 { m_isIdle=true; }else//超过九十米的话,为了省点资源,就把这个敌人销毁罢 { Destroy(this.gameObject); } } 这个攻击函数会先对自己与攻击对象的距离进行判断,若果两者之间的距离小于自身的攻击半径,就向前发射一条射线,若果碰撞到了任何人类层的目标,就进行攻击(实例化一个普通攻击的粒子效果)。 否则两则距离小于20米的话,就项目标跑过去,当然,距离大于90米的话(出了玩家的视线范围),就直接销毁自身。 好了,天色不早了,接下来的明天继续吧。 2014.1.21 5.【敌人AI (二)与....攻击判定】 昨天的脚本现在其实已经可以运行进行测试了。 那么就先测试一下。 首先找到_Enemy文件夹中导入的怪物模型,这里我以wolf,也就是狼为例,它也将作为玩家最先见到的敌人。 将狼的模型拖入场景中,然后在Inspector面板中将小狼模型所需要用到的动画按照先前脚本中定义的名字改名,这里主要是为了方便,因为默认状态动画有了,就不用在继承“敌人”脚本的子类中去赋值动画名称了,相比在脚本中赋值动画名来说,在Inspector中改动画的名字显得轻松多勒! 如图: 只需要将需要用到的动画改名就可以了。 然后就是脚本了。 在_Script文件夹下的Enemy文件夹下新建一个脚本,名为“EnemyWolf”,双击打开脚本。 在最开始那儿就不能让其继承自Monobehavaour,而要改为Enemy。 然后覆盖先前啥都没写的Init函数,他将实现小狼属性的初始化: publicclassEnemyWolf: Enemy{ protectedoverridevoidInit() { SetAttribute("小狼",2,100,20,20,2,60,2.5f); } } 好了! 这个脚本就算完成了! 什么? ! 就这点? 木错,以后的敌人跟这个相差应该也不是很大,就是这样。 然后将一个狗叫的音效拖到脚本的m_sound上(浪叫不好找啊,就用狗叫来代替咯.....~~),并将音效赋给小狼的AudioSource组件,去掉勾选默认播放。 现在可以将脚本拖到场景里的小狼身上了,运行游戏,小狼开始在原地转悠起来,过一会儿叫一声.....并且看见玩家的角色后,还会跑过来一直跟着。 (本来是有问题的,调试了良久之后,放在这儿的脚本当然没有问题了......),现在初步的AI就完成了。 额......又调试了一下,发现还是有个小问题,就是敌人经常看不见玩家的角色似的? 直接无视? 又仔细调试了几遍,发现了原因所在,这个原因挺重要的,就在这儿提一下: 默认这些对象的position一般都是物体的最底部,也就是人的脚下之类的地方,直接发射射线的话,从地面射出的射线恐怕也只有射中别人的脚.......(╯□╰),所以发射射线时,应该让坐标向上抬起一段距离,所以应该改一改Enemy的脚本,把脚本中两处发射射线的地方,第一个起始位置加上一个Vector3.up: if(Physics.Raycast(m_transform.position+Vector3.up,m_forward,outm_hit,m_attackRadius,m_layerMask)) { 。 。 。 。 。 。 } 现在就没问题了,当角色进入攻击半径时,它甚至会进行攻击。 只是因为现在还没有将攻击的粒子赋给它,所以控制台会打印一个错误。 刚才用手机去了一趟官网,被打击勒...... 那么导入粒子资源包,创建一个_Particle文件夹存放,然后拖放一个粒子效果到小狼脚本上去,同时将小狼攻击音效拖放到其身上,再次运行游戏,便可以看见攻击效果了。 考虑到以后工程项目中都会存在这些资源,所以以后我都不会再提起导入资源包的事儿了。 虽然是这些行为看似没问题了,不过小狼的攻击明显过快,这里就还的设置一个攻击间隔,定义在最开始的“变量”标签中: protectedfloatm_attackDelay=2;//攻击延时 然后再Attack函数中进行攻击间隔的判断,只有当攻击间隔时间到了,小狼才会再次攻击: m_attackDelay-=Time.deltaTime;
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Unity3D 游戏 开发 笔记
![提示](https://static.bdocx.com/images/bang_tan.gif)