[RenPy] 纯文本查看 复制代码
# ---------- 安卓水平仪 ----------
init python:
"""
Android Sensor (Ver 1.0 Ren'py 8.3.6)
Author: Maz
"""
import math
import time
class AndroidSensor:
def __init__(self,max_roll=15.0,max_pitch=30.0,max_yaw=30.0,deadzone=0.05):
# ---------- 调用系统 ----------
self.sensor_get = False # 传感器系统是否可用
self.sensor_listener = None # Android传感器监听
self.sensor_manager = None # Android传感器终端
self.sensor_timeout = None # 传感器数据回调间隔 在init用默认的GAME (20ms)
self.sensor_rotate = [0,0,0,0] # 传感器旋转矢量数据(四元)
# 超时自动注销
self.sensor_init = False # 是否已收到首帧传感器数据(收到时校准基准)
self.sensor_update = False # 活跃锁
self.sensor_update_time = 0.0 # 活跃时间戳
self.sensor_update_timeout = 5.0 # 活跃间隔
# ---------- 感应角度 ----------
# 映射三轴量程,对应半屏,越小越灵敏
self.max_roll = max_roll # 横滚角 左右倾斜 人类习惯短边倾角变化不大
self.max_pitch = max_pitch # 俯仰角 前后翻动 长边变化大,放宽防饱和
self.max_yaw = max_yaw # 偏航角 左右旋转 旋转幅度大,约等同于长边
self.deadzone = deadzone # 死区 三轴量程不同,归一化比例保证手感一致
# ---------- 即时角度 ----------
# 映射三轴距离
self.roll = 0.0 # x
self.pitch = 0.0 # y
self.yaw = 0.0 # z
# ---------- 基准角度 ----------
# 映射三轴原点
self.base_roll = 0
self.base_pitch = 0
self.base_yaw = 0
# ---------- 稳定角度 ----------
# 动态基准更新
self.stable_roll = 0.0
self.stable_pitch = 0.0
self.stable_yaw = 0.0
# 缓动对齐
self.stable_update = False # 更新锁
self.stable_update_timeout = 1.0 # 更新间隔
# 稳定检测
self.stable_timer = 0.0 # 确认计时(秒)
self.stable_timeout = 5.0 # 确认间隔(秒)角度增量持续在阈值内这么久才锁定基准角
self.stable_threshold = 5.0 # 判定阈值(度)相邻采样角度变化总量小于阈值才视为稳定
self.stable_sample = (0.0,0.0,0.0) # 上次采样角度
self.stable_target = (0.0,0.0,0.0) # 锁定目标角度
# 采样节流
self.stable_step_time = 0.0 # 节流时间戳
self.stable_step_timeout = 1.0 # 节流间隔
self.init_sensors()
# 准备传感器
def init_sensors(self):
try:
# Android相关
import jnius
PythonActivity = jnius.autoclass('org.renpy.android.PythonSDLActivity')
Context = jnius.autoclass('android.content.Context')
Sensor = jnius.autoclass('android.hardware.Sensor')
SensorManager = jnius.autoclass('android.hardware.SensorManager')
activity = PythonActivity.mActivity
self.sensor_manager = activity.getSystemService(Context.SENSOR_SERVICE)
self.sensor_timeout = SensorManager.SENSOR_DELAY_GAME
# 旋转矢量传感器(系统级融合,含陀螺仪补偿,响应快、跟手)
self.rotation_vector = self.sensor_manager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
if self.rotation_vector is None:
self.rotation_vector = self.sensor_manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
if self.rotation_vector is None:
print("设备缺少旋转矢量传感器")
return
# Android持续回调
class SensorListener(jnius.PythonJavaClass):
__javainterfaces__ = ['android/hardware.SensorEventListener']
def __init__(self,manager,**kwargs):
super(SensorListener,self).__init__(**kwargs)
self.manager = manager
@jnius.java_method('(Landroid/hardware/SensorEvent;)V')
def onSensorChanged(self,event):
# 长时间没有组件调用 update(活跃时间戳未刷新):自行注销监听释放资源
if time.time() - self.manager.sensor_update_time > self.manager.sensor_update_timeout:
self.manager._unregister()
return
# 保存旋转矢量(四元数 x,y,z,w)
v = event.values
self.manager.sensor_rotate = [v[i] for i in range(min(4,len(v)))]
# 计算倾角
self.manager._check_direction()
# 首次收到数据:以当前真实姿态校准基准角与稳定角(原点起步)
if not self.manager.sensor_init:
self.manager.sensor_init = True
self.manager.base_roll = self.manager.roll
self.manager.base_pitch = self.manager.pitch
self.manager.base_yaw = self.manager.yaw
self.manager.stable_roll = self.manager.roll
self.manager.stable_pitch = self.manager.pitch
self.manager.stable_yaw = self.manager.yaw
# 更新稳定角检测
self.manager._check_stable()
@jnius.java_method('(Landroid/hardware/Sensor;I)V')
def onAccuracyChanged(self,sensor,accuracy):
pass
self.sensor_listener = SensorListener(self)
self.sensor_get = True
print("倾角传感器对象准备完成(按需注册监听)")
except Exception as e:
print(f"无法初始化倾角传感器: {e}")
self.sensor_get = False
# 注销传感器
def stop_sensors(self):
self._unregister()
# 申请传感器监听
def _register(self):
if self.sensor_update or not self.sensor_get:
return
try:
self.sensor_manager.registerListener(
self.sensor_listener,
self.rotation_vector,
self.sensor_timeout
)
self.sensor_update = True
# 注册是异步的,回调首帧才拿到真实姿态:先清校准标志,收到首帧数据时再同步基准角与稳定角(原点起步)
self.sensor_init = False
except Exception as e:
print(f"注册传感器监听失败: {e}")
# 注销传感器监听
def _unregister(self):
if not self.sensor_update:
return
try:
self.sensor_manager.unregisterListener(self.sensor_listener)
except:
pass
self.sensor_update = False
# 根据旋转矢量计算设备方向
def _check_direction(self):
try:
import jnius
SensorManager = jnius.autoclass('android.hardware.SensorManager')
# 旋转矢量 → 旋转矩阵 → 欧拉角
R = [0] * 9
I = [0] * 9
SensorManager.getRotationMatrixFromVector(R,self.sensor_rotate)
orientation = [0] * 3
SensorManager.getOrientation(R,orientation)
# 弧度转角度 映射到 x/y/z
self.yaw = math.degrees(orientation[0]) # 偏航角(左右旋转)
self.pitch = math.degrees(orientation[1]) # 俯仰角(前后翻动)
self.roll = math.degrees(orientation[2]) # 横滚角(左右倾斜)
except Exception as e:
print(f"计算方向时出错: {e}")
# 死区过滤:角度差小于该轴死区时不响应
def _check_deadzone(self,angle_diff,zone):
if abs(angle_diff) < zone:
return 0
return angle_diff
# 稳定追踪:稳定确认后锁定目标角度,再缓动对齐
def _check_stable(self):
now = time.time()
# 更新节流,避免高频回调拖累性能
if now - self.stable_step_time < self.stable_step_timeout:
return
self.stable_step_time = now
# 相邻采样三轴角度变化总量(度)
cur = (self.roll,self.pitch,self.yaw)
delta = abs(cur[0] - self.stable_sample[0]) + abs(cur[1] - self.stable_sample[1]) + abs(cur[2] - self.stable_sample[2])
self.stable_sample = cur
if self.stable_update:
# 对齐锁定,稳定角度用缓动间隔向目标角度逼近
k = 1.0 - math.exp(-self.stable_step_timeout / self.stable_update_timeout)
# 三轴分别平滑对齐(带 ±180 环绕处理)
diff = self.stable_target[0] - self.stable_roll
if diff > 180:
diff -= 360
elif diff < -180:
diff += 360
self.stable_roll += diff * k
diff = self.stable_target[1] - self.stable_pitch
if diff > 180:
diff -= 360
elif diff < -180:
diff += 360
self.stable_pitch += diff * k
diff = self.stable_target[2] - self.stable_yaw
if diff > 180:
diff -= 360
elif diff < -180:
diff += 360
self.stable_yaw += diff * k
# 收敛到目标角度附近结束锁定
if (abs(self.stable_roll - self.stable_target[0]) < self.stable_threshold and
abs(self.stable_pitch - self.stable_target[1]) < self.stable_threshold and
abs(self.stable_yaw - self.stable_target[2]) < self.stable_threshold):
self.stable_update = False
else:
# 角度波动低于阈值 设备趋于稳定累加稳定确认计时
if delta < self.stable_threshold:
self.stable_timer += self.stable_step_timeout
# 大于稳定确认定时 锁定当前角为目标基准角,开始缓动对齐基准
if self.stable_timer >= self.stable_timeout:
self.stable_target = cur
self.stable_timer = 0.0
self.stable_update = True
else:
# 仍在晃动 清零计时
self.stable_timer = 0.0
# 返回偏移量:归一化后按屏幕尺寸还原
def update(self):
# 记录使用时间戳(本帧有组件调用);若监听已注销则按需重新申请
self.sensor_update_time = time.time()
if not self.sensor_update:
self._register()
if not self.sensor_get:
return 0.0,0.0,0.0
# 尚未收到首帧传感器数据:直接返回零偏移,避免用 0 基准计算造成初始跳变
if not self.sensor_init:
return 0.0,0.0,0.0
# 基准长时间跟随稳定角更新(有新的稳定角就同步基准);新组件首帧也会触发 update_base
self.update_base()
# 相对基准的角度差(三轴:roll→x,pitch→y,yaw→z)
roll_diff = self.roll - self.base_roll
pitch_diff = self.pitch - self.base_pitch
yaw_diff = self.yaw - self.base_yaw
# 处理 roll/yaw 的360度跳变问题(pitch 范围 ±90 无需处理)
if roll_diff > 180:
roll_diff -= 360
elif roll_diff < -180:
roll_diff += 360
if yaw_diff > 180:
yaw_diff -= 360
elif yaw_diff < -180:
yaw_diff += 360
# 死区/阈值过滤:各轴死区 = 量程 × 比例(按轴归一化)
roll_diff = self._check_deadzone(roll_diff, self.max_roll * self.deadzone)
pitch_diff = self._check_deadzone(pitch_diff, self.max_pitch * self.deadzone)
yaw_diff = self._check_deadzone(yaw_diff, self.max_yaw * self.deadzone)
# 归一化:各轴独立量程(度),量程越小该轴越灵敏;偏差达量程即满屏半偏移
norm_roll = max(-1.0,min(1.0,roll_diff / self.max_roll))
norm_pitch = max(-1.0,min(1.0,pitch_diff / self.max_pitch))
norm_yaw = max(-1.0,min(1.0,yaw_diff / self.max_yaw))
# 通过屏幕尺寸还原成像素偏移量:以 roll→X 为基准(用宽),pitch→Y 与旋转 yaw→Z 用高同比
# 满量程(max_roll/max_pitch/max_yaw 度)即对应半屏位移
tilt_x = norm_roll * config.screen_width * 0.5
tilt_y = norm_pitch * config.screen_height * 0.5
tilt_z = norm_yaw * config.screen_height * 0.5
return tilt_x,tilt_y,tilt_z
# 更新基准角:持续跟随稳定角
def update_base(self):
if not self.sensor_get:
return
if self.stable_update:
self.base_roll = self.stable_roll
self.base_pitch = self.stable_pitch
self.base_yaw = self.stable_yaw
# 全局单例,同时作为是否能够调用的判断
ANDROID_SENSOR = None
if renpy.variant("android"):
# jnius 仅 Android 可用,iOS 无传感器支持则保持 None
ANDROID_SENSOR = AndroidSensor()
# 传感器可用才注册退出注销回调;初始化失败保持 None(从未注册,无需注销)
if ANDROID_SENSOR.sensor_get:
config.quit_callbacks.append(ANDROID_SENSOR.stop_sensors)
else:
ANDROID_SENSOR = None
# 使用方法:
#
# 调用 ANDROID_SENSOR.update() 会返回水平仪映射在XYZ三轴上的三个量