Unity3D实现坦克大战

news2024/11/26 8:34:08

一、效果图演示

二、逻辑剖析

从界面上:

  • 需要一个Canvas满屏对着用户,该Canvas上展示用户的游戏数据,比如血条。
  • 需要一个Canvas放在蓝色坦克上方,也需要实时对着用户,显示敌人的血条信息
  • 两个坦克
  • 一个平面Plane放草地的纹理

从逻辑上:

  • 前后箭头键控制玩家前进或后退
  • 左右箭头键控制玩家左右转向
  • 鼠标左键或空格键控制玩家发射炮弹
  • 玩家血条希纳是在屏幕左上角
  • 相机在玩家后上方的位置,始终跟随玩家,朝玩家正前方看
  • 玩家移动时,敌人转向玩家,当偏离玩家的角度小于5度时,发射炮弹
  • 敌人血条显示在其上方,并且始终看向相机

三、界面组件信息

(1)游戏对象层级结构

(2)组件参数信息

1.玩家Player组件参数

NameTypePositionRotationScaleColor
PlayerEmpty(0, 0.25, -5)(0, 0, 0)(1, 1, 1)#228439
ButtonCube(0, 0, 0)(0, 0, 0)(2, 0.5, 2)#228439
TopCube(0, 0.5, 0)(0, 0, 0)(1, 0.5, 1)#228439
GunCylinder(0, 0, 1.5)(90, 0, 0)(0.2, 1, 0.4)#228439
FirePointEmpty(0, 1.15, 0)(0, 0, 0)(1, 1, 1)--

Player 游戏对象添加了刚体组件,并修改 Mass = 100,Drag = 1,AngularDrag = 0.1,Freeze Rotation 中勾选 X 和 Z。 

2.玩家HP组件参数

NameTypePositionWidth/HeightColor
PlayerHPCanvas(960, 540, 0)1920/1080--
Panel     Panel位置信息全是0#FFFFFF
HealthBGImage(-809,464,0)200/20#FFFFFF
HealthImage(-809,464,0)200/20#FF2230

玩家 PlayerHP 的 Canvas 渲染模式是 Screen Space - Overlay。

制作一个红色的图片放入Health的Source Image中,Health 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal。

3.敌人组件参数

NameTypePositionRotationScaleColor
EnemyEmpty(0, 0.25, 5)(0, 180, 0)(1, 1, 1)#15D3F9
ButtonCube(0, 0, 0)(0, 0, 0)(2, 0.5, 2)#15D3F9
TopCube(0, 0.5, 0)(0, 0, 0)(1, 0.5, 1)#15D3F9
GunCylinder(0, 0, 1.5)(90, 0, 0)(0.2, 1, 0.4)#15D3F9
FirePointEmpty(0, 1.15, 0)(0, 0, 0)(1, 1, 1)--

Enemy 游戏对象添加了刚体组件,并修改 Mass = 100,Drag = 0.5,AngularDrag = 0.1,Freeze Rotation 中勾选 X 和 Z。  

4.敌人HP组件参数

NameTypePositionWidth/HeightColor
HPCanvas(0, 0.85, 0)2/0.2--
HealthBGImage(0,0,0)2/0.2#FFFFFF
HealthImage(0,0,0)2/0.2#FF2230

敌人 HP 的 Canvas 渲染模式是 World Space,将刚才的红色底图也放入Health的Source Image中,Health 的 ImageType 设置为 Filled,Fill Method 设置为 Horizontal。

5.地面和炮弹的组件参数

NameTypePositionRotationScaleColor
PlanePlane(0, 0, 0)(0, 0, 0)(10, 10, 10)GrassRockyAlbedo
BulletSphere(0, 0.5, -5)(0, 0, 0)(0.3, 0.3, 0.3)#228439

炮弹作为预设体拖拽到 Assets/Resources/Prefabs 目录下,并且添加了刚体组件。

四、脚本代码:

