Strict Standards: Declaration of action_plugin_importoldchangelog::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /nfsmnt/hosting1_2/8/2/82f54cb0-e474-4da4-9cdc-40dcd737e16b/ on line 8

Strict Standards: Declaration of action_plugin_safefnrecode::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /nfsmnt/hosting1_2/8/2/82f54cb0-e474-4da4-9cdc-40dcd737e16b/ on line 0

Strict Standards: Declaration of action_plugin_popularity::register() should be compatible with DokuWiki_Action_Plugin::register($controller) in /nfsmnt/hosting1_2/8/2/82f54cb0-e474-4da4-9cdc-40dcd737e16b/ on line 0

Strict Standards: Declaration of Doku_Renderer_metadata::table_open() should be compatible with Doku_Renderer::table_open($maxcols = NULL, $numrows = NULL, $pos = NULL) in /nfsmnt/hosting1_2/8/2/82f54cb0-e474-4da4-9cdc-40dcd737e16b/ on line 24

Strict Standards: Declaration of Doku_Renderer_metadata::table_close() should be compatible with Doku_Renderer::table_close($pos = NULL) in /nfsmnt/hosting1_2/8/2/82f54cb0-e474-4da4-9cdc-40dcd737e16b/ on line 24
tery:tutorial_new_creature_firemine [TeryWiki]

How to add new creature to game

Step 1

First, lets decide what will be new creature capable of, how will it look like, how will it move and if it will attack or fire somehow.

I have decided to create a 'fire mine' which will be a small ball moving around flying. If it detects enemy somewhere near it will first stop, signalize a warning, and then after some time it will start to shoot lasers in all directions.

Step 2 - Graphics

Creating graphics for the creature.

I use Gimp at the moment, so I'll start up the Gimp and sketch up small electronic ball, metal and techno style. It is a low-resolution image, about 32×32 pixels. I sketch it in RGB mode, and I'll convert it to game palette later.

Firemine sketch

There. Took me around 15 minutes to create this. You can probably see that I'm not handy with painting ;-) .

Now lets create some modified copies of this sprite in the same image - to simulate the movement, attacking phase and such. Movement will be easy because it will only hover in the air. I'll enable grid in Gimp to make it easier for me. I'll also switch image mode from RGB to the paletized 8bit mode, so it matches the engine requirements. I'll have to select Tery color palette file.

With anim frames

This is how does the movement animation look like.

And now I will create warning phase, very simple - mine will emitt white rings around it. I have changed image background color to purple, to distinguish a transparent color from white color. It will emitt red ring around when it fires.

All of the FireMine frames

Now as a next step we have to convert the images into SPR animation file format which Tery engine can handle.

Step 3 - Conversion

Converting graphics into game format.

Lets use Biturn tool to convert the sprites to SPR format. First, don't forget to save source image fire_mine.bmp into BMP format with no RLE, and transform it to paletized 8bit RGB. Then load up the Tery palette, it should convert image automatically to palette mode (for more information see SPR page and graphics page ).

Now, I'll specify frames which get converted to single sprites or animated sprites. I have to specify animation frames, center of the animation images and delay on single frames. For this I create a SPC file, which is just a text file describing animation.

I have created four files one for each animation, like this:


SetSourceImage fire_mine.bmp

# movement up and down, 8 frames
FrameCutFromImage f1 0 0 32 32 :16 :32 150
FrameCutFromImage f2 +32 0 32 32 :16 :32 150
FrameCutFromImage f3 +32 0 32 32 :16 :32 150
FrameCutFromImage f4 +32 0 32 32 :16 :32 150
FrameCutFromImage f5 +32 0 32 32 :16 :32 150
FrameCutFromImage f6 +32 0 32 32 :16 :32 150
FrameCutFromImage f7 +32 0 32 32 :16 :32 150
FrameCutFromImage f8 +32 0 32 32 :16 :32 150



SetSourceImage fire_mine.bmp

