Version 74 (modified by fvultier, 9 years ago) (diff) |
---|
How to create Orxonox Levels
It it recommended to first have a look at some of the existing levels. In the single player menu at the tab all to the right you find a complete list of all orxonox levels.
Create the basic file
- Go to the folder ../data/levels. The level files ending with .oxw are simple XML files and can be opened with any text editor. Each level file stored in this folder can be selected later in Orxonox.
- Copy „empty_level.oxw“ and paste it again in the folder.
- Rename the copy. Try to use a name that sounds good. Avoid whitespace – use „_“ or CamelCase instead.
- Open your level with your favourite editor.
About XML
XML is a description language that looks similar to HTML. We use tags to describe objects:
<templates> <!-- Opening tag 1 --> <Template link=lodtemplate_default /> <!-- Opening and closing tag 2 at once --> </templates> <!-- Closing tag 1 --> <!-- This is a comment. -->
A <tag> can contain several attributes. An attribute is the place where a value is set.
<Light type=directional position="0,0,0" direction="0.253, 0.593, -0.765" diffuse="1.0, 0.9, 0.9, 1.0" specular="1.0, 0.9, 0.9, 1.0" /> <!-- Quite a lot of attributes. -->
The syntax is attribute=“value(s)“. If only one value is set, the quotation marks are not necessary.
Tip: XML files do not have to be compiled. If you changed a level file (and didn't forget to save it), you simply have to reload the level to view the changes. If your level doesn't load have a look at the terminal by pressing [alt] + [tab].
What it's all about
By describing a level via XML you actually add C++ objects to the level. Each tag starting with a capital letter refers to a class that contains a XMLPort function. In order to understand better what a certain tag is doing it is recommended to read the corresponding source files. Example: The <Light> tag shown before refers to the light class.
First steps
- In the first tag in your XML file, define the level's name in the menu. The description will be displayed in the level selection menu when a player hovers his mouse over your level's "name". screenshot should be a *.png image placed in the data_extern/images/levelpreviews folder.
<LevelInfo name = "Teambase Match" description = "Fight for the bases." tags = "" screenshot = "teambase.png" />
- Decide wether you create a level for a specific gametype or a single player mission. The gametype is set in the <Level> tag.
<Level gametype = "TeamBaseMatch" >
- Set the level's backgroud (skybox). The skybox is an image of what you can seen on the horizon. You can add an new skybox by changing the corresponding parameter:
<Scene ambientlight = "0.5, 0.5, 0.5" skybox = "Orxonox/Starbox" >
Possible values for skybox are:
"Orxonox/Starbox" | "Orxonox/skypanoramagen1" | "Orxonox/skypanoramagen2" | "Orxonox/skyBoxMoreNebula" |
Basics
- Worldentity: Point in space with orientation ( ~point with a vector attached to it).
- Static Entity: Worldentity with a fixed position and fixed orientation.
- Movable Entity: Worldentity that can rotate or move constantly.
- Controllable Entity: Completely freely movable point. Usually steered by a controller.
- Model: Each visible 3d-Object. A model consists of a mesh (the form) and a material (the surface colouring).
- Collisionshape: A Collisionshape is the physical representation of a model. Currently collisionshapes only have the form of a sphere, cube/ashlar, cone or plain. For StaticEntities the collisionType is „static“. Movable Entities have the collisionType "dynamic". At the moment static collisionshapes do not provide a shield against bullets. The collision of projectiles is only detected by dynamic collisionshapes. Another significant difference: If you collide with a static collisionshape, you'll be pushed away. If you collide with a dynamic collisionshape you'll push the dynamic collisionshape and it's movable entity away.
Or even more vivid - if you place two movable entities with collisionshapes too close together, such that the collisionshapes ovelap, they'll burst apart. If both objects are static they will stay as they are, although their collisionshapes overlap.
Worldentity + Model + Collisionshape = adding objects to the level
Copy the following code to your level. The code should be placed inside the Scene of the level.
<StaticEntity position="0,-10000,0" direction="0,-1,0" collisionType=static mass=100000 friction=0.01 > <attached> <Model position="0,0,0" mesh="cube.mesh" scale3D="10000,10000,1000" /> </attached> <collisionShapes> <BoxCollisionShape position="0,0,0" halfExtents="10000,10000,1000" /> </collisionShapes> </StaticEntity>
The StaticEntity defines the model's place and orientation (and some other values). The Model (a cube) is attached to the StaticEntity. With the proper sized collisionshape attached to the StaticEntity you have a "solid" cube. Without a collisionshape, the cube wouldn't be solid and your spaceship could just fly through it. This exampe is quite useful, since you usually can't see a collisionshape's size. If you combine an invisible collisionshape with a fitting model you can see where a collisionshape is for testing purposes. In this special case both the scale3D and the halfExtents parameters are identical. To make the collisionshapes visible in the game type "debugDrawPhysics true" in the ingame console.
Models
A level depends on its models. All finished models are stored in ../data_extern/models. If you want to view most models in the Gallery level. At the moment we have several asteroids, spaceships, a satellite, two different space stations and some smaller models. For test purposes you can use simple models like the cube or the sphere:
<Model mesh="cube.mesh" position="0,0,0" scale=10 /> <Model mesh="sphere.mesh" position="100,0,0" scale3d="10,20,20" />
The attribute "scale" applies a uniform scale in all directions. By using the attribute "scale3d" you can define different scaling factors for the x-, y- and z-directions.
Coordinate system
Copy the following code to your level file to make the axis of the coordinate system visible. The x-axis is coloured red, the y-axis green and the z-axis is blue.
<!-- Large coordinate axis --> <Model position="0,0,0" mesh="Coordinates.mesh" scale="20"/>
Start the level and press the F1 button on the keyboard to display the position of the spaceship. Verify that the x component of the position increases if you fly along the x-axis (red). Let's add a few models to the scene:
<!-- Large coordinate axis --> <Model position="0,0,0" mesh="Coordinates.mesh" scale="20"/> <!-- Example position and scale --> <Model position="200,0,0" mesh="cube.mesh" scale="10"/> <Model position="0,200,0" mesh="sphere.mesh" scale="3"/> <Model position="0,0,200" mesh="cylinder.mesh" scale3D="3,3,10"/> <Model position="0,100,100" mesh="rocket.mesh" scale="3"/> <Model position="100,100,0" mesh="Coordinates.mesh" scale="3"/>
Fly to the models and verify their position. Worldentities and therefore models (because the Model class inherits from the WorldEntity class) don't only have a position and a scale attribute. They also have an orientation. The orientation in three dimensional space is described by four numbers:
<!-- Large coordinate axis --> <Model position="0,0,0" mesh="Coordinates.mesh" scale="20"/> <!-- Example orientation --> <Model position="100,100,0" orientation="1.0,2.0,3.0,4.0" mesh="Coordinates.mesh" scale="3"/>
These four numbers are called a "Quaternion". Because it is not obvious how these four numbers affect the orientation of an object we created the "direction" attribute for the WorldEntity class which is much simpler to use in level design.
<!-- Large coordinate axis --> <Model position="0,0,0" mesh="Coordinates.mesh" scale="20"/> <!-- Example direction --> <!-- the -z axis of this model points in x-direction --> <Model position="100,100,0" mesh="Coordinates.mesh" scale="3" direction="1,0,0"/> <!-- the -z axis of this model points in the direction of the bisecting line of the angle between the x- and y-axis --> <Model position="100,100,100" mesh="Coordinates.mesh" scale="3" direction="1,1,0"/>
The direction attribute specifies in what direction the -z axis of the WorldEntity points. The -z axis of the model at position (100,100,0) points in the x-direction which is parallel to the large red axis. Another way to discribe the orientation of a WorldEntity is the "lookat" attribute:
<!-- Large coordinate axis --> <Model position="0,0,0" mesh="Coordinates.mesh" scale="20"/> <!-- Example lookat --> <!-- the -z axis of all theese models point towards the cube --> <Model position="50,50,50" mesh="cube.mesh" scale="3"/> <Model position="100,100,0" mesh="Coordinates.mesh" scale="3" lookat="50,50,50"/> <Model position="100,0,100" mesh="Coordinates.mesh" scale="3" lookat="50,50,50"/> <Model position="0,100,100" mesh="Coordinates.mesh" scale="3" lookat="50,50,50"/> <Model position="100,100,100" mesh="Coordinates.mesh" scale="3" lookat="50,50,50"/>
All four small "Coordinates.mesh" models look with their -z axis to the yellow cube located at (50,50,50). Because the attributes orientation, direction and lookat all represent the same information always at most one of them should be specified. Don't do this:
<!-- Too much information --> <Model position="100,100,0" orientation="1.0,2.0,3.0,4.0" direction="0,100,0" lookat="0,100,100" mesh="Coordinates.mesh" scale="3"/>
There won't be any error message but the worldentity may not behave as expected.
MovableEntity - Let's get the world moving
Worldentities can be attached to other worldentities. If you want a model to move in circles, you can create a MovableEntity that rotates and a StaticEntity attached to it. The model that should be rotating is attached to the StaticEntity.
<?lua dofile("includes/CuboidSpaceStation.lua") ?> <!-- ----------------Rotating SpaceStation--------------- --> <MovableEntity position="1,1,1" rotationrate="-4.5" rotationaxis="0,0,1"> <attached> <StaticEntity position="-2500,0,0" yaw=90 pitch=90> <attached> <?lua createSpaceStationPar(0,2,1,2,1,4,1,50) ?> </attached> </StaticEntity> </attached> </MovableEntity>
Note that in this example the Model is created by a lua script that is called in a lua tag. Lua is a scripting language that is a very powerful tool for level design. Later in this tutorial you will learn more about lua. Here is anouther example about the MovableEntity class:
<MovableEntity position="0,0,0" rotationrate="45" rotationaxis="0,0,1"> <attached> <Model position="0,0,0" mesh="cube.mesh" scale3D="2,2,20" /> <MovableEntity position="0,0,0" rotationrate="180" rotationaxis="0,1,0"> <attached> <Model position="0,0,0" mesh="sphere.mesh" scale3D="1,1,10" /> </attached> </MovableEntity> </attached> </MovableEntity>
The first MovableEntity rotates slowly around the z-axis. It has a model of a cube and another MovableEntity attached to it. This second MovableEntity rotates fast around the y-axis. Notice that the first MovableEntity rotates around the global z-axis of the Scene, but the second MovableEntity rotates around the y-axis of its local coordinate system which is relative to its parent (=the first MovableEntity) Inside the second MovableEntity there is another Model attached. Every Model is a StaticEntity. So here he have a nesting of WorldEntities with three layers. The following example shows how to use a MovableEntity to perform a linear movement.
<MovableEntity position="100,0,0" velocity="0,0,10"> <attached> <Model position="0,0,0" mesh="cube.mesh" scale3D="2,2,20" /> <MovableEntity position="0,0,0" rotationrate="180" rotationaxis="0,1,0"> <attached> <Model position="0,0,0" mesh="sphere.mesh" scale3D="1,1,10" /> </attached> </MovableEntity> </attached> </MovableEntity>
Linear movement and rotation can also be combined by setting all three parameters (velocity, rotationaxis and rotationrate). You might noticed that it is possible to fly through the models. This is because the MovablEntities have no physics. Lets change that:
<MovableEntity position="0,0,0" rotationrate="90" rotationaxis="0,0,1" collisionType="dynamic" mass=10> <attached> <Model position="0,0,0" mesh="cube.mesh" scale3D="40,4,4" /> </attached> <collisionShapes> <BoxCollisionShape position="0,0,0" halfExtents="40,4,4" /> </collisionShapes> </MovableEntity>
Never forget to change the collisionType to dynamic. Otherwise the collision shapes are useless.
In the next example the rotating MovableEntity damages your spaceship if you touch it.
<MovableEntity position="0,0,0" rotationrate="90" rotationaxis="0,0,1" collisionType="dynamic" mass=1000 collisiondamage=0.02 enablecollisiondamage=true> <attached> <Model position="0,0,0" mesh="cube.mesh" scale3D="40,4,4" /> </attached> <collisionShapes> <BoxCollisionShape position="0,0,0" halfExtents="40,4,4" /> </collisionShapes> </MovableEntity>
You always need to set both the collisiondamage and the enablecollisiondamage attribute.
Collision shapes
The most common collision shapes are the box collision shape and the sphere collision shape. However there are more types of collision shapes in Orxonox:
<StaticEntity position="0,0,0" collisionType="static"> <collisionShapes> <SphereCollisionShape position="0,0,0" radius="100" /> <CylinderCollisionShape position="0,200,0" radius="50" height="150" /> <BoxCollisionShape position="0,400,0" halfExtents="30, 50, 80" /> <ConeCollisionShape position="0,600,0" radius="50" height="150" /> <PlaneCollisionShape position="0,800,0" normal="0, -1, 0" offset="30" /> </collisionShapes> </StaticEntity>
In this example the static entity has five collision shapes. Flying into any of these will cause a collision detected by the physics engine.
Spawnpoints
A Spawnpoint is the entrance point for controllable entities (spaceships). Without a spawnpoint no level can work!
<SpawnPoint team=0 position="-200,0,0" lookat="0,0,0" spawnclass=SpaceShip pawndesign=spaceshipassff />
You can define which kind of spacecraft a player/ bots can use. Additionally the corresponding template has to be included:
pawndesign | include() | additional information |
spaceshipassff | "templates/spaceshipAssff.oxt" | default spaceship - equipped with rockets |
spaceshippirate | "templates/spaceshipPirate.oxt" | Spaceship used by pirates |
spaceshipswallow | "templates/spaceshipSwallow.oxt" | fast, nice design |
spaceshipHtwo | "templates/spaceshipH2.oxt" | |
spaceshipghost | "templates/spaceshipGhost.oxt" | stealth aircraft |
spaceshipring | "templates/spaceshipRing.oxt" | spaceship with a large ring |
spaceshipHeartAttack | "templates/spaceshipHeartAttack.oxt" | large flat spaceship with many laser weapons |
spaceshipHXY | "templates/spaceshipHXY.oxt" | |
spaceshipHXYSL | "templates/spaceshipHXYSL.oxt" | fast |
spaceshipTransporterSL | "templates/spaceshipTransporterSL.oxt" | slow transporter |
spaceshipTransporter | "templates/spaceshipTransporter.oxt" | slow transporter, equal to SL version |
You find all spaceship files in ../data/levels/templates. The first entry in a file reveals the pawndesign:
<Template name=spaceshipghost>
To use a specific spaceship in a level you first need to include the spaceship's template:
<?lua
include("templates/spaceshipPirate.oxt")
?>
Then change the "pawndesign" attribute of the spawnpoint to the name of the template (see table above).
TeamSpawnPoint
If the level is designed for several teams you have to use team spawn points. Notice the "team" attribute of the TeamSpawnPoint.
<TeamSpawnPoint team=0 position="1000,0,0" lookat="0,0,0" spawnclass=SpaceShip pawndesign=spaceshipassff/>
WaypointController
So far we used only the classes StaticEntity and MovableEntity. There is another important WorldEntity derivative you should know: The ControllableEntity. A ControllableEntity is controlled by a controller (an instance of the class Controller). This controller executes steering commands on the ControllableEntity. By using Controller and ControllableEntity you can create objects that move in the space along any path you like. The simplest case of a Controller is the WaypointController:
<SpaceShip position="300,0,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshipescort /> </templates> <controller> <WaypointController accuracy=10 team=3> <waypoints> <Model mesh="cube.mesh" scale=8 position="300,0,0" /> <Model mesh="cube.mesh" scale=8 position="300,500,0" /> <Model mesh="cube.mesh" scale=8 position="0,500,0" /> <StaticEntity position="0,0,0" /> </waypoints> </WaypointController> </controller> </SpaceShip>
In this example the SpaceShip (The SpaceShip class is a derivative of the ControllableEntity class) is controlled by a WaypointController. The ship will fly from its starting position to the first cube. There it will change its direction and fly to the second cube … When the SpaceShip arrived at the last waypoint in the list it will continue at the top of the list again. If you want the waypoints to be invisible just replace Model by StaticEntity as done for the last waypoint in the list above.
Take a look at the "waypoints.oxw" level to learn more about the WaypointController.
WaypointPatrolController
An advanced version of the WaypointController is the WaypointPatrolController. Spaceships controlled by a simple WaypointController will never shoot at you, however if you replace the WaypointController by a WaypointPatrolController they do if you get close enough to them. Try it by setting the "alertnessradius" and "attackradius" attributes properly.
Templates
Let's expand the above example a little:
<SpaceShip position="300,0,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshipescort /> </templates> <controller> <WaypointController accuracy=10 team=3> <waypoints> <StaticEntity position="300,0,0" /> <StaticEntity position="300,500,0" /> <StaticEntity position="0,500,0" /> <StaticEntity position="0,0,0" /> </waypoints> </WaypointController> </controller> </SpaceShip> <SpaceShip position="300,500,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshipassff2 /> </templates> <controller> <WaypointController accuracy=10 team=3> <waypoints> <StaticEntity position="300,0,0" /> <StaticEntity position="300,500,0" /> <StaticEntity position="0,500,0" /> <StaticEntity position="0,0,0" /> </waypoints> </WaypointController> </controller> </SpaceShip> <SpaceShip position="0,500,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshippirate /> </templates> <controller> <WaypointController accuracy=10 team=3> <waypoints> <StaticEntity position="300,0,0" /> <StaticEntity position="300,500,0" /> <StaticEntity position="0,500,0" /> <StaticEntity position="0,0,0" /> </waypoints> </WaypointController> </controller> </SpaceShip>
There are now three different spaceships all following the same route. If you want to modify a single waypoint for all spaceships you have to change it at three different place in the XML code. A better approach is the use of templates:
<Template name=waypointstemplate> <WaypointController> <waypoints> <StaticEntity position="300,0,0" /> <StaticEntity position="300,500,0" /> <StaticEntity position="0,500,0" /> <StaticEntity position="0,0,0" /> </waypoints> </WaypointController> </Template> <SpaceShip position="300,0,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshipescort /> </templates> <controller> <WaypointController accuracy=10 team=3> <templates> <Template link=waypointstemplate /> </templates> </WaypointController> </controller> </SpaceShip> <SpaceShip position="300,500,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshipassff2 /> </templates> <controller> <WaypointController accuracy=10 team=3> <templates> <Template link=waypointstemplate /> </templates> </WaypointController> </controller> </SpaceShip> <SpaceShip position="0,500,0" lookat="0,0,0" team=3> <templates> <Template link=spaceshippirate /> </templates> <controller> <WaypointController accuracy=10 team=3> <templates> <Template link=waypointstemplate /> </templates> </WaypointController> </controller> </SpaceShip>
Pawn
If an object in your level should have health and be killable you need the Pawn class. Pawns can have a weapon system, can have a shield and if you shoot at them they'll die if their health is reduced to zero.
<Pawn team=1 health=30 shieldhealth=130 initialshieldhealth=130 maxshieldhealth=150 shieldabsorption=0.95 position="0,300,0" direction="0,-1,0" collisionType=dynamic mass=100000 name=box> <attached> <Model position="0,0,0" mesh="crate.mesh" scale3D="3,3,3" /> </attached> <collisionShapes> <BoxCollisionShape position="0,0,0" halfExtents="15,15,15" /> </collisionShapes> </Pawn>
Pawn interits from ControllableEntity.
Pickups
Pickups give a player a temporary bonus when collecting it. Bonuses are invisibility, health, boost, shield, a drone, …
- Include the pickupsRepresentationTemplates. This include should be somewhere between the LevelInfo and the opening Level tag.
<?lua include("templates/pickupRepresentationTemplates.oxt") ?>
- Include the pickups. This include should be after the opening Scene tag.
<?lua include("includes/pickups.oxi") ?>
- Add a PickupSpawner. An invisible device that puts pickups in the level.
<PickupSpawner pickup=smallshieldpickup position="-25,-25,-125" triggerDistance="10" respawnTime="5" maxSpawnedItems="10" />
Pickupspawner - attributes:
- triggerDistance: Distance to collect the pickup. The larger the triggerDistance, the easier it is to get the pickup.
- respawnTime: After respawntime seconds a new pickup will appear, if the pickup had been collected.
- maxSpawnedItems: After maxSpawnedItems no further pickup will appear.
Pickups - have a look at pickups.oxw:
<ShieldPickup template=hugeshieldpickup /> | <ShieldPickup template=mediumshieldpickup /> | <ShieldPickup template=smallshieldpickup /> | |
<HealthPickup template=crazyhealthpickup /> | <HealthPickup template=hugehealthpickup /> | <HealthPickup template=mediumhealthpickup /> | <HealthPickup template=smallhealthpickup /> |
<SpeedPickup template=hugespeedpickup /> | <SpeedPickup template=mediumspeedpickup /> | <SpeedPickup template=mediumspeedpickup /> | <SpeedPickup template=smalljumppickup /> |
<InvisiblePickup template=hugeinvisiblepickup /> | <InvisiblePickup template=mediuminvisiblepickup /> | <InvisiblePickup template=smallinvisiblepickup /> | |
<MetaPickup metaType="use" /> | <MetaPickup metaType="drop" /> | <MetaPickup metaType="destroy" /> | <MetaPickup metaType="destroyCarrier" /> |
<DronePickup template=dronepickup /> | <PickupCollection template=triplehealthspeedinvisibilitypickup /> |
Billboards
Pickups are represented by billboards. Billboards are 2D images that are always facing the viewer. A 2D circle image seems to be a 3D sphere. E.g. The blinking lights on the assff wing are realized via billboards. Example of a static light sphere:
<MovableEntity position="0,0,0"> <attached> <Billboard position="100,0,0" material="Examples/Flare" colour="0, 0, 1" scale=1/> </attached> </MovableEntity>
Billboard in action:
- Mark points of interest with a light. Maybe this will attact the player.
Have a look at "theTimeMachine.oxw" to see some further billboards in action.
ForceFields
ForceFields push the player in a certain direction if the ForceField was triggered.
<ForceField position="-500,0,500" direction="0,0,-1" diameter=500 velocity=2500 length=750 />
As you maybe already have noticed - you don't have the ForceField to be attached to a Worldentity.
ForceField in action:
- Place the ForceField next to the Spawnpoint to give the player extra boost when entering the level.
Have a look at "asteroids.oxw" and "theTimeMachine.oxw", to see how to make ForceFields visible in the level.
SpaceBoundaries
If you want to restrict the position of pawns to certain areas, space boundaries may be useful. They are commonly used to avoid that a Pawn leaves the level.
<SpaceBoundaries warnDistance="1" maxDistance="200" showDistance="150" reactionMode="0" healthDecrease="0.9" position="0,0,0"/>
Have a look at "myTestLevel.oxw" and "SpaceBoundaries.h", to see how space boundaries work in detail.
Backlight
Backlights are a simple light source in a level. You can specify the colour and size of the light. The Backlight is only a Billboard. This means that it will not cause an illuminated mesh to become brighter. A Backlight is only faked light that may be used for example for blinking light at the wings of a spaceship.
<Backlight position="0,0,0" scale=1.5 colour="0.9, 0.4, 0.0" width=7 length=500 lifetime=0.3 elements=20 trailmaterial="Trail/backlighttrail" material="Examples/Flare" loop=1 />
Light
For real light use the Light class. In the follwing example the cube is only bright at the front side because there is no light behind him.
<Light type=directional position="0,0,0" direction="1, 1, 0" diffuse="1.0, 0.9, 0.9, 1.0" specular="1.0, 0.9, 0.9, 1.0"/> <Light type=point position="100,0,0" direction="1, 1, 0" diffuse="1.0, 0.9, 0.9, 1.0" specular="1.0, 0.9, 0.9, 1.0"/> <Light type=spotlight position="20,0,0" spotlightrange="10,20,30" direction="1, 1, 0" diffuse="1.0, 0.9, 0.9, 1.0" specular="1.0, 0.9, 0.9, 1.0"/> <Model mesh="Coordinates.mesh" position = "20,0,0" scale=10/> <Model mesh="cube.mesh" position = "100,100,100" scale=50/>
ParticleEmitter
Particle emitters are WorldEntities that run a particle effect. Define their position using the "position" attribute. The "source" attribute defines which particle effect is executed. All Orxonox particle effects are stored in the data/particle folder.
<ParticleEmitter scale=5 position="100, 0, 0" source="Orxonox/thruster3" lifetime=2.0 loop=1 startdelay=0.0 />
Take a look at the class ParticleEmitter to learn more about how particle effects work in Orxonox.
ParticleSpawner
A ParticleEmitter runs the defined effect forever. If you want to show the effect only once use the more advanced ParticleSpawner class. Search for the XMLPort function in the ParticleSpawner class to see what parameters you can define.
Planets
Planets are a nice addition to many levels. Use the Planet class to add a planet to a level.
<Planet position="10000,0,0" scale="100" collisionType="dynamic" linearDamping="0.8" angularDamping="0" mass="5000000" pitch="0" mesh="planets/muunilinst.mesh" atmosphere="atmosphere1" rotationaxis="1,0,0" rotationrate="1.0" atmospheresize="80.0f" imagesize="1024.0f" collisiondamage = 2 enablecollisiondamage = true > <attached> <ForceField position="0,0,0" mode="sphere" diameter="1000" velocity="-500" /> </attached> <collisionShapes> <SphereCollisionShape radius="100" position="0,0,0" /> </collisionShapes> </Planet>
Notice that in this example there is a force field attached to the planet to simulate gravitation. The planet also has a collision shape. Have a look at "planets.oxw" and "Planet.h", to see how planets work in detail.
Portals
Portals allow to teleport through space. First you need to place at least two PortalEndPoint. Give each end point a unique id using e.g. id="1" for one portal and id="2" for the other. Then you need the PortalLink class to define which PortalEndPoint is connected to which other PortalEndPoint. The attribute target of a PortalEndPoint may be used to restrict the classes that can use the PortalEndPoint. By default all Pawn instances can use it. By typeing e.g. target="MobileEntity" all MobileEntity instances can use the PortalEndPoint. The templates in the example below only have the purpose to make the end points visible. Use the design attribute of a PortalEndPoint to automatically apply the specified template to it.
<Template name=PortalDefault> <PortalEndPoint> <attached> <Model mesh="Spacegate.mesh" yaw="90"/> </attached> </PortalEndPoint> </Template> <Template name=PortalBig> <PortalEndPoint> <attached> <Billboard material="Portals/Big" /> </attached> </PortalEndPoint> </Template> <PortalEndPoint position="0,0,0" id="1" distance="40" target="Pawn" design="PortalDefault"/> <PortalEndPoint position="-100,0,0" id="2" distance="40" target="MobileEntity" design="PortalBig"/> <PortalLink fromID="1" toID="2" /> <PortalLink fromID="2" toID="1" />
Have a look at "Portals.dox", "portals.oxw", "PortalLink.h" and "PortalEndPoint.h", to see how portals work in detail.
Lua
Lua is the scripting language we use in our levels. At the beginning of the file templates for spaceships, the hud and more are loaded. What lua does in this context is editing the XML file and inserting the concerning XML (of the spaceships, hud, ..) before the level is loaded. That's why you cannot rely on the line numbers displayed when an error occurs, since before loading lua changes the file.
A quick overview over Lua: Learn Lua in 15 minutes
What lua can do:
- load external skripts: for example the cuboid spacestation.
<!-- First, the script has to be included (only once). --> <?lua dofile("includes/asteroidField.lua") ?> <!-- Usage: Creates an asteroid belt --> <?lua asteroidBelt(20000, 0, 13000, -48, -34, 70, 100, 200, 22000, 20000, 500, 1) ?> <!-- asteroidBelt(centerX, centerY, centerZ, yaw, pitch, segments, minSize, maxSize, radius0, radius1, count, fog) -->
- Create a bunch of objects (depending on the index i). Whatever is placed within those lua tags will be created several times.
Note that <?lua print(i*100 + 50) ?> directly inserts the calculated value when the level is loaded.
<!-- A for loop. The index i starts from 1, is increased up to 10 by adding +1. --> <?lua for i = 1, 10, 1 do ?> <SpawnPoint team=0 position="<?lua print(i*100 + 50) ?>,0,0" lookat="0,0,0" spawnclass=SpaceShip pawndesign=spaceshipassff /> <?lua end ?>
- Randomized values, sinus, cosinus and more. (Via sinus and cosinus circular shapes can be created easily. The randomization changes your level's appearance whenever it is reloaded.)
<!-- Positioning objects in a circle --> <?lua max = 16 for i = 0, max, 1 do y = math.sin(i/max*6)*750 z = math.cos(i/max*6)*750 ?> <StaticEntity position="<?lua print(y) ?>,0,<?lua print(z) ?>" scale="<?lua print(math.random() * 10 + 5) ?>" collisionType="static" > <?lua end ?>
Sound
There are two important classes responsible for the sound in Orxonox you can use in a level: The WorldAmbientSound class and the WorldSound class. Place the WorldAmbientSound inside your scene and it will be hearable everywhere in the level.
<WorldAmbientSound source="Earth.ogg" looping="true" playOnLoad="true" volume=0.9/>
The WorldSound class is a WorldEntity. This allows you to give the sound a position and orientation in space. You won't be able to hear a WorldSound to far away from its position. WorldSounds are typically used for engine and gun noise and explosions.
<WorldSound position="0,0,0" direction="1,0,0" mainstate="activity" source="sounds/Explosion2.ogg" looping="true" volume=0.7/> <Model mesh="Coordinates.mesh" position="0,0,0" scale=3/>
SimpleNotification
The next section about the event system will need the SimpleNatification class to display text messages. Place the following code block inside the level before the opening Scene tag
<?lua include("includes/notifications.oxi") ?> <NotificationQueueCEGUI name="narrative" targets="simpleNotification" size=3 displayTime=3.9 position="0.2, 0, 0.1, 0" fontSize="23" fontColor="0.3, 1, 0.2, 0.8" alignment="HorzCentred" displaySize="0.6, 0, 0, 0" />
Triggers && Events
Example: ../data/levels/events.oxw
So far your level design is static. Everything is placed in the level, when it is loaded (except the spawned objects) and doesn't change its behaviour once loaded. Via the trigger/event system you can make the level react on what the player does. A trigger is like a button. When you activate it an event is created. This event can change the status of objects. This way objects can be activated/deativated and be made visible/invisible.
Distance Triggers
<Backlight position="0,0,0" visible=true frequency=0.6 amplitude=3 material="Flares/lensflare" colour="1,0,0"/> <SimpleNotification message="Red Backlight." broadcast="true"> <events> <trigger> <DistanceTrigger position="0,0,0" target="ControllableEntity" distance=25 stayActive="true"/> </trigger> </events> </SimpleNotification>
DistanceTriggers are activated, when the target (e.g. a pawn, a spaceship, …) is close enough to the distance trigger (defined via "distance"). The stayActive attribute keeps the switch activated once triggered - the event can only be created once. In the next example the DistanceTrigger ist no more placed inside the SimpleNotification. Instead it ist placed outside and a EventListerner is placed inside.
<Backlight position="0,200,0" visible=true frequency=0.6 amplitude=3 material="Flares/lensflare" colour="0,0,1"/> <DistanceTrigger name="flying1" position="0,200,0" target="Pawn" distance=10 stayActive="true" delay=2/> <SimpleNotification message="Blue Backlight." broadcast="true"> <events> <trigger> <EventListener event="flying1" /> </trigger> </events> </SimpleNotification>
The name flying1 is needed in order to catch the event, when the trigger was activated. The SimpleNotification is sent to the player when the trigger is activated. Note that the trigger is activated with a delay of 2 seconds. The EventListener provides an 1:n mapping between one listener and multiple event-sources. In the next example any of the two event listeners will show the simple notification:
<Backlight position="0,400,100" visible=true frequency=0.6 amplitude=3 material="Flares/lensflare" colour="0,1,0"/> <Backlight position="0,400,-100" visible=true frequency=0.6 amplitude=3 material="Flares/lensflare" colour="0,1,0"/> <DistanceTrigger name="flying2" position="0,400,-100" distance=25 target="ControllableEntity" /> <DistanceTrigger name="flying2" position="0,400,100" distance=25 target="ControllableEntity" /> <SimpleNotification message="Green Backlight." broadcast="true"> <events> <trigger> <EventListener event="flying2" /> </trigger> </events> </SimpleNotification>
The problem of distance triggers is that you have to rely on that the player is actually activating it as intended. Try to attract the player by using bliking billboards, pickups to reward the player, … and make the radius large enough such that the player can hardly miss it. Another problem might be that the distance trigger is triggered by another Pawn/Spaceship/… (whatever is specified as target). To exclude certain objects to activate the trigger or to only allow a specific object to activate the trigger, the DistanceTriggerBeacon was created:
<!-- This trigger CANNOT be triggered --> <DistanceTrigger position="10,-202, -42" distance="800" target="Spaceship" beaconMode="exclude" targetname="stupidSpaceship" name="dockMe"/> <!-- By this Spaceship --> <SpaceShip position = "10,-202, -42" ... > <attached> <DistanceTriggerBeacon name="stupidSpaceship" /> <Model mesh="HydroHarvester.mesh" mass=10 position="0,0,0" scale=50 /> </attached> </SpaceShip>
<!-- This trigger can ONLY be triggered --> <DistanceTrigger position="10,-202, -42" distance="800" target="Spaceship" beaconMode="identify" targetname="coolSpaceship" name="dockMe"/> <!-- By this Spaceship --> <SpaceShip position = "100,300,900" ... > <attached> <DistanceTriggerBeacon name="coolSpaceship" /> <Model mesh="HydroHarvester.mesh" mass=10 position="0,0,0" scale=50 /> </attached> </SpaceShip>
Other event sources
<!-- When this Pawn is destroyed --> <Pawn health=30 position="0,300,0" direction="0,-1,0" collisionType="dynamic" mass="100000" name="kaboom" radarname = "Box 1"> <attached> <Model position="0,0,0" mesh="crate.mesh" scale3D="3,3,3" /> </attached> <collisionShapes> <BoxCollisionShape position="0,0,0" halfExtents="15,15,15" /> </collisionShapes> </Pawn> <!-- an event called "kaboom" is created --> <SimpleNotification message="Right click on the next target." broadcast="true"> <events> <trigger> <EventListener event="kaboom" /> </trigger> </events> </SimpleNotification>
Advanced Triggers
A triggers can be combined in several ways:
- A trigger can have a trigger. The outer trigger can only be triggered, after the inner trigger was triggered:
<DistanceTrigger name="navigationend" position="0,0,0" distance=950 target="SpaceShip" stayActive="false" delay=1.1> <DistanceTrigger name="flying4" position="-900,640,600" target="Pawn" distance=60 stayActive="true" delay=1/> </DistanceTrigger>
In "missionOne.oxw" the order the player destroys the boxes should not influence the order the messages arrive, what the player should do next after having destroyed such a box. The solution to this problem is naming each box as "box". When the fist box is destroyed, the innermost EventTrigger listens to the event "box" and creates the "boxtrigger1" event in return. Similarly, when the next box is destroyed later, the innermost EventTrigger is not triggered again, as it stays active. That way, the next EventTrigger is triggered that way.
<EventTrigger name="boxtrigger4" activations="1" stayactive="true" delay=0.1> <events> <trigger> <EventListener event="box" /> </trigger> </events> <EventTrigger name="boxtrigger3" activations="1" stayactive="true" delay=0.1> <events> <trigger> <EventListener event="box" /> </trigger> </events> <EventTrigger name="boxtrigger2" activations="1" stayactive="true" delay=0.1> <events> <trigger> <EventListener event="box" /> </trigger> </events> <EventTrigger name="boxtrigger1" activations="1" stayactive="true" delay=0.1> <events> <trigger> <EventListener event="box" /> </trigger> </events> </EventTrigger> </EventTrigger> </EventTrigger> </EventTrigger>
- A trigger can have several triggers that are combined with boolean logic:
<Trigger name="duball2" mode="and" stayactive="true"> <EventTrigger activations="1" stayactive="true" delay=4 > <events> <trigger> <EventListener event="duball1" /> </trigger> </events> </EventTrigger> <EventTrigger activations="1" invert="true"> <events> <trigger> <EventListener event="toHydroFarmer" /> </trigger> </events> </EventTrigger> </Trigger>
In this case the "duball2" event triggers a message but only, if the event "toHydroFarmer" was not triggered before. This is achieved by inverting (not-operation) the "toHydroFarmer" event and combining the two corresponding EventTriggers with "and".
What events can do
- Making objects (in)visible:
<DistanceTrigger name="switchBillboards" position="-900,640,600" target="Pawn" distance=70 stayActive="true" delay=1/> <!-- switchBillboards makes this billboard INVISIBLE --> <BlinkingBillboard position="-900,640,600" frequency=0.6 amplitude=3 material="Flares/lensflare" colour="1,1,0.05"> <events> <visibility> <EventTrigger invert=true> <events> <trigger> <EventListener event=switchBillboards /> </trigger> </events> </EventTrigger> </visibility> </events> </BlinkingBillboard> <!-- switchBillboards makes this billboard VISIBLE --> <Billboard position="-900,640,600" amplitude=1 material="Flares/lensflare" colour="0,0.8,0.1"> <events> <visibility> <EventTrigger> <events> <trigger> <EventListener event=switchBillboards /> </trigger> </events> </EventTrigger> </visibility> </events> </Billboard>
- (De)activating objects. In "missionOne.oxw" if the player dies after having taken the spacecruiser, the player should be respawned with the other spaceship at a sensible place. This is managed by deactivating the old TeamSpawnPoint and activating a new TeamSpawnPoint.
<TeamSpawnPoint team=0 position="2000,1500,-1500" direction="-1,-1,1" spawnclass=SpaceShip pawndesign=spaceshipspacecruiser> <events> <activity> <EventListener event="attack" /> </activity> </events> </TeamSpawnPoint>
<SpaceShip position="3000,-4000,4000" lookat="-1300,-600,900" name="attacker"visible="true"> <events> <visibility> <EventListener event="ondock" /> </visibility> </events> <templates> <Template link=spaceshippirate /> <!--spaceshipTransporter --> </templates> <controller> <WaypointPatrolController alertnessradius=3129 team=1 active=false> <waypoints> <Model mesh="cube.mesh" scale=0 position="-1300,-600,900" /> <Model mesh="cube.mesh" scale=0 position="0,0,0" /> </waypoints> <events> <activity> <EventListener event="attack" /> <!-- activates enemies--> </activity> </events> </WaypointPatrolController> </controller> </SpaceShip>
<MovableEntity scale=1.5 position="0,0,0" velocity="0,0,0" rotationaxis="0,1,0" rotationrate=90> <events> <activity> <DistanceTrigger position="0,0,0" distance=50 target="ControllableEntity" invert=true /> </activity> </events> <attached> <Model position="0,0,0" scale=1 mesh="JumpEnemy1.mesh" /> </attached> </MovableEntity>
- Play world sounds.
<WorldSound position="0,0,0" source="sounds/ReadyGo.ogg" > <events> <play> <EventListener event="playsound" /> </play> </events> </WorldSound> <DistanceTrigger name="playsound" position="0,0,0" target="Pawn" distance=50 stayActive="true" />
- Play world ambient sounds.
<WorldAmbientSound source="Asteroid_rocks.ogg" looping="true" playOnLoad="false" > <events> <play> <EventListener event="playsound" /> </play> </events> </WorldAmbientSound> <DistanceTrigger name="playsound" position="0,0,0" target="Pawn" distance=50 stayActive="true" />
- Run particle effects
<ParticleSpawner position="0,0,0" source="Orxonox/bigexplosion" lifetime=3.5 loop=0 autostart=0> <events> <spawn> <EventListener event="spawnparticle" /> </spawn> </events> </ParticleSpawner> <DistanceTrigger name="spawnparticle" position="0,0,0" target="SpaceShip" distance=50 stayActive="true" />
- Executing ConsoleCommands (= executing code):
<!-- End the Mission (success) --> <Script code="Mission endMission true" onLoad="false"> <events> <trigger> <EventListener event=endOfLevel /> </trigger> </events> </Script>
The ConsoleCommand "Mission endMission true" gets executed, which will end the mission and make the player accomplish the mission.
How to find out more console commands
Open the in-game console and press [Tab] (command completion) to find out which console commands already exist. Note that if there is no shortcut, the command completion only reveals the command word by word - you have to specify the class and press tab again to see which commmands are created in a specific class and repeat this process again to see which argument is needed for a specific ConsoleCommand.
Quests
Example: ../data/levels/quests.oxw
Best Practices
- Make objects that belong to each other be close to each other. Ideally the trigger is placed right before the object listening to the trigger's event. Use comments to mark groups that belong together.
- As a level designer, you are a story teller. Always tell your story in several ways, since a regular user will miss some information and yet should know what is going on. E.g. send a message that some traitors have turned their flags and now cooperate with the enemy. Make sure that the concerning spaceships are re-coloured in the radar & HUD. Let one of them fly to the player and attack. (Show, don't tell.)
- Reward the player when he is doing something right and correct his mistakes early, if he is doing something wrong.
- Let someone else play-test your level. Where does the player get stuck? What is too easy / too hard?
- Have you discovered a missing feature? Create a new ticket on orxonox.net.
Trouble Shooting
Housten, …
- the level does not show up in the level list. This looks like an XML error.
- the console shows that there is an XML error. First of all: do not trust the error's line number, as the lua scripts will modify the level files when they are loaded.
- Save your file and commit it. If you are lucky, your modifications since the last commit are rather small and thus you might easily spot the error when looking at the diff. (Open the Timeline on orxonox.net and open your commit.)
- Undo the last edits and save the file. If the error does not show up, commit again. Now you should be able to spot the error.
- Start uncommenting parts of the level (everything you created after the SpawnPoint) and regularly check if the error is gone. If the error does not show up, you just uncommented the error.
Content from External Sources
When designing a level, one often runs out of models and other content to use in the level. As your time is limited, try to get external content that matches our licences. (ideally CC-BY-SA or CC-BY or Public Domain)
Export the concerning .blend file with the OgreMesh Exporter.
Written by 'jo' and 'Fabien Vultier'