游戏编程 您所在的位置:网站首页 psv1000右摇杆不能控制上下左右 游戏编程

游戏编程

2023-07-31 08:07| 来源: 网络整理| 查看: 265

Godot引擎中控制移动的几种方式

cover-02

1。我们要讲什么?

本篇我们要梳理一下游戏中常见的几种角色移动的控制方式,比如: 使用键盘或手柄:

八个方向移动旋转(左右键控制) + 移动(上下键控制)

使用鼠标:

自由移动(点击位置)旋转(始终朝向鼠标) + 移动(上下键控制) 2。准备一个简单的演示

本篇为了聚焦我们要研究的问题,我们不再给角色加入动画等其它效果,仅使用godot引擎的图标作为精灵,之后的一切控制演示都由它承担。 在这里插入图片描述

首先建立一个以KinematicBody2D为载体的精灵,其包含的节点和层级关系如上图。KinematicBody2D是一个运动学性质的节点。(在另一篇文章里会详细说)Sprite是一个精灵节点,把godot的图标(icon.png)拖到它的Texture属性上。CollisionShape2D是一个碰撞检测盒,我们给了它一个矩形形状。本篇文章中可以不使用。(加上是为了消除警告)给KinematicBody2D节点附加一个脚本。(选中此节点,然后点击右上角的在这里插入图片描述 图标)最后我们可以看到如下效果,因为精灵默认处于(0,0)处,所以会展示在窗口的角落。因为我们脚本里什么都没做,所以图标也只是静静的躺在那里。 在这里插入图片描述 3。配置输入映射(Input Map)

输入映射可以理解成什么东西呢?它相当于提前把我们可能按的键,比如键盘的W A S D、手柄的某个键或鼠标的某个键绑定到一个字符串,而godot提供了一个方便的函数,我们只要把我们绑定的字符串提供给这个函数,根据返回值真假(true or false)就可以知道与这个字符串绑定的的键是否被按下。

找到配置的对话框:Project -> Project Settings… -> Input Map选项卡 在这里插入图片描述从上面的图中可以知道,我们配置了5个映射关系: 在这里插入图片描述好,现在如果我们要检测上、下、左、右键是否有按下,我们可能写出这样的代码: func get_input(): ... if Input.is_action_pressed('move_right'): velocity.x += 1 if Input.is_action_pressed('move_left'): velocity.x -= 1 if Input.is_action_pressed('move_down'): velocity.y += 1 if Input.is_action_pressed('move_up'): velocity.y -= 1 ... 下面是输入映射的基本操作:在Action文本框中输入你想绑定的字符串名字,一般是提示性的(比如:move_left、attack等等);点击Add按钮后,你要绑定的字符串会出现在下面的列表里;最后在你的字符串右边,点击"+"号来添加你要将哪些键映射到这个字符串。 在这里插入图片描述能不能对godot所有支持的键做一个基本的介绍,比如: 在这里插入图片描述其中Joy Axis(轴)的标记会多一点,因为一般手柄上会有左右两个摇杆,所以:0表示左摇杆横轴,1表示左摇杆纵轴,2表示右摇杆横轴,3表示右摇杆纵轴;“+” 表示正半轴,"-"表示负半轴;这两组标记组合一下得到下面的所有输入: 在这里插入图片描述复习一遍。所以,下面这个映射的含义是:当第0号玩家(Device x表示第x号玩家,用于多个人玩的时候)的手柄的左摇杆的横轴推向正半轴的时候,表示它要往右移动(呼~) 在这里插入图片描述总结一下。引入这种输入映射的机制有什么好处,相信大家也都能有点体会: 比较明显的一点是,可以将多个键映射到一个字符串上,拿移动来说,即支持键盘同时支持手柄的游戏很常见,也非常方便且必要的,用这种多对一的关系可以很自然的实现,而不用在代码中写两套检测输入的逻辑;第二,明确了语义,如果我们直接在代码里检测X键是否被按下,如果被按下就执行一系列动作,那到底是什么动作呢?通过将X键绑定到“jump”这个字符串,当代码中再遇到它时,并不是X键被按下,而是跳跃键被按下了。恩!我不用自己瞎猜了~还有,godot帮我们做这一切,这意味着,当我们想改变某个键的含义时,只需要在Input Map里把名字改了,非常灵活~ 输入映射已经讲的足够多了,下面我们正式开始~ 4。八个方向的移动

