전장 두꺼비 만들기 스크롤 배경 효과

'전두꺼비'의 터빈 터널 관문을 누가 싫어하겠는가?다음 gif와 같이 스크롤 배경을 분석하고 다시 만들기로 결정했습니다.

Battletoads(1991) 터빈 터널층

나는 Pico-8의 원시 재창작을 하고 있다.그 불가사의한 유사점을 봐라.😉

다음은 실제 게임을 캡처한 것입니다.자세히 살펴보면 정령의 가장자리와 반복되는 곳을 볼 수 있다.나는 직사각형과 흰색 화살표로 몇 개의 점을 표시했다.

이것은 내가 만든 배경이며, 나의 정령이 어떻게 반복되는지 보여준다.
참고: 예는 Pico-8 Fantasy 콘솔에서 Lua를 사용하여 작성되었습니다.함수는 나의 해석과 관련된 코드 발췌문을 포함하는데, 반드시 완전한 코드는 아니다.
내가 모든 코드를 삭제하고 다른 일을 하기 전에, 내가 배경을 굴리는 최초의 사고 과정을 설명하는 것을 참을성 있게 들어 주십시오.
우선, 나는 모든 배경 부분에 단독 그룹을 만들기로 결정했다.배열은 화면의 너비를 덮어쓰기 위해 충분한 요소를 최소한으로 수용할 것이다.모든 원소는 정령의 너비, x1값(정령의 왼쪽 상단)과 x2값(정령의 오른쪽 상단)을 추적하는 대상이다.
내가 처음 통과했을 때, 나는 아래의 코드를 만들었다.first_row, second_row 등은 모두 표로 Lua에서 대상과 그룹을 만드는 데 사용되는 데이터 구조이다.init_bg()에서, 나는 0에서 7로 순환해서,sprite 줄마다 표를 만들었다.add(first_row, { x1 = i * 16, w = 16, x2 = i * 16 + 16 }) 값표를 first_row표로 전송한다.7*16=112로 인해 마지막 정령의 x값은 112이고 이 정령의 x값은 16px로 게임 화면의 128px 너비를 덮어씁니다.32px 넓은 정령이 포함된 표에서 최종적으로 만들어진 정령의 수량은 화면을 덮어쓰는 데 필요한 수량을 초과하지만, 추가 화면 밖의 정령이 있는지 여부는 중요하지 않습니다.create_land() 비슷한 일을 했지만 10배로 순환했고 두 개의 표를 만들었다. 하나는 육지의 꼭대기에 사용되고 다른 하나는 밑에 사용된다.
function _init()
  -- speeds to control how fast each row is moving
  top_bg_speed = 5
  bottom_bg_speed = 6
  middle_bg_speed = 1
  land_speed = 5

  -- tables for each row of sprites
  first_row = {}
  second_row = {}
  land_top = {}
  land_bottom = {}
  third_row = {}
  bottom_row = {}

  init_bg()
end

function init_bg()
  for i = 0, 7 do
    add(first_row, create_spr_table(i, 16))

    add(second_row, create_spr_table(i, 32))

    add(third_row, create_spr_table(i, 32))

    add(bottom_row, create_spr_table(i, 16))
  end

  create_land()
end

function create_spr_table(i, w)
  return { x1 = i * w, w = w, x2 = i * w  + w }
end

function create_land()
  for i = 0, 10 do
    add(land_top, create_spr_table(i, 32))
    add(land_bottom, create_spr_table(i, 32))
  end
end

