Thursday, December 13, 2007



After some "deliberation", my scissor creatures are now 3 times as large, and 10 times as few. Doing this allowed me to involve more complex sense and think routines as many many little scissors quickly bogged down the engine.

The perception function, only searching for the player, implements a distance check, uses the dot product to determine if the player is in front, and a ray cast to see if the player is not obstructed to determine if we can actually see the player. While this works very well for initial perception of the player, it can be easy to deceive the AI by running behind it. To overcome this, I crafted the logic so that when we have been following the player, and they run out of the field of view, we simply check the distance again and maintain pursuit if the player is close enough (most of the time).

When the AI is not chasing down the player, it will follow predetermined paths. Currently, I have 4 of these paths, with two AI spawned on each. Because of this, and the way that the AI move (quick, but slow to stop) they seem to race around some invisible track. In a way, this comes across as a game that the scissors play normally. The screen shot below shows one of these paths with the AI running about.



Within the AI, I have 4 important functions.

  • The first, "think", is a scheduled routine that calls the sensory "checkFOVPlayer", then carries out the simple finite state automata. The FSA has two states, following the player, and not following the player. The states are guided simply by whether or not the AI can "see" the player (or if it has "forgotten" where the player is, see above).

  • The second, "checkFOVPlayer", determines if the player is in the field of view of the AI. It is explained in some detail above, and is included below.

  • The "onCollision" callback event is being used for attacking the player. When the AI rams into the player, it will push them both apart and damage the player. Very simple response, but it is effective.

  • The last, "onReachDestination", is another callback which is triggered when the engine finishes moving the object to its destination. When this occurs, and the AI is not following the player (running the track), it will iterate to the next node and let the engine move us to the next node.




function scissorbody::checkFOVPlayer(%this,%obj) {
%line = VectorSub(%obj.player.getPosition(),%obj.getPosition());
//%dist = VectorLen(%line);
%dist = manhattanDistance(%line);
%cantsee = true;
if (%dist < dot =" VectorDot(%obj.getEyeVector(),%line);"> 0.0) {
%typemask = %TypeMasks::TerrainObjectType & $TypeMasks::StaticShapeObjectType;
if (ContainerRayCast(%obj.getPosition(),%obj.player.getPosition(),%typemask) $= 0) {
%obj.setMoveDestination(%obj.player.getPosition(),true);
%obj.followPlayer = true;
%cantsee = false;
}
}
}
if (%cantsee && ($dist > 30)) {
%obj.followPlayer = false;
%obj.stop();
}
}

Monday, December 3, 2007

Scissor AI

Before I talk about AI and my current progress, I'll say that I made myself a nice little function to warp my character around the map by right clicking on it. Nothing big, but it sure is helpful.







Alright, so the main antagonist to the boxman character is a race of scissor people. Fitting, right? Now, they don't need to be all that intelligent, basically just chase the player out of their area and chop him up. Ancillary behaviour is to just roam around (with some range of speed to establish different amounts of "determination") and probably have them sometimes fight amongst themselves, as they are vicious scissors. I would also like them to have a very simple learning scheme where they are capable of mapping limited states to good or bad outcomes (mostly just to learn that walking into water is bad, and possibly that a player carrying a rock is bad). I figure I can establish this by having limited state knowledge (where am I? is the player nearby? does the player have a weapon?), then by examining immediate outcomes, downplay states with negative outcome for a short time.

As it stands now, I've got some simple reflex agents that will watch for the player and charge them when they show up in the line of sight. The problem is that I'm using built in functions that set the aim object, which just happens to cause the AI to literally stare at the player no matter where they are. This is not the behaviour I want, they cannot be aware of the player at all times, they must happen to see the player then charge. To do this, I will have to build my own sight sensor function.







This morning, I was toying around with placement of the scissor race. I placed a marker out in the field and randomly placed n(100 now) scissor people around this marker. I soon found that when I let them all come at me, an interesting cluster pattern appears due to the collision amongst them. While this looks pretty cool, the collision also causes the player to become stuck. Immediately I thought I could overcome this by removing the collision between player objects. Of course, this caused the AI to combine on the field. It also did something crazy to the dynamic shadows produced for the AI shapes. You can see in the image that there is a yellow glow on the shadow. This is certainly not a desired effect, so I believe I will have to do something else to fix the sticking of the player. I'm thinking to just have the scissor people bounce off of the player, or the player from the AI or both.

Thursday, November 8, 2007

Main menu

Ok, so this isn't a huge update, but there's some interesting things to say.

I changed my main menu from using plain old buttons to shiny bitmap buttons, and then had the brilliant idea to add a sprite animation just because I could. I made some little sprites of my box man walking (from the side, flipped horizontally instead of rendering twice for each step) and standing still (facing forward), so I could make him walk to the button you were hovering over.

Actually, I started out wanting to make an animated cursor. So that he would "walk" as you moved left and right. But the only way I could get the mouse position was to place a GuiMouseEventCtrl over the entire gui, which effectively blocks all other items. That's when I decide to just have him walk at the bottom by the buttons.

To do this, I needed some of those GuiMouseEventCtrls to tell me when I moused over the buttons (the buttons themselves don't lend you any events). I found that if I nested the buttons inside the controls, I could get them both to work just fine. So, I used the controls to set the desired x position of the boxman (it's relative to the screensize, so it won't go nuts if you use a different resolution).

For the animation, I have a scheduled function that runs every 50 miliseconds (shown below) that checks the sprite's x position (held in a global variable, oh my!) to the desired x position and decides whether he should be walking somewhere or not. The animation is done via numbered sprites and string concatenation, and a counter. Since I only have 4 sprites per animation, I crafted another counter so that the sprite step would occur every 10 times this function runs.

Anyway, here's the code and a picture.


$mouseAnim = 1;
$mouseXPos = 60;
$animCounter = 10;

function mouseUpdate() {
if ($mouseXPos > 0) {
%boxPos = GetWord(BoxManBitmap.position,0);
%boxYPos = GetWord(BoxManBitmap.position,1);
if (mAbs(%boxPos - $mouseXPos) < position =" SetWord(BoxManBitmap.position,0,%boxPos+5);"> $mouseXPos) {
BoxManBitmap.position = SetWord(BoxManBitmap.position,0,%boxPos-5);
BoxManBitmap.setBitmap("ChadGame/client/ui/gfx/BoxManWalk_L"@$mouseAnim@".png",false);
}

$animCounter -= 1;
if ($animCounter < animcounter =" 10;" mouseanim =" ($mouseAnim-1)%4+1;" mousexpos =" 60;" mousexpos =" -1;" pos =" VectorAdd(playArea.position,VectorScale(playArea.extent,.5));" mousexpos =" GetWord(%Pos,0)-60;" pos =" VectorAdd(optionArea.position,VectorScale(optionArea.extent,.5));" mousexpos =" GetWord(%Pos,0)-55;" pos =" VectorAdd(quitArea.position,VectorScale(quitArea.extent,.5));" mousexpos =" GetWord(%Pos,0)-40;">

Thursday, October 25, 2007

Catching up on Cardboard Land

It might be useful to explain this project for anyone trying to figure out what my various mind dumps are all about.

Cardboard Land is a simple game where you are a cardboard man living in a world of cardboard. It is up to you to venture out from the safety of your village to find supplies. You must find as many as possible across the lands. These supplies are objects such as; glue, staplers, tape, paper clips, etc.. On your adventure you will encounter disastrous adversaries, such as; scissor people, water, tornados, huge piles of chewed gum, and possibly fire people.

Simple enough, I suppose. Here are some of my favorite screenshots so far.











Stay tuned for more real information as it occurs.

Tuesday, October 23, 2007

Tornados in Cardboard Land

I started working on a wind area trigger within torquescript, and ended up creating a tornado because that is a fairly interesting effect to recreate. As this is a fairly short script, I'll just paste it here with all my comments and possibly add some stuff.
The flow is this: I create a trigger of my defined type, which should call the onAdd callback to initialise my tornado shape and particle emitter. However, somehow the onAdd isn't being called, so I'm just doing it explicitly. There are two main features of this script, a loop that runs every 50 ms when a player is intersecting my trigger, which takes the difference in positions between the player and trigger, then uses that information to build up some impulse vectors to pull the player in, and spin him around the z axis of my trigger. The second loop is independent of intersection, and is used to move the object around in a random manner. I created a momentum vector for the XY plane to keep the thing from just jumping around, and devised an "adjustment" vector to pull it towards the initial position if we stray too far from there.

Here are the two important functions:

function WindTrigger::onTickTrigger(%this,%trigger) {
%max = %trigger.getNumObjects();
for (%n=0;%n<%max;%n++) {
%obj = %trigger.getObject(%n);
if (%obj.getClassName() $= "Player") {
//find XY distance to player
%pos = VectorSub(%obj.getPosition(),%trigger.getWorldBoxCenter());
%posX = getWord(%pos,0);
%posY = getWord(%pos,1);
%dist = VectorLen(VectorNormalize(setWord(%pos, 2, "0.0")));
//calculate circular tangent vector and apply centripetal force to maintain circular movement
%windX = -%posY - 5*%posX;//mSin(%posX)*mCos(%posY);
%windY = %posX - 5*%posY;//mSin(%posY)*mCos(%posZ);
%windZ = 10;//mSin(%posZ)*mCos(%posX);
%obj.applyImpulse(%obj.getPosition(),VectorScale(%windX SPC %windY SPC %windZ,100*(1.5-%dist)));
}
}
Parent::onTickTrigger(%this,%trigger);
}

function WindTrigger::moveMe(%this,%trigger) {
//get our scale
%scale = %trigger.getScale();
%scaleX = getWord(%scale,0);
%scaleY = getWord(%scale,1);

//compute an adjustment vector to apply on top of
//our movement to keep within some distance of original position
%pos = %trigger.getPosition();
%adjustment = VectorSub(%trigger.startPos,%pos);
%adjDist = VectorLen(%adjustment);
%adjustment = VectorNormalize(%adjustment);
%adjX = 0;
%adjY = 0;
//stay within 25 units of original position (or try to)
if (%adjDist > 25) {
%adjX = getWord(%adjustment,0);
%adjY = getWord(%adjustment,1);
}

//random(-.25,.25) with a step of .01
%trigger.mvX += getRandom(-25,25)/100.0 + %adjX;
%trigger.mvY += getRandom(-25,25)/100.0 + %adjY;
//cap movement to [-1,1]
if (%trigger.mvX <= -1)
%trigger.mvX = -1;
if (%trigger.mvX >= 1)
%trigger.mvX = 1;

if (%trigger.mvY <= -1)
%trigger.mvY = -1;
if (%trigger.mvY >= 1)
%trigger.mvY = 1;

//find our new position
%posX = getWord(%pos,0)+%trigger.mvX;
%posY = getWord(%pos,1)+%trigger.mvY;
%posZ = getTerrainHeight(%posX+5 SPC %posY-5);

//move trigger
%trigger.position = (%posX) SPC (%posY) SPC %posZ;
%trigger.setScale(%trigger.getScale());

//adjust to center for inner objects
%posX += %scaleX/2;
%posY -= %scaleY/2;
%pos = %posX SPC %posY SPC %posZ;

//update StaticShape and ParticleEmitter positions
%trigger.myTornado.position = %pos;
%trigger.myTornado.setScale(%trigger.myTornado.getScale());
%trigger.myParticles.position = %pos;
%trigger.myParticles.setScale(%trigger.myParticles.getScale());

//make sure we get called again
%this.schedule(100,"moveMe",%trigger);
}

It works pretty well, but I haven't added anything to turn off all this simulation when the player is not near the tornado area. I think I may just put a large trigger around the area to enable/disable the tornado set when the player enters and leaves the area. I'll have to play around with this to see if it is sufficiently useable, otherwise I might need to turn down the delays so it doesn't use up as much resources.

Primer

Well, I guess I should start documenting my development in ways other than filling up my desk with post-it notes. So, this place is as good as any. I believe I will start with my Cardboard Land game, and just go from there instead of trying to catch up (and remember various thoughts that have slipped through the cracks). So stay tuned, listeners (haha, radio) for information will establish itself right here.