An ice piece of work...

Need help with using DizzyAGE then look here
Queex
Hard Boiled Egg
Posts: 124
Joined: Thu Oct 22, 2009 5:04 pm

An ice piece of work...

Post by Queex » Tue Nov 24, 2009 11:35 pm

So... I want to implement ice.

That is, a type of block that, when you move onto it, forces you to keep moving (or rolling) in the same direction until you encounter normal ground again. Jumping or using the action key should be impossible on ice. Just to put icing on the cake, ice should behave as normal blocks when you have a certain item in your inventory.

I have some ideas as to how to go about it, but the last time I looked at the main movement code I couldn't quite fathom it. In fact, I suspect that some of the functions I'm thinking I would need to change are not, in fact, exposed by the engine and I'd have to do it another way.

Reading Grandad's thread about climbing, I'm wondering if custom movement is the actually the best way to go about it rather than trying to put together a general solution.

Can someone with more experience give me a couple of pointers as to the most reliable way of going about this?
- Lost Temple Dizzy -
- Iron Tower Dizzy -

Colin
Hard Boiled Egg
Posts: 594
Joined: Mon May 28, 2007 8:34 am
Location: Worthing, West Sussex, UK

Post by Colin » Wed Nov 25, 2009 12:14 am

as a starter, I suggest downloading the source code to Dizzy and the other side from the Dizzyage site. I know that alex put ice into his game! It sounds just like what your after.
I haven't had a look at the code though, so i'm not sure how difficult it is to understand.
ImageImageImage

Queex
Hard Boiled Egg
Posts: 124
Joined: Thu Oct 22, 2009 5:04 pm

Post by Queex » Wed Nov 25, 2009 9:47 am

I shall do that, then. It's bit of a relief to know it can be done, and I can look at how it was done.

Cheers.
- Lost Temple Dizzy -
- Iron Tower Dizzy -

Meph
Hard Boiled Egg
Posts: 2973
Joined: Fri Apr 13, 2007 12:47 am
Location: England, Suffolk

Post by Meph » Wed Nov 25, 2009 5:57 pm

Try the Winter dizzy. being a smaller game you probably find it easer.
Or star ship.. i remember it being uses there.
Its always the cracked ones that let the light in

delta
Hard Boiled Egg
Posts: 3965
Joined: Fri Feb 09, 2007 7:08 pm
Location: North West
Contact:

Post by delta » Wed Nov 25, 2009 6:03 pm

Meph wrote:Try the Winter dizzy. being a smaller game you probably find it easer.
It isn't used in Winter World Dizzy.
Image

Image

"Quotes from the internet may not be genuine" - Abraham Lincoln

Meph
Hard Boiled Egg
Posts: 2973
Joined: Fri Apr 13, 2007 12:47 am
Location: England, Suffolk

Post by Meph » Wed Nov 25, 2009 6:23 pm

oh.. i thought it was, must be thinking of a different ice puzzle.. (ah using the flipper to spread his weight.. thats what i was thinking of)

Well its in Star ship.
:egg:
Its always the cracked ones that let the light in

delta
Hard Boiled Egg
Posts: 3965
Joined: Fri Feb 09, 2007 7:08 pm
Location: North West
Contact:

Post by delta » Wed Nov 25, 2009 7:16 pm

right, this is how to implement it.

firstly, in the map, any (flat!) surface you want to set as ice needs to have the properties set as follows: Type=STATIC, Material=10 (set it manually, it will show as UNKNOWN), and draw=IMG+MAT.

we can now use material 10 to define when dizzy is on 'ice' or not.

also, place an item in the map with the ID set as 1000 (or whatever you want).


now to the coding. Put the following in def.gs:

Code: Select all

#def MAT_ICE		10			// ice (hard); player slides until off the ice
and in gamedef.gs, put:

Code: Select all

#def ID_ICEPICK			1000		// set this to the ID of whatever is used to walk on ice.

#def G_ICEWALK			69		// if player is on ice. 0=no, 1=yes

#def STATUS_ICE			99		// set status for ice movement
Set '1000' to the object ID of whatever is used to walk on the ice normally.

Now for the actual meat of the coding. Firstly, name object 1000 in function ObjectsSetNames() in game.gs:

Code: Select all

	ObjSetName(ObjFind(1000),"AN ICE PICK");
then make sure the game doesn't start with the player in 'ice' mode, by putting the following line of code at the bottom of function BeginNewGame() in game.gs:

Code: Select all

	GameSet(G_ICEWALK,0);
