| Home | A simple start. |
|
|
|
Introduction
This tutorial is reasonably complex; it helps if you've programmed in C++ or Java before.
23-08-2013: Updated; OnFrame() is now OnPreFrame().
The goal here is to create generic models for a car, and make them openable using keys. The example is a bit simple, in that it responds to keystrokes even when you're typing in the console. And it uses handles in a reasonably free way. Still, it might provide a good example of how to get things done in Onyx. Note that this tutorial was written with Racer v0.8.41; newer versions may still change interfaces, as it's a work in progress language.
The Car
The example car is Stereo's Simca Aronde; get it here. It contains a number of generic models; the trunk and doors are separated 3D models.
The Code
To get the code running, you need to load the Onyx script. In car.ini, set scripts.load to car.oxs and use the following script:
/*
* Car script, handling any added logic
* 08-06-2012: Created (based on Stereo's variant for the Simca Aronde in QScript)
* NOTES:
* - No constructors exist yet in Onyx; would be handy
*/
#include "racer.oxs"
// Objects
Car car; // The car we control
// Angle animation
class AngleAnimator
{
handle gm; // The generic model
Vector3 pos, // Position of model
rotMask; // Mask to define rotation (i.e. 0,1,0)
int open; // Desired state
float angle; // Current angle
void UpdateAngle(float incRate,float decRate,float minAngle,float maxAngle,float interval)
// Angle handler for a generic model.
//
// targetState: usually 0 (closed) to 1 (open), where 0 represents the smaller angle.
// incRate: angular change (per ms) in radians when opening.
// decRate: angular change (per ms) in radians when closing. note: both should be positive.
// minAngle: min value for the angle
// maxAngle: max value for the angle
// 'interval' is the delta time since the last frame
//
// Returns the updated angle
{
float targetState=open;
// Figure out where we want to go
float targetAngle=targetState*(maxAngle-minAngle)+minAngle;
// Move towards target angle
if(targetAngle>angle)
angle=angle+incRate*interval;
if(targetAngle<angle)
angle=angle-decRate*interval;
// Clamp angle within a valid range
if(angle<minAngle) angle=minAngle;
else if(angle>maxAngle)angle=maxAngle;
// display the object correctly
GenModSetPosition(gm,pos);
GenModSetRotation(gm,angle*rotMask.x,angle*rotMask.y,angle*rotMask.z);
}
};
AngleAnimator trunkAnim,leftAnim,rightAnim;
// Generic
int keyPressed;
// Frame interval
float lastT;
void OnInit(handle h)
// Called when the car is created
// 'h' is the handle to the car
{
echo("OnInit of car");
// This next line should change in the future
car.h=h;
// Trunk animation setup
trunkAnim.gm=car.GetGenericModel(8);
trunkAnim.pos.x=0;
trunkAnim.pos.y=0.35;
trunkAnim.pos.z=-1.31;
trunkAnim.rotMask.x=0; trunkAnim.rotMask.y=1; trunkAnim.rotMask.z=0;
// Doors
leftAnim.gm=car.GetGenericModel(9);
leftAnim.pos.x=0.739;
leftAnim.pos.y=-0.58;
leftAnim.pos.z=0.628;
leftAnim.rotMask.x=-1; leftAnim.rotMask.y=0; leftAnim.rotMask.z=0;
rightAnim.gm=car.GetGenericModel(10);
rightAnim.pos.x=-0.739;
rightAnim.pos.y=-0.58;
rightAnim.pos.z=0.628;
rightAnim.rotMask.x=1; rightAnim.rotMask.y=0; rightAnim.rotMask.z=0;
}
void OnPreFrame()
{
float t=GetSimTime(); // Milliseconds
t=t*0.001; // Time in seconds
// Time since last frame
float dt=t-lastT;
lastT=t;
if(IsKeyPressed(KEY_T))
{
if(keyPressed==false)
{
echo("T pressed! Toggle trunk");
keyPressed=true;
trunkAnim.open=1-trunkAnim.open;
}
} else if(IsKeyPressed(37))
{
if(keyPressed==false)
{
echo("Left pressed! Toggle door");
keyPressed=true;
leftAnim.open=1-leftAnim.open;
}
} else if(IsKeyPressed(39))
{
if(keyPressed==false)
{
keyPressed=true;
rightAnim.open=1-rightAnim.open;
}
} else
{
keyPressed=false;
}
// Animate
trunkAnim.UpdateAngle(3.0,3.0, 0,1.2, dt);
leftAnim.UpdateAngle(2.0,2.0, 0,1.1, dt);
rightAnim.UpdateAngle(2.0,2.0, 0,1.0, dt);
}
(last updated August 23, 2013 )