1.CameraController

 CameraController 脚本组件挂在 MainCamera 游戏对象上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    // Start is called before the first frame update
    private Transform player; // 玩家
    private Vector3 relaPlayerPos; // 相机在玩家坐标系中的位置
    private float targetDistance = 15f; // 相机看向玩家前方的位置

    void Start()
    {
        relaPlayerPos = new Vector3(0, 4, -8);
        player = GameObject.Find("Player/Top").transform; // 世界坐标系位置
    }

    private void LateUpdate()
    {
        ComCameraPos();
    }

    // 计算相机坐标
    private void ComCameraPos()
    {
        Vector3 target = player.position + player.forward * targetDistance;
        transform.position = transformVecter(relaPlayerPos, player.position, player.right, player.up, player.forward);
        transform.rotation = Quaternion.LookRotation(target - transform.position);
    }

    // 以origin为原点,已知vec在坐标轴locX/locY/locZ中的位置,将vec转为世界坐标系的位置
    private Vector3 transformVecter(Vector3 vec, Vector3 origin, Vector3 locX, Vector3 locY, Vector3 locZ) 
    { 
        return vec.x * locX + vec.y * locY + vec.z * locZ + origin;
        
    }
}

2.BulletInfo

using UnityEngine;

public class BulletInfo
{
    public string name; // 炮弹名
    public Color color; // 炮弹颜色
    public Vector3 flyDir; // 炮弹飞出方向
    public float speed; // 炮弹飞行速度
    public float fireRange; // 炮弹射程

    public BulletInfo(string name, Color color, Vector3 flyDir, float speed, float fireRange)
    {
        this.name = name;
        this.color = color;
        this.flyDir = flyDir;
        this.speed = speed;
        this.fireRange = fireRange;
    }
}

3.BulletController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BulletController : MonoBehaviour
{
    private BulletInfo bulletInfo; // 炮弹信息
    private volatile bool isDying = false;

    // Start is called before the first frame update
    void Start()
    {
        gameObject.name = bulletInfo.name;
        GetComponent<MeshRenderer>().material.color = bulletInfo.color;
        float lifeTime = bulletInfo.fireRange / bulletInfo.speed; // 存活时间
        Destroy(gameObject, lifeTime);
    }

    // Update is called once per frame
    void Update()
    {
        transform.GetComponent<Rigidbody>().velocity = bulletInfo.flyDir * bulletInfo.speed;
    }

    public void SetBulletInfo(BulletInfo bulletInfo)
    {
        this.bulletInfo = bulletInfo;
    }

    private void OnCollisionEnter(Collision other)
    {
        if (isDying)
        {
            return;
        }

        if(IsHitEnemy(gameObject.name, other.gameObject.name))
        {
            other.transform.Find("HP/Health").GetComponent<Image>().fillAmount -= 0.1f;
            isDying = true;
            Destroy(gameObject, 0.1f);
        }else if(IsHitPlayer(gameObject.name, other.gameObject.name))
        {
            GameObject.Find("PlayerHP/Panel/Health").GetComponent<Image>().fillAmount -= 0.1f;
            isDying = true;
            Destroy(gameObject, 0.1f);
        }
    }

    private bool IsHitEnemy(string name, string otherName)
    {
        return name.Equals("PlayerBullet") && otherName.Equals("Enemy");
    }

    private bool IsHitPlayer(string name, string otherName)
    {
        return name.Equals("EnemyBullet") && otherName.Equals("Player");
    }
}

4.PlayerController

