Monday, 29 December 2014

(SOLVED) Can You Find the Bug?

I'll leave this here for posterity...

The problem was not with the Vector method, but with the caller. I was casting the angle of roundAngle() to int instead of using the math::round function. D'oh!


This is supposed to constrict the vector to 45 degree slots. Only 2D, ignore the z component. The (rounding?) bug occurs when the correct answer is 180 and -90; this algorithm returns 179 and -89 respectively. Why?!

    // ----------------------------------------------------
    //   round the angle to nearest x degrees
    // ----------------------------------------------------
    Vector3 Vector3::roundAngle ( int nearest_angle )
    {
            // vector to return
            Vector3 rounded;

            // convert to radians
            double nearest_radians = RADIANS ( nearest_angle );

            // get the angle of this vector
            double angle =  atan2 ( this->y, this->x );

            // remainder between 2 angles
            double remainder = std::fmod ( angle, nearest_radians );

            // if there is a remainder, do the rounding
            if ( remainder != 0 ) {

                    double new_angle = round ( angle / nearest_radians ) * nearest_radians;

                    rounded.x = cos ( new_angle );
                    rounded.y = sin ( new_angle );
            }else{
                    rounded.x = this->x;
                    rounded.y = this->y;
                    rounded.z = this->z;
            }

            if ( fabs ( rounded.x ) < TOL ) rounded.x = 0;
            if ( fabs ( rounded.y ) < TOL ) rounded.y = 0;
            if ( fabs ( rounded.z ) < TOL ) rounded.z = 0;

            return rounded.normalised();
    }

Friday, 26 December 2014

RIP

The Amiga always had better graphics and sounds, but the first time I loaded a new season on PC, I knew its days were numbered!


Dad's IBM Thinkpad back in 1994. I installed Day of the Tentacle and Championship Manager 93 and I was hooked!

Wednesday, 24 December 2014

Homepage

Here's a website for the game (if it's ever finished). Amazing what you can do with css these days.

 Senseless Soccer Homepage


Fixed Delta

Fixed as in "constant"... the engine now has a fixed time step (delta) for the physics simulations. Can you tell the difference?



Basically, the game step tries to run at 60 frames per second. The physics and rendering are decoupled and each can concede time back to one another. This means that the physics step may be executed more than once per frame, but always with the same time delta.

Monday, 15 December 2014

Words of Wisdom

"Having one less character to type is NOT a good reason to prefer float over double..."

The extra precision can surprise you by fixing a lot of weird buggy stuff that you hadn't managed to trace back to floating point precision and, unless you're targeting some really specific old hardware or trying to simulate the universe or something, doubles don't cost you anything so might as well use em!

That's the problem with taking examples from badly written books - when your focus is on learning a bit of path-finding or something and the fact that the author riddled his code with floats just doesn't register!

Out of interest, switching to double fixed a few bugs like the rounding of vector angles to their nearest 45 degrees.

Wednesday, 10 December 2014

Under the Hood

The past few late nights have been spent splitting the project into reusable components. It was already structured that way but now we have a shared game library that can be linked to for use in future games.

The lib can be linked statically or dynamically.

An example of the line between the game and the lib is the Player Sprite. The Player Sprite is part of the game and inherits from Sprite which is part of the generic library. Sprite then inherits from a generic Renderable object.

The lib also contains stuff like math tools (vectors), physics, input and Window management. Anything that could be reused for another game really!

P.S. Finally made the jump from bog-standard make to cmake for the shared library. It's just as cryptic but a little easier to use I guess!

Thursday, 4 December 2014

Joystick Input

For future integration in-game, interface tweaked for joystick input (like SWOS).

Moving the joystick highlights different players or the ball (flashing border), pressing fire locks on to the currently selected object(fixed border) to be able to move it around. Pressing fire again unlocks and returns to the "select object" mode.



Monday, 1 December 2014

Quick Tactics Editor in Java



Utilizing the simple grid system from the game, this makes a nice tool for visually setting up tactics and player positions for different situations.

Each player icon represents a player position structure, which is basically just an ordered list of numbers specifying which sector the player should try to get to for each ball sector. See this post for info.

Positions can be created, copied, imported and exported so a full tactic is simply a collection of 10 unique positions in a group.

In future, we'll add more detailed tactics and strategies like passing style, positions for set pieces, marking style etc.

Sunday, 30 November 2014

Invisible But Substantial!

Here's an invisible but quite substantial update. There are 2 improvements regarding the player sprite sheet graphic.

1. Instead of loading 20 (1 for each player) textures into memory, the player sprites now all share the same texture.

2. Instead of having the team colors in the image, thus requiring a whole  new sprite sheet image for each different kit color, the sprite sheet is key coded and the colors are changed in-game via a palette system.

Next we need templates for different kit designs.

This will allow for changing/creating kits in game.

Implementation of the shared texture is rather trivial. We let the team own a shared texture object and simply point all player renderable textures to this.

offside!
For the color palette, we make a list of pairs indicating the old color (from png image) and the new color to replace it with. For example, replace all instances of RGB(255,0,0) with RGB(0,0,255). Add a pair for each of the shades of red, and this results in the above sprite sheet magically changing to a blue kit... w00t!

Monday, 24 November 2014

Z-Order

A small but nifty enhancement: graphics are rendered in the correct order from the camera perspective. For example, here's the ball going "behind" the player instead of always on top.

Saturday, 22 November 2014

Thursday, 20 November 2014

Player Values

This is an interesting one.

The Daily Mail reckons that, adjusted for inflation, an Alan Shearer in his prime would be worth 72 million squids today.

The sensi soccer algorithm valued him at 70.9 mil.

For Sheringham, DM says 28 million, sensi calculated 27.1 mil!

Ian Wright: 46mil vs 41.2mil.

Spooky!

The biggest discrepancy is the Mail valuing Robbie Fowler at 36 mil whilst sensi says 67.7 mil. Obviously sensi got it right - simply the greatest goalscorer of all time.



Thursday, 13 November 2014

Wednesday, 12 November 2014

Monday, 10 November 2014

First Goalie Save!

Sidetrack

Gave this a quick go on an impulsive whim - real time zooming feature. Worked out better than I thought! It might be interesting to play with for goal celebrations or replays... but tbh it was more a proof of concept for an idea I want to implement in my next project! ;)

 

Sunday, 9 November 2014

Small Update

- new pitch type
- tweaked ball physics
- goalkeeper diving progress
- perfect A.I. for current Liverpool back 4

 

Monday, 13 October 2014

Another Sidetrack

In preparation for a home arcade machine, and since Hyperspin doesn't work on GNU/Linux, I've hacked together my own mame launcher:


Alternate theme for large game sets:

Wednesday, 21 May 2014

Thursday, 8 May 2014

And Finally

To conclude our little foray into h4X0R1ng, we might as well write a bit of code to output the data we've gathered so we go from this:




(yes, this is the Arsenal team in the original game format, with the hex editor translations on the right - big help, eh?)
to this:



Having already done the hard part to rip the data, the code for saving as plain text is trivial:

1:  void write(std::vector&lt; TEAM &gt; team, const std::string&amp; filename)  
2:  {  
3:      std::ofstream out_file;  
4:      out_file.open (filename.c_str());  
5:        
6:        
7:      out_file &lt;&lt; "Team Format: [NAME] [COUNTRY CODE] [ID] [DIVISION] [MANAGER]"  
8:      &lt;&lt; std::endl;  
9:      out_file &lt;&lt; "Player Format: [NAME] [POSITION] [PASSING] [SHOOTING] [HEADING] [TACKLING] [BALL CONTROL] [SPEED] [FINISHING]"  
10:      &lt;&lt; std::endl  
11:      &lt;&lt; std::endl;  
12:        
13:      for(unsigned int team_count=0; team_count LESS_THAN team.size(); team_count++){  
14:          out_file   
15:          &lt;&lt; "------------------------" &lt;&lt; std::endl  
16:            
17:          &lt;&lt; team[team_count].name  
18:          &lt;&lt; " " &lt;&lt; (int) team[team_count].id  
19:          &lt;&lt; " " &lt;&lt; (int) team[team_count].nation  
20:          &lt;&lt; " " &lt;&lt; (int) team[team_count].division  
21:          &lt;&lt; " " &lt;&lt;    team[team_count].getCoachString().c_str() &lt;&lt; std::endl  
22:            
23:          &lt;&lt; "------------------------" &lt;&lt; std::endl  
24:                    
25:          &lt;&lt; std::endl;  
26:            
27:          for(unsigned int player_count=0; player_count LESS_THAN PLAYERS_PER_TEAM; player_count++){  
28:              out_file &lt;&lt; team[team_count].players[player_count].name  
29:                
30:              &lt;&lt; " " &lt;&lt;    team[team_count].players[player_count].getPositionString()  
31:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].passing  
32:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].shooting   
33:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].heading   
34:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].tackling   
35:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].control   
36:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].speed   
37:              &lt;&lt; " " &lt;&lt; (int) team[team_count].players[player_count].finishing   
38:                
39:              &lt;&lt; std::endl;  
40:          }  
41:            
42:          out_file &lt;&lt; std::endl &lt;&lt; std::endl;          
43:      }  
44:        
45:      out_file.close();  
46:  }  

Out of Interest

