unity3d 三分钟实现简单的赛车漂移

本文介绍了一种利用Unity实现简单赛车漂移的方法。通过调整赛车的重心和使用虚拟摇杆控制,结合特定的动力分配策略,使得赛车在游戏中能够完成基本的漂移动作。

http://www.cnblogs.com/shenggege/p/5393815.html


提到赛车游戏,大家最关心的应该就是漂移吧?!

从学unity开始,我就一直在断断续续的研究赛车

因为自己技术太烂、悟性太差等原因,我走了不少弯路

也许你会说,网上那么多资料,你不会查啊

是啊!网上一搜一大把unity赛车例程,还有好几篇被转发了很多次的赛车漂移实现思路

 

【wheelcollider:自己技术太烂搞不定】

赛车例程几乎都是用wheelcollider来实现的

我一直以为wheelcollider的参数简直就不是人能调的

直到上个星期我才听我好朋友 阿亮 说wheelcollider的参数不是随便调的

它是按照真实赛车来实现的,所以参数也要按照真实赛车来调

比如赛车车轮的间距、赛车重量、悬挂高度......

就好比小轿车使用了大货车的悬挂参数、大货车使用了小轿车的车轮参数

好吧,我承认我自己也是听得迷迷糊糊!

反正总而言之一句话:wheelcollider的参数的确是非常难调,但是懂的人还是能一下子就调好的(好像说的是废话,我是不懂的- -)

 

【实现思路的文章:自己悟性太差搞不定】

然后是关于赛车漂移实现思路的文章

有几篇被转发了很多次的文章,被转发了那么多次,那么漂移应该就是这个实现思路吧?可是大家的实现方式都不一样

我根本不知道漂移的实现是否有一个唯一的标准,如果有,那这个标准又是什么呢?

看了这些文章,漂移我还是没有研究出来,还是一点思路都没有

 

后来我偶然得到了一份商业赛车游戏的代码,然后漂移就实现出来了- -

听我说起来好像挺容易的!其实研究那份代码的过程特别煎熬!唉,不说了,都是泪啊

 

声明:下文中我要实现出来的漂移仅仅是个简单的漂移,非常简单!!!一个思路而已,并没有用到那份商业赛车游戏中的任何一句代码

注意:请使用Unity4.X版本,5.X版本的wheelcollider参数又变了,要哭了

 

准备工作:

1、下载CarWaypoint插件,使用插件中的demo。地址:http://www.cnblogs.com/shenggege/p/4295616.html

2、easytouch插件

 

导入CarWaypoint插件和easytouch插件

打开CarWaypoints/demo/Car Physics 场景,可以看到一辆车

Hierarchy面板除了前四项其余全部隐藏,运行起来跑一下,还是挺有漂移的感觉的,这是调wheelcollider参数调出来的

我们现在需要做的是用摇杆操作赛车,用另外一个思路让赛车转弯时会漂移

 

创建一个虚拟摇杆取名为:MoveJoystick,InteractionType选择Event Notification

新建一个C#脚本:RockerController,复制以下代码

