2019年7月22日 学习日记

说实话今天是二十三号,不过我在补二十二号的博客,现在已经是晚上了,今天也发生了很多有趣的事情比如遇到了打伞也没有用的雨,倔强的我就算全身湿透也不愿放弃外出欣赏风景的机会,于是我买了一身新衣服加人字拖,松花江畔独自小酌。享受孤独和美好的景色也是生活的一部分。

让我们来进行一个回忆,这个过程可能和我第一天的日记有一些重叠,不过不要在意,因为这是我从第一个游戏又做到第六个游戏了,刚开始我直接做第六个游戏,出现了一系列问题,我就决定先打一下基础,了解一下这个软件的逻辑结构和代码使用方式。
1、挂上角色和场景
2、创建一个组件并将角色挂在上面
3、在组件上挂上rigidbody和collider
4、创建C#脚本控制角色
5、由于这个游戏存在AI(人工智能——敌人),且和玩家角色的 很多行为都一样,这里我们选择利用C#中的继承,将玩家和敌人的共同点都放在一个公共的Character上,这会减少代码量让代码显得有层次感。
6、创建我们需要的变量和需要实例化的接口:

    protected CharacterController cc;//这里是cc控件
    protected Animator animator;//动画控件
    protected bool rotateComplete = true;//旋转是否完成(转身)
    Vector3 pendingVelocity;//向量——》速度
    public float runSpeed;//跑动速度
    public float jumpPower;//跳跃瞬间给速
    public int damage = 100;//一次攻击的伤害
    public int health = 100;//生命值
    public GameObject deathFX;//死亡特效,需要用GameObject点出来

取到控件:

 private void Awake()
    {
        cc = GetComponent<CharacterController>();//得到当前CharacterController脚本
        animator = GetComponentInChildren<Animator>();//得到角色下的Animator,子节点下的animator(model)
    }

由于我们将Character脚本挂在了Character控件上而不是Player,且Player是Character的子节点,这时候要想得到子节点的脚本就需要用GetComponentInChildren方法。

刷新方法,这个是比较重要的,因为每一帧都要检测,且大部分方法都要在这里进行调用以对游戏产生实时的影响:

    private void Update()
    {
        //移动
        pendingVelocity.z = 0f;//z轴速度锁定
        cc.Move(pendingVelocity * Time.deltaTime);//移动方向为速度乘以前后帧差时间 
        //更新动画
        animator.SetFloat("Speed", cc.velocity.magnitude);//magnitude返回了向量的长度,即距离(0,0,0)。最常用来返回物体的移动速度,这个只能读取,也可以用标准化后除以速度
        animator.SetBool("Grounded", cc.isGrounded);//methodname,bool
        //更新重力
        pendingVelocity.y += cc.isGrounded ? 0f : Physics.gravity.y * 10f * Time.deltaTime;//在地上就没有y速度,没在地上就要受到重力作用,单位i力*g*时间帧差
        AttackCheck();
    }

这里其实有一个小问题,就是pendingVelocity的值在哪里取到
更新动画的方法就是对animator中设置的过程判断量进行传入,对其值进行更新。
更新重力是为了让物体飞起来的时候有一个竖直向下大小-10m/s^2的加速度,判断条件就是在不在地上
至于最后的攻击检测,就是看这个角色有没有被攻击,不管是人或者是敌人都有被攻击的可能。
下面对各个模块进行说明:

    public void Jump()
    {
        if (cc.isGrounded)//在地上
        {
            pendingVelocity.y = jumpPower;//跳跃后初始速度
        }
    }

很简单的跳跃方法,先利用自带的charactercontroller组件中的isGrounded方法得到物体是否在地上,再巨顶是否进行y轴速度的改变(跳跃)


    public void Move(float inputX)//移动函数
    {
        pendingVelocity.x = inputX * runSpeed;//x轴速度大小为,单位方向乘以运动的速度runSpeed
    }

移动函数,其实和跳跃函数差不多,本质就是对物体速度对应轴速度的改变,不过这里有方向和速度,两部分分开给,明显这里的inputX是要用我们的getAxis方法来得到,而runspeed则需要我们来自己设置,来确定速度的大小。


    public void Rotate(Vector3 lookDir, float turnSpeed)//转向速度
    {
        rotateComplete = false;//旋转未完成
                               //   var targetPos = transform.position + lookDir;//目标位置是原位置加上方向向量后的位置
        var characterPos = transform.position;//取到现在的位置
        //去掉y轴影响
        //characterPos.y = 0;
        //   targetPos.y = 0;
        //角色面朝的向量
        //   Vector3 faceToDir = targetPos - characterPos;//目标向量减去原向量,也就是LookDir
        Vector3 faceToDir = lookDir;
        //角色面朝目标方向的四元数
        Quaternion faceToQuat = Quaternion.LookRotation(faceToDir);
        //球面插值
        Quaternion slerp = Quaternion.Slerp(transform.rotation, faceToQuat, turnSpeed * Time.deltaTime);
        if (slerp == faceToQuat)
        {
            rotateComplete = true;
        }
        transform.rotation = slerp;
    }

