ebiten: Add VibrationOptions to specify intensity (#1891)

Updates #1452
This commit is contained in:
Hajime Hoshi 2021-12-04 22:14:02 +09:00 committed by GitHub
parent b638b72f0b
commit 39ef252c2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 28 deletions

View File

@ -32,15 +32,21 @@ const (
)
type Game struct {
touchIDs []ebiten.TouchID
gamepadIDs []ebiten.GamepadID
touchIDs []ebiten.TouchID
gamepadIDs []ebiten.GamepadID
touchCounter int
}
func (g *Game) Update() error {
g.touchIDs = g.touchIDs[:0]
g.touchIDs = inpututil.AppendJustPressedTouchIDs(g.touchIDs)
if len(g.touchIDs) > 0 {
ebiten.Vibrate(200 * time.Millisecond)
g.touchCounter++
op := &ebiten.VibrateOptions{
Duration: 200 * time.Millisecond,
Intensity: 0.5*float64(g.touchCounter%2) + 0.5,
}
ebiten.Vibrate(op)
}
g.gamepadIDs = g.gamepadIDs[:0]

View File

@ -63,7 +63,7 @@ type UI interface {
SetScreenTransparent(transparent bool)
SetInitFocused(focused bool)
Vibrate(duration time.Duration)
Vibrate(duration time.Duration, intensity float64)
Input() Input
Window() Window

View File

@ -1673,6 +1673,6 @@ func (u *UserInterface) setOrigPos(x, y int) {
u.origPosY = y
}
func (u *UserInterface) Vibrate(duration time.Duration) {
func (u *UserInterface) Vibrate(duration time.Duration, intensity float64) {
// Do nothing.
}

View File

@ -648,7 +648,9 @@ func (u *UserInterface) SetInitFocused(focused bool) {
u.initFocused = focused
}
func (u *UserInterface) Vibrate(duration time.Duration) {
func (u *UserInterface) Vibrate(duration time.Duration, intensity float64) {
// intensity is ignored.
if js.Global().Get("navigator").Get("vibrate").Truthy() {
js.Global().Get("navigator").Call("vibrate", float64(duration/time.Millisecond))
}

View File

@ -21,30 +21,41 @@ import (
)
/*
#include <jni.h>
#include <stdlib.h>
#include <stdint.h>
#include <android/log.h>
// Basically same as:
//
// Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
// v.vibrate(millisecond)
//
// TODO: As of API Level 26, the new API should be Used instead:
//
// Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
// v.vibrate(VibrationEffect.createOneShot(millisecond, VibrationEffect.DEFAULT_AMPLITUDE))
// if (Build.VERSION.SDK_INT >= 26) {
// v.vibrate(VibrationEffect.createOneShot(milliseconds, intensity * 255))
// } else {
// v.vibrate(millisecond)
// }
//
// Note that this requires a manifest setting:
//
// <uses-permission android:name="android.permission.VIBRATE"/>
//
static void vibrateOneShot(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, int64_t milliseconds) {
static void vibrateOneShot(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, int64_t milliseconds, double intensity) {
JavaVM* vm = (JavaVM*)java_vm;
JNIEnv* env = (JNIEnv*)jni_env;
jobject context = (jobject)ctx;
static int apiLevel = 0;
if (!apiLevel) {
const jclass android_os_Build_VERSION = (*env)->FindClass(env, "android/os/Build$VERSION");
apiLevel = (*env)->GetStaticIntField(
env, android_os_Build_VERSION,
(*env)->GetStaticFieldID(env, android_os_Build_VERSION, "SDK_INT", "I"));
(*env)->DeleteLocalRef(env, android_os_Build_VERSION);
}
const jclass android_content_Context = (*env)->FindClass(env, "android/content/Context");
const jclass android_os_Vibrator = (*env)->FindClass(env, "android/os/Vibrator");
@ -59,10 +70,29 @@ static void vibrateOneShot(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx,
(*env)->GetMethodID(env, android_content_Context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"),
android_context_Context_VIBRATOR_SERVICE);
(*env)->CallVoidMethod(
env, vibrator,
(*env)->GetMethodID(env, android_os_Vibrator, "vibrate", "(J)V"),
milliseconds);
if (apiLevel >= 26) {
const jclass android_os_VibrationEffect = (*env)->FindClass(env, "android/os/VibrationEffect");
const jobject vibrationEffect =
(*env)->CallStaticObjectMethod(
env, android_os_VibrationEffect,
(*env)->GetStaticMethodID(env, android_os_VibrationEffect, "createOneShot", "(JI)Landroid/os/VibrationEffect;"),
milliseconds, (int)(intensity * 255));
(*env)->CallVoidMethod(
env, vibrator,
(*env)->GetMethodID(env, android_os_Vibrator, "vibrate", "(Landroid/os/VibrationEffect;)V"),
vibrationEffect);
(*env)->DeleteLocalRef(env, android_os_VibrationEffect);
(*env)->DeleteLocalRef(env, vibrationEffect);
} else {
(*env)->CallVoidMethod(
env, vibrator,
(*env)->GetMethodID(env, android_os_Vibrator, "vibrate", "(J)V"),
milliseconds);
}
(*env)->DeleteLocalRef(env, android_content_Context);
(*env)->DeleteLocalRef(env, android_os_Vibrator);
@ -74,10 +104,10 @@ static void vibrateOneShot(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx,
*/
import "C"
func (u *UserInterface) Vibrate(duration time.Duration) {
func (u *UserInterface) Vibrate(duration time.Duration, intensity float64) {
_ = app.RunOnJVM(func(vm, env, ctx uintptr) error {
// TODO: This might be crash when this is called from init(). How can we detect this?
C.vibrateOneShot(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), C.int64_t(duration/time.Millisecond))
C.vibrateOneShot(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), C.int64_t(duration/time.Millisecond), C.double(intensity))
return nil
})
}

View File

@ -42,7 +42,7 @@ package mobile
// return nil;
// }
//
// static void vibrate(double duration) {
// static void vibrate(double duration, double intensity) {
// if (@available(iOS 13.0, *)) {
// static BOOL initializeHapticEngineCalled = NO;
// static CHHapticEngine* engine = nil;
@ -64,7 +64,7 @@ package mobile
// (id<NSCopying>)(CHHapticPatternKeyEventParameters):@[
// @{
// (id<NSCopying>)(CHHapticPatternKeyParameterID): CHHapticEventParameterIDHapticIntensity,
// (id<NSCopying>)(CHHapticPatternKeyParameterValue): @1.0,
// (id<NSCopying>)(CHHapticPatternKeyParameterValue): [NSNumber numberWithDouble:intensity],
// },
// ],
// },
@ -103,9 +103,9 @@ import (
var vibrationM sync.Mutex
func (u *UserInterface) Vibrate(duration time.Duration) {
func (u *UserInterface) Vibrate(duration time.Duration, intensity float64) {
vibrationM.Lock()
defer vibrationM.Unlock()
C.vibrate(C.double(float64(duration) / float64(time.Second)))
C.vibrate(C.double(float64(duration)/float64(time.Second)), C.double(intensity))
}

View File

@ -18,20 +18,38 @@ import (
"time"
)
// Vibrate vibrates the device.
// VibrateOptions represents the options for device vibration.
type VibrateOptions struct {
// Duration is the time duration of the effect.
Duration time.Duration
// Intensity is the strength of the device vibration.
// The value is in between 0 and 1.
Intensity float64
}
// Vibrate vibrates the device with the specified options.
//
// Vibrate works on mobiles and browsers.
//
// On browsers, Intensity in the options is ignored.
//
// On Android, this line is required in the manifest setting to use the vibration:
//
// <uses-permission android:name="android.permission.VIBRATE"/>
//
// On Android, Intensity in the options is recognized only when the API Level is 26 or newer.
// Otherwise, Intensity is ignored.
//
// On iOS, Vibrate works only when iOS version is 13.0 or newer.
// Otherwise, Vibrate does nothing.
//
// Vibrate is concurrent-safe.
func Vibrate(duration time.Duration) {
uiDriver().Vibrate(duration)
func Vibrate(options *VibrateOptions) {
uiDriver().Vibrate(options.Duration, options.Intensity)
}
// VibrateGamepadOptions represents the options to vibrate a gamepad.
// VibrateGamepadOptions represents the options for gamepad vibration.
type VibrateGamepadOptions struct {
// Duration is the time duration of the effect.
Duration time.Duration