PlayerController 脚本组件挂在 Player 游戏对象上。 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private Transform firePoint; // 开火点
    private GameObject bulletPrefab; // 炮弹预设体
    private float tankMoveSpeed = 4f; // 坦克移动速度
    private float tankRotateSpeed = 2f; // 坦克转向速度
    private float fireWaitTime = float.MaxValue; // 距离上次开火已等待的时间
    private float bulletCoolTime = 0.15f; // 炮弹冷却时间

    void Start()
    {
        firePoint = transform.Find("Top/Gun/FirePoint");
        bulletPrefab = (GameObject)Resources.Load("Prefabs/Bullet");
    }

    // Update is called once per frame
    void Update()
    {
        fireWaitTime += Time.deltaTime;
        float hor = Input.GetAxis("Horizontal");
        float ver = Input.GetAxis("Vertical");
        Move(hor, ver);
        if(Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.Space))
        {
            Fire();
        }

    }

    // 坦克移动
    private void Move(float hor, float ver) 
    {
        if (Mathf.Abs(hor) > 0.1f || Mathf.Abs(ver) > 0.1f) {
            GetComponent<Rigidbody>().velocity = transform.forward * tankMoveSpeed * ver;
            GetComponent<Rigidbody>().angularVelocity = Vector3.up * tankRotateSpeed * hor;
        }
    }

    // 开炮
    private void Fire()
    {
        if (fireWaitTime > bulletCoolTime) { 
            BulletInfo bulletInfo = new BulletInfo("PlayerBullet", Color.red, transform.forward, 10f, 15f);
            GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity);
            bullet.AddComponent<BulletController>().SetBulletInfo(bulletInfo);
            fireWaitTime = 0f;
        }
    }
}

5.EnemyController

EnemyController 脚本组件挂在 Enemy 游戏对象上。  

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    private Transform target; // 目标
    private Transform top; // 炮头
    private Transform firePoint; // 开火点
    private Transform hp; // 血条
    private GameObject bulletPrefab; // 炮弹预设体
    private float rotateSpeed = 0.4f; // 坦克转向速度
    private float fireWaitTime = float.MaxValue; // 距离上次开火已等待时间
    private float bulletCoolTime = 1f; // 炮弹冷却时间

    // Start is called before the first frame update
    void Start()
    {
        target = GameObject.Find("Player/Top").transform;
        top = transform.Find("Top");
        firePoint = transform.Find("Top/Gun/FirePoint");
        hp = transform.Find("HP");
        bulletPrefab = (GameObject)Resources.Load("Prefabs/Bullet");
    }

    // Update is called once per frame
    void Update()
    {
        fireWaitTime += Time.deltaTime;
        if (LookAtTarget()) {
            Fire();
        }
        HPLookAtCamera();
    }

    private bool LookAtTarget()
    {
        Vector3 dir = target.position - top.position;
        float angle = Vector3.Angle(dir, top.forward);
        if(angle > 5)
        {
            int axis = Vector3.Dot(Vector3.Cross(dir, top.forward), Vector3.up) > 0 ? -1 : 1;
            GetComponent<Rigidbody>().angularVelocity = axis * Vector3.up * rotateSpeed;
            return false;
        }
        GetComponent<Rigidbody>().velocity = Vector3.zero;
        return true;
    }

    private void HPLookAtCamera()
    {
        Vector3 cameraPos = Camera.main.transform.position;
        Vector3 target = new Vector3(cameraPos.x, hp.position.y, cameraPos.z);
        hp.LookAt(target);
    }

    private void Fire()
    {
        if(fireWaitTime > bulletCoolTime)
        {
            BulletInfo bulletInfo = new BulletInfo("EnemyBullet", Color.yellow, top.forward, 5f, 10f);
            GameObject bullet = Instantiate(bulletPrefab, firePoint.position, Quaternion.identity); // 通过预设体创建炮弹
            bullet.AddComponent<BulletController>().SetBulletInfo(bulletInfo);
            fireWaitTime = 0;
        }
    }

}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1433446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nice Touch

Nice Touch是Unity最简单的多点触控输入解决方案,来自Corgi Engine和Infinite Runner Engine的制造商。所有功能完整文档论坛 功能列表: • 超级简单的设置 •兼容Unity的新旧输入系统 • 内置多点触控输入 •适用于所有移动设备 • 虚拟操纵杆 • 动态虚拟操纵杆 • 可重新定…

WebChat——一个开源的聊天应用