Now we need to define the density of the new 'Ice' material, so put the following line into function HandlerGameInit() in handlers.gs:

Code: Select all

	MaterialSetDensity( MAT_ICE,		MATD_HARD );
We now need to set the conditions of entry into the slide. So in function HandlerPlayerUpdate() in handlers.gs (just below the 'check clouds' bit of coding is ideal), put the following code:

Code: Select all

	// Check Ice
	if(IsMaterialUnderPlayer(MAT_ICE)&&InventoryFind(ObjFind(ID_ICEPICK))==-1)
	{
		GameSet(G_ICEWALK,1);
	}
this checks the material of whatever is under dizzy, and if he is on top of ice, AND doesn't have the Ice Pick, changes G_ICEWALK to 1, which is then picked up in the next bit of coding...

which you need to put right at the bottom of function HandlerPlayerUpdate() in handlers.gs. replace this:

Code: Select all

	// Custom movement test
	// PlayerSet(P_CUSTOMMOVE,1);
	// CM_Update();
with this:

Code: Select all

	// Custom movement test
	if(GameGet(G_ICEWALK)==1) { CM_Update(); }
This calls the custom movement coding when G_ICEWALK is 1.


The rest of the code needs to be put into file movement.gs.

firstly, change function CM_Update() from this:

Code: Select all

func CM_Update()
{
	status = PlayerGet(P_STATUS);
	
	// input status
	if( status==STATUS_IDLE || status==STATUS_WALK )
	{
		CM_EnterKeyState();
		status = PlayerGet(P_STATUS); // re-read status
	}
	
	if( status==STATUS_IDLE )
		CM_UpdateIdle();
	else
	if( status==STATUS_WALK )
		CM_UpdateWalk();
	else
	if( status==STATUS_JUMP )
		CM_UpdateJump();
	else
	if( status==STATUS_FALL )
		CM_UpdateFall();
	else
	if( status==STATUS_SCRIPTED )
		CM_UpdateScripted();
	
	if( PlayerGet(P_STATUS)!=STATUS_SCRIPTED )
	{
		snap = CM_CheckCollidersSnap();
		status = PlayerGet(P_STATUS);

		// stand check only if not already snapped to collider
		if( !snap && (status==STATUS_IDLE || status==STATUS_WALK) )
		{
			h = CM_CheckFallY(1); // see if it can fall 
			if(h>0) // if any space below then enter in fall
			{
				CM_EnterFall();
				PlayerSet(P_Y,PlayerGet(P_Y)+1); 
				PlayerSet(P_POW,PlayerGet(P_POW)+1); // force one step down (DIZZYMATCH)
			}
		}
		
		// fix collision by rising dizzy
		CM_CheckCollision();
	}
}
to this (changes highlighted in red):

Code: Select all

func CM_Update()
{
	status = PlayerGet(P_STATUS);
	
	// input status
	if( status==STATUS_IDLE || status==STATUS_WALK [color="#ff0000"]|| STATUS_ICE[/color] )
	{
		CM_EnterKeyState();
		status = PlayerGet(P_STATUS); // re-read status
	}
	
	if( status==STATUS_IDLE )
		CM_UpdateIdle();
	else
	if( status==STATUS_WALK )
		CM_UpdateWalk();
	else[color="#ff0000"]
	if( status==STATUS_ICE )
		CM_UpdateIce();
	else[/color]
	if( status==STATUS_JUMP )
		CM_UpdateJump();
	else
	if( status==STATUS_FALL )
		CM_UpdateFall();
	else
	if( status==STATUS_SCRIPTED )
		CM_UpdateScripted();
	
	if( PlayerGet(P_STATUS)!=STATUS_SCRIPTED )
	{
		snap = CM_CheckCollidersSnap();
		status = PlayerGet(P_STATUS);

		// stand check only if not already snapped to collider
		if( !snap && (status==STATUS_IDLE || status==STATUS_WALK) )
		{
			h = CM_CheckFallY(1); // see if it can fall 
			if(h>0) // if any space below then enter in fall
			{
				CM_EnterFall();
				PlayerSet(P_Y,PlayerGet(P_Y)+1); 
				PlayerSet(P_POW,PlayerGet(P_POW)+1); // force one step down (DIZZYMATCH)
			}
		}
		
		// fix collision by rising dizzy
		CM_CheckCollision();
	}
}
Lower down, in the section headed:

Code: Select all

//////////////////////////////////////////////////////
// ENTER STATES
//////////////////////////////////////////////////////
put a new function (below function CM_EnterWalk( dir ) is ideal):

Code: Select all

func CM_EnterIce( dir )
{
	PlayerSet(P_STATUS, STATUS_ICE); 
	PlayerSet(P_DIR, dir);
	PlayerSet(P_POW, 0);
	PlayerSet(P_FLIP, (PlayerGet(P_FLIP) & FLIPY) | (dir==-1));

	tile = PlayerGet(P_COSTUME)+PlayerGet(P_TILEWALK);
	if( PlayerGet(P_TILE)!=tile )
	{
		PlayerSet(P_FRAME, 0);
		PlayerSet(P_TILE, tile);
	}
}

you also need to change function CM_EnterKeyState() from this:

Code: Select all

func CM_EnterKeyState()
{
	if( PlayerGet(P_LIFE)<=0 ) { CM_EnterIdle(); return; } // prepare to die

	dir = 0;
	if( GetKey(KEY_RIGHT) )	dir++;
	if( GetKey(KEY_LEFT) )	dir--;
	if( GetKey(KEY_JUMP) )		
	{
		// call jump handler to determine the power of the jump
		ScrSetHandlerData(0,-1); // send no material
		ScrSetHandlerData(1,0);  // clean return for safety
		HandlerJump();
		pow = ScrGetHandlerData(1); // receive power
		if(pow>0) CM_EnterJump(dir,pow); // 7 would be the default jump power
	}
	else
	if(dir!=0)
	{
		CM_EnterWalk(dir);
	}
	else 
	{
		CM_EnterIdle();
	}
}
to this (changes highlighted in red):

Code: Select all

func CM_EnterKeyState()
{
	if( PlayerGet(P_LIFE)<=0 ) { CM_EnterIdle(); return; } // prepare to die
	[color="#ff0000"]if( GameGet(G_ICEWALK)==1 ) { CM_EnterIce(PlayerGet(P_DIR)); return; } 		// walking on ice - no player input.[/color]

	dir = 0;
	if( GetKey(KEY_RIGHT) )	dir++;
	if( GetKey(KEY_LEFT) )	dir--;
	if( GetKey(KEY_JUMP) )		
	{
		// call jump handler to determine the power of the jump
		ScrSetHandlerData(0,-1); // send no material
		ScrSetHandlerData(1,0);  // clean return for safety
		HandlerJump();
		pow = ScrGetHandlerData(1); // receive power
		if(pow>0) CM_EnterJump(dir,pow); // 7 would be the default jump power
	}
	else
	if(dir!=0)
	{
		CM_EnterWalk(dir);
	}
	else 
	{
		CM_EnterIdle();
	}
}
Lastly, further down, in the section headed:

Code: Select all

//////////////////////////////////////////////////////
// UPDATE STATES
//////////////////////////////////////////////////////
put another new function (below function CM_UpdateWalk() is ideal):

Code: Select all

func CM_UpdateIce()
{
	if( CM_CheckWalkX() )
		PlayerSet(P_X, PlayerGet(P_ X) + PlayerGet(P_DIR)*CM_STEP X) ;

	if(!IsMaterialUnderPlayer(MAT_ICE)) { PlayerEnterIdle(); GameSet(G_ICEWALK,0); }
}
What all this movement coding basically does is bypass the player input code if the player is on ice (without the ice pick), and keep him moving in the direction he is facing until he isn't on the ice anymore, whereupon it puts him back into idle (stopping him moving), and reverts back to the default movement.

Let me know if you have any problems :)
Image

Image

"Quotes from the internet may not be genuine" - Abraham Lincoln

Queex
Hard Boiled Egg
Posts: 124
Joined: Thu Oct 22, 2009 5:04 pm

Post by Queex » Thu Nov 26, 2009 12:07 am

That is incredibly comprehensive. Thank you very much! I shall attend to putting it in immediately.

What's interesting is how easy it turns out to be to put in new custom materials- I had assumed it would be incredibly difficult but now I have a template to work from I can consider putting some different ones in for other puzzles.

*Some time later*

It almost works- but there are a couple of cases where it goes funny (probably as a result of me using ice in different places than before)-

* When you fall onto ice from directly above, you get stuck. I've fixed it with this version of the function:

Code: Select all

