复刻小时候FC经典游戏-坦克大战
我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛
大家小时候一定玩过一款FC游戏-坦克大战,笔者小时候最喜欢玩的就是这个游戏,今天将带着大家写一个单人版的坦克大战,在线试玩,第一次加载游戏比较慢,后续就会很快了。
开发思路流程:
- 开始界面
- 随机生成地图
- 创建坦克类(处理坦克音效、出生、死亡、开火、移动等逻辑)
- 敌人AI处理
- 游戏结束逻辑
1. 开始界面
开始界面的逻辑很简单,只需要加一个选择P1和P2的选项和一个开始按钮即可,并监听输入。
public class Option : MonoBehaviour
{
private int option = 0;
public Transform pos1;
public Transform pos2;
void Update()
{
if(Input.GetKeyDown(KeyCode.W))
{
transform.position = pos1.position;
option = 0;
}
if (Input.GetKeyDown(KeyCode.S))
{
transform.position = pos2.position;
option = 1;
}
if(option == 0 && Input.GetKeyDown(KeyCode.Space))
{
SceneManager.LoadScene(1);
}
}
}
复制代码
2. 随机生成地图
- 加载每种地图块资源并储存
- 用随机数来控制随机地图块类型
- for循环生成每一格地图(这里注意不包括玩家和敌人出生位置,还有老家位置)
public class MapCreation : MonoBehaviour
{
// 0:老家 1:墙 2:障碍 3:出生效果 4:河流 5:草 6:空气墙
public GameObject[] item;
private Vector3 heartPosition;
private Vector3[] defendHeartWallPosition = new Vector3[5];
private List<Vector3> existPosition = new List<Vector3>();
private void Awake()
{
CreateHeart();
CreateAirBarriar();
CreatePlayer();
existPosition.Add(new Vector3(-10, 8, 0));
existPosition.Add(new Vector3(0, 8, 0));
existPosition.Add(new Vector3(10, 8, 0));
CreateRandomMap();
InvokeRepeating("createEnemy", 0, 5);
}
private Vector3 createEnemyPosition()
{
int positionIndex = Random.Range(0, 3);
Vector3[] list = new Vector3[3] { new Vector3(-10, 8, 0), new Vector3(10, 8, 0), new Vector3(0, 8, 0) };
return list[positionIndex];
}
private void createEnemy()
{
if (PlayerManager.Instance.enemyNum <= 0)
{
return;
}
GameObject enemy = CreateItem(item[3], createEnemyPosition(), Quaternion.identity);
enemy.GetComponent<Born>().createPlayer = false;
}
private GameObject CreateItem(GameObject createGameObject, Vector3 position, Quaternion rotation)
{
GameObject gb = Instantiate(createGameObject, position, rotation);
gb.transform.SetParent(gameObject.transform);
if(!HasPosition(position))
{
existPosition.Add(position);
}
return gb;
}
private void CreateRandomMap()
{
for (int i = 0; i < 20; i++)
{
CreateItem(item[2], CreateRandomPosition(), Quaternion.identity);
CreateItem(item[4], CreateRandomPosition(), Quaternion.identity);
CreateItem(item[5], CreateRandomPosition(), Quaternion.identity);
}
for (int i = 0; i < 50; i++)
{
CreateItem(item[1], CreateRandomPosition(), Quaternion.identity);
}
}
private void CreatePlayer()
{
CreateItem(item[3], new Vector3(-2, -8, 0), Quaternion.identity);
}
private void CreateHeart()
{
// 实例化老家
heartPosition = new Vector3(0, -8, 0);
defendHeartWallPosition[0] = new Vector3(-1, -8, 0);
defendHeartWallPosition[1] = new Vector3(1, -8, 0);
for (int i = 2; i <= 4; i++)
{
defendHeartWallPosition[i] = new Vector3(i - 3, -7, 0);
}
CreateItem(item[0], heartPosition, Quaternion.identity);
for (int i = 0; i < defendHeartWallPosition.Length; i++)
{
CreateItem(item[1], defendHeartWallPosition[i], Quaternion.identity);
}
}
private void CreateAirBarriar()
{
for (int i = -10; i < 11; i++)
{
CreateItem(item[6], new Vector3(i, 9, 0), Quaternion.identity);
CreateItem(item[6], new Vector3(i, -9, 0), Quaternion.identity);
}
for (int i = -8; i < 9; i++)
{
CreateItem(item[6], new Vector3(-11, i, 0), Quaternion.identity);
CreateItem(item[6], new Vector3(11, i, 0), Quaternion.identity);
}
}
private Vector3 CreateRandomPosition()
{
while (true)
{
Vector3 newPosition = new Vector3(Random.Range(-10, 11), Random.Range(8, -9), 0);
if(!HasPosition(newPosition))
{
return newPosition;
}
}
}
private bool HasPosition(Vector3 position)
{
for (int i = 0; i < existPosition.Count; i++)
{
if(existPosition[i] == position)
{
return true;
}
}
return false;
}
}
复制代码
3. 玩家出生逻辑
这个就比较简单,初始化玩家预制体,并且播放出生动画即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Born : MonoBehaviour
{
public GameObject playerPrefab;
public GameObject[] enemyPrefabs;
public bool createPlayer;
// Start is called before the first frame update
void Start()
{
Invoke("PlayerBorn", 0.8f);
Destroy(gameObject, 0.8f);
}
private void PlayerBorn()
{
if(createPlayer)
{
Instantiate(playerPrefab, transform.position, transform.rotation);
}
else
{
int index = Random.Range(0, 2);
Instantiate(enemyPrefabs[index], transform.position, transform.rotation);
}
}
}
复制代码
4. 创建玩家类和敌人类
在这里要实现的有:
- 坦克的移动、开火、死亡
- 敌人的Ai
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float moveSpeed = 3;
public Sprite[] tankSprite;
public GameObject bulletPrefab;
public GameObject explosionPrefab;
public GameObject shieldPrefab;
public AudioClip attackAudio;
public AudioSource moveAudio;
public AudioClip[] tankAudio;
private SpriteRenderer sr;
private Vector3 bulletEulerAngle;
private float attackTime;
private float isDefendedTimeVal = 3f;
private bool isDefended = true;
// Start is called before the first frame update
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
void Start()
{
}
// Update is called once per frame
void Update()
{
if(isDefended)
{
shieldPrefab.SetActive(true);
isDefendedTimeVal -= Time.deltaTime;
if(isDefendedTimeVal <= 0)
{
isDefended = false;
shieldPrefab.SetActive(false);
}
}
}
private void FixedUpdate()
{
if(!PlayerManager.Instance.isDefeat)
{
Move();
if (attackTime >= 0.4f)
{
Attack();
}
else
{
attackTime += Time.fixedDeltaTime;
}
}
}
private void Attack()
{
if (Input.GetKeyDown(KeyCode.Space))
{
AudioSource.PlayClipAtPoint(attackAudio, transform.position);
Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletEulerAngle));
attackTime = 0;
}
}
private void Move()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
if (v != 0 && h != 0)
{
return;
}
if (h > 0)
{
sr.sprite = tankSprite[1];
bulletEulerAngle = new Vector3(0,0, -90);
}
else if (h < 0)
{
sr.sprite = tankSprite[3];
bulletEulerAngle = new Vector3(0, 0, 90);
}
if (v > 0)
{
sr.sprite = tankSprite[0];
bulletEulerAngle = new Vector3(0, 0, 0);
}
else if (v < 0)
{
sr.sprite = tankSprite[2];
bulletEulerAngle = new Vector3(0, 0, -180);
}
if (Mathf.Abs(v) > 0.05f || Mathf.Abs(h) > 0.05f)
{
moveAudio.clip = tankAudio[1];
moveAudio.volume = 0.1f;
if (!moveAudio.isPlaying)
{
moveAudio.Play();
}
}
else
{
moveAudio.clip = tankAudio[0];
moveAudio.volume = 0.1f;
if (!moveAudio.isPlaying)
{
moveAudio.Play();
}
}
transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);
transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
}
private void Die()
{
if(!isDefended)
{
Instantiate(explosionPrefab, transform.position, transform.rotation);
Destroy(gameObject);
PlayerManager.Instance.isDead = true;
PlayerManager.Instance.life--;
}
}
}
复制代码
敌人:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float moveSpeed = 3;
public Sprite[] tankSprite;
public GameObject bulletPrefab;
public GameObject explosionPrefab;
private SpriteRenderer sr;
private Vector3 bulletEulerAngle;
private float attackTime;
private float turnDirectionTime;
private float v;
private float h;
// Start is called before the first frame update
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
void Start()
{
}
// Update is called once per frame
void Update()
{
if (attackTime >= 3)
{
Attack();
}
else
{
attackTime += Time.deltaTime;
}
}
private void FixedUpdate()
{
Move();
}
private void Attack()
{
Instantiate(bulletPrefab, transform.position, Quaternion.Euler(transform.eulerAngles + bulletEulerAngle));
attackTime = 0;
}
private void Move()
{
if (turnDirectionTime >= 4)
{
int randomDirection = Random.Range(0, 8);
if (randomDirection >= 5)
{
v = -1;
h = 0;
}
else if (randomDirection >= 3)
{
v = 0;
h = 1;
}
else if (randomDirection >= 1)
{
v = 0;
h = -1;
}
else
{
v = -1;
h = 0;
}
turnDirectionTime = 0;
}
else
{
turnDirectionTime += Time.deltaTime + 0.05f;
}
if (h > 0)
{
sr.sprite = tankSprite[1];
bulletEulerAngle = new Vector3(0, 0, -90);
}
else if (h < 0)
{
sr.sprite = tankSprite[3];
bulletEulerAngle = new Vector3(0, 0, 90);
}
if (v > 0)
{
sr.sprite = tankSprite[0];
bulletEulerAngle = new Vector3(0, 0, 0);
}
else if (v < 0)
{
sr.sprite = tankSprite[2];
bulletEulerAngle = new Vector3(0, 0, -180);
}
transform.Translate(Vector3.right * h * moveSpeed * Time.fixedDeltaTime, Space.World);
transform.Translate(Vector3.up * v * moveSpeed * Time.fixedDeltaTime, Space.World);
}
private void Die()
{
Instantiate(explosionPrefab, transform.position, transform.rotation);
Destroy(gameObject);
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.transform.tag == "Enemy")
{
turnDirectionTime = 5;
}
}
}
复制代码
5. 处理子弹逻辑
这里要处理子弹分别打到砖块、坦克、钢铁上面的音效和效果
public class Bullet : MonoBehaviour
{
public float moveSpeed = 10;
public bool isPlayerBullet;
public AudioClip barriarAudio;
void Update()
{
transform.Translate(transform.up * moveSpeed * Time.deltaTime, Space.World);
}
private void OnTriggerEnter2D(Collider2D collision)
{
switch (collision.tag)
{
case "Tank":
if (!isPlayerBullet)
{
collision.SendMessage("Die");
Destroy(gameObject);
}
break;
case "Heart":
collision.SendMessage("Die");
Destroy(gameObject);
break;
case "Wall":
Destroy(collision.gameObject);
Destroy(gameObject);
break;
case "Enemy":
if (isPlayerBullet)
{
collision.SendMessage("Die");
Destroy(gameObject);
PlayerManager.Instance.score++;
PlayerManager.Instance.enemyNum--;
}
break;
case "Barriar":
if(isPlayerBullet)
{
AudioSource.PlayClipAtPoint(barriarAudio, transform.position);
}
Destroy(gameObject);
break;
default:
break;
}
}
}
复制代码
6. 生成老家和处理游戏结束逻辑
当老家被攻击则游戏结束
public class Heart : MonoBehaviour
{
private SpriteRenderer sr;
public Sprite brokenHeart;
public GameObject explosionPrefab;
public AudioClip dieAudio;
// Start is called before the first frame update
void Start()
{
sr = GetComponent<SpriteRenderer>();
}
private void Die()
{
AudioSource.PlayClipAtPoint(dieAudio, transform.position);
Instantiate(explosionPrefab, transform.position, transform.rotation);
sr.sprite = brokenHeart;
PlayerManager.Instance.isDefeat = true;
}
}
复制代码
代码比较简单,实现了最基本的游戏逻辑,大家可以自己扩展一些坦克武器,比如说火箭炮,地雷等游戏道具,创造一个属于你自己的坦克大战。