Ride this horse


#1

I’ve just created horse script. You can run it from:

https://transmissiongate.com/hifi/car/horse.js

If you want to host your own, this is the script:

// copyright 2018
// Fluffy Jenkins
//GeorgeDeac


var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var isActive = false;

var horseWearPosition = {x: 0, y: -0.48, z: 0.3};
var horseWearRotation = {x: 0, y: 1, z: 0, w: -4.371138828673793e-8};

var jointIndex = -1;

var horseSize = {
    z: 2.4621,
    y: 2.1832,
    x: 0.5781
};

var horseColours = ["horse@walk.fbx","horse@walkw.fbx","horse@walkb.fbx"];


var horseIconActive = Script.resolvePath("horse.png");
var horseIconInactive = Script.resolvePath("horsew.png");

var SCALE = 1.1;
var TIMEOUT = 500;

var horseEntity;

var horseName = "horse";

var horseAnimationURL = Script.resolvePath("horse@trot.FBX");
var horseAnimationFPS = 30;

var horseJumpURL = Script.resolvePath("horse@jump.FBX");
var horseJumpFPS = 30;

var horseRunURL = Script.resolvePath("horse@run.FBX");
var horseRunFPS = 30;

var horseEatURL = Script.resolvePath("horse@eat.FBX");
var horseEatFPS = 30;

var stopAnimationURL = Script.resolvePath("horse@idle.FBX");
var stopAnimationFPS = 30;

var ANIMATION_URL = Script.resolvePath("sitting.fbx");
var ANIMATION_FPS = 30, ANIMATION_FIRST_FRAME = 1, ANIMATION_LAST_FRAME = 200;

var animationPrefetch = AnimationCache.prefetch(ANIMATION_URL);


var height=178;  



function sitMe() {
    MyAvatar.restoreAnimation();

    MyAvatar.getAnimationRoles().filter(function (role) {
        return !/^(right|left)/.test(role);
    }).forEach(function (role) {
        MyAvatar.overrideRoleAnimation(role, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME);

    });
	MyAvatar.clearJointData(MyAvatar.getJointIndex("Hips"))
	MyAvatar.setJointTranslation("Hips",{x:0,y:height,z:0});
	Camera.mode = "third person";
	Camera.position = Vec3.sum(MyAvatar.position, { x: 0, y: 100, z: 50 });
    Script.setTimeout(function () {
        MyAvatar.setJointRotation(jointIndex, Quat.IDENTITY);
    }, TIMEOUT);
}

function unSitMe() {
    MyAvatar.getAnimationRoles().filter(function (role) {
        return !/^(right|left)/.test(role);
    }).forEach(function (role) {
        MyAvatar.restoreRoleAnimation(role);
    });
    MyAvatar.clearJointData(jointIndex);
	Camera.mode = "first person";
}


function findBarnacles(name) {
    return Object.keys(MyAvatar.getAvatarEntityData())
        .filter(function (id) {
            return Entities.getNestableType(id) === 'entity';
        })
        .map(function (entityID) {
            return Entities.getEntityProperties(entityID);
        })
        .filter(function (props) {
            return props.name === name;
        });
}

var toggle = function () {
    isActive = !isActive;
    activeButton.editProperties({isActive: isActive});
    if (isActive) {
        activateFunction();
    } else {
        deactivateFunction();
    }
};

function activateFunction() {
    jointIndex = MyAvatar.getJointIndex("Hips");
    sitMe();	
	
    var currenthorseColour = horseColours[Math.floor(Math.random()*horseColours.length)];
	var horseURL = Script.resolvePath(currenthorseColour);
    horseEntity = Entities.addEntity({
        visible: true,
        name: "horse",
        type: "Model",
        modelURL: horseURL,
        parentID: MyAvatar.sessionUUID,
        parentJointIndex: jointIndex,
        localPosition: horseWearPosition,
        localRotation: - horseWearRotation,
        dimensions: Vec3.multiply((SCALE * MyAvatar.scale), horseSize),
		animation:{"url":stopAnimationURL,"fps":stopAnimationFPS,"running":1,"loop":1},
        collisionless: true
    }, true);

}

var stopped = {
       url: stopAnimationURL,
       running: true,
       fps: 30,
       firstFrame: 0,
       lastFrame: 240,
       loop: true
    }
	

var yaw = 0;
var direction = "straight";	

