游戏简介:一个只能移动枪口的射击游戏,敌人会在前方场景随机生成,需要玩家在20秒的时间内尽可能多地开枪击杀。
1.枪支随鼠标移动
先导入网上找到的场景模型、枪支模型(网上找来的免费资源,感谢网友!),如图:
然后对枪编写脚本:
为方便就不事先获得枪支的Transform引用,直接gameObject.GetComponent调用Transform组件下的LookAt方法,该方法可使枪向射线与场景碰撞点处调整方向,因此事先要将为场景模型添加Mesh Collider,也因此在游戏测试时如果将鼠标指向天空等无碰撞体的地方,枪支则不会移动。(可通过在背景处创建一个蓝色的Plane当作天空来解决此问题)
在为场景模型添加碰撞体时,有时候会有多种碰撞体可选,因为这里我们要碰撞体仅仅是为了得到一个碰撞点令枪可以移动,所以可以选择简单一些的碰撞体模型,一般碰撞体的复杂程度可由上图中的verts(顶点)来确定,顶点越少,计算量越小,游戏运行越流畅。
注意:测试时可能会发现枪支随鼠标的旋转很不自然(绕着枪中心处旋转),现实中应该是绕着射手的肩膀旋转,因此应将旋转中心设置在枪的后部,这可以通过创建空物体以及父子关系来调整。因此以上脚本应该挂载到作为父物体的空物体上:
2. 枪支红点效果
在gun父物体下再创建一个空物体RedPoint,位置设置在枪口(因为红光是从枪口射出的,不是从枪中间射出的),并为其添加Line Renderer组件,设置好其颜色、宽度。(具体步骤上文“线特效”处有提到)
然后补充脚本:
private Ray ray;
private RaycastHit hit;
private Transform gunTransform;
private Transform pointTransform;
private LineRenderer rayLineRenderer;
void Start()
{
gunTransform = gameObject.GetComponent<Transform>();
pointTransform = gunTransform.FindChild("RedPoint");
rayLineRenderer = pointTransform.gameObject.GetComponent<LineRenderer>();
}
void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray,out hit))
{
gunTransform.LookAt(hit.point);
rayLineRenderer.SetPosition(0, pointTransform.position);
rayLineRenderer.SetPosition(1, hit.point);
}
}
这里要通过gun下面的子物体RedPoint生成红光,但脚本是挂载在gun上的,因此要通过FindChild方法得到RedPoint及其LineRenderer组件的引用。
最后通过SetPosition绘制红线,这里有两个参数,第一个是射线经过的路程上的拐点的索引,第二个是位置。脚本中设置了两个拐点(其实就是一条线段,如果有三个不在同一直线上的拐点,则红线由两条线段组成),最终效果:
3.敌人生成与击倒
导入僵尸模型,调整其大小,并为其及其所有零部件(子物体)的标签设置为Zombie。
因为模型本身没有自带碰撞体组件,需要手动添加,因为本人使用的模型过于复杂(还有动画效果),因此仅对父物体添加一个胶囊型的碰撞体(覆盖其头部与身体)。考虑到其动画会使其向前稍微移动,因此碰撞体的位置靠前一些。
然后编写脚本:(接在rayLineRenderer.SetPosition后面)
if (hit.collider.tag == "Zombie" && Input.GetMouseButtonDown(0))
{
zombieTransform = hit.collider.gameObject.GetComponent<Transform>();
zombieTransform.Rotate(Vector3.left, 90);
GameObject.Destroy(hit.collider.gameObject, 2);
}
当向鼠标指向处发射的射线碰撞到的是僵尸,且按下鼠标左键(开火),则首先获取到僵尸的Transform组件的引用,然后旋转(击倒效果),并在2秒后销毁该物体。
如果有较好的模型,可对模型的所有零部件(子物体)添加碰撞体,则脚本应该为:(接在rayLineRenderer.SetPosition后面)
if (hit.collider.tag == "Zombie" && Input.GetMouseButtonDown(0))
{
Transform zombieParent = hit.collider.gameObject.GetComponent<Transform>().parent;
Transform[] zombie = zombieParent.GetComponentsInChildren<Transform>();
for (int i = 0; i < zombie.Length; i++)
{
zombie[i].gameObject.AddComponent<Rigidbody>();
}
zombieParent.Rotate(Vector3.left, 90);
GameObject.Destroy(zombieParent.gameObject, 2);
}
因为碰撞到的是模型的子物体,所以通过.parent得到父物体的Transform组件,进而通过GetComponentsInChildren得到所有零部件的Transform组件,最后通过循环对所有子物体添加Rigidbody,这样一来,僵尸会“四分五裂”,一方面因为重力,另一方面是因为各个子物体间的碰撞体有重合,添加Rigidbody后各部分会自发散开。
接下来新建一个实例化僵尸的脚本:
public GameObject prefabZombie;
float i = 0;
void Update()
{
if (i>1)
{
Vector3 position = new Vector3(Random.Range(-35,0),2.6f,Random.Range(-10,-5));
GameObject.Instantiate(prefabZombie, position, Quaternion.identity);
i = 0;
}
i += Time.deltaTime;
}
在合适的区域每秒生成一个僵尸,这里用累加Time.deltaTime的方式实现每秒执行,也可以用Invoke函数实现。
为了方便管理生成的僵尸,我们可以将所有生成的僵尸设置为一个空物体的子物体:
public GameObject prefabZombie;
public Transform zombieControler;
void Start()
{
zombieControler = gameObject.GetComponent<Transform>();
InvokeRepeating("Creat", 1, 1);
}
void Creat()
{
Vector3 position = new Vector3(Random.Range(-35, 0), 2.6f, Random.Range(-10, -5));
GameObject.Instantiate(prefabZombie, position, Quaternion.identity);
newZombie.GetComponent<Transform>().SetParent(zombieControler);
}
用SetParent方法即可实现,最后将该脚本挂载到空物体ZombieControler上即可。
4. 音效及界面UI
添加开枪音效:
private AudioSource gunSound;
void Start()
{
gunSound = gameObject.GetComponent<AudioSource>();
}
...
if (Input.GetMouseButtonDown(0))
{
gunSound.Play();
if (hit.collider.tag == "Zombie")
{
...
}
}
然后为挂载以上脚本的枪支模型添加Audio Source组件,选择合适的开枪音效即可。(注意不要勾选Play On Awake和Loop)
接下来创建UI界面,先将游戏分为三个状态:开始、游戏中以及结束,并编辑对应的UI(创建空物体,添加GUIText组件并调整文字位置、大小、颜色),以下是UI叠加在一起的Game界面:
开始界面要有游戏玩法的基本介绍以及“开始游戏”的按钮,游戏中需要显示分数以及剩余时间,游戏结束需要分数统计、“重新开始”按钮和“退出游戏”按钮。
接下来对父物体UI,创建脚本:
首先编辑一个控制游戏状态的方法(这里采用枚举类型,主要是为了直观,要我写可能就用0、1、2来表示了)。
并将UI在某状态是否显示进行规定:(类似的就省略不写了)
public enum GameState { Start, Gaming, End }
private GameObject startUI;
...
void Start()
{
startUI = GameObject.Find("Start");
...
ChangeState(GameState.Start);
}
public void ChangeState(GameState state)
{
if (state==GameState.Start)
{
startUI.SetActive(true);
gamingUI.SetActive(false);
endUI.SetActive(false);
...
}
if (state == GameState.Gaming)
{
...
}
if (state == GameState.End)
{
...
}
}
这里需要注意的是ChangeState要为public,因为其他脚本的一些操作也会改变游戏状态。(例如点击开始游戏按钮)
接下来编辑“开始游戏”按钮,以下脚本挂载在“StartSign”:
private UIContorl uiContorl;
void Start()
{
uiContorl = GameObject.Find("UI").GetComponent<UIContorl>();
}
void OnMouseDown()
{
uiContorl.ChangeState(UIContorl.GameState.Gaming);
}
首先查找到父物体UI挂载的脚本中的ChangeState方法,只要MouseDown就改变游戏状态。
同理可设置重新开始按钮和退出按钮,其中退出按钮极为简单:
void OnMouseDown()
{
Application.Quit();
}
5. 分数、时间显示及部分细节
当在游戏开始和结束状态时,枪支无法移动,因此要对Move脚本进行编辑:
public void ChangeGunMove(bool state)
{
f = state;
}
void Update()
{
if (f)
{
...
}
}
然后在ChangeState方法中的对应阶段设置state为true或false即可。
开始和结束阶段僵尸也无法生成,因此Invoke不能写在Start里面,更改CreateZombie脚本:(以下为CreateZombie脚本的完整版)
在ChangeState的Gaming状态下调用CreateZombie即可,另外,在游戏结束时,除了要CancelInvoke,还要把玩家未消灭的僵尸清除。
接下来要在Gaming中显示分数,在UIControl脚本中添加:
public void AddScore()
{
score++;
scoreText.text = "击杀数:"+score;
}
然后在Move脚本中添加:
if (hit.collider.tag == "Zombie")
{
uiContorl.AddScore();
...
}
每次进入到Gaming状态,要重置分数,在结束状态也要再显示一次分数:
if (state == GameState.Gaming)
{
score = 0;
scoreText.text = "击杀数:" + score;
...
}
if (state == GameState.End)
{
finalScore.text = "最终击杀数:" + score;
...
}
接下来显示剩余时间,在UIControl脚本中添加:
void Update()
{
if (startTime)
{
time -= Time.deltaTime;
timeText.text = "剩余时间:" + Math.Round(time, 2);
if (time < 0)
{
ChangeState(GameState.End);
startTime = false;
}
}
}
startTime为bool类型,因为Update时刻运行,因此添加该变量确保倒计时只在Gaming状态下运行。
if (state == GameState.Gaming)
{
startTime = true;
time = 20;
...
}
至此全部脚本编写完毕。
6. 效果及源代码
Game界面:
Build后运行结果:(分别是开始界面、游戏界面、结束界面)
原谅我懒得细调开始和结束UI的位置……
最后放出Move脚本:
UIControll脚本:
最后提醒:
做项目前一定要仔细规划(游戏阶段划分、资源模型的管理、脚本,尤其是各种变量名编写一定要注意,我在写的时候差点被自己编的变量名绕晕),此外还有脚本中各个变量的访问修饰符(public还是private一定要搞清楚)。
实际上,如果是个人开发,变量全写public也可以,但这并不是什么好习惯,尤其是在日后团队协作全写public就是坑队友嘛,所以建议现在开始就养成好习惯,细究public和private。
本文部分内容来自擅码网(http://www.mkcode.net)Unity 3D课程,经本人学习、整理得来,若有错漏,欢迎指正!