复制代码
  1 #region HeadComments
  2 /* ========================================================================
  3 * Copyright (C) 2015 ArthunGame
  4 *
  5 * 作    者:Arthun
  6 * 文件名称:RockerController
  7 * 功    能:摇杆控制器
  8 * 
  9 * =========================================================================
 10 */
 11 #endregion
 12 
 13 using UnityEngine;
 14 
 15 public class RockerController : MonoBehaviour
 16 {
 17     public static RockerController Instance;
 18 
 19     /// <summary>
 20     /// 摇杆移动回调
 21     /// </summary>
 22     /// <param name="isMove"></param>
 23     /// <param name="angle"></param>
 24     /// <param name="move"></param>
 25     public delegate void RockerMoveCallback(bool isMove, float angle, MovingJoystick move);
 26     public RockerMoveCallback rockerMoveCallback;
 27 
 28     void Awake()
 29     {
 30         if (Instance != null)
 31             Debug.LogError("Instance RockerController x2");
 32 
 33         Instance = this;
 34     }
 35 
 36     /// <summary>
 37     /// 当摇杆可用时注册事件(程序启动执行)
 38     /// </summary>
 39     void OnEnable()
 40     {
 41         EasyJoystick.On_JoystickMove += OnJoystickMove;
 42         EasyJoystick.On_JoystickMoveEnd += OnJoystickMoveEnd;
 43     }
 44 
 45     /// <summary>
 46     /// 当摇杆不可用时移除事件(程序关闭时执行) 
 47     /// </summary>
 48     void OnDisable()
 49     {
 50         EasyJoystick.On_JoystickMove -= OnJoystickMove;
 51         EasyJoystick.On_JoystickMoveEnd -= OnJoystickMoveEnd;
 52     }
 53 
 54     /// <summary>
 55     /// 当摇杆销毁时移除事件 (程序关闭时执行) 
 56     /// </summary>
 57     void OnDestroy()
 58     {
 59         EasyJoystick.On_JoystickMove -= OnJoystickMove;
 60         EasyJoystick.On_JoystickMoveEnd -= OnJoystickMoveEnd;
 61     }
 62 
 63     /// <summary>
 64     /// 移动摇杆结束
 65     /// </summary>
 66     /// <param name="move"></param>
 67     void OnJoystickMoveEnd(MovingJoystick move)
 68     {
 69         OnRockerMoveInfo(false, -1f, move);
 70     }
 71 
 72     /// <summary>
 73     /// 正在移动摇杆
 74     /// </summary>
 75     /// <param name="move"></param>
 76     void OnJoystickMove(MovingJoystick move)
 77     {
 78         if (move.joystickAxis.x != 0 || move.joystickAxis.y != 0)
 79         {
 80             OnRockerMoveInfo(true, circularAngle(move), move);
 81         }
 82     }
 83 
 84     /// <summary>
 85     /// 获取摇杆全角度 0~360
 86     /// </summary>
 87     /// <param name="move"></param>
 88     /// <returns> 左边0 上90 右180 下 270</returns>
 89     float circularAngle(MovingJoystick move)
 90     {
 91         Vector2 pos = new Vector2
 92         {
 93             x = move.joystickAxis.x * 90f + 90f,
 94             y = move.joystickAxis.y * 90f + 90f
 95         };
 96 
 97         if (pos.y < 90f)
 98         {
 99             if (pos.x < 90f)
100             {
101                 return 270f + pos.y;
102             }
103             else if (pos.x > 90f)
104             {
105                 return 180f + (90f - pos.y);
106             }
107         }
108 
109         return pos.x;
110     }
111 
112     /// <summary>
113     /// 摇杆移动信息
114     /// </summary>
115     /// <param name="isMove"></param>
116     /// <param name="angle">当前摇杆角度 取值范围0~360 -1为未操作</param>
117     void OnRockerMoveInfo(bool isMove, float angle, MovingJoystick move)
118     {
119         if (rockerMoveCallback != null)
120         {
121             rockerMoveCallback(isMove, angle, move);
122         }
123     }
124 }
复制代码

 RockerController挂到MoveJoystick上

 

新建一个脚本:FollowCamera并复制以下代码

复制代码
 1 #region HeadComments
 2 /* ========================================================================
 3 * Copyright (C) 2016 ArthunGame
 4 *
 5 * 作    者:Arthun
 6 * 文件名称:FollowCamera
 7 * 功    能:跟随摄像机
 8 * 创建时间:2016/03/11 19:38:35
 9 * 
10 * =========================================================================
11 */
12 #endregion
13 
14 using UnityEngine;
15 
16 public class FollowCamera : MonoBehaviour
17 {
18     static FollowCamera _instance;
19     public static FollowCamera GetInstance()
20     {
21         return _instance;
22     }
23 
24     public Transform Target;
25     public float Distance = 10f;
26     public float Height = 20f;
27     public float HeightDamping = 2f;
28 
29     void Awake()
30     {
31         if (_instance != null)
32             Debug.LogError("Instance FollowCamera x2");
33 
34         _instance = this;
35     }
36 
37     void LateUpdate()
38     {
39         float currentHeight = Mathf.Lerp(transform.position.y, Target.position.y + Height, HeightDamping * Time.deltaTime);
40 
41         transform.position = Target.position;
42         transform.position -= Vector3.forward * Distance;
43         transform.position = new Vector3(transform.position.x, currentHeight, transform.position.z);
44 
45         transform.LookAt(Target);
46     }
47 
48     public void SetTarget(Transform target)
49     {
50         Target = target;
51     }
52 
53     public void SetParame(float dis, float height, float heightDamp)
54     {
55         Distance = dis;
56         Height = height;
57         HeightDamping = heightDamp;
58     }
59 }
复制代码