Web Chat 是开源的聊天系统&#xff0c;支持一键免费部署私人Chat网页的应用程序。 开源地址&#xff1a;https://github.com/loks666/webchat 目录树 TOC &#x1f44b;&#x1f3fb; 开始使用 & 交流&#x1f6f3; 开箱即用 A 使用 Docker 部署B 使用 Docker-compose…

web前端-------弹性盒子(2)

上一讲我们谈的是盒子的容器实行&#xff0c;今天我们来聊一聊弹性盒子的项目属性&#xff1b; *******************&#xff08;1&#xff09;顺序属性 order属性&#xff0c;用于定义容器中项目的出现顺序。 顺序属性值&#xff0c;为整数&#xff0c;可以为负数&#xff…

由亚马逊云科技 Graviton4 驱动的全新内存优化型实例 Amazon EC2 实例(R8g),现已开放预览

下一代 Amazon Elastic Compute CloudAmazon EC2) 实例的预览版现已公开 提供。全新的 R8g 实例 搭载新式 Graviton4 处理器&#xff0c;其性价比远超任何现有的内存优化实例。对于要求较高的内存密集型工作负载&#xff0c;R8g 实例是不二之选&#xff1a;大数据分析、高性能数…

SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式 基础(持续更新~)

具体操作&#xff1a; day2: 作用&#xff1a; 出现跨域问题 配相对应进行配置即可解决&#xff1a; IDEA连接的&#xff0c;在url最后加参数?useSSLfalse注意链接密码是123&#xff08;docker中mysql密码&#xff09; 注意&#xff0c;虚拟机中设置的密码和ip要和主机上…

代码随想录算法训练营第17天 | 110.平衡二叉树, 257. 二叉树的所有路径 ,404.左叶子之和

二叉树理论基础&#xff1a; https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE 110.平衡二叉树 题目链接&#xff1a;https://leetcode.cn/problems/balanced-binary-tree…

2024最新最详细【接口测试总结】

序章 ​ 说起接口测试&#xff0c;网上有很多例子&#xff0c;但是当初做为新手的我来说&#xff0c;看了不不知道他们说的什么&#xff0c;觉得接口测试&#xff0c;好高大上。认为学会了接口测试就能屌丝逆袭&#xff0c;走上人生巅峰&#xff0c;迎娶白富美。因此学了点开发…

林浩然与杨凌芸的Java奇缘:一场继承大戏

林浩然与杨凌芸的Java奇缘&#xff1a;一场继承大戏 Lin Haoran and Yang Lingyun’s Java Odyssey: A Tale of Inheritance 在一个充满代码香气的午后&#xff0c;我们故事的男主角——林浩然&#xff0c;一个热衷于Java编程的程序员&#xff0c;正在和他的“梦中女神”、同样…

【蓝桥杯选拔赛真题64】python数字塔 第十五届青少年组蓝桥杯python 选拔赛比赛真题解析

python数字塔 第十五届蓝桥杯青少年组python比赛选拔赛真题 一、题目要求 (注:input()输入函数的括号中不允许添加任何信息) 提示信息: 数字塔是由 N 行数堆积而成,最顶层只有一个数,次顶层两个数,以此类推。相邻层之间的数用线连接,下一层的每个数与它上一层左上…

JS第二天、原型、原型链、正则

☆☆☆☆ 什么是原型&#xff1f; 构造函数的prototype 就是原型 专门保存所有子对象共有属性和方法的对象一个对象的原型就是它的构造函数的prototype属性的值。prototype是哪来的&#xff1f;所有的函数都有一个prototype属性当函数被创建的时候&#xff0c;prototype属性…

机器学习超参数优化算法(贝叶斯优化)

文章目录 贝叶斯优化算法原理贝叶斯优化的实现&#xff08;三种方法均有代码实现&#xff09;基于Bayes_opt实现GP优化基于HyperOpt实现TPE优化基于Optuna实现多种贝叶斯优化 贝叶斯优化算法原理 在贝叶斯优化的数学过程当中&#xff0c;我们主要执行以下几个步骤&#xff1a; …

