使用的是Unity 2018版和VS2019
创建新的工程,导入素材包,下面为链接:
链接:https://pan.baidu.com/s/1_KLjColSJVN123aLy4_4fw
提取码:o101
导入素材
文章目录
- 初步制作物体
- 动画效果
- 坦克移动
- 子弹的制作
- 当坦克被击中时会爆炸(爆炸特效及代码)
- 出生无敌特效
- 敌人AI编写与优化
- 出生特效及代码
- 家特效及代码
- 开始地图创建
- 场景切换
- 统计分数与血量
- 省了最后一步,游戏开始界面
先把相机的天空盒改为
黑色,
画面比例调为5:4还有
长度改为8.5
这时候我们就会发现我们的游戏实景面板(左下角的窗口)已经变为黑色的了。
初步制作物体
接着我们要做游戏里的物体,我们打开Project面板Assets文件夹下的GameResource文件,下面有三个文件夹,从上到下分别是声音、字体和图片。我们找到玩家的图片、按照以下方法,创造一个实体。
接着按这样的方法将草丛(Grass)、可以被子弹打穿的障碍墙(Wall)、不可被子弹打穿的障碍墙(Barrier)、空气墙(AirBarriar)、老鹰标志(Heart…就是我们要保护的老窝)
动画效果
接下来我们来做会动的水(River)、爆炸效果(Explosion)、无敌效果(Shield)、出生效果(Born)!我们拿爆炸效果为例:
按住Ctrl键同时选中两张爆炸效果的图片,然后拖到左边的目录下;
这时候会弹出一个对话框,我们更改文件名为:Explosion;
更改玩之后点击保存,这时候我们点击运行,就可以发现,我们的爆炸效果会动了(别忘了调整大小为3:3:3)。这时候可以看到在我们的Project面板下,多出了两个新朋友。
为了更加方便的管理,我们在Project面板下新创建两个文件夹一个为 Animation、另一个是AnimationController。然后把图标为三角的文件拖入Animation中,另一个拖入AnimationController中。读者请自制其他动画组件。做完之后文件这样命名,如下图所示:
Effect为动画特效文件,Map为游戏里的障碍物、草丛、家等,Tank为坦克。
坦克移动
创建一个Player脚本托在坦克上
我将坦克的最终代码写了出来,防止分开写大家看不懂,比较绕
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
//子弹欧拉角
private Vector3 bullectEulerAngles;
private float moveSpeed = 3;
private float timeVal;
private float defendTimeVal = 3f;
//渲染坦克转弯后的素材
private SpriteRenderer sr;
public Sprite[] tankSprite; //上右下左
private bool isDefended = true;
public GameObject explosionPrefab;
public GameObject bullectPrefab;
public GameObject defendEffectPrefab;
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
private void Update()
{
//是否处于无敌状态
if (isDefended)
{
defendEffectPrefab.SetActive(true);
defendTimeVal -= Time.deltaTime;
if (defendTimeVal <= 0)
{
Debug.Log(111);
isDefended = false;
defendEffectPrefab.SetActive(false);
}
}
}
private void FixedUpdate()
{
if (PlayerManager.Instance.isDefeat)
{
return;
}
Move();
//攻击cd
if (timeVal >= 0.4f)
{
Attack();
}
else
{
timeVal += Time.fixedDeltaTime;
}
}
//移动
public void Move()
{
float v = Input.GetAxisRaw("Vertical");//垂直轴值输入
transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
if (v < 0)
{
sr.sprite = tankSprite[2];
bullectEulerAngles = new Vector3(0, 0, -180);
}
else if (v > 0)
{
sr.sprite = tankSprite[0];
bullectEulerAngles = new Vector3(0, 0, 0);
}
//优先级问题,优先向垂直方向走
if (v != 0)
{
return;
}
float h = Input.GetAxisRaw("Horizontal");//水平轴值输入 返回-1到1
transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);
//移动时调整方向
if (h < 0)
{
sr.sprite = tankSprite[3];
bullectEulerAngles = new Vector3(0, 0, 90);
}
else if (h > 0)
{
sr.sprite = tankSprite[1];
bullectEulerAngles = new Vector3(0, 0, -90);
}
}
//子弹射击
private void Attack()
{
if (Input.GetKeyDown(KeyCode.J))
{
//实例化 子弹产生角度:当前坦克角度+子弹应该旋转的角度 欧拉角
Instantiate(bullectPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bullectEulerAngles));
timeVal = 0f;
}
}
//坦克得死亡方法
private void Die()
{
if (isDefended)
{
return;//跳过死亡方法
}
PlayerManager.Instance.isDead = true;
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
}
这里是坦克的属性面板,赋值。记得向坦克加刚体和碰撞体
将刚体里的Gravity Scale(重力改为0)不然向下掉
顺便将坦克的Z轴锁住
给River、Eagle、Barrir、Player添加上Box Collider 2D。
小技巧:直接在预制体上进行操作会省很多步骤哟!
子弹的制作
其他实体一样,我们从我们的素材中找到子弹样式的图片,拖入场景,调整大小比例为3:3:3,并且重命名为Playerbullect,添加预制体:
下面给出子弹的最终代码和属性面板
创建一个Bullect类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullect : MonoBehaviour {
public bool IsPlayerBullect;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.Translate(transform.up * 10 * Time.deltaTime, Space.World);
}
//碰撞检测
private void OnTriggerEnter2D(Collider2D collision)
{
switch (collision.tag)
{
case "Player": //打到自己
if (!IsPlayerBullect)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
case "Heart"://打到家
collision.SendMessage("HeartDie");
Destroy(gameObject);
break;
case "Enemy"://打到敌人
if (IsPlayerBullect)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
case "Barrier"://打到障碍物
Destroy(gameObject);
//AudioSource.PlayClipAtPoint(hitAudio, transform.position);
break;
case "Wall"://打到墙
Destroy(collision.gameObject);
Destroy(gameObject);
break;
default:
break;
}
}
}
向对应的物体添加标签
同样向子弹中添加刚体和碰撞体
碰撞体中又Is Ttigger属性 打上对勾
将刚体里的Gravity Scale(重力改为0)不然向下掉
将我方子弹复制一个,改名为EnemyBullect,敌方子弹与我方子弹的唯一区别就是 敌方子弹不打对勾Is Player Bullect
当坦克被击中时会爆炸(爆炸特效及代码)
爆炸特效在上面已经做了 名字是Explode
代码只有一个销毁自身
public class Explosion : MonoBehaviour {
// Use this for initialization
void Start () {
Destroy(gameObject, 0.2f);
}
// Update is called once per frame
void Update () {
}
}
出生无敌特效
上面也已经做了,现在说说吧它放哪
敌人AI编写与优化
直接将自己坦克的素材换成敌方坦克的
将Player代码remove删除掉,创建一个Enemy脚本
最终代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
//子弹欧拉角
private Vector3 bullectEulerAngles;
private float v = -1;
private float h;
private float moveSpeed = 3;
private float timeVal;
private float timeValChangeDirection = 4;
//渲染坦克转弯后的素材
private SpriteRenderer sr;
public Sprite[] tankSprite; //上右下左
public GameObject explosionPrefab;
public GameObject bullectPrefab;
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
private void Update()
{
Move();
//攻击cd
if (timeVal >= 3f)
{
Attack();
}
else
{
timeVal += Time.deltaTime;
}
}
//移动
public void Move()
{
if (timeValChangeDirection >= 4)
{
int num = Random.Range(0, 8);
if (num > 5)
{
v = -1;
h = 0;
}
else if (num == 0)
{
v = 1;
h = 0;
}
else if (num >0 && num <= 2)
{
h = -1;
v = 0;
}
else if (num < 2 && num <= 4)
{
h = 1;
v = 0;
}
timeValChangeDirection = 0;
}
else
{
timeValChangeDirection += Time.fixedDeltaTime;
}
transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
if (v < 0)
{
sr.sprite = tankSprite[2];
bullectEulerAngles = new Vector3(0, 0, -180);
}
else if (v > 0)
{
sr.sprite = tankSprite[0];
bullectEulerAngles = new Vector3(0, 0, 0);
}
//优先级问题,优先向垂直方向走
if (v != 0)
{
return;
}
transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);
//移动时调整方向
if (h < 0)
{
sr.sprite = tankSprite[3];
bullectEulerAngles = new Vector3(0, 0, 90);
}
else if (h > 0)
{
sr.sprite = tankSprite[1];
bullectEulerAngles = new Vector3(0, 0, -90);
}
}
//子弹射击
private void Attack()
{
//实例化 子弹产生角度:当前坦克角度+子弹应该旋转的角度 欧拉角
Instantiate(bullectPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bullectEulerAngles));
timeVal = 0f;
}
//坦克得死亡方法
private void Die()
{
PlayerManager.Instance.playerScore++;
//产生爆炸特效
Instantiate(explosionPrefab, transform.position, transform.rotation);
//死亡
Destroy(gameObject);
}
private void OnCollisionEnter2D(Collider2D collider)
{
if (collider.gameObject.tag == "Enemy")
{
timeValChangeDirection = 4;
}
}
}
身上的组件和需要赋值的,将名称改为SmallEnemy和BigEnemy,做一大一小
出生特效及代码
上面已经创建过了,直接上属性面板图和最终代码
创建一个Born脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Born : MonoBehaviour {
public GameObject palyerPrefab;
public GameObject[] enemyPrefabList;
public bool createPlayer;
// Use this for initialization
void Start () {
Invoke("BornTank", 1);
Destroy(gameObject, 1);
}
private void BornTank()
{
if (createPlayer)
{
Instantiate(palyerPrefab, transform.position, transform.rotation);
}
else
{
int num = Random.Range(0, 2);
Instantiate(enemyPrefabList[num], transform.position, Quaternion.identity);
}
}
// Update is called once per frame
void Update () {
}
}
家特效及代码
最终代码,创建一个Heart脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Heart : MonoBehaviour {
private SpriteRenderer sr;
public GameObject explosionPrefab;
public Sprite BrokenSprite;
public AudioClip dieAudio;
// Use this for initialization
void Start () {
sr = GetComponent<SpriteRenderer>();
}
public void HeartDie()
{
sr.sprite = BrokenSprite;
Instantiate(explosionPrefab, transform.position, transform.rotation);
PlayerManager.Instance.isDefeat = true;
AudioSource.PlayClipAtPoint(dieAudio, transform.position);
}
}
附上值
开始地图创建
这是一个新的场景,刚才做的是game场景
创建一个空物体MapCreation,将脚本托给它
0老家 1墙 2障碍 3出生效果 4河流 5草 6空气墙
按这个顺序拖进去
上最终代码,创建MapCreation脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapCreation : MonoBehaviour
{
//需要初始化的地图所需物体的数组
//0空气墙1障碍2草3老家4河流5墙6出生特效
public GameObject[] item;
//已经有物体的位置列表
private List<Vector3> itemPositionList = new List<Vector3>();
private void Awake()
{
InitMap();
}
private void InitMap()
{
//实例化老家
CreateItem(item[3], new Vector3(0, -8, 0), Quaternion.identity);//无旋转
//用墙把老家围起来
CreateItem(item[5], new Vector3(-1, -8, 0), Quaternion.identity);
CreateItem(item[5], new Vector3(1, -8, 0), Quaternion.identity);
for (int i = -1; i < 2; i++)
{
CreateItem(item[5], new Vector3(i, -7, 0), Quaternion.identity);
}
//实例化外围墙
for (int i = -11; i < 12; i++)
{
CreateItem(item[0], new Vector3(i, 9, 0), Quaternion.identity);
CreateItem(item[0], new Vector3(i, -9, 0), Quaternion.identity);
}
for (int i = -8; i < 9; i++)
{
CreateItem(item[0], new Vector3(-11, i, 0), Quaternion.identity);
CreateItem(item[0], new Vector3(11, i, 0), Quaternion.identity);
}
//实例化地图
for (int i = 0; i < 20; i++)
{
CreateItem(item[2], CreateRandomPosition(), Quaternion.identity);
CreateItem(item[5], CreateRandomPosition(), Quaternion.identity);
CreateItem(item[4], CreateRandomPosition(), Quaternion.identity);
CreateItem(item[1], CreateRandomPosition(), Quaternion.identity);
}
//实例化玩家
GameObject go = Instantiate(item[6], new Vector3(-2, -8, 0), Quaternion.identity);
go.GetComponent<Born>().createPlayer = true;
//实例化敌人
CreateItem(item[6], new Vector3(-10, 8, 0), Quaternion.identity);
CreateItem(item[6], new Vector3(0, 8, 0), Quaternion.identity);
CreateItem(item[6], new Vector3(10, 8, 0), Quaternion.identity);
//隔一段时间在3个地方随机产生敌人 invoke延时调用 "方法名"
InvokeRepeating("CreateRandomEnemy", 4, 5);
}
private void CreateItem(GameObject createObject, Vector3 createPosition, Quaternion createRotation)
{
//将地图资源的父级设为MapCreation
GameObject itemGo = Instantiate(createObject, createPosition, createRotation);
itemGo.transform.SetParent(gameObject.transform);
itemPositionList.Add(createPosition);
}
//产生随机位置的方法
private Vector3 CreateRandomPosition()
{
//不生成x=-10,x=10两行和y=-8,y=8两列
while (true)
{
Vector3 createPosition = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);
if (!IshavePosition(createPosition))
{
return createPosition;
}
}
}
//判断位置有没有
private bool IshavePosition(Vector3 createPosition)
{
for (int i = 0; i < itemPositionList.Count; i++)
{
if (createPosition == itemPositionList[i])
{
return true;
}
}
return false;
}
//产生随机敌人的方法
private void CreateRandomEnemy()
{
int num = Random.Range(0, 3);
if (num == 0)
{
CreateItem(item[6], new Vector3(-10, 8, 0), Quaternion.identity);
}
else if (num == 1)
{
CreateItem(item[6], new Vector3(0, 8, 0), Quaternion.identity);
}
else
{
CreateItem(item[6], new Vector3(10, 8, 0), Quaternion.identity);
}
}
}
场景切换
在这里添加进去,配上最后的代码,即实现场景切换
统计分数与血量
最终代码,创建一个PlayerManager脚本和空物体PlayerManager
将脚本托给PlayerManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class PlayerManager : MonoBehaviour {
public int lifeValue = 3;
public int playerScore = 0;
public bool isDead;
public bool isDefeat;
public Text PlayerScoreText;
public Text PlayerLifeValueText;
public GameObject isDefeatUi;
public GameObject born;
private static PlayerManager instance;
public static PlayerManager Instance
{
get
{
return instance;
}
set
{
instance = value;
}
}
private void Awake()
{
Instance = this;
}
private void Recover()
{
if (lifeValue <= 0)
{
isDefeat = true;
Invoke("ReturnToTheMainMenu", 3);
}
else
{
lifeValue--;
GameObject go = Instantiate(born, new Vector3(-2, -8, 0), Quaternion.identity);
go.GetComponent<Born>().createPlayer = true;
isDead = false;
}
}
public void ReturnToTheMainMenu()
{
SceneManager.LoadScene(0);
}
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (isDefeat)
{
isDefeatUi.SetActive(true);
return;
}
if (isDead)
{
Recover();
}
PlayerScoreText.text = playerScore.ToString();
PlayerLifeValueText.text = lifeValue.ToString();
}
}
省了最后一步,游戏开始界面
搭建这样一个UI界面
最后一个最终代码,创建Pointer脚本,托给这个Image
顺便将两个空物体拖进去
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Pointer : MonoBehaviour
{
//属性
private bool IsW = true;
//引用
public Transform[] Pos;
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
IsW = true;
transform.position = Pos[0].position;
}
else if (Input.GetKeyDown(KeyCode.S))
{
IsW = false;
transform.position = Pos[1].position;
}
if (IsW = true && Input.GetKeyDown(KeyCode.J))
{
SceneManager.LoadScene(1);//切换场景
}
}
}
坦克大战结束了,有什么问题可以在评论区或私信发消息。
欢迎大家捧场,谢谢!!!