Controller.keyPressEvent.connect(function(event) {
  if (event.text == "w") {
	  var forward = {url: horseAnimationURL,running: true, fps: 30, loop: true}
      Entities.editEntity(horseEntity,  {animation: forward})

	  
      } else if (event.text == "s") {
	  var back = {url: horseAnimationURL,running: true, fps: 30,  loop: true}
      Entities.editEntity(horseEntity,  {animation: back});  
	  
	} else if (event.key == 32) { var jump = {url: horseJumpURL,running: true, fps: 30, loop: true} 
	 Entities.editEntity(horseEntity, {animation: jump});
	  
      } else if (event.text == "v") {
	  var forward = {url: horseEatURL,running: true, fps: 30,  loop: true}
      Entities.editEntity(horseEntity,  {animation: forward});  
	  
      } else if (event.text == "SHIFT" && "w") {
	  var forward = {url: horseRunURL,running: true, fps: 30,  loop: true}
      Entities.editEntity(horseEntity,  {animation: forward});  
	  
      }	 else if (event.text == "SHIFT" && "s") {
	  var forward = {url: horseRunURL,running: true, fps: 30,  loop: true}
      Entities.editEntity(horseEntity,  {animation: back}); 
	  } else if (event.text == "[") {
	  height = height-5;
      MyAvatar.setJointTranslation("Hips",{x:0,y:height,z:0}); 
	  } else if (event.text == "]") {
	  height = height+5;
      MyAvatar.setJointTranslation("Hips",{x:0,y:height,z:0}); 
	}
});

Controller.keyReleaseEvent.connect(function(event){
if (event.text == "w") {
	Entities.editEntity(horseEntity,  {animation: stopped});
}else if (event.text == "s") {
	Entities.editEntity(horseEntity,  {animation: stopped});
}else if (event.key == 32) {
	Entities.editEntity(horseEntity,  {animation: stopped});
}else if (event.text == "v") {
	Entities.editEntity(horseEntity,  {animation: stopped});
} else if (event.text == "SHIFT" && "s") {
	Entities.editEntity(horseEntity,  {animation: stopped});
} else if (event.text == "SHIFT" && "w") {
	Entities.editEntity(horseEntity,  {animation: stopped});
}
});

function deactivateFunction() {
    unSitMe();
    Entities.deleteEntity(horseEntity);
}

var activeButton = tablet.addButton({
    icon: horseIconInactive,
    activeIcon: horseIconActive,
    text: "horse",
    isActive: false,
    sortOrder: 10
});

Script.scriptEnding.connect(function () {
    activeButton.clicked.disconnect(toggle);
    tablet.removeButton(activeButton);
    deactivateFunction();
});

activeButton.clicked.connect(toggle);

var oldhorses = findBarnacles(horseName);
oldhorses.forEach(function (ent) {
    Entities.deleteEntity(ent.id);
});

You must download this .FBX files and host them on the same path as the horse.js script
(on web or on the atp server).

https://transmissiongate.com/hifi/car/horse@walkb.fbx
https://transmissiongate.com/hifi/car/horse@walkw.fbx
https://transmissiongate.com/hifi/car/horse@walk.fbx
https://transmissiongate.com/hifi/car/horse@trot.FBX
https://transmissiongate.com/hifi/car/horse@run.FBX
https://transmissiongate.com/hifi/car/horse@jump.FBX
https://transmissiongate.com/hifi/car/horse@idle.FBX
https://transmissiongate.com/hifi/car/horse@eat.FBX

Enjoy the ride!


#2

Thank you so much for sharing!


#3

Depending on your avatar, you must adjust the y value of the hips, to have the horse standing with his fits on the floor. You must change y: values in line 66

MyAvatar.setJointTranslation("Hips",{x:0,y:260,z:0});

a bigger value, will rise up the horse.


#4

This is very educational programming style. I see you are using a lot of functional style programming. Very nice :slight_smile:
I need to ask you something to learn how to script in hf, it seems :slight_smile:


#5

I’ve updated the script, and now you can adjust the Y position of the horse by pressing " [ " and " ] " keys, to fits your avatar.


#6

New version of the script. The animations are driven by the Avatar.speed. I’ve added some sounds too.
You can adjust the Y position of the horse by pressing " [ " and " ] " keys, to fits your avatar.

// copyright 2018
// Fluffy Jenkins
// GeorgeDeac
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var isActive = false;

var trot = SoundCache.getSound("https://transmissiongate.com/hifi/car/trot.wav");
var galop = SoundCache.getSound("https://transmissiongate.com/hifi/car/galop.wav");
var eatanim = SoundCache.getSound("https://transmissiongate.com/hifi/car/eat.wav");
var scream = SoundCache.getSound("https://transmissiongate.com/hifi/car/scream.wav");

