赞
踩
面试的时候,先是正常的面试,然后是笔试,最后是机试。算好一切准备得还算完善,就是机试题目坦克大战,里面会有几个要点老是忘记。在这里首先要郑重感谢我不知道在哪里看到的博客以及他的作者,正好解决了我的疑惑。当然最后面试成功也是面试官给我的极大耐心(大概给我五六个多小时来做,实际并没有要求完成时间……)
其实这个小demo,需要实现的功能十分简单:一个简单场景,几个敌方坦克,一个主角坦克,要求每个坦克都要有血条,地方坦克可以不用处理,也就是地方坦克就是个摆设,不用写AI,也不攻击,只用默默承受伤害就好了,主角坦克,可以移动,可以发射子弹,击中敌人,敌人掉血,数据也不用太关心。要求Demo基本正常运行,类、接口设计合理,也有简单的优化。
下面只分析一些要点,然后给出源码。
敌人坦克和主角坦克做一些简单Prefab,然后移除小的Collider,做一个大的足够包含坦克就可以,这么做的原因是不希望炮筒参与碰撞。主角坦克加上rigidbody。
子弹Prefab,需要合适Collider,和rigidbody,这样在打中敌人的时候是可以在OnCollisionEnter里面回收子弹,已经向敌人发送“你被打中了”的消息。在这里子弹就是一个小立方体:
血条Prefab,制作很简单,直接在场景中添加UGUI的Slider组件,修改Background颜色,禁用拖块儿,禁用交互。
这样的话Prefab就做完了,将子弹和血条放到Resources目录下,用于动态加载,一般来说都应该动态加载的,但这是小demo,为了简单,还是直接放到场景中吧。
坦克类Tank,主角和敌人有一些共有的属性,而且具体脚本都必须挂载在游戏物体上,所以来一个基类Tank,并且继承自MonoBehaviour,添加一些共有属性,生命值啊,最大生命值啊,移动速度啊,等等。敌人类Enemy,主角类Player。
对象池ObjectPool,实际项目开发过程中,会有很多敌人,有那么多敌人自然就有更多的血条,更不用说子弹满天飞,所以需要对象池,来避免频繁的创建和销毁对象所造成的性能上的损失。ObjectPool,同时因为需要全局调用,所以加上得附加单例属性。具体解决方案可以看看我的另一篇博客。这个坦克大战Demo里,就子弹使用了对象池。所以没有使用完整的对象池技术。
子弹Bullet类的设计,子弹有很多种,每种可能有所不同,共同点当然也有,所以Bullet可以作为基类,让具体的子弹继承。本Demo中,那个立方体子弹就是Bullet类的实例化。你懂的。
敌人管理器类EnemyManager,用于管理场景中所有的敌人。不同功能决定了不同的数据结构和算法,这里就是一个List,保存GameObject。
血条类HpBar,用于处理血条的相关逻辑。初始化,被攻击时掉血等;
首先要说的就是碰撞相关的,两个GameObject要发生碰撞,必须两个都要有碰撞器Collider,同时运动的一方需要有Rigidbody(这也是笔试常见题目);
在设置血条的位置的时候,要是用到Camera.main.WorldToScreenPoint,将世界坐标转换到屏幕坐标,参数是设置好了的血条的世界坐标坐标。血条实例化后是在Canvas目录下,所以需要将她应该在的世界坐标位置转换成屏幕坐标,用于设置她在Canvas下的像素坐标。
子弹 Spawn出来之后,需要将它的位置设置到枪口生成点,世界下的旋转和生成点保持一致,最后才添加力往正前方发射出去,千万别将子弹的transform.parent设置成生成点,会有很有趣的事情发生;
不要什么操作都放到Update里面,例如,将控制主角坦克移动Rigidbody.MovePosition的逻辑放在Update里,又将控制UI显示的逻辑也放在了Update里面,UI发生了意想不到的鬼畜事件,UI显示,这里是血条应该放在LateUpdate里面;
同样是上面类似的问题,一般来说Rigidbody相关操作属于物理方面的,所以应该放在FixedUpdate函数里,如果放在Update里面的话,穿墙而过会毫无阻碍,常见的穿透问题,将Rigibody移动操作放在FixedUpdate里后还发生的话,可以设置碰撞检测的机制。具体请百度。
主角坦克基座和炮塔具有不同的运动方式,首先来说基座,通过键盘的w、s、a、d来控制坦克的移动。
x = Input.GetAxis("Horizontal");
y = Input.GetAxis("Vertical");
if(Mathf.Abs(x)>0||Mathf.Abs(y)>0)
{
rigid.MovePosition(transform.position + new Vector3(x, 0, y) * Speed * Time.deltaTime);
}
主角正在向正前方移动,然后突然向右移动,这时便涉及到旋转,这里需要将主角的正前方向右旋转90度,Quaternion.LookRotation便能够得到这个旋转的度数,只是她作为四元数不易读,但是可以转换成欧拉角,LookRotation第一参数是需要目标方向,第二个参数是旋转轴,这个函数的意思是,将正前方表示的向量方向绕着指定的旋转轴旋转到目标方向向量后所需旋转的角度。
得到这角度之后,可知直接transform.rotation设置,这里因为是Rigidbody,所以使用Rigibody.MoveRotation也不错,另一方面,为了更好的表现,可以使用插值,Quaternion.Lerp或者Quaternion.Slerp,听说后者更好,没错,是听说
Vector3 targetDirection = new Vector3(h, 0f, v);
Quaternion targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
Quaternion newRotation = Quaternion.Lerp(rigid.rotation, targetRotation, Speed * Time.deltaTime);
rigid.MoveRotation(newRotation);
旋转到这里就完了,其他的好像没有什么好说的了,源代码实现了炮塔的两种控制方式(需要在代码里修改)
1、炮塔指向离得最近的敌人;
2、通过鼠标控制炮塔的旋转(射线Ray);
有需要的可以下载源代码:
https://gitee.com/xianglinlove/Tank
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。