21222人加入学习
(135人评价)
Unity初级案例 - 坦克大战(Unity2017.1)

制作于2017年12月23日

价格 免费

坦克移动控制代码优化,原先的有个奇怪的问题,按住W或者S的时候没法左右移动,因为 v != 0 被 return 掉了。

    public float speed = 3;    
    private List<string> codelist = new List<string>();
    private string lastKeyCode = string.Empty;    

    // Update is called once per frame
    void Update()
    {
        SetLastKeyCode();
    }

    private void FixedUpdate()
    {
        PlayerMove();
    }

    private void PlayerMove()
    {
        Vector3 vector = new Vector3();
        Quaternion quaternion = new Quaternion();

        switch (lastKeyCode)
        {
            case "W":
                vector = new Vector3(0, speed * Time.deltaTime, 0);
                quaternion = Quaternion.Euler(0.0f, 0.0f, 0.0f);
                break;
            case "S":
                vector = new Vector3(0, -speed * Time.deltaTime, 0);
                quaternion = Quaternion.Euler(0.0f, 0.0f, 180.0f);
                break;
            case "A":
                vector = new Vector3(-speed * Time.deltaTime, 0, 0);
                quaternion = Quaternion.Euler(0.0f, 0.0f, 90.0f);
                break;
            case "D":
                vector = new Vector3(speed * Time.deltaTime, 0, 0);
                quaternion = Quaternion.Euler(0.0f, 0.0f, -90.0f);
                break;
            default:
                vector = new Vector3(); break;
        }

        if (lastKeyCode != "")
        {
            //设置坦克的朝向
            transform.rotation = quaternion;
            //设置坦克的位置
            transform.Translate(vector, Space.World);
        }
    }

    #region SetLastKeyCode
    private void SetLastKeyCode()
    {
        #region KeyDown
        if (Input.GetKeyDown(KeyCode.W)) { MoveKeyDown("W"); }
        if (Input.GetKeyDown(KeyCode.S)) { MoveKeyDown("S"); }
        if (Input.GetKeyDown(KeyCode.A)) { MoveKeyDown("A"); }
        if (Input.GetKeyDown(KeyCode.D)) { MoveKeyDown("D"); }
        #endregion

        #region KeyUp
        if (Input.GetKeyUp(KeyCode.W)) { MoveKeyUp("W"); }
        if (Input.GetKeyUp(KeyCode.S)) { MoveKeyUp("S"); }
        if (Input.GetKeyUp(KeyCode.A)) { MoveKeyUp("A"); }
        if (Input.GetKeyUp(KeyCode.D)) { MoveKeyUp("D"); }
        #endregion
    }

    private void MoveKeyDown(string keyCode)
    {
        if (!codelist.Contains(keyCode))
        {
            codelist.Add(keyCode);
        }

        if (codelist.Count > 0)
        {
            var key = codelist[codelist.Count - 1];
            lastKeyCode = key;
        }
    }

    private void MoveKeyUp(string keyCode)
    {
        codelist.RemoveAll(key => key.Contains(keyCode));
        if (codelist.Count > 0)
        {
            var key = codelist[codelist.Count - 1];
            lastKeyCode = key;
        }
        else
        {
            lastKeyCode = string.Empty;
        }
    }
    #endregion

 

[展开全文]

Input.GetAxisRaw("Hrizantal")和Inpit.GetAxis("Hrizantal")他们没有区别

[展开全文]

发生碰撞检测的两个必要条件:

两方都要有刚体

其中一方要有刚体,最好是运动的一方(因为刚体如果经常不运动就会发生休眠,这就不会发生碰撞检测了)

 

2D和3D游戏请使用对应不同的碰撞器(刚体也是)

 

update方法每一帧占用的时间是不同的,因为会受到系统、电脑性能的影响,所以方法调用的时机间隔就会不均匀.

但是fixedUpdate则是每隔一定周期执行一次

 

产生抖动的原因:

因为坦克加上了刚体,所以有了受力,再加上在update方法调用的时机不均匀,所以这样受力也就不均匀,从而产生了抖动。

 

 

首先说结论:物理模拟放在FixedUpdate中。

   原因首先得说FixedUpdate和Update的区别,FixedUpdate是每隔一定的周期执行一次,比如每隔1秒执行30次;Update是每帧之前执行一次,比如一般的FPS为30,也就是1秒有30帧,那么也是1秒执行30次。但是每隔一定的周期执行与系统性能无关,无论系统怎么样,都会1秒执行30次。而帧与系统性能有关,通常受到渲染的影响。比如同样一款游戏,性能好的电脑的FPS是30,差一点的可能只有10。这样Update就不是固定的1秒30次了。

  那么为什么物理模拟要放在FixedUpdate中呢?举个例子,A和B相距30米,一个人从A移动到B,在Unity中可能是这样实现的,在Update/FixedUpdate中写个位移函数,每次移动1米。那么对于在FixedUpdate中写,不论电脑怎么样,1秒内执行了30次,这个人从A移动到了B。但是对于写在Update中的情况,如果电脑好,FPS为30,1秒内执行了30次,这个人同样从A移动到了B,但是电脑性能差,FPS为10,1秒执行了10次,这个人只移动了10米。

   当然上面只是个例子,说的专业一点就是Update会因为系统性能产生抖动的现象

