scrolling dodge 'em up

Ask and answer questions about making games and related topics. Unrelated topics go in that other forum.

Moderators: marionline, SDHawk

User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

scrolling dodge 'em up

Post by Willy Elektrix »

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.
User avatar
Bob the Hamster
Lord of the Slimes
Posts: 7660
Joined: Tue Oct 16, 2007 2:34 pm
Location: Hamster Republic (Ontario Enclave)
Contact:

Post by Bob the Hamster »

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:

Code: Select all

while(not(time to quit)) do(
  Move the player
  Do any other updates
  Check for collisions
  wait(1)
)
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.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

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.
Thanks for your awesome explanation. It took me about 2 hours of reading and fooling around to make this work. Your help was intrinsic.

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.
TMC
Metal King Slime
Posts: 4308
Joined: Sun Apr 10, 2011 9:19 am

Post by TMC »

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

Code: Select all

set slice velocity x (scrollslice, 1) 
to

Code: Select all

set slice velocity x (playerslice, 1) 
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.
Last edited by TMC on Thu Jan 05, 2017 5:53 am, edited 2 times in total.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

TMC wrote:

Code: Select all

set slice velocity x (scrollslice, 1) 
to

Code: Select all

set slice velocity x (playerslice, 1) 
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.

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.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

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?

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.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

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
TMC
Metal King Slime
Posts: 4308
Joined: Sun Apr 10, 2011 9:19 am

Post by TMC »

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).
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.
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 
Equivalently "slice edge y (playerslice, edge:top) + 19", etc.

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 
But that doesn't solve any problem you have right now, and as you can see, using putslice is actually much simpler.
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?
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.txt
Last edited by TMC on Fri Jan 06, 2017 10:23 am, edited 2 times in total.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

Just wanted to let you know that I've figured out most of the kinks. I'm going to develop a short demo of this game to see if it's any fun. I'll let you know when I have something to show.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

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".

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
User avatar
Bob the Hamster
Lord of the Slimes
Posts: 7660
Joined: Tue Oct 16, 2007 2:34 pm
Location: Hamster Republic (Ontario Enclave)
Contact:

Post by Bob the Hamster »

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:

Code: Select all

global variable (109, input delay)
Now, if this variable is zero, then you check for movement, and if movement happens, set

Code: Select all

input delay := 3
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?
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

Bob:

Your idea worked like a charm. I also used it to slow down the animation speed on the player slice. Thanks!

This game is starting to shape up!
TMC
Metal King Slime
Posts: 4308
Joined: Sun Apr 10, 2011 9:19 am

Post by TMC »

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.

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 &#40;abs&#40;queued move&#41; < move speed&#41; then &#40;
  if &#40;key is pressed &#40;key&#58; down&#41;&#41; then &#40;queued move += 10&#41;
  if &#40;key is pressed &#40;key&#58; up&#41;&#41; then &#40;queued move -= 10&#41;
&#41;

# "move" is amount to move this tick
variable&#40;move&#41;
if &#40;queued move > move speed&#41; then &#40;
  move &#58;= move speed
&#41; else if &#40;queued move < -1 * move speed&#41; then &#40;
  move &#58;= -1 * move speed
&#41; else &#40;
  # Only a few pixels left to go; complete the move
  move &#58;= queued move
&#41;
queued move -= move
set slice y&#40;playerslice, slice y&#40;playerslice&#41; + move&#41;
You would still use a completely separate "input delay" variable to control the animation speed.
Last edited by TMC on Sun Jan 15, 2017 10:26 am, edited 3 times in total.
User avatar
Willy Elektrix
Liquid Metal Slime
Posts: 910
Joined: Sun Aug 15, 2010 11:30 pm

Post by Willy Elektrix »

This definitely looks smooth, although there is a side effect that I'm not sure I understand.

Code: Select all

###Player movement inputs
if &#40;inputdelay == 0&#41; then &#40;
	if &#40;key is pressed &#40;key&#58; down&#41;&#41; then &#40;
		set slice y&#40;playerslice, slice y&#40;playerslice&#41; + 10&#41;
		inputdelay &#58;= 3
	&#41;
	if &#40;key is pressed &#40;key&#58; up&#41;&#41; then &#40;
		set slice y&#40;playerslice, slice y&#40;playerslice&#41; -- 10&#41;
		inputdelay &#58;=3
	&#41;
&#41;
if &#40;inputdelay >> 0&#41; then &#40;decrement &#40;inputdelay, 1&#41;&#41;
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?
Post Reply