复刻小时候FC经典游戏-坦克大战

复刻小时候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;
    }
}

复制代码

代码比较简单,实现了最基本的游戏逻辑,大家可以自己扩展一些坦克武器,比如说火箭炮,地雷等游戏道具,创造一个属于你自己的坦克大战。

猜你喜欢

转载自juejin.im/post/7081946830560296990