Related
I am looking for a tile based lighting system for my tile based game. I have not tried anything because I can't think of an effective way to do this. I have searched stack overflow and I found this but its not what I want. I am making a 2d version of Minecraft with pygame.
here is my tile class
class tile():
def __init__(self, block_category, block_type, x, y, world, win):
self.x, self.y, self.width, self.height = (x*64), (y*64), 64, 64
self.block_type = block_type
self.light_level = 1 # i want light level to range from 0-1
self._image = None
self.world = world
self.win = win
self.posx, self.posy = x, y
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
def draw(self):
#draw code here self.posx, self.win, self.world and self.posy are used here if you are wondering
def change_block(self, block_category, block_type):
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
and my world data looks like this
def generate_world(self):
for x in range(0, self.width):
self.tiles[x] = {}
for y in range(0, self.height):
self.tiles[x][y] = tile("terrain", "air", x, y, self, self.win)
for x in range(0, self.width):
for y in range(0, self.height):
if y == 0:
self.tiles[x][y].change_block("terrain", "bedrock")
elif y == 38:
self.tiles[x][y].change_block("terrain", "grass_block")
elif y < 38 and y > 34:
self.tiles[x][y].change_block("terrain", "dirt")
elif y < 35 and y > 0:
self.tiles[x][y].change_block("terrain", "stone")
if x == 0 or x == self.height - 1:
self.tiles[x][y].change_block("terrain", "bedrock")
return self.tiles
my game looks like this
For 2D games like you're making, how we could apply lighting - more like, shadowing - could go into 2 options:
Change screen color to shadow color & set transparency to objects, as OP suggested
Sandwich entire thing between screen and light layer
Let's start with problem of 1st option:
Problem of setting transparency
Here's demo code based on your idea:
"""
Demonstration of color overlapping
"""
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
# setting alpha on player
self.image.set_alpha(125)
def update(self, *args, **kwargs):
x, y = pg.mouse.get_pos()
c_x, c_y = self.rect.center
self.rect.move_ip(x - c_x, y - c_y)
def mainloop():
player = Player()
screen = pg.display.set_mode((500, 500))
circle_colors = (255, 0, 0), (0, 255, 0), (0, 0, 255)
circle_coords = (150, 250), (250, 250), (350, 250)
# make surface, set alpha then draw circle
bg_surfaces = []
for (color, center) in zip(circle_colors, circle_coords):
surface = pg.Surface((500, 500), pg.SRCALPHA, 32)
surface.convert_alpha()
surface.set_alpha(125)
pg.draw.circle(surface, color, center, 75)
bg_surfaces.append(surface)
running = True
while running:
screen.fill((0, 0, 0))
# draw background
for surface in bg_surfaces:
screen.blit(surface, surface.get_rect())
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
player.update()
screen.blit(player.image, player.rect)
pg.display.flip()
if __name__ == '__main__':
pg.init()
mainloop()
pg.quit()
As you see, now the player (White square)'s color is Mixed with background circles.
It's basically just like what the drawing program does with layers.
Set layer transparency 50% and stack - everything mixes, producing undesirable effect which is far from lighting effect you wanted.
Unless you want Creeper or Steve to blend with the background and become a ghosty figure, it's better to go for sandwiched layout.
Sandwiched Layout
Following is demo code which uses mouse position as light source position.
Rendering order is Ground > Player > light overlay(shadow)
Demo code:
"""
Code demonstration for https://stackoverflow.com/q/72610504/10909029
Written on Python 3.10 (Using Match on input / event dispatching)
"""
import math
import random
import itertools
from typing import Dict, Tuple, Sequence
import pygame as pg
class Position:
"""Namespace for size and positions"""
tile_x = 20
tile_size = tile_x, tile_x
class SpriteGroup:
"""Namespace for sprite groups, with chain iterator keeping the order"""
ground = pg.sprite.Group()
entities = pg.sprite.Group()
light_overlay = pg.sprite.Group()
#classmethod
def all_sprites(cls):
return itertools.chain(cls.ground, cls.entities, cls.light_overlay)
class Player(pg.sprite.Sprite):
"""Player class, which is merely a rect following pointer in this example."""
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
SpriteGroup.entities.add(self)
self.rect.move_ip(225, 225)
def update(self, *args, **kwargs):
pass
# Intentionally disabling mouse following code
# x, y = pg.mouse.get_pos()
# c_x, c_y = self.rect.center
# self.rect.move_ip(x - c_x, y - c_y)
class TileLightOverlay(pg.sprite.Sprite):
"""
Light overlay tile. Using separate sprites, so we don't have to blit on
every object above ground that requires lighting.
"""
# light lowest boundary
lighting_lo = 255
# light effect radius
light_radius = Position.tile_x * 8
def __init__(self, x, y):
super(TileLightOverlay, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill((0, 0, 0))
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.light_overlay.add(self)
def update(self, *args, **kwargs):
self.image.set_alpha(self.brightness)
#property
def brightness(self):
"""Calculate distance between mouse & apply light falloff accordingly"""
distance = math.dist(self.rect.center, pg.mouse.get_pos())
if distance > self.light_radius:
return self.lighting_lo
return (distance / self.light_radius) * self.lighting_lo
class TileGround(pg.sprite.Sprite):
"""Ground tile representation. Not much is going on here."""
def __init__(self, x, y, tile_color: Sequence[float]):
super(TileGround, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill(tile_color)
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.ground.add(self)
# create and keep its pair light overlay tile.
self.light_tile = TileLightOverlay(x, y)
class World:
"""World storing ground tile data."""
# tile type storing color etc. for this example only have color.
tile_type: Dict[int, Tuple[float, float, float]] = {
0: (56, 135, 93),
1: (36, 135, 38),
2: (135, 128, 56)
}
def __init__(self):
# coord system : +x → / +y ↓
# generating random tile data
self.tile_data = [
[random.randint(0, 2) for _ in range(25)]
for _ in range(25)
]
# generated tiles
self.tiles = []
def generate(self):
"""Generate world tiles"""
for x, row in enumerate(self.tile_data):
tiles_row = [TileGround(x, y, self.tile_type[col]) for y, col in enumerate(row)]
self.tiles.append(tiles_row)
def process_input(event: pg.event.Event):
"""Process input, in case you need it"""
match event.key:
case pg.K_ESCAPE:
pg.event.post(pg.event.Event(pg.QUIT))
case pg.K_UP:
pass
# etc..
def display_fps_closure(screen: pg.Surface, clock: pg.time.Clock):
"""FPS display"""
font_name = pg.font.get_default_font()
font = pg.font.Font(font_name, 10)
color = (0, 255, 0)
def inner():
text = font.render(f"{int(clock.get_fps())} fps", True, color)
screen.blit(text, text.get_rect())
return inner
def mainloop():
# keeping reference of method/functions to reduce access overhead
fetch_events = pg.event.get
display = pg.display
# local variable setup
screen = display.set_mode((500, 500))
player = Player()
world = World()
world.generate()
clock = pg.time.Clock()
display_fps = display_fps_closure(screen, clock)
running = True
# main loop
while running:
screen.fill((0, 0, 0))
# process event
for event in fetch_events():
# event dispatch
match event.type:
case pg.QUIT:
running = False
case pg.KEYDOWN:
process_input(event)
# draw in ground > entities > light overlay order
for sprite in SpriteGroup.all_sprites():
sprite.update()
screen.blit(sprite.image, sprite.rect)
# draw fps - not related to question, was lazy to remove & looks fancy
clock.tick()
display_fps()
display.flip()
if __name__ == '__main__':
pg.init()
pg.font.init()
mainloop()
pg.quit()
You'll see it's blending properly with shadow without mixing color with ground tiles.
There could be much better approach or ways to implement this - as I never used pygame before, there would be bunch of good/better stuffs I didn't read on document.
But one thing for sure - always approach your goal with mindset that everything is related to your problem until you reach the goal! Comment you thought it wasn't going to be helpful gave me idea for this design.
One option is a black background, then I use set_alpha() to set how light or dark the tile is (how much the black background is seen through the tile) and no overlay is needed. Thanks to #jupiterbjy's original answer for inspiration.
I'm having couple of issues while trying to make a QHeaderView for my QTableView.
I want QHeaderView to be resizable by the user (Qt.ResizeMode.Interactive) while being able to stretch its sections proportionately when the window or QTableView is being resized. I found this problem online, and managed to mostly solve it but there is still some stuttering when the resizing begins and I think there should be a better solution than mine. Currently it's done by using QTimer to stop sections from going out of the viewport. Timer is being updated every millisecond. If the update interval is bigger, sections would go out of viewport and magically teleport back when the timer is updated, so once per millisecond in my case. There's still some stuttering visible if the user is dragging sections out of the viewport by dragging their mouse faster, not so visible when the mouse is slower, but visible none the less.
Every section should be resizable and movable, besides the first two. The first two sections should be immovable and fixed. I managed to make them fixed and they don't seem to have an effect on resizing of the sections, but I have no idea how to make them immovable while all the other sections are movable.
Sections should have text eliding, which I managed to make an item delegate for, but setting it on QHeaderView seems to do absolutely nothing (paint() method doesn't even get called). It's probably because item delegate isn't affecting sections, if so, how can I make a delegate that does affect them?
Here's my current code (it's a bit of a mess, but hopefully you'll get the idea):
import sys
import weakref
from typing import Any, Optional
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import pyqtSlot, Qt
from PyQt6.QtGui import QFontMetrics
from PyQt6.QtWidgets import QHeaderView, QStyledItemDelegate, QStyleOptionViewItem
class MyItemDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
text = index.data(Qt.ItemDataRole.DisplayRole)
# print(text)
if text:
elided_text = QFontMetrics(option.font).elidedText(str(text), Qt.TextElideMode.ElideRight, option.rect.width())
painter.drawText(option.rect, Qt.AlignmentFlag.AlignLeft, elided_text)
class HeaderView(QtWidgets.QHeaderView):
def __init__(self,
orientation: QtCore.Qt.Orientation = Qt.Orientation.Horizontal,
parent: Optional[QtWidgets.QWidget] = None):
super(HeaderView, self).__init__(orientation, parent)
item_delegate = MyItemDelegate(self)
self.setItemDelegate(item_delegate)
self.setMinimumSectionSize(5)
self.setStretchLastSection(True)
self.setCascadingSectionResizes(True)
self.setSectionsMovable(True)
self.fixed_section_indexes = (0, 1)
timer = QtCore.QTimer(self)
timer.setSingleShot(True)
timer.setTimerType(Qt.TimerType.PreciseTimer)
timer.timeout.connect(self._update_sizes)
resize_mode_timer = QtCore.QTimer(self)
resize_mode_timer.setTimerType(Qt.TimerType.PreciseTimer)
resize_mode_timer.setSingleShot(True)
resize_mode_timer.timeout.connect(lambda: self.setSectionResizeMode(QHeaderView.ResizeMode.Interactive))
self._resize_mode_timer = weakref.proxy(resize_mode_timer)
self._timer = weakref.proxy(timer)
self.sectionResized.connect(self._handle_resize)
self.setTextElideMode(Qt.TextElideMode.ElideLeft)
self.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
self.proportions = []
self.mouse_pressed = False
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None:
self.mouse_pressed = False
super().mouseReleaseEvent(e)
self.proportions = [self.sectionSize(i) / self.width() for i in range(self.count())]
# print(self.mouse_pressed)
def init_sizes(self):
each = self.width() // self.count()
for i in range(self.count()):
self.resizeSection(self.logicalIndex(i), each)
#pyqtSlot(int, int, int)
def _handle_resize(self, logicalIndex: int, oldSize: int, newSize: int):
self._timer.start(1)
def resizeEvent(self, event: QtGui.QResizeEvent):
super().resizeEvent(event)
width = self.width()
# sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
for i in range(self.count()):
if not self.proportions:
break
if i not in self.fixed_section_indexes:
self.resizeSection(i, int(self.proportions[i] * width_without_fixed))
self._timer.start(1)
#pyqtSlot()
def _update_sizes(self):
width = self.width()
sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
# width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
index = len(sizes) - 1
i = 0
while index >= 0 and sum(sizes) > width:
i += 1
if i > 100:
break
if sizes[index] > 5 and index not in self.fixed_section_indexes: # minimum width (5)
new_width = width - (sum(sizes) - sizes[index])
if new_width < 5:
new_width = 5
sizes[index] = new_width
index -= 1
for j, value in enumerate(sizes):
self.resizeSection(self.logicalIndex(j), value)
if not self.proportions:
self.proportions = [self.sectionSize(i) / width for i in range(self.count())]
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super(Model, self).__init__(parent)
self.__headers = ["Column A", "Column B", "Column C", "Column D", "Column E", "Column F", "Column G"]
self.__data = []
for i in range(10):
row = [0, 1, 2, 3, 42222222222, 5, 6, 74444444]
self.__data.append(row)
def rowCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__data)
def columnCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__headers)
def headerData(self, section: int, orientation: QtCore.Qt.Orientation,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal:
return self.__headers[section]
return f"{section}"
return None
def data(self, index: QtCore.QModelIndex,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]:
return self.__data[index.row()][index.column()]
return None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = QtWidgets.QTableView()
view.resize(600, 600)
header = HeaderView()
view.setHorizontalHeader(header)
model = Model()
view.setModel(model)
header.init_sizes()
view.horizontalHeader().resizeSection(0, 30)
view.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
view.horizontalHeader().resizeSection(1, 30)
view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
view.show()
app.exec()
I'm new to this so forgive me.
In the follow code on_draw(), on_update(), on_key_press(), and on_key_release() are never called but I know the code works so they do something, but why does it work?
I was under the impression that you had to do something like Mygame.on_update() to use a class function.
class Player(arcade.Sprite):
""" Player class """
def __init__(self, image, scale):
""" Set up the player """
# Call the parent init
super().__init__(image, scale)
# Create a variable to hold our speed. 'angle' is created by the parent
self.speed = 0
def update(self):
# Convert angle in degrees to radians.
angle_rad = math.radians(self.angle)
# Rotate the ship
self.angle += self.change_angle
# Use math to find our change based on our speed and angle
self.center_x += -self.speed * math.sin(angle_rad)
self.center_y += self.speed * math.cos(angle_rad)
class MyGame(arcade.Window):
"""
Main application class.
"""
def __init__(self, width, height, title):
"""
Initializer
"""
# Call the parent class initializer
super().__init__(width, height, title)
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Variables that will hold sprite lists
self.player_list = None
# Set up the player info
self.player_sprite = None
# Set the background color
arcade.set_background_color(arcade.color.BLACK)
def setup(self):
""" Set up the game and initialize the variables. """
# Sprite lists
self.player_list = arcade.SpriteList()
# Set up the player
self.player_sprite = Player(":resources:images/space_shooter/playerShip1_orange.png",
SPRITE_SCALING)
self.player_sprite.center_x = SCREEN_WIDTH / 2
self.player_sprite.center_y = SCREEN_HEIGHT / 2
self.player_list.append(self.player_sprite)
def on_draw(self):
"""
Render the screen.
"""
# This command has to happen before we start drawing
self.clear()
# Draw all the sprites.
self.player_list.draw()
def on_update(self, delta_time):
""" Movement and game logic """
# Call update on all sprites (The sprites don't do much in this
# example though.)
self.player_list.update()
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed. """
# Forward/back
if key == arcade.key.UP:
self.player_sprite.speed = MOVEMENT_SPEED
elif key == arcade.key.DOWN:
self.player_sprite.speed = -MOVEMENT_SPEED
# Rotate left/right
elif key == arcade.key.LEFT:
self.player_sprite.change_angle = ANGLE_SPEED
elif key == arcade.key.RIGHT:
self.player_sprite.change_angle = -ANGLE_SPEED
def on_key_release(self, key, modifiers):
"""Called when the user releases a key. """
if key == arcade.key.UP or key == arcade.key.DOWN:
self.player_sprite.speed = 0
elif key == arcade.key.LEFT or key == arcade.key.RIGHT:
self.player_sprite.change_angle = 0
def main():
""" Main function """
window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
In below code, I have image shaped transparent window and a image inside of it, I would like to move the image(screw photo)by mouse. I wrote a bind function for that screw image but it does not move? what might be the problem?
As it can be seen I added images and bind functions. Is there a missing logic?
import wx
from wx import *
import wx.lib.statbmp as sb
from io import StringIO
# Create a .png image with something drawn on a white background
# and put the path to it here.
IMAGE_PATH = './wood.png'
class ShapedFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Shaped Window",
style = wx.FRAME_SHAPED | wx.SIMPLE_BORDER | wx.STAY_ON_TOP)
self.hasShape = False
self.delta = wx.Point(0,0)
# Load the image
image = wx.Image(IMAGE_PATH, wx.BITMAP_TYPE_PNG)
image.SetMaskColour(255,255,255)
image.SetMask(True)
self.bmp = wx.Bitmap(image)
self.SetClientSize((self.bmp.GetWidth(), self.bmp.GetHeight()))
dc = wx.ClientDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
self.SetWindowShape()
self.Bind(wx.EVT_RIGHT_UP, self.OnExit)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_WINDOW_CREATE, self.SetWindowShape)
panel = MyPanel(parent=self)
def SetWindowShape(self, evt=None):
r = wx.Region(self.bmp)
self.hasShape = self.SetShape(r)
def OnDoubleClick(self, evt):
if self.hasShape:
self.SetShape(wx.Region())
self.hasShape = False
else:
self.SetWindowShape()
def OnPaint(self, evt):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0,0, True)
def OnExit(self, evt):
self.Close()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
class MyPanel(wx.Panel):
# A panel is a window on which controls are placed. (e.g. buttons and text boxes)
# wx.Panel class is usually put inside a wxFrame object. This class is also inherited from wxWindow class.
def __init__(self,parent):
super().__init__(parent=parent)
MyImage(self)
class MyImage(wx.StaticBitmap):
def __init__(self,parent):
super().__init__(parent=parent)
jpg1 = wx.Image('./Images/screwsmall.png', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
# bitmap upper left corner is in the position tuple (x, y) = (5, 5)
self.myImage = wx.StaticBitmap(parent, -1, jpg1, (10 + jpg1.GetWidth(), 5), (jpg1.GetWidth(), jpg1.GetHeight()))
self.myImage.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.myImage.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
pos = self.ClientToScreen(evt.GetPosition())
newPos = (pos.x - self.delta.x, pos.y - self.delta.y)
self.Move(newPos)
def OnLeftUp(self, evt):
if self.HasCapture():
self.ReleaseMouse()
def OnLeftDown(self, evt):
self.CaptureMouse()
pos = self.ClientToScreen(evt.GetPosition())
origin = self.GetPosition()
self.delta = wx.Point(pos.x - origin.x, pos.y - origin.y)
if __name__ == '__main__':
app = wx.App()
ShapedFrame().Show()
app.MainLoop()
Visual output of my code you can use different shapes in local directory. To install wxpython for 3.x you can check this link https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-16.04/ download your version for ubuntu and use pip install command.
I am going to put my answer. As it can be seen in the #RolfofSaxony comment, there is a drag image demo inside the WXPYTHON tar file. In that DragImage.py file there are two different classes do the dragging job. I modified those functions and wrote my own two class. You can use these classes in your code as a component. My code is working and tested.
class DragShape:
def __init__(self, bmp):
self.bmp = bmp
self.pos = (0,0)
self.shown = True
self.text = None
self.fullscreen = False
def HitTest(self, pt):
rect = self.GetRect()
return rect.Contains(pt)
def GetRect(self):
return wx.Rect(self.pos[0], self.pos[1],
self.bmp.GetWidth(), self.bmp.GetHeight())
def Draw(self, dc, op = wx.COPY):
if self.bmp.IsOk():
memDC = wx.MemoryDC()
memDC.SelectObject(self.bmp)
dc.Blit(self.pos[0], self.pos[1],
self.bmp.GetWidth(), self.bmp.GetHeight(),
memDC, 0, 0, op, True)
return True
else:
return False
#----------------------------------------------------------------------
class DragCanvas(wx.ScrolledWindow):
def __init__(self, parent, ID):
wx.ScrolledWindow.__init__(self, parent, ID)
self.shapes = []
self.dragImage = None
self.dragShape = None
self.hiliteShape = None
self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
bmp = images.TheKid.GetBitmap()
shape = DragShape(bmp)
shape.pos = (200, 5)
self.shapes.append(shape)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
# We're not doing anything here, but you might have reason to.
# for example, if you were dragging something, you might elect to
# 'drop it' when the cursor left the window.
def OnLeaveWindow(self, evt):
pass
# Go through our list of shapes and draw them in whatever place they are.
def DrawShapes(self, dc):
for shape in self.shapes:
if shape.shown:
shape.Draw(dc)
# This is actually a sophisticated 'hit test', but in this
# case we're also determining which shape, if any, was 'hit'.
def FindShape(self, pt):
for shape in self.shapes:
if shape.HitTest(pt):
return shape
return None
# Fired whenever a paint event occurs
def OnPaint(self, evt):
dc = wx.PaintDC(self)
self.PrepareDC(dc)
self.DrawShapes(dc)
# print('OnPaint')
# Left mouse button is down.
def OnLeftDown(self, evt):
# Did the mouse go down on one of our shapes?
shape = self.FindShape(evt.GetPosition())
# If a shape was 'hit', then set that as the shape we're going to
# drag around. Get our start position. Dragging has not yet started.
# That will happen once the mouse moves, OR the mouse is released.
if shape:
self.dragShape = shape
self.dragStartPos = evt.GetPosition()
# Left mouse button up.
def OnLeftUp(self, evt):
if not self.dragImage or not self.dragShape:
self.dragImage = None
self.dragShape = None
return
# Hide the image, end dragging, and nuke out the drag image.
self.dragImage.Hide()
self.dragImage.EndDrag()
self.dragImage = None
if self.hiliteShape:
self.RefreshRect(self.hiliteShape.GetRect())
self.hiliteShape = None
# reposition and draw the shape
# Note by jmg 11/28/03
# Here's the original:
#
# self.dragShape.pos = self.dragShape.pos + evt.GetPosition() - self.dragStartPos
#
# So if there are any problems associated with this, use that as
# a starting place in your investigation. I've tried to simulate the
# wx.Point __add__ method here -- it won't work for tuples as we
# have now from the various methods
#
# There must be a better way to do this :-)
#
self.dragShape.pos = (
self.dragShape.pos[0] + evt.GetPosition()[0] - self.dragStartPos[0],
self.dragShape.pos[1] + evt.GetPosition()[1] - self.dragStartPos[1]
)
self.dragShape.shown = True
self.RefreshRect(self.dragShape.GetRect())
self.dragShape = None
# The mouse is moving
def OnMotion(self, evt):
# Ignore mouse movement if we're not dragging.
if not self.dragShape or not evt.Dragging() or not evt.LeftIsDown():
return
# if we have a shape, but haven't started dragging yet
if self.dragShape and not self.dragImage:
# only start the drag after having moved a couple pixels
tolerance = 2
pt = evt.GetPosition()
dx = abs(pt.x - self.dragStartPos.x)
dy = abs(pt.y - self.dragStartPos.y)
if dx <= tolerance and dy <= tolerance:
return
# refresh the area of the window where the shape was so it
# will get erased.
self.dragShape.shown = False
self.RefreshRect(self.dragShape.GetRect(), True)
self.Update()
item = self.dragShape.text if self.dragShape.text else self.dragShape.bmp
self.dragImage = wx.DragImage(item,
wx.Cursor(wx.CURSOR_HAND))
hotspot = self.dragStartPos - self.dragShape.pos
self.dragImage.BeginDrag(hotspot, self, self.dragShape.fullscreen)
self.dragImage.Move(pt)
self.dragImage.Show()
# if we have shape and image then move it, posibly highlighting another shape.
elif self.dragShape and self.dragImage:
onShape = self.FindShape(evt.GetPosition())
unhiliteOld = False
hiliteNew = False
# figure out what to hilite and what to unhilite
if self.hiliteShape:
if onShape is None or self.hiliteShape is not onShape:
unhiliteOld = True
if onShape and onShape is not self.hiliteShape and onShape.shown:
hiliteNew = True
# if needed, hide the drag image so we can update the window
if unhiliteOld or hiliteNew:
self.dragImage.Hide()
if unhiliteOld:
dc = wx.ClientDC(self)
self.hiliteShape.Draw(dc)
self.hiliteShape = None
if hiliteNew:
dc = wx.ClientDC(self)
self.hiliteShape = onShape
self.hiliteShape.Draw(dc, wx.INVERT)
# now move it and show it again if needed
self.dragImage.Move(evt.GetPosition())
if unhiliteOld or hiliteNew:
self.dragImage.Show()
I am making a 8bit style platformer. The player falls and gains speed because of the pseudo gravity but he will fall a few pixels into the ground level. Without gravity he will land on the ground and not fall though but it is a constant fall speed.When in the ground you can go up but he will fall when you let up. He will not got down so that is not an issue for now. Any help would be appreciated.
The player class/file.
import pygame,sys
from pygame.locals import *
class Player:
x=0
y=0
offset = 5
L=False
R=False
U=False
D=False
image = None
gravity = .25
velocity = offset
objectDict = None #this si the list of the current objects so that collision can be check with every
#object.. get updated every loop to keep a accurate check of locations
rect = None
grav = True #TODO use this to check if we are paying attention to the gravity
def __init__(self,x,y):
self.x = x
self.y = y
self.image = pygame.image.load('Resources/Pics/player.png')
def draw(self,DISPLAY):
#print('draw will go here')
imgRect = self.image.get_rect()
imgRect.midleft = (self.x,self.y)
self.rect = imgRect
DISPLAY.blit(self.image, imgRect)
#and now im here
def checkCollide(self,otherRect):
return self.rect.colliderect(otherRect)
def checkCollideAll(self):
if(self.objectDict != None):
# print(len(self.objectDict))
# for x in range(1,len(self.objectDict)):
# newb = self.checkCollide(self.objectDict[x].getRect())
# print(self.objectDict[x].getRect())
# if(newb):
# return True
# return False
collideNum = self.rect.collidelist(self.objectDict)
if(collideNum == -1):
return False
else:
return True
def willCollideBelow(self):
if(self.objectDict):
checkRect = (self.x,(self.y),self.image.get_size())
collideNum = self.rect.collidelist(self.objectDict)
if collideNum == -1:
return False
else:
return True
def objUpdate(self,dict):
self.objectDict = dict
def getRect(self):
return self.rect
def update(self):
# while(self.checkCollideAll()):
# print('while happened')
# self.y -= self.offset
# imgRect = self.image.get_rect()
# imgRect.midleft = (self.x,self.y)
# self.rect = imgRect
# print(self.willCollideBelow())
if not self.willCollideBelow():
self.D = True
# print('will fall')
else:
self.D = False
if self.U == True:
self.y -= self.offset
if self.D == True:
self.y += self.velocity
if not self.velocity >= 9.8:
self.velocity += self.gravity
else:
self.velocity = self.offset
if self.L == True:
self.x -= self.offset
if self.R == True:
self.x += self.offset
You didn't provide a running example and your code is hard to read (pascal case, a lot of unnecessary parenthesis), but here's my guess:
In your willCollideBelow function, you check if you hit an object beneath the player:
def willCollideBelow(self):
if(self.objectDict):
checkRect = (self.x,(self.y),self.image.get_size())
collideNum = self.rect.collidelist(self.objectDict)
if collideNum == -1:
return False
else:
return True
instead of just returning True or False, return the object (or the index of the object) you actually collide with:
def will_collide_below(self):
if(self.objectDict):
# using 'collidelistall' would be better, but that's another topic
return self.rect.collidelist(self.objectDict)
Now that you know which object the player collides with, you can adjust the vertical position of the player:
ground_i = self.will_collide_below()
if ground_i:
ground = self.objectDict[ground_i]
self.velocity = 0
self.rect.bottom = ground.top # or self.y = ground.top
You'll get the idea.
Some more notes:
You use different variables to store the position of the player (I see x, y, rect and imgRect). It would make you code a lot simpler if you would just use a single Rect to store the position:
class Player:
...
def __init__(self,x,y):
self.image = pygame.image.load('Resources/Pics/player.png')
self.rect = self.image.get_rect(midleft=(x,y))
def draw(self, display):
display.blit(self.image, self.rect)
def update(self):
...
if self.L: # no need to check == True
self.rect.move_ip(-self.offset)
if self.R: # simply use move_ip to alter the position
self.rect.move_ip(self.offset)
You also use a bunch of class variables where you really should use instance variables, like rect, L, R, U and D.