一、 概述
1.1 项目类型
3D多人坦克大战
1.2 开发工具
·Unity2019.4.29
·Visual Studio 2019
二、 设计题目实现
2.1 概要设计
双人坦克游戏:
- 坦克:可移动旋转,发射炮弹
- 子弹:按一定方向一定速度发射;炮弹周围会产生冲击波,击中坦克或接触地面后爆炸
- 坦克生命:坦克被击中后血条相应变化;血条不随坦克的旋转而旋转;血条减到小于等于0后爆炸
- 相机:摄像机的大小及位置实时变化
- 音效:游戏背景音;坦克开动时的引擎声音;坦克静止时发动机待机的声音;炮弹爆炸时的的声音;炮弹蓄力的声音;炮弹发射时的声音;坦克爆炸的声音
玩家编号 |
移动旋转按键 |
炮弹发射按键 |
1 |
W S A D |
space |
2 |
↑ ↓ ← → |
enter |
一方死亡,则一轮游戏结束
2.2 详细设计与实现
2.2.1 项目场景设置
2.2.1.1项目工程设置
下载坦克大战资源包,并导入到Unity中.
在右上角选择Layout-->2 by 3.
Project面板右键选择OneColumn Layout.
在Game窗口选择分辨率16 :9.
2.2.2.2场景设置
创建Scence场景:Project面板选中Assets右键Create/Scene,命名MainScene,双击激活并保存。
将Hierarchy面板中的Directional Light删除
将Project/Prefabs/LevelArt拖如Hierarchy面板中
打开Window/Rendering/Lighting Set 面板打开停靠在inspector面板旁边。
2.2.2.3光线设置
去掉勾选Lighting/Debug Settings/Auto Generate
设置 Indirect Resolution 为 0.5 (合适的烘焙渲染帧率值)
修改Environment Lighting/Source 为Color
设置 Ambient Color( 72, 62, 113 )
点击Generate Lighting开始烘焙场景
2.2.2.4 镜头设置
选中Main Camera在Inspector面板中。
修改Main Camera的Transform组件Position属性为( -43, 42, -25 )
修改Rotation( 40, 60, 0 )
摄像机组件修改Projection属性为OrthograPhic
正交0rthographic(无消失点投影):无法判断距离
透视Perspective(有消失点投影):透视视图和我们从眼睛看到的视图是一样的
修改Clear Flags 属性为 Solid Color
修改Background颜色为 ( 80, 60, 50 )
保存场景。
2.2.2 坦克的移动旋转
2.2.2.1坦克创建
1. 将坦克模型Project/Models/Tank拖拽到Hierarchy面板中。
2. 制作坦克预制体
在_Completed-Assets文件夹下新建文件夹Prefabs,将坦克模型拖拽到该文件夹下。
- 在Hoierarchy中选中Tank
- 添加Rigidbody刚体组件(实现物理碰撞的关键组件)
给Tank添加Rigidbody 组件通过Rigidbody/Constraints 约束坦克的移动Y轴和旋转X,Z轴
- 添加Box Collider碰撞盒子(实现物理碰撞的关键组件)
给Tank添加Box Collider组件,修改Center( 0, 0.85, 0 ),Size( 1.5, 1.7, 1.6 ),使其完美地包裹住坦克
- 添加坦克运动时车轮后的烟雾效果
将Project面板中Prefabs/DushTrail拖拽到HierarcHy面板中Tank下,作为tank的子物体
复制一个DushTrail,分别重命名:LeftDustTrail,RightDustTrail,分别设置Position做标
(-0.5,0,-0.75),(0.5,0,-0.75)。
选中Hierarchy面板里的Tank,在Inspector面板, 设置标签Tag
3. 点击Aplay all,应用到预制体中。删除Hierarchy面板里的Tank,将_Completed-Assets/Prefabs下的预制体拖入Hierarchy面板中。
2.2.2.3添加脚本
1. 在_Completed-Assets文件夹下新建文件夹Scripts,在Scripts下新建Tank文件夹,以后与坦克有关的代码均放置在该文件夹下。在该文件夹下新建C# Scripts,命名为Tank Movement。将该脚本拖拽到Tank上。
2. 在Project Setting-->input Manager中设置按钮映射
即:
玩家编号 |
移动旋转按键 |
炮弹发射按键 |
1 |
W S A D |
space |
2 |
↑ ↓ ← → |
enter |
3. 打开脚本,完成脚本编写
2.2.2.4脚本编写
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankMovement : MonoBehaviour
{
private Rigidbody m_Rigidbody;
private float turnValue;//获取坦克左右旋转的输入值
private float moveValue;//获取坦克前后移动的输入值
[HideInInspector]
public int m_PlayerNumber = 1;//设置玩家编号 不同玩家用不同的按钮控制坦克实现移动
public float m_MoveSpeed = 0.5f;//设置坦克的前后移动速度
public float m_TurnSpeed = 3.6f;//设置坦克的左右旋转速度
private float m_MovementInputValue;//获取坦克前后移动的输入值
private float m_TurnInputValue;//获取坦克左右旋转的输入值
private string m_MovementAxisName;//前后移动轴名称
private string m_TurnAxisName;//左右旋转轴名称
/*----------------------------音效---------------------------------*/
public AudioSource m_MovementAudio;
public AudioClip m_EngineIdling;
public AudioClip m_EngineDriving;
public float m_PitchRange = 0.2f; //引擎频率区间
private float m_OriginalPitch;//引擎频率
/*---------------------------粒子特效--------------------------------*/
private ParticleSystem[] m_ParticleSystems;
private void Awake()
{
m_Rigidbody = this.gameObject.GetComponent<Rigidbody>();//获取Rigidbody组件
m_ParticleSystems = GetComponentsInChildren<ParticleSystem>();//获取粒子特效
}
void Start()
{
m_MovementAxisName = "Vertical" + m_PlayerNumber;
m_TurnAxisName = "Horizontal" + m_PlayerNumber;
//初始化引擎频率
m_OriginalPitch = m_MovementAudio.pitch;
}
void Update()
{
//更新输入轴信息
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
Sound();
}
private void FixedUpdate()
{
//移动
Vector3 mowement = transform.forward * m_MovementInputValue * m_MoveSpeed;
m_Rigidbody.MovePosition(m_Rigidbody.position + mowement);
//旋转
Quaternion turnRotate = Quaternion.Euler(0, m_TurnInputValue * m_TurnSpeed, 0);
m_Rigidbody.MoveRotation(m_Rigidbody.rotation * turnRotate);
}
private void OnEnable()//激活坦克的时候调用
//OnEnable():初始化移动旋转输入参数;关闭是否被物理引擎驱动开关:Is Kinematic=false;打开粒子特效
{
m_MovementInputValue = 0;
m_TurnInputValue = 0;
m_Rigidbody.isKinematic = false;//不禁用物理引擎效果
//打开粒子特效
for (int i = 0; i < m_ParticleSystems.Length; i++)
{
m_ParticleSystems[i].Play();
}
}
private void OnDisable()
//OnDisable();关闭是否被物理引擎驱动开关:Is Kinematic=true;关闭粒子特效
{
m_Rigidbody.isKinematic = true;
for (int i = 0; i < m_ParticleSystems.Length; i++)
{
m_ParticleSystems[i].Stop();
}
}
private void Sound()
{
/*------------------------切换待机音效和移动音效------------------------------*/
if (Mathf.Abs(m_MovementInputValue) < 0.1f && Mathf.Abs(m_TurnInputValue) < 0.1f)
{
//停了
if (m_MovementAudio.clip == m_EngineDriving)
{
m_MovementAudio.clip = m_EngineIdling;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
else
{
//在移动中
if (m_MovementAudio.clip == m_EngineIdling)
{
m_MovementAudio.clip = m_EngineDriving;
m_MovementAudio.pitch = Random.Range(m_OriginalPitch - m_PitchRange, m_OriginalPitch + m_PitchRange);
m_MovementAudio.Play();
}
}
}
}
2.2.3 炮弹射击
2.2.3.1炮弹创建
1. 场景添加子弹。将Models文件夹中的Shell模型拖拽到Hierarchy面板中。
2. 制作炮弹预制体
添加Rigidbody刚体
添加Light Component组件,设置参数:Position(-0.003,0.4,0.2)
添加Capsule Collider组件,勾选上Is Trigger触发器选项。设置参数:
Center(0,0,0.2) ,Radius为0.15 ,Height为0.55,Direction:Z-Axis
添加炮弹爆炸粒子特效.
给ShellExplosion添加AudioSource组件,设置AudioClip音效资源选择
ShellExplosion.mp3, 取消勾选Play On Awake。
3. 添加发射点。选中Hierarchy面板里的Tank,创建一个GameObject,重命名为FireTransform,设置Position为(0,1.7,1.35),Rotation为(350,0,0) 。
4. 添加UI发射条。作为坦克蓄力的可视化显示。
选中Hierarchy面板里的Tank,创建Canvas,选中Canvas对象上在Canvas组件上修改属性Render Mode为WorldSpace。
新建Slider,命名为AimSlider
将Canavs和Slider都Reset一下,将Canavs的Rotation设置为(90,0,0),修改Width,Height为3.5。打开AimSlider所有子物体,删HandleSlideArea和Background。选中AimSlider设置Position(1,-9,-1 )
2.2.3.2添加脚本
创建Tank Shotting脚本,将脚本拖到CompletedTank中。
创建ShellExplosion脚本,将脚本拖到CompletedShell中
2.2.3.3脚本编写
·Tank Shotting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TankShooting : MonoBehaviour
{
[HideInInspector]
public int m_PlayerNumber = 1; //坦克编号
public Rigidbody m_Shell; //炮弹刚体对象
public float m_MaxLaunchForce = 35f; //最大力度
public float m_MinLaunchForce = 15f; //最小力度
public float m_MaxChargeTime = 0.75f; //换弹时间
public Slider m_AimSlider; //Slider组件
private Transform m_FireTransform; 发射点的Transform组件
private string m_FireButton; //发射按钮名称
private float m_ChargeSpeed; //发射速度
private float m_CurrentLaunchForce; //发射力度
private bool m_Fired; //发射状态
public AudioSource m_ShootingAudio; //发射音效组件
private AudioClip m_FireClip; //发射音效
public AudioClip m_ChargingClip; //发射准备音效
void Start()
{
m_FireTransform = transform.Find("FireTransform");//初始化角色发射按钮输入轴名称
m_FireButton = "Fire" + m_PlayerNumber;
m_ChargeSpeed = (m_MaxLaunchForce - m_MinLaunchForce) / m_MaxChargeTime;//初始化发射蓄力箭头速度为最小
m_FireClip = ResourcesManager.Instance.Load<AudioClip>("AudioClips/ShotFiring");
}
private void OnEnable()//初始化发射力度。
{
m_CurrentLaunchForce = m_MinLaunchForce;
m_AimSlider.value = m_MinLaunchForce;
}
void Update()//1):当发射力度达到最大值的时候:如果没有处于发射中, 就直接发射;
//2):否则如果是刚刚按下:初始化发射状态为false,发射力度为最小,发射音效为发射准备音效
//3):持续按下中:蓄力过程中需要累加发射力度,同时更新UI。
//4):抬起发射按钮:则直接发射。
{
if (m_CurrentLaunchForce >= m_MaxLaunchForce && !m_Fired)
{
m_CurrentLaunchForce = m_MaxLaunchForce;
Fire();
}
else if (Input.GetButtonDown(m_FireButton))
{
//刚刚按下
m_Fired = false;
m_CurrentLaunchForce = m_MinLaunchForce;
//音效
m_ShootingAudio.clip = m_ChargingClip;
m_ShootingAudio.Play();
}
else if (Input.GetButton(m_FireButton) && !m_Fired)
{
m_CurrentLaunchForce += m_ChargeSpeed * Time.deltaTime; //Time.deltaTime:上一帧执行的时长
m_AimSlider.value = m_CurrentLaunchForce;
}
else if (Input.GetButtonUp(m_FireButton) && !m_Fired)
{
Fire();
}
}
private void Fire()
{
m_Fired = true;
Rigidbody shellInstiate = Instantiate(m_Shell,
m_FireTransform.position,
m_FireTransform.rotation);//通过刚体组件给炮弹施加一个初速度
shellInstiate.velocity = m_CurrentLaunchForce * m_FireTransform.forward;
/*---------设置音效并且播放音效;初始化发射力度最小---------*/
//切换音效
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play();
/*-------------初始化一下参数------------*/
m_CurrentLaunchForce = m_MinLaunchForce;
m_AimSlider.value = m_MinLaunchForce;
}
}
·ShellExplosion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShellExplosion : MonoBehaviour
{
[SerializeField]
private GameObject shellExplosion;
public float m_MaxLifeTime = 1.5f;//设置最长飞行时间
public float m_ExplosionRadius = 5f;//冲击波半径
public LayerMask m_TankMask;//分层,以区别炮弹打到哪个上了
public float m_ExplosionForce = 1000f; //爆炸推力大小
public ParticleSystem m_ExplosionParticle;//子弹爆炸特效
public float m_MaxDamage = 40f;
/*------音效-------*/
public AudioSource m_ExplosionAudio;//子弹爆炸音效
private void Start()
{
Destroy(gameObject, m_MaxLifeTime);//经过一定时间,炮弹自动销毁
}
private void OnTriggerEnter(Collider other)//射线寻找目标;给目标施加推力;给目标施加伤害;
//将特效从炮弹上移除;
//播放特效,音效,销毁特效,销毁炮弹。
{
Collider[] colliders = Physics.OverlapSphere(transform.position, m_ExplosionRadius, m_TankMask);
for (int i = 0; i < colliders.Length; i++)
{
/*——--------------—坦克血条扣分—------------------——*/
//施加推力
Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
targetRigidbody.AddExplosionForce(m_ExplosionForce, transform.position, m_ExplosionRadius);
//施加伤害
TankHealth targetHealth = colliders[i].GetComponent<TankHealth>();
targetHealth.TankDamage(CaculateDamage(colliders[i].transform.position));
}
/*—————子弹爆炸特效——————*/
shellExplosion.transform.parent = null;
//m_ExplosionParticle.transform.parent = null;
m_ExplosionParticle = shellExplosion.GetComponent<ParticleSystem>();
m_ExplosionParticle.Play();//播放子弹爆炸特效
m_ExplosionAudio.Play();//播放子弹爆炸音效
//延时销毁特效:特效不销毁会一直存在。
Destroy(shellExplosion, m_ExplosionParticle.main.duration);
Destroy(gameObject);
}
//计算爆炸的方法
private float CaculateDamage(Vector3 targetPosition)//根据炮弹爆炸的位置来计算(位置离目标越近伤害越大)。
{
float explosionDistance = (targetPosition - transform.position).magnitude;
float relativeDistance = (m_ExplosionRadius - explosionDistance) / m_ExplosionRadius;
float damage = relativeDistance * m_MaxDamage;
damage = Mathf.Max(0, damage);
return damage;
}
}
2.2.4 镜头调整
2.2.4.1相机设置
1.在Hierarchy面板中右键创建空物体,重命名为CameraRig,在Inspector面板中设置位置:
Position(0,0,0) 旋转角度:Rotation(40,60,0)。
在Hierarchy面板中将Main Camera拖拽到CameraRig下作为其子物体,同时设置Main Camera位置 Position(0,0,-65) 旋转角度:Rotation(0,0,0)
2.2.4.2添加脚本
1.在Project面板的Scripts/Camera/创建脚本CameraControl.cs.
2.将脚本添加到Hierachy面板里CameraRig上。
3.打开CameraControl.cs脚本编辑:
1)移动相机CameraControl.Move()
2)缩放相机CameraControl.Zoom()
2.2.4.3脚本编写
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraControl : MonoBehaviour
{
public Transform[] m_Targets;
public float m_DampTime = 0.2f; //相机移动时间
public float m_MinSize = 6.5f;//视口最小值
public float m_ScreenEdyeBuffer = 4f;//缓冲区大小
private Vector3 m_DesiredPosition;//目标位置
private Vector3 m_MoveVelocity;
private float m_ZoomSpeed;
private Camera m_Camera;
void Start()
{
m_Camera = GetComponentInChildren<Camera>();//获取Camera组件。
}
private void FixedUpdate()//移动相机,改变相机视口大小
{
FindAveragePosition();//计算目标点
//移动相机(父物体)
transform.position = Vector3.SmoothDamp(transform.position, m_DesiredPosition, ref m_MoveVelocity, m_DampTime);
//改变相机视口大小
m_Camera.orthographicSize = Mathf.SmoothDamp(m_Camera.orthographicSize, FindReguireSize(), ref m_ZoomSpeed, m_DampTime);
}
private void FindAveragePosition()//计算相机当前应该到达的位置。
{
int numTargets = 0;//Player数量
//累加坐标
Vector3 averagePos = new Vector3();
for (int i = 0; i < m_Targets.Length; i++)
{
//判断Player是否显示
if (!m_Targets[i].gameObject.activeSelf)
{
continue;
}
numTargets++;
averagePos = averagePos + m_Targets[i].position;
}
//求平均位置
if (numTargets > 0)
{
averagePos.y = transform.position.y;
m_DesiredPosition = averagePos / numTargets;
}
}
private float FindReguireSize()//计算视口大小
{
float size = 0;//初始化视口大小
Vector3 desiredLocalPos = transform.InverseTransformPoint(m_DesiredPosition);
for (int i = 0; i < m_Targets.Length; i++)
{
//判断Player是否被禁用
if (!m_Targets[i].gameObject.activeSelf)
{
continue;
}
//将Player从世界坐标转到以CameraRig为中心的本地坐标系
Vector3 targetLocalPos = transform.InverseTransformPoint(m_Targets[i].position);
//计算目标点到Player的向量
Vector3 desirePosToTarget = targetLocalPos - desiredLocalPos;
size = Mathf.Max(size, desirePosToTarget.y);
size = Mathf.Max(size, desirePosToTarget.x / m_Camera.aspect);//aspect:宽/高
}
//给相机边缘加一个缓冲区
size += m_ScreenEdyeBuffer;
//限制视口最小值
size = Mathf.Max(size, m_MinSize);
return size;
}
//重置位置的方法
public void SetStartPositionAndSize()//回合结束的时候初始化方法。
{
FindAveragePosition();
transform.position = m_DesiredPosition;
m_Camera.orthographicSize = FindReguireSize();
}
}
2.2.5 坦克生命值
2.2.5.1坦克血条制作
1. Scence面板左上角的Toggle Tool Handle Position设置成Pivot而不是Center。
2. 选中Hierarchy面板里的Tank,创建一个Slider,重命名为HealthSlider。
3. 打开HealthSlider所有子物体,删HandleSlideArea。选中其他所有子对象,将其设置为平铺。
4. 选中Background,将Source Image设置为图片资源Health Wheel。选中Fill,将Image Type设置为Filed,实现360°填充效果,选中将Source Image设置为图片资源Health Wheel,颜色设置为绿色。
2.2.5.2添加脚本
1. 在Scripts/Tank目录下创建脚本TankHealth
2. 双击点开CompletedTank预制体,将脚本拖入
2.2.5.3脚本编写
·TankHealth
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class TankHealth : MonoBehaviour
{
public Slider m_Slider;
private float m_CurrentHealth;//定义血量值
public int m_StartingHealth = 100;//血量初始值
//颜色
public Image m_FillImage;
public Color m_ZeroHealthColor;//血量为0颜色
public Color m_FullHealthColor;//满血颜色
[SerializeField]
private GameObject tankExplosion;
//public GameObject m_Explosion; //死亡特效
//private ParticleSystem m_ExplosionEffect;
//private GameObject m_ExplosionInstance;//对象(在场景中显示)
private AudioSource m_ExplosionAudio;//音效
private void Awake()//初始化:特效组件;特效音效组件;
{
//m_ExplosionInstance = Instantiate(m_Explosion);//实例化坦克爆炸特效
//m_ExplosionEffect = m_ExplosionInstance.GetComponent<ParticleSystem>();//获取ParticleSystem
///*------音效------*/
//m_ExplosionAudio = m_ExplosionInstance.GetComponent<AudioSource>();
}
private void OnEnable()//初始化血量为100;更新血量UI值以及UI颜色。
{
m_CurrentHealth = m_StartingHealth;
SetHealthUI();
}
//坦克受伤 amount:伤害值
public void TankDamage(float amount)//血量减少;
//更新到UI上;
//判断是否已经死亡。
{
//血量减少
m_CurrentHealth -= amount;
//更新UI
SetHealthUI();
//小于等于0调用死亡方法
if (m_CurrentHealth <= 0)
{
OnDeath();
}
}
private void SetHealthUI()//更新UI:设置UI血量;
//修改颜色(根据血量多少设置)。
{
m_Slider.value = m_CurrentHealth;
//lerp:差值变化,从a变到b,后面是比例
m_FillImage.color = Color.Lerp(m_ZeroHealthColor, m_FullHealthColor, m_CurrentHealth / m_StartingHealth);
}
//死亡方法
private void OnDeath()//显示死亡特效;
//播放死亡特效;
//播放死亡音效;
//隐藏关闭坦克。
{
//m_ExplosionInstance.transform.position = transform.position;//爆炸放到坦克的位置上去
//m_ExplosionEffect.Play();//播放坦克爆炸特效
//m_ExplosionAudio.Play();//播放音效
//Destroy(m_ExplosionInstance, m_ExplosionEffect.main.duration);//爆炸效果播放完后删除爆炸实例
GameObject tankExplosionInstance = Instantiate(tankExplosion); //实例化坦克爆炸特效
tankExplosionInstance.transform.position = transform.position;//爆炸放到坦克的位置上去
ParticleSystem tankExplosionEffect = tankExplosionInstance.GetComponent<ParticleSystem>();//获取ParticleSystem
tankExplosionEffect.Play();//播放坦克爆炸特效
Destroy(tankExplosionInstance, tankExplosionEffect.main.duration);//爆炸效果播放完后删除爆炸实例
m_ExplosionAudio = tankExplosionInstance.GetComponent<AudioSource>();
m_ExplosionAudio.Play();//播放音效
gameObject.SetActive(false);//坦克血量小于等于0后,坦克消失
}
/*点击按钮就会执行伤害方法(20点伤害)
private void OnGUI()
{
if (GUI.Button(new Rect(10, 10, 150, 50), "攻击"))
{
TankDamage(20);
}
}*/
}
·UIDirectionControl
实现坦克血条不随着坦克的旋转而旋转
using UnityEngine;
public class UIDirectionControl : MonoBehaviour
{
public bool m_UseRelativeRotation = true;
private Quaternion m_RelativeRotation;
private void Start()
{
m_RelativeRotation = transform.parent.localRotation;
}
private void Update()
{
if (m_UseRelativeRotation)
transform.rotation = m_RelativeRotation;
}
}
2.2.6 游戏管理系统
2.2.6.1创建游戏管理系统
1. 创建空物体作为坦克生成点,分别重命名为:SpawnPoint1:Position(-3,0,30)
Rotation(0,180,0) SpawnPoint2:Position(13,0,-5) Rotation(0,0,0)
2. 创建显示UI:右键创建UI/Cavas重命名为MessageCanvas,在Cavas下右键UI>Text创建Text文本组件,锚点选择Alt+最右下模式, 参数为(0,0,0,0,0)Anchors:Min(0.1,0.1),Max(0.9,0.9)
3. 文本内容:TANKS!,字体:BowlbyOne-Regular,大小:50,文本格式选择居中显示,勾选Best Fit,Max Size:60, Color(255,255,255,255)。在Text上添加组件Shadow,设置参数:Color(114,71,40,128),Effect Distance(-3,-3)
4. 创建GameManger:
1):在Hierarchy面板中创建空物体重命名为GameManger,添加脚本组件GameManager.cs
2):设置脚本面板参数:Camera Control:拖拽CameraRig Message Text:拖拽消息面板Text, Tank Prefab:拖拽预制件Tank Tanks:Size选择2,第一个坦克Color(42,100,178),第二个坦克Color(229,46,40),Spawn Point选择对应的出生点。
3):给GameManger添加组件Audio Source,AudioClip:BackgroundMusic ,勾选Play On
Awake, 勾选:Loop。
2.2.6.2添加脚本
1. 将Resources.cs直接拖入Scripts/Manager目录中;在Scripts/Manager目录下创键GameManager脚本;在Scripts/Tank目录下创键TankItem脚本
2. 将GameManager脚本拖到Hierarchy面板中的GameManager上;
2.2.6.3脚本编写
·GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
//管理游戏主要流程
public class GameManger : MonoBehaviour
{
public GameObject m_TankPrefab; //坦克预制体
public CameraControl m_CameraControl; //摄像机控制脚本组件
public TankItem[] m_Tanks; //所有坦克属性集
public Text m_MessageText; //UI显示组件对象
public float m_StartDelay = 3f; //开始准备时长
public float m_EndDelay = 3f; //结束等待时长
public int m_NumRoundsToWin = 3; //赢得游戏需要赢的局数
private TankItem m_GameWinner; //游戏赢家
private TankItem m_RoundWinner; //回合赢家
private int m_RoundNumber; //当前回合数
void Start()//初始化:创建并初始化所有坦克;获取所有坦克的位置组件对象赋给CameraControl.Targets;开始游戏
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].m_Instance=Instantiate(m_TankPrefab, m_Tanks[i].m_SpawnPoint.position, m_Tanks[i].m_SpawnPoint.rotation);
m_Tanks[i].m_PlayerNumber = i + 1;
m_Tanks[i].Init();
}
Transform[] targets = new Transform[m_Tanks.Length];
for (int i = 0; i < m_Tanks.Length; i++)
{
targets[i] = m_Tanks[i].m_Instance.transform;
}
m_CameraControl.m_Targets = targets;
StartCoroutine(GameLoop());
}
private IEnumerator GameLoop()//使用协程:开始循环游戏
{
yield return null;
yield return StartCoroutine(RoundStarting());
yield return StartCoroutine(RoundPlaying());
yield return StartCoroutine(RounEnding());
if (m_GameWinner != null)
{
//重新加载场景
SceneManager.LoadScene("MainScence");
}
else
{
//回合结束,又开始
StartCoroutine(GameLoop());
}
}
private IEnumerator RoundStarting()//回合初始化:调用坦克对象的初始化方法进行初始化;
//调用坦克对象的冻结方法冻结坦克控制;
//调用摄像机初始化相机位置和视口大小;
//初始化回合界面显示文本;
//等待一定时间再进行下一步。
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].Reset();
m_Tanks[i].DisableControl();
}
m_CameraControl.SetStartPositionAndSize();
m_RoundNumber++;
m_MessageText.text = "ROUND" + m_RoundNumber + "!";
yield return new WaitForSeconds(m_StartDelay);
}
private IEnumerator RoundPlaying()//开始游戏:调用坦克对象的激活方法激活坦克控制;
//清除UI界面显示内容;
{
//回合初始化
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].EnableControl();
}
m_MessageText.text = string.Empty;
while (!OneTankLeft())
{
yield return null; //等待一帧
}
}
private IEnumerator RounEnding()//回合结束:调用坦克对象的冻结方法冻结坦克控制
{
for (int i = 0; i < m_Tanks.Length; i++)
{
m_Tanks[i].DisableControl();
}
m_RoundWinner = GetRoundWinner();
m_GameWinner = GetGameWinner();
if(m_RoundWinner!=null)
m_RoundWinner.m_Wins++;
m_MessageText.text = EndingMessage();
yield return new WaitForSeconds(m_EndDelay);
}
private bool OneTankLeft()//判断是否只剩下一个坦克(OneTankLeft()),如是则回合结束否则游戏一直进行。
{
int numTankLeft = 0;
for (int i = 0; i < m_Tanks.Length; i++)
{
if (m_Tanks[i].m_Instance.activeSelf)
numTankLeft++;
}
return numTankLeft<=1;
}
private TankItem GetRoundWinner()//获取回合赢家(GetRoundWinner());增加赢家分数;
{
for (int i = 0; i < m_Tanks.Length; i++)
{
if (m_Tanks[i].m_Instance.activeSelf)
return m_Tanks[i];
}
return null;
}
private TankItem GetGameWinner()//获取游戏赢家(GetGameWinner()判断是否进行下一回合。)
{
for (int i = 0; i < m_Tanks.Length; i++)
{
if (m_Tanks[i].m_Wins==m_NumRoundsToWin)
return m_Tanks[i];
}
return null;
}
private string EndingMessage()//显示回合结束的信息(EndingMessage());
//等待一定时间用于显示信息。判断游戏是否结束;如果没有则继续循环进行下一回合;如果结束了重置一下游戏场景。
{
string message = "DRAW!";
if (m_RoundWinner != null)
message = m_RoundWinner.m_ColoredPlayerText+ "WINS THE ROUND";
message = "\n" + message + "\n\n\n";
for (int i = 0; i < m_Tanks.Length; i++)
{
message += m_Tanks[i].m_ColoredPlayerText + ":" + m_Tanks[i].m_Wins + " WIN\n";
}
if (m_GameWinner != null)
message = m_GameWinner.m_ColoredPlayerText + "WINS THE GAME!";
return message;
}
}
·TankItem
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//代表一个坦克对象的信息和提供一些方法
[Serializable] //序列化:序列化后可以在控制面板中查看其公开属性
public class TankItem
{
public Color m_PlayerColor; //颜色
public Transform m_SpawnPoint; //出生点
[HideInInspector] public int m_PlayerNumber; //编号
[HideInInspector] public GameObject m_Instance; //坦克实例
[HideInInspector] public int m_Wins; //赢得的回合数
[HideInInspector] public string m_ColoredPlayerText; //赢家信息
private TankMovement m_Movement; //移动组件
private TankShooting m_Shooting; //射击组件
private GameObject m_CanvasGO; //Canvas节点对象
//游戏开始游戏对象初始化方法
public void Init()
{
//初始化组件
m_Movement = m_Instance.GetComponent<TankMovement>();
m_Shooting = m_Instance.GetComponent<TankShooting>();
m_CanvasGO = m_Instance.GetComponentInChildren<Canvas>().gameObject;
//初始化编号
m_Movement.m_PlayerNumber = m_PlayerNumber;
m_Shooting.m_PlayerNumber = m_PlayerNumber;
//初始化玩家信息颜色 PLAYER1
m_ColoredPlayerText = "<color=#" + ColorUtility.ToHtmlStringRGB(m_PlayerColor) + ">PLAYER" + m_PlayerNumber + "</color>";
//将颜色应用到坦克上
MeshRenderer[] renderers = m_Instance.GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < renderers.Length; i++)
{
renderers[i].material.color = m_PlayerColor;
}
}
//激活本对象控制状态:移动,射击
public void EnableControl()
{
m_Movement.enabled = true;
m_Shooting.enabled = true;
m_CanvasGO.SetActive(true);
}
//冻结本对象的行为
public void DisableControl()
{
m_Movement.enabled = false;
m_Shooting.enabled = false;
m_CanvasGO.SetActive(false);
}
//回合结束初始化方法
public void Reset()
{
m_Instance.transform.position = m_SpawnPoint.position;
m_Instance.transform.rotation = m_SpawnPoint.rotation;
m_Instance.SetActive(false);
m_Instance.SetActive(true);
}
}
2.2.7 音效管理
混音器
1. 在Project/Assets下创建文件夹AudioMixers,选中AudioMixers右键Create/AudioMixer,重命名MainMix.双击打开Audio Mixer面板,拖放到Scene面板旁边。
2. 在Groups栏,点击加号创建三个组,分别命名为Music,SFX,Driving。
3. 赋值:
·选中Tank预制件,第一个Audio Source的Output选择Driving.第二个选择SFX;
·选中Shell预制件双击打开,选中其子物体ShellExplosion,AudioSource组件的Output属性选择为SFX.
·在Hierarchy面板中选中GameManager.AudioSource的Output属性选则Music.
·选中TankExplosion预制件,AudioSource组件上Output属性设置SFX.
4. 回到Audio Mixer Window面板,设置Music相对强度为-12,Driving为-28。
5. Music面板下点击Add/Duck Volume,设置参数Threshold:-45dB. Rotio:250%。Attack Time:0.
6. SFX面板选中Add/Send.参数选则,Receive:Music\Duck Volume Send level:0dB.文章来源:https://www.toymoban.com/news/detail-860909.html
2.3 使用说明
不同玩家通过不同的按钮控制对应坦克的前后移动左右旋转以及炮弹发射。当击中对方坦克时,对方坦克扣血。当血条扣完后,坦克爆炸,一轮游戏结束。一轮游戏结束后可新开始一轮。游戏结束后,玩家可通过屏幕上的信息知道各自输赢了几次。文章来源地址https://www.toymoban.com/news/detail-860909.html
到了这里,关于Unity游戏程序设计——3D双人坦克大战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!