|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 歌者文明清理员 于 2023-7-26 23:11 编辑
目前已经放弃 SCALE
怎么会,居然把 pygame 写成了 pycharm
目前问题是不能将天体和轨道合并在同一个点上
cjjJasonchen 的回答:https://fishc.com.cn/forum.php?m ... 284&pid=6297452
当用鼠标滚轮滑动的时候行星会变大或者变小,这时,他们的中心点会发生变化,
而此时,轨迹的生成位置还是在行星原本大小时的center,
所以看上去轨道就不和行星在一起了
我有点没懂,这么说不应该是轨道的曲线很诡异吗,再说我这个现象不是滚轮,是拖动时发生的
目前的想法:https://fishc.com.cn/forum.php?m ... 284&pid=6298215
那可以不可以让StarSprite和轨迹都先放到另外一个surface上,对另外一个surface处理后再放回screen?
这样也有一个问题,就是轨迹的粗细不固定
但这个肯定不可行,首先这个 Surface 会超出屏幕
因为 surf 也得有大小,不能无限大,所以就会出现过一会儿就黑屏的情况
求救,不然 Star Motion Simulate 将止步于此……
code.zip
(128.5 KB, 下载次数: 1)
本帖最后由 hellomyprogram 于 2023-7-27 11:12 编辑
对于这种大型程序,你可以将代码拆分成不同板块来分别储存。
大功告成!以下是结果(代码部分): import itertools
import os
import pygame
import pyini
import tkinter as tk
import tkinter.filedialog as fd
from time import sleep
from threading import Thread
pygame.init()
pygame.key.set_repeat(1000, 50)
G = 6
# why do you need to create another object?
class StarObject:
def __init__(self, x, y, vx, vy, mass):
self.x = x
self.y = y
self.vx = vx # v means speed
self.vy = vy
self.mass = mass
@property
def info(self):
return self.x, self.y, self.vx, self.vy, self.mass
class StarSprite(pygame.sprite.Sprite):
def __init__(self, name, star, radius, color):
# pygame.sprite.Sprite.__init__(self)
super().__init__()
self.name = name
self.star = star # this is a StarObject
self.radius = radius
self.color = color
self.trail = []
self.image = pygame.Surface((radius * 2, radius * 2)).convert_alpha()
self.image.fill((0, 0, 0, 0))
pygame.draw.circle(self.image, color, (radius, radius), radius, 0)
self.rect = self.image.get_rect()
self.flush()
def flush(self):
self.rect.center = self.star.x, self.star.y
self.trail.append(self.rect.center)
def __repr__(self):
return self.name
__str__ = __repr__
class Message(pygame.sprite.Sprite):
def __init__(self, text, pos):
pygame.sprite.Sprite.__init__(self)
self._text = text
self.flush(pos)
@property
def text(self):
return self._text
@text.setter
def text(self, text):
pos = self.rect.topright
self._text = text
self.flush(pos)
def flush(self, pos):
self.image = font.render(self._text, False, (255, 255, 255))
self.image = pygame.transform.scale_by(self.image, 1 / SCALE)
self.rect = self.image.get_rect()
self.rect.topright = pos
def get_distance(sprite1, sprite2):
x1, y1 = sprite1.star.x, sprite1.star.y
x2, y2 = sprite2.star.x, sprite2.star.y
dx = x2 - x1
dy = y2 - y1
return (dx ** 2 + dy ** 2) ** 0.5
def disappear_message(delay=5, interval=0.09):
sleep(delay)
while message.text:
try:
message.text = message.text[:-1]
except pygame.error:
return
sleep(interval)
def move(t):
global sprites
sprites_to_delete = set()
for sprite1, sprite2 in itertools.combinations(sprites, 2): # this is way faster
star1 = sprite1.star
star2 = sprite2.star
if star1 is star2:
continue
x1, y1, vx1, vy1, m1 = star1.info
x2, y2, _, _, m2 = star2.info
ax1, ay1 = 0, 0
dx = x2 - x1
dy = y2 - y1
r = get_distance(sprite1, sprite2)
if is_collide(sprite1, sprite2):
heavier = sprite1 if star1.mass > star2.mass else sprite2
lighter = sprite2 if heavier is sprite1 else sprite1
sprites_to_delete.add(lighter)
heavier.star.vx += lighter.star.vx
heavier.star.vy += lighter.star.vy
message.text = language["star"]["collide"] % (heavier, lighter)
Thread(target=disappear_message).start()
f = G * m1 * m2 / (r ** 2)
accel = f / m1
ax1 += accel * (dx / r)
ay1 += accel * (dy / r)
x, y = (
x1 + vx1 * t + 0.5 * ax1 * (t ** 2),
y1 + vy1 * t + 0.5 * ay1 * (t ** 2)
)
tempx, tempy = star1.x, star1.y
star1.x, star1.y = x, y
star1.vx = (x - tempx) / t
star1.vy = (y - tempy) / t
sprite1.flush()
sprites -= sprites_to_delete # delete everything in sprites_to_delete
def is_collide(sprite1, sprite2):
r1, r2 = sprite1.radius, sprite2.radius
return r1 + r2 > get_distance(sprite1, sprite2)
def zoom(direction, each=0.01):
global scale
if direction > 0:
scale += each
elif direction < 0:
scale -= each
message.text = language["game"]["zoom"] % scale
Thread(target=disappear_message).start()
def change_view(move_x, move_y):
rel[0] += move_x
rel[1] += move_y
message.text = language["game"]["rel"] % str(tuple(rel))
with open("config/config.ini", "r", encoding="utf-8") as f:
config = pyini.ConfigParser(f.read())
with open(f"config/language_{config['language']['default']}.ini", "r", encoding="utf-8") as f:
language = pyini.ConfigParser(f.read())
SCALE = int(config["window"]["screen_zoom"]) / 100 # what's this? is it different from "scale"?
clock = pygame.time.Clock()
drag = False
rel = [0, 0] # do you mean "the world origin on the screen"
scale = 1
paused = False
size = width, height = (1000 / SCALE, 1000 / SCALE)
screen = pygame.display.set_mode(size)
screen_original = pygame.Surface(size)
movement = 10
pygame.display.set_icon(pygame.image.load(config["window"]["icon"]))
pygame.display.set_caption("Pygame Star Motion Simulate")
with open(f"simulation/{config['simulation']['file']}.simulation", "r", encoding="utf-8") as f:
sprites = eval(f.read()) # you can use sets rather than lists
font = pygame.font.SysFont("Microsoft YaHei UI", 20)
message = Message("", (width - 10, 10))
running = True
while running:
screen.fill((0, 0, 0))
clock.tick(30)
if not paused:
move(2)
for sprite, trail in map(lambda each_sprite: (each_sprite, each_sprite.trail), sprites):
if len(trail) < 2:
continue
trail = list(map(lambda point: ((point[0] + rel[0]) * scale, (point[1] + rel[1]) * scale), trail))
pygame.draw.lines(screen, sprite.color, False, trail, 2)
for sprite in sprites:
image = sprite.image
image = pygame.transform.scale(image, (sprite.radius * 2 * scale,) * 2)
screen.blit(image, ((sprite.rect.topleft[0] + rel[0]) * scale, (sprite.rect.topleft[1] + rel[1]) * scale))
try:
screen.blit(message.image, message.rect)
except pygame.error:
pass
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
drag = True
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
drag = False
elif event.button == 4:
zoom(1)
else:
zoom(-1)
elif event.type == pygame.MOUSEMOTION:
if drag:
change_view(*event.rel)
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
paused = not paused
elif event.mod & pygame.KMOD_CTRL and event.key == pygame.K_s:
root = tk.Tk()
root.withdraw()
filepath = fd.asksaveasfilename(
initialdir=os.path.dirname(__file__),
defaultextension=".png",
filetypes=((language["game"]["picture"] % "PNG", "*.png"), )
)
if filepath:
pygame.image.save(screen, filepath)
root.destroy()
elif event.key in (pygame.K_LEFT, pygame.K_a, pygame.K_KP_4):
change_view(movement, 0)
elif event.key in (pygame.K_RIGHT, pygame.K_d, pygame.K_KP_6):
change_view(-movement, 0)
elif event.key in (pygame.K_UP, pygame.K_w, pygame.K_KP_8):
change_view(0, movement)
elif event.key in (pygame.K_DOWN, pygame.K_s, pygame.K_KP_2):
change_view(0, -movement)
elif event.key in (pygame.K_EQUALS, pygame.K_KP_PLUS):
zoom(1)
elif event.key in (pygame.K_MINUS, pygame.K_KP_MINUS):
zoom(-1)
pygame.quit()
存储部分要把.simulation的文件中的方括号改为大括号。以下是完整文件:
code (fixed).zip
(128.92 KB, 下载次数: 2)
其实,问题出在这一行(第188行)上: screen.blit(image, sprite.rect)
这样并没有改变星球的显示位置,只改变了大小,将这一行改为这样: screen.blit(image, ((sprite.rect.topleft[0] + rel[0]) * scale, (sprite.rect.topleft[1] + rel[1]) * scale))
无关紧要的问题:
- 你可以将StarObject与StarSprite整合在一起,分开来似乎没有什么用;
- 你在进行缩小判断的时候只用了else语句,这样会匹配其他按键(例如鼠标中键和鼠标右键),你可以试试;
- 把大写的SCALE改为SCREEN_SIZE_SCALE语义会明确一点;
- 你可以将两个循环通过itertools.combination整合到一起(又不需要第三方库),一次性计算两个星球之间的关系,比用两个循环要快;
- set(集合)自带差集、并集和交集算法,可以使用set加快运行速度,减少代码量;
- 对于加速度、速度以及力这些矢量,你可以创建一个向量(Vector)对象来存储并更改他们,加强可读性;
- 你可以定时清理trail(星球轨迹),否则内存占用会一直增加,直到爆栈。
- 像你这样的文件,可以使用git作为版本管理工具(不想开源可以不用github),在每一个版本附上这个版本的说明,如果出了什么bug,可以回退版本查看问题出在哪里。
|
|