Settle the eyes down while walking
Two things made the eyes jump. The body bobbed two units per step and the head rode along with it, and each eye ran its own random blink timer, so the cat winked one eye at a time at intervals of its own choosing. The bob is now a property on the cat rather than an animation owning torso.y, the amplitude is softer, and the head translates against it by 75 percent, the way a walking cat keeps its head level. Measured on a 240 px cat, the eye's vertical travel per step went from 4.0 px to 0.8 px. Both eyes now share one eyelid, driven by a single timer and a single animation. That also removes the Behavior that was fighting the blink animation for the same property, and the mid-blink flicker to the drawn closed-eye arch, which now only appears when the eyes are really shut. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1581a760be
commit
f28dfec978
1 file changed
+54
-33
+54
-33
@@ -16,6 +16,11 @@ Item {
|
|||||||
property bool showCodes: true
|
property bool showCodes: true
|
||||||
property real sipDip: 0
|
property real sipDip: 0
|
||||||
|
|
||||||
|
// Vertical travel of the body during a step. The head cancels most of it
|
||||||
|
// again, because a walking cat keeps its head almost level and the eyes
|
||||||
|
// are what the eye follows.
|
||||||
|
property real bob: 0
|
||||||
|
|
||||||
// The cat announces what it is doing in the machine's own language.
|
// The cat announces what it is doing in the machine's own language.
|
||||||
readonly property string moodCode: mood === "running" || mood === "chasing" ? "G00"
|
readonly property string moodCode: mood === "running" || mood === "chasing" ? "G00"
|
||||||
: mood === "walking" ? "G01"
|
: mood === "walking" ? "G01"
|
||||||
@@ -98,6 +103,7 @@ Item {
|
|||||||
id: torso
|
id: torso
|
||||||
width: 100
|
width: 100
|
||||||
height: 90
|
height: 90
|
||||||
|
y: cat.bob
|
||||||
|
|
||||||
// tail
|
// tail
|
||||||
Shape {
|
Shape {
|
||||||
@@ -238,6 +244,7 @@ Item {
|
|||||||
x: 46; y: 12
|
x: 46; y: 12
|
||||||
width: 50; height: 44
|
width: 50; height: 44
|
||||||
transformOrigin: Item.Bottom
|
transformOrigin: Item.Bottom
|
||||||
|
transform: Translate { y: -cat.bob * 0.75 }
|
||||||
|
|
||||||
Behavior on x { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
|
Behavior on x { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
|
||||||
Behavior on y { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
|
Behavior on y { NumberAnimation { duration: 450; easing.type: Easing.InOutQuad } }
|
||||||
@@ -311,34 +318,31 @@ Item {
|
|||||||
Item {
|
Item {
|
||||||
id: eye
|
id: eye
|
||||||
required property int index
|
required property int index
|
||||||
property real open: 1
|
|
||||||
x: (index === 0 ? 12 : 29) + cat.look.x * 2.2
|
x: (index === 0 ? 12 : 29) + cat.look.x * 2.2
|
||||||
y: 17 + cat.look.y * 1.6
|
y: 17 + cat.look.y * 1.6
|
||||||
width: 9.5; height: 12
|
width: 9.5; height: 12
|
||||||
|
|
||||||
Behavior on open { NumberAnimation { duration: 110 } }
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: eyeBall
|
id: eyeBall
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: eye.width * (cat.mood === "alert" || cat.mood === "batting" ? 1.15 : 1)
|
width: eye.width * (cat.mood === "alert" || cat.mood === "batting" ? 1.15 : 1)
|
||||||
height: eye.height * eye.open * (cat.mood === "alert" || cat.mood === "batting" ? 1.15 : 1)
|
height: eye.height * cat.eyeOpen * (cat.mood === "alert" || cat.mood === "batting" ? 1.15 : 1)
|
||||||
radius: width / 2
|
radius: width / 2
|
||||||
color: cat.eyeColor
|
color: cat.eyeColor
|
||||||
visible: eye.open > 0.08
|
visible: !cat.eyesShut && cat.eyeOpen > 0.02
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|
||||||
Rectangle { // highlight
|
Rectangle { // highlight
|
||||||
x: parent.width * 0.5; y: parent.height * 0.14
|
x: parent.width * 0.5; y: parent.height * 0.14
|
||||||
width: 3.4; height: 3.4 * eye.open
|
width: 3.4; height: 3.4 * cat.eyeOpen
|
||||||
radius: 1.7
|
radius: 1.7
|
||||||
color: "white"
|
color: "white"
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: parent.width * 0.22; y: parent.height * 0.6
|
x: parent.width * 0.22; y: parent.height * 0.6
|
||||||
width: 1.6; height: 1.6 * eye.open
|
width: 1.6; height: 1.6 * cat.eyeOpen
|
||||||
radius: 0.8
|
radius: 0.8
|
||||||
color: "white"
|
color: "white"
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
@@ -347,7 +351,7 @@ Item {
|
|||||||
|
|
||||||
Shape { // closed eye: happy arch or sleepy curve
|
Shape { // closed eye: happy arch or sleepy curve
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: eye.open <= 0.08
|
visible: cat.eyesShut
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeColor: cat.ink
|
strokeColor: cat.ink
|
||||||
@@ -363,23 +367,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer { // blink
|
|
||||||
interval: 2600 + Math.random() * 3800
|
|
||||||
running: !cat.sleeping && !cat.happy && cat.mood !== "alert"
|
|
||||||
repeat: true
|
|
||||||
onTriggered: { interval = 2600 + Math.random() * 3800; blinkAnim.start() }
|
|
||||||
}
|
|
||||||
SequentialAnimation {
|
|
||||||
id: blinkAnim
|
|
||||||
NumberAnimation { target: eye; property: "open"; to: 0; duration: 80 }
|
|
||||||
NumberAnimation { target: eye; property: "open"; to: 1; duration: 120 }
|
|
||||||
}
|
|
||||||
|
|
||||||
states: State {
|
|
||||||
name: "closed"
|
|
||||||
when: cat.sleeping || cat.happy
|
|
||||||
PropertyChanges { eye.open: 0 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,14 +437,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// walking bob
|
|
||||||
SequentialAnimation on y {
|
|
||||||
running: cat.moving
|
|
||||||
loops: Animation.Infinite
|
|
||||||
alwaysRunToEnd: true
|
|
||||||
NumberAnimation { to: cat.running ? -4 : -2; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
|
|
||||||
NumberAnimation { to: 0; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
|
|
||||||
}
|
|
||||||
|
|
||||||
// purr wiggle while petted / loving
|
// purr wiggle while petted / loving
|
||||||
SequentialAnimation on rotation {
|
SequentialAnimation on rotation {
|
||||||
@@ -543,6 +522,48 @@ Item {
|
|||||||
|
|
||||||
onMoodCodeChanged: if (showCodes && moodCode.length > 0) codeFlash.restart()
|
onMoodCodeChanged: if (showCodes && moodCode.length > 0) codeFlash.restart()
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
running: cat.moving
|
||||||
|
loops: Animation.Infinite
|
||||||
|
alwaysRunToEnd: true
|
||||||
|
NumberAnimation { target: cat; property: "bob"; to: cat.running ? -3 : -1.6; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
|
||||||
|
NumberAnimation { target: cat; property: "bob"; to: 0; duration: cat.running ? 130 : 240; easing.type: Easing.InOutSine }
|
||||||
|
}
|
||||||
|
|
||||||
|
// One pair of eyelids for both eyes. Two independent blink timers made the
|
||||||
|
// cat wink at random, which read as the eyes jittering.
|
||||||
|
readonly property bool eyesShut: sleeping || happy
|
||||||
|
property real eyeOpen: 1
|
||||||
|
|
||||||
|
onEyesShutChanged: {
|
||||||
|
blinkSeq.stop()
|
||||||
|
lidAnim.to = eyesShut ? 0 : 1
|
||||||
|
lidAnim.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: lidAnim
|
||||||
|
target: cat
|
||||||
|
property: "eyeOpen"
|
||||||
|
duration: 140
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: blinkSeq
|
||||||
|
NumberAnimation { target: cat; property: "eyeOpen"; to: 0.06; duration: 90; easing.type: Easing.InQuad }
|
||||||
|
NumberAnimation { target: cat; property: "eyeOpen"; to: 1; duration: 140; easing.type: Easing.OutQuad }
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: blinkTimer
|
||||||
|
interval: 3600
|
||||||
|
running: !cat.eyesShut && !cat.lifted
|
||||||
|
repeat: true
|
||||||
|
onTriggered: { interval = 3200 + Math.random() * 4200; blinkSeq.start() }
|
||||||
|
Component.onCompleted: interval = 2000 + Math.random() * 4000
|
||||||
|
}
|
||||||
|
|
||||||
// Head dips into the mug, holds, comes back up.
|
// Head dips into the mug, holds, comes back up.
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
running: cat.mood === "drinking" && !cat.lifted
|
running: cat.mood === "drinking" && !cat.lifted
|
||||||
|
|||||||
Reference in new issue
Block a user