八个方向的移动,也就是我们理解的那样,先看一眼下面的图来确认一下: 在这里插入图片描述 上下左右这个好理解,根据我们第【3。】部分对输入映射的讲解,在检测到"按下键盘的W S A D" 或 “手柄摇杆扳向对应方向” 时分别往对应的方向移动即可。 另外四个方向呢?这个我们结合代码来讲,在之前附加的脚本中键如下面的代码:

extends KinematicBody2D export (int) var speed = 200 var velocity = Vector2() func get_input(): velocity = Vector2() if Input.is_action_pressed('move_right'): velocity.x += 1 if Input.is_action_pressed('move_left'): velocity.x -= 1 if Input.is_action_pressed('move_down'): velocity.y += 1 if Input.is_action_pressed('move_up'): velocity.y -= 1 velocity = velocity.normalized() * speed func _physics_process(delta): get_input() velocity = move_and_slide(velocity) 明确一点很重要:速度是一个矢量!我们用了4个if分支,而不是if…elif…elif…else。这就造成了一种效果,速度会累加起来(再次明确,速度是一个矢量,累加的不只是大小,还有方向)结合一个具体过程来理解:某次,我们同时按下了向右和向下(W&&D键或摇杆右下扳),也就是说先后命中了两个if语句(如下代码): if Input.is_action_pressed('move_right'): velocity.x += 1 ... if Input.is_action_pressed('move_down'): velocity.y -= 1 速度累加过程如下图:步骤1,给速度的横轴正向增1;步骤2,给速度的纵轴正向增1(godot的y坐标增向和数学中的相反);步骤3,这时这两个矢量相加的方向是指向(1,1)但大小是“根2”,这是不行的,这样的效果是,精灵斜向(比如,右下)运动要比正向(比如,上下左右)运动快!;所以有了步骤4,将速度重新规范化到1,整体运动速度的大小则靠乘一个因子speed来实现。 在这里插入图片描述最后看看演示效果: 在这里插入图片描述 5。旋转(左右键控制) + 移动(上下键控制)

有了第【4。】中对各种概念的把握,其它几种控制运动的方式也就更好理解了,我们要梳理的第二种控制方式可以产生类似于行星的轨迹,这在好多星球大战的游戏中可以用到。将下面的代码键入脚本:

extends KinematicBody2D export (int) var speed = 200 export (float) var rotation_speed = 1.5 var velocity = Vector2() var rotation_dir = 0 func get_input(): rotation_dir = 0 velocity = Vector2() if Input.is_action_pressed('move_right'): rotation_dir += 1 if Input.is_action_pressed('move_left'): rotation_dir -= 1 if Input.is_action_pressed('move_down'): velocity = Vector2(-speed, 0).rotated(rotation) if Input.is_action_pressed('move_up'): velocity = Vector2(speed, 0).rotated(rotation) func _physics_process(delta): get_input() rotation += rotation_dir * rotation_speed * delta velocity = move_and_slide(velocity) 我们的控制方式是:按左右旋转(键盘的A、W键或摇杆往左右扳),按上下移动。我们引入了控制旋转速度的变量rotation_speed和记录旋转方向的变量rotation_dir。发现有什么不同了吗?不同的本质在于旋转多少度是一个标量,rotation_dir我们只是想要它的符号,它的+或-两个符号分别代表了顺时针或逆时针旋转;rotation_speed表示旋转多少度。rotation是KinematicBody2D节点本身的属性,所以修改了它就可以让精灵转动起来了。最后两个问题:velocity为什么没有规范化?速度为什么要调用rotated()?这两个问题都可以用下面的代码解答: velocity = Vector2(speed, 0).rotated(rotation) Vector2(speed, 0)可以写成Vector2(1 * speed, 0)所以速度本身就是已经规范化好的。调用rotated(rotation)的原因是我们始终想让精灵朝旋转的方向运行,所以也叫“小行星轨迹”!下面是“小行星轨迹”的演示: 在这里插入图片描述 6。自由移动(鼠标点击位置)

