Unity百游修炼(3)——Tank_Battle(双人对战)详细制作全流程
一.项目简介
《Tank_Battle》是一款基于 Unity 引擎开发的 3D 双人对战坦克游戏。玩家在游戏中操控各自的坦克,通过灵活移动、旋转和发射子弹等操作,展开激烈的对战,旨在摧毁对方坦克以获得胜利。该项目结合了丰富的游戏元素和交互机制,为玩家带来沉浸式的游戏体验。
游戏画面如下:
Tank_battle
二.创建场景
1.创建3D工程(命名为Tank_Battle)
2.导入unity_package(资源包)到Project窗口中(资源在素材里面)
3.将文件夹Prefab中levelart(场景)拖入到hierarchy窗口中
4.删除场景自带的直射光,因为levelart中自带了直射光

5.调节场景光线:Window——Reddering——Lighting——Environment(source光源调成color颜色,颜色调成和环境贴合的颜色)
6.Main camera改变天空场景颜色
三.添加坦克(加拖尾特效)
1.把models中Tank拖放到Hierarchy视图中
2.把prefab中的DustTrall拖入到Tank上,复制一个,选中两个拖尾,分别将两个拖尾移动到轮子履带处
3.拖尾位置如下
4.给tank添加Box collider组件
5.调节collider的大小
6.把tank变成prefab,直接把tank拖到Projectors——Prefab文件夹中(为了后面坦克多了统一管理)
四.控制坦克前后移动
1.创建控制坦克移动的脚本并命名为Tank_ Move(记得创建一个Scripst文件夹统一管理脚本)
2.选择Tank,添加刚体组件Rigidbody
3.进入Tank_Move脚本
4.获取Tank本身Rigidbody组件
4.1创建刚体变量,保存
public class TankMove : MonoBehaviour
{
public Rigidbody Tank_body;//创建刚体变量接受Tank的刚体
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
4.2把脚本拖放到Tank身上,然后把Tank脚本的Tank_body变量指定为Tank本身的Rigidbody
5.按键控制tank前后移动
5.1按键ws控制移动
5.2检测按键ws按下并保存按下的返回值
Input.GetAxisRaw();作用是:检测是否有按键按下,并返回float值
"Vertical"作用是:竖直方向
5.3按键使得坦克向前或者向后移动
transform.forward 的含义
方向表示:它是一个三维向量(Vector3 类型),指向游戏对象所面向的正前方。这个方向是相对于游戏对象的旋转而言的,当游戏对象的旋转发生改变时,transform.forward 的方向也会随之改变。
五.控制坦克旋转
1.按键ad控制旋转
2.检测按键ad按下并保存按下的返回值
3.绕Y轴移动
这行代码的主要功能是设置坦克刚体(Tank_body)的角速度,从而让坦克绕着轴进行旋转。旋转的轴是游戏对象自身的向上方向(transform.up),即Y轴,旋转的速度由 r 和 TankRotate_Speed 共同决定。
4.完整代码展现
六.控制两辆坦克(实现双人一起玩)
1.打开Edit——Projectsettings——input Manger
2.保证ad只控制Player1的旋转,左右箭头只控制Player2的旋转
2.1光标放到Horizontal上,右击创建两个新的Horizontal,分别命名为Horizontal_Player1,Horizontal_Player2
2.2玩家一删除下图圈出来的内容,表示取消左右箭头控制前进
2.3玩家二删除下图圈出来的内容,表示取消ad按键控制旋转
3.保证ws只控制Player1的前进后退,上下箭头只控制Player2的前进后退
3.1光标放到Vertical上,右击创建两个新的Vertical,分别命名为Vertical_Player1,Vertical_Player2
3.2玩家一删除下图圈出来的内容,表示取消上下箭头控制前进
3.3玩家二删除下图圈出来的内容,表示取消ws按键控制旋转
七.控制坦克发射子弹
1.将子弹创建出来,并实例化
1.1将子弹素材拖入到Hierarchy中,并拖入Prefab成为预制体,并添加胶囊碰撞器
1.2修改碰撞体大小(记得按下图操作,一定要改成z轴)
1.3实例化子弹——确定位置。创建一个空物体,用来确定子弹发射位置,空物体是倾斜的(因为炮管是倾斜的),命名为fireposition
2.添加子弹攻击脚本Tank_Shoot——实例化子弹
2.1创建脚本
2.2创建变量接受位置,同时选中每次创建的子弹
public Transform firePosition;//接受发射子弹的位置物体
public GameObject ShellPrefab;//接受每次创建的实例化子弹
2.3把脚本拖到发射位置物体上,然后把发射位置和发射的子弹预制体选中
2.4把发射位置物体,拖到Tank物体上,作为其子对象,这样就可以保证相对位置不变了
2.5空格键控制子弹发射
2.6控制子弹飞行速度
八.子弹爆炸效果
1.创建子弹脚本Sheet
2.接受爆炸效果的预制体
3.当子弹触发器触碰到其他物体时,触发爆炸效果
4.我增加了一个开火也可以有爆炸效果,模仿后坐力很大

九.控制子弹造成伤害
1.给tank加tag标签,区分子弹是否触碰到了tank
2.写脚本——tank受到伤害处理的脚本
public class Tank_health : MonoBehaviour
{
public int hp = 100;//坦克血量
public GameObject Tankbaozha;//坦克爆炸效果
public AudioSource TankbaozhaAudioSource;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void HurtHealthy()
{
if (hp < 0)
{
return;
}
hp -= Random.Range(10, 20);//伤害是随机的 10 20
if (hp < 0)//坦克没了
{
TankbaozhaAudioSource.Play();
GameObject.Instantiate(Tankbaozha,transform.position+Vector3.up,transform.rotation);
GameObject.Destroy(this.gameObject);
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
3.tank触发到tank,调用2中生成的脚本
public void OnTriggerEnter(Collider other)
{
if (SheelExplosionPrefab != null)
{
GameObject.Instantiate(SheelExplosionPrefab, transform.position, transform.rotation);
}
if(other.tag=="Tank")
{
other.SendMessage("HurtHealthy");//即调用的方法
}
}
十.相机跟随
原理:相机与两坦克的中心保持一定的向量关系,同时还有近大远小的视觉效果
1.算出两坦克的中心点与摄像机坐标的向量差。
2.保证摄像机始终与中心点有个相同向量差
3.当坦克间距变小时,摄像头视角变小;反之,摄像头变大
十一.添加音效,音乐
1.背景音乐
(1)创建一个空物体,命名为:Music
(2)在空物体上添加组件audio source
(3)在组件上添加背景音乐
(4)进行基础设置即可
2.子弹音效:每次发射都会启动
原理:一样的添加audio source组件用来存储音效,每次在实例化子弹的时候,启动音效
十二.问题
1.可能出现的问题1:Assembly-CSharp-Editor渲染函数老旧过时了。
解决方案:更换函数,下面是更换之后的代码:(复制粘贴即可)
using System;
using UnityEditor;
using UnityEngine;
namespace UnityStandardAssets.ImageEffects
{
[CustomEditor(typeof(ColorCorrectionLookup))]
class ColorCorrectionLookupEditor : Editor
{
SerializedObject serObj;
void OnEnable()
{
serObj = new SerializedObject(target);
}
private Texture2D tempClutTex2D;
public override void OnInspectorGUI()
{
serObj.Update();
EditorGUILayout.LabelField("Converts textures into color lookup volumes (for grading)", EditorStyles.miniLabel);
//EditorGUILayout.LabelField("Change Lookup Texture (LUT):");
//EditorGUILayout.BeginHorizontal ();
//Rect r = GUILayoutUtility.GetAspectRect(1.0ff);
Rect r; Texture2D t;
//EditorGUILayout.Space();
tempClutTex2D = EditorGUILayout.ObjectField(" Based on", tempClutTex2D, typeof(Texture2D), false) as Texture2D;
if (tempClutTex2D == null)
{
t = AssetDatabase.LoadMainAssetAtPath(((ColorCorrectionLookup)target).basedOnTempTex) as Texture2D;
if (t) tempClutTex2D = t;
}
Texture2D tex = tempClutTex2D;
if (tex && (target as ColorCorrectionLookup).basedOnTempTex != AssetDatabase.GetAssetPath(tex))
{
EditorGUILayout.Separator();
if (!(target as ColorCorrectionLookup).ValidDimensions(tex))
{
EditorGUILayout.HelpBox("Invalid texture dimensions!\nPick another texture or adjust dimension to e.g. 256x16.", MessageType.Warning);
}
else if (GUILayout.Button("Convert and Apply"))
{
string path = AssetDatabase.GetAssetPath(tex);
TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
bool doImport = textureImporter.isReadable == false;
if (textureImporter.mipmapEnabled == true)
{
doImport = true;
}
// 修改:检查纹理压缩格式
if (textureImporter.textureCompression != TextureImporterCompression.Uncompressed)
{
doImport = true;
}
if (doImport)
{
textureImporter.isReadable = true;
textureImporter.mipmapEnabled = false;
// 修改:设置纹理压缩格式为无压缩
textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
//tex = AssetDatabase.LoadMainAssetAtPath(path);
}
(target as ColorCorrectionLookup).Convert(tex, path);
}
}
if ((target as ColorCorrectionLookup).basedOnTempTex != "")
{
EditorGUILayout.HelpBox("Using " + (target as ColorCorrectionLookup).basedOnTempTex, MessageType.Info);
t = AssetDatabase.LoadMainAssetAtPath(((ColorCorrectionLookup)target).basedOnTempTex) as Texture2D;
if (t)
{
r = GUILayoutUtility.GetLastRect();
r = GUILayoutUtility.GetRect(r.width, 20);
r.x += r.width * 0.05f / 2.0f;
r.width *= 0.95f;
GUI.DrawTexture(r, t);
GUILayoutUtility.GetRect(r.width, 4);
}
}
//EditorGUILayout.EndHorizontal ();
serObj.ApplyModifiedProperties();
}
}
}
十三.结语
《Tank_Battle》不仅是一款具有娱乐性的游戏,也是一个优秀的 Unity 开发实践项目。对于初学者来说,它展示了如何利用 Unity 引擎进行场景搭建、对象控制、碰撞检测和特效实现等基础开发技术;对于有一定经验的开发者,也可以从中获取关于双人对战游戏设计和实现的思路和方法。同时,项目还提供了可能出现问题的解决方案,有助于开发者在遇到类似问题时快速解决。