Which game would you like to see written?
Jeffrey Lee (213) 6048 posts |
Very nice! For a while I’d been wondering exactly how old racing games created their pseudo-3d tracks, and codeincomplete seems to have hit the nail on the head. |
Michael Drake (88) 336 posts |
Yes, that’s an excellent series of articles. The JavaScript Outrun clone game demo actually plays nicely too. |
Anthony Vaughan Bartram (2454) 458 posts |
Thanks for those well written articles Alan. I’m reading through them now. However, after Overlord, which was 2.5D, I think I’m going to move more toward full 3D. So if I produce a car game, It’d be cool to have depth & hills as well. There are short-cuts and pseudo effects which I’ll probably need to employ for speed. One question : would there be any objections to employing drops and rises in the road for a Fervour style game – rather than making it flat? |
Tony Noble (1579) 62 posts |
Battlezone – there was a nice remake of this done for Mac and PC with network play – Spectre Fairly sure there was an open-source version about as well, with different play modes – capture the flag, single player, deathmatch, etc. Can’t remember what it was called at the moment, though… |
Tony Noble (1579) 62 posts |
…and immediately, googling helps me remember it: BZFlag Open source and written in C. Maybe worthy of a conversion, or does RISCOS not have any openGL libraries? |
Jeffrey Lee (213) 6048 posts |
No objections here. If you could split the track into ‘lanes’ and have different heights for each lane then that would be pretty cool. Unlike the classic games from the 80’s and 90’s you probably don’t need to worry about having a super-optimised renderer, so you could probably just build up a list of polygons for the track + sprites for objects, depth sort them, and render everything from back to front, taking a bit of a performance hit whenever there’s some overdraw due to overlapping regions of the track. Or if you’re thinking of full 3D in the future then using a span buffer would be a reasonable way of avoiding overdraw (for the track polygons, at least). I’m not sure of any good references for it (other than an old magazine series (from Acorn Computing?) which introduced me to the idea), but basically the idea is that you render the polygons manually one scanline at a time, but rather than blindly rendering back-to-front you depthsort the polygons on a per-scanline basis in order to allow you to eliminate overdraw. If you wanted to render sprites in the world then you’d probably get the polygon renderer to also build up a span-based depth buffer, so that you can later use it to do depth testing for the sprite scanlines (and just render them back-to-front, taking the overdraw hit if two sprites overlap on screen). |
David Williams (2619) 103 posts |
Jeffrey: [Possibly OT] I remember being amazed (and still am!) at ‘Crazee Rider’ on the Acorn Electron; how Kevin Edwards was able to render the bending road so quickly and smoothly. I can’t imagine (apart from possibly using precalculated curves) how he pulled it off. |
Jeffrey Lee (213) 6048 posts |
Yes, there’d certainly be a lot of precalculation going on – although part of the trick is that you don’t actually need proper curves! Writing a pseudo-3d track render is certainly easy once you know how (the below only took me 3 hours to knock up), but optimising it to run at a reasonable framerate is a completely different matter! ON ERROR PRINT REPORT$;" at ";ERL : END VDU 26 xeig%=VDU 4 yeig%=VDU 5 xlimit%=(VDU 11)+1 ylimit%=(VDU 12)+1 c_grass%=&40804000 c_road1%=&40404000 c_road2%=&30303000 c_line%=&ffffff00 c_sky%=&f0c0c000 line_segment_count%=4 segment_len=1 lane_width=2 road_lanes=3 line_width=0.1 edge_width=0.3 cam_x=0 cam_y=3 cam_z=0 fov=ylimit% dist%=300 REM Generate X offsets and colours of road segments segments_wide%=road_lanes+1+road_lanes road_width=road_lanes*lane_width+2*edge_width+(road_lanes-1)*line_width DIM segment_x(segments_wide%) DIM segment_col%(2,segments_wide%) segment_x(0)=-road_width/2 segment_col%(0,0)=c_grass% segment_col%(1,0)=c_grass% segment_x(1)=segment_x(0)+edge_width segment_col%(0,1)=c_road2% segment_col%(1,1)=c_line% I%=2 FOR L%=1 TO road_lanes IF L%>1 THEN segment_x(I%)=segment_x(I%-1)+line_width segment_col%(0,I%)=c_line% segment_col%(1,I%)=c_road2% I%+=1 ENDIF segment_x(I%)=segment_x(I%-1)+lane_width segment_col%(0,I%)=c_road1% segment_col%(1,I%)=c_road2% I%+=1 NEXT L% segment_x(I%)=segment_x(I%-1)+edge_width segment_col%(0,I%)=c_road2% segment_col%(1,I%)=c_line% I%+=1 DIM proj_x%(2,segments_wide%) REM draw scene first_road%=0 old_ylim%=ylimit% REPEAT first%=-1 PROCdraw(first_road%) SYS "ColourTrans_SetGCOL",c_sky% IF ylim%<old_ylim% THEN RECTANGLE FILL 0,ylim%<<yeig%,xlimit%<<xeig%,(old_ylim%-ylim%)<<yeig% ENDIF old_ylim%=ylim% first_road%=first% : REM Skip rendering segments which we know aren't visible cam_z += 1 REM Hug the inside of the curves cam_x = SIN(first%/50)*lane_width*(road_lanes-1)*0.5 UNTIL FALSE END DEF PROCdraw(start%) ylim%=0 road_x=0 road_y=0 xd=0 yd=0 FOR i%=start% TO dist%+start% yd=SIN(i%/60)*0.2 PROCdraw_segment(i%,road_x,road_y,road_x+xd,road_y+yd) IF first%<>-1 THEN road_x += xd road_y += yd xd += SIN(i%/50)*0.01 ENDIF NEXT i% ENDPROC DEF PROCdraw_segment(i%,road_x0,road_y0,road_x1,road_y1) road_z0 = i%*segment_len IF road_z0 <= cam_z THEN ENDPROC road_z1 = road_z0 + segment_len proj_y0% = FNproj(road_y0-cam_y,road_z0-cam_z,ylimit%) proj_y1% = FNproj(road_y1-cam_y,road_z1-cam_z,ylimit%) IF proj_y1% <= proj_y0% THEN ENDPROC IF proj_y1% <= ylim% THEN ENDPROC IF first%=-1 THEN first%=i% PROCproj_x(road_x0-cam_x,road_z0-cam_z,0) PROCproj_x(road_x1-cam_x,road_z1-cam_z,1) cols%=(i% DIV line_segment_count%) MOD 2 lastx0%=0 lastx1%=0 FOR I%=0 TO segments_wide% PROCpoly(lastx0%,proj_x%(0,I%),proj_y0%,lastx1%,proj_x%(1,I%),proj_y1%,segment_col%(cols%,I%)) lastx0%=proj_x%(0,I%) lastx1%=proj_x%(1,I%) NEXT I% PROCpoly(lastx0%,xlimit%,proj_y0%,lastx1%,xlimit%,proj_y1%,segment_col%(cols%,0)) ylim%=proj_y1% ENDPROC DEF FNproj(val,dist,ofs%) =INT((val/dist)*fov)+(ofs%/2) DEF PROCproj_x(ofs,dist,idx%) FOR I%=0 TO segments_wide% proj_x%(idx%,I%)=FNproj(ofs+segment_x(I%),dist,xlimit%) NEXT I% ENDPROC DEF PROCpoly(x0l%,x0r%,y0%,x1l%,x1r%,y1%,c%) IF x0l%>=x0r% AND x1l%>=x1r% THEN ENDPROC IF x0r%<x0l% THEN x0r%=x0l% SYS "ColourTrans_SetGCOL",c% IF y0%=y1%-1 THEN REM Just a horizontal line; draw a rectangle to take advantage of GraphicsV RECTANGLE FILL x0l%<<xeig%,y0%<<yeig%,(x0r%-x0l%)<<xeig%,0 ENDPROC ENDIF REM Decompose into a rectangle and two triangles, to take advantage of GraphicsV rectangle plotting IF x0l%<x1l% THEN left%=x1l% ELSE left%=x0l% IF x0r%<x1r% THEN right%=x0r% ELSE right%=x1r% IF left%<right% THEN RECTANGLE FILL left%<<xeig%,y0%<<yeig%,(right%-left%)<<xeig%,((y1%-y0%)<<yeig%)-1 PLOT 4,x0l% << xeig%,y0% << yeig% PLOT 4,x1l% << xeig%,y1% << yeig% IF left%=x1l% THEN PLOT 80+5,left% << xeig%,y0% << yeig% ELSE PLOT 80+5,left% << xeig%,y1% << yeig% ENDIF PLOT 4,x0r% << xeig%,y0% << yeig% PLOT 4,x1r% << xeig%,y1% << yeig% IF right%=x1r% THEN PLOT 80+5,right% << xeig%,y0% << yeig% ELSE PLOT 80+5,right% << xeig%,y1% << yeig% ENDIF ENDPROC ENDIF REM Hard case, plot 2 triangles PLOT 4,x0l% << xeig%,y0% << yeig% PLOT 4,x0r% << xeig%,y0% << yeig% PLOT 80+5,x1l% << xeig%,y1% << yeig% PLOT 80+5,x1r% << xeig%,y1% << yeig% ENDPROC |
David Feugey (2125) 2709 posts |
Works well and gives ideas :) |
Colin (478) 2433 posts |
I thought I was playing Revs again while watching that Jeffrey. |
Rick Murray (539) 13840 posts |
Holy hell – that little bit of code does a hilly road with sort-of perspective? That’s kind of impressive. Would there be any speed gains in caching a table of the GCOLs instead of calling ColourTrans a lot? |
Rick Murray (539) 13840 posts |
A bit faster? Make it look more like the roads we know:
And, at the very beginning:
Runs faster in MODE 15 and even delivers Epic Widescreen on the Pi. ;-) |
Alan Robertson (52) 420 posts |
Excellent. So which version of RISC OS will now include a port of Outrun as a little Easter Egg? |
Anthony Vaughan Bartram (2454) 458 posts |
This is really cool Jeffrey. I’m learning a few more RISC OS features from this code. Thanks for the thoughts on overdraw & 3D. |
Jeffrey Lee (213) 6048 posts |
It would make things a bit faster, yes. But there are also plenty of other improvements that could be made. Very nice! For a while I’d been wondering exactly how old racing games created their pseudo-3d tracks, and codeincomplete seems to have hit the nail on the head. A while ago I was contemplating making a 3D maze screensaver (similar to the old Windows one) for adding to the screensavers in the disc image. But then I realised transformed sprite plotting using OS_SpriteOp only works with parallelograms, so if I wanted it to look any good I’d have to plot the sprites manually (OS_SpriteOp would have been useful because it would have produced a solution that worked with any screen mode). Maybe I should turn this into a screensaver instead? (with a hidden playable mode, of course!) This is really cool Jeffrey. I’m learning a few more RISC OS features from this code. Glad to be of service! |
David Williams (2619) 103 posts |
Jeffrey, perhaps I should’ve sought your permission beforehand but I’ve adapted your nice & informative bit of code for use with ‘BBC BASIC for Windows’ (BB4W). The performance probably won’t be as good as on fast RISC OS hardware because although BB4W (as interpreters go) is very fast, it relies on Windows GDI graphics functions to draw graphics which tends to be rather slow (in relative terms, LINE/DRAW/PLOT/RECTANGLE etc. are significantly faster under RISC OS than under Windows). This probably has to do with several layers of abstraction one has got to bust through just to draw a line on the screen. A compiled Windows executable (EXE) is available from the following URL (it can be run straight out of the Zip folder without manually extracting first). It does seem to draw an extraneous line at the bottom of the window — that’s my fault, not Jeffrey’s! http://www.proggies.uk/bb4w/road2.zip EDIT: This updated version is much faster (uses my own graphics plotters): http://www.proggies.uk/bb4w/road3.zip David. |
David Feugey (2125) 2709 posts |
Works well too :) |
Jeffrey Lee (213) 6048 posts |
No problem – I’m happy for people to make use of the code. It’s not really that special, considering it’s essentially just a BASIC version of some of the core concepts implemented in the JavaScript engine that Alan linked to. I’ve been playing with the code some more today – after converting it into C and making a few optimisations it’s now running at an average of 60fps on an Iyonix at 1920×1200! (and 200fps at 1080p on a Raspberry Pi 1, although the build below is VSync locked so that it won’t look too silly) http://www.phlamethrower.co.uk/misc2/track.zip Main optimisations were:
If BASIC is your thing, then all of the above optimisations (except the converting to C one!) could easily be applied to the BASIC version too – although I’d be surprised if you got the same level of performance as the C version. Also note that the original renderer as used in the BASIC version does have some overdraw, so if you aren’t going for a span buffer then you might want to fix that at least. And calling ColourTrans_SetGCOL 9*300 times per frame (as the current code does) can add significant overhead, even for 32bpp modes where the SWI doesn’t need to do any expensive colour lookups. Next step is going to be to work out how to render sprites in the world. |
David Williams (2619) 103 posts |
Thanks, Jeffrey. Your new, more optimized version runs like a dream under RPCEmu (not tried it on real ARM hardware yet; would doubtlessly look great on my RPi 2). |
Anthony Vaughan Bartram (2454) 458 posts |
The new game is coming on (a little gradual as I was Ill for a couple of weeks). I’m taking a different approach from Jeffrey as I’m pre-calculating tables to render objects as a projection which take a 3D plan view as input. Then just POCing with the built in matrix transforms in BASIC V for polygon translation (i.e. movement). Currently trying to generate the road using a 3 stage compiler – language description => intermediate form => polygons. Then plan to use a sliding window to render the polygons (in Z order) via the projection lookup array. All good fun. (Hopefully this will all work quick enough….) |
Jeffrey Lee (213) 6048 posts |
Sounds good! Getting sprites/objects working with a standard polygon renderer should be a lot easier than with the span buffer approach I’ve been using (one of the reasons I haven’t found the time to implement it yet – maybe over Christmas). And in case you’re interested (and can track down copies of the magazine – this is the only place I’ve found so far which has working links to scans) it’s the “Real 3D graphics” series in Acorn Computing which I was thinking of as the thing that introduced me to the concept of span buffers (although IIRC the technique described was an “active edge list”, which kept track of the left and right edges of each polygon and decomposed them into horizontal spans on the fly rather than generating and storing a full span buffer for the entire screen). It looks like it was present in issues 143, 144, 146 and 147 – but with 147 being the last issue of the magazine I think the series was cut short and wasn’t continued elsewhere. |
Anthony Vaughan Bartram (2454) 458 posts |
Hi Jeffrey, Thanks I will look to see if I can find that series online. In addition, I’ve found out that a colleague of mine used to work at CodeMasters in the late 1990s and he has been sharing fast texture mapping/lighting techniques with me. I don’t know if I’ll have time to incorporate it, but its quite enlightening never-the-less. Tony |
Alan Robertson (52) 420 posts |
I’m resurrecting this old thread, as it looks like someone has written an Open Source game engine for Sega’s 3D Racing game, Out Run. Looks like there are already a few ports to other systems too. No pressure ;-) |
Jeffrey Lee (213) 6048 posts |
Nice. I suspect (or at least I hope) Anthony is further along than me – over Christmas I did find the time to build a lot of the framework needed for rendering objects (loading and converting drawfiles; tracking car and track side object positions; issuing draw calls), but ran out of time before I was able to look into an efficient way of integrating the objects with the span buffer. |
Anthony Vaughan Bartram (2454) 458 posts |
Hi Jeffrey and Alan. I’m still targeting Wakefield for a Fervour style game. I have the code, music elements in place. I just need to integrate, create content and test. I didn’t feel comfortable releasing this at the SW show, as I felt it was too quick and needed a little rest after releasing 2 titles in the last 12 months. Tony |