这种控制方式是最直观的一种,适合很多RPG游戏,因为我们希望我们点到哪,我们控制的角色就在地图上走到哪。整顿一下思路,首先需要有个方法来得到当前用户鼠标点击的位置,我们之前绑定了一个click字符串可以直接拿来用,然后用当前精灵所在的位置和点击的位置(目标位置)构造出一个速度,大功告成!

extends KinematicBody2D export (int) var speed = 200 var target = Vector2() var velocity = Vector2() func _input(event): if event.is_action_pressed('click'): target = get_global_mouse_position() func _physics_process(delta): velocity = position.direction_to(target) * speed # look_at(target) if position.distance_to(target) > 5: velocity = move_and_slide(velocity) 如果发生了一个鼠标左键的点击事件,紧接着我们就可以用get_global_mouse_position()函数得到点击的位置。position是KinematicBody2D本身的一个属性,也就是精灵当前所在位置。point_A.direction_to(Point_B)可以得到点A到点B的方向向量(自然是规范化后的单位向量)得到的单位方向向量 * speed,speed是因子,从而控制整体运动速度 if position.distance_to(target) > 5: ... 最后一个问题,也就是为什么要加上面这句条件代码?目的是为了防止抖动。为什么会产生抖动?因为每次执行到move_and_slide(velocity)这个运动函数时所走过的距离之和并不一定正好等于精灵位置和目的位置间的距离。不太懂?没关系,我们试着举一个实例: 假设我们将speed提高到36000,那么速度velocity = 1 * 36000 = 36000,单位是像素/秒(pps)假设我们的每秒帧数(fps)是60左右,那么一帧所用时间大概稳定在1/60秒左右,也就是说从上一次move_and_slide(velocity)执行到这一次move_and_slide(velocity)大概是1/60秒由以上两个假设,我们可以推出:每次执行到move_and_slide(velocity)时,我们走过的距离基本是36000 * 1/60 = 600个像素的倍数。现在,在距离精灵300个像素处点击,过程如下图: 在这里插入图片描述 在这里插入图片描述圈1,我们本应该走300像素,可是我们起步就是600像素。圈2中,再次执行到函数体时,我们的确还没有到达目标位置(因为超了300像素),但是计算出的速度与之前相反,于是我们本想反向走300像素,但是起步还是600像素,所以很不幸,我们回到了原来的位置。圈3与圈1发生一样的情况,后来的圈4到圈n则不断重复这一过程,精灵由此表现为在两个位置不断切换,当速度不那么大时(此处为了效果明显,我们用了一个较大的速度 ),两个精灵的位置重叠,便感觉精灵在不停的“抖动”。这就好像,你的步子恒为1米,但是在你想踩到你前方半米处的地面,结果是你总是在原处和你前面一米处踱步,永远也踩不到半米的那个地,原因是你的步子迈的太大了! 还是看看最终的效果: 在这里插入图片描述 7。旋转(始终朝向鼠标) + 移动(上下键控制)

有了前面几种控制运动的铺垫,这种只不过是前面两种的组合情况罢了。很多枪战类游戏中使用了把玩家与鼠标的连线方向来做为弹轨方向的控制方式。

extends KinematicBody2D export (int) var speed = 200 var velocity = Vector2() func get_input(): look_at(get_global_mouse_position()) velocity = Vector2() if Input.is_action_pressed('move_down'): velocity = Vector2(-speed, 0).rotated(rotation) if Input.is_action_pressed('move_up'): velocity = Vector2(speed, 0).rotated(rotation) func _physics_process(delta): get_input() velocity = move_and_slide(velocity) get_global_mouse_position()之前说过是为了得到鼠标的位置;look_at()(是根类Node的方法)函数很方便,可以将精灵转动适当的角度,以朝向目标点;这两个函数组合起来产生的效果就是,精灵始终朝向我们鼠标所在位置,好像它在监视我们鼠标动作的一举一动!其它的代码没有什么新东西,都在之前涉及到了,这里就不再重复了。还是看看最终的效果: 在这里插入图片描述 8。总结 我们讲了很多,很多点可以运用到自己的游戏设计中,也可以帮助我们更好的理解代码做了什么,最大化godot引擎的威力。本文参考:https://docs.godotengine.org/en/stable/tutorials/2d/2d_movement.html#估计大家也读累了(其实是我写累了),总结就自己脑补吧~如果觉的文章对你有帮助,就走波关注吧,之后继续分享,谢谢支持~

公众号:Dawo



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有