Here's how a non l33t h4X0R might knock up a quick program to get the data out of the sensi dat files. It could be optimized, cleaned up, and generally written much better. But it gets the job done and that's all we care about for a one-shot program!
1:  // number of players ineach team  
2:  const unsigned int PLAYERS_PER_TEAM = 16;  
3:  // number of characters in player name  
4:  const unsigned int PLAYER_NAME_LENGTH = 23;  
5:  // number of characters in team name  
6:  const unsigned int TEAM_NAME_SIZE = 19;  
7:  // number of characters in coach name  
8:  const unsigned int COACH_NAME_SIZE = 25;  
9:  struct PLAYER  
10:  {  
11:  SNIP!   
12:  };  
13:  struct KIT  
14:  {  
15:  SNIP!  
16:  };  
17:  struct TEAM  
18:  {  
19:  SNIP!  
20:  };  
21:  int main(int argc, char *argv[])  
22:  {  
23:      // must be 2  
24:      if ( argc != 2 )   
25:      {  
26:          std::cout << "ERROR:"              << std::endl;  
27:          std::cout << "----------------------------"   << std::endl;  
28:          std::cout << "usage: sensi_data <filename>"  << std::endl;  
29:          std::cout << "----------------------------"   << std::endl;  
30:          return 1;  
31:      }  
32:      // a list to store the teams  
33:      std::vector< TEAM > all_teams;  
34:      // open the teams data file as an input stream  
35:      std::ifstream input (argv[1], std::ios::in | std::ios::binary);  
36:      // team file data starts at byte 1  
37:      int byte_index = 1;  
38:      // stream input into this  
39:      unsigned char x;  
40:      // vector to store all bytes (loading them in to this first makes it easy to  
41:      // inspect the bytes in the debugger)  
42:      std::vector<unsigned char> bytes;  
43:      // tell the input stream NOT to skip anything (white spaces etc)  
44:      input >> std::noskipws;  
45:      // load the bytes into a vector  
46:      while (input >> x) {  
47:          bytes.push_back(x);  
48:      }  
49:      // loop controllers to parse all teams  
50:      int current_team = 1;  
51:      // the second byte in the file gives us the number of teams in the file  
52:      int number_teams = bytes[byte_index++];      
53:      // main loop for each team  
54:      while (current_team++ < number_teams){  
55:        // store the ripped data in a TEAM struct  
56:        TEAM team;  
57:        // unknown byte at start of each team (wtf?)  
58:        byte_index++;          
59:        team.nation   = bytes[byte_index++];      // team country code          
60:        team.id     = bytes[byte_index++];      // team id  
61:        team.id2    = bytes[byte_index++];      // unknown id  
62:        team.x1     = bytes[byte_index++];      // unknown byte  
63:        for(int i=0;i<TEAM_NAME_SIZE;i++){  
64:            team.name[i] = bytes[byte_index++];   // team name   
65:        }          
66:        team.tactics  = bytes[byte_index++];      // tactics code  
67:        team.division  = bytes[byte_index++];      // division  
68:        // -------------------------------------------  
69:        // KITS  
70:        // -------------------------------------------  
71:        team.kit1.type = bytes[byte_index++];      // kit 1 type  
72:        team.kit1.col1 = bytes[byte_index++];      // kit 1 main color  
73:        team.kit1.col2 = bytes[byte_index++];      // kit 1 secondary color  
74:        team.kit1.shorts= bytes[byte_index++];      // kit 1 shorts color  
75:        team.kit1.socks = bytes[byte_index++];      // kit 1 socks color  
76:        team.kit2.type = bytes[byte_index++];      // kit 2 type  
77:        team.kit2.col1 = bytes[byte_index++];      // kit 2 main color  
78:        team.kit2.col2 = bytes[byte_index++];      // kit 2 secondary color  
79:        team.kit2.shorts= bytes[byte_index++];      // kit 2 shorts color  
80:        team.kit2.socks = bytes[byte_index++];      // kit 2 socks color  
81:        // -------------------------------------------  
82:        // END KITS  
83:        // -------------------------------------------  
84:        for(int i=0;i<COACH_NAME_SIZE;i++){  
85:            team.coach_name[i] = bytes[byte_index++];// coach name  
86:        }          
87:        for(int i=0;i<15;i++){  
88:            team.x2[i] = bytes[byte_index++]; // 15 bytes of unknown data (what is this FOR?!)  
89:        }  
90:        // -------------------------------------------  
91:        // EACH PLAYER IN THE TEAM  
92:        // -------------------------------------------  
93:        for(unsigned int p=0;p<PLAYERS_PER_TEAM;p++){  
94:            team.players[p].nat = bytes[byte_index++];  // nationality  
95:            team.players[p].x1 = bytes[byte_index++];  // unknown byte  
96:            team.players[p].numb= bytes[byte_index++];  // shirt number  
97:            for(unsigned int i=0;i<PLAYER_NAME_LENGTH;i++){  
98:                team.players[p].name[i] = bytes[byte_index++];  // name  
99:            }  
100:            // ---------------------------------------------------------------  
101:            // PLAYER POSITION  
102:            //  
103:            // this byte stores the players position and skin/hair colour  
104:            // first 3 significant bits signify position as follows:  
105:            //   000 = goalkeeper  
106:            //   001 = right back  
107:            //   010 = left back  
108:            //   011 = defender  
109:            //   100 = right wing  
110:            //   101 = left wing  
111:            //   110 = midfielder  
112:            //   111 = attacker  
113:            //  
114:            // the next 2 bits signify skin/hair color:  
115:            //   00 = white skin/black hair  
116:            //   01 = white skin/blonde hair  
117:            //   10 = black skin/black hair  
118:            //   11 = unknown  
119:            team.players[p].position = (bytes[byte_index++] >> (5)) & 0xff;  
120:            //  
121:            // ---------------------------------------------------------------  
122:            team.players[p].x3     = bytes[byte_index++];// unknown byte  
123:            // ---------------------------------------------------------------  
124:            // player attributes.  
125:            // apart from passing, it's 2 attributes per byte  
126:            // 4 most significant bits and then 4 least significant bits  
127:            // for a total score out of 15 ( binary 1111) for each skill  
128:            // passing  
129:            team.players[p].passing = (bytes[byte_index++] )   & 0xf;   
130:            // shooting  
131:            team.players[p].shooting= (bytes[byte_index] >> (4)) & 0xff;   
132:            // heading  
133:            team.players[p].heading = (bytes[byte_index++] )   & 0xf;   
134:            // tackling  
135:            team.players[p].tackling= (bytes[byte_index] >> (4)) & 0xff;   
136:            // control  
137:            team.players[p].control = (bytes[byte_index++] )   & 0xf;   
138:            // speed  
139:            team.players[p].speed  = (bytes[byte_index] >> (4)) & 0xff;   
140:            // finishing  
141:            team.players[p].finishing= (bytes[byte_index++] )   & 0xf;   
142:            //  
143:            // ---------------------------------------------------------------  
144:            team.players[p].value    = bytes[byte_index++]; // value code  
145:            team.players[p].x4     = bytes[byte_index++]; // unknown byte  
146:            team.players[p].x5     = bytes[byte_index++]; // unknown byte  
147:            team.players[p].x6     = bytes[byte_index++]; // unknown byte  
148:            team.players[p].x7     = bytes[byte_index++]; // unknown byte  
149:            team.players[p].x8     = bytes[byte_index++]; // unknown byte  
150:        }  
151:        // -------------------------------------------  
152:        // END PLAYER  
153:        // -------------------------------------------  
154:        // save the team struct to the list  
155:        all_teams.push_back(team);  
156:      }  
157:      return 0;  
158:  }  

