FSR402压力传感器Unity演示
1. 方案简介
通过FSR402压力传感器检测压力数据,利用Arduino读取IO口的电压信息,将压力数据通过串口传输给电脑,python读取串口数据后将压力数据传递给Unity,并制作出两只手指挤压小球的动画展示压力大小。
2.FSR402压力传感器电路

FSR402接线图
3. Ardunio接收并串口发送数据
#include <Arduino.h>
int fsrPin0 = A0; // A0 接口
int fsrPin1 = A1; // A0 接口
int fsrReading0;
int fsrReading1;
void setup(void)
{
Serial.begin(9600);
}
void loop(void)
{
fsrReading0 = analogRead(fsrPin0);
fsrReading1 = analogRead(fsrPin1);
Serial.print(fsrReading0);
Serial.print(",");
Serial.print(fsrReading1);
Serial.print("\n");
delay(100);
}
4. Python接收数据并通过Socket发送给Unity
import serial
import socket
ser = serial.Serial('COM3', 9600, timeout=0.2)
#socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverAddressPort = ("127.0.0.1", 5054)#这里的IP地址无关紧要,因为Unity中会搜寻全部地址,关键是端口号一致就行
# 接收返回的信息
while True:
while ser.inWaiting()>0:
data = []
revdata = ser.readline()
revdata = str(revdata)[2:len(str(revdata))-3]
A0,A1 = revdata.split(',')
data.extend([int(A0), int(A1)])
print(data)
sock.sendto(str.encode(str(data)), serverAddressPort)
5. Unity配置
这里包括Unity中socket数据的接收、小球和摄像头的布置、还有挤压小球时小球Mesh变化的代码
5.1 socket接收第四步中Python传来的数据
- Unity中新建Project
- 在场景(Scene)或者GameObject中新建空对象(Create Empty),这里我把他命名为Manger,用来存放UDP接受Python数据的Script代码。

Unity中新建Manager对象
- 将UDPReceive.cs文件拖入到Project中的Assets文件夹中
##UDPReceive.cs##
using UnityEngine;
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public class UDPReceive : MonoBehaviour
{
Thread receiveThread;
UdpClient client;
public int port = 5052;
public bool startRecieving = true;
public bool printToConsole = false;
public string data;
public void Start()
{
receiveThread = new Thread(
new ThreadStart(ReceiveData));
receiveThread.IsBackground = true;
receiveThread.Start();
}
// receive thread
private void ReceiveData()
{
client = new UdpClient(port);
while (startRecieving)
{
try
{
IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
byte[] dataByte = client.Receive(ref anyIP);
data = Encoding.UTF8.GetString(dataByte);
if (printToConsole) { print(data); }
}
catch (Exception err)
{
print(err.ToString());
}
}
}
}


Unity配置界面
5.2 新建小球和左右摄像头
在Unity场景中新建小球:GameObject -> 3DObject -> Sphere *在Unity场景中左右摄像头:GameObject -> Camera 并将左右Camera移动至小球的左右侧,小球处于摄像头中心位置。这里是为了让摄像头发出射线,从而模拟两只手指抓握时发力的场景。

Unity场景设置
5.3 挤压小球时Mesh变化的代码
参考网址实现效果如下:

小球变形效果
5.4 参照5.3中的代码加入压力数据
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeshChangeDemo : MonoBehaviour
{
public UDPReceive udpReceive;
public MeshFilter meshFilter;
public Camera LeftCamera,RightCamera;
public float changeForce = 10f;//10f
public float springForce = 20f;//20f
public float forceOffset = 0.1f;
public float damping = 5f;
public float uniformScale = 1f;
public float uniformScaleRate = 0f;
private Mesh mesh;
private Vector3[] originVertices;
private Vector3[] displayVertices;
private Vector3[] vertexVelocities;
// Start is called before the first frame update
void Start()
{
uniformScale = transform.localScale.x;
mesh = meshFilter.mesh;
originVertices = (Vector3[])mesh.vertices.Clone();
displayVertices = (Vector3[])mesh.vertices.Clone();
vertexVelocities = new Vector3[displayVertices.Length];
}
// Update is called once per frame
void Update()
{
RaycastHit hit;
string data = udpReceive.data;
data = data.Remove(0, 1);
data = data.Remove(data.Length - 1, 1);
string[] points = data.Split(',');
float A0 = (float.Parse(points[0])+0.1f)/850;
float A1 = (float.Parse(points[1])+0.1f)/850;
//print(A0);
//print(A1);
//if (A0 > 0.01)
//{
if (Physics.Raycast(RightCamera.ScreenPointToRay(new Vector3(180, 180, 0)), out hit))
{
MeshChangeDemo meshChangeDemo = hit.transform.GetComponent<MeshChangeDemo>();
if (meshChangeDemo != null)
{
meshChangeDemo.ChangeMesh(hit.point + hit.normal * forceOffset, changeForce* A0);
}
}
for (int i = 0; i < displayVertices.Length; i++)
{
Vector3 v = vertexVelocities[i];
Vector3 displacement = displayVertices[i] - originVertices[i];
v -= displacement * springForce * Time.deltaTime; //回弹形状
v *= 1f - damping * Time.deltaTime; //阻尼
vertexVelocities[i] = v;
//displayVertices[i] += v * (Time.deltaTime / uniformScale); //应用速度 除以uniformScale是为了兼容缩放了物体的情况 可理解为放大物体则形变更小,反之更大
float uniform = (uniformScale * uniformScaleRate);
if (uniform == 0)
{
uniform = 1;
}
displayVertices[i] += v * (Time.deltaTime / uniform);
}
mesh.vertices = displayVertices;
mesh.RecalculateNormals();
//}
//if (A1 > 0.01)
//{
RaycastHit hit1;
if (Physics.Raycast(LeftCamera.ScreenPointToRay(new Vector3(200, 165, 0)), out hit1))
{
MeshChangeDemo meshChangeDemo = hit1.transform.GetComponent<MeshChangeDemo>();
if (meshChangeDemo != null)
{
meshChangeDemo.ChangeMesh(hit1.point + hit1.normal * forceOffset, changeForce* A1);
}
}
for (int i = 0; i < displayVertices.Length; i++)
{
Vector3 v = vertexVelocities[i];
Vector3 displacement = displayVertices[i] - originVertices[i];
v -= displacement * springForce * Time.deltaTime; //回弹形状
v *= 1f - damping * Time.deltaTime; //阻尼
vertexVelocities[i] = v;
//displayVertices[i] += v * (Time.deltaTime / uniformScale); //应用速度 除以uniformScale是为了兼容缩放了物体的情况 可理解为放大物体则形变更小,反之更大
float uniform = (uniformScale * uniformScaleRate);
if (uniform == 0)
{
uniform = 1;
}
displayVertices[i] += v * (Time.deltaTime / uniform);
}
mesh.vertices = displayVertices;
mesh.RecalculateNormals();
//}
}
public void ChangeMesh(Vector3 point, float force)
{
point = transform.InverseTransformPoint(point);
for (int i = 0; i < displayVertices.Length; i++)
{
Vector3 pointToVertex = displayVertices[i] - point;
//注意:物体缩放了,但是这里获取到的物体网格顶点实际是没有缩放时的顶点位置,也就是会是得到的一个错误的向量 如果你的物体被缩放了。
pointToVertex *= uniformScale;//处理物体被缩放,但是模型本身顶点没缩放 导致向量与实际不符,现在乘上一个缩放系数它自身 如果是2倍则是会将向量还原到正确的倍数
float f = force / (1f + pointToVertex.sqrMagnitude); //Fv = F / (1 + d^2) 获取顶点受力公式
float v = f * Time.deltaTime; //dv = a * dt a = F/m 设m=1,则 dv = F * dt
vertexVelocities[i] += pointToVertex.normalized * v;
}
}
}
6. 效果展示

FSR402+Arduino采集数据


左:Python串口读取数据 | 右:Unity接收数据

Unity挤压小球动画展示
Enjoy Reading This Article?
Here are some more articles you might like to read next: