人人都能写游戏系列(三)Unity 3D平衡球游戏

news/2024/10/19 6:19:45/

目录

  • 引言
    • 开始的准备
  • 基本代码
    • 控制小球的移动
      • 更进一步
    • 相机跟随
  • 制作道具
    • 大风车
    • 跳板
      • 更进一步
    • 循环移动的木板
      • 更进一步
    • 一次性上升踏板
    • 火炮
      • 更进一步
    • 小结
  • 支持我

引言

本系列中,我会在0美术的情况下,教大家开发几款简单的小游戏。适合Unity的初学者。
本系列其他游戏开发
今天要开发的就是本系列教程中的第一篇3D游戏教程,过程比较繁琐,我会分几个部分一一讲解。

开始的准备

老规矩,我们来新建一个项目,起名为BalanceBall(平衡球)。
在这里插入图片描述
先来布置一个简单的场景,以方便我们用来测试接下来要做的各种道具。
场景很简单,添加一个我们的核心小球(sphere),一个广阔的场地(plane)。
在这里插入图片描述
接下来我们调整一下小球的位置,大小以及命名,并给小球添加上一个rigidbody组件。
在这里插入图片描述
因为小球是我们的玩家(player),所以我们约定俗成的,给小球设置一个tag,在unity中已经有现成的player的tag了,我们直接选上就可以,这个tag在后面,会有很重要的使用。
在这里插入图片描述
我们的基础准备,到这里就可以了。

基本代码

控制小球的移动