[展开全文]

使用自身方向 第二个参数要使用space.world

[展开全文]

Invoke方法记时和Destory方法记时是同时开始的,如果Invoke方法比Destory方法设置的时间长一些,比如1.0f和0.8f,则在调用Borntank方法生成坦克的1s内,到第0.8s时就开始运行Destory方法销毁了Born游戏物体,坦克无法生出。

坦克的产生依赖于Born游戏物体,如果游戏界面没有Born物体,则无法产生坦克。

Enemy遇到Player无法被销毁。

[展开全文]

PlayerMove  Script:

publix float moveSpeed=3;//定义速度

void Start(){

 

}

void Update(){

    float h = Input.GetAxisRam("Horizontal");

transform.Translate(Vector3.right*h*moveSpeed*Time.deltaTime,Space.World);

float v = Input.GetAxisRam("Vertical");

transform.Translate(Vector3.up*v*moveSpeed*Time.deltaTime,Space.World);

}

[展开全文]
  • 图片与图集的区别
  • 图集:
  • 单张图片:
  • 切割图集:
  • 第二个选项,以固定大小的格切割 

 

[展开全文]

# 项目问题
1. 2D建议Game面板5:4比例,摄像头距离为8.5。

2. 移动:
    - 获取水平垂直输入:

    ```c#
    float h = Input.GetAxisRaw("Horizontal");
    float v = Input.GetAxisRaw("Vertical");
    ```
    - 移动:

    ```c#
    transform.Translate(Vector3.right * h  * moveSpeed * Time.deltaTime, Space.World);
    transform.Translate(Vector3.up * v  * moveSpeed * Time.deltaTime, Space.World);
    ```
    
    - ==Time.deltaTime: 每一帧所花费的时间==

3. 获取组件GetComponent<>();
    - 获取图片渲染:

    ```c#
    private SpriteRenderer sr; 
    sr = GetComponent<SpriteRenderer>(); 
    赋值 sr.sprite = sprite
    ```

4. 添加刚体之后的元素在移动时候,出现抖动现象应该从Update移动到FixUpdate中执行(deltaTime 要改为 fixedDeltaTime)。

5. 监听键盘鼠标输入:

    ```c#
    Input.GetKeyDown(KeyCode.Space)
    Input.GetKeyUp(KeyCode.A)
    ```

6. 实例化物体 Instantiate(物体, 位置, 角度(四元数))

    ```c#
    public GameObject bulletPrefab;
    GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
    ```

7. 欧拉角(x、y、z),四元数(向量)
    - 欧拉角转换为四元数
        
    ```
        Quaternion rotation = Quaternion.Euler(0, 30, 0);
    ```
    - Vector3 是欧拉角
    
    ```
    Vector3 rotationVector = new Vector3(0, 30, 0);
    ```

8. transform.up 和 Vector3.up (Space.World)
    - transform.up 是相对于自身移动,移动方向不一定为(0,1,0),但是模长一定是1.
    - Vector3.up 相对于世界坐标移动,移动方向一定是(0,1,0)

9. 2D碰撞检测(触发):OnTriggerEnter2D()
    
    ```
        private void OnTriggerEnter2D(Collider2D collision)
        {
            print(collision.tag);
            print(collision.name);
        }
    ```
    - collision 被碰撞物体信息
    - ==collision.SendMessage("Die"); 触发被碰撞物体内部方法==
    
10. 设置物体激活状态: SetActive(状态)

    ```
    public GameObject bulletPrefab;
    bulletPrefab.SetActive(true);
    bulletPrefab.SetActive(false);
    ```
    - SetActive(true); 设置物体启用
    - SetActive(false); 设置物体禁用
    
11. 延时调用方法:Invoke(方法名, 延时时间秒)

    ```
    Invoke("PlayerBorn", 0.8f);
    private void PlayerBorn()
    {
        Instantiate(playerPrefab, transform.position, transform.rotation);
    }
    ```
    
12. 重复调用方法:InvokeRepeating(方法名,多少秒之后立即调用,每隔多少秒调用一次)
    
13. 随机数(包左不包右):Random.Range(0, 1)
14. Quaternion.identity: 无旋转即:Quaternion(0,0,0,0)
15. SetParent: 设置父物体

    ```
        GameObject gb = Instantiate(createGameObject, position, rotation);
        gb.transform.SetParent(gameObject.transform);
    ```
    
