Since then I have revised the code to also make the 'player' rotate in the direction of travel, to simulate the effect of the car steering.
Take a look:
Original Movie (without steering) - Demo SWF
Modified Code (with steering) - Demo SWF
As you can see the rotation produced by the modified code offers more realistic looking game play, allowing the 'player' car to appear to swerve as the player moves the mouse, and making the 'player' car straighten up as it comes to the end of its movement.
The Code
The code is actually remarkably simple, much simpler than I expected. As I look back through my development versions it gets progressively simpler as I cut out all the redundant code and am left with only what matters.
I will give you the code below, the comments explain how it works. Just paste it into your Flash Actionscript panel, and click the format button to make it nice and easy to read: /* Includes code from http://dansinteractive.blogspot.com http://www.digitalarena.co.uk */ /* This code works best if your movie is set to run at 30fps. */
/* The higher the frame rate the lower this number must be. */
var easing = 10;
/* Influences how far the player_mc rotates in response to the mouse distance from the player_mc. The lower the number the greater the rotation. */
var rotatefactor = 4.5;
/* This function animates the player_mc in response to mouse moving. */
onEnterFrame = function () {
/* Works out the current distance between the X coordinate of the mouse and the X coordinate of the player_mc */
mousediff = _root._xmouse-_root.player_mc._x;
/* Moves the player_mc along the X axis towards the location of the mouse with easing to provide some delay and smooth movement. */
_root.player_mc._x += mousediff/easing;
/* Rotates the player_mc towards the mouse. The amount of rotation is determined by the difference between the player_mc location and the mouse location along the X axis, and the rotation factor defined in the variable at the top. The closer they are together the smaller the amount of rotation, the further apart the larger the rotation. With the _rotation method a negative number means anti-clockwise (counter-clockwise) and a positive number means clockwise. Even so, we do NOT need to test which side of the player_mc the mouse is on to determine whether the angle of rotation should be positive or negative. This is because we base the rotation on the distance between the mouse and the player_mc, and if the mouse is to the left of the player_mc this code will return a negative number which in turn will give us a negative rotation factor. Cool.*/
As you can see, the whole affect is achieved without trigonometry. As such it simulates the visual appearance of the 'player' car steering, but is not a mathematically accurate model of steering. But it is only intended to provide user mouse control of a car for a simple top-down driving game - accurate physics are not necessary.
The thing I am most pleased about (apart from the cool effect) is how little code it takes. Without comments, it is a mere 7 lines long. And yet it provides so much more engaging game play (I think so anyway). If you find it helpful, leave a comment.
Now we are beginning to see the end result of all my experiments with manipulating colour using Actionscript. Using the techniques from my previous posts, and a few others to get a working interface, this post explains how to make a simple RGB colour mixer that gives a preview of the colour and the hexadecimal value in a form that can be copy and pasted.
Demo SWF
The Interface
While it is very tempting for those with a graphics background to want to start by designing a groovy interface (including me), it is sometimes a good idea to get just a basic prototype working so you can get the bugs out of the code - and that's what you see here.
The demo above shows my basic interface. Before we can go much further you will need to lay out something similar on your stage. The items in the demo above are labelled, check the key below:
KEY:
A = MovieClip, instance name 'colourspot_mc' B = Input Text, instance name 'red_txt', variable 'red' C = Input Text, instance name 'green_txt', variable 'green' D = Input Text, instance name 'blue_txt', variable 'blue' E = Dynamic Text, variable 'displayhex' F = MoveiClip, instance name 'redpointer_mc' G = MovieClip, instance name 'redclicker_mc' H = MoveiClip, instance name 'greenpointer_mc' I = MovieClip, instance name 'greenclicker_mc' J = MoveiClip, instance name 'bluepointer_mc' K = MovieClip, instance name 'blueclicker_mc'
Once you have set up your objects on the stage as above, you are ready to start coding.
The Code
I won't go into long and drawn out explanations of everything this time. The colour manipulation is explained in previous posts on this blog, and other things are explained within the code as comments.
NOTE: For some reason blogger is not treating this code very well. When you dump it into Flash make sure you hit the 'Format' button so it looks as it should.
/* set default values for R, G and B */ var red = 0; var green = 0; var blue = 0; /* Create colour object to control colourspot_mc */ var my_color:Color = new Color(colourspot_mc); /* Button triggers conversion of RGB into hex, then applies it to the colour object */ onEnterFrame = function() { /*This enables colour to be constantly updated */ /* ensures that only values between 0 and 255 can be used for red */ if (red>255) { red = 255; } if (red<0) { red = 0; } /* convert red decimal into hex */ var decred = new Number(red); hexred = decred.toString(16); if (decred<=15) { hexredfinal = "0"+hexred; } else { hexredfinal = hexred; } /* ensures that only values between 0 and 255 can be used for green */ if (green>255) { green = 255; } if (green<0) { green = 0; } /* convert green decimal into hex */ var decgreen = new Number(green); hexgreen = decgreen.toString(16); if (decgreen<=15) { hexgreenfinal = "0"+hexgreen; } else { hexgreenfinal = hexgreen; } /* ensures that only values between 0 and 255 can be used for blue */ if (blue>255) { blue = 255; } if (blue<0) { blue = 0; } /* convert blue decimal into hex */ var decblue = new Number(blue); hexblue = decblue.toString(16); if (decblue<=15) { hexbluefinal = "0"+hexblue; } else { hexbluefinal = hexblue; } /* build the final 6 digit hex figure and prepend with 0x as needed by Flash */ hex = "0x"+hexredfinal+hexgreenfinal+hexbluefinal; /* build the final 6 digit hex figure and prepend with # as needed by HTML */ displayhex = "#"+hexredfinal+hexgreenfinal+hexbluefinal; /* set the colour property of the colour object controlling the MovieClip */ my_color.setRGB(hex); /* Update the location of the pointers */ _root.redpointer_mc._x = ((_root.redclicker_mc._width/256)*red)+_root.redclicker_mc._x; _root.greenpointer_mc._x = ((_root.greenclicker_mc._width/256)*green)+_root.greenclicker_mc._x; _root.bluepointer_mc._x = ((_root.blueclicker_mc._width/256)*blue)+_root.blueclicker_mc._x; }; /* Set the red decimal value with a clicker */ _root.redclicker_mc.onPress = function () { redleft = _root.redclicker_mc._x; clickpos = _xmouse; clickval = clickpos-redleft; clickprop = (clickval/_root.redclicker_mc._width)*256; red = Math.floor(clickprop); /*moving the pointer _root.redpointer_mc._x = clickpos; } /* Set the green decimal value with a clicker */ _root.greenclicker_mc.onPress = function () { redleft = _root.greenclicker_mc._x; clickpos = _xmouse; clickval = clickpos-redleft; clickprop = (clickval/_root.greenclicker_mc._width)*256; green = Math.floor(clickprop); /*moving the pointer */ _root.greenpointer_mc._x = clickpos; } /* Set the blue decimal value with a clicker */ _root.blueclicker_mc.onPress = function () { /*Getting the zero point on the clicker bar */ redleft = _root.blueclicker_mc._x; /*Capturing the location of the click on the clicker bar */ clickpos = _xmouse; /*Calculating how far along the clicker bar the click was made */ clickval = clickpos-redleft; /*Calculating the proportional distance along the bar at which the click was made as a figure between 0 and 1, then converting that into a value between 0 and 256. */ clickprop = (clickval/_root.blueclicker_mc._width)*256; /*Converting the figure to an integer by rounding DOWN to the nearest whole number. Rounding DOWN ensures we can get 0 at the bottom and 255 rather than 256 at the top. */ blue = Math.floor(clickprop); /*moving the pointer object to the position clicked on the clicker bar */ _root.bluepointer_mc._x = clickpos; }
And there we have it.
Solving Issues as You Go
It is always interesting to solve some issues as you go. For instance, I hadn't quite decided whether I wanted users to be able to type RGB values, or just limit them to using the colour bars.
When I decided that some users might want to be able to type them (as it might be easier and faster in some situations) I realised that some users might enter a value greater than 255 - which would then give inaccurate results. So, at the last minute I included the code that converts any number higher than 255 into 255 and any number lower than 0 into 0. Like I say, always interesting.
Another implication was with the pointers on the colour bars (items F, H and J in my diagram). Making the RGB numbers updatem depending on where the user clicked, and moving the pointers to the place the user clicked was one thing. But now we had to also work it the other way, and get the pointers to move to the place on the colour bar representing the value the user typed. It wasn't difficult, just a case of working the equation the other way, but it needed doing.
Sometimes seemingly small decisions about user experience can have a significant impact on our code. That doesn't mean we shouldn't put the user first, but it does mean we should be prepared to modify our code when needed. After all, our interactive applications need to actually do what the user wants, if we want any users to use them.
Another of my students hit me with a conundrum to solve, and as usual I can't resist the challenge.
They are constructing a flash game to help new computer users become more comfortable with using a mouse. The idea is to have a top down driving game in which the player must move the car side to side to avoid on coming traffic and bad guys.
My student is already aware that you can easily make the player character follow the mouse, but they were not happy with such instant precise movements for the game (cars, after all, just don't respond like that). So I have been looking at some code to add a dampening or easing effect on the car so there is a smoother response when the mouse is jerked.
Demo SWF
Easing with mouse chasing movieclips
To kick off, the frame rate of the movie is 30fps for nice smooth animation. Then chuck the following code into the first frame:
var easing = 0.1; //The higher the frame rate the lower this number must be
onEnterFrame = function () { _root.player_mc._x += (_root._xmouse-_root.player_mc._x)*easing; };
And it really is that simple. To tighten or loosen the easing just ammend the easing variable.
Then just whack a movie clip on the stage called player_mc and you're done.
Taking the game code further
It is too late at night to waffle about it now, but you may find the developed code helpful. I may even get round to finishing it:
/* Students are required to design and build a simple game that teaches mouse control. However one student complained that the mouse is too sensitive for his game idea (a topdown car game). One solution to his problem is to have acar contolled only along the X axis (left and right) andthat we do some simple algbra to dampen or ease the control ofthe player car. Here goes.*/
/*First declare some variables */
var easing = 0.1; /*easing sensitivity, the greater the frame rate the smaller this value needs to be.*/
var crashcount = 0;
/*Then we hide the mouse cursor so the user is not distracted and can focus on the player movie clip instead (which will be constrained to follow the mouse only on the X axis.*/
Mouse.hide();
/*Now a function to implement the easing and test for collisions with the enemy*/
onEnterFrame = function () { /*Here's the easing*/ _root.player_mc._x += (_root._xmouse-_root.player_mc._x)*easing;
/*Because of limitation on Flash's hit test (whole bounding box or just a single point) the following code checks for collisions on 2 single points on the player, the top right, and the top left of the player. Because all collisions will be front on, this will be sufficient to see if the player collided with the enemy. We choose the single point method because it allows us to lump all the enemies into one movie clip and for us to weave between the gaps without raising a collision as we would if we used the bounding box.*/
if (_root.enemy_mc.hitTest(_root.player_mc._x+(_root.player_mc._width/2), _root.player_mc._y-(_root.player_mc._height/2), true)) { crashcount += 1; } if (_root.enemy_mc.hitTest(_root.player_mc._x-(_root.player_mc._width/2), _root.player_mc._y-(_root.player_mc._height/2), true)) { crashcount += 1; } };