c# - 在 Unity 2D 中使用物理冲撞斜坡
问题描述
我正在 Unity 中开发一个 2D 项目。
角色控制器是基于物理的,所以我使用刚体来移动玩家。一切工作正常,除非我尝试对角色应用高速运动,如破折号。
这就是代码的样子。
我只是检查玩家是否在冲刺,所以我将 Vector2 运动增加了一定量。
private void DashMovement() {
if (isDashing) {
movement.x *= dashFactor;
}
}
我也在计算地面角度,所以我将运动矢量设置为跟随地面倾斜度。
private void OnSlopeMovement() {
if (isGrounded && !isJumping) {
float moveDistance = Mathf.Abs(movement.x);
float horizontalOnSlope = Mathf.Cos(groundAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign(movement.x);
float verticalOnSlope = Mathf.Sin(groundAngle * Mathf.Deg2Rad) * moveDistance;
if (horizontalOnSlope != 0)
movement.x = horizontalOnSlope;
if (isGrounded && verticalOnSlope != 0)
movement.y = verticalOnSlope;
}
SetMaxFallVelocity();
}
所以我设置了刚体速度以使其移动。
private void Move() {
movement.x *= Time.fixedDeltaTime;
if(isGrounded && !isJumping) movement.y *= Time.fixedDeltaTime;
Vector3 targetVelocity = new Vector2(movement.x, movement.y);
PlayerController.rb2d.velocity = Vector3.SmoothDamp(PlayerController.rb2d.velocity, targetVelocity, ref velocity, movementSmoothing);
}
当我应用足够高的速度时,就会出现问题。我知道这个问题是因为物理学。我认为检查地面并用于计算 groundAngle 的射线工作速度不够快,无法跟踪该运动,因此我无法将玩家固定在地面上。
我想找到一个解决方案,而不会使玩家运动,或在斜坡上停止冲刺。
编辑:
这就是我获得地面角度的方式:
private void GroundAngle() {
Vector2 rayOrigin = feetCollider.bounds.center;
rayOrigin.y += 0.1f;
Vector2 rayDirection = (Input.GetAxisRaw("Horizontal") == 0) ? Vector2.right : new Vector2(Input.GetAxisRaw("Horizontal"), 0);
int groundCollisions = Physics2D.RaycastNonAlloc(rayOrigin, Vector2.down, groundResults, Mathf.Infinity, groundMask);
if (groundCollisions > 0) {
groundAngle = Vector2.Angle(groundResults[0].normal, rayDirection) - 90f;
//Debug.DrawRay(rayOrigin, Vector2.down, Color.green);
if (groundAngle > 0 && !isDashing) {
rayOrigin.x += Input.GetAxisRaw("Horizontal") * .125f;
Physics2D.RaycastNonAlloc(rayOrigin, Vector2.down, groundResults, Mathf.Infinity, groundMask);
groundAngle = Vector2.Angle(groundResults[0].normal, rayDirection) - 90f;
//Debug.DrawRay(rayOrigin, Vector2.down, Color.blue);
}
}
}
感谢@Ruzhim 的帮助。我只是为这个问题发布了第一个“解决方案”。根据 Ruzhim 的建议,我以这种方式使用了他的代码。
private void SetPositionAfterTick() {
if (isDashMovement) {
Vector2 currentPosition = new Vector2(transform.position.x, transform.position.y);
currentPosition.y = feetCollider.bounds.min.y;
Vector2 feetPosAfterTick = currentPosition + PlayerController.rb2d.velocity * Time.deltaTime;
float maxFloorCheckDist = .1f;
RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(feetPosAfterTick + Vector2.up * maxFloorCheckDist, Vector2.down, maxFloorCheckDist * 5f);
if (groundCheckAfterTick) {
Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;
if (wantedFeetPosAfterTick != feetPosAfterTick) {
//PlayerController.rb2d.transform.position = (wantedFeetPosAfterTick + new Vector2(0f, feetCollider.bounds.min.y - PlayerController.rb2d.position.y));
PlayerController.rb2d.velocity = Vector2.zero;
}
}
}
}
这足以继续完善该机制。我仍然需要以某种方式设置位置。刚体的位置计算不起作用,因为它现在被抬起,因为条件 (wantedFeetPosAfterTick != feetPosAfterTick) 始终为真,所以角色会摔倒地板并摔倒。
正如你所看到的,我还需要控制下坡运动,因为它有时会使用斜坡运动,而其他人则向前冲刺。
解决方案
这就是asker Rubzero实现以下代码为他们工作的方式:
private void SetPositionAfterTick() { if (isDashMovement) { Vector2 currentPosition = new Vector2(transform.position.x, transform.position.y); currentPosition.y = feetCollider.bounds.min.y; Vector2 feetPosAfterTick = currentPosition + PlayerController.rb2d.velocity * Time.deltaTime; float maxFloorCheckDist = .1f; RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(feetPosAfterTick + Vector2.up * maxFloorCheckDist, Vector2.down, maxFloorCheckDist * 5f); if (groundCheckAfterTick) { Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point; if (wantedFeetPosAfterTick != feetPosAfterTick) { //PlayerController.rb2d.transform.position = (wantedFeetPosAfterTick + new Vector2(0f, feetCollider.bounds.min.y - PlayerController.rb2d.position.y)); PlayerController.rb2d.velocity = Vector2.zero; } } } }
这足以继续完善该机制。我仍然需要以某种方式设置位置。刚体的位置计算不起作用,因为它现在被抬起,因为条件
(wantedFeetPosAfterTick != feetPosAfterTick)
始终为真,所以角色会摔倒地板并摔倒。正如你所看到的,我需要控制下坡运动,因为它有时会使用斜坡运动,而其他人则向前冲刺。
我同意 AresCaelum 的观点;如果您不想在完成上/下坡时保持动量,则使用物理学进行坡度运动与您想要做的几乎相反。具体来说,您的问题在这里:
float moveDistance = Mathf.Abs(movement.x); float horizontalOnSlope = Mathf.Cos(groundAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign(movement.x); float verticalOnSlope = Mathf.Sin(groundAngle * Mathf.Deg2Rad) * moveDistance;
这是一个问题,因为玩家在一帧中水平移动的次数越多,根据他们所在坡道的斜率,他们垂直移动的次数就越多。然而,如果他们应该只在帧期间的部分移动期间沿着坡道行进,则此假设不成立。因此,您需要一种方法来处理这种情况。
一种解决方案是使用来自玩家所在位置的光线投射,然后如果它在地板上方,则更改垂直速度,以便将它们放置在该地板的位置。
首先,确定是否在物理框架中发生了斜坡运动......
private bool slopeMovementOccurred = false;
void FixedUpdate() {
slopeMovementOccurred = false;
// ...
}
private void OnSlopeMovement() {
if (isGrounded && !isJumping) {
slopeMovementOccurred = true;
// ...
}
SetMaxFallVelocity();
}
...如果有,请确定物理更新后玩家的去向。然后从该位置上方(一定量)向下(前一个量的两倍)进行physics2d raycast 以找到玩家的位置应该在哪里,然后更改rb2d.velocity
它以将玩家准确放置在他们应该在的高度。
假设您可以计算出某种Vector2 feetOffset
具有玩家脚部局部位置的位置:
void FixedUpdate() {
// ...
StickToSlopeLanding();
}
void StickToSlopeLanding() {
if (slopeMovementOccurred) {
Vector2 curVelocity = PlayerController.rb2d.velocity;
Vector2 feetPosAfterTick = PlayerController.transform.position
+ PlayerController.feetOffset
+ curVelocity * Time.deltaTime;
float maxFloorCheckDist = 1.0f;
// determine where the player should "land" after this frame
RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(
feetPosAfterTick + Vector2.up * maxFloorCheckDist,
-Vector2.up, maxFloorCheckDist * 2f);
if (groundCheckAfterTick.collider != null) {
Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;
// if basic physics won't take them to landing position
if (wantedFeetPosAfterTick != feetPosAfterTick) {
Vector2 wantedVelocity = curVelocity
+ Vector2.up
* ((wantedFeetPosAfterTick.y - feetPosAfterTick.y)
/ Time.deltaTime);
// adjust velocity so that physics will take them to landing position
PlayerController.rb2d.velocity = wantedVelocity;
// optionally, set a flag so that next frame
// it knows the player should be grounded
}
}
}
}
希望这能让您找到可行的解决方案。
注意:您可能还需要移动刚体,使其不会尝试穿过坡道顶部的拐角,并且您可以使用另一个光线投射确定将刚体放置在哪里,将来自该点的速度设置为水平的:
void StickToSlopeLanding() {
if (slopeMovementOccurred) {
Vector2 curVelocity = PlayerController.rb2d.velocity;
Vector2 feetPosAfterTick = PlayerController.transform.position
+ PlayerController.feetOffset
+ curVelocity * Time.deltaTime;
float maxFloorCheckDist = 1.0f;
// determine where the player should "land" after this frame
RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(
feetPosAfterTick + Vector2.up * maxFloorCheckDist,
-Vector2.up, maxFloorCheckDist * 2f);
if (groundCheckAfterTick.collider != null) {
Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;
// if basic physics won't take them to landing position
if (wantedFeetPosAfterTick != feetPosAfterTick) {
// look for corner of ramp+landing.
// Offsets ensure we don't raycast from inside/above it
float floorCheckOffsetHeight = 0.01f;
float floorCheckOffsetWidth = 0.5f;
RaycastHit2D rampCornerCheck = Physics2D.Raycast(
wantedFeetPosAfterTick
- floorCheckOffsetHeight * Vector2.up
- floorCheckOffsetWidth * Mathf.Sign(movement.x) * Vector2.right,
Mathf.Sign(movement.x) * Vector2.right);
if (rampCornerCheck.collider != null) {
// put feet at x=corner position
Vector2 cornerPos = Vector2(rampCornerCheck.point.x,
wantedFeetPosAfterTick.y);
PlayerController.rb2d.position = cornerPos
- PlayerController.feetOffset;
// adjust velocity so that physics will take them from corner
// to landing position
Vector2 wantedVelocity = (wantedFeetPosAfterTick - cornerPos)
/ Time.deltaTime;
PlayerController.rb2d.velocity = wantedVelocity;
// optionally, set a flag so that next frame
// it knows the player should be grounded
}
}
}
}
}
推荐阅读
- javascript - 如何用子图像覆盖固定大小的容器,保持全宽/高比?
- spring-boot - 在 Spring Boot 中无法使用 Spring-WS 使用 SOAP WS,但可以从 SOAPUI 工作
- python - 在 discord.py 中更改用户昵称 on_member_update
- terraform - 如何将模板文件函数传递给 Terraform 0.12 中 EC2 资源的 user_data 参数?
- javascript - Firestore 不会将注册电子邮件添加到新的数据库集合
- python - 使用枚举在python中打印上一行
- java - 从 Mono 调用返回一个值
- r - 崇高:从光标运行脚本直到开始脚本没有选择行
- python - 如何使用 Python 从 .csv 文件中获取行和上一行?
- python - opencv python 在表格有空隙的地方添加网格线