|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
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,可以回退版本查看问题出在哪里。
|
|