Wednesday, 7 May 2014

A Little Dream Come True

Alright that's a bit of an over statement. But I remember back in the day, I must have been 15 years old max, with an interest in computers but absolutely no programming knowledge whatsoever. I almost wet my pants when I got an Amiga coverdisk with "Easy Amos" on it. It was supposed to let you make your own games. At that age, I couldn't get anywhere with it. What a disappointment.

It was a minor victory just to FIND the Sensible Soccer data files. What a disappointment then (after struggling just to OPEN the files) to be presented with something like this:


How the fuck was I supposed to update Liverpool with their exciting new signings, Sean Dundee and Bjorn Kvarme?! What a disappointment!

Fast forward a couple of years and the very beginnings of my education in computers. I was in the right circles, speaking to the right people. A HEX EDITOR, THAT'S WHAT THE FUCK I NEEDED!

Finally I'd be able to update the pool to their rightful glory with wonder signings such as Djimi Traore and Bernard Diomede!

So I cracked open my shiny new hex editor, loaded in the file, and...


FUUUUUUUUCK!!!!!!

Alright, at least there's something readable on the right, but this is just a dick tease! Maybe I could change a few names, have the beast Biscan playing for Liverpool, but he'd have all the stats of whoever he replaced. Ballix. And how can I change the stats? How can I give Heskey 10 out of 10 for "falling over"? How can I give Michael Owen -1 for loyalty? Robbie Fowler 4,000,000 for finishing? Where IS all this stuff ?!?! WHAT A DISAPPOINTMENT!

Anyway, I went on my merry way, learning my programming trade and only now, thanks to this Senseless Soccer project, have I rediscovered these dat files. Well guess what bitches. NOW I FINALLY  have the knowhow to make those dat files sing to my own damn tune.

There is an online community keeping these data files up to date, which will be pretty cool for Senseless Soccer, but for now, JUST BECAUSE I CAN, here's some select tidbits ripped from the original SWOS amiga game: (attributes out of 15) - see next post for the code!


Note: As far as I know, the applied skill values in the game range from 1-7. A value over 7 signifies that this skill shows up as one of the 3 top skills for that player but in the actual gameplay, 7 is deducted to give it a val in the range 1-7.

So for example, Mark Wright has Passing, Heading and Tackling (PHT) as his 3 key skills but his passing rating of 12 is actually 5 out of 7.

