Hey guys,
So I have been trying to avoid this for as long as I can but it is now time to make the ai system. So far I have it so my three enemy sprites throw balls at random to one of the three players. Now what I am having trouble with is having them move. pretty much what i have are three enemy sprites and two blockades. I need them to move around on the right half of the screen. What is the best way of doing this?
Thanks
-Lars
Dodgeball AI?
(20 posts) (3 voices)-
Posted 2 years ago #
-
Hey Lars
I asked a not total different question here: http://www.cocos2d-iphone.org/forum/topic/267
In the end I have populated a NSDictionary with NSArrays populated by NSArrays with contain actions. Then use some randoms and conditions for a sprite to decide where it goes. It actually works really well, with my sprites being able to "think" - and make sensible movements. Can probably post up some code if you are interested.
Justin
Posted 2 years ago # -
Hey Justin
I remember reading that a while ago and wasnt sure how well that would work with my game but if you dont mind posting some code I would really like to see how that works exactly.
Thanks
-LarsPosted 2 years ago # -
No worries Lars :) It might not be what you want - but it may give you some ideas. This will be a cut down version - mine is quite big now! The code is a little messy too - I think it probably needs some loving care. Also note that this is a working in progress - some code should probably shift into here for the decision process - and out of my layer.
//SpriteActions.h #import <Foundation/Foundation.h> #import "Sprite.h" #import "cocos2d.h" @interface SpriteActions : NSDictionary { } - (id) moveByX: (float)x y:(float)y; - (id) moveByX: (float)x y:(float)y rot:(float)rot; @end //SpriteActions.m #import "SpriteActions.h" @implementation SpriteActions - (void) dealloc { [super dealloc]; } - (id) moveByX: (float)x y:(float)y { float dur = sqrt(x * x + y * y) * 0.02F; return [MoveBy actionWithDuration:dur position:ccp(x,y)]; } - (id) moveByX: (float)x y:(float)y rot:(float)rot { float dur = sqrt(x * x + y * y) * 0.02F; return [Spawn actions:[MoveBy actionWithDuration:dur position:ccp(x,y)], [RotateBy actionWithDuration:dur angle:rot], nil]; } - (id) init { id action1 = [self moveByX:-37 y:0]; id action2 = [self moveByX:0 y:37]; id action3 = [Sequence actions: [self moveByX:0 y:23], [self moveByX:8 y:45 rot:10], [self moveByX:14 y:20 rot:80], [self moveByX:15 y:0], nil]; NSArray *keys101 = [NSArray arrayWithObjects:@"102",@"103", nil]; NSArray *objects101 = [NSArray arrayWithObjects:action1,action2, nil]; NSDictionary *dictionary101 = [NSDictionary dictionaryWithObjects:objects101 forKeys:keys101]; NSArray *keys102 = [NSArray arrayWithObjects:@"101",@"104",@"104", nil]; NSArray *objects102 = [NSArray arrayWithObjects:action2,action3, action1, nil]; NSDictionary *dictionary102 = [NSDictionary dictionaryWithObjects:objects102 forKeys:keys102]; NSArray *keys = [NSArray arrayWithObjects:@"101",@"102", nil]; NSArray *objects = [NSArray arrayWithObjects:dictionary101,dictionary102, nil]; self = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; // for (id key in self) { // NSLog(@"key: %@, value: %@", key, [self objectForKey:key]); // } return self; } @endThe next part is in my motionlayer. Will just show the changes from standard. Firstly in the MotionLayer.h
#import "SpriteActions.h" @interface MotionLayer : Layer { SpriteActions * spriteActions; } @property (nonatomic, retain) SpriteActions * spriteActions;In the MotionLayer.m
@synthesize spriteActions; - (id) init { self.spriteActions = [[SpriteActions alloc] init]; } -(void) moveAISprite { // Unschedule the selector [self unschedule:@selector(moveAISprite)]; currentSpriteNumber = (arc4random() % [activeSprites count]) +1; MySprite *c = [activeSprites objectAtIndex:currentSpriteNumber-1]; NSString * originalLocation = [NSString stringWithString:[c GetLocation]]; // Please note that MySprite class has a property for location - the same locations that are referenced for the actions. if(![c ready]) { id action1 = [self nextActionForSprite:c]; id action2; while (![c isSpriteAtDestination]) { action2 = [Sequence actions: action1, [self nextActionForSprite:c], nil]; action1 = action2; } id action = [Sequence actions: action1, nil]; [c runAction: action]; [pathSlotManager setSlot:originalLocation empty:YES]; [pathSlotManager setSlot:[c GetLocation] empty:NO]; } } - (id) nextActionForSprite:(MySprite *)c { id actionDictionary = [self.spriteActions objectForKey:[c GetLocation]]; NSArray *keys = [actionDictionary allKeys]; int pathchoice=(arc4random() % [keys count]); NSString *newKey = [keys objectAtIndex:pathchoice]; int pathFailureNo = 0; while (![pathSlotManager isSlotEmpty:newKey]) { if (pathFailureNo++ < 10) { NSLog(@"TRY AGAIN"); pathchoice=(arc4random() % [keys count]); newKey = [keys objectAtIndex:pathchoice]; } else { for (newKey in keys) { if ([pathSlotManager isSlotEmpty:newKey]) break; } } } id action1; action1 = [actionDictionary objectForKey:newKey]; [c SetLocation:newKey]; return action1; }Okay, will explain a bit better next post.
Posted 2 years ago # -
Okay. The SpriteActions class stores all your actions - the useful thing for me is that I have about 20 - 30 points that my sprites can move to and each point has between 1 and 3 points that the sprite can move to from there. The useful part is that a lot of the actions to move between points are the same. So this code reuses the actions.
The nextaction stuff uses randoms etc to select a random path from each point. I have put in code there to see if the next point is busy - and if so try again. pathSlotManager is a subclassed NSDictionary that just returns YES or NO depending on the state of a point. So that if there is a sprite already in the point - then it returns NO (no the slot is not available/empty) and the code tries again.
Its all pretty straight forward - but works for me. I have a number of happy sprites moving around the place, and looking very intelligent - until they get off path - but that is just a bug in one or two of my actions :P
Let me know what you think.
Justin
Posted 2 years ago # -
Hey Justin,
That looks great and i might be able to make it work for my game. I am getting a couple of warnings and errors.First warning is c may not respond to -GetLocation and -SetLoaction. Is this just an NSString inside the MySprite Class? did you make a GetLocation and SetLocation Method then as well?
Second im getting warning: 'ActiveLayer' may not respond to '-nextActionForSprite'
And Third im getting: error: 'pathSlotManager' undeclared 9first use in this function)
Thanks a lot
-LarsPosted 2 years ago # -
Hello Lars
Yes location is just an NSString.
- (void) SetLocation:(NSString *) val{
currentLocation = val;
}- (NSString *) GetLocation{
return currentLocation;
}Add the following to your ActiveLayer.h
- (id) nextActionForSprite:(MySprite *)c;Comment out the pathSlotManager for the moment... It is just a class that returns a YES/NO depending on the slot status.. Plus allow you to change that state. Can post that code later.
Justin
Posted 2 years ago # -
Ok so I am sorry im having problems its probably just because im tired. I should of noticed that i should add the - (id) nextActionForSprite:(MySprite *)c; to the .h. but anyways I still have a couple problems first:
under nextActionForSprite the line id actionDictionary = [self.spriteActions objectForKey:[c GetLocation]]; i get two errors:
1: error: type of accessor does not match the type of property 'spriteActions'
2: error: cannot convert ot a pointer typeand then you have while (![pathSlotManager isSlotEmpty:newKey])
and im getting the pathSlotManager undeclared of course. What should i do there sincce thats what choses the next action.Again i appologize that im having these problems is 1:30 am here so im a little tired
Thanks
-LarsPosted 2 years ago # -
Hey mate no worries.
1/2. You are probably missing a "*" some where! I think.
second bit - we can override so it runs once.
bool stopWhile = NO;
while (!stopWhile)
{
stopWhile = YES;
}You might need to drop the !
Hope that works, I am on WST - so it is 2.43pm here!
Justin
Posted 2 years ago # -
Hey so its compiling now but it crashes right away! Heres the report:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[PlayerSprite ready]: unrecognized selector sent to instance 0x53b8600'
The thing is that its not PlayerSprite it should be EnemySprite. Ive gone through the code multiple times now but ive changed everything to EnemySprite. any ideas?
Thanks again
-LarsPosted 2 years ago # -
Okay so good old late nights got me again i just made a stupid mistake but I am still getting an crash. its crashing at:
int pathchoice = 9arc4random() % [keys count]);I dont get anything in the logs right away but if i hit continue in the debugger i get:
Current language: auto; currently objective-c
Program received signal: “EXC_ARITHMETIC”.Do i have to set location anywhere first?
Thanks
-LarsPosted 2 years ago # -
I hope you mean:
int pathchoice=(arc4random() % [keys count]);is the 9 just a typo on the message?
You can override the pathchoice - just to test it.
Replace with:int pathchoice = 0;
Justin
Posted 2 years ago # -
If the 9 is a typo and this line still throws an exception, my guess is that the keys collection is empty, i.e. [keys count] is returning 0 and the mod arithmetic throws the exception.
Posted 2 years ago # -
True.
After the line:
NSArray *keys = [actionDictionary allKeys];
add the following line:NSLog(@"KEYS: %@", keys);
and see what you get.
Justin
Posted 2 years ago # -
Hey ok so i added the NSLog and it returns (null) and if i change it to int pathchoice = 0; it gets stuck at the second pathchoice = (arc4random() % [keys count]);
And yeah the 9 was just a typo. but do i need to set currentLoaction anywhere? first?
Thanks
-LarsPosted 2 years ago # -
Yes, I set that value on the MySprite when it is init'ed. If you are still having issues, let me know - and maybe we can work something out.
Justin
Posted 2 years ago # -
Hey Justin
So in what format did you set location? just using @"150,150" or what?
Thanks again
-LarsPosted 2 years ago # -
The location is just a label - in this case a NSString.
So lets say we have 3 locations, @"101", @"102", and @"103".
101 might be ccp(10,20)
102 might be ccp(20,30)
103 might be ccp(30,40)I have extra code in there, that allows me to call [globalSlotManager slotPosition:@"101"];
that will give me the CGPoint for the slot. Or slotRect for the CGRect. Or slotRotation for the rotation :)There is a lot of stuff in there sorry, was hoping there was enough there for you to get started.
Justin
Posted 2 years ago # -
Hey,
So I added the location and now it shows this in the log2009-07-26 20:10:47.758 SnowFight[45816:20b] KEYS: (
101,
104
)[Session started at 2009-07-26 20:10:47 -0500.]
2009-07-26 20:10:47.759 SnowFight[45816:20b] KEYS: (
102,
103
)
2009-07-26 20:10:47.760 SnowFight[45816:20b] KEYS: (null)and then it crashes at int pathchoice=(arc4random() % [keys count]);
I appologize that this is taking so long
-LarsPosted 2 years ago # -
Okay, I think that is coming through on the second hit - so it has gone to the first point and then branched - and come up to the second.
Looks like this could take a while, due to me not shelling out all the code! Are you able to email me your project or put it somewhere - and I will retro-fit the code in? Obviously you will probably want it to be someone non-public - to keep your ideas safe. Email me off list - justin at my domain.
Otherwise I could probably cut my project back to a working demo - but I am not sure I have the time at the moment.
Justin
Posted 2 years ago #
Reply
You must log in to post.