前端 - 基础 列表标签 - 自定义列表 详解

使用场景 &#xff1a; 常用于对术语或名词进行解释和描述&#xff0c;定义列表的列表前没有任何项目符号。 在 HTML 标签中&#xff0c; < dl > 标签用于定义 描述列表 &#xff08; 或定义列表 &#xff09; 该标签会与 <dt> ( 定义项目/名字 ) 和 <dd…

vue不同环境配置不同打包命令

这个需求非常普遍&#xff0c;通常情况我们在开发的时候一般会有三个环境&#xff1a;开发环境、测试环境、生产环境&#xff0c;我们一步步来看下。 vue环境变量是什么&#xff1f; 指的是在不同地方&#xff08;开发环境、测试环境、生产环境&#xff09;&#xff0c;变量就…

【教学类-46-04】吉祥字门贴4.0(华文彩云 文本框 空心字涂色 建议简体)

作品展示 背景需求&#xff1a; 1、制作了空心字的第1款 华光通心圆_CNKI &#xff0c;发现它不能识别某些简体字&#xff0c;但可以识别他们的繁体字&#xff08;繁体为准&#xff09; 【教学类-46-01】吉祥字门贴1.0&#xff08;华光通心圆_CNKI 文本框 空心字涂色&#xf…

3D Line Mapping Revisited论文阅读

1. 代码地址 GitHub - cvg/limap: A toolbox for mapping and localization with line features. 2. 项目主页 3D Line Mapping Revisited 3. 摘要 提出了一种基于线的重建算法&#xff0c;Limap&#xff0c;可以从多视图图像中构建3D线地图&#xff0c;通过线三角化、精心…

如何在 Microsoft Azure 上部署和管理 Elastic Stack

作者&#xff1a;来自 Elastic Osman Ishaq Elastic 用户可以从 Azure 门户中查找、部署和管理 Elasticsearch。 此集成提供了简化的入门体验&#xff0c;所有这些都使用你已知的 Azure 门户和工具&#xff0c;因此你可以轻松部署 Elastic&#xff0c;而无需注册外部服务或配置…

链表经典算法(+OJ刷题)

文章目录 前言一、移除链表元素二、链表的中间节点三.反转链表四.合并两个有序链表五.分割链表六.环形链表的约瑟夫问题总结 创作不易&#xff0c;点赞收藏一下呗&#xff01;&#xff01;&#xff01; 前言 在上一节&#xff0c;我们介绍了单链表的增&#xff0c;删&#xff…

Oracle12c之Sqlplus命令行窗口基本使用

Oracle12c之Sqlplus命令行窗口基本使用 文章目录 Oracle12c之Sqlplus命令行窗口基本使用1. 连接1. 超级用户2. 普通用户1. 创建普通用2. 连接 2. 修改用户连接数1. 查看默认连接最多用户数1. PL/SQL developer中查看2. Sqlplus中查看 2. 查看目前已经连接的用户数3. 修改用户连…

从零开始 TensorRT(4)命令行工具篇:trtexec 基本功能

前言 学习资料&#xff1a; TensorRT 源码示例 B站视频&#xff1a;TensorRT 教程 | 基于 8.6.1 版本 视频配套代码 cookbook 参考源码&#xff1a;cookbook → 07-Tool → trtexec 官方文档&#xff1a;trtexec 在 TensorRT 的安装目录 xxx/TensorRT-8.6.1.6/bin 下有命令行…

AtCoder Regular Contest 171(A~B)

A - No Attacking N*N棋盘上&#xff0c;放A个rook棋和B个pawn棋。 条件1&#xff1a;假设&#xff08;i&#xff0c;j&#xff09;上有一个rook&#xff0c;那么这 i 行和这 j 列&#xff0c;都不能再有其他棋子。 条件2&#xff1a;假设&#xff08;i&#xff0c;j&#x…