Page 1 of 1

Weird Snake behavior

Posted: Mon Jun 18, 2018 5:43 pm
by vSxo.lua
Hi, its me again. This time I have some problems with game logic. Its a snake-type game, when you move, you paint floor with blue-ish color.
The problem is that when snake direction is "top" or "left" everything is ok, painting is smooth. But when snake is facing "right" or "down" firstly head of the snake moves to desired destination and only then floor is painted. It causes some breaks in snake body. I do not know how to fix it. I tried many solutions but they were no right solutions at all.

Function that is responsible for this logic is Game:update(dt). snake.realX and snake.realY are real pixel coordinates. snake.newX and snake.newY are grid based coordinates. snake.x and snake.y are coordinates from last snake position. Im checking if snake[1].newX or snake[1].newY is different from old coordinates. If it is Im creating snake object at old coordinates and setting old coordinates to new coordinates. But unfortunately it is not working. And I dont see where is the logic error.

If you can tell me how to make floor painting smooth in every direction I would very appreciate it. :)

main.lua

Code: Select all

Object = require "classic"
require "game"

WINDOW_WIDTH = 960
WINDOW_HEIGHT = 640

function love.load()
	-- window
	love.window.setMode(WINDOW_WIDTH, WINDOW_HEIGHT, {vsync = true, resizable = false, fullscreen = false})
	love.window.setTitle("Snake Expansion")
	love.graphics.setDefaultFilter('nearest', 'nearest')
	
	-- sounds 
	winSound = love.audio.newSource("win.wav", "static")
	menuSound = love.audio.newSource("menu.wav", "static")
	moveSound = love.audio.newSource("move.wav", "static")
	itemSound = love.audio.newSource("item.wav", "static")
	collisionSound = love.audio.newSource("collision.wav", "static")
	
	-- graphics
	background = love.graphics.newImage("background.png")
	ring = love.graphics.newImage("ring.png")
	smallFont = love.graphics.newFont("liquid.ttf", 30)
	normalFont = love.graphics.newFont("liquid.ttf", 40)
	bigFont = love.graphics.newFont("liquid.ttf", 50)
	wallTile = love.graphics.newImage("wall.png")
	floorTile = love.graphics.newImage("floor.png")
	
	-- varibles
	angle = 0
	gameState = "start"
	titleVisible = 0
end

function love.update(dt)
	angle = angle + (2 * dt)
	if gameState == "start" then
		titleVisible = math.min(255, titleVisible + (100 * dt))
	end
	if titleVisible >= 255 and gameState == "start" then
		gameState = "menu"
	end
	if gameState == "play" then
		game:update(dt)
	end
	
end

function love.mousepressed(mouseX, mouseY, button, isTouch)
	if button == 1 and gameState == "menu" then
		if mouseX > WINDOW_WIDTH/2 - 100 and mouseX < WINDOW_WIDTH/2 + 100 and mouseY >  WINDOW_HEIGHT/2 and mouseY < WINDOW_HEIGHT/2 + 50 then
			print("Play clicked")
			game = Game(1)
			gameState = "play"
		end
		if mouseX > WINDOW_WIDTH/2 - 100 and mouseX < WINDOW_WIDTH/2 + 100 and mouseY >  WINDOW_HEIGHT/2 + 100 and mouseY < WINDOW_HEIGHT/2 + 150 then
			print("Options clicked")
			gameState = "options"
		end
		if mouseX > WINDOW_WIDTH/2 - 100 and mouseX < WINDOW_WIDTH/2 + 100 and mouseY >  WINDOW_HEIGHT/2 + 200 and mouseY < WINDOW_HEIGHT/2 + 250 then
			print("Exit clicked")
			love.event.quit()
		end
	end
end