# no movement, only white ring around
FrameCutFromImage f1 0 32 32 32 :16 :32 150
FrameCutFromImage f2 +32 +0 32 32 :16 :32 50
FrameCutFromImage f3 +32 +0 32 32 :16 :32 50
FrameCutFromImage f4 +32 +0 32 32 :16 :32 400



SetSourceImage fire_mine.bmp

# movement up and down, 8 frames
FrameCutFromImage f1 128 32 32 32 :16 :32 150



SetSourceImage fire_mine.bmp

# no movement, only white ring around
FrameCutFromImage f1 0 32 32 32 :16 :32 150
FrameCutFromImage f2 +32 +0 32 32 :16 :32 50
FrameCutFromImage f3 +32 +0 32 32 :16 :32 50
FrameCutFromImage f4 +32 +0 32 32 :16 :32 400


In each of these files you can see that first line contains '#Tery_SPC_V2' as a identification, so Biturn can load it. Next line identifies source image containing the animation frames, and then it is followed by frame information. For more information about syntax see SPC.

Now, we can view 'fmine_w.spc' and 'fmine_i.spc' files in Biturn, to see how animations work out. Start up Biturn and click on the fmine_w.spc to see the preview. Try to replay it with the controls. I had to play with it, edit the fmine_w.spc file to adjust the frame delays and order of frames. Now I convert those animations to 'spr version 3' format, so Tery can load them. ( Do not use spr version 2, its outdated. You can use version 4 though, its good too ).

FireMine in Biturn

I have repeated these steps on other animations fmine_a.spc, fmine_i.spc and fmine1.spc. Now as a result I have four sprite files - fmine1.spr, fmine_a.spr, fmine_i.spr and fmine_w.spr.

Step 4 - Placing data

Now I have to place new creature data into the game data directory, so the game can use it. I have 4 sprite animations so I'll place these into the creature sprite directory ”/data/gfx/creature”. I have created a subdirectory “firemine” and copied all four sprites there, into ”/data/gfx/creature/firemine”. If I would want to add new creature sounds I would place them into ”/data/sfx/creature/” directory, but I don't have those yet.

Step 5 - Creating creature type

Adding new creature type to game engine

Creating creature type is a little longer process as I want to create creature with new style of behavior. So now I open the Tery c++ project, open the crtype.cpp file which specifies creature types and their creation. I find a function CCreatureTypeList::LoadCreatures() and at the end of the function add a new creature specification. Specification defines creature name, size and properties, its sprites, sounds and all other thingies.

	// fire mine start
	// setup basic important properties as name, creature group name ("t1" will do fine now), bounding box sizes
	cli.SetName( "Fire Mine" );
	cli.SetGroup( "t1" );
	cli.sizex = 32; cli.sizey = 32;

	// setup physical properties. Will it obey gravity laws, or not ? Is it creature with jumping ability ?
	// if it moves around, what speed does it move around ?
	cli.no_gravity = 1;
	cli.can_jump = 0;
	cli.vertical_maxspeed	= 160;
	cli.vertical_speedup	= 350;
	cli.vertical_slowdown	= 600;
	cli.horizontal_maxspeed	= 160;
	cli.horizontal_speedup	= 350;
	cli.horizontal_slowdown	= 600;
	// specify sprite names for each state.
	// 'cs_none' or 'no status' specifies picture image in the game editor
	cli.SetSpriteStatusName( cs_none , "fmine1" );
	cli.SetSpriteStatusName( cs_idle , "fmine_i" );
	cli.SetSpriteStatusName( cs_walk , "fmine_i" );
	cli.SetSpriteStatusName( cs_fly  , "fmine_i" );
	cli.SetSpriteStatusName( cs_dead , "fmine1" );
	cli.SetSpriteStatusName( cs_attack1 , "fmine_w" );
	cli.SetSpriteStatusName( cs_attack2 , "fmine_a" );
	// setup a position at which the shots are fired from creature's body, and its direction.
	barrel.pos.Set( 0, 0 );
	barrel.direction_vector.Set( 10, 0 );
	cli.gun_barrels[ "gun" ] = barrel;
	// add creature specification to list
	list.push_back( cli );
	// fire mine end