这个比较复杂,我决定先看一遍代码明天早上再复习一遍然后写这个解析
2019年7月24日 09:03:40
我们在上面声明的bool变量在这里派上了用场,用来判断旋转是否完成。刚开始赋值。其实这个变量本质上只在这个旋转方法里面用,在外部并不会影响什么,因此初值对结果没有影响。其中取消y影响的代码已经被我注释了,因为对物体的运动感不影响,我不知道这段代码的作用,也不知道自己是不是以后会知道,先标出来。这里他用两个向量的插值来确定面向的方向,而实际上在二维空间内,横方向只有正负一,因此我们可以直接把接收来的lookDIr来用于面向方向的判断,不需要进行减法直接进行赋值。——>下面用到了一个库(方法),四元数库。我们通过角色要面向的方向可以通过Quaternion.LookRotation方法来得到一个四元数类型的字段,我们需要通过这个四元数字段来当作形参来调用Slerp方法。:Quaternion.Slerp(transform.rotation, faceToQuat, turnSpeed * Time.deltaTime);这里的理解方法是从transform.rotation旋转到faceToQuat(四元数)这个角度,且旋转速度为每一帧以turnSpeed进行转动,一旦我们新创建的四元数和我们通过面向方向得到的四元数相等了,旋转就完成了,transform.rotation也就变成了slerp(四元数)


死亡函数:

    public void Death()
    {
        var fx = Instantiate(deathFX, transform.position, Quaternion.Euler(Vector3.zero));
        Destroy(fx, 2);
        Destroy(gameObject);
    }

Instantiate是用来复制东西的,在官方API中Quaternion.Euler:Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis.意思是绕指定轴旋转指定度数。而instantiate是复制一个deathFX物体并旋转Vector3.zero角度后放在Transform.position位置。//加载物体5秒后销毁游戏物体 Destroy (gameObject, 5);可见我们在两秒之后摧毁对应游戏组件再摧毁玩家角色本身。特效在人消失的两秒后消失。


承受伤害函数:

public void TakeDamage(Character inflicter, int damage)
    {
        inflicter.Jump();
        health -= damage;
        if (health<=0)
        {
            Death();
        }
    }

敌人和我们都可以受到伤害,因此这个方法两方都有调用,不过这里存在同一命名空间的自调用,因此一起说明。

    public void AttackCheck()
    {
        var dist = cc.height / 2;
        //向下射线检测
        RaycastHit hit;
        if (Physics.Raycast(transform.position, Vector3.down, out hit, dist + 0.05f))
            //public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
            
        {
            if (hit.transform.GetComponent<Character>()&&hit.transform!=transform)
            {
                hit.transform.GetComponent<Character>().TakeDamage(this, damage);
            }
        }
    }

这人受到伤害跳一下的设定真的很超级玛丽,生命值自减也无所谓,生命值缩减到零或者零之下死亡也没毛病,主要是谁来调用函数。可见,上面的先声明了一个变量来接收角色的高度,而这里要利用的就是射线,通过设置射线的长度和方向、起点,来判断我们是否踩中了敌人的头部Physics.Raycast(transform.position, Vector3.down, out hit, dist + 0.05f),取到当前角色位置,竖直向下方向,长度为dist+0.05,判断hit是否成功。
hit若能得到被hit的角色组件并击中的不是施加攻击的角色,则会给被hit角色一个伤害。即:hit.transform.GetComponent<Character>().TakeDamage(this, damage);
说实话,前面这一大串:hit.tansform.GetComponent(),其实就是一个Character,如果前面要是命名了一个变量来接收,就不用写。其中hit.transform就挂着游戏对象,因此这里的this就是对应的击中角色,damage代表掉血。


下面看AI敌人的制作方法
下面看AI敌人的制作方法
下面看AI敌人的制作方法
下面看AI敌人的制作方法
下面看AI敌人的制作方法
下面看AI敌人的制作方法
下面看AI敌人的制作方法:

    Character character;
    float lastCheckStateTime = 0;
    float simulateInputX;
    bool simulateJump;
    void Start()
    {
        character = GetComponent<Character>();
    }
    void Update()
    {
        if (Time.time > lastCheckStateTime + 2)//判断时间间隔是否超过两秒
        {
            lastCheckStateTime = Time.time;      //改变检测时间的前time秒
            simulateInputX = Random.Range(-1f, 1f);//输入的X值在正负一之间取到
            simulateJump = Random.Range(0, 2) == 1 ? true : false;//跳跃
        }
        MoveControl(simulateInputX);
        JumpControl(simulateJump);
    }

    void MoveControl(float inputX)
    {
        character.Move(inputX);
        if (inputX != 0)
        {
            var dir = Vector3.right * inputX;//转向方向为单位向量乘以接收的正负一,其实说到底也是-+1
            character.Rotate(dir, 10);
        }
    }
    void JumpControl(bool jump)
    {
        if (jump)
        {
            character.Jump();//调用跳跃方法
            simulateJump = false;//每次AI跳跃之后一定要把simulateJump给关了
        }
    }

这里是直接对方法的调用,不过是由自己随机输入来传给他的父亲命名空间。再创建一个脚本,自己写一个相机:

    public Transform target;//位置类型的目标,玩家的transform
    public float distance = 8.0f;//与目标角色的距离
    public float hight = -1.0f;//相机的高度
    private void LateUpdate()//刷新函数
    {
        if (!target)//目标未选定
        {
            return;
        }
        transform.position = target.position;//取到角色的位置
        //Z轴间隔距离
        transform.position -= Vector3.forward * distance;//在Z轴与角色有一定的距离
                                                         //transform.position =transform.position - Vector3.forward * distance;
                                                         //高度调整
        transform.position = new Vector3(transform.position.x, hight, transform.position.z);//重置高度
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值