var horseWearPosition = {
    x: 0,
    y: -0.48,
    z: 0.3
};
var horseWearRotation = {
    x: 0,
    y: 1,
    z: 0,
    w: -4.371138828673793e-8
};

var jointIndex = -1;
var headIndex = -1;

var horseSize = {
    z: 2.4621,
    y: 2.1832,
    x: 0.5781
};

var horseColours = ["horse@walk.fbx", "horse@walkw.fbx", "horse@walkb.fbx"];


var horseIconActive = Script.resolvePath("horse.png");
var horseIconInactive = Script.resolvePath("horsew.png");

var SCALE = 1.1;
var TIMEOUT = 500;

var horseEntity;

var horseName = "horse";

var horseAnimationURL = Script.resolvePath("horse@trot.FBX");
var horseAnimationFPS = 30;

var horseJumpURL = Script.resolvePath("horse@jump.FBX");
var horseJumpFPS = 30;

var horseRunURL = Script.resolvePath("horse@run.FBX");
var horseRunFPS = 60;

var horseEatURL = Script.resolvePath("horse@eat.FBX");
var horseEatFPS = 30;

var stopAnimationURL = Script.resolvePath("horse@idle.FBX");
var stopAnimationFPS = 30;

var ANIMATION_URL = Script.resolvePath("sitting.fbx");
var ANIMATION_FPS = 30,
    ANIMATION_FIRST_FRAME = 1,
    ANIMATION_LAST_FRAME = 200;

var animationPrefetch = AnimationCache.prefetch(ANIMATION_URL);
var cameraPosition;

var height = 178;
var speed;
var plays = false;
var eat = false;
var playg = true;
var injector;
injector = Audio.playSound(scream, {
    position: MyAvatar.position,
    volume: 0.1,
    loop: false
});

var oldCameraMode = Camera.mode;

function update() {
    speed = Vec3.length(MyAvatar.velocity);
    if (speed < 4 && speed > 0) {
        var forward = {
            url: horseAnimationURL,
            running: true,
            fps: 30,
            loop: true
        };
        Entities.editEntity(horseEntity, {
            animation: forward
        });
        if (playg == false) {
            injector.stop();
            injector = Audio.playSound(trot, {
                position: MyAvatar.position,
                volume: 0.5,
                loop: true
            });
            playg = true;
        }
        if (plays == false) {
            injector = Audio.playSound(trot, {
                position: MyAvatar.position,
                volume: 0.5,
                loop: true
            });
            plays = true;
            eat = false;
        }
    } else if (speed >= 4) {
        var forward = {
            url: horseRunURL,
            running: true,
            fps: 30,
            loop: true
        };
        Entities.editEntity(horseEntity, {
            animation: forward
        });
        if (playg == true) {
            injector.stop();
            plays = false;
        }
        if (plays == false) {
            injector = Audio.playSound(galop, {
                position: MyAvatar.position,
                volume: 0.5,
                loop: true
            });
            plays = true;
            playg = false;
            eat = true;
        }
    } else if (speed == 0) {

        injector.stop();
        plays = false;
        playg = true;

        if (eat == true) {
            var forward = {
                url: horseEatURL,
                running: true,
                fps: 30,
                loop: true
            };
            Entities.editEntity(horseEntity, {
                animation: forward
            });


        } else {
            var forward = {
                url: stopAnimationURL,
                running: true,
                fps: 30,
                loop: true
            };
            Entities.editEntity(horseEntity, {
                animation: forward
            });
        }

    }

}

function sitMe() {
    MyAvatar.restoreAnimation();

    MyAvatar.getAnimationRoles().filter(function(role) {
        return !/^(right|left)/.test(role);
    }).forEach(function(role) {
        MyAvatar.overrideRoleAnimation(role, ANIMATION_URL, ANIMATION_FPS, true, ANIMATION_FIRST_FRAME, ANIMATION_LAST_FRAME);

    });
    MyAvatar.clearJointData(MyAvatar.getJointIndex("Hips"))
    MyAvatar.setJointTranslation("Hips", {
        x: 0,
        y: height,
        z: 0
    });



    Script.update.connect(this.update);

    Script.setTimeout(function() {
        MyAvatar.setJointRotation(jointIndex, Quat.IDENTITY);
    }, TIMEOUT);
}

function unSitMe() {
    MyAvatar.getAnimationRoles().filter(function(role) {
        return !/^(right|left)/.test(role);
    }).forEach(function(role) {
        MyAvatar.restoreRoleAnimation(role);
    });
    MyAvatar.clearJointData(jointIndex);

    Camera.mode = oldCameraMode;

    Script.update.disconnect(this.update);
}


function findBarnacles(name) {
    return Object.keys(MyAvatar.getAvatarEntityData())
        .filter(function(id) {
            return Entities.getNestableType(id) === 'entity';
        })
        .map(function(entityID) {
            return Entities.getEntityProperties(entityID);
        })
        .filter(function(props) {
            return props.name === name;
        });
}

var toggle = function() {
    isActive = !isActive;
    activeButton.editProperties({
        isActive: isActive
    });
    if (isActive) {
        activateFunction();
    } else {
        deactivateFunction();
    }
};

function activateFunction() {
    jointIndex = MyAvatar.getJointIndex("Hips");
    headIndex = MyAvatar.getJointIndex("Head");
    sitMe();

    var currenthorseColour = horseColours[Math.floor(Math.random() * horseColours.length)];
    var horseURL = Script.resolvePath(currenthorseColour);
    horseEntity = Entities.addEntity({
        visible: true,
        name: "horse",
        type: "Model",
        modelURL: horseURL,
        parentID: MyAvatar.sessionUUID,
        parentJointIndex: jointIndex,
        localPosition: horseWearPosition,
        localRotation: -horseWearRotation,
        dimensions: Vec3.multiply((SCALE * MyAvatar.scale), horseSize),
        animation: {
            "url": stopAnimationURL,
            "fps": stopAnimationFPS,
            "running": 1,
            "loop": 1
        },
        userData: { "grabbableKey": {"grabbable": false } },
        collisionless: true,
        wearable: {
            joints: {
                hips: [{
                    x: 0,
                    y: 0,
                    z: 0
                }, {
                    x: 0.4934672713279724,
                    y: 0.3605862259864807,
                    z: 0.6394805908203125,
                    w: -0.4664038419723511
                }]
            }
        }
    }, true);


    oldCameraMode = Camera.mode;

    // ONLY DO CAMERA CHANGE IN DESKTOP MODE:
    if (!HMD.active) {
        Camera.cameraEntity = Entities.addEntity({
            visible: false,
            name: "cambox",
            type: "Box",
            parentID: MyAvatar.sessionUUID,
            parentJointIndex: headIndex,
            localPosition: {
                x: 0,
                y: 0,
                z: MyAvatar.scale * 0.5
            }, //Vec3.ZERO,
            localRotation: Quat.fromPitchYawRollDegrees(0, 180, 0), // Quat.IDENTITY,
            dimensions: {
                x: 0.2,
                y: 0.2,
                z: 0.2
            },
            collisionless: true,

        }, true);
        Camera.mode = "entity";
    } else {
        Camera.mode = "third person";
    }
}

var stopped = {
    url: stopAnimationURL,
    running: true,
    fps: 30,
    firstFrame: 0,
    lastFrame: 240,
    loop: true
}


Controller.keyPressEvent.connect(function(event) {
    if (event.text == "[") {
        height = height - 5;
        MyAvatar.setJointTranslation("Hips", {
            x: 0,
            y: height,
            z: 0
        });
    } else if (event.text == "]") {
        height = height + 5;
        MyAvatar.setJointTranslation("Hips", {
            x: 0,
            y: height,
            z: 0
        });
    }
});



function deactivateFunction() {
    unSitMe();
    Entities.deleteEntity(horseEntity);
}

var activeButton = tablet.addButton({
    icon: horseIconInactive,
    activeIcon: horseIconActive,
    text: "horse",
    isActive: false,
    sortOrder: 10
});

Script.scriptEnding.connect(function() {
    activeButton.clicked.disconnect(toggle);
    tablet.removeButton(activeButton);
    deactivateFunction();
});

activeButton.clicked.connect(toggle);

var oldhorses = findBarnacles(horseName);
oldhorses.forEach(function(ent) {
    Entities.deleteEntity(ent.id);
});

#7

Very promising… I like it.


#8

Thank you Thoys an Humble Tim for assistance !!!


#9

Maybe you can add:
userData: “{ “grabbableKey”: { “grabbable”: false } }”,

in the json of horseEntity = Entities.addEntity({
because in HMD, the horse gets highlighted.

I wonder if it can possible to make it works in HMD.


#10

Unfortunately, in VR the entity camera, doesn’t work as expected, I think this bug must be fixed by developers, because it is a nice feature to have.