note: See creature for more of its properties.

note: these variables should be added through xml file in the future instead of adding it through code

If I wanted to add a creature of the same behavior as some existing creature I would finish here. If I compile code now and start Tery I can find a new creature called 'fire mine' in the creature group 't1', with correct sprite in the Creature palette.

Firemine shown in editor

Step 6 - Behavior

Adding creature behavior code

So, we want to go further and add completely new creature with different behavior. For that we need to create new creature code - new c++ class, derive it from some suitable existing creature class, and then change its behavior as required.

So I Add a “creature_firemine” class to project, derive it from ”s_creature” base class, which means that it will have only basic possibilities: moving around, obeying/disobeying gravity, and jumping if it obeys gravity. Fire mine will not obey gravity, it will fly around. I'll derive it also from ”TimersArray” class as I want to use some simple timers in my class, for measuring time from one shot to another and such.

Default empty class code looks like this:



  #include "creature.h"
  #include "timers.h"

  namespace TeryCreatures {

  class creature_firemine : public s_creature, public Tery::TimersArray



I'll make the class available for game engine by adding this code:

  if ( strcmp( name, "Fire Mine" ) == 0 )
    return new TeryCreatures::creature_firemine;

into the function CCreatureTypeList::CreateCreatureInstance( const char* name ) located in the file “crtype.cpp”. So from now on it will be instantiated correctly, but it does not have any special functionality. Lets add it now.

I'll re-implement function void creature_firemine::ApplyTime_ControlsToVelocity( float tds ) which handles movement of the creature and firing. It basically should take all the keyboard inputs and move or fire accordingly.


void creature_firemine::ApplyTime_ControlsToVelocity( float tds )
	// let the default creature implementation handle the creature movement
	// it will use arrows to move it left, right, up and down
	s_creature::ApplyTime_ControlsToVelocity( tds );
	// Projectile is a creature too, and this is its name.
	static	std::string		m_projectile_type( "laser" );
	CTeryGame*	gi;
	gi = GameInstance;
	// if action key 2 is pressed, then fire the weapon.
	// fire it only if cooldown time since last firing passed.
	if (( control_keys[ ck_action2 ] ) && ( firemine_lasers.m_able ) && 
		( m_firing_status == fs_not_firing )) {
		m_firing_status = fs_warning;
		// wait for some time
		TimerStart( ti_attack, gi->GetGameTime() );
		TimerSetEndTime( ti_attack, gi->GetGameTime() + firemine_lasers_warning_duration );
		// change to warning sprite
		ChangeStatus( cs_attack1 );
	if ( m_firing_status != fs_not_firing ) {
		switch( m_firing_status ) {
		case fs_warning:
			// starting the warning. Status was changed to 'attack1' before and sprite
			// was changed too, to show white circles around the creature. Now I'm
			// only waiting a little, and then status will change to 'after warning'
			if ( TimerEndTimePassed( ti_attack, gi->GetGameTime() ) ) {
				m_firing_status = fs_after_warning;
				TimerStart( ti_attack, gi->GetGameTime() );
				TimerSetEndTime( ti_attack, gi->GetGameTime() + firemine_lasers_after_warning_delay );
				// change to warning sprite
				ChangeStatus( cs_none );
		case fs_after_warning:
			// after warning status shows creature in 'standing still' position.
			// wait a little until the attack. Then, start up the attack,
			// change sprite to attack sprite, and start attack timer.
			if ( TimerEndTimePassed( ti_attack, gi->GetGameTime() ) ) {
				m_firing_status = fs_shooting;
				m_shot_number = 0;
				// change to warning sprite
				ChangeStatus( cs_attack2 );
				// set up timer that finishes right away, so shooting can start up
				TimerStart( ti_attack, gi->GetGameTime() );
				TimerSetEndTime( ti_attack, gi->GetGameTime() );
		case fs_shooting:
			// when shooting then wait few moments untill shooting next projectile.
			// if time passes, then alter firing angle and create new projectile
			// if more than 'firemine_lasers_num_of_shots' is fired then move to another 
			// status - 'cooldown'
			if ( TimerEndTimePassed( ti_attack, gi->GetGameTime() ) ) {
				// calculate position of new projectile
				s_xy_long	pos( absolute_pos.x, absolute_pos.y );
				s_xy_float	pv;
				float		angle;
		firemine_lasers_num_of_shots		s_creature	*pprojectile;
				angle = GetPointingAngle() + firemine_lasers_angle_increment * m_shot_number;
				CTeryLevelObjects::AngleToVector( angle, pv.x, pv.y );
				pprojectile = LevelObjectsInstance->AddProjectileFiredByObject(
					*this, "gun", m_projectile_type.c_str(),
					"laser shot", pv.x, pv.y );
				if ( pprojectile ) {
					pprojectile->SetOwnerId( GetId() );
					pprojectile->SetDirection( GetDirection() );
					pprojectile->SetPointingAngle( angle );
					// play sound
					//PlaySound( m_fire_sound_id );
				} else {
					LogS( InfoError, "creature_firemine::ApplyTime_ControlsToVelocity() Error creating projectile, obj id %i '%s', projectile type '%s'\n",
						GetId(), GetName(), m_projectile_type.c_str() );
				// set up timer to wait for next shot
				TimerStart( ti_attack, gi->GetGameTime() );
				TimerSetEndTime( ti_attack, gi->GetGameTime() + firemine_lasers_one_shot_delay );
				if ( m_shot_number >= firemine_lasers_num_of_shots ) {
					ChangeStatus( cs_idle );
					TimerStart( ti_attack, gi->GetGameTime() );
					TimerSetEndTime( ti_attack, gi->GetGameTime() + firemine_lasers_cooldown_delay );
					m_firing_status = fs_cooldown;
		case fs_cooldown:
			// wait untill cooldown ends and return back to normal idle status.
			if ( TimerEndTimePassed( ti_attack, gi->GetGameTime() ) ) {
				m_firing_status = fs_not_firing;
				m_ai_flying.status = as_nothing;
				ChangeStatus( cs_idle );
		}// switch
	} // if firing
}// creature_firemine::ApplyTime_ControlsToVelocity


	virtual void ApplyTime_AIActionToController( float tds );
	struct {
		bool	m_able;
		bool	m_fired;
	} firemine_lasers;
	// constants specifying time delays in ms and other values
	enum {
		firemine_lasers_warning_duration = 2000,
		firemine_lasers_after_warning_delay = 1000,
		firemine_lasers_one_shot_delay = 100,
		firemine_lasers_cooldown_delay = 5000,
		firemine_lasers_angle_increment = 45,
		firemine_lasers_num_of_shots = 16,
	} ;
	enum e_firing_status {
	} ;
	// is mine firing yet ? value from [e_firing_status]
	int		m_firing_status;
	// how many shots were fired in current firing frenzy
	int		m_shot_number;
	enum e_timer_idents {
		ti_attack = 1,
		ti_waiting = 2,
	} ;

Now the creature can do some damage. It has a nice feature that if you press 'action2' button, it will give out a warning, wait for a moment and then it will fire 16 projectiles in all directions. You can try the functionality out by adding the new creature into any game level through LevelEditor, assigning its type to 'player controlled' and its control to 'keyboard'. Now you can move it around with arrows and let it fire with Action2 key ( that is probably Enter key on keyboard, depends on game settings ).

Step 7 - AI

Now to make creature think on its own - lets create some AI code which will make creature move and fire randomly. We'll make it fly here and there around some point, it will choose randomly where it will wander, but it won't move further than [a] pixels from point [x,y] specified in its properties. If any other creature comes close to Fire Mine it will start its Action2 - it will fire. Minimum distance of the creature to initiate fire will be specified in its properties aswell.

AI code is placed in function void ApplyTime_AIActionToController( float tds )

void creature_firemine::ApplyTime_AIActionToController( float tds )
	float distf;
	int	flag, disti;
	CTeryGame*	gi;
	gi = GameInstance;

	if (( GetAction() != ca_moving_around ) &&
		( GetAction() != ca_waiting ))
	switch( status ) {
	case cs_none:
		ChangeStatus( cs_idle );
	case cs_idle:
	case cs_walk:
	case cs_fly:
		// idle status. That means that mine should move around and detect enemies
		switch( m_ai_flying.status ) {
		case as_nothing:
			// am I moving ? if not, then calculate random movement vector, and start moving.
			// check if firemine is too far from its guarding spot. If it is, then move to the guarding spot
			disti = LevelObjectsInstance->DistanceOfTwoPoints(
				absolute_pos.x, absolute_pos.y, 
				m_ai_flying.guarding_point.x, m_ai_flying.guarding_point.y );
			m_ai_flying.prev_dist = disti;
			// if its in movement radius, then send it out randomly
			m_ai_flying.status = as_moving;
			// direction and distance
			m_ai_flying.go_distance = m_ai_flying.guarding_radius / 5 + rand() % ( m_ai_flying.guarding_radius / 2 );
			CTeryLevelObjects::AngleToVector( rand() % 360, 
				m_ai_flying.go_vector.x, m_ai_flying.go_vector.y );
			// how long to wait after moved to target spot
			m_ai_flying.wait_time_ms = 1000 + rand() % 4000;
			// set keys which control movement to move in required direction
			analog_control.position[ 0 ] = m_ai_flying.go_vector.x;
			analog_control.position[ 1 ] = m_ai_flying.go_vector.y;
			m_ai_flying.go_starting_point = absolute_pos;
			ChangeStatus( cs_fly );
		case as_moving:
		case as_moving_return:
			flag = 0;
			// check if collided
			if ( IsColided() )
				flag = 1;
			if ( flag == 0 ) {
				// check if its on the end of road
				disti = LevelObjectsInstance->DistanceOfTwoPoints(
					absolute_pos.x, absolute_pos.y, 
					m_ai_flying.go_starting_point.x, m_ai_flying.go_starting_point.y );
				if ( disti > m_ai_flying.go_distance )
					flag = 2;
			if ( flag == 0 ) {
				// check if its too far from its guarding spot
				disti = LevelObjectsInstance->DistanceOfTwoPoints(
					absolute_pos.x, absolute_pos.y, 
					m_ai_flying.guarding_point.x, m_ai_flying.guarding_point.y );
				if (( disti >= m_ai_flying.guarding_radius ) && ( disti > m_ai_flying.prev_dist ))
					flag = 1;
			// turn it away, if too far or collided
			if ( flag != 0 ) {
				// change status and appearance to 'idle'
				m_ai_flying.status = as_waiting;
				ChangeStatus( cs_idle );
				// reset keys to zero, so creature will stop its movement
				analog_control.position[ 0 ] = 0;
				analog_control.position[ 1 ] = 0;
				// setup waiting timer
				// if it collided instead of finishing its distance, do not wait and get back
				// to 'idle' status
				if ( flag == 1 )
					m_ai_flying.status = as_nothing;
				else {
					TimerStart( ti_waiting, gi->GetGameTime() );
					TimerSetEndTime( ti_waiting, gi->GetGameTime() + m_ai_flying.wait_time_ms );
			m_ai_flying.prev_dist = disti;
			// if end of road is not reached, then let it be - it will just move because 
			// its movement keys are set.
		case as_waiting:
			// if i was waiting long enough after movement, then lets get back to repeat 
			// the whole cycle of "idle - moving - waiting"
			if ( TimerEndTimePassed( ti_waiting, gi->GetGameTime() ) ) {
				m_ai_flying.status = as_nothing;
		} // switch( m_ai_flying.status ) 
		// in any status do detect enemies and start attacking if there is any around.
		s_creature	*enemy_creature;
			m_ai_flying.guarding_point.x, m_ai_flying.guarding_point.y, 
			GetId(), 0, 0, &enemy_creature, &distf );
		if ( distf < m_ai_flying.enemy_detect_radius ) {
			// set keyboard key 'action2' of this creature to 'pushed'
			control_keys[ ck_action2 ] = 1;
			// stop movement
			// reset keys which control movement
			analog_control.position[ 0 ] = 0;
			analog_control.position[ 1 ] = 0;
	case cs_attack1:
	case cs_attack2:
		// attack status. Mine is attacking. All sorted out in 'ApplyTime_ControlsToVelocity()'


	virtual void ApplyTime_AIActionToController( float tds );
	enum e_ai_status {
	} ;
	struct {
		// what is it doing at the moment ? value from 'e_ai_status'
		e_ai_status		status;

		// if it will start walking, then how far will it walk
		int		go_distance;
		// in which direction
		s_xy_float	go_vector;
		// movement starting point
		object_data_level_position	go_starting_point;
		// if waiting, then how long to wait
		int		wait_time_ms;
		// center point which it is guarding
		object_data_level_position	guarding_point;
		// how far can it move from the center point
		int		guarding_radius;
		// how far must be an enemy to start the alarm
		int		enemy_detect_radius;
		int		prev_dist;
	} m_ai_flying;

Step 8 - Finished

So its finished.

What to do next? Test out the creature if it works well. That is probably the longest part of the process - the fine-tuning of the creature's behavior. Test if it acts accordingly in various situations (in collision, in some triggers, when there are multiple enemies around etc.

Maybe I could add some sounds to the creature states - some firing sounds, warning sound, dying sound etc. They can be added just like I added sprites in steps number 4 & 5. I could add Dying animation and some dying behavior, as at the moment FireMine will just stop reacting when it dies.

How long does it take to create one creature ? Well, step 1 is up to your decision. Step 2 - drawing - depends on your skills and your requirements. It took me 2 hours for FireMine. Step 3 - conversion - is not hard, it was done in 30 minutes, and step 4 - copying of files - is done in 5 minuts. Step 5 - adding it to game engine was 30 minutes, its quite easy, but hardest are step 6 - creating its behavior and 7 - creating the AI. Step 6 took me 45 minutes, but another 2 hours to test and fine-tune it. Step 7 was like 90 minues of basic work and then 3 hours of testing.

Oh by the way, I have added few handy virtual functions to the creature. ResetCreatureData() and CreatingFinished() initializes its data to zero, so it is set up properly. Another function FinishPlacing() is called every time when FireMine is moved inside editor. When its called it updates 'guarding point' to current position, so if you place new FireMine into the level, it will know that it should guard a point right where it was placed in level.

	// resets the data when creature is created
	virtual void ResetCreatureData( void );
	// some things have to be initialized only after creature is completely created
	virtual void CreatingFinished( void );
	// when object is dragged or placed somewhere, then update that it is 
	// its new guarding spot
	virtual void FinishPlacing( void );
void creature_firemine::ResetCreatureData( void )
	m_firing_status = fs_not_firing;
	firemine_lasers.m_able = true;
	firemine_lasers.m_fired = false;
	m_ai_flying.status = as_nothing;
	m_ai_flying.guarding_point.x = m_ai_flying.guarding_point.y = 0;
	m_ai_flying.guarding_radius = 40;
	m_ai_flying.enemy_detect_radius = 80;

void creature_firemine::CreatingFinished( void )
	ChangeStatus( cs_idle );

	if ( control == cct_ai )
		analog_control.enabled = true;

	// because of searching of enemy creatures around - function GetNearestCreature() requires that
	SetOwnerId( GetId() );

	m_ai_flying.guarding_point = absolute_pos;

void creature_firemine::FinishPlacing( void )
	m_ai_flying.guarding_point = absolute_pos;
tery/tutorial_new_creature_firemine.txt · Last modified: 2008/08/08 18:21 by mirex
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki