Zelda style combat; Implementing and testing

Started by R4L, Thu 14/07/2011 07:36:18

Previous topic - Next topic

R4L

Hey all. It's been a while since I've done anything on these boards, so I wanted to share this and hopefully get some critique.

I'm working on my coding skills, and I've uploaded a test game for you all to try that has a very simple implementation of Zelda style combat.

I've included the source and everything. I just want to know if it's a good start, and maybe some guidelines on how I can improve it. Right now, the hit detection isn't great, and health decreases too fast because the code that checks for when to remove health is under repeatedly execute. I've tried to limit it a bit by removing health only when Link is in certain frames of his attack animation, but it still decreases health a bit too fast.

I made sure to add comments to the script, so you know what code I've added. The main sections to check are the header, game start, repeatedly execute always, and on key press.

Here's a link to the test game. It's not much, but it's a start I guess. Move with the arrow keys, attack with X.

Thanks!

EDIT: I figured it would be easier to add the code here so no one has to open it up in the editor and search for it.

The Header
Code: ags

// Main header script - this will be included into every script in
// the game (local and global). Do not place functions here; rather,
// place import definitions and #define names here to be used by all
// scripts.

int health; //enemy health
export health;


Game Start
Code: ags

// Called when the game starts, before the first room is loaded
function game_start() {   
  // Put the code all in a function and then just call the function. 
  // It saves cluttering up places like game_start.
  initialize_control_panel(); 
  // Use the KeyboardMovement module to, per default, replicate the standard
  // keyboard movement of most Sierra games. See KeyboardMovement.txt for more info
  KeyboardMovement.SetMode(eKeyboardMovement_Pressing); 

  health = 100; //declared enemy's HP
}


Repeatedly Execute
Code: ags

function repeatedly_execute() {
  
  // Put here anything you want to happen every game cycle, even when
  // the game is paused. This will not run when the game is blocked
  // inside a command like a blocking Walk()
  
  if (IsGamePaused() == 1) return;

  // Put here anything you want to happen every game cycle, but not
  // when the game is paused.
  
//checks enemy's health to make him disappear when it's 0 or below
//doesn't actually do anything; you can still hit the enemy, but it's
//for demo purposes

  if (health <= 0){
    if(cFoe.Transparency > 0){
      return;
    }
    cFoe.Transparency = 100;
  return;
}
}


Rep. Execute Always
Code: ags

function repeatedly_execute_always() {
  
  // Put anything you want to happen every game cycle, even
  // when the game is blocked inside a command like a
  // blocking Walk().
  // You cannot run blocking commands from this function.
  
//this whole next block is for displaying the int health on the statusbar on label3
//and checking when to check for hit detection while Link is in his
//attacking view and apply damage by subtracting from health
  
  Label3.Text = String.Format("%d", health);
  
  if (cLink.Frame == 6){
      cLink.ChangeView(VIEW2);
    }
 
 while (((cLink.View == ATTACK) && (cLink.Animating == true) && (cLink.Frame >= 4))) {
  if (cLink.IsCollidingWithChar(cFoe)){
    health = health-4;
  }
    return;
  }
}


On Key Press
Code: ags

function on_key_press(eKeyCode keycode) {
  // The following is called before "if game is paused keycode=0", so
  // it'll happen even when the game is paused.
  
//for animating Link's attack
  if (keycode == eKeyX){
    cLink.ChangeView(ATTACK);
    cLink.Animate(2, 1, eOnce, eNoBlock, eForwards);
  }
}

monkey0506

#1
I haven't actually tested this out yet, but you should be able to correct the health issue by simply including another variable:

Code: ags
// top of GlobalScript.asc
bool attacked = false;

// inside on_key_press
if (keycode == 'X') // I prefer this method for displayable characters :P
{
  cLink.LockView(ATTACK);
  cLink.Animate(2, 1, eOnce, eNoBlock, eForwards);
  attacked = true;
}

// rep_ex_always
if ((cLink.View == ATTACK) && (!cLink.Animating)) cLink.UnlockView();
if ((cLink.View == ATTACK) && (cLink.Frame >= 4) && (attacked) && (cLink.IsCollidingWithChar(cFoe)))
{
  health -= 4; // this is a shortcut for what you were already doing...
  attacked = false;
}


Also, why are you running a while loop inside rep_ex_always with a return statement inside the loop that is executed every iteration of the loop? That essentially makes your loop into an if statement, because it would only ever run once at most.

Oh, and I switched from using ChangeView to Lock/UnlockView which is the preferred method for temporary animations.

Edit: I just realized you are defining your health variable inside of the script header. DO NOT DO THIS! EVER!! There is never a reason to define a variable inside of a script header, period. To make a variable global, you define the variable inside of the ASC script file, export the variable in the same ASC script file, and import it into the ASH header file (with the import keyword). The way you are doing it right now is creating separate variables for every room, not creating a single global variable.

Finally, (since I commented on everything else :P), there's no benefit behind checking the value of a variable before setting it, so you may as well just set the transparency immediately after checking the health, instead of checking it first. Oh, and one more thing, there's no reason to manually call return if it's the last statement in a function. The only reason you need to call return is if you want to prevent the function from executing code in the function after that point.

R4L

#2
Quote from: monkE3y_05_06 on Thu 14/07/2011 08:14:27
Also, why are you running a while loop inside rep_ex_always with a return statement inside the loop that is executed every iteration of the loop? That essentially makes your loop into an if statement, because it would only ever run once at most.

Because I don't know any better. :) Haha thanks for telling me that though.

Quote

Oh, and I switched from using ChangeView to Lock/UnlockView which is the preferred method for temporary animations.

OK, good to know that. I wasn't sure which one to use, but I know now.

Quote
Edit: I just realized you are defining your health variable inside of the script header. DO NOT DO THIS! EVER!! There is never a reason to define a variable inside of a script header, period. To make a variable global, you define the variable inside of the ASC script file, export the variable in the same ASC script file, and import it into the ASH header file (with the import keyword). The way you are doing it right now is creating separate variables for every room, not creating a single global variable.

Finally, (since I commented on everything else :P), there's no benefit behind checking the value of a variable before setting it, so you may as well just set the transparency immediately after checking the health, instead of checking it first. Oh, and one more thing, there's no reason to manually call return if it's the last statement in a function. The only reason you need to call return is if you want to prevent the function from executing code in the function after that point.

Alright, I'll remove the int from the header. I added this to the ASC at the very beginning:

Code: ags

int health;
export health;


and added this to the header:

Code: ags

import health;


But now I'm getting expected variable or function after import, not 'health' error. Am I supposed to add an integer value to this?

EDIT: Wow.... forgot to add import int health. Works now.

R4L

Sorry for the double post, but I wanted to ask something about the hit detection.

Does the normal IsCharColliding command check only x values? I found that I have to be in an exact spot for damage to be dealt.

Shane 'ProgZmax' Stevens

#4
You may want to look through the module section for the pixelperfectcollisions module.  This will allow you to have precision in the collision detection.  Something else you might want to do is make Link's sword (or even just the blade) a separate character when he uses it.  That way you can run the collision detection between the sword and the enemy rather than between ALL of Link and the enemy.  Since you're going with Zelda style combat you don't need to do that with enemies since touching them does the same damage as them attacking you.

monkey0506

QuoteChecks if the character is touching OTHERCHAR. This function just checks the baseline of both characters, so if one is standing a fair distance behind the other, it will not be marked as colliding.

You could perhaps work something up with checking the distance between the characters and then changing the character's baselines based on distance (say, if the distance is within 10 pixels they will be set to the same baseline, otherwise their baseline could just be reset to 0). You'd also want to include some checks to make sure the character is facing the correct direction (based on their loop). And you might want to take a look at Character.BlockingWidth, Character.BlockingHeight, and Character.Solid.

Edit: ProgZ posted while I was typing, and makes a good point. ;)

Khris

I actually wouldn't use the built in collision function but rather check an "attack box" against enemy coords.
One reason for this is that it should be possible to hit more than one enemy at once. Once this system is in place, it's trivial to include the spin attack or other special moves.

R4L

Quote from: ProgZmax on Fri 15/07/2011 18:24:11
You may want to look through the module section for the pixelperfectcollisions module.  This will allow you to have precision in the collision detection.  Something else you might want to do is make Link's sword (or even just the blade) a separate character when he uses it.  That way you can run the collision detection between the sword and the enemy rather than between ALL of Link and the enemy.  Since you're going with Zelda style combat you don't need to do that with enemies since touching them does the same damage as them attacking you.

That's the plan, I just want to make sure I can get the attacking part to work first. I'll look for that module too. Thanks for the reference Prog. :)

Quote from: monkE3y_05_06 on Fri 15/07/2011 18:27:48
QuoteChecks if the character is touching OTHERCHAR. This function just checks the baseline of both characters, so if one is standing a fair distance behind the other, it will not be marked as colliding.

You could perhaps work something up with checking the distance between the characters and then changing the character's baselines based on distance (say, if the distance is within 10 pixels they will be set to the same baseline, otherwise their baseline could just be reset to 0). You'd also want to include some checks to make sure the character is facing the correct direction (based on their loop). And you might want to take a look at Character.BlockingWidth, Character.BlockingHeight, and Character.Solid.

Edit: ProgZ posted while I was typing, and makes a good point. ;)

Very good points that I did not think about... I'll have to add in some code to check for which way Link is facing, and fix the attack code to reflect that. I'll look at the manual too for those functions.

Quote from: Khris on Fri 15/07/2011 18:32:12
I actually wouldn't use the built in collision function but rather check an "attack box" against enemy coords.
One reason for this is that it should be possible to hit more than one enemy at once. Once this system is in place, it's trivial to include the spin attack or other special moves.

This could be something I could implement with the pixel perfect collision detection module too right?

monkey0506

#8
Unless the character was just shaped really weird, you should probably be able to use the BlockingWidth and BlockingHeight to define the "attack box", and then use the pixel-perfect collision functions to check for coordinates within that box (which should be centered around player.x, and have its bottom at player.y).

So, just to give you an idea:

Code: ags
int x1 = player.x - (player.BlockingWidth / 2);
int y1 = player.y - player.BlockingHeight;
int x2 = player.x + (player.BlockingWidth / 2);
int y2 = player.y;

R4L

Quote from: monkE3y_05_06 on Fri 15/07/2011 21:20:19
Unless the character was just shaped really weird, you should probably be able to use the BlockingWidth and BlockingHeight to define the "attack box", and then use the pixel-perfect collision functions to check for coordinates within that box (which should be centered around player.x, and have its bottom at player.y).

So, just to give you an idea:

Code: ags
int x1 = player.x - (player.BlockingWidth / 2);
int y1 = player.y - player.BlockingHeight;
int x2 = player.x + (player.BlockingWidth / 2);
int y2 = player.y;


Oooohhh OK! That doesn't even sound that hard!  :=

SMF spam blocked by CleanTalk