The following screenshot is how this shows up in the game (couldn't find Liverpool team, sorry). Tony Adams has HTP as his key skills - heading, tackling and passing!


Tuesday, 6 May 2014

A note on docs

If you are reasonably diligent in commenting your code using the doxygen markup convention, it can spit out some pretty meaningful and helpful documentation for you.

Here's an example of some documentation for the camera class. Everything is automatically generated, even the diagrams.

This gives you a good overview of the capabilities of the camera, and an indication of where you might want to go to make changes.

The diagram shows that the camera can follow a physical object (like a ball or a player). A physical object has the atrributes acceleration, position and velocity. The camera is itself a physical object!

It has rectangles to represent its viewport (visible area) and bounds (total world space).

The diagram also (correctly) indicates that the camera is a singleton class with an instance pointer to itself. I don't really like singletons so we'll be changing that. Besides, in this case a singleton isn't a good solution, since it's entirely logical to have more than one camera.

Pretty reasonable and informative!

Monday, 28 April 2014

Timeout

Getting in the mood for my next project... got myself an arcade fighting stick! (also works pretty well for sensi!)



We've Come a Long Way

In just a few short months!



Thursday, 24 April 2014

Banana Shots!

Otherwise known as "aftertouch" which is just as well because the banana aspect isn't implemented yet. Anyway, here is the height aspect of the aftertouch system.

I don't know why this became synonymous with Sensi because KickOff certainly had it years before, and I'm pretty sure Microprose Soccer had it too, which was like one of the first footy games ever.


There are three possibilities after the ball has left your foot:

1. Release the D-pad completely: adds a modest and constant amount of lift depending on the power of the shot;

2. Press the D-pad in the direction of the shot: keeps the shot as low as possible;

3. Press the D-pad in the opposite direction: add lift in proportion to the length of time the D-pad is held.

Short explanation: we can now do big loopy shots and clearances and generally hoof the ball miles in the air! w00t!

The Menu Please!

A mockup for the start screen and menus.


Wednesday, 23 April 2014

Hallelujah

I said at the start that part of the reason for having a blog was simply for motivation and inspiration.

Well, it works bitches! Since the last post (after a couple weeks of toil in silence) the issue has been solved in the past couple of hours! Fuckin' magic.

Tech details to follow.

Frustrations

I've been stuck on the same problem for a couple of weeks now. It's really annoying. The game has reached a level of complexity where it's really tough to address difficult issues in one or two hour spurts every so often.

Basically, because of the complexity of the AI system (to allow for more interesting stuff later), it is a big job in itself just to try out a particular approach. There are 4 different levels of state machines :

1.  the player at the physical level  - standing, running etc. (handles "locomotion" details)

2. the player at the decision making level - the "brain" which instructs the physical level player to go into a "running" state for example. The brain can be in a "covering" state where it basically analyses where the ball is and tells the physical player to run to a suitable position. Or a dribbling state where it tells the player where to dribble to, or decides to pass, shoot or clear the ball.

3. the team states. For example when a team is in a "defending" state the players are aware of this and can go into a "cover" state as opposed to a "support" state for an attacking team.

4. the match states - for example "kickoff", "throwin" etc. which feeds into both teams so they can select the correct state for themselves and their players.

So you can see, in tweaking the architecture of how the whole thing works together, it sometimes gets rather intimidating and something you'd want to tackle (excuse the pun) in more concentrated blocks of work (as opposed to 30 mins tonight, and then an hour in 5 days when you've completely lost your flow of thought already.)

Anyway, it's annoying because the thing is so close to  actually being a complete game of football. The specific issue I'm having now is merely the transition of possession when two players are vying for control of the ball at the same time. It sounds trivial but there are a lot of domino like effects and implications for the system of state machines in handling who is actually dribbling the ball.

If a player can't recognize correctly that he should be dribbling as opposed to pressing the ball, he'll constantly chase after the ball whilst dribbling at the same time, resulting in him running blindly in a straight line - ARRRGHH!


Monday, 14 April 2014

Input Switching

A quick first try. Works quite well for a 20 minute hack up! The joystick control is switched to the closest team player to the ball, as signified by the squad number.  In future we'll make it more intelligent by assessing where the user is pushing the joystick to switch to the player he *wants* to control and not merely the closest to the ball.



It's easier to play in high resolution!!

AI vs AI mark 2

There are invisible walls around the pitch because throw ins etc are not implemented yet, and all shots are calculated to hit the post!

Thursday, 10 April 2014

Stadium Teaser

Using the graphics from Yoda Soccer for now.


Short Passing

Just a quickie. Haven't had time to do much , but the players can now pick a pass to the first team mate who happens to wander into their "short passing zone." Angle and weight of passes are quite accurate.


The next step is to give the player in possession a realistic objective based AI (instead of just wandering randomly and passing to the first available teammate) and also give the other players better supporting AI.

Here it is in old skool resolution:

Wednesday, 9 April 2014

RIP


Well Hello Mr Euler

The big no no of Euler integration is right out of Physics 101.  Briefly, the method looses accuracy as the time differential gets larger, due to its inherent assumption of constant acceleration.

When the timestep is very small and constant, this error is negligible. However, in computer games we can not guarantee the uniformity of the time step very well and, for most real objects, acceleration is not constant.

For something like the ball, which we want to behave pretty realistically as a physical object, it's probably a good idea to use a better integration method. If you are interested in the technical details, google "Verlet" and "RK4."

Anyway, for a game as simple (relatively) as Senseless Soccer, the Euler method is *almost* good enough. But you do get the odd visible hint of its shortcomings ever now and then due to the frame rate/time step measurement fluctuating for whatever reason. It manifests in the form of players running through the ball, the ball looking like it just teleported and bouncing off the posts too late or too early etc.. So lets implement a slightly more robust method for the ball.

Contrary to how most of the online tutorials come across, it's quite simple!! To smooth out the errors we simply take the velocities across 2 time steps, and average them to get a better estimation of what the current position should be.

The other methods average the values across a larger sample for even greater accuracy, but for our purposes this improved Euler should be fine. It may be some sort of psychological confirmation bias, but I think the ball looks more realistic already!



Incidentally, for the movement of the players we stick to simple Euler integration (of course the other methods would work fine too, but just aren't necessary). This is because the players always move with a constant velocity. There is no acceleration involved. It would be possible to implement a game where the players do have an acceleration component (so they couldn't change direction or stop immediately) but if you imagine the space ship in Asteroids as a player, you can see how infuriating that gameplay mechanism would be for a footy game like this!

Monday, 7 April 2014

Sunday, 6 April 2014

Quirks

One of the quirks of sensible soccer is that the player can only run in eight directions. This was a necessary mechanic back in the day due to the joysticks and gamepads being strictly 8-directional.

Believe it or not, the modern style FIFA games worked pretty much the same way until quite recently (you can see them making a big deal out of "360 degree movement" in their ads) where they take advantage of the analogue "thumbsticks" on modern controllers.

Such a system wouldn't really work with a retro style sprite based game, as you'd have to have either a huge amount of sprites to cater for small differences in the angle of movement, strictly top-down sprites that can be rotated by the game, or ridiculous looking players who look in a slightly different direction to where they're headed.
So we're sticking with the simple sprite-based 8 directional movement scheme. It'll be slightly unrealistic for players like Rio Ferdinand, who only seems to be able to move in 4 directions these days, but what can ya do.

This does throw up a few quirks though. For example, with the movement of the AI players, you can't simply calculate the best direction to run in and update it every frame as this results in a very unrealistic zig-zag path as they attempt to reach their target.

[note there is more complexity to this when you consider static vs moving targets but we'll simplify it for the sake of explanation]

So to get a relatively realistic behavior that mirrors how humans use the controller, we calculate the heading to the target, round it to the nearest 45 degrees and set the player off on his merry little way. Obviously, if the target didn't just happen to lie on a 45 degree angle, he'll never actually reach it and the whole thing will fall apart. To counter this, we add a check to compare the distance from the target each frame. If the new distance from the target is larger than the last distance, this is a good time to recalculate the heading to the nearest 45 degrees. This *roughly* simulates how a human player would manipulate the controller to get where he wants to go.

This is an extremely simple solution to what is a huge field in game design (path finding etc) but it's interesting nonetheless and should be a good enough solution for our purposes.

Interestingly, the original sensi solved this problem with a complete and utter cheat. Its computer controlled players are not restricted to the 8-directional movement. It's not so galling visually when the computer does it, but there was always a subconscious "hmm that's not quite right" factor without knowing exactly what was up. If you go back and play or watch it now, it's quite obvious when you know what's happening.

Who knows, perhaps there are other reasons for the "cheat" that will force us to adopt this strategy later on. Living and learning!

Wednesday, 2 April 2014

Mindful Passing

When a player grabs possession, he has a few decisions to make. One of them is if there is a good pass available. The first step is to gather all the players in a possible receiving position. The mechanics of this kind of game means that the player is always moving when he has possession and must pass in the general direction he is moving. It's only fair the AI does the same.

So the small circle represents his short passing range and the large circle represents his long pass range. Any team mates within these circles will be considered as a possible passing target. Of course, if the player is under pressure and no passes are  available, he'll have the option of hoofing the ball upfield blindly. So for Man Utd, the circles are essentially redundant.

I've zoomed way out so you can see what's going on:

Sunday, 30 March 2014

Phew

And after a few hours work, following on from the previous post, here's a first test of "covering" positions for a basic 4-4-2 using the "sector system" of positioning.

Needs some tweaking, but pretty solid for the proof of concept.


This midfield is clearly composed of Juan Mata, Mesut Ozil, Luka Modric and David Silva.

Simplifying Tactics

Well I've just wasted about a week of work by trying to be too clever!

Not truly wasted though, that's the point of the project; always learning something. Anyway, I had some really intricate ideas involving calculating the ball position, distance from ball and taking into consideration opposition positions with vector maths to come up with some "intelligent" movement which could simulate flock behavior, cohesion, separation etc...

It works, to a point. But the problem is that there are so many variables involved and so many different rules for different positions (center back, center midfielder) etc that it becomes almost impossible to allow the user to define his own formations and tactics.

So I'm going back to the basic method which, I assume, the original Sensible Soccer used. Basically the field is divided into a grid of squares and, for each player position, we define a square that he should try to get to depending on which square the ball is in.

It's a LOT of tedious work to set up, but once the initial framework is in place it'll be pretty quick and easy to create custom tactics though copy 'n' tweak.

The more squares there are, the more realistic and customizable (apparantly that's not a word, why not!?) the tactic, but the more work it is to define. For the sake of the game, I've veered to the side of "more squares for more fun." Here's how the tactical layout looks. You can see the potential for customization that when you consider for every ball position, you can specify which square any particular player should try to get to.

Later on, these basic instructions can be modified by general team tactics like "press" or "cover."

That's 285 positions to specify per player position, per situation. Eek! But remember, half of the positions can be simply mirrored to the other side of the pitch, and certain situations don't need to be specified but can be calculated, eg "one square north and one square east."

Sunday, 23 March 2014

Look at the State of Ye!

Here we see the power of the state machine pattern. It takes a bit of time and effort to get set up, but once in place it is extremely clean, flexible, extensible, and easy to debug.

Everything is a state machine (well, almost). The player, his brain, the match, the team... all state machines.

Here is a vid of the game automatically going through the start phase of the match -

- state enter pitch
- state lineup
- state kickoff
- state play




we now have the framework in place to start giving the player AI some real goals and objectives, and hopefully get something resembling a real match. Onward!

Friday, 21 March 2014

Kickabout

Now the players have an extremely basic brain hacked in, just to show what's possible. Now... time to program some real Digital Minds!! w00t!

Pitches

More updates to report. Variable pitch sizes are now possible. That's pretty cool. I heard Man Utd were thinking about making their pitch a bit smaller to suit their new style and players. Barcelona have a big wide pitch to stretch the opposition with their precise passing and control of the ball, Utd are reducing theirs to a 5-a-side pitch because they only have 5 decent players left.

Anyway, also threw a few wee men onto the field and gave them enough brains to find their starting positions. Looking pretty cool.

Digital Mind...

The very first hint of a smell of a whiff of a bi of AI... the goalie picks it up, runs to a random spot in his box and hoofs it upfield to nobody in particular. STILL better than Jamo then...

David James

You might have noticed a goalkeeper sneaked into the last video. Yeap he has pretty good base position between the goal and the ball and now he can even dive! So that's better than Jamo already then... Oh, also added a stadium for fun.

Mental!

Whilst messing around with the new ball physics, it occured to me that it'd probably be useful for the players to know where the ball is going, and not only where it is.

It helps with stuff like through balls etc, where the player can run directly into the future path of the ball instead of readjusting frame by frame.

Introducing Mr. red dot. It's not a laser sight, just a cool prediction of where the ball is going to end up.

Balls

That good ol' ball optical illusion is still pretty sweet. But lets have a go at enhancing it. A more detailed ball graphic, resizing it depending on the height and a rolling animation in proportion to the speed all makes for a pretty nice effect.