func CM_EnterKeyState()
{
	if( PlayerGet(P_LIFE)<=0 ) { CM_EnterIdle(); return; } // prepare to die
	if( GameGet(G_ICEWALK)==1 ) {
    [color="Red"]dir = PlayerGet(P_DIR);
    if(dir!=0){ CM_EnterIce(PlayerGet(P_DIR)); return; }
  }[/color]

	dir = 0;
	if( GetKey(KEY_RIGHT) )	dir++;
	if( GetKey(KEY_LEFT) )	dir--;
	
	[color="Red"]if (GameGet(G_ICEWALK)==1){
    //only if fall onto ice from above
    if(dir!=0) {
      CM_EnterIce(dir); return;
    }
    else {
      CM_EnterIdle(); return;
    }
  }[/color]
	
	if( GetKey(KEY_JUMP) )		
	{
		// call jump handler to determine the power of the jump
		ScrSetHandlerData(0,-1); // send no material
		ScrSetHandlerData(1,0);  // clean return for safety
		HandlerJump();
		pow = ScrGetHandlerData(1); // receive power
		if(pow>0) CM_EnterJump(dir,pow); // 7 would be the default jump power
	}
	else
	if(dir!=0)
	{
		CM_EnterWalk(dir);
	}
	else 
	{
		CM_EnterIdle();
	}
}
Which, if the current direction is 0, waits until you pick a direction before sliding.

* When you reach the end of a patch of ice, you fall straight down (how I discovered the above, in fact). I'd prefer it if you moved as if you'd walked off the edge, and kept your momentum. Is tinkering with custom movement the key, or do you also need to pay attention to the transition back to regular movement?

* As a small cosmetic thing, it would be nice if a jumping dizzy who landed on ice continued to roll until he got off the ice.

I'll work on the last two and see if I can crack them. It's late here and I think my head is stuffed with cotton wool.
- Lost Temple Dizzy -
- Iron Tower Dizzy -

Queex
Hard Boiled Egg
Posts: 124
Joined: Thu Oct 22, 2009 5:04 pm

Post by Queex » Fri Nov 27, 2009 6:22 pm

Some more refinements I've made- If Dizzy is partially on ice and solid ground, he no longer slides. Falling off the edge now behaves as you would expect. Continuing to roll is yet to be cracked.

In HandlePlayerUpdate in handlers.gs:

Code: Select all

// Check Ice
	if(IsMaterialUnderPlayer(MAT_ICE) && !(IsMaterialUnderPlayer(MAT_BLOCK) || IsMaterialUnderPlayer(MAT_CLIMB)) && InventoryFind(ObjFind(ID_CRAMPON))==-1)
	{
		GameSet(G_ICEWALK,1);
	}
In movement.gs:

Code: Select all

func CM_UpdateIce()
{
  if(IsMaterialUnderPlayer(MAT_BLOCK) || IsMaterialUnderPlayer(MAT_CLIMB)){
    GameSet(G_ICEWALK,0);
    PlayerEnterIdle();
  } else if(!IsMaterialUnderPlayer(MAT_ICE)){
    GameSet(G_ICEWALK,0);
    PlayerEnterFall();
  } else {
    if( CM_CheckWalkX() )
      PlayerSet(P_X, PlayerGet(P_ X) + PlayerGet(P_DIR)*CM_STEP X) ;
  }
}
A quick correction to the portion in CM_Update:

Code: Select all

if( status==STATUS_IDLE || status==STATUS_WALK || [color="Red"]status==[/color]STATUS_ICE)
	{
		CM_EnterKeyState();
		status = PlayerGet(P_STATUS); // re-read status
	}
Coming together nicely.

I've uncovered another troubling little glitch- if Dizzy falls onto an ice block, but vertically as a result of there being another block by his head stopping his horizontal movement, he enters the pattern as if he was falling vertically. I'll work on it.
- Lost Temple Dizzy -
- Iron Tower Dizzy -

Queex
Hard Boiled Egg
Posts: 124
Joined: Thu Oct 22, 2009 5:04 pm

Post by Queex » Fri Nov 27, 2009 8:22 pm

Okay, I think I need help.

I need some way of detecting when Dizzy has just been falling sideways when he lands on ice, because by the time that HandlerPlayerUpdate() is called, he's already been set to idle and any previous directional information has been lost.

One way round this would be to switch to custom movement when his status is falling, and catch him when he falls on ice.

However, it seems that the falling custom movement is broken, because he falls much faster than normal when you set it so.

(In fact, switching over to custom movement all the time seems to simply double how fast Dizzy moves...)

What have I missed?
- Lost Temple Dizzy -
- Iron Tower Dizzy -

Post Reply