function love.draw()
	love.graphics.setColor(255,255,255,255)
	love.graphics.draw(background, 0, 0)
	love.graphics.setColor(255,255,255,100)
	love.graphics.setBlendMode("add")
	love.graphics.draw(ring, WINDOW_WIDTH/2, WINDOW_HEIGHT/2, angle, 1, 1, 100, 100)
	love.graphics.setBlendMode("alpha")
	if gameState == "start" or gameState == "menu" then
		love.graphics.setFont(bigFont)
		love.graphics.setColor(255,0,255,titleVisible)
		love.graphics.printf("Snake Expansion", 0, WINDOW_HEIGHT/4, WINDOW_WIDTH, "center")
		love.graphics.setFont(normalFont)
		love.graphics.printf("Play", 0, WINDOW_HEIGHT/2, WINDOW_WIDTH, "center")
		love.graphics.printf("Options", 0, WINDOW_HEIGHT/2 + 100, WINDOW_WIDTH, "center")
		love.graphics.printf("Exit", 0, WINDOW_HEIGHT/2 + 200, WINDOW_WIDTH, "center")
	end
	love.graphics.setColor(255, 255, 255, 255)
	if gameState == "play" then
		game:draw()
	end
end
snake.lua

Code: Select all

Snake = Object:extend()

segmentsCount = 0
dir = 2

function Snake:new(x, y, speed)
	self.speed = speed or 0
	segmentsCount = segmentsCount + 1
	self.x = x
	self.y = y
	self.width = 32
	self.height = 32
	self.realX = self.x * 32
	self.realY = self.y * 32
end

function Snake:collision(object) 
	if self.x > object.x and self.x + self.width < object.x and self.y > object.y and self.y + self.height < object.y then
		return true
	else
		return false
	end
end

function Snake:update(dt)
	if self.speed ~= 0 then	
		if dir == 1 then
			self.realY = self.realY - self.speed * dt
		elseif dir == 2 then
			self.realX = self.realX + self.speed * dt
		elseif dir == 3 then
			self.realY = self.realY + self.speed * dt
		elseif dir == 4 then
			self.realX = self.realX - self.speed * dt
		end
	end
end

function Snake:draw()
	love.graphics.rectangle("fill", self.realX, self.realY, self.width, self.height)
end
game.lua

Code: Select all

require "snake"
require "tile"

Game = Object:extend()

map = {}
objects = {}
snake = {}
direction = 2
local canMove = true

function Game:new(level)
	self.level = level
	snake = {}
	self.loadMap(self)
	self.visible = 0
end

function Game:loadMap()
	-- loading level from file
	for line in love.filesystem.lines("level" .. tostring(self.level) .. ".lst")	do 
		table.insert(map, line)
	end
	
	-- instantiate objects on map
	for index, str in ipairs(map) do
		for i = 1, #str do
			local letter = str:sub(i, i)
			if letter == "X" then
				table.insert(objects, Tile(i, index, "wall"))
			elseif letter == "S" then
				table.insert(objects, Tile(i, index, "floor"))
				snake[1] = Snake(i, index, 10)
			elseif letter == "_" then 
				table.insert(objects, Tile(i, index, "floor"))
			end
		end
	end
end

function Game:update(dt)
	self.visible = math.min(255, self.visible + 100 * dt)
	if canMove == false and math.floor(snake[1].realX) % 32 == 0 and math.floor(snake[1].realY) % 32 == 0 then
		dir = direction
		canMove = true
	end
	snake[1]:update(dt)
	snake[1].newX = math.floor(math.floor(snake[1].realX)/32)
	snake[1].newY = math.floor(math.floor(snake[1].realY)/32)
	if snake[1].newX ~= snake[1].x or snake[1].newY ~= snake[1].y then
		table.insert(snake, 2, Snake(snake[1].x, snake[1].y))
		snake[1].x = snake[1].newX
		snake[1].y = snake[1].newY
	end
end

function love.keypressed(key)
	-- adding change direction to the queqe
	if key == "left" and canMove == true then
		if direction == 1 then 
			direction = 4
		else
			direction = direction - 1
		end
		canMove = false
	elseif key == "right" and canMove == true then
		if direction == 4 then
			direction = 1
		else
			direction = direction + 1
		end
		canMove = false
	end
