Tuesday, 21 April 2020

Meta game to test strategies

Meta – adjective (applied to a creative work) referring to itself or to its genre.

In the context of games there is a usage that means “Most Effective Tactics Available”.



I wanted to explore some strategies for Non Player Characters (NPCs) chasing the player in a game. To avoid you having to read the chapter in my book on Ray Casting I created a simple game world where the only nods to three dimensions are that sprites are rendered from low Y positions to high (from back to front) and by giving static sprites a rectangular section that can block the progress of the player or NPC. The player and NPCs are represented by sprites that have a “South Park” style of movement.

The player is entirely dependent upon key strokes to avoid the NPCs (only the forward, left and right arrows). As a small aid to the player, the player sprite shows a small red tell-tale to indicate its current rotation and thus future direction. The player sprite leaves a trail as it moves and this can be used by the NPCs to track down their quarry.

This meta game is called “Paper Chase” as it has some of the characteristics of the outdoor game of that name that used to feature in yarns for schoolboys (and maybe real life for some). Sometimes it was called “Hare and Hounds” or “Fox and Hounds” and the code reflects that by calling the player object a fox and the NPCs Hounds.

The NPCs can draw on a range of methods to decide upon how they progress over the game surface to try and catch the player. In the initial set-up for the demo the NPC sprites show a coloured circle at their base that indicates the method currently being executed.

The demo can be played here.

The source code can be downloaded from GitHub.

The NPC methods:

Casting about. As an initial state the Hounds just wander about locally hoping to get sight of the fox or the trail left by the fox. If a hound gets stuck at any time in the game it can revert to this behaviour.

Pursue. If the hound can see the fox then it will move directly towards the fox. The hounds will lose sight of the fox if there is a static sprite between them.

 Pick up the trail. If a hound can see the fox’s trail but not the fox then the hound will move towards the trail. The code uses a Linked List object to locate a point on the trial that is both “visible” and closest to the fox.

Follow the Trail. Once a hound has reached the fox’s trail then the trail will be followed until the hound can “see” the fox.

Experimentation:

If you download the code from the GitHub repository there is a lot you can experiment with. The length of the fox’s trail, the number of hounds and the complexity of the game area (try adding more static sprites). The relative speeds of the fox and hounds can be adjusted as could the rotational speed of the fox in response to keystrokes.

Would another strategy work for the hounds? The weakest strategy is the “casting about” although that rarely lasts for more than a second or two – certainly if the fox gets moving.

The code:

There is a Sprite object that has an image and an x/y location. This location is the mid-point of the sprites base and is not the x/y location used to draw the sprite on the <canvas> which is calculated when required when the Sprite’s render() method is called. This basic Sprite object is used for all static sprites.

The more mobile sprites are based upon the Actor object which inherits from the Sprite object. The image used for each Actor object has four elements showing a front, left, right and back view. When an Actor object’s rotation value is set then the object calculates image element to display by choosing the one closest to the rotation value. The Actor object render() method manages the drawing of the chosen image component and then calls an animate() method. The animate() method makes a call to a getDirections() method which is the one that implements one of the four chase strategies  used by the Hounds. The Actor object also has a collisionDetect() method that checks that the relevant sprite can move to a given location. A sprite may be blocked by collisions with the lower portion of a static Sprite or with a circle surrounding the fox and hounds.



The Hound objects inherit from the Actor object to add a few members specific to the role. The hunting object holds the functions that the Hound getDirection() method points to. These are switched as required to facilitate the chase.

Useful functions include:

getAngleIndex4() which is passed the rotation value for the fox or a Hound (divided by Math.PI) and returns an index value (0 to 3) that is the closest match.

findpointOnCircle() will be very familiar to readers of my book as it calculates the x/y coordinates of a point on the circumference of a circle at a given angle.

circIntersect() broadly checks if two circles intersect but will also return true if one is inside the circumference of the other.

lineIntersectsRect() is a bit of a heavy hitter but does what it says and checks if a line intersects with a rectangle. It was a nice opportunity to try out the “destructuring assignment” to swap the values contained in two numeric variables. Note though, that the rectangle is assumed to be aligned with the coordinate system.

Collection Objects:

The code makes use of a (partly implemented) Linked List object and (gratuitously perhaps) the Set for objects thingy I blogged recently.




No comments:

Post a Comment