30fps를 실행하는 _update() 함수에서, 나는 모든 표를 반복해서 새 x1x2 값을 설정했다.줄마다 속도 변수가 있어 원하는 엘프가 굴러가는 속도에 따라 조정할 수 있습니다.전투의 두꺼비는 터빈 오토바이를 처음 탔을 때 전경과 배경의 정령이 조금씩 다른 속도로 움직이며 점차 올라간다. 똑같은 속도로 움직이는 것처럼 보인다. 속도가 너무 빨라 구분할 수 없기 때문이다.내 거야.gif에서 알 수 있듯이 나는 다른 속도를 유지했다. 왜냐하면 나는 이 효과를 좋아하기 때문이다. 그러나 나는 _init()의 변수 값을 바꾸어 모든 부분을 같은 속도로 쉽게 운행할 수 있다.
모든 요정이 왼쪽으로 이동할 때, 나는 요정이 언제 화면을 떠나는지 알아야 한다. 그러면 요정이 책상에서 제거될 수 있다.나는 시계마다 마지막 정령의 x2값을 알아야 하기 때문에 새 정령을 시계에 밀어넣을 때 이 x2값을 새 정령의 x1값으로 사용할 수 있다.육상정령의 탁자는 다른 탁자와 달라야 한다. 끊임없이 반복되지 않기 때문에 스크린 밖에서 작동해야 한다.어떤 줄은 하나의 정령으로 구성되어 있으며, 그 중 하나의 시계의 순환을 사용하여 함께 업데이트된다.
function _update()
  -- only some rows are shown here, the rest are basically the same

  -- update first row values
  for i = 1, #first_row do
    first_row[i].x1 -= top_bg_speed
    set_new_x2(first_row, i)
  end

  -- update second/third row values
  for i = 1, #second_row do
    second_row[i].x1 -= middle_bg_speed
    third_row[i].x1 -= middle_bg_speed

    set_new_x2(second_row, i)
    set_new_x2(third_row, i)
  end

  -- update land values
  for i = 1, #land_top do
    land_top[i].x1 -= land_speed
    land_bottom[i].x1 -= land_speed

    set_new_x2(land_top, i)
    set_new_x2(land_bottom, i)
  end

  -- remove element once it goes out of view
    if (is_offscreen_left(first_row[1].x2)) del_first_value(first_row)

    if is_offscreen_left(second_row[1].x2) then
    del_first_value(second_row)
    del_first_value(third_row)
  end

  -- add element to end of table if there's an empty space between the last element x2 value and end of the game screen
  if (should_add_bg_spr(first_row[#first_row].x2)) add_bg_spr_to_end(first_row, 16 )

  if should_add_bg_spr(second_row[#second_row].x2) then
    add_bg_spr_to_end(second_row, 32)
    add_bg_spr_to_end(third_row, 32)
  end

  -- update land
    if (is_offscreen_left(land_top[#land_top].x2)) reset_land()
end

function reset_land()
  for i = 1, #land_top do
    local start = i * 32 + 128

    land_top[i].x1 = start
    land_top[i].x2 = land_top[i].x1 + 32

    land_bottom[i].x1 = start
    land_bottom[i].x2 = land_top[i].x1 + 32
  end
end

function add_bg_spr_to_end(tbl, w)
  local x1 = tbl[#tbl - 1].x2
  add(tbl, { x1 = x1, w = w, x2 = x1 + w })
end

function should_add_bg_spr(x)
  -- 148 for a smoother addition since it's out of view
  return x <= 148
end

function del_first_value(tbl)
  del(tbl, tbl[1])
end

function is_offscreen_left(x)
  return x <= 0
end

function set_new_x2(tbl, idx)
    tbl[idx].x2 = tbl[idx].x1 + tbl[idx].w
end
같은 30fps 속도로 운행하는 _draw() 함수에서 나는 모든 시계를 다시 한 번 훑어보고 모든 시계 요소의 x1 값으로 모든 정령을 그렸다.중간에 한 줄이 정적이어서 책상이 없어서 그것을 놓지 않았다.나는 8회(0-7회)만 순환하면 스크린에 프레임당 8개의 정적 정령을 채울 수 있다.토지는 말단이 있기 때문에, 이곳의 처리 방식도 다르다.내가 랜드테이블을 반복할 때, 만약 인덱스가 1 (Lua 인덱스는 0이 아니라 1로 시작) 이거나, 인덱스가 그룹의 길이에 해당한다면, 나는 정령을 끝으로 설정할 것이다.만약 이것이 내가 마지막으로 flptrue로 설정한 것이라면, 이것은 정령이 x축에서 뒤집혀야 하는지를 제어합니다.
주: Pico-8에서 정령은spr(정령 번호, x, y, w, h,flip x,flip y)로 그려지는데, 그중 w는 넓은 정령의 수량이고, h는 높은 정령의 수량이다.모든 요정은 8x8이기 때문에 16x16의 그림을 그리기 위해 w=2와 h=2를 설정해야 합니다
function _draw()
    cls()

  -- draw top row
  for i = 1, #first_row do
    spr(64, first_row[i].x1, 0, 2, 2) -- first_row
  end

  -- draw second/third rows
  for i = 1, #second_row do
    spr(66, second_row[i].x1, 16, 4, 4) -- second row
    spr(70, third_row[i].x1, 48, 4, 2) -- third row
  end

  -- draw static middle row
  for i = 0, 7 do
    spr(96, i * 16, 64, 2, 2)
  end

  -- draw land
  draw_land()

  -- draw bottom row
  for i = 1, #bottom_row do
    spr(204, bottom_row[i].x1, 112, 2, 2)
  end
end

function draw_land()
  for i = 1, #land_top do
    local flp = false
    local top_spr = 196
    local bottom_spr = 200

    if i == 1 or i == #land_top then
      top_spr = 192
      bottom_spr = 232
    end

    if (i == #land_top) flp = true

    spr(top_spr, land_top[i].x1, 64, 4, 4, flp)
    spr(bottom_spr, land_bottom[i].x1, 96, 4, 2, flp)
  end
end
이 모든 것이 완벽하게 최종 결과를 창조했다.그리고 내가 수량 (128px의 너비 + 최소 32px의 추가 화면 밖을 덮을 수 있음) 을 설정하고 모든 정령이 화면을 떠날 때 x 값을 변경할 수 있을 때, 왜 테이블에서 추가하거나 삭제해야 하는지 생각했다.내가 애써 시도하기 전에, 나는 사실 이런 일을 할 필요가 없다는 것을 갑자기 깨달았다.🤦‍♀️ 나는 책상이 아예 필요 없다.나는 한 순환에서 모든 내용을 만들 수 있으며, 두 변수를 사용하여 줄마다 x 값을 추적할 수 있다.

한 번 더 해달래요.


나는 모든 테이블과 그 부수적인 기능을 삭제하고 몇 가지 새로운 변수를 만들었다. top_startX, middle_startX, bottom_startX, land_startX.이러한 값은 각 행의 시작 위치를 제어합니다.그것들은 0으로 초기화되어 각 프레임은 줄마다 속도를 줄이고 -128까지 마이너스로 변한다.그런 다음 0으로 재설정됩니다.그래, land_startX 빼고 내가 이따가 설명할게.
현재 유일하게 해야 할 일 _update() 은 줄마다 시작하는 x값에서 속도를 줄이는 것이다._draw()에는 하나의 순환이 있는데 이 순환에서 줄마다 그려져 육지를 뺀다.정령의 폭이 적어도 32px일 때, 나는 직접 정령을 그릴 수 있다. 왜냐하면 128px 화면의 폭을 초과한 정령을 만들 수 있기 때문이다.예를 들어 i=7이면 spr(66, i * 32 + middle_startX, 16, 4, 4) 224의 x값에 middle_startX와 같은 값을 더하여 정령을 만든다.내가 위에서 설명한 바와 같이, 그것은 각 프레임에서 줄어들어, 요정이 왼쪽으로 굴러가는 것처럼 보일 것이다.정령이 16x16에 불과할 때 오른쪽은 덮어지지 않습니다. 정령은 다음 순환이 시작되기 전에 왼쪽으로 이동하기 때문입니다.나는 최소한 16 (7이 아니라) 까지 순환해야만 완전히 덮어쓸 수 있기 때문에, x 값 + 128 (화면 너비) 에 추가 정령을 그렸다.
토지에 대해 나는 land_endX 변수를 만들어야 한다. 이 변수는 토지의 길이 +1 곱하기 32(토지 부분의 너비)와 같다.그리고 리셋land_startX을 한 번<= -land_endX했습니다.나는 그것을 128로 리셋하지 않고 필요할 때 토지 사이의 거리를 바꾸기 위해 gap_start 파라미터를 추가했다.
다음은 스크롤 효과를 만드는 전체 코드입니다.이전에 시계를 사용한 방법과 비교하면, 그것은 더욱 간단하고, 더욱 깨끗하며, 작업 원리도 같다.
function _init()
  top_bg_speed = 5
  bottom_bg_speed = 6
  middle_bg_speed = 1
  land_speed = 5
  top_startX = 0
  middle_startX = 0
  bottom_startX = 0
  land_startX = 0
end

function _draw()
  cls()
  palt(0, false) -- make black visible
  palt(1, true) -- make darkblue transparent

  -- draw bg (minus land)
  for i = 0, 7 do
    -- top row 16x16
    spr(64, i * 16 + top_startX, 0, 2, 2)
    spr(64, i * 16 + top_startX + 128, 0, 2, 2)

    -- second row 32x32
    spr(66, i * 32 + middle_startX, 16, 4, 4)

    -- third row 32x16
    spr(70, i * 32 + middle_startX, 48, 4, 2)

    -- static middle row
    spr(96, i * 16, 64, 2, 2)

    -- bottom row 16x16
    spr(204, i * 16 + bottom_startX, 112, 2, 2)
    spr(204, i * 16 + bottom_startX + 128, 112, 2, 2)
  end

  -- draw land
  draw_land(20, 128)

  -- reset
  if (x_should_reset(top_startX)) top_startX = 0
  if (x_should_reset(middle_startX)) middle_startX = 0
  if (x_should_reset(bottom_startX)) bottom_startX = 0
end

function x_should_reset(x)
  return x <= -128
end

function _update()
  top_startX -= top_bg_speed
  middle_startX -= middle_bg_speed
  bottom_startX -= bottom_bg_speed
  land_startX -= land_speed
end

function draw_land(length, gap_start)
  local land_endX = (length + 1) * 32 -- +1 for off screen padding

  -- reset
  -- gap_start is what px you want the next piece of land to start
  if (land_startX <= -land_endX) land_startX = gap_start

  for i = 1, length do
    local flp = false
    local top_spr = 196
    local bottom_spr = 200

    if (i == 1 or i == length) then -- end piece
      top_spr = 192
      bottom_spr = 232
    end

    if (i == length) flp = true -- right end piece

    spr(top_spr, i * 32 + land_startX, 64, 4, 4, flp) -- top
    spr(bottom_spr, i * 32 + land_startX, 96, 4, 2, flp) -- bottom
  end
end

좋은 웹페이지 즐겨찾기