16. 单例模式:

    在使用 Unity 开发游戏的时候,经常会需要各种 Manager 类用于管理一些全局的变量和方法,例如最常见的 GameManager 用于记录各种需要在整个游戏过程中持久化的数据。本文以 GameManager 为例,假设我们有以下几个需求:
    
    - 整个游戏过程中必须有且只有一个 GameManager
    - 在 GameManager 里会记录一个叫 Value 的整型变量
    - 切换游戏场景的时候 GameManager 不能被销毁
    - 有两个游戏场景分别叫 FirstScene 和 SecondScene
    - 每一个场景中都会有一个按钮,点击后会跳转到另一场景,并且对 GameManager.Value 进行 +1 操作
    - 每一个场景中都会显示当前 GameManager.Value 的值
    
    ```
    public class GameManager : MonoBehaviour
    {
        public static GameManager Instance { get; private set; }
        public int Value { get; set; } = 0;
     
        private void Awake()
        {
            Instance = this;
        }
    }
    ```

17. 声音播放:
    
    - AudioSource.PlayClipAtPoint()

    ```
        public AudioClip barriarAudio; // 获取声音资源
        AudioSource.PlayClipAtPoint(barriarAudio, transform.position); // 播放(静态方法)
    ```
    - AudioSource 和 AudioClip
        - AudioSource 是声音控制
        - AudioClip 是声音资源
        
        ```
            public AudioSource moveAudio;
            public AudioClip[] tankAudio;
            if (!moveAudio.isPlaying)
            {
                moveAudio.clip = tankAudio[1];
                moveAudio.Play();
            }
        ```
        - moveAudio.Play() 播放
        - moveAudio.clip 设置声音资源
        - moveAudio.volume = 0.5f; 声音大小0-1

[展开全文]

其实在一开始控制坦克的朝向那里,不使用替换的方式而是直接改变坦克本身朝向的话,这里就可以直接 transform.rotation 而不需要再去+子弹应该旋转的角度了。

[展开全文]

判断位置列表是否包含位置可以使用 List 的Contains 方法,用于判断列表中是否包含某个元素。

        while (true)
        {
            Vector3 createPostion = new Vector3(Random.Range(-9, 10), Random.Range(-7, 8), 0);
            if (!itemPositionList.Contains(createPostion))
            {
                return createPostion;
            }
        }

 

[展开全文]

transform.up=y轴移动;

transform.right=x轴移动;

 

[展开全文]

通过建立访问数组来解决图片方向的问题

[展开全文]

1. 发生碰撞的条件:两者身上都有碰撞器,且至少一者身上有刚体

 

2. 刚体一般添加给运动的一方,因为物体物体长时间不运动时,其刚体组件会休眠,导致无法发生碰撞检测

 

3.  生命周期函数 FixedUpdate(),再Update()之后执行

 

4.  FixedUpdate()和Update()说明:

     ①FixedUpdate():固定物理帧,每帧大小一致;物体受力不变,碰撞时不会发生抖动

     ②Update()每帧大小不一致,且因设备而异;物体受力发生改变,碰撞时会发生抖动 

[展开全文]

x轴:vector3.right

y轴:vector3.up

float h = Input.GetAxis("Horizontal");
transform.Translate(Vector3.right * h * moveSpeed * Time.deltaTime, Space.World);
//三维向量,方向×按键输入×移动速度×按时间运动(每秒10米而不是每桢10米),空间为整个空间。


float v = Input.GetAxis("Vertical");
transform.Translate(Vector3.up * v * moveSpeed * Time.deltaTime, Space.World);
//三维向量,方向×按键输入×移动速度×按时间运动,空间为整个空间。

 

[展开全文]

新建 2D Tank 项目

导入package文件(zhi'jie't

 

[展开全文]

Texture Type(贴图显示):默认Default;

改成Sprite(2D and UI))点击Apply;

[展开全文]

Bullect

public float moveSpeed = 10;

transform.Translate(transform.right*moveSpeed*Time.deltaTime,Space.World);

transform.Translate(transform.right*moveeSpeed*Time,deltaTime,Space.World);

private float timeVal;

OnTriggerEnter 2D(Collider2D collision)

[展开全文]

考试重点

碰撞检测:发生碰撞的两方都要有碰撞体,其中一方要有刚体,还有最好是运动的一方(因为刚体长期不用就会休眠)

碰撞检测有2D和3D的区别 应该对应起来

没有加Boxcllide2D就可以让玩家穿过

 

遇到的问题是

1玩家会斜着走,玩家体验不理想(改进的方法是player的rigid body上的constriaints的z 轴选上)

2还有碰到别的物体时会有抖动(是因为加上刚体之后在Update中每一帧的受力不同,但是在fixUpdate中就可以每一帧都相同)(改进方法是Player脚本用一个生命周期函数改进FixUpdate也叫固定物理针,然后将所有的方法放到固定物理针中就可以了,就不会发生都懂的情况了,但是也只是解决了大部分问题而已,有些小的问题还要用其他方法解决

玩家按键盘的时候可能会同时按下几个键导致游戏人物斜着z

[展开全文]

授课教师

SiKi学院老师

课程特色

图文(1)
视频(31)
下载资料(1)