end

function Game:draw()
	love.graphics.setColor(255,255,255,255)
	for i, v in ipairs(objects) do
		v:draw()
	end
	colorChange = 255/#snake * 0.5
	for i = #snake, 1, -1 do
		love.graphics.setColor(colorChange * (i - 1), colorChange * (i - 1), 255, 255)
		snake[i]:draw()
	end
	love.graphics.setColor(255,0,255,self.visible)
	love.graphics.setFont(normalFont)
	love.graphics.printf("Level: " .. self.level, 0, 50, WINDOW_WIDTH, "center")
	love.graphics.setColor(255,255,255,255)
	--print("Snake newx: " .. snake[1].newX)
	--print("Snake newy: " .. snake[1].newY)
	--print("Snake x: " .. snake[1].x)
	--print("Snake y: " .. snake[1].y)
end
tile.lua (probably not needed here, but whatever)

Code: Select all

Tile = Object:extend()

function Tile:new(x, y, typeof)
	self.x = x
	self.y = y
	self.tileType = typeof
end

function Tile:update(dt)

end

function Tile:draw()
	if self.tileType == "wall" then
		love.graphics.draw(wallTile, self.x * 32, self.y * 32)
	elseif self.tileType == "floor" then
		love.graphics.draw(floorTile, self.x * 32, self.y * 32)
	end
end
Project is using classic.lua library and it is attached to the post. Thank you in advance :>

Re: Weird Snake behavior

Posted: Tue Jun 19, 2018 12:47 am
by pgimeno
The reason is pretty clear.

You're using the top left corner of the head as your reference position. That's part of your problem.

While you are, say, half way east, the floor of dividing your X coordinate by 32 is still at the square you're leaving, because your top left corner hasn't left that square yet. Only when your top left corner leaves that square, is when you actually paint it.

It works when you move west, because your top left corner starts at the left edge of the current square, and as soon as you move, it crosses to the next square, which causes the square you're leaving to be painted immediately as you begin to move, and since the head is almost entirely at that square, it partially covers the tail and everything looks fine.

Similarly for the up/down direction, of course.

As for solutions... well, that's more difficult. The easiest solution I can think of is flooring when you're going left or up, and ceiling when you're going right or down. By the way, you don't need the double floor, you can just floor after dividing by 32. When drawing, you should always floor though.

Disclaimer: I have not tested this solution. I can think of a couple other solutions: one is adding a "padding square" specifically for those directions; another one is using the centre of the square as reference, so that you paint always where the head stops.

Re: Weird Snake behavior

Posted: Wed Jun 20, 2018 4:17 pm
by vSxo.lua
Ceiling is not working :<

I tried to add this padding square but it didnt work out,
How to implement this centre of the square as reference?

Edit:
Okay i have one idea with this centre of the square. I will report if this will succeed.

Re: Weird Snake behavior

Posted: Thu Jun 21, 2018 3:05 pm
by pgimeno
I've tried the ceiling. It works if you update before setting canMove.

Before:

Code: Select all

	if canMove == false and math.floor(snake[1].realX) % 32 == 0 and math.floor(snake[1].realY) % 32 == 0 then
		dir = direction
		canMove = true
	end
	snake[1]:update(dt)
	snake[1].newX = math.floor(math.floor(snake[1].realX)/32)
	snake[1].newY = math.floor(math.floor(snake[1].realY)/32)
After:

Code: Select all

	snake[1]:update(dt)
	if canMove == false and math.floor(snake[1].realX) % 32 == 0 and math.floor(snake[1].realY) % 32 == 0 then
		dir = direction
		canMove = true
	end
	snake[1].newX = dir == 2 and math.ceil(snake[1].realX/32) or math.floor(snake[1].realX/32)
	snake[1].newY = dir == 3 and math.ceil(snake[1].realY/32) or math.floor(snake[1].realY/32)

Re: Weird Snake behavior

Posted: Sun Jun 24, 2018 1:41 pm
by vSxo.lua
Yep, its working :> Thanks.