scrolling dodge 'em up
Moderators: marionline, SDHawk
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
scrolling dodge 'em up
I am conceptualizing another game for the OHR Multicart project. I want to make a game where the screen scrolls constantly to the right. The hero is on the left side of the screen. Your goal is to navigate the hero around pieces of terrain (i.e. fences, trees, walls) without touching them. Imagine a scrolling shooting, except you don't shoot and there are no "enemies", only fixed pieces of terrain to avoid.
I've only ever used OHR to make RPGs. I don't know much about making this sort of game. Can anyone give me a few clues to get started? I don't expect you to write the whole game for me.
For instance, I know that I will probably need to have pixel-based movement. I also know that i will need to implement some kind of scrolling and collision detection. Does anyone have some scripts I can look at to get started?
Thanks for your help. Sorry for the open ended nature of this question.
I've only ever used OHR to make RPGs. I don't know much about making this sort of game. Can anyone give me a few clues to get started? I don't expect you to write the whole game for me.
For instance, I know that I will probably need to have pixel-based movement. I also know that i will need to implement some kind of scrolling and collision detection. Does anyone have some scripts I can look at to get started?
Thanks for your help. Sorry for the open ended nature of this question.
- Bob the Hamster
- Lord of the Slimes
- Posts: 7660
- Joined: Tue Oct 16, 2007 2:34 pm
- Location: Hamster Republic (Ontario Enclave)
- Contact:
First I would make a very long narrow map. I would put my obstacles on the map and use regular wallmaps.
For this game I don't see any obvious advantage to using a hero or an NPC, so I would load a walkabout sprite slice for the player. I would set its parent to be a layer of the map
There would be a script loop that does something like:
The player sprite would move right automatically, or up and down if the arrow key presses are detected.
To make the camera move, you could just do "camera follows slice" on the player, but then the player is in the center. Maybe the player should be left of center? An easy solution to that is to make an invisible container slice and set its parent to be the player sprite. Set its x position a bit to the right, like maybe 100 in the x coordinate, and it will move automatically with the player. Make the camera follow this invisible slice instead
For collisions, you can convert the player's pixel position into tile coordinates by dividing both the x and the y by 20. I would probably do this 4 times, once for each corner of the sprite slice. Getting the corner pixel coordinates is easy with "slice edge x/y"
If you only have solid passible/impassable tiles, then you can crack collisions with a very simple "read pass block" for each corner.
If you want full support for walls that are only on one edge of a tile, the collision checks get more complicated, but they are still totally doable.
For this game I don't see any obvious advantage to using a hero or an NPC, so I would load a walkabout sprite slice for the player. I would set its parent to be a layer of the map
There would be a script loop that does something like:
Code: Select all
while(not(time to quit)) do(
Move the player
Do any other updates
Check for collisions
wait(1)
)
To make the camera move, you could just do "camera follows slice" on the player, but then the player is in the center. Maybe the player should be left of center? An easy solution to that is to make an invisible container slice and set its parent to be the player sprite. Set its x position a bit to the right, like maybe 100 in the x coordinate, and it will move automatically with the player. Make the camera follow this invisible slice instead
For collisions, you can convert the player's pixel position into tile coordinates by dividing both the x and the y by 20. I would probably do this 4 times, once for each corner of the sprite slice. Getting the corner pixel coordinates is easy with "slice edge x/y"
If you only have solid passible/impassable tiles, then you can crack collisions with a very simple "read pass block" for each corner.
If you want full support for walls that are only on one edge of a tile, the collision checks get more complicated, but they are still totally doable.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
Thanks for your awesome explanation. It took me about 2 hours of reading and fooling around to make this work. Your help was intrinsic.Bob the Hamster wrote:For collisions, you can convert the player's pixel position into tile coordinates by dividing both the x and the y by 20. I would probably do this 4 times, once for each corner of the sprite slice. Getting the corner pixel coordinates is easy with "slice edge x/y"
If you only have solid passible/impassable tiles, then you can crack collisions with a very simple "read pass block" for each corner.
The scrolling and movement seem to be working. I'm having trouble with the collision detection. I thought I understood it, but as I try different things, I realize that I'm not even close. I believe this relates to my (lack of) understanding of the "read pass block" command.
How far off am I?
Code: Select all
plotscript, movement, begin
variable (playerslice, scrollslice, playerleft, playerright, playertop, playerbottom, pass)
playerslice := load walkabout sprite (1)
scrollslice := lookup slice(sl:map layer1)
set parent (playerslice, scrollslice)
camera follows slice (playerslice)
set slice velocity x (scrollslice, 1)
while (check tag (tag:levelcomplete) == off) do (
if (key is pressed (key: down)) then (move slice by (playerslice, 0, 2, 1))
if (key is pressed (key: up)) then (move slice by (playerslice, 0, -2, 1))
playerleft := (slice edge x (playerslice, edge:left)) / 20
playerright := (slice edge x (playerslice, edge:right)) / 20
playertop := (slice edge y (playerslice, edge:top)) / 20
playerbottom := (slice edge y (playerslice, edge:bottom)) / 20
pass := read pass block (playerleft,playertop)
if (pass,and,southwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playertop)
if (pass,and,eastwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playertop)
if (pass,and,southwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playertop)
if (pass,and,eastwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,northwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,eastwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,northwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,eastwall) then (set tag (tag:levelcomplete,on))
wait (1)
)
game over
end
Last edited by Willy Elektrix on Thu Jan 05, 2017 4:55 am, edited 1 time in total.
Your mistake is moving map layer 1 (scrollslice) instead of moving the player. What is on layer 1? Nothing? Anything on that layer wouldn't move relative to the camera.
Note that the slice edge x/y commands give the slice offset relative to its parent. Since the player is currently fixed relative to map layer 1, aside from moving up/down, playerleft will always be zero.
Change
to
Next, you checks for north/south walls appear correct for a 20x20 player hitbox, but the checks for eastwall mean that you collide as soon as you overlap a tile with an eastern wall; that is, you collide 20 pixels before the wall. Just change eastwall to westwall and it'll be right.
Note that the slice edge x/y commands give the slice offset relative to its parent. Since the player is currently fixed relative to map layer 1, aside from moving up/down, playerleft will always be zero.
Change
Code: Select all
set slice velocity x (scrollslice, 1)
Code: Select all
set slice velocity x (playerslice, 1)
Last edited by TMC on Thu Jan 05, 2017 5:53 am, edited 2 times in total.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
Hm. Then the playerslice stops scrolling right whenever it moves up and down. I'm trying to work out a way that is continuously scrolling. Let me try a few things.TMC wrote:toCode: Select all
set slice velocity x (scrollslice, 1)
Code: Select all
set slice velocity x (playerslice, 1)
Also, there is some strange behavior with the collision detection I noticed. The top edge of the walkabout can be directly adjacent to the south edge of a wall without a pixel between them.
However, if the bottom edge of the tile is directly adjacent to the north edge of a wall, it will trigger a collision (even if they do not overlap).
Last edited by Willy Elektrix on Fri Jan 06, 2017 1:46 am, edited 1 time in total.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
Alright. See below.
I made it so the scrolling works, but the collision detection is pretty wonky. A big problem is that the scrolling is fast (10 pixels per tick) so if the player hits something, he is 10 pixels past it when the detection actually happens. In other words, the player can touch the corner of a box without a realizing it, and the die 10 pixels beyond it.
I'll see what I can figure out. Maybe if I reorder the lines in the loop...?
Also, I tried to look at the script files for SpaceInvadersOHR. Only the RPG and HSS files are included. I can open the HSS file in a text editor, but all the line breaks are gone. How can I actually look at the scripts?
I made it so the scrolling works, but the collision detection is pretty wonky. A big problem is that the scrolling is fast (10 pixels per tick) so if the player hits something, he is 10 pixels past it when the detection actually happens. In other words, the player can touch the corner of a box without a realizing it, and the die 10 pixels beyond it.
I'll see what I can figure out. Maybe if I reorder the lines in the loop...?
Also, I tried to look at the script files for SpaceInvadersOHR. Only the RPG and HSS files are included. I can open the HSS file in a text editor, but all the line breaks are gone. How can I actually look at the scripts?
Code: Select all
plotscript, movement, begin
variable (playerslice, scrollslice, playerleft, playerright, playertop, playerbottom, pass)
playerslice := load walkabout sprite (1)
scrollslice := lookup slice(sl:map layer1)
set parent (playerslice, scrollslice)
camera follows slice (playerslice)
while (check tag (tag:levelcomplete) == off) do (
set slice velocity x (playerslice, 10)
if (key is pressed (key: down)) then (move slice by (playerslice, 10, 10, 1))
if (key is pressed (key: up)) then (move slice by (playerslice, 10, -10, 1))
playerleft := (slice edge x (playerslice, edge:left)) / 20
playerright := (slice edge x (playerslice, edge:right)) / 20
playertop := (slice edge y (playerslice, edge:top)) / 20
playerbottom := (slice edge y (playerslice, edge:bottom)) / 20
pass := read pass block (playerleft,playertop)
if (pass,and,southwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playertop)
if (pass,and,westwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playertop)
if (pass,and,southwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playertop)
if (pass,and,westwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,northwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,westwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,northwall) then (set tag (tag:levelcomplete,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,westwall) then (set tag (tag:levelcomplete,on))
wait (1)
)
game over
end
Last edited by Willy Elektrix on Fri Jan 06, 2017 2:14 am, edited 3 times in total.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
Willy Elektrix wrote:Alright. See below.
I made it so the scrolling works, but the collision detection is pretty wonky. A big problem is that the scrolling is fast (10 pixels per tick) so if the player hits something, he is 10 pixels past it when the detection actually happens. In other words, the player can touch the corner of a box without a realizing it, and the die 10 pixels beyond it.
I'll see what I can figure out. Maybe if I reorder the lines in the loop...?
Code: Select all
plotscript, movement, begin variable (playerslice, scrollslice, playerleft, playerright, playertop, playerbottom, pass) playerslice := load walkabout sprite (1) scrollslice := lookup slice(sl:map layer1) set parent (playerslice, scrollslice) camera follows slice (playerslice) while (check tag (tag:levelcomplete) == off) do ( set slice velocity x (playerslice, 10) if (key is pressed (key: down)) then (move slice by (playerslice, 10, 10, 1)) if (key is pressed (key: up)) then (move slice by (playerslice, 10, -10, 1)) playerleft := (slice edge x (playerslice, edge:left)) / 20 playerright := (slice edge x (playerslice, edge:right)) / 20 playertop := (slice edge y (playerslice, edge:top)) / 20 playerbottom := (slice edge y (playerslice, edge:bottom)) / 20 pass := read pass block (playerleft,playertop) if (pass,and,southwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerleft,playertop) if (pass,and,westwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerright,playertop) if (pass,and,southwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerright,playertop) if (pass,and,westwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerleft,playerbottom) if (pass,and,northwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerleft,playerbottom) if (pass,and,westwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerright,playerbottom) if (pass,and,northwall) then (set tag (tag:levelcomplete,on)) pass := read pass block (playerright,playerbottom) if (pass,and,westwall) then (set tag (tag:levelcomplete,on)) wait (1) ) game over end
Oh, that's because the bottom and right edges are exactly 20 pixels below/right of the top/left edges, which means they are actually one pixel past. You might say this is wrong (it's surprising) but there is a good reason for it: a slice anchored to the right edge of its parent is anchored to a point 1 pixel right of it, so that they're side by side without overlap or gap.Willy Elektrix wrote:However, if the bottom edge of the tile is directly adjacent to the north edge of a wall, it will trigger a collision (even if they do not overlap).
To fix it, just write:
Code: Select all
playerright := (slice edge x (playerslice, edge:right) -- 1) / 20
playerbottom := (slice edge y (playerslice, edge:bottom) -- 1) / 20
The problem of the collision being detected one tick later is an engine limitation: scripts run first, then slice velocity commands take effect, then the screen is drawn. So either you have to avoid all commands for moving slices (and use just "put slice" instead), or compensate by calculating where the slice is going to be when the velocity is enacted: add 10 to the x position and 0, 10 or -10 to the y position (before dividing by 20). But notice that now you're exactly duplicating the work of the move slice by/set slice velocity x commands, so there's actually no point using them, you might as well calculate the new x/y and then put playerslice there with putslice. That's how SpaceInvadersOHR and all OHR platformers do it.
Or a crude workaround: turn up the frame rate and decrease the pixels per tick, so that the ship only overlaps by a few pixels before exploding. Why wouldn't you want smooth scrolling anyway?
As an aside, another way you could have solved your scrolling problem would be to parent the playerslice to another slice that moves across at a constant velocity (like what you had in the first place, but using a container instead of map layer), and calculating the position of playerslice relative to the actual map by adding together the two offsets:
Code: Select all
playerleft := (slice x (scrollingcontainer) + slice edge x (playerslice, edge:left)) / 20
That means the file has Unix line endings. Almost every text editor on the planet aside from Notepad can tolerate Unix line endings. Even Wordpad might work. Alternatively, here it is: https://hastebin.com/ajegayujiv.txtI can open the HSS file in a text editor, but all the line breaks are gone. How can I actually look at the scripts?
Last edited by TMC on Fri Jan 06, 2017 10:23 am, edited 2 times in total.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
I've been experimenting with making the game run at 60 FPS which is much nicer for a game of this sort. However, there is a problem.
I want the player to move 10 pixels at a time with a key press. This is really important for the sake of the level design. This is done with "put slice" commands.
Running at 60 FPS, moving the the player 10 pixels at a time is way too fast and the key presses are too responsive. So there is a "wait (3)" which slows down the loop which handles input and collision detection.
This completely screws with the collision detection. The player scrolls with a "set velocity" command which is not effect by the "wait (3)". As a result, the player might hit an object and not be killed if the collision happens during the "wait".
I want the player to move 10 pixels at a time with a key press. This is really important for the sake of the level design. This is done with "put slice" commands.
Running at 60 FPS, moving the the player 10 pixels at a time is way too fast and the key presses are too responsive. So there is a "wait (3)" which slows down the loop which handles input and collision detection.
This completely screws with the collision detection. The player scrolls with a "set velocity" command which is not effect by the "wait (3)". As a result, the player might hit an object and not be killed if the collision happens during the "wait".
Code: Select all
global variable (500,lives)
global variable (501,level)
global variable (502,score)
global variable (503,zonescore)
#####MAIN GAME SCRIPT#####
plotscript, movement, begin
suspend player
#Set variables on new game
if (check tag (tag:gamenew) == off) then (
lives := 50
level := 1
score := 0
set tag (tag:gamenew,on)
)
#Next level
teleport to map (level--1)
###Declares slices
variable (playerslice, scrollslice, containerslice, deadslice, playerleft, playerright, playertop, playerbottom, pass,
scrollspeed)
playerslice := load walkabout sprite (1)
containerslice := load walkabout sprite (0)
scrollslice := lookup slice(sl:map layer1)
set parent (playerslice, scrollslice)
set parent (containerslice, playerslice)
put slice (playerslice,0,80)
set slice x(containerslice, slice x(playerslice) +120)
camera follows slice (containerslice)
#Determine if turbo/reverse zone
if (level == 4) then (
scrollspeed := 8
set tag (tag:reverse, off)
show text box (5)
wait for text box
) else if (level == 7) then (
scrollspeed := 5
set tag (tag:reverse, on)
show text box (6)
wait for text box
) else (
scrollspeed := 5
set tag (tag:reverse, off)
show text box (4)
wait for text box
)
###Set scrolling speed
set slice velocity(playerslice, scrollspeed, 0)
#####MAIN GAME LOOP#####
while (check tag (tag:levelcomplete)==off,and,check tag (tag:playerdead)==off,and,check tag(tag:reverse)==off) do (
###Animate sprite
if (get sprite frame (playerslice) == 0) then (set sprite frame (playerslice,1)
) else if (get sprite frame (playerslice) == 1) then (set sprite frame (playerslice,0))
###Player movement inputs
if (key is pressed (key: down)) then (set slice y(playerslice, slice y(playerslice) + 10))
if (key is pressed (key: up)) then (set slice y(playerslice, slice y(playerslice) -- 10))
###Collision detection
playerleft := slice edge x (playerslice, edge:left) / 20
playerright := slice edge x (playerslice, edge:right--1) / 20
playertop := slice edge y (playerslice, edge:top) / 20
playerbottom := slice edge y (playerslice, edge:bottom--1) / 20
pass := read pass block (playerleft,playertop)
if (pass,and,southwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerleft,playertop)
if (pass,and,westwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerright,playertop)
if (pass,and,southwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerright,playertop)
if (pass,and,westwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,northwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerleft,playerbottom)
if (pass,and,westwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,northwall) then (set tag (tag:playerdead,on))
pass := read pass block (playerright,playerbottom)
if (pass,and,westwall) then (set tag (tag:playerdead,on))
if (pass,and,harmtile) then (set tag (tag:levelcomplete,on))
#####END OF MAIN LOOP#####
wait (3)
)
if (check tag (tag:playerdead) == on) then (###Player Dead
###Death animation
deadslice := load walkabout sprite (2)
set parent (deadslice, scrollslice)
put slice (deadslice, slice x (playerslice), slice y (playerslice))
free slice (playerslice)
set tag (tag:playerdead, off)
decrement (lives,1)
wait (2)
set sprite frame (deadslice,1)
wait (2)
set sprite frame (deadslice,0)
wait (2)
set sprite frame (deadslice,1)
if (lives >> 0) then (###Continue
show text box (1)
) else (###Game Over
show text box (2)
wait for text box
game over
)
) else if (check tag (tag:levelcomplete) == on) then (###Level Complete
set tag (tag:levelcomplete, off)
zonescore := level * lives
increment (score, zonescore)
show text box (3)
increment (level,1)
)
#####END OF MAIN GAME SCRIPT####
end
- Bob the Hamster
- Lord of the Slimes
- Posts: 7660
- Joined: Tue Oct 16, 2007 2:34 pm
- Location: Hamster Republic (Ontario Enclave)
- Contact:
Yeah, I can see how a wait(3) would mess everything up, since it pauses all scripts, not just the input script.how about this:
Make a global variable like:
Now, if this variable is zero, then you check for movement, and if movement happens, set
If input delay is it zero, then you subtract 1 from input delay, and skip movement input checking entirely for that frame
Does that make any sense?
Make a global variable like:
Code: Select all
global variable (109, input delay)
Code: Select all
input delay := 3
Does that make any sense?
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
Why not go a little further and also perform the 10 pixel movement more smoothly, over the course of 3 ticks? Of course this means you need to hit up/down 3 ticks before you reach an obstacle, making the game more difficult.
Here's how you would implement this. 3 doesn't divide evenly into 10, so I went for moving the player 4 pixels per tick, which allows a movement every 2.5 ticks. Any movement speed will work.
You would still use a completely separate "input delay" variable to control the animation speed.
Here's how you would implement this. 3 doesn't divide evenly into 10, so I went for moving the player 4 pixels per tick, which allows a movement every 2.5 ticks. Any movement speed will work.
Code: Select all
define constant (4, move speed)
global variable (504, queued move)
# Can't move if already moving (unless there's less than one tick of movement to go)
if (abs(queued move) < move speed) then (
if (key is pressed (key: down)) then (queued move += 10)
if (key is pressed (key: up)) then (queued move -= 10)
)
# "move" is amount to move this tick
variable(move)
if (queued move > move speed) then (
move := move speed
) else if (queued move < -1 * move speed) then (
move := -1 * move speed
) else (
# Only a few pixels left to go; complete the move
move := queued move
)
queued move -= move
set slice y(playerslice, slice y(playerslice) + move)
Last edited by TMC on Sun Jan 15, 2017 10:26 am, edited 3 times in total.
- Willy Elektrix
- Liquid Metal Slime
- Posts: 910
- Joined: Sun Aug 15, 2010 11:30 pm
This definitely looks smooth, although there is a side effect that I'm not sure I understand.
My movement code used to look like this.
When using your code, the control is much less tight. When tapping the up/down key, I tend to accidentally move 20 instead of just 10. This is not a problem with my previous version of the code because the 3 tick delay prevented this.
I can reduce this version by making the "move speed" constant lower, bu then the player moves too slowly. I also tried implementing a "wait" after the input, but that just negates the smooth scrolling movement altogether. Any ideas on this?
Code: Select all
###Player movement inputs
if (inputdelay == 0) then (
if (key is pressed (key: down)) then (
set slice y(playerslice, slice y(playerslice) + 10)
inputdelay := 3
)
if (key is pressed (key: up)) then (
set slice y(playerslice, slice y(playerslice) -- 10)
inputdelay :=3
)
)
if (inputdelay >> 0) then (decrement (inputdelay, 1))
When using your code, the control is much less tight. When tapping the up/down key, I tend to accidentally move 20 instead of just 10. This is not a problem with my previous version of the code because the 3 tick delay prevented this.
I can reduce this version by making the "move speed" constant lower, bu then the player moves too slowly. I also tried implementing a "wait" after the input, but that just negates the smooth scrolling movement altogether. Any ideas on this?