将MainCamera上的SmoothFollowA脚本删除,替换为刚才新建的FollowCamera脚本

 

打开CarController脚本,添加几个变量

复制代码
1     public float EngineTorque = 1500f;//引擎扭矩 
2     public WheelCollider WheelBL;//后左轮
3     public WheelCollider WheelBR;//后右轮
4     public Vector3 CenterOfMass = Vector3.zero;//质量中心
5 
6     bool mRockerIsMove = false;//摇杆是否移动
7     Vector3 mTargetBodyAngle = Vector3.zero;//目标车身角度
8     float mBodyRotateSpeed = 3f;//旋转速度
复制代码

在Start中设置赛车质量中心(越低越不容易翻车)和摇杆回调

复制代码
 1     void Start()
 2     {
 3         rigidbody.centerOfMass = CenterOfMass;
 4         RockerController.Instance.rockerMoveCallback += rockerMoveCallback;
 5 
 6         //获取路标点数据
 7         _WaypointsXML.GetXmlData(WaypointsModelAll, null, waypointsData.text);
 8 
 9         Debug.Log("赛道总长度:" + CalcTotalDis().ToString());//计算赛道长度
10     }
11 
12     /// <summary>
13     /// 摇杆移动回调
14     /// </summary>
15     /// <param name="isMove"></param>
16     /// <param name="angle"></param>
17     /// <param name="move"></param>
18     void rockerMoveCallback(bool isMove, float angle, MovingJoystick move)
19     {
20         if (move.joystickName != "MoveJoystick")
21             return;
22 
23         mRockerIsMove = isMove;
24         mTargetBodyAngle.y = move.Axis2Angle(true);
25     }
复制代码

 

最后,在FixedUpdate中加入三句代码

复制代码
1         if (mRockerIsMove)
2         {
3             transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(mTargetBodyAngle), Time.fixedDeltaTime * mBodyRotateSpeed);
4         }
5 
6         WheelBL.motorTorque = WheelBR.motorTorque = -EngineTorque * (mRockerIsMove ? 0.7f : 0f) * 5000f * Time.fixedDeltaTime;
复制代码

看到这里,是不是吓了一跳,这漂移也太简单了吧!一句代码搞定漂移、一句代码搞定动力

原理是根据摇杆旋转的角度车身也相对应的旋转,加上动力有惯性,就产生圆弧形的漂移了

思路就是这么简单,也许你认为这实在是太简单得离谱了,可我却花费了一年多的时间才明白

现在,你只需要花几分钟看看这篇没什么高深技术的文章,就能知道漂移实现的思路

 

运行跑一下,有漂移效果,但是似乎不怎么明显,不够顺是不是?

解决方法很简单,在后面两个车轮处加一个向前推的力,在加一个目标方向侧推的力,就能每次都画出完美的漂移弧度

 

文章标题叫《三分钟实现简单的赛车漂移》,写的过程中才发现三分钟更本做不完

因为我自己在做的时候,很多代码都是之前写好了的,直接实现漂移部分的代码就可以了,所以感觉三分钟应该够了,就几句代码


