' ' Animation system demo. You can read the detailed explanation at: ' https://qbmikehawk.neocities.org/articles/animations/index.html ' have fun. - Mike Hawk. ' CONST gBufferSize = 4096 ' buffer size, in INTEGERS CONST gImageCount = 128 ' collection size, in images CONST actionCount = 8 ' reserve memory for eight actions CONST actorCount = 6 ' six actors CONST animLoop = 0 ' animation loops back to 0 CONST animOscil = 1 ' animation goes back and forth CONST animFreeze = 2 ' animation freezes on last frame CONST actHeroWalk = 0 ' custom animation indices CONST actHeroStand = 1 ' custom animation indices CONST actHeroExplode = 2 ' custom animation indices CONST actHeroAttack = 3 ' custom animation indices CONST actZombieWalk = 4 ' custom animation indices CONST actZombieStand = 5 ' custom animation indices CONST actItemPotion = 6 ' custom animation indices CONST actItemSword = 7 ' custom animation indices CONST isPlayer = 0 ' custom behavior CONST isZombie = 1 ' custom behavior CONST isSword = 2 ' custom behavior CONST isPotion = 3 ' custom behavior TYPE imageType ' image collection ofs AS INTEGER ' location in gBuffer() x AS INTEGER ' origin point, horizontal coordinate y AS INTEGER ' origin point, vertical coordinate END TYPE TYPE actionType ' actions... viewAngle AS INTEGER ' angle coverage, in degrees frameFirst AS INTEGER ' index of the first frame in gImage() frameCount AS INTEGER ' number of animation frames per angle animMode AS INTEGER ' forward, back-and-forth, stop animDelay AS INTEGER ' delay before next frame END TYPE TYPE actorType ' actors... actIndex AS INTEGER ' current action index anmFrame AS INTEGER ' current frame anmFrameNext AS INTEGER ' countdown to next frame anmFrameVec AS INTEGER ' frame increase/decrease value anmFacing AS INTEGER ' facing direction, simplified angle AS INTEGER ' facing direction, in degrees x AS INTEGER ' horizontal location y AS INTEGER ' vertical location vecX AS INTEGER ' horizontal movement ("forward" vector) vecY AS INTEGER ' vertical movement ("forward" vector) travel AS INTEGER ' traveling ticks behavior AS INTEGER ' actor behavior speed AS INTEGER ' speed reset value speedCD AS INTEGER ' speed countdown END TYPE DECLARE SUB getImage (x AS INTEGER, y AS INTEGER, w AS INTEGER, h AS INTEGER, oX AS INTEGER, oY AS INTEGER) DECLARE SUB actorSetAction (id AS INTEGER, actId AS INTEGER) DECLARE SUB actorSetAngle (id AS INTEGER, degrees AS INTEGER) DECLARE SUB initDemo () DECLARE SUB mainLoop () DECLARE SUB mainRender () DECLARE SUB mainActorBoundaries (id AS INTEGER) DECLARE SUB mainActorMove (id AS INTEGER) DECLARE SUB mainActorAnim (id AS INTEGER) DIM SHARED gBuffer(gBufferSize - 1) AS INTEGER ' image data buffer DIM SHARED gImage(gImageCount - 1) AS imageType ' image collection DIM SHARED gBufferOfs AS INTEGER ' writing position in gBuffer() DIM SHARED gImageCnt AS INTEGER ' writing position in gImage() DIM SHARED action(actionCount - 1) AS actionType DIM SHARED actor(actorCount - 1) AS actorType initDemo mainLoop SCREEN 0 WIDTH 80, 25 END '' graphic data - each pair of characters represents a BYTE (8 bits) in '' hexadecimal form. Inside each BYTE are 4 pixels (2 bits each) from least to '' most significant bits. The image (and colors) are converted in the '' initialization routine. ' a 4-color 128x80 image (sprites from Dragon Slayer) DATA 5051050050510500005415000054150000504505005045050054150000541500 DATA A4A75A00A4A75A0000AD7A0000AD7A0000A5DA1A00A5DA1A00AD7A0000AD7A00 DATA E9A9E601E9A9E601409BE601409BE601409B6A6B409B6A6B406BE901406BE901 DATA 79A6660679A66606D0996607D099660790999A6D90999A6DD09AA607D09AA607 DATA 7D5AAA1A7D5AAA1AD0AAAA07D0AAAA07A4AAA57DA4AAA57DD0A69A07D0A69A07 DATA 5DAA55155DAA5515D0555507D05555075455AA755455AA75D0A55A07D0A55A07 DATA 446B9B01446B9B01509BE605509BE60540E6E91140E6E911509EB605509EB605 DATA 00DD9A0100DD9A01409AA601409AA60140A6770040A67700409BE601409BE601 DATA 40B6AA0140B5AA0150ABEA0550ABEA0540AA5E0140AA9E01506BE905506BE905 DATA D0D96A1590DA6A00E4AD7A1AA4AD7A1B00A9A70654A96707B4BD7E1AA4BD7E1E DATA A467556B407DD505B957951D7456D56E50577D01E955D91AB456951FF456951E DATA A4DD7A6F40EB7A1B7D7D7D7AAD7D7D7DE4ADEB01F9AD771A74AD7A1BE4AD7A1D DATA 50546A15406B6A0754A75A7AADA5DA15D0A9E90154A9150550FB6F0550F9EF05 DATA 00DD9F0700D57F0140DFA71554DAF70140FD5700D0F67700409796014096D601 DATA 407AE51A00B55A00D07AA906906AAD0700A55E00A45BAD01D07AA906906AAD07 DATA 50A9A55650A56A05946A55155455A91650A95A05955A6A05946A55155455A916 DATA 5051050000541500005045050054150000541500005415000054150000000000 DATA A4A75A0000AD7A0000A5DA1A00AD7A00009D7600009D7600009D760000041400 DATA E9A9E601409BE601409B6A6B406BE901409966014099660140A96A01001D7D00 DATA 79A66606D099660790999A6DD09AA607D0ABEA07D0ABEA07D05B650040195401 DATA 7D5AAA1AD0AAAA07A4AAA57DD0A69A07D0555507D0555507D0AD7904D1050000 DATA 5DAA5515D05555075455AA75D0A55A0750ABEA0550ABEA0550BB6D0050000004 DATA 446B9B01509BE60540E6E911509EB605509BE60550BBEE055059D90510000000 DATA 00DD9A01409AA60140A67700409BE60150AAAA05546AA9155404900500100000 DATA 00D5AA0150ABEA0540AA5700506BE905546BEA15791BE46D7D00D11500000000 DATA 406A6B00E4AD7A1B00E9A901B4BD7E1EF91D756FFD05507FFD01406904000014 DATA 40F5D505B957D56E50575F01B456951EBD17D47E9407D1165400446B1D000074 DATA 00AD7B077D7D7D7DD0ED7A0074AD7A1D547D7D15400550014010401514000074 DATA 00AD690754A7DA15D0697A0050FBEF0540A7DA014017D4010001D00100001014 DATA 00547F0140DFF70140FD15004097D60140DFF701405FF5014017F40100040400 DATA 00B55A00D07AAD0700A55E00D07AAD07D07AAD07D07AAD07D01AAD0750000005 DATA 50A56A05946AA91650A95A05946AA916946AA916A41EB41AA41EB41AE401501E DATA 5051050000000054150000000000000040544500405445000051150100511501 DATA A4A75A00000000AD7A0000004000000000691A0000691A0000A46A0000A46A00 DATA E9A9E6010000409BE60100009001000050AA6A0050AA6A0010A9AA1110A9AA11 DATA 79A666060000D09966070000A401000040A6A70140A6A70140DEDE0640DEDE06 DATA 7D5AAA1A0000D0AAAA070000A4010000D0AA9901D0AA99014066560640566606 DATA 5DAA55150000D05555070000A4010000D0A9A701D0A9A70140DEDE0640DEDE06 DATA 446B9B010000509BE6050000A4010000409A6F01409A6F0100B9B90100B9B901 DATA 00DD9A010000409AA6010000A401000010A9AA0110A9AA0100A9AA0100A9AA01 DATA 50B6AA41000050ABEA1500549501000000E41B0000E41B00006DE501006DE501 DATA A4DB6A975515B4AD7A6E00AD7A06000000D5560000D456000059950540599501 DATA A477D599AA6A6456956D406BE9010000405F6A0000746A0040A76A1DD0A56A07 DATA 50D5BA99AA1AE4757D15D09AA6070000907E150000FD1500D0DF5F7DF4D5DF1F DATA 40B76A95550590A6DA01D0A69A07000040561500007D1500E457D565645D556F DATA 00D97F460000D0D7F701D0A55A07000000FD770000697F00A4FDFE1750FFFE69 DATA 506695070000946AA505509EB615000040FEBD0100D5570050AB5D0740D7A917 DATA B55AAD5A0000555AA916409BE61900005069690540AD7A014055A91650AA5505 DATA 000000504505401A0000506BE915000000511501005115010051150100511501 DATA 000000A5DA1A401A0000B4BD7E06000000A4690000A4690000A46A0000A46A00 DATA 0000409B6A6B401A0000B957D501000000A9AA0500A9AA0510A9A6111069AA11 DATA 000090999A6D401A000069AD7A01000040DA9A0140DA9A014066AA0640AA6606 DATA 0000A4AAA57D401A000054FB9F0100004066AA074066AA0740AA6A0640A6AA06 DATA 00005455AA75400600004097D601000040DA6A0740DA6A07409BA607406B9A07 DATA 000040E6E91100010000D07AAD06000040F9A60140F9A60100E9AA0100A9AE01 DATA 000040A6770000000000946A5515000040AA6A0440AA6A04005DD501005DD501 DATA 000041AA9E050000000000000000000000E41B0000E41B0000F57F0100F57F01 DATA 5455D6A9E71A000000000000000000000095170000955700405FD501005DD507 DATA A9AA6657DD1A0000000000000000000000A91D0000A9F501D0FDFF0740FFFF1D DATA A4AA66AE57050000000000000000000000547F000054BD06F4DD7D1FD0F7DD7D DATA 505556A9DE010000000000000000000000547D00005495016455556FE4575565 DATA 000091FD67000000000000000000000000FD690000DD7F0050FFFF69A4FDFF17 DATA 0000D05699050000000000000000000000D55700407EBF0140D7A91750AB5D07 DATA 0000A57AA55E0000000000000000000040AD7A015069690550AA55054055A916 DATA 4054450040544500405445000051150100000000000000000000000000000000 DATA 00691A0000A91A0000691A0000A46A0000400100540000000000000000000000 DATA 50AA6A0044AA6A0440AA6A0110A9A61100D00600A40100000000000000000000 DATA 40A6A70190B7B70190B666004066AA0600D41700A40600000000000000000000 DATA D0AA9901909999019099EA0140AA6A06006419009F1B00000000000000000000 DATA D0A9A70190B7B70190B6DA01409BA60700B41E057F6E00000000000000000000 DATA 409A6F01406E6E0050BE690000E9AA0100D0471AFCB901000000000000000000 DATA 10A9AA0140AA6A0090AA1A01005DD50100D09665F0E706000000000000000000 DATA 00E41B00405B790000F9060000F57F0100F47265C09F1B1D0000000000000000 DATA 00D456005056650140E50500405FD50740014019007F6E070000000000000000 DATA 00746A0074A95A07406A0700D0FDFF1D50F4EB0400FC79010000000000000000 DATA 00F415007DF5571F00D50700F4DDDD7D50F76B0700F0D7010000000000000000 DATA 00F415005957751900D50700645555655C57553700C05D070000000000000000 DATA 00A47F00D4BFFF0540BF060050FFFF177FFDEFFD00C0F7190000000000000000 DATA 00D45700D075D70140F5050040D75D07FC55553F00C0C1170000000000000000 DATA 00AD7A01946AAA0550AB1E0050AAA916000000000000000F0000000000000000 DATA "" '' action definition. Each action may contain multiple animations from '' different angles. The total amount of frames per action is the number of '' frames per angle multiplied by the number of view points. The first data '' statement contains the frameCount, the number of viewAngles, the animMode '' (how the animation plays,) and animDelay (the number of game ticks each '' frame stays on screen) for the action. The subsequent DATA statements '' contain the coordinates of each frame: the top left coordinate, the width '' and height, and the origin point. Important: the order in which animations '' are defined matches the constants defined earlier in the program. ' action 0: actHeroWalk (2 frames, 4 views, loop, every 15 ticks) DATA 2, 4, 0, 15 DATA 0,0, 16,16, 8,15 DATA 16,0, 16,16, 8,15 DATA 32,0, 16,16, 8,15 DATA 48,0, 16,16, 8,15 DATA 64,0, 16,16, 8,15 DATA 80,0, 16,16, 8,15 DATA 96,0, 16,16, 8,15 DATA 112,0, 16,16, 8,15 ' action 1: actHeroStand (1 frame, 4 views, freeze, every 400 ticks) DATA 1, 4, 2, 400 DATA 0,16, 16,16, 8,15 DATA 16,16, 16,16, 8,15 DATA 32,16, 16,16, 8,15 DATA 48,16, 16,16, 8,15 ' action 2: actHeroExplode (4 frames, 1 view, oscil., every 5 ticks) DATA 4, 1, 1, 5 DATA 64,16, 16,16, 8,15 DATA 80,16, 16,16, 8,15 DATA 96,16, 16,16, 8,15 DATA 112,16, 16,16, 8,15 ' action 3: actHeroAttack (1 frame, 4 views, freeze, every 400 ticks) DATA 1, 4, 0, 400 DATA 0,32, 24,16, 8,15 DATA 24,32, 16,24, 8,15 DATA 0,48, 24,16, 16,15 DATA 40,32, 16,24, 8,23 ' action 4: actZombieWalk (2 frames, 4 views, loop, every 18 ticks) DATA 2, 4, 0, 18 DATA 64,32, 16,16, 8,15 DATA 80,32, 16,16, 8,15 DATA 96,32, 16,16, 8,15 DATA 112,32, 16,16, 8,15 DATA 64,48, 16,16, 8,15 DATA 80,48, 16,16, 8,15 DATA 96,48, 16,16, 8,15 DATA 112,48, 16,16, 8,15 ' action 5: actZombieStand (1 frame, 4 views, freeze, every 400 ticks) DATA 1, 4, 0, 400 DATA 0,64, 16,16, 8,15 DATA 16,64, 16,16, 8,15 DATA 32,64, 16,16, 8,15 DATA 48,64, 16,16, 8,15 ' action 6: actItemPotion (1 frame, 1 view, freeze, every 400 ticks) DATA 1, 1, 2, 400 DATA 64,64, 16,16, 8,15 ' action 7: actItemSword (1 frame, 1 view, freeze, every 400 ticks) DATA 1, 1, 2, 400 DATA 80,64, 16,16, 8,15 '' '' let actor(id) perform action(actId) '' SUB actorSetAction (id AS INTEGER, actId AS INTEGER) ' prevent action from resetting itself IF (actId = actor(id).actIndex) THEN EXIT SUB ' change action actor(id).actIndex = actId IF (actId < 0) THEN EXIT SUB ' return to the first frame actor(id).anmFrame = 0 ' restart countdown actor(id).anmFrameNext = action(actId).animDelay ' animate "forward" actor(id).anmFrameVec = 1 ' force new facing index actorSetAngle id, actor(id).angle END SUB '' '' change actor(id)'s angle '' SUB actorSetAngle (id AS INTEGER, degrees AS INTEGER) DIM viewAngle AS INTEGER, angle AS INTEGER ' keep angle in range 0 to 359 angle = degrees IF (angle > 359) THEN angle = angle MOD 360 ELSE WHILE (angle < 0) angle = angle + 360 WEND END IF ' get animation angle coverage viewAngle = action(actor(id).actIndex).viewAngle ' assign new angle, update facing index actor(id).angle = angle actor(id).anmFacing = ((angle + viewAngle \ 2) \ viewAngle) MOD (360 \ viewAngle) ' movement vector: actor(id).vecX = COS(angle * .01745329#) * 5 actor(id).vecY = SIN(angle * .01745329#) * 5 END SUB '' '' capture graphics from active video page. X and Y are the upper-left '' corner of the image to capture, W and H the width and height in pixels, '' and OX and OY the origin point of the captured image so we can adjust '' the PUT statement coordinates. '' SUB getImage (x AS INTEGER, y AS INTEGER, w AS INTEGER, h AS INTEGER, oX AS INTEGER, oY AS INTEGER) DIM imgSize AS INTEGER ' bits per plane and number of planes for ' mode 7 (mode 13 is bpp = 8, planes = 1) CONST bpp = 1 CONST planes = 4 ' get the image size, in INTEGERS imgSize = 4 + INT((w * bpp + 7) / 8) * planes * h imgSize = (imgSize + (imgSize AND 1)) \ 2 'capture image to gBuffer(), starting at element gBufferOfs GET (x, y)-STEP(w - 1, h - 1), gBuffer(gBufferOfs) LINE (x, y)-STEP(w - 1, h - 1), 5, B ' register image in the collection gImage(gImageCnt).ofs = gBufferOfs gImage(gImageCnt).x = oX gImage(gImageCnt).y = oY ' advance cursor in gImage() and gBuffer() gImageCnt = gImageCnt + 1 gBufferOfs = gBufferOfs + imgSize END SUB '' '' decode graphics, configure each action and load frames into the array. '' SUB initDemo DIM id AS INTEGER, cnt AS INTEGER, vws AS INTEGER, mde AS INTEGER DIM dly AS INTEGER, x AS INTEGER, y AS INTEGER, w AS INTEGER DIM h AS INTEGER, oX AS INTEGER, oY AS INTEGER DIM s AS STRING, p AS INTEGER, clr(3) AS INTEGER ' enter mode 7 SCREEN 7, 0, 0, 0 s = "Loading...": y = 10: GOSUB initDemoPrint ' decode each line (2 bits per pixel) s = "Decoding graphics": y = 11: GOSUB initDemoPrint y = 0 clr(0) = 0: clr(1) = 8: clr(2) = 15: clr(3) = 7 DO READ s IF LEN(s) THEN x = 0 FOR i% = 0 TO LEN(s) \ 2 - 1 p = VAL("&h" + MID$(s, i% * 2 + 1, 2)) PSET (x, y), clr(p AND &H3) PSET (x + 1, y), clr((p \ 4) AND &H3) PSET (x + 2, y), clr((p \ 16) AND &H3) PSET (x + 3, y), clr((p \ 64) AND &H3) x = x + 4 NEXT i% y = y + 1 ELSE EXIT DO END IF LOOP ' for each action s = "Loading actions": y = 12: GOSUB initDemoPrint FOR id = 0 TO actionCount - 1 ' read the frame count per view, views, mode and delay READ cnt, vws, mde, dly ' configure action(id).viewAngle = 360 \ vws action(id).frameFirst = gImageCnt action(id).frameCount = cnt action(id).animMode = mde action(id).animDelay = dly ' capture all the frames needed for this action FOR frm = 0 TO action(id).frameCount * vws - 1 ' read the coordinates, width, height, and origin READ x, y, w, h, oX, oY ' store getImage x, y, w, h, oX, oY NEXT frm NEXT id ' initialize actors (force "no action") s = "Init. actors": y = 13: GOSUB initDemoPrint FOR id = 0 TO actorCount - 1 actor(id).actIndex = -1 NEXT id actorSetAction 0, actHeroStand actor(0).x = 160: actor(0).y = 100 actor(0).behavior = isPlayer actor(0).speedCD = 8 actorSetAction 1, actZombieStand actor(1).x = 64: actor(1).y = 20 actor(1).behavior = isZombie actor(1).speedCD = 10 actorSetAction 2, actZombieStand actor(2).x = 220: actor(2).y = 28 actor(2).behavior = isZombie actor(2).speedCD = 10 actorSetAction 3, actZombieStand actor(3).x = 80: actor(3).y = 135 actor(3).behavior = isZombie actor(3).speedCD = 10 actorSetAction 4, actItemSword actor(4).x = 110: actor(4).y = 140 actor(4).behavior = isSword actorSetAction 5, actItemPotion actor(5).x = 130: actor(5).y = 154 actor(5).behavior = isPotion ' and we're finally done EXIT SUB initDemoPrint: SCREEN , , 0, 0 ' switch to active page 0, view page 0 LOCATE y, 21 - LEN(s) \ 2: PRINT s ' center print SCREEN , , 1, 0 ' switch to active page 1, view page 0 RETURN END SUB '' '' update actor animation '' SUB mainActorAnim (id AS INTEGER) DIM tempAct AS actionType ' if the action index is below 0, skip IF (actor(id).actIndex < 0) THEN EXIT SUB ' if the countdown is not yet 0... IF (actor(id).anmFrameNext) THEN ' ...decrease countdown actor(id).anmFrameNext = actor(id).anmFrameNext - 1 ' if the countdown reached 0... ELSE ' shorthand to the action in action() tempAct = action(actor(id).actIndex) ' ...reset countdown actor(id).anmFrameNext = tempAct.animDelay ' ...increase (or decrease) the frame number actor(id).anmFrame = actor(id).anmFrame + actor(id).anmFrameVec ' if the current frame is under 0 (we went back too far) IF (actor(id).anmFrame = -1) THEN actor(id).anmFrame = 0 ' reset to 0 actor(id).anmFrameVec = 1 ' go forth again ' if the current frame is over the frame count for this action... ELSEIF (actor(id).anmFrame = tempAct.frameCount) THEN SELECT CASE tempAct.animMode ' restart to 0 if the action mode is animLoop CASE animLoop actor(id).anmFrame = 0 ' cap frame number and go backward if action mode is animOscil CASE animOscil actor(id).anmFrame = actor(id).anmFrame - 1 actor(id).anmFrameVec = -1 ' cap frame number and stop if action mode is animFreeze CASE ELSE actor(id).anmFrame = actor(id).anmFrame - 1 actor(id).anmFrameVec = 0 END SELECT END IF END IF END SUB '' '' clipping actor coordinates '' SUB mainActorBoundaries (id AS INTEGER) IF (actor(id).x < 32) THEN actor(id).x = 32 ELSEIF (actor(id).x > 298) THEN actor(id).x = 298 END IF IF (actor(id).y < 32) THEN actor(id).y = 32 ELSEIF (actor(id).y > 199) THEN actor(id).y = 199 END IF END SUB '' '' aimless wandering '' SUB mainActorMove (id AS INTEGER) IF (actor(id).speed) THEN actor(id).speed = actor(id).speed - 1 EXIT SUB ELSE actor(id).speed = actor(id).speedCD SELECT CASE actor(id).travel CASE IS > 0 actor(id).travel = actor(id).travel - 1 actor(id).x = actor(id).x + actor(id).vecX actor(id).y = actor(id).y + actor(id).vecY CASE IS < 0 actor(id).travel = actor(id).travel + 1 CASE ELSE actorSetAngle id, INT(RND * 360) actor(id).travel = INT(RND * 51) - 25 END SELECT END IF END SUB '' '' main routine, where the magic happens '' SUB mainLoop DIM tCurrent AS SINGLE, tPrevious AS SINGLE, tElapsed AS SINGLE DIM tLag AS SINGLE, id AS INTEGER, newAngle AS INTEGER, user AS STRING tPrevious = TIMER DO ' update timer tCurrent = TIMER tElapsed = tCurrent - tPrevious tLag = tLag + tElapsed tPrevious = tCurrent ' update game logic until we're all caught up WHILE (tLag > .01) '' GAME LOGIC '' ' parse every actor FOR id = 0 TO actorCount - 1 ' actor behavior SELECT CASE actor(id).behavior ' zombie behavior CASE isZombie mainActorMove id IF (actor(id).travel > 0) THEN actorSetAction id, actZombieWalk ELSE actorSetAction id, actZombieStand END IF ' player behavior CASE isPlayer ' check input user = INKEY$ SELECT CASE LEN(user) CASE 1 SELECT CASE ASC(user) CASE 27 ' escape EXIT DO CASE 32 ' space actorSetAction id, actHeroAttack END SELECT CASE 2 newAngle = -1 SELECT CASE CVI(user) CASE &H4D00 ' walk right (toggle) newAngle = 0 CASE &H5000 ' walk down (toggle) newAngle = 90 CASE &H4B00 ' walk left (toggle) newAngle = 180 CASE &H4800 ' walk up (toggle) newAngle = 270 END SELECT IF (newAngle <> -1) THEN IF (newAngle = actor(id).angle) AND (actor(id).actIndex = actHeroWalk) THEN actorSetAction id, actHeroStand ELSE actorSetAction id, actHeroWalk actorSetAngle id, newAngle END IF END IF END SELECT ' should move? IF (actor(id).actIndex = actHeroWalk) THEN IF (actor(id).speed) THEN actor(id).speed = actor(id).speed - 1 ELSE actor(id).speed = actor(id).speedCD actor(id).x = actor(id).x + actor(id).vecX actor(id).y = actor(id).y + actor(id).vecY END IF END IF END SELECT ' clip coordinates (same for everyone) mainActorBoundaries id ' animation system (same for everyone) mainActorAnim id NEXT id ' congrats, you caught up by 0.01 seconds! tLag = tLag - .01 WEND '' RENDER '' mainRender LOOP END SUB '' '' Render screen '' SUB mainRender DIM tempImg AS imageType, tempAct AS actionType ' work on page 1, display 0 SCREEN , , 1, 0 ' clear screen CLS ' parse every actor FOR id = 0 TO actorCount - 1 ' if the action index is below 0, skip IF NOT (actor(id).actIndex < 0) THEN ' shorthand to the action in action() tempAct = action(actor(id).actIndex) ' shorthand to the image data in gImage() tempImg = gImage(tempAct.frameFirst + tempAct.frameCount * actor(id).anmFacing + actor(id).anmFrame) ' draw the actor PUT (actor(id).x - tempImg.x, actor(id).y - tempImg.y), gBuffer(tempImg.ofs), PSET END IF NEXT id ' copy page 1 (invisible) to 0 (visible) PCOPY 1, 0 END SUB