在平衡球游戏中,小球的移动是重中之重,如果小球不能动,那么我们做出花来也没有用,所以,我们就创建一个名字叫Player的脚本,用来控制小球的移动。
在这里插入图片描述
让小球动起来,我们有很多很多种方式,比较常用的是直接操作的小球的position,但是这种方法,没有缓慢加速的功能,小球也不会旋转,会显得很滑,很假。所以我们这里采用更加真实的方法,就好像真实世界中小球受到了重力作用移动了一样。所以我们这里,使用rigidbody来给小球添加外力。
我们可以用Input.GetKey(KeyCode.W);的方式来添加四个判断(wasd),来控制小球的移向,我们也可以用更高级的Axis来控制,Axis可以模拟轴移动,类似摇杆的感觉。
我们还希望能在编辑器中随意控制小球移动的速度,所以还需要一个public的变量。
我们可能会反复使用小球的rigidbody,所以我们为了效率,要把它存储起来。
有了以上的内容,我们就可以写出小球移动的脚本了。

	Rigidbody rigid;public float force=5;void Start () {rigid = transform.GetComponent<Rigidbody>();}void Update () {rigid.AddForce(new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical"))*force);}

将脚本挂载到小球上,运行游戏,我们的小球已经可以自由运动了。

更进一步

我们的小球可以运动了,但是它很难停下来,这也是不太合理的。我们希望可以控制它停下来。
那么接下来,我们就要写脚本让小球停下来了。
有同学说了,停住还不容易,只要让小球的速度为0就可以了。
但是我们更希望小球是缓慢停住,而不是突然停住,那我们应该怎么办呢?
答案当然是插值了。我在人人都能写游戏系列(一)Unity简单跳一跳游戏开发一文中使用了平滑插值,用来压缩方块。这里我们当然也可以使用平滑插值,为了学习到更多的内容,我们这回使用mathf中的lerp,线性插值来解决这个问题。
lerp是很简单的,需要三个参数, (float a, float b, float t),a就是起点,b就是终点,t就是0-1的范围,此函数返回float,是(b-a)*t+a的值(就是一次线性函数),有了这些知识,我们就可以动手升级我们的小球脚本了。

完成的小球脚本如下。

using UnityEngine;public class Player : MonoBehaviour {Rigidbody rigid;public float force=5;//用来控制刹车的快慢public float reduce = 0.1f;void Start () {rigid = transform.GetComponent<Rigidbody>();}void Update () {rigid.AddForce(new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical"))*force);//按住空格,小球会缓慢减速。if(Input.GetKey(KeyCode.Space)){rigid.velocity = new Vector3(Mathf.Lerp(rigid.velocity.x, 0, reduce),rigid.velocity.y,Mathf.Lerp(rigid.velocity.z, 0, reduce)); }}
}

至此,小球的脚本已经完成了。
tips:Input.GetKey和Input.GetKeyDown都可以获取按键的值,区别在于GetKeyDown只触发一次,GetKey会持续触发。

相机跟随

我们在运行游戏的时候发现,我们的小球忽远忽近,感觉很怪,所以我们需要相机始终跟随着小球。
我们很容易就能发现,相机应该始终在小球的后方,相差一个固定的值。
我们创建一个名字叫Track的脚本。
在这里插入图片描述
我们希望能在编辑器中调整相机和小球的偏差,而不是每次运行游戏才知道调整的合适不合适,这样有助于我们提升效率。所以我们在脚本的最开始,加上[ExecuteInEditMode],表示,我们不运行的游戏的时候,此脚本也可以工作。
在这里插入图片描述
为了跟随小球,我们很明显的需要一个小球的位置,为此,我们可以通过Find函数来寻找场景的中的物体,也可以通过public的方法来拖拽复职,我们这里选用简单的public方式。

public Transform player;

为了性能,我们需要保存一下相机自己的位置。
为了方便调整,我们需要一个public的变量,用来表示相机到小球的距离,我们通常在LateUpdate()中更新相机位置,我们还需要相机看向小球。有了这些想法,脚本很容易就写完了。

using UnityEngine;
[ExecuteInEditMode]
public class Track : MonoBehaviour 
{public Transform player;Transform trans;public Vector3 dis;// Use this for initializationvoid Start () {trans = this.transform;}void LateUpdate(){trans.position = player.position+dis;trans.LookAt(player);}
}

将脚本挂载到相机上,然后在编辑器面板中调整dis到一个自己觉得合适的值。
在这里插入图片描述
运行下游戏,是不是我们的小球已经有模有样了?

制作道具

光有个小球,我们还是什么都干不了的,游戏的魅力在于就是有各种各样的关卡和道具,在平衡球游戏中,主要就是道具了。下面我们就来制作各种各样的道具。

大风车

大风车就是能把小球打飞的那种。因为没有美术,我们就只能女留房号男自强,咳咳,是自己制作了。
我们在场景中创建一个Cylinder(圆柱)
在这里插入图片描述
我们更改一下它的长宽高(缩放),让它变成细长条。并沿着x轴旋转90度(放倒)。

在这里插入图片描述
同样的,我们再创建一个圆柱,然后调整一下它的旋转,让其为直角,风车的模型就有了。
在这里插入图片描述
中间的小球很影响咱们的视野,我们隐藏掉它。
在这里插入图片描述
为了方便我们写脚本控制风车,我们创建一个空物体,让这两根圆柱都成为其的子物体。
在这里插入图片描述
模型已经ok,我们来写脚本让它旋转起来吧。
创建一个名叫Windmill的脚本。
在这里插入图片描述
风车的旋转也很简单,我们只要让他不停的转就可以了,为了保证帧数稳定,我们使用FixedUpdate。
这里的转,我们使用Transform的Rotate方法。为什么不使用刚体旋转?因为我们希望风车在和小球发生碰撞时,也不会减速和跳起。
经过本系列前面的教程,这种脚本应该对你来说很简单了。

using UnityEngine;public class Windmill : MonoBehaviour {Transform trans;public float speed = 30;void Start () {trans = this.transform;}void FixedUpdate(){trans.Rotate(Vector3.up * speed * Time.fixedDeltaTime);}
}

将脚本挂载到风车的父物体的GameObject上,运行游戏,我们就可以看到风车在转动了。
在这里插入图片描述
tips:这里我将小球挪了个位置,防止小球在风车内部
运行测试发现,小球确实不能影响风车的转动,风车确实可以影响小球的运动。(如果你觉得风车的长度太短,你也可以自行修改)
修改下风车的命名,并将其拖入下方,成为prefab。
在这里插入图片描述
我们第一个道具就完成了!有没有点成就感啊,嘿嘿。

跳板

跳板是小球踩上去以后,会瞬间的跳起来的一个道具。
借由跳板,我们的小球就可以跳上更高的平台。
还是先来制作跳板。跳板的模型选择可以有多种多样,为了更广的知识面,我们选用Quad(四边形)。
在这里插入图片描述
为了和地面区分开来,我们给跳板挂载一个红色的材质。
在这里插入图片描述
接着,调整四边形,让它躺在地上的合适位置,方便我们的测试。
在这里插入图片描述
我们采用触碰触发的方式触发跳跃,而不是碰撞的方式触发跳跃,所以要勾选上如图所示内容。
在这里插入图片描述
至此,跳板的模型,我们就做好了,接下来来写跳板的脚本。
跳板的脚本也很简单,主要是检测触发器被触发,然后施加给小球一个瞬间的力,就可以让小球跳起了。

using UnityEngine;public class Jump : MonoBehaviour {public float force=10;private void OnTriggerEnter(Collider other){if(other.CompareTag("Player")){other.GetComponent<Rigidbody>().AddForce(Vector3.up * force,ForceMode.Impulse);}}
}

这里就使用了tag标签,只有当小球触碰触发器的时候才会跳跃,别的物体触碰它并不会被弹起。

更进一步

我们发现,小球在刚刚进入红色区域的时候就已经被弹起了,我们更希望小球完全进入红色区域在被弹起,那我们应该怎么办呢?
这个问题有两种办法,第一种是写脚本,计算小球中心到平面的距离,第二种就是修改触发器的面积。
这里采用第二种方法,把触发器的面积变小,自然小球就能更好的更自然的弹起来了。
删除mesh collider,添加box collider,将其面积变小,即可完成上述需求。
在这里插入图片描述
将跳板更名为Jump,保存为prefab,至此,我们的第二个道具,跳板也完成了。

循环移动的木板

在很多游戏中,就存在循环移动的木板,把玩家从一岸移动到另一岸。这里,我们也创建一个可以循环移动的木板。
还是先来场景中创建我们的板子,当然了,这里用cube最方便了。
在这里插入图片描述
我们的测试场景中已经有很多东西了,我们已经保存了prefab,就可以删除一些不必要的东西了。我们把风车 跳板 大地都删除掉,让我们的player占在方块上。
在这里插入图片描述
然后在创建一个方块,表示成河对面。
在这里插入图片描述
在创建一个方块,将其拍扁,它就是我们的木板了。
在这里插入图片描述
场景有了,我们来写脚本吧。
我们不希望直接在脚本中为移动距离赋值,因为我们更希望我们的板子可以通用,不受到河岸距离的限制。
那我们应该怎么做呢?
很简单,检测碰撞!只要碰到河岸就换方向
这样我们就可以实现了更广泛的适配。
脚本如下:

using UnityEngine;public class Board : MonoBehaviour {Transform trans;//前进方向int dir = 1;public float speed = 1;void Start () {trans = this.transform;}void FixedUpdate () {trans.position += Vector3.forward * speed*Time.fixedDeltaTime*dir;}private void OnCollisionEnter(Collision collision){if(!collision.gameObject.CompareTag("Player"))dir = -dir;}
}

这样,我们的循环移动的木板也做好了。

更进一步

我们运行发现,板子移动速度太快,我们的小球很难在板子上站稳,直接操作position更是无摩擦的抽板子,是在是地狱式难度。为了降低难度,为了和物理世界规律相同,我们将移动的代码改为使用rigidbody下的MovePosition。于是,脚本变成了如下所示:

using UnityEngine;public class Board : MonoBehaviour {Rigidbody rigid;//前进方向int dir = 1;public float speed = 1;void Start () {rigid = GetComponent<Rigidbody>();}void FixedUpdate () {rigid.MovePosition(rigid.position + Vector3.forward * speed * Time.fixedDeltaTime * dir);}private void OnCollisionEnter(Collision collision){if(!collision.gameObject.CompareTag("Player"))dir = -dir;}
}

经过测试发现,我们的小球站上去后,给了板子一个摩擦力,板子会运行的飞快,这是不合理的,所以我们这回不在移动位置,只控制移动的速度。

using UnityEngine;public class Board : MonoBehaviour {Rigidbody rigid;//前进方向int dir = 1;public float speed = 1;void Start () {rigid = GetComponent<Rigidbody>();}void FixedUpdate () {rigid.velocity = Vector3.forward * speed * Time.fixedDeltaTime * dir;}private void OnCollisionEnter(Collision collision){if(!collision.gameObject.CompareTag("Player"))dir = -dir;}
}

我们给木板添加上一个材质球,更改一下其颜色,与河岸分割开来,更名并保存为prefab,我们的木板制作,到此结束。
在这里插入图片描述

一次性上升踏板

我们这回来制作一次性上升踏板,可以帮助我们的小球上升到更高的位置,这里涉及了两个脚本之间的通讯。
还是回到我们的场景,删除我们的board,然后再添加一个cube。
在这里插入图片描述
调整缩放,使其变成一个扁木板,并创建一个小球,赋予小球红色材质,并安放到板子的合适位置上,使小球有所嵌入到板子中,因为这样看起来比较像开关。
在这里插入图片描述
接下来,我们要做的就是,当小球触碰开关的时候,浮板会上升,然后隐藏掉开关,制作成一次性上升浮板。
很显然,我们需要这个开关和板子之间的通信,也需要上升的速度,上升的最大高度,这样的参数。
这个上升,我们依然像循环的木板一样,有很多种方案的选择。这里我们先采用刚体的MovePosition看看
先来写开关的代码

using UnityEngine;public class Switch : MonoBehaviour {private void OnTriggerEnter(Collider other){if(other.gameObject.CompareTag("Player")){//通知板子上升//隐藏自己this.gameObject.SetActive(false);}}
}

再来写板子上升的代码

using UnityEngine;public class UpBoard : MonoBehaviour 
{Rigidbody rigid;//指示是否要上升bool canup = false;//1向上走,-1向下走public int dir = 1;//上升速度public float speed = 20;//最大上升高度public float max = 10;//记录最开始的y位置float startY;void Start () {rigid = GetComponent<Rigidbody>();startY = rigid.position.y;}void FixedUpdate(){if (!canup)return;rigid.MovePosition(rigid.position +Vector3.up*dir*speed*Time.fixedDeltaTime);if (rigid.position.y - startY >= max)canup = false;}//用于和开关通信public void MoveUp(){}
}

现在我们来写两个脚本通信的方法,这件事情上我们也有很多种选择,比如共用一个全局变量,或者直接Find到物体,再通过GetComponent找到脚本,再修改其中的值,直接用public赋值等等。
我们这里遵循简单原则,选择public赋值的方法。那么我们先补全开关的脚本。

using UnityEngine;public class Switch : MonoBehaviour {//在面板赋值,需要注意,必须是挂载的物体。public UpBoard up;private void OnTriggerEnter(Collider other){if(other.gameObject.CompareTag("Player")){//通知板子上升up.MoveUp();//隐藏自己this.gameObject.SetActive(false);}}
}

这个要怎么赋值?先把开关脚本和上升浮板脚本挂好,然后按图所示。
在这里插入图片描述
注意,这里不能从下面的面板中拖拽上去,那样会在内存创建一个新的实例,是不会达到我们的效果的。
我们的moveup还是空方法,我们需要补全他。
观看我们的脚本可以发现,我们只需要将canup赋值为true,即可让脚本上升了,所以,完整的浮板脚本如下。

using UnityEngine;public class UpBoard : MonoBehaviour 
{Rigidbody rigid;//指示是否要上升bool canup = false;//1向上走,-1向下走public int dir = 1;//上升速度public float speed = 20;//最大上升高度public float max = 10;//记录最开始的y位置float startY;void Start () {rigid = GetComponent<Rigidbody>();startY = rigid.position.y;}void FixedUpdate(){if (!canup)return;rigid.MovePosition(rigid.position +Vector3.up*dir*speed*Time.fixedDeltaTime);if (rigid.position.y - startY >= max)canup = false;}//用于和开关通信public void MoveUp(){canup = true;}
}

保存脚本,我们运行游戏,发现报错了
在这里插入图片描述
这是因为,我们的上升浮板并没有添加一个rigidbody组件,所以我们将其填上,再运行游戏,我们发现,我们的开关没有消失,小球碰撞到了开关,没能穿过它,这是因为我们的开关的碰撞器没有勾选Is Trigger。
勾选上,再次运行游戏,我们发现我们的小球把浮板砸下去了,这是因为物理组件的特性,所以我们需要锁止浮板的轴。
在这里插入图片描述
我们运行发现,我们的板子因为受到小球的重力作用,仍会下降,这是不符合我们的需要的,而且小球不一定可以一瞬间就碰到开关,这样的体验很不好,所以我们采用MovePosition的方案失败了。所以这里我们采用直接移动板子的方法。
完整的上升浮板代码如下

using UnityEngine;public class UpBoard : MonoBehaviour 
{Transform trans;//指示是否要上升bool canup = false;//1向上走,-1向下走public int dir = 1;//上升速度public float speed = 1;//最大上升高度public float max = 10;//记录最开始的y位置float startY;void Start () {trans = this.transform;startY = trans.position.y;}void FixedUpdate(){if (!canup)return;trans.position += Vector3.up * dir * speed * Time.fixedDeltaTime;if (trans.position.y - startY >= max)canup = false;}//用于和开关通信public void MoveUp(){canup = true;}
}

删除掉上升浮板的rigidbody组件,调整速度大小,调整命名,最终效果如下
在这里插入图片描述
在这里插入图片描述
保存为prefab,我们的上升浮板就制作完成了。

火炮

这是本篇教程中最难的部分,我们还是先来到场景中,制作一个大炮。
先将cube的x拉长,方便我们摆放大炮。
在这里插入图片描述
我们知道,大炮是由两个轮子,一个炮筒组成的。
这三个部分都可以用圆柱体来代替,所以,我们就创建三个圆柱体。把两个改成扁扁的轮子,一个改成长长的炮筒。
这就是我造的轮子了。
在这里插入图片描述
大家可以按这个参数造轮子,也可以按自己觉得合适的大小来。
炮管的参数
在这里插入图片描述
这里我觉得刚才的轮子太小了,又进行了一波修改,反正只要看起来差不多就行了,毕竟这种东西,也没有什么定数。
将两个轮子摆在炮筒旁边,我们就完成了大炮的基本构建。
附上新的轮子参数
在这里插入图片描述
我们知道,unity在操作物体的时候,是按物体的mesh中心来的,圆柱体的mesh中心在圆柱的中间。
而我们希望操作大炮是操作圆柱的底部,因为操作中间,当炮筒变成90度的时候,很可能就离开轮子了,那看起来太假了。
我们知道,操作父物体的时候,子物体会跟随父物体的变化而变化,所以这个问题就很容易解决了,我们直接在炮筒的底部,添加一个空物体,作为炮管的父物体,这样,操作这个空物体的transform,炮筒就会跟着动了。
先创建物体,将物体摆放在圆柱的底部,具体做法是,x,z坐标和圆柱一致,只修改y值,就可以保证是在圆柱底部的中央了。
在这里插入图片描述
接着,把炮筒挂为其的子物体。这里直接拖拽炮筒就可以了。
在这里插入图片描述
我们还需要一个发射点,就是小球是从哪里发射出去的,显然,这应该是在炮筒的顶部,而且随着炮筒的移动而移动,根据父物体移动子物体会跟随的道理,我们再创建一个空物体,调整好位置后,挂载到炮筒上,变成炮筒的子物体。
在这里插入图片描述
这里要注意,我们必须把发射点离炮筒远一些,因为小球有半径,如果不够远,小球会卡在炮筒里无法发射。
这个位置也是要经过实际测试才会知道,现在我就先把它摆在这里了。
为了方便管理大炮,我们还需要创建一个空物体,作为整个大炮的父物体。
在这里插入图片描述
然后,我们删除掉大炮子物体的所有碰撞体,我们来自己给大炮做一个碰撞模型。
在这里插入图片描述
现在大炮的碰撞体已经全部删除了,我们给大炮更改下命名,方便观察和说明。
在这里插入图片描述
我们给Cannon添加上一个Box Collider。
在这里插入图片描述
然后点击,尽可能的调整碰撞碰撞范围,使碰撞体紧贴大炮。
在这里插入图片描述
我调整的效果如下
在这里插入图片描述
好了,大炮已经造好,创建一个名为Cannon的脚本,我们可以开始写代码了。
在这里插入图片描述
这里的思路是,当小球碰撞到了大炮,小球就进入大炮,然后我们可以调整炮管的角度,按下指定键,就可以发射了。
有几个关键的地方需要注意,首先,小球未和大炮发生碰撞,我们不能操作大炮。其次,如何体现出小球进入了大炮,再然后,如何让摄像机跟随大炮。这几个想明白了,脚本就很好写了。
先来写碰撞检测和如何移动大炮的角度吧。

using UnityEngine;public class Cannon : MonoBehaviour {//移动点的位置。public Transform trans;Transform player;//炮管的位置。public Transform gun;//移动炮管的速度public float speed=20;void Start () {}void Update () {//表示没碰到大炮时,大炮不会被操作。if (player == null)return;trans.Rotate(Vector3.right * speed * Time.deltaTime * Input.GetAxis("Vertical"));}void OnCollisionEnter(Collision collision){if(collision.gameObject.CompareTag("Player")){player = collision.gameObject.transform;player.gameObject.SetActive(false);//这里用了取巧的办法,让摄像机看向大炮。player.position = gun.position;}}
}

将脚本挂载到Cannon上,拖过拖拽赋值,我们运行游戏,发现脚本很好的运行了。
在这里插入图片描述
接下来,我们来写如何让大炮发射小球。
为了和前面的按键区分开来,我们这里使用g键发射。

using UnityEngine;public class Cannon : MonoBehaviour {//移动点的位置。public Transform trans;Transform player;//炮管的位置。public Transform gun;//发射点的位置public Transform shoot;//移动炮管的速度public float speed=20;//发射力public float force = 10;void Start () {}void Update () {//表示没碰到大炮时,大炮不会被操作。if (player == null)return;trans.Rotate(Vector3.right * speed * Time.deltaTime * Input.GetAxis("Vertical"));if(Input.GetKeyDown(KeyCode.G)){//让小球到发射点位置player.position = shoot.position;//让小球可见player.gameObject.SetActive(true);//施加一个瞬时力,方向是移动点的y轴朝向。player.GetComponent<Rigidbody>().AddForce(force*trans.up,ForceMode.Impulse);}}void OnCollisionEnter(Collision collision){if(collision.gameObject.CompareTag("Player")){player = collision.gameObject.transform;player.gameObject.SetActive(false);//这里用了取巧的办法,让摄像机看向大炮。player.position = gun.position;}}
}

保存代码,在场景赋值,调整好角度,按下g,就可以发射了。
在这里插入图片描述

更进一步

我们运行游戏会发现,大炮可以360度旋转,会插到地面中去,所以,我们要调整大炮所能旋转的角度。比如最基本的,我们让大炮只能0-90度旋转。
那我们应该怎么做呢?
有同学说了,这还不简单?只要判断transform.rotation.x就可以了。
事实是这样么?
在transform的文档中是这么写的
在这里插入图片描述
这个返回的是Quaternion,并不是所谓的角度。
那么Quaternion,又是什么呢?
文档中说了

四元数用于表示旋转。它们结构紧凑,不受万向节锁定的影响,可以轻松插补。 Unity内部使用四元数来表示所有旋转。

四元数是一个很不容易理解的概念。它的出现是为了解决欧拉旋转中的万向锁问题。具体的更多的关于四元数的内容,大家可以自行百度,这里因为篇幅原因,只讲如何使用。
四元数和欧拉角的互化,有着一套极为繁琐的公式,不过别担心,unity引擎已经为我们做好了这套转化工作。
通过四元数的eulerAngles方法,我们就可以轻松获取到xyz三轴的旋转角了。
有了这些知识,我们控制旋转角度看似就很简单了。

using UnityEngine;public class Cannon : MonoBehaviour {//移动点的位置。public Transform trans;Transform player;//炮管的位置。public Transform gun;//发射点的位置public Transform shoot;//移动炮管的速度public float speed=20;//发射力public float force = 10;void Start () {}void Update () {//表示没碰到大炮时,大炮不会被操作。if (player == null)return;//控制旋转Quaternion a = trans.rotation;if (a.eulerAngles.x >= 90 && Input.GetAxis("Vertical") >= 0)return;if(a.eulerAngles.x <= 0 && Input.GetAxis("Vertical") <= 0)return;trans.Rotate(Vector3.right * speed * Time.deltaTime * Input.GetAxis("Vertical"));if(Input.GetKeyDown(KeyCode.G)){//让小球到发射点位置player.position = shoot.position;//让小球可见player.gameObject.SetActive(true);//施加一个瞬时力,方向是移动点的朝向。player.GetComponent<Rigidbody>().AddForce(force*trans.up,ForceMode.Impulse);}}void OnCollisionEnter(Collision collision){if(collision.gameObject.CompareTag("Player")){player = collision.gameObject.transform;player.gameObject.SetActive(false);//这里用了取巧的办法,让摄像机看向大炮。player.position = gun.position;}}
}

我们保存脚本,回到场景中运行代码,我们发现,代码并没有按我们想象中的那样生效,这是为什么呢?
答案很简单,那是因为四元数返回的角度是从0-360度的,不存在负角,而场景中,则是正负0-180的,所以我们需要一步转化,将角度转化成我们面板看到的那样。

  float changeAngle(float angle){if (angle > 180)return angle - 360;return angle;}

有了这个,我们在把角度放到里面转化一下,这样子,我们的脚本就貌似完成了。

using UnityEngine;public class Cannon : MonoBehaviour {//移动点的位置。public Transform trans;Transform player;//炮管的位置。public Transform gun;//发射点的位置public Transform shoot;//移动炮管的速度public float speed=20;//发射力public float force = 10;void Start () {}void Update () {//表示没碰到大炮时,大炮不会被操作。if (player == null)return;Quaternion a = trans.rotation;//转化角度if (changeAngle(a.eulerAngles.x) >= 90 && Input.GetAxis("Vertical") >= 0)return;if(changeAngle(a.eulerAngles.x) <= 0 && Input.GetAxis("Vertical") <= 0)return;trans.Rotate(Vector3.right * speed * Time.deltaTime * Input.GetAxis("Vertical"));if(Input.GetKeyDown(KeyCode.G)){//让小球到发射点位置player.position = shoot.position;//让小球可见player.gameObject.SetActive(true);//施加一个瞬时力,方向是移动点的朝向。player.GetComponent<Rigidbody>().AddForce(force*trans.up,ForceMode.Impulse);}}void OnCollisionEnter(Collision collision){if(collision.gameObject.CompareTag("Player")){player = collision.gameObject.transform;player.gameObject.SetActive(false);//这里用了取巧的办法,让摄像机看向大炮。player.position = gun.position;}}float changeAngle(float angle){if (angle > 180)return angle - 360;return angle;}
}

我们再次运行发现,它有时候会生效,让炮筒无法旋转,有时候不会卡住,直接转到了底部。这是为什么呢?
通过Debug发现,a.eulerAngles.x返回的角在正方向上是0-90-0的过程。由于90为极限值,用户按住w时候,由于大炮速度过快,很容易就超过了90,并没有触发a.eulerAngles.x >= 90这个判断,而到底部之后,a.eulerAngles.x开始返回大于180度的角,经过转化,会触发changeAngle(a.eulerAngles.x) <= 0 这个判断,导致大炮卡住。如果在最开始,直接向后运动,会直接触发changeAngle(a.eulerAngles.x) <= 0 这个判断卡住,也就是说,我们的问题就在于90度这个位置。我们应该怎么办呢?
很简单,还是利用四元数,这回我们不是直接返回a.eulerAngles.x的角度去判断,而是通过a和世界正上方向的夹角来判断,这样,我们在正方向上也会返回大于90度的值,这个问题就解决了。

最后修改后的大炮代码如下:

using UnityEngine;public class Cannon : MonoBehaviour {//移动点的位置。public Transform trans;Transform player;//炮管的位置。public Transform gun;//发射点的位置public Transform shoot;//移动炮管的速度public float speed=20;//发射力public float force = 10;void Start () {}void Update () {//表示没碰到大炮时,大炮不会被操作。if (player == null)return;Quaternion a = trans.rotation;Quaternion c = Quaternion.Euler(Vector3.up);//c是世界的正上方向,通过判断ac夹角,我们来判断是否超过了90度。if (changeAngle(Quaternion.Angle(a,c)) >= 90 && Input.GetAxis("Vertical") >= 0)return;if(changeAngle(a.eulerAngles.x) <= 0 && Input.GetAxis("Vertical") <= 0)return;//既然都用了四元数了,我们也把旋转角改成四元数的模式吧!Quaternion b = Quaternion.Euler(Vector3.right * speed * Time.deltaTime * Input.GetAxis("Vertical"));trans.rotation = a * b;if(Input.GetKeyDown(KeyCode.G)){//让小球到发射点位置player.position = shoot.position;//让小球可见player.gameObject.SetActive(true);//施加一个瞬时力,方向是移动点的朝向。player.GetComponent<Rigidbody>().AddForce(force*trans.up,ForceMode.Impulse);}}void OnCollisionEnter(Collision collision){if(collision.gameObject.CompareTag("Player")){player = collision.gameObject.transform;player.gameObject.SetActive(false);//这里用了取巧的办法,让摄像机看向大炮。player.position = gun.position;}}float changeAngle(float angle){if (angle > 180)return angle - 360;return angle;}
}

小结

到此为止,我们的道具就制作完成了,由于篇幅原因,进一步的关卡/死亡/通关等判断和布置,请期待下一篇章。

支持我

您的支持,就是我创作的最大动力


http://www.ppmy.cn/news/375856.html

相关文章

系统维护工具GhostXP U盘制作方法

“谁有系统盘啊&#xff0c;借我一下。” “我有&#xff0c;不过在家呢” 装系统是不是挺无语的&#xff0c;需要有光盘。光盘这种东西那么大&#xff0c;揣兜里也不方便。 但是&#xff0c;随着存储技术的发展&#xff0c;U盘越来越普及了&#xff0c;容量也越来越大&#…

计算机软驱的连接方式,岛精仿真软驱、斯托尔USB软驱、斯坦格电脑横机软盘改U盘...

仿真软驱又名仿软驱接口的U盘驱动器&#xff0c;用于替代各种工控设备中的软驱&#xff0c;使设备的录入工具从软盘升级为U盘。仿真软驱改优盘适用&#xff1a;岛精、斯托尔、斯坦格、天元、慈星电脑横机田岛、百灵达、幸福、大豪绣花机环球、松下、雅马哈、中古、富士、西门子…

如何让你的手机U盘集PE工具、系统安装、无线破解等众多功能于一身

不久前&#xff0c;手里的U盘坏了&#xff0c;于是乎&#xff0c;又在网上淘了一个Type-C U盘&#xff0c;刚好手机电脑都可以用。 那么现在U有了&#xff0c;我们要做什么呢&#xff1f; 第一&#xff1a;让U盘插在手机上时&#xff0c;可以供手机读写&#xff0c;实现手机存…

android 鼠标 悬浮,鼠标悬浮球不闪退版本下载-鼠标悬浮球 安卓版v1.1-PC6安卓网...

鼠标悬浮球app是一款专为大屏手机准备的系统工具&#xff0c;鼠标悬浮球不闪退版本支持自定义悬浮球图标&#xff0c;用户可以轻松设置独特的悬浮球&#xff0c;鼠标悬浮球app&#xff0c;为你的手机带来便捷的操作体验。 软件介绍 鼠标悬浮球app从【全局负一屏】中单独提取出来…

【Unity3D】利用物体碰撞检测、键盘输入处理完成平衡球游戏

物体碰撞检测是游戏编程里面非常常用的功能&#xff0c;一旦碰到就怎么怎么样&#xff0c;比如掉血等。Unity3D直接就提供了物体碰撞检测的接口&#xff0c;实现起来很方便。借此功能&#xff0c;完成了一个平衡球游戏&#xff0c;同时说明Unity3D的物体碰撞检测、键盘输入处理…

晨枫U盘维护工具V2.0版(转)

-- 珠海晨枫工作室 制作教程&#xff1a; HDD模式 | ZIP模式 | U写入模式 | 本地硬盘安装 | 重装封装光盘[光盘版/量产] 更新说明&#xff1a; 2009.1.10: 1、增加支持更多的启动模式&#xff0c;现在支持USB-HDD、USB-ZIP、U写入、光盘启动、U盘量产USB-CDROM等多种启动模式&a…

linux下做pe工具,手工组合制作多功能U盘维护工具(LINUX、PE、DOS、GHOST)

以前都是采用的论坛大佬们的一键式搞定 今天突发奇想自己手工弄一下&#xff1a; 首先使用工具&#xff1a; USBBOOT (U盘格式化引导) UltraISO(ISO文件读取) 深度袖珍PE & Dos系统维护光盘V2.5 ISO文件 步骤&#xff1a; USBBOOT格式化成 USB-HDD模式 引导成功 然后用 U…

服务器优盘启动安装win7系统教程,晨枫u盘启动工具安装原版Win7的两种方法(32位64位系统通用)...

安装原版Win7两类方法 第一类方法(32位64位系统通用)&#xff1a; 具体步骤&#xff1a; 【1】先使用晨枫U盘启动制作工具制作完启动U盘(参照制作教程)。 【1】找到Windows7系统的iso镜像&#xff0c;用UltraISO或者WinRAR打开iso镜像&#xff0c;然后提取/解压所有文件到你的U…