<think>我们正在为Arduino项目构建一个使用PS2手柄摇杆控制基于TB6612FNG驱动的麦轮小车的系统。麦轮(MecanumWheel)小车可以实现全向移动,因此需要同时控制四个电机的速度和方向来实现前、后、左、右以及旋转等运动。方案概述:1.硬件组成:-控制器:ArduinoUno(或其他型号)-遥控部分:PS2手柄和接收器模块(使用SPI通信)-电机驱动:TB6612FNG模块(每个模块可驱动两个电机,因此需要两个模块驱动四个麦轮)-电机:四个直流减速电机(每个麦轮一个)-电源:锂电池组(如7.4V或11.1V,根据电机和驱动需求选择)2.控制原理:-PS2手柄的两个摇杆(通常为左侧和右侧)将提供两个二维控制信号(每个摇杆X和Y方向)。-麦轮小车的运动需要将摇杆的输入转换为四个电机的速度和方向。这通常通过运动学逆解来完成。-对于四麦轮小车,其运动学方程为:$$\begin{bmatrix}v_1\\v_2\\v_3\\v_4\end{bmatrix}=\begin{bmatrix}1&1&-1\\-1&1&1\\-1&-1&-1\\1&-1&1\end{bmatrix}\begin{bmatrix}v_x\\v_y\\\omega\end{bmatrix}$$其中,$v_1$到$v_4$是四个轮子的速度,$v_x$和$v_y$是车体在x和y方向上的速度分量,$\omega$是车体自转角速度。注意:上述矩阵的符号取决于车轮的安装方向(即车轮角度和电机的正转方向)。这里假设的是标准安装(45度)。-通常,我们将左侧摇杆的Y轴用于控制小车前后(对应$v_y$),左侧摇杆的X轴用于控制小车左右平移(对应$v_x$),右侧摇杆的X轴用于控制小车自转(对应$\omega$)。3.接线:-PS2接收器模块连接到Arduino的SPI引脚(不同Arduino型号引脚可能不同,以Uno为例:MOSI-11,MISO-12,SCK-13,SS-10)以及VCC和GND。-两个TB6612FNG模块的控制引脚连接到Arduino的数字引脚(每个TB6612FNG控制两个电机,需要两个方向控制引脚和一个PWM引脚,因此四个电机共需要4个方向控制引脚和4个PWM引脚)。-电源:锂电池正极连接到两个TB6612FNG的VMOT引脚,负极连接到GND(注意共地:锂电池的GND、TB6612FNG的GND、Arduino的GND需要连接在一起。但大电流电源(电机)和逻辑电源(Arduino)之间建议用电容隔离干扰)4.程序设计:-初始化部分:-初始化PS2接收器,与手柄配对。-设置TB6612FNG控制引脚为输出模式。-主循环:-读取PS2手柄的摇杆值(范围通常是0-255,但会被映射到-255~255之间,因为摇杆有正负方向)。-根据摇杆值计算$v_x,v_y,\omega$。通常将摇杆值归一化到[-1,1]区间。-使用运动学逆解计算四个电机的速度(归一化到[-1,1]区间,然后乘以一个最大速度值,比如255)。-将计算得到的速度值(PWM值)和方向写入TB6612FNG。5.电机控制:-TB6612FNG每个电机需要两个数字引脚控制方向(IN1,IN2)和一个PWM引脚控制速度。例如:电机1:IN1(高)、IN2(低)表示正转;IN1(低)、IN2(高)表示反转;两个都低为停止,两个都高为刹车。速度由PWM引脚控制(0-255)。-在程序中,根据计算得到的电机速度的正负来设置方向,然后输出PWM。考虑到代码的复杂性,我们提供一个简化的框架代码。注意:具体引脚号需要根据实际接线更改。代码框架:步骤1:包含必要的库(PS2手柄库,如PS2X)步骤2:定义TB6612FNG的引脚连接步骤3:定义全局变量和PS2手柄对象步骤4:初始化函数步骤5:读取PS2摇杆数据并计算电机速度步骤6:设置电机运动注意:由于PS2X库需要自行下载,确保已安装。以下是代码示例:</think> ### Arduino PS2手柄摇杆控制TB6612FNG驱动麦轮小车实现方案 #### 一、系统架构设计 ```mermaid graph LR A[PS2手柄] -->|无线信号| B(PS2接收器模块) B -->|SPI通信| C{Arduino主控板} C -->|PWM控制| D[TB6612FNG驱动模块1] C -->|PWM控制| E[TB6612FNG驱动模块2] D --> F[麦轮电机1] & G[麦轮电机2] E --> H[麦轮电机3] & I[麦轮电机4] J[锂电池组] --> D & E K[稳压模块] --> C ``` #### 二、硬件选型清单 1. **主控制器**:Arduino Uno/Nano(需至少6个PWM引脚)[^3] 2. **驱动模块**:TB6612FNG × 2(单个模块可驱动2个直流电机) 3. **电机类型**:12V直流减速电机 × 4(带编码器版本更佳) 4. **麦轮结构**:45°标准布局的麦克纳姆轮组 5. **无线模块**:PS2手柄 + PS2接收器模块 6. **电源系统**: - 驱动电源:7.4V-12V锂电(容量≥2000mAh) - 控制电源:5V稳压模块(AMS1117等)[^1] #### 三、运动学控制模型 麦轮全向运动基本方程: $$ \begin{bmatrix} v_{fl} \\ v_{fr} \\ v_{rl} \\ v_{rr} \end{bmatrix} = \begin{bmatrix} 1 & -1 & -1 \\ 1 & 1 & 1 \\ 1 & 1 & -1 \\ 1 & -1 & 1 \end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ \omega \end{bmatrix} $$ 其中: - $v_{fl}, v_{fr}, v_{rl}, v_{rr}$ = 四个轮速 - $v_x$ = X轴向速度(PS2左摇杆X轴) - $v_y$ = Y轴向速度(PS2左摇杆Y轴) - $\omega$ = 旋转速度(PS2右摇杆X轴) #### 四、核心代码实现 ```cpp #include <PS2X.h> // PS2手柄库 // PS2引脚定义 #define PS2_DAT 12 #define PS2_CMD 13 #define PS2_SEL 10 #define PS2_CLK 11 // TB6612FNG引脚定义 #define AIN1 2; #define AIN2 3; #define PWMA 5 // 左前轮 #define BIN1 4; #define BIN2 7; #define PWMB 6 // 右前轮 #define CIN1 8; #define CIN2 9; #define PWMC 10 // 左后轮 #define DIN1 11; #define DIN2 12; #define PWMD 13 // 右后轮 PS2X ps2x; // 手柄对象 void setup() { // 初始化引脚模式 pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT); // ...其他引脚类似 // PS2初始化 while(ps2x.config_gamepad(PS2_CLK, PS2_CMD, PS2_SEL, PS2_DAT) != 0) { delay(500); } } void loop() { ps2x.read_gamepad(); // 读取手柄数据 // 获取摇杆值(-255~255) int Vx = map(ps2x.Analog(PSS_LX), 0, 255, -255, 255); int Vy = map(ps2x.Analog(PSS_LY), 0, 255, -255, 255); int Omega = map(ps2x.Analog(PSS_RX), 0, 255, -255, 255); // 计算轮速 int fl = constrain(Vy + Vx + Omega, -255, 255); int fr = constrain(Vy - Vx - Omega, -255, 255); int rl = constrain(Vy - Vx + Omega, -255, 255); int rr = constrain(Vy + Vx - Omega, -255, 255); // 设置电机 setMotor(AIN1, AIN2, PWMA, fl); // 左前 setMotor(BIN1, BIN2, PWMB, fr); // 右前 setMotor(CIN1, CIN2, PWMC, rl); // 左后 setMotor(DIN1, DIN2, PWMD, rr); // 右后 delay(20); // 控制周期 } // 电机控制函数 void setMotor(int in1, int in2, int pwmPin, int speed) { bool dir = speed > 0; digitalWrite(in1, dir ? HIGH : LOW); digitalWrite(in2, dir ? LOW : HIGH); analogWrite(pwmPin, abs(speed)); } ``` #### 五、关键优化点 1. **死区处理**:添加摇杆中心死区消除微小抖动 ```cpp if(abs(Vx)<15) Vx=0; if(abs(Vy)<15) Vy=0; ``` 2. **速度平滑**:使用IIR滤波器平滑速度变化 ```cpp filtered_speed = 0.3 * current_speed + 0.7 * prev_speed; ``` 3. **刹车功能**:按键触发所有电机短路刹车 ```cpp if(ps2x.ButtonPressed(PSB_RED)) { digitalWrite(AIN1, HIGH); digitalWrite(AIN2, HIGH); // ...其他电机类似 } ``` #### 六、电源管理 1. 电机电源与控制电源**独立供电** 2. 驱动端并联100μF电解电容+0.1μF陶瓷电容 3. 每路PWM增加RC滤波电路(R=1kΩ, C=0.1μF) > **注意**:麦轮小车需要精确的电机同步,建议: > 1. 为电机增加编码器反馈 > 2. 使用PID闭环控制轮速